From 788bcc91ed65c454a312c712cb75cddb34f22ce3 Mon Sep 17 00:00:00 2001 From: beanbag44 <107891830+beanbag44@users.noreply.github.com> Date: Mon, 6 Jul 2026 11:25:41 +0100 Subject: [PATCH 01/43] rename material directory to inventory and rename MaterialContainer to Container --- .../command/commands/TransferCommand.kt | 2 +- .../com/lambda/config/blocks/EatConfig.kt | 2 +- .../lambda/config/blocks/InventoryConfig.kt | 24 +++++------ .../com/lambda/event/events/ContainerEvent.kt | 6 +-- .../simulation/checks/BreakSim.kt | 12 +++--- .../simulation/checks/InteractSim.kt | 10 ++--- .../simulation/context/BreakContext.kt | 2 +- .../simulation/result/results/BreakResult.kt | 4 +- .../result/results/GenericResult.kt | 4 +- .../interaction/handlers/ContainerHandler.kt | 34 ++++++++-------- .../interaction/handlers/GlideHandler.kt | 2 +- .../ContainerSelection.kt | 27 ++++++------- .../{material => inventory}/StackSelection.kt | 4 +- .../container/Container.kt} | 31 ++++++-------- .../container/ExternalContainer.kt | 2 +- .../container/containers/ArmorContainer.kt | 40 +++++++++++++++++++ .../container/containers/ChestContainer.kt | 8 ++-- .../container/containers/CreativeContainer.kt | 8 ++-- .../containers/EnderChestContainer.kt | 10 ++--- .../container/containers/HotbarContainer.kt | 6 +-- .../containers/InventoryContainer.kt | 6 +-- .../container/containers/MainHandContainer.kt | 6 +-- .../container/containers/OffHandContainer.kt | 6 +-- .../container/containers/PlayerContainer.kt | 40 +++++++++++++++++++ .../containers/ShulkerBoxContainer.kt | 10 ++--- .../container/containers/StashContainer.kt | 8 ++-- .../managers/breaking/BreakManager.kt | 4 +- .../lambda/module/modules/combat/AutoTotem.kt | 2 +- .../module/modules/combat/CrystalAura.kt | 6 +-- .../lambda/module/modules/combat/KillAura.kt | 2 +- .../module/modules/debug/ContainerTest.kt | 2 +- .../module/modules/movement/BetterFirework.kt | 4 +- .../elytrafly/modes/GrimControlElytraFly.kt | 2 +- .../lambda/module/modules/player/ToolSaver.kt | 2 +- .../module/modules/render/ContainerPreview.kt | 2 +- .../lambda/module/modules/world/AutoPortal.kt | 2 +- .../lambda/module/modules/world/Scaffold.kt | 4 +- .../lambda/module/modules/world/StashMover.kt | 2 +- .../lambda/task/tasks/AcquireMaterialTask.kt | 4 +- .../task/tasks/ContainerTransferTask.kt | 12 +++--- .../kotlin/com/lambda/task/tasks/EatTask.kt | 4 +- .../com/lambda/util/player/SlotUtils.kt | 32 +++++++++------ 42 files changed, 241 insertions(+), 159 deletions(-) rename src/main/kotlin/com/lambda/interaction/{material => inventory}/ContainerSelection.kt (70%) rename src/main/kotlin/com/lambda/interaction/{material => inventory}/StackSelection.kt (99%) rename src/main/kotlin/com/lambda/interaction/{material/container/MaterialContainer.kt => inventory/container/Container.kt} (88%) rename src/main/kotlin/com/lambda/interaction/{material => inventory}/container/ExternalContainer.kt (94%) create mode 100644 src/main/kotlin/com/lambda/interaction/inventory/container/containers/ArmorContainer.kt rename src/main/kotlin/com/lambda/interaction/{material => inventory}/container/containers/ChestContainer.kt (90%) rename src/main/kotlin/com/lambda/interaction/{material => inventory}/container/containers/CreativeContainer.kt (91%) rename src/main/kotlin/com/lambda/interaction/{material => inventory}/container/containers/EnderChestContainer.kt (86%) rename src/main/kotlin/com/lambda/interaction/{material => inventory}/container/containers/HotbarContainer.kt (89%) rename src/main/kotlin/com/lambda/interaction/{material => inventory}/container/containers/InventoryContainer.kt (87%) rename src/main/kotlin/com/lambda/interaction/{material => inventory}/container/containers/MainHandContainer.kt (89%) rename src/main/kotlin/com/lambda/interaction/{material => inventory}/container/containers/OffHandContainer.kt (89%) create mode 100644 src/main/kotlin/com/lambda/interaction/inventory/container/containers/PlayerContainer.kt rename src/main/kotlin/com/lambda/interaction/{material => inventory}/container/containers/ShulkerBoxContainer.kt (90%) rename src/main/kotlin/com/lambda/interaction/{material => inventory}/container/containers/StashContainer.kt (88%) diff --git a/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt b/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt index e37d1bdfc..b3c99c2e7 100644 --- a/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt +++ b/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt @@ -28,7 +28,7 @@ import com.lambda.brigadier.executeWithResult import com.lambda.brigadier.required import com.lambda.command.LambdaCommand import com.lambda.config.automation.AutomationConfig -import com.lambda.interaction.material.StackSelection.Companion.selectStack +import com.lambda.interaction.inventory.StackSelection.Companion.selectStack import com.lambda.interaction.handlers.ContainerHandler import com.lambda.interaction.handlers.ContainerHandler.findContainersWithMaterial import com.lambda.interaction.handlers.ContainerHandler.findContainersWithSpace diff --git a/src/main/kotlin/com/lambda/config/blocks/EatConfig.kt b/src/main/kotlin/com/lambda/config/blocks/EatConfig.kt index 1e0e5714b..0900a1e0f 100644 --- a/src/main/kotlin/com/lambda/config/blocks/EatConfig.kt +++ b/src/main/kotlin/com/lambda/config/blocks/EatConfig.kt @@ -19,7 +19,7 @@ package com.lambda.config.blocks import com.lambda.context.Automated import com.lambda.context.AutomatedSafeContext -import com.lambda.interaction.material.StackSelection.Companion.selectStack +import com.lambda.interaction.inventory.StackSelection.Companion.selectStack import com.lambda.threading.runSafe import com.lambda.util.Describable import com.lambda.util.NamedEnum diff --git a/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt b/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt index 6a0c26116..c60f8b4b8 100644 --- a/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt +++ b/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt @@ -19,9 +19,9 @@ package com.lambda.config.blocks import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent -import com.lambda.interaction.material.ContainerSelection -import com.lambda.interaction.material.StackSelection -import com.lambda.interaction.material.container.MaterialContainer +import com.lambda.interaction.inventory.ContainerSelection +import com.lambda.interaction.inventory.StackSelection +import com.lambda.interaction.inventory.container.Container import com.lambda.util.Describable import com.lambda.util.NamedEnum import net.minecraft.item.Item @@ -41,11 +41,11 @@ interface InventoryConfig { val containerSelection: ContainerSelection get() = ContainerSelection.selectContainer { val allowedContainers = buildSet { - addAll(MaterialContainer.Rank.entries) - if (!accessShulkerBoxes) remove(MaterialContainer.Rank.ShulkerBox) - if (!accessEnderChest) remove(MaterialContainer.Rank.EnderChest) - if (!accessChests) remove(MaterialContainer.Rank.Chest) - if (!accessStashes) remove(MaterialContainer.Rank.Stash) + addAll(Container.Rank.entries) + if (!accessShulkerBoxes) remove(Container.Rank.ShulkerBox) + if (!accessEnderChest) remove(Container.Rank.EnderChest) + if (!accessChests) remove(Container.Rank.Chest) + if (!accessStashes) remove(Container.Rank.Stash) } ofAnyType(*allowedContainers.toTypedArray()) } @@ -60,11 +60,11 @@ interface InventoryConfig { context(_: SafeContext) fun materialComparator(selection: StackSelection) = when (this) { - WithMaxItems -> compareBy { it.rank } + WithMaxItems -> compareBy { it.rank } .thenByDescending { it.materialAvailable(selection) } .thenBy { it.name } - WithMinItems -> compareBy { it.rank } + WithMinItems -> compareBy { it.rank } .thenBy { it.materialAvailable(selection) } .thenBy { it.name } } @@ -72,11 +72,11 @@ interface InventoryConfig { context(_: SafeContext) fun spaceComparator(selection: StackSelection) = when (this) { - WithMaxItems -> compareBy { it.rank } + WithMaxItems -> compareBy { it.rank } .thenByDescending { it.spaceAvailable(selection) } .thenBy { it.name } - WithMinItems -> compareBy { it.rank } + WithMinItems -> compareBy { it.rank } .thenBy { it.spaceAvailable(selection) } .thenBy { it.name } } diff --git a/src/main/kotlin/com/lambda/event/events/ContainerEvent.kt b/src/main/kotlin/com/lambda/event/events/ContainerEvent.kt index 2c0031cb1..f586dd52a 100644 --- a/src/main/kotlin/com/lambda/event/events/ContainerEvent.kt +++ b/src/main/kotlin/com/lambda/event/events/ContainerEvent.kt @@ -19,14 +19,14 @@ package com.lambda.event.events import com.lambda.event.callback.Cancellable import com.lambda.event.callback.ICancellable -import com.lambda.interaction.material.container.MaterialContainer +import com.lambda.interaction.inventory.container.Container import net.minecraft.screen.slot.Slot sealed class ContainerEvent { data class Transfer( val fromSlot: Slot, val toSlot: Slot, - val from: MaterialContainer, - val to: MaterialContainer + val from: Container, + val to: Container ) : ICancellable by Cancellable() } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt index a9fd15e65..afe212b7f 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt @@ -31,12 +31,12 @@ import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.managers.hotbar.HotbarManager import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest import com.lambda.interaction.managers.rotating.RotationManager -import com.lambda.interaction.material.ContainerSelection.Companion.selectContainer -import com.lambda.interaction.material.StackSelection -import com.lambda.interaction.material.StackSelection.Companion.EVERYTHING -import com.lambda.interaction.material.StackSelection.Companion.selectStack +import com.lambda.interaction.inventory.ContainerSelection.Companion.selectContainer +import com.lambda.interaction.inventory.StackSelection +import com.lambda.interaction.inventory.StackSelection.Companion.EVERYTHING +import com.lambda.interaction.inventory.StackSelection.Companion.selectStack import com.lambda.interaction.handlers.ContainerHandler.findContainersWithMaterial -import com.lambda.interaction.material.container.MaterialContainer +import com.lambda.interaction.inventory.container.Container import com.lambda.util.BlockUtils.blockState import com.lambda.util.BlockUtils.calcItemBlockBreakingDelta import com.lambda.util.BlockUtils.instantBreakable @@ -168,7 +168,7 @@ class BreakSim private constructor(simInfo: SimInfo) } val silentSwapSelection = selectContainer { - ofAnyType(MaterialContainer.Rank.Hotbar) + ofAnyType(Container.Rank.Hotbar) } val hotbarCandidates = stackSelection 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 3aa534c83..f7372a0be 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 @@ -32,10 +32,10 @@ import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotat import com.lambda.interaction.managers.rotating.Rotation import com.lambda.interaction.managers.rotating.Rotation.Companion.rotation import com.lambda.interaction.managers.rotating.RotationManager -import com.lambda.interaction.material.ContainerSelection.Companion.selectContainer -import com.lambda.interaction.material.StackSelection -import com.lambda.interaction.material.StackSelection.Companion.select -import com.lambda.interaction.material.container.MaterialContainer +import com.lambda.interaction.inventory.ContainerSelection.Companion.selectContainer +import com.lambda.interaction.inventory.StackSelection +import com.lambda.interaction.inventory.StackSelection.Companion.select +import com.lambda.interaction.inventory.container.Container import com.lambda.util.BlockUtils.blockState import com.lambda.util.EntityUtils.getPositionsWithinHitboxXZ import com.lambda.util.PlaceDirection @@ -213,7 +213,7 @@ class InteractSim private constructor(simInfo: InteractSimInfo) } val stackSelection = item?.select()?.apply { if (item == Items.AIR) count = 0 } ?: StackSelection.selectStack(0, sorter = compareByDescending { it.inventoryIndex == player.inventory.selectedSlot }) - val containerSelection = selectContainer { ofAnyType(MaterialContainer.Rank.Hotbar) } + val containerSelection = selectContainer { ofAnyType(Container.Rank.Hotbar) } val container = stackSelection.findContainersWithMaterial(containerSelection).firstOrNull() ?: run { result(GenericResult.WrongItemSelection(pos, stackSelection, player.mainHandStack)) return null diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/context/BreakContext.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/context/BreakContext.kt index 2222f6791..67ec97b81 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/context/BreakContext.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/context/BreakContext.kt @@ -20,7 +20,7 @@ package com.lambda.interaction.construction.simulation.context import com.lambda.context.Automated import com.lambda.graphics.mc.RenderBuilder import com.lambda.interaction.managers.rotating.RotationRequest -import com.lambda.interaction.material.StackSelection +import com.lambda.interaction.inventory.StackSelection import com.lambda.threading.runSafe import com.lambda.util.BlockUtils.emptyState import net.minecraft.block.BlockState diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt index 0de9e0844..6837ab8ea 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt @@ -32,9 +32,9 @@ import com.lambda.interaction.construction.simulation.result.Drawable import com.lambda.interaction.construction.simulation.result.Navigable import com.lambda.interaction.construction.simulation.result.Rank import com.lambda.interaction.construction.simulation.result.Resolvable -import com.lambda.interaction.material.StackSelection.Companion.selectStack +import com.lambda.interaction.inventory.StackSelection.Companion.selectStack import com.lambda.interaction.handlers.ContainerHandler.transferByTask -import com.lambda.interaction.material.container.containers.HotbarContainer +import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.task.Task import net.minecraft.block.BlockState import net.minecraft.item.Item diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/GenericResult.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/GenericResult.kt index eeabf4945..a9b550507 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/GenericResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/GenericResult.kt @@ -27,9 +27,9 @@ import com.lambda.interaction.construction.simulation.result.Drawable import com.lambda.interaction.construction.simulation.result.Navigable import com.lambda.interaction.construction.simulation.result.Rank import com.lambda.interaction.construction.simulation.result.Resolvable -import com.lambda.interaction.material.StackSelection +import com.lambda.interaction.inventory.StackSelection import com.lambda.interaction.handlers.ContainerHandler.transferByTask -import com.lambda.interaction.material.container.containers.HotbarContainer +import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.task.Task import net.minecraft.client.data.TextureMap.side import net.minecraft.item.ItemStack diff --git a/src/main/kotlin/com/lambda/interaction/handlers/ContainerHandler.kt b/src/main/kotlin/com/lambda/interaction/handlers/ContainerHandler.kt index e7a083287..733931f37 100644 --- a/src/main/kotlin/com/lambda/interaction/handlers/ContainerHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handlers/ContainerHandler.kt @@ -23,12 +23,12 @@ import com.lambda.core.Loadable import com.lambda.event.events.InventoryEvent import com.lambda.event.events.PlayerEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.material.ContainerSelection -import com.lambda.interaction.material.StackSelection -import com.lambda.interaction.material.StackSelection.Companion.select -import com.lambda.interaction.material.container.MaterialContainer -import com.lambda.interaction.material.container.containers.ChestContainer -import com.lambda.interaction.material.container.containers.EnderChestContainer +import com.lambda.interaction.inventory.ContainerSelection +import com.lambda.interaction.inventory.StackSelection +import com.lambda.interaction.inventory.StackSelection.Companion.select +import com.lambda.interaction.inventory.container.Container +import com.lambda.interaction.inventory.container.containers.ChestContainer +import com.lambda.interaction.inventory.container.containers.EnderChestContainer import com.lambda.util.BlockUtils.blockEntity import com.lambda.util.ReflectionUtils import com.lambda.util.extension.containerStacks @@ -42,11 +42,11 @@ import net.minecraft.screen.slot.Slot // ToDo: Make this a Configurable to save container caches. Should use a cached region based storage system. @Suppress("unused") object ContainerHandler : Loadable { - private val containers: List + private val containers: List get() = compileContainers + runtimeContainers - private val compileContainers = ReflectionUtils.getInstances() - private val runtimeContainers = mutableSetOf() + private val compileContainers = ReflectionUtils.getInstances() + private val runtimeContainers = mutableSetOf() var lastInteractedBlockEntity: BlockEntity? = null @@ -89,7 +89,7 @@ object ContainerHandler : Loadable { fun containers() = containers.flatMap { setOf(it) + it.shulkerContainer }.sorted() context(automatedSafeContext: AutomatedSafeContext) - fun StackSelection.transfer(destination: MaterialContainer) = + fun StackSelection.transfer(destination: Container) = with(automatedSafeContext) { findContainerWithMaterial( inventoryConfig.containerSelection @@ -98,25 +98,25 @@ object ContainerHandler : Loadable { } context(automatedSafeContext: AutomatedSafeContext) - fun StackSelection.transferByTask(destination: MaterialContainer) = + fun StackSelection.transferByTask(destination: Container) = with(automatedSafeContext) { findContainerWithMaterial()?.transferByTask(this@transferByTask, destination) } context(_: SafeContext) fun findContainer( - block: (MaterialContainer) -> Boolean, - ): MaterialContainer? = containers().find(block) + block: (Container) -> Boolean, + ): Container? = containers().find(block) context(automatedSafeContext: AutomatedSafeContext) fun StackSelection.findContainerWithMaterial( containerSelection: ContainerSelection = automatedSafeContext.inventoryConfig.containerSelection - ): MaterialContainer? = findContainersWithMaterial(containerSelection).firstOrNull() + ): Container? = findContainersWithMaterial(containerSelection).firstOrNull() context(automatedSafeContext: AutomatedSafeContext) fun StackSelection.findContainersWithMaterial( containerSelection: ContainerSelection = automatedSafeContext.inventoryConfig.containerSelection, - ): List = + ): List = containers() .filter { containerSelection.matches(it) } .filter { it.materialAvailable(this) >= count } @@ -125,12 +125,12 @@ object ContainerHandler : Loadable { context(automatedSafeContext: AutomatedSafeContext) fun StackSelection.findContainerWithSpace( containerSelection: ContainerSelection = automatedSafeContext.inventoryConfig.containerSelection - ): MaterialContainer? = findContainersWithSpace(containerSelection).firstOrNull() + ): Container? = findContainersWithSpace(containerSelection).firstOrNull() context(automatedSafeContext: AutomatedSafeContext) fun StackSelection.findContainersWithSpace( containerSelection: ContainerSelection = automatedSafeContext.inventoryConfig.containerSelection - ): List = + ): List = containers() .filter { containerSelection.matches(it) } .filter { it.spaceAvailable(this) >= count } diff --git a/src/main/kotlin/com/lambda/interaction/handlers/GlideHandler.kt b/src/main/kotlin/com/lambda/interaction/handlers/GlideHandler.kt index c519e2774..8f3df5c02 100644 --- a/src/main/kotlin/com/lambda/interaction/handlers/GlideHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handlers/GlideHandler.kt @@ -20,7 +20,7 @@ package com.lambda.interaction.handlers import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.material.StackSelection.Companion.selectStack +import com.lambda.interaction.inventory.StackSelection.Companion.selectStack import com.lambda.module.modules.combat.AutoArmor import com.lambda.module.modules.movement.elytrafly.ElytraFly import com.lambda.module.modules.movement.elytrafly.ElytraFly.mode diff --git a/src/main/kotlin/com/lambda/interaction/material/ContainerSelection.kt b/src/main/kotlin/com/lambda/interaction/inventory/ContainerSelection.kt similarity index 70% rename from src/main/kotlin/com/lambda/interaction/material/ContainerSelection.kt rename to src/main/kotlin/com/lambda/interaction/inventory/ContainerSelection.kt index fca5a4d20..5d0948fb0 100644 --- a/src/main/kotlin/com/lambda/interaction/material/ContainerSelection.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/ContainerSelection.kt @@ -15,23 +15,23 @@ * along with this program. If not, see . */ -package com.lambda.interaction.material +package com.lambda.interaction.inventory import com.lambda.context.SafeContext -import com.lambda.interaction.material.container.MaterialContainer +import com.lambda.interaction.inventory.container.Container /** * ContainerSelection is a class that holds a predicate for matching MaterialContainers. * It can be combined using "and", "or", etc. */ class ContainerSelection { - private var selector: (MaterialContainer) -> Boolean = { true } + private var selector: (Container) -> Boolean = { true } /** * Tests whether the provided container matches this selection. */ @ContainerSelectionDsl - fun matches(container: MaterialContainer): Boolean = selector(container) + fun matches(container: Container): Boolean = selector(container) /** * Returns a function that matches containers having at least one stack @@ -39,7 +39,7 @@ class ContainerSelection { */ @ContainerSelectionDsl context(_: SafeContext) - fun matches(stackSelection: StackSelection): (MaterialContainer) -> Boolean = + fun matches(stackSelection: StackSelection): (Container) -> Boolean = { container -> container.matchingSlots(stackSelection).isNotEmpty() } /** @@ -47,42 +47,42 @@ class ContainerSelection { * defined in the provided ContainerSelection. */ @ContainerSelectionDsl - fun matches(containerSelection: ContainerSelection): (MaterialContainer) -> Boolean = + fun matches(containerSelection: ContainerSelection): (Container) -> Boolean = { container -> containerSelection.matches(container) } /** * Returns a function that matches containers whose rank is any of the types provided. */ @ContainerSelectionDsl - fun ofAnyType(vararg types: MaterialContainer.Rank): (MaterialContainer) -> Boolean = + fun ofAnyType(vararg types: Container.Rank): (Container) -> Boolean = { container -> types.contains(container.rank) } /** * Returns a function that matches containers whose rank is not any of the types provided. */ @ContainerSelectionDsl - fun noneOfType(vararg types: MaterialContainer.Rank): (MaterialContainer) -> Boolean = + fun noneOfType(vararg types: Container.Rank): (Container) -> Boolean = { container -> !types.contains(container.rank) } /** * Returns a function that combines two container predicates using logical AND. */ @ContainerSelectionDsl - infix fun ((MaterialContainer) -> Boolean).and(other: (MaterialContainer) -> Boolean): (MaterialContainer) -> Boolean = + infix fun ((Container) -> Boolean).and(other: (Container) -> Boolean): (Container) -> Boolean = { container -> this(container) && other(container) } /** * Returns a function that combines two container predicates using logical OR. */ @ContainerSelectionDsl - infix fun ((MaterialContainer) -> Boolean).or(other: (MaterialContainer) -> Boolean): (MaterialContainer) -> Boolean = + infix fun ((Container) -> Boolean).or(other: (Container) -> Boolean): (Container) -> Boolean = { container -> this(container) || other(container) } /** * Returns a function that negates the current selection predicate. */ @ContainerSelectionDsl - fun ((MaterialContainer) -> Boolean).negate(): (MaterialContainer) -> Boolean = + fun ((Container) -> Boolean).negate(): (Container) -> Boolean = { container -> !this(container) } companion object { @@ -91,8 +91,7 @@ class ContainerSelection { @ContainerSelectionDsl fun selectContainer( - block: ContainerSelection.() -> (MaterialContainer) -> Boolean - ): ContainerSelection = - ContainerSelection().apply { selector = block() } + block: ContainerSelection.() -> (Container) -> Boolean + ): ContainerSelection = ContainerSelection().apply { selector = block() } } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/material/StackSelection.kt b/src/main/kotlin/com/lambda/interaction/inventory/StackSelection.kt similarity index 99% rename from src/main/kotlin/com/lambda/interaction/material/StackSelection.kt rename to src/main/kotlin/com/lambda/interaction/inventory/StackSelection.kt index 96a8b96ad..daa5231cb 100644 --- a/src/main/kotlin/com/lambda/interaction/material/StackSelection.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/StackSelection.kt @@ -15,9 +15,9 @@ * along with this program. If not, see . */ -package com.lambda.interaction.material +package com.lambda.interaction.inventory -import com.lambda.interaction.material.StackSelection.Companion.StackSelectionDsl +import com.lambda.interaction.inventory.StackSelection.Companion.StackSelectionDsl import com.lambda.util.EnchantmentUtils.getEnchantment import com.lambda.util.item.ItemStackUtils.shulkerBoxContents import com.lambda.util.item.ItemUtils diff --git a/src/main/kotlin/com/lambda/interaction/material/container/MaterialContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/Container.kt similarity index 88% rename from src/main/kotlin/com/lambda/interaction/material/container/MaterialContainer.kt rename to src/main/kotlin/com/lambda/interaction/inventory/container/Container.kt index 895bdcbdb..9e514a27d 100644 --- a/src/main/kotlin/com/lambda/interaction/material/container/MaterialContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/Container.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.material.container +package com.lambda.interaction.inventory.container import com.lambda.context.Automated import com.lambda.context.AutomatedSafeContext @@ -24,9 +24,8 @@ import com.lambda.event.EventFlow.post import com.lambda.event.events.ContainerEvent import com.lambda.interaction.managers.inventory.InventoryRequest import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest -import com.lambda.interaction.material.StackSelection -import com.lambda.interaction.material.container.containers.ShulkerBoxContainer -import com.lambda.task.Task +import com.lambda.interaction.inventory.StackSelection +import com.lambda.interaction.inventory.container.containers.ShulkerBoxContainer import com.lambda.task.tasks.ContainerTransferTask import com.lambda.util.Nameable import com.lambda.util.item.ItemStackUtils.count @@ -47,9 +46,9 @@ import net.minecraft.screen.slot.Slot import net.minecraft.text.Text // ToDo: Make jsonable to persistently store them -abstract class MaterialContainer( +abstract class Container( val rank: Rank -) : Nameable, Comparable { +) : Nameable, Comparable { context(_: SafeContext) abstract val slots: List abstract var stacks: List @@ -106,7 +105,7 @@ abstract class MaterialContainer( }.map { slot -> ShulkerBoxContainer( slot.stack.shulkerBoxContents, - containedIn = this@MaterialContainer, + containedIn = this@Container, shulkerSlot = slot ) }.toSet() @@ -115,12 +114,6 @@ abstract class MaterialContainer( this.stacks = slots } - class FailureTask(override val name: String) : Task() { - override fun SafeContext.onStart() { - failure(name) - } - } - context(_: SafeContext) open fun InventoryRequest.InvRequestBuilder.transfer(fromHere: Slot, toSlot: Slot) { if (fromHere.stack.isEmpty) pickupAndPlace(toSlot.id, fromHere.id) @@ -131,11 +124,11 @@ abstract class MaterialContainer( } context(automatedSafeContext: AutomatedSafeContext) - fun transfer(stackSelection: StackSelection, destination: MaterialContainer): Boolean = + fun transfer(stackSelection: StackSelection, destination: Container): Boolean = with(automatedSafeContext) { val fromSlot = getSlot(stackSelection) ?: return false val toSlot = destination.getReplaceableSlot() ?: return false - val transferEvent = ContainerEvent.Transfer(fromSlot, toSlot, this@MaterialContainer, destination) + val transferEvent = ContainerEvent.Transfer(fromSlot, toSlot, this@Container, destination) if (transferEvent.post().isCanceled()) return false return inventoryRequest { if (swapMethodPriority > destination.swapMethodPriority) transfer(fromSlot, toSlot) @@ -144,7 +137,7 @@ abstract class MaterialContainer( } context(automatedSafeContext: AutomatedSafeContext) - fun transferByTask(stackSelection: StackSelection, destination: MaterialContainer, failIfNoMaterial: Boolean = false) = + fun transferByTask(stackSelection: StackSelection, destination: Container, failIfNoMaterial: Boolean = false) = ContainerTransferTask(this, destination, stackSelection, automatedSafeContext, failIfNoMaterial) protected fun InventoryRequest.InvRequestBuilder.pickupAndPlace(fromId: Int, toId: Int) { @@ -180,6 +173,8 @@ abstract class MaterialContainer( OffHand, Hotbar, Inventory, + Armor, + Player, Creative, ShulkerBox, EnderChest, @@ -187,8 +182,8 @@ abstract class MaterialContainer( Stash } - override fun compareTo(other: MaterialContainer) = - compareBy { + override fun compareTo(other: Container) = + compareBy { it.rank }.compare(this, other) } diff --git a/src/main/kotlin/com/lambda/interaction/material/container/ExternalContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/ExternalContainer.kt similarity index 94% rename from src/main/kotlin/com/lambda/interaction/material/container/ExternalContainer.kt rename to src/main/kotlin/com/lambda/interaction/inventory/container/ExternalContainer.kt index 291cbbaa2..9d0bf3f14 100644 --- a/src/main/kotlin/com/lambda/interaction/material/container/ExternalContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/ExternalContainer.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.material.container +package com.lambda.interaction.inventory.container import com.lambda.context.AutomatedSafeContext import com.lambda.task.Task diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/ArmorContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/ArmorContainer.kt new file mode 100644 index 000000000..061298208 --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/ArmorContainer.kt @@ -0,0 +1,40 @@ +/* + * 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.inventory.container.containers + +import com.lambda.Lambda.mc +import com.lambda.context.SafeContext +import com.lambda.interaction.inventory.container.Container +import com.lambda.util.player.SlotUtils.armorSlots +import com.lambda.util.player.SlotUtils.armorStacks +import com.lambda.util.text.buildText +import com.lambda.util.text.literal +import net.minecraft.item.ItemStack +import net.minecraft.screen.slot.Slot + +@Suppress("unused") +object ArmorContainer : Container(Rank.Armor) { + context(safeContext: SafeContext) + override val slots: List + get() = safeContext.player.armorSlots + override var stacks: List + get() = mc.player?.armorStacks ?: emptyList() + set(_) {} + + override val description = buildText { literal("Armor") } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/material/container/containers/ChestContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/ChestContainer.kt similarity index 90% rename from src/main/kotlin/com/lambda/interaction/material/container/containers/ChestContainer.kt rename to src/main/kotlin/com/lambda/interaction/inventory/container/containers/ChestContainer.kt index f5c2a670e..cbbf7c616 100644 --- a/src/main/kotlin/com/lambda/interaction/material/container/containers/ChestContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/ChestContainer.kt @@ -15,13 +15,13 @@ * along with this program. If not, see . */ -package com.lambda.interaction.material.container.containers +package com.lambda.interaction.inventory.container.containers import com.lambda.context.AutomatedSafeContext import com.lambda.context.SafeContext import com.lambda.interaction.handlers.ContainerHandler -import com.lambda.interaction.material.container.ExternalContainer -import com.lambda.interaction.material.container.MaterialContainer +import com.lambda.interaction.inventory.container.ExternalContainer +import com.lambda.interaction.inventory.container.Container import com.lambda.task.Task import com.lambda.task.TaskGenerator import com.lambda.task.tasks.OpenContainerTask @@ -38,7 +38,7 @@ data class ChestContainer( override var stacks: List, val blockPos: BlockPos, val containedInStash: StashContainer? = null -) : MaterialContainer(Rank.Chest), ExternalContainer { +) : Container(Rank.Chest), ExternalContainer { context(safeContext: SafeContext) override val slots get(): List = diff --git a/src/main/kotlin/com/lambda/interaction/material/container/containers/CreativeContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/CreativeContainer.kt similarity index 91% rename from src/main/kotlin/com/lambda/interaction/material/container/containers/CreativeContainer.kt rename to src/main/kotlin/com/lambda/interaction/inventory/container/containers/CreativeContainer.kt index 5845263a1..3c00c1a6a 100644 --- a/src/main/kotlin/com/lambda/interaction/material/container/containers/CreativeContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/CreativeContainer.kt @@ -15,12 +15,12 @@ * along with this program. If not, see . */ -package com.lambda.interaction.material.container.containers +package com.lambda.interaction.inventory.container.containers import com.lambda.context.SafeContext import com.lambda.interaction.managers.inventory.InventoryRequest -import com.lambda.interaction.material.StackSelection -import com.lambda.interaction.material.container.MaterialContainer +import com.lambda.interaction.inventory.StackSelection +import com.lambda.interaction.inventory.container.Container import com.lambda.util.text.buildText import com.lambda.util.text.literal import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen @@ -30,7 +30,7 @@ import net.minecraft.item.ItemStack import net.minecraft.screen.PlayerScreenHandler import net.minecraft.screen.slot.Slot -data object CreativeContainer : MaterialContainer(Rank.Creative) { +data object CreativeContainer : Container(Rank.Creative) { context(_: SafeContext) override val slots: List get() = emptyList() diff --git a/src/main/kotlin/com/lambda/interaction/material/container/containers/EnderChestContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/EnderChestContainer.kt similarity index 86% rename from src/main/kotlin/com/lambda/interaction/material/container/containers/EnderChestContainer.kt rename to src/main/kotlin/com/lambda/interaction/inventory/container/containers/EnderChestContainer.kt index a20fbfb83..a7b6993fa 100644 --- a/src/main/kotlin/com/lambda/interaction/material/container/containers/EnderChestContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/EnderChestContainer.kt @@ -15,15 +15,15 @@ * along with this program. If not, see . */ -package com.lambda.interaction.material.container.containers +package com.lambda.interaction.inventory.container.containers import com.lambda.context.AutomatedSafeContext import com.lambda.context.SafeContext -import com.lambda.interaction.material.StackSelection.Companion.select +import com.lambda.interaction.inventory.StackSelection.Companion.select import com.lambda.interaction.handlers.ContainerHandler import com.lambda.interaction.handlers.ContainerHandler.findSlotsWithMaterial -import com.lambda.interaction.material.container.ExternalContainer -import com.lambda.interaction.material.container.MaterialContainer +import com.lambda.interaction.inventory.container.ExternalContainer +import com.lambda.interaction.inventory.container.Container import com.lambda.task.TaskGenerator import com.lambda.task.tasks.BuildTask.Companion.breakAndCollectBlock import com.lambda.task.tasks.OpenContainerTask @@ -35,7 +35,7 @@ import net.minecraft.block.entity.EnderChestBlockEntity import net.minecraft.item.ItemStack import net.minecraft.item.Items -object EnderChestContainer : MaterialContainer(Rank.EnderChest), ExternalContainer { +object EnderChestContainer : Container(Rank.EnderChest), ExternalContainer { context(safeContext: SafeContext) override val slots get() = diff --git a/src/main/kotlin/com/lambda/interaction/material/container/containers/HotbarContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/HotbarContainer.kt similarity index 89% rename from src/main/kotlin/com/lambda/interaction/material/container/containers/HotbarContainer.kt rename to src/main/kotlin/com/lambda/interaction/inventory/container/containers/HotbarContainer.kt index f2dc5d3f4..1e62c37aa 100644 --- a/src/main/kotlin/com/lambda/interaction/material/container/containers/HotbarContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/HotbarContainer.kt @@ -15,19 +15,19 @@ * along with this program. If not, see . */ -package com.lambda.interaction.material.container.containers +package com.lambda.interaction.inventory.container.containers import com.lambda.Lambda.mc import com.lambda.context.SafeContext import com.lambda.interaction.managers.inventory.InventoryRequest -import com.lambda.interaction.material.container.MaterialContainer +import com.lambda.interaction.inventory.container.Container import com.lambda.util.player.SlotUtils.hotbarSlots import com.lambda.util.player.SlotUtils.hotbarStacks import com.lambda.util.text.buildText import com.lambda.util.text.literal import net.minecraft.screen.slot.Slot -object HotbarContainer : MaterialContainer(Rank.Hotbar) { +object HotbarContainer : Container(Rank.Hotbar) { context(safeContext: SafeContext) override val slots: List get() = safeContext.player.hotbarSlots diff --git a/src/main/kotlin/com/lambda/interaction/material/container/containers/InventoryContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/InventoryContainer.kt similarity index 87% rename from src/main/kotlin/com/lambda/interaction/material/container/containers/InventoryContainer.kt rename to src/main/kotlin/com/lambda/interaction/inventory/container/containers/InventoryContainer.kt index 10867bdb2..7cc269548 100644 --- a/src/main/kotlin/com/lambda/interaction/material/container/containers/InventoryContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/InventoryContainer.kt @@ -15,11 +15,11 @@ * along with this program. If not, see . */ -package com.lambda.interaction.material.container.containers +package com.lambda.interaction.inventory.container.containers import com.lambda.Lambda.mc import com.lambda.context.SafeContext -import com.lambda.interaction.material.container.MaterialContainer +import com.lambda.interaction.inventory.container.Container import com.lambda.util.player.SlotUtils.inventorySlots import com.lambda.util.player.SlotUtils.inventoryStacks import com.lambda.util.text.buildText @@ -27,7 +27,7 @@ import com.lambda.util.text.literal import net.minecraft.item.ItemStack import net.minecraft.screen.slot.Slot -object InventoryContainer : MaterialContainer(Rank.Inventory) { +object InventoryContainer : Container(Rank.Inventory) { context(safeContext: SafeContext) override val slots: List get() = safeContext.player.inventorySlots diff --git a/src/main/kotlin/com/lambda/interaction/material/container/containers/MainHandContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/MainHandContainer.kt similarity index 89% rename from src/main/kotlin/com/lambda/interaction/material/container/containers/MainHandContainer.kt rename to src/main/kotlin/com/lambda/interaction/inventory/container/containers/MainHandContainer.kt index 88d936f34..018859489 100644 --- a/src/main/kotlin/com/lambda/interaction/material/container/containers/MainHandContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/MainHandContainer.kt @@ -15,19 +15,19 @@ * along with this program. If not, see . */ -package com.lambda.interaction.material.container.containers +package com.lambda.interaction.inventory.container.containers import com.lambda.Lambda.mc import com.lambda.context.SafeContext import com.lambda.interaction.managers.inventory.InventoryRequest -import com.lambda.interaction.material.container.MaterialContainer +import com.lambda.interaction.inventory.container.Container import com.lambda.util.player.SlotUtils.mainHandSlots import com.lambda.util.text.buildText import com.lambda.util.text.literal import net.minecraft.item.ItemStack import net.minecraft.screen.slot.Slot -object MainHandContainer : MaterialContainer(Rank.MainHand) { +object MainHandContainer : Container(Rank.MainHand) { context(safeContext: SafeContext) override val slots: List get() = safeContext.player.mainHandSlots diff --git a/src/main/kotlin/com/lambda/interaction/material/container/containers/OffHandContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/OffHandContainer.kt similarity index 89% rename from src/main/kotlin/com/lambda/interaction/material/container/containers/OffHandContainer.kt rename to src/main/kotlin/com/lambda/interaction/inventory/container/containers/OffHandContainer.kt index 2ae235e42..f48df2fd4 100644 --- a/src/main/kotlin/com/lambda/interaction/material/container/containers/OffHandContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/OffHandContainer.kt @@ -15,19 +15,19 @@ * along with this program. If not, see . */ -package com.lambda.interaction.material.container.containers +package com.lambda.interaction.inventory.container.containers import com.lambda.Lambda.mc import com.lambda.context.SafeContext import com.lambda.interaction.managers.inventory.InventoryRequest -import com.lambda.interaction.material.container.MaterialContainer +import com.lambda.interaction.inventory.container.Container import com.lambda.util.player.SlotUtils.offHandSlots import com.lambda.util.text.buildText import com.lambda.util.text.literal import net.minecraft.item.ItemStack import net.minecraft.screen.slot.Slot -object OffHandContainer : MaterialContainer(Rank.OffHand) { +object OffHandContainer : Container(Rank.OffHand) { context(safeContext: SafeContext) override val slots: List get() = safeContext.player.offHandSlots diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/PlayerContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/PlayerContainer.kt new file mode 100644 index 000000000..1fbe889ec --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/PlayerContainer.kt @@ -0,0 +1,40 @@ +/* + * 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.inventory.container.containers + +import com.lambda.Lambda.mc +import com.lambda.context.SafeContext +import com.lambda.interaction.inventory.container.Container +import com.lambda.util.player.SlotUtils.allSlots +import com.lambda.util.player.SlotUtils.allStacks +import com.lambda.util.text.buildText +import com.lambda.util.text.literal +import net.minecraft.item.ItemStack +import net.minecraft.screen.slot.Slot + +@Suppress("unused") +object PlayerContainer : Container(Rank.Player) { + context(safeContext: SafeContext) + override val slots: List + get() = safeContext.player.allSlots + override var stacks: List + get() = mc.player?.allStacks ?: emptyList() + set(_) {} + + override val description = buildText { literal("Player") } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/material/container/containers/ShulkerBoxContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/ShulkerBoxContainer.kt similarity index 90% rename from src/main/kotlin/com/lambda/interaction/material/container/containers/ShulkerBoxContainer.kt rename to src/main/kotlin/com/lambda/interaction/inventory/container/containers/ShulkerBoxContainer.kt index f0ade4bb5..5a644647b 100644 --- a/src/main/kotlin/com/lambda/interaction/material/container/containers/ShulkerBoxContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/ShulkerBoxContainer.kt @@ -15,13 +15,13 @@ * along with this program. If not, see . */ -package com.lambda.interaction.material.container.containers +package com.lambda.interaction.inventory.container.containers import com.lambda.context.AutomatedSafeContext import com.lambda.context.SafeContext import com.lambda.interaction.handlers.ContainerHandler -import com.lambda.interaction.material.container.ExternalContainer -import com.lambda.interaction.material.container.MaterialContainer +import com.lambda.interaction.inventory.container.ExternalContainer +import com.lambda.interaction.inventory.container.Container import com.lambda.task.TaskGenerator import com.lambda.task.tasks.BuildTask.Companion.breakAndCollectBlock import com.lambda.task.tasks.OpenContainerTask @@ -38,9 +38,9 @@ import net.minecraft.util.math.BlockPos data class ShulkerBoxContainer( override var stacks: List, - val containedIn: MaterialContainer, + val containedIn: Container, val shulkerSlot: Slot, -) : MaterialContainer(Rank.ShulkerBox), ExternalContainer { +) : Container(Rank.ShulkerBox), ExternalContainer { context(safeContext: SafeContext) override val slots get(): List = diff --git a/src/main/kotlin/com/lambda/interaction/material/container/containers/StashContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/StashContainer.kt similarity index 88% rename from src/main/kotlin/com/lambda/interaction/material/container/containers/StashContainer.kt rename to src/main/kotlin/com/lambda/interaction/inventory/container/containers/StashContainer.kt index e84b3ce4d..6c23b7d5b 100644 --- a/src/main/kotlin/com/lambda/interaction/material/container/containers/StashContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/StashContainer.kt @@ -15,11 +15,11 @@ * along with this program. If not, see . */ -package com.lambda.interaction.material.container.containers +package com.lambda.interaction.inventory.container.containers import com.lambda.context.SafeContext -import com.lambda.interaction.material.StackSelection -import com.lambda.interaction.material.container.MaterialContainer +import com.lambda.interaction.inventory.StackSelection +import com.lambda.interaction.inventory.container.Container import com.lambda.util.math.roundedBlockPos import com.lambda.util.text.buildText import com.lambda.util.text.highlighted @@ -31,7 +31,7 @@ import net.minecraft.util.math.Box data class StashContainer( val chests: Set, val pos: Box, -) : MaterialContainer(Rank.Stash) { +) : Container(Rank.Stash) { context(_: SafeContext) override val slots: List get() = chests.flatMap { it.slots } diff --git a/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakManager.kt b/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakManager.kt index 79d8d30b2..551e48286 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakManager.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakManager.kt @@ -73,8 +73,8 @@ import com.lambda.interaction.managers.breaking.SwapInfo.Companion.getSwapInfo import com.lambda.interaction.managers.hotbar.HotbarRequest import com.lambda.interaction.managers.interacting.InteractManager import com.lambda.interaction.managers.rotating.RotationRequest -import com.lambda.interaction.material.StackSelection -import com.lambda.interaction.material.StackSelection.Companion.select +import com.lambda.interaction.inventory.StackSelection +import com.lambda.interaction.inventory.StackSelection.Companion.select import com.lambda.threading.runGameScheduled import com.lambda.threading.runSafe import com.lambda.threading.runSafeAutomated diff --git a/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt b/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt index f2ac4ff80..76708df58 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt @@ -25,7 +25,7 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.handlers.FriendHandler import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest -import com.lambda.interaction.material.StackSelection.Companion.select +import com.lambda.interaction.inventory.StackSelection.Companion.select import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.combat.CombatUtils.hasDeadlyCrystal diff --git a/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt b/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt index 20cf1d653..2dfdd7150 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt @@ -31,10 +31,10 @@ import com.lambda.interaction.managers.hotbar.HotbarRequest import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest import com.lambda.interaction.managers.rotating.Rotation.Companion.rotationTo import com.lambda.interaction.managers.rotating.RotationManager -import com.lambda.interaction.material.StackSelection.Companion.selectStack +import com.lambda.interaction.inventory.StackSelection.Companion.selectStack import com.lambda.interaction.handlers.ContainerHandler.transfer -import com.lambda.interaction.material.container.containers.HotbarContainer -import com.lambda.interaction.material.container.containers.OffHandContainer +import com.lambda.interaction.inventory.container.containers.HotbarContainer +import com.lambda.interaction.inventory.container.containers.OffHandContainer import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.threading.runSafe diff --git a/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt b/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt index 29c598b01..dc3f79d4a 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt @@ -30,7 +30,7 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.managers.hotbar.HotbarRequest import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest -import com.lambda.interaction.material.StackSelection.Companion.selectStack +import com.lambda.interaction.inventory.StackSelection.Companion.selectStack import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.threading.runSafeAutomated diff --git a/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt b/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt index fc9ceb73a..599e9dc87 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt @@ -19,7 +19,7 @@ package com.lambda.module.modules.debug import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.material.StackSelection.Companion.select +import com.lambda.interaction.inventory.StackSelection.Companion.select import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.task.RootTask.run diff --git a/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt b/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt index 316743d57..48bc00c39 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt @@ -31,8 +31,8 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.handlers.GlideHandler import com.lambda.interaction.managers.hotbar.HotbarRequest import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest -import com.lambda.interaction.material.StackSelection.Companion.select -import com.lambda.interaction.material.StackSelection.Companion.selectStack +import com.lambda.interaction.inventory.StackSelection.Companion.select +import com.lambda.interaction.inventory.StackSelection.Companion.selectStack import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.threading.runSafe diff --git a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/GrimControlElytraFly.kt b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/GrimControlElytraFly.kt index 95138afa0..d7620c087 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/GrimControlElytraFly.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/GrimControlElytraFly.kt @@ -25,7 +25,7 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest import com.lambda.interaction.managers.rotating.RotationManager -import com.lambda.interaction.material.StackSelection.Companion.select +import com.lambda.interaction.inventory.StackSelection.Companion.select import com.lambda.module.modules.movement.BetterFirework.startFirework import com.lambda.module.modules.movement.elytrafly.ElytraFly.FlyMode import com.lambda.module.modules.movement.elytrafly.ElytraFlyMode diff --git a/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt b/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt index e4c528946..160b949c8 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt @@ -24,7 +24,7 @@ import com.lambda.event.events.ContainerEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest -import com.lambda.interaction.material.container.containers.HotbarContainer +import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.EnchantmentUtils.forEachEnchantment diff --git a/src/main/kotlin/com/lambda/module/modules/render/ContainerPreview.kt b/src/main/kotlin/com/lambda/module/modules/render/ContainerPreview.kt index d8c79f5d4..a5fa8c7f5 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/ContainerPreview.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/ContainerPreview.kt @@ -21,7 +21,7 @@ import com.lambda.Lambda.mc import com.lambda.config.Tab import com.lambda.config.entries.Setting.Companion.onValueChange import com.lambda.config.settings.complex.Bind -import com.lambda.interaction.material.container.containers.EnderChestContainer +import com.lambda.interaction.inventory.container.containers.EnderChestContainer import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.threading.runSafe diff --git a/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt b/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt index 4cf4a1432..2d541772f 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt @@ -38,7 +38,7 @@ import com.lambda.interaction.handlers.BaritoneHandler 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.material.StackSelection.Companion.selectStack +import com.lambda.interaction.inventory.StackSelection.Companion.selectStack import com.lambda.module.Module import com.lambda.module.modules.world.AutoPortal.PosHandler.currAnchorPos import com.lambda.module.modules.world.AutoPortal.PosHandler.obiPositions 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 f14d5d125..32a761990 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/Scaffold.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/Scaffold.kt @@ -32,8 +32,8 @@ import com.lambda.interaction.construction.simulation.BuildSimulator.simulate import com.lambda.interaction.construction.simulation.context.BuildContext import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.managers.interacting.InteractRequest.Companion.interactRequest -import com.lambda.interaction.material.StackSelection.Companion.selectStack -import com.lambda.interaction.material.container.containers.HotbarContainer +import com.lambda.interaction.inventory.StackSelection.Companion.selectStack +import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.threading.runSafeAutomated 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 e7c2b2307..5ae411744 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt @@ -47,7 +47,7 @@ import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotat 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.interaction.inventory.container.containers.EnderChestContainer import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.task.RootTask.run diff --git a/src/main/kotlin/com/lambda/task/tasks/AcquireMaterialTask.kt b/src/main/kotlin/com/lambda/task/tasks/AcquireMaterialTask.kt index d3a8eeab4..72aec175b 100644 --- a/src/main/kotlin/com/lambda/task/tasks/AcquireMaterialTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/AcquireMaterialTask.kt @@ -19,10 +19,10 @@ package com.lambda.task.tasks import com.lambda.context.Automated import com.lambda.context.SafeContext -import com.lambda.interaction.material.StackSelection +import com.lambda.interaction.inventory.StackSelection import com.lambda.interaction.handlers.ContainerHandler import com.lambda.interaction.handlers.ContainerHandler.findContainerWithMaterial -import com.lambda.interaction.material.container.containers.HotbarContainer +import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.task.Task import com.lambda.threading.runSafeAutomated diff --git a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt index 4d92b7202..ee24da55f 100644 --- a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt @@ -23,16 +23,16 @@ import com.lambda.event.events.ContainerEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest -import com.lambda.interaction.material.StackSelection -import com.lambda.interaction.material.container.ExternalContainer -import com.lambda.interaction.material.container.MaterialContainer -import com.lambda.interaction.material.container.containers.InventoryContainer +import com.lambda.interaction.inventory.StackSelection +import com.lambda.interaction.inventory.container.ExternalContainer +import com.lambda.interaction.inventory.container.Container +import com.lambda.interaction.inventory.container.containers.InventoryContainer import com.lambda.task.Task import com.lambda.threading.runSafeAutomated class ContainerTransferTask( - private var fromContainer: MaterialContainer, - private val toContainer: MaterialContainer, + private var fromContainer: Container, + private val toContainer: Container, private val stackSelection: StackSelection, automated: Automated, private val failIfNoMaterial: Boolean = false diff --git a/src/main/kotlin/com/lambda/task/tasks/EatTask.kt b/src/main/kotlin/com/lambda/task/tasks/EatTask.kt index e67320e33..99c636fae 100644 --- a/src/main/kotlin/com/lambda/task/tasks/EatTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/EatTask.kt @@ -24,8 +24,8 @@ import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.managers.hotbar.HotbarRequest -import com.lambda.interaction.material.container.containers.HotbarContainer -import com.lambda.interaction.material.container.containers.InventoryContainer +import com.lambda.interaction.inventory.container.containers.HotbarContainer +import com.lambda.interaction.inventory.container.containers.InventoryContainer import com.lambda.task.Task import com.lambda.threading.runSafeAutomated import net.minecraft.item.ItemStack diff --git a/src/main/kotlin/com/lambda/util/player/SlotUtils.kt b/src/main/kotlin/com/lambda/util/player/SlotUtils.kt index d09c0ae32..81e89d0c2 100644 --- a/src/main/kotlin/com/lambda/util/player/SlotUtils.kt +++ b/src/main/kotlin/com/lambda/util/player/SlotUtils.kt @@ -29,22 +29,30 @@ import net.minecraft.screen.slot.SlotActionType object SlotUtils { val ClientPlayerEntity.allSlots get() = currentScreenHandler.slots.filter { it.inventory is PlayerInventory } val ClientPlayerEntity.armorSlots get() = allSlots.filterIsInstance() - val ClientPlayerEntity.inventorySlots: List get() = - if (currentScreenHandler is CreativeInventoryScreen.CreativeScreenHandler) emptyList() - else if (currentScreenHandler === playerScreenHandler) allSlots.subList(4, 31) - else allSlots.subList(0, 27) - val ClientPlayerEntity.hotbarSlots: List get() = - if (currentScreenHandler is CreativeInventoryScreen.CreativeScreenHandler) allSlots - else if (currentScreenHandler === playerScreenHandler) allSlots.subList(31, 40) - else allSlots.subList(27, 36) - val ClientPlayerEntity.hotbarAndInventorySlots: List get() = - if (currentScreenHandler is CreativeInventoryScreen.CreativeScreenHandler) allSlots - else if (currentScreenHandler === playerScreenHandler) allSlots.subList(4, 40) - else allSlots.subList(0, 36) + + val ClientPlayerEntity.inventorySlots: List + get() = + if (currentScreenHandler is CreativeInventoryScreen.CreativeScreenHandler) emptyList() + else if (currentScreenHandler === playerScreenHandler) allSlots.subList(4, 31) + else allSlots.subList(0, 27) + + val ClientPlayerEntity.hotbarSlots: List + get() = + if (currentScreenHandler is CreativeInventoryScreen.CreativeScreenHandler) allSlots + else if (currentScreenHandler === playerScreenHandler) allSlots.subList(31, 40) + else allSlots.subList(27, 36) + + val ClientPlayerEntity.hotbarAndInventorySlots: List + get() = + if (currentScreenHandler is CreativeInventoryScreen.CreativeScreenHandler) allSlots + else if (currentScreenHandler === playerScreenHandler) allSlots.subList(4, 40) + else allSlots.subList(0, 36) + val ClientPlayerEntity.mainHandSlots: List get() = listOf(hotbarSlots[inventory.selectedSlot]) val ClientPlayerEntity.offHandSlots: List get() = if (currentScreenHandler === playerScreenHandler) listOf(allSlots.last()) else emptyList() val ClientPlayerEntity.allStacks: List get() = hotbarAndInventoryStacks + equipmentStacks + val ClientPlayerEntity.armorStacks: List get() = inventory.equipment.map.filter { it.key.index in 1..4 }.map { it.value } val ClientPlayerEntity.equipmentStacks: List get() = inventory.equipment.map.map { it.value } val ClientPlayerEntity.hotbarStacks: List get() = inventory.mainStacks.slice(0..8) val ClientPlayerEntity.inventoryStacks: List get() = inventory.mainStacks.slice(9..35) From b961cf2a422c2bdd910de7c119e331aa733dc802 Mon Sep 17 00:00:00 2001 From: beanbag44 <107891830+beanbag44@users.noreply.github.com> Date: Thu, 9 Jul 2026 00:24:12 +0100 Subject: [PATCH 02/43] rework StackSelection. Uses builder dsl and the final product is immutable. --- .../command/commands/TransferCommand.kt | 2 +- .../com/lambda/config/blocks/EatConfig.kt | 28 +- src/main/kotlin/com/lambda/gui/MenuBar.kt | 2 +- .../simulation/checks/BreakSim.kt | 44 +- .../simulation/checks/InteractSim.kt | 15 +- .../simulation/context/BreakContext.kt | 2 +- .../simulation/result/results/BreakResult.kt | 6 +- .../result/results/GenericResult.kt | 4 +- .../result/results/InteractResult.kt | 2 +- .../simulation/result/results/PreSimResult.kt | 2 +- .../interaction/handlers/ContainerHandler.kt | 2 +- .../interaction/handlers/FriendHandler.kt | 2 +- .../interaction/handlers/GlideHandler.kt | 66 +-- .../handlers/breaking/BrokenBlockHandler.kt | 4 +- .../handlers/breaking/RebreakHandler.kt | 6 +- .../interaction/inventory/StackSelection.kt | 493 ++++++++---------- .../inventory/container/Container.kt | 11 +- .../container/containers/ArmorContainer.kt | 1 - .../container/containers/ChestContainer.kt | 2 +- .../container/containers/CreativeContainer.kt | 2 +- .../containers/EnderChestContainer.kt | 4 +- .../containers/HotbarAndInventoryContainer.kt | 39 ++ .../container/containers/HotbarContainer.kt | 2 +- .../container/containers/MainHandContainer.kt | 2 +- .../container/containers/OffHandContainer.kt | 2 +- .../containers/ShulkerBoxContainer.kt | 2 +- .../managers/breaking/BreakManager.kt | 23 +- .../managers/breaking/BreakRequest.kt | 138 ++--- .../managers/interacting/InteractManager.kt | 10 +- .../managers/interacting/InteractRequest.kt | 63 ++- .../managers/rotating/RotationRequest.kt | 101 ++-- .../kotlin/com/lambda/module/hud/Baritone.kt | 2 +- .../lambda/module/modules/combat/AutoArmor.kt | 14 +- .../lambda/module/modules/combat/AutoTotem.kt | 2 +- .../module/modules/combat/CrystalAura.kt | 17 +- .../lambda/module/modules/combat/KillAura.kt | 23 +- .../module/modules/combat/PlayerTrap.kt | 6 +- .../lambda/module/modules/combat/Surround.kt | 4 +- .../combat/autodisconnect/AutoDisconnect.kt | 5 +- .../module/modules/debug/ContainerTest.kt | 2 +- .../module/modules/debug/RotationTest.kt | 2 +- .../module/modules/movement/AutoSpiral.kt | 2 +- .../module/modules/movement/BetterFirework.kt | 22 +- .../modules/movement/ElytraAltitudeControl.kt | 2 +- .../lambda/module/modules/movement/Speed.kt | 2 +- .../movement/elytrafly/ElytraFlyMode.kt | 8 +- .../elytrafly/modes/BounceElytraFly.kt | 2 +- .../elytrafly/modes/GrimControlElytraFly.kt | 13 +- .../lambda/module/modules/player/AntiAim.kt | 2 +- .../module/modules/player/AutoElytraSwap.kt | 8 +- .../lambda/module/modules/player/FastBreak.kt | 2 +- .../module/modules/player/InventoryMove.kt | 2 +- .../module/modules/player/PacketMine.kt | 2 +- .../lambda/module/modules/player/Replay.kt | 4 +- .../module/modules/player/RotationLock.kt | 2 +- .../module/modules/player/StackReplenish.kt | 8 +- .../lambda/module/modules/player/ToolSaver.kt | 9 +- .../module/modules/render/ContainerPreview.kt | 8 +- .../lambda/module/modules/render/Freecam.kt | 2 +- .../lambda/module/modules/render/Nametags.kt | 2 +- .../lambda/module/modules/render/Tracers.kt | 2 +- .../lambda/module/modules/world/AirPlace.kt | 2 +- .../lambda/module/modules/world/AutoPortal.kt | 14 +- .../module/modules/world/AutoVillagerCycle.kt | 2 +- .../module/modules/world/HighwayTools.kt | 2 +- .../module/modules/world/MapDownloader.kt | 4 +- .../lambda/module/modules/world/Scaffold.kt | 8 +- .../lambda/module/modules/world/StashMover.kt | 46 +- .../lambda/task/tasks/AcquireMaterialTask.kt | 2 +- .../kotlin/com/lambda/task/tasks/BuildTask.kt | 10 +- .../task/tasks/ContainerTransferTask.kt | 4 +- .../kotlin/com/lambda/task/tasks/EatTask.kt | 2 +- .../lambda/task/tasks/OpenContainerTask.kt | 2 +- .../com/lambda/util/item/ItemStackUtils.kt | 4 +- .../kotlin/com/lambda/util/item/ItemUtils.kt | 4 +- 75 files changed, 681 insertions(+), 688 deletions(-) create mode 100644 src/main/kotlin/com/lambda/interaction/inventory/container/containers/HotbarAndInventoryContainer.kt diff --git a/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt b/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt index b3c99c2e7..6027680c4 100644 --- a/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt +++ b/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt @@ -28,10 +28,10 @@ import com.lambda.brigadier.executeWithResult import com.lambda.brigadier.required import com.lambda.command.LambdaCommand import com.lambda.config.automation.AutomationConfig -import com.lambda.interaction.inventory.StackSelection.Companion.selectStack import com.lambda.interaction.handlers.ContainerHandler import com.lambda.interaction.handlers.ContainerHandler.findContainersWithMaterial import com.lambda.interaction.handlers.ContainerHandler.findContainersWithSpace +import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack import com.lambda.task.RootTask import com.lambda.task.Task import com.lambda.threading.runSafeAutomated diff --git a/src/main/kotlin/com/lambda/config/blocks/EatConfig.kt b/src/main/kotlin/com/lambda/config/blocks/EatConfig.kt index 0900a1e0f..10af94609 100644 --- a/src/main/kotlin/com/lambda/config/blocks/EatConfig.kt +++ b/src/main/kotlin/com/lambda/config/blocks/EatConfig.kt @@ -19,7 +19,8 @@ package com.lambda.config.blocks import com.lambda.context.Automated import com.lambda.context.AutomatedSafeContext -import com.lambda.interaction.inventory.StackSelection.Companion.selectStack +import com.lambda.interaction.inventory.StackAndSlot +import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack import com.lambda.threading.runSafe import com.lambda.util.Describable import com.lambda.util.NamedEnum @@ -55,17 +56,17 @@ interface EatConfig { @Suppress("unused") enum class SelectionPriority( - val comparator: Comparator, + val comparator: Comparator>, override val displayName: String, override val description: String ): NamedEnum, Describable { LeastNutritious( - compareBy { it.item.nutrition }, + compareBy { it.stack.item.nutrition }, "Least Nutritious", "Eats food items with the least nutritional value." ), MostNutritious( - compareByDescending { it.item.nutrition }, + compareByDescending { it.stack.item.nutrition }, "Most Nutritious", "Eats food items with the most nutritional value." ) @@ -94,14 +95,17 @@ interface EatConfig { } ?: false context(c: Automated) - fun selector() = selectStack(sorter = c.eatConfig.selectionPriority.comparator) { - when(this@Reason) { - None -> any() - Hunger -> isOneOfItems(c.eatConfig.nutritiousFood) - Damage -> isOneOfItems(c.eatConfig.regenerationFood) - Fire -> isOneOfItems(c.eatConfig.resistanceFood) - } and if (c.eatConfig.ignoreBadFood) isNoneOfItems(c.eatConfig.badFood) else any() - } + fun selector() = + selectStack { + when(this@Reason) { + Hunger -> isOneOfItems(c.eatConfig.nutritiousFood) + Damage -> isOneOfItems(c.eatConfig.regenerationFood) + Fire -> isOneOfItems(c.eatConfig.resistanceFood) + else -> {} + } + if (c.eatConfig.ignoreBadFood) isNoneOfItems(c.eatConfig.badFood) + sortedWith(c.eatConfig.selectionPriority.comparator) + } } companion object { diff --git a/src/main/kotlin/com/lambda/gui/MenuBar.kt b/src/main/kotlin/com/lambda/gui/MenuBar.kt index 4dc97c8d1..2b205382b 100644 --- a/src/main/kotlin/com/lambda/gui/MenuBar.kt +++ b/src/main/kotlin/com/lambda/gui/MenuBar.kt @@ -39,8 +39,8 @@ import com.lambda.imgui.ImGui import com.lambda.imgui.ImGui.closeCurrentPopup import com.lambda.imgui.flag.ImGuiCol import com.lambda.imgui.flag.ImGuiStyleVar -import com.lambda.imgui.type.ImBoolean import com.lambda.imgui.flag.ImGuiWindowFlags +import com.lambda.imgui.type.ImBoolean import com.lambda.interaction.handlers.BaritoneHandler import com.lambda.module.ModuleRegistry import com.lambda.module.ModuleRegistry.moduleNameMap diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt index afe212b7f..ffea00e15 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt @@ -28,15 +28,15 @@ import com.lambda.interaction.construction.simulation.result.BuildResult import com.lambda.interaction.construction.simulation.result.results.BreakResult import com.lambda.interaction.construction.simulation.result.results.GenericResult import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.managers.hotbar.HotbarManager -import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest -import com.lambda.interaction.managers.rotating.RotationManager +import com.lambda.interaction.handlers.ContainerHandler.findContainersWithMaterial import com.lambda.interaction.inventory.ContainerSelection.Companion.selectContainer +import com.lambda.interaction.inventory.StackAndSlot import com.lambda.interaction.inventory.StackSelection -import com.lambda.interaction.inventory.StackSelection.Companion.EVERYTHING -import com.lambda.interaction.inventory.StackSelection.Companion.selectStack -import com.lambda.interaction.handlers.ContainerHandler.findContainersWithMaterial +import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack import com.lambda.interaction.inventory.container.Container +import com.lambda.interaction.managers.hotbar.HotbarManager +import com.lambda.interaction.managers.rotating.RotationManager +import com.lambda.interaction.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.util.BlockUtils.blockState import com.lambda.util.BlockUtils.calcItemBlockBreakingDelta import com.lambda.util.BlockUtils.instantBreakable @@ -145,26 +145,20 @@ class BreakSim private constructor(simInfo: SimInfo) private fun AutomatedSafeContext.getSwapStack(): Pair? { // Stack size 0 to account for attacking with an empty hand. Empty slots have stack size 0 - val stackSelection = selectStack( - count = 0, - sorter = compareByDescending { - it.canBreak(CachedBlockPosition(world, pos, false)) - }.thenByDescending { - state.calcItemBlockBreakingDelta(pos, it) - }.thenByDescending { - it.inventoryIndex == HotbarManager.serverSlot - } - ) { - EVERYTHING - .andIf(breakConfig.efficientOnly) { - isEfficientForBreaking(state) - }.andIf(breakConfig.suitableToolsOnly) { - isSuitableForBreaking(state) - }.andIf(breakConfig.forceSilkTouch) { - hasEnchantment(Enchantments.SILK_TOUCH) - }.andIf(breakConfig.forceFortunePickaxe) { - hasEnchantment(Enchantments.FORTUNE) + val stackSelection = selectStack { + if (breakConfig.efficientOnly) isEfficientForBreaking(state) + if(breakConfig.suitableToolsOnly) isSuitableForBreaking(state) + if (breakConfig.forceSilkTouch) hasEnchantment(Enchantments.SILK_TOUCH) + if (breakConfig.forceFortunePickaxe) hasEnchantment(Enchantments.FORTUNE) + sortedWith { + compareByDescending> { + it.stack.canBreak(CachedBlockPosition(world, pos, false)) + }.thenByDescending { + state.calcItemBlockBreakingDelta(pos, it.stack) + }.thenByDescending { + it.stack.inventoryIndex == HotbarManager.serverSlot } + } } val silentSwapSelection = selectContainer { 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 f7372a0be..f5d4b848e 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 @@ -28,14 +28,14 @@ import com.lambda.interaction.construction.simulation.result.results.GenericResu import com.lambda.interaction.construction.simulation.result.results.InteractResult import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.handlers.ContainerHandler.findContainersWithMaterial -import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest +import com.lambda.interaction.inventory.ContainerSelection.Companion.selectContainer +import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.select +import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack +import com.lambda.interaction.inventory.container.Container import com.lambda.interaction.managers.rotating.Rotation import com.lambda.interaction.managers.rotating.Rotation.Companion.rotation import com.lambda.interaction.managers.rotating.RotationManager -import com.lambda.interaction.inventory.ContainerSelection.Companion.selectContainer -import com.lambda.interaction.inventory.StackSelection -import com.lambda.interaction.inventory.StackSelection.Companion.select -import com.lambda.interaction.inventory.container.Container +import com.lambda.interaction.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.util.BlockUtils.blockState import com.lambda.util.EntityUtils.getPositionsWithinHitboxXZ import com.lambda.util.PlaceDirection @@ -60,7 +60,6 @@ import net.minecraft.entity.Entity import net.minecraft.item.BlockItem import net.minecraft.item.Item import net.minecraft.item.ItemPlacementContext -import net.minecraft.item.Items import net.minecraft.screen.slot.Slot import net.minecraft.state.property.Properties import net.minecraft.util.Hand @@ -211,8 +210,8 @@ class InteractSim private constructor(simInfo: InteractSimInfo) supervisorScope.cancel() return null } - val stackSelection = item?.select()?.apply { if (item == Items.AIR) count = 0 } - ?: StackSelection.selectStack(0, sorter = compareByDescending { it.inventoryIndex == player.inventory.selectedSlot }) + val stackSelection = item?.select() + ?: selectStack { sortedWith { compareByDescending { it.stack.inventoryIndex == player.inventory.selectedSlot } } } val containerSelection = selectContainer { ofAnyType(Container.Rank.Hotbar) } val container = stackSelection.findContainersWithMaterial(containerSelection).firstOrNull() ?: run { result(GenericResult.WrongItemSelection(pos, stackSelection, player.mainHandStack)) diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/context/BreakContext.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/context/BreakContext.kt index 67ec97b81..5fbdb6fa7 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/context/BreakContext.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/context/BreakContext.kt @@ -19,8 +19,8 @@ package com.lambda.interaction.construction.simulation.context import com.lambda.context.Automated import com.lambda.graphics.mc.RenderBuilder -import com.lambda.interaction.managers.rotating.RotationRequest import com.lambda.interaction.inventory.StackSelection +import com.lambda.interaction.managers.rotating.RotationRequest import com.lambda.threading.runSafe import com.lambda.util.BlockUtils.emptyState import net.minecraft.block.BlockState diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt index 6837ab8ea..1ac15b8ed 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt @@ -22,7 +22,6 @@ import baritone.api.pathing.goals.GoalInverted import com.lambda.context.AutomatedSafeContext import com.lambda.graphics.mc.RenderBuilder import com.lambda.graphics.util.DirectionMask.mask -import com.lambda.interaction.handlers.BaritoneHandler import com.lambda.interaction.construction.simulation.context.BreakContext import com.lambda.interaction.construction.simulation.result.BuildResult import com.lambda.interaction.construction.simulation.result.ComparableResult @@ -32,8 +31,9 @@ import com.lambda.interaction.construction.simulation.result.Drawable import com.lambda.interaction.construction.simulation.result.Navigable import com.lambda.interaction.construction.simulation.result.Rank import com.lambda.interaction.construction.simulation.result.Resolvable -import com.lambda.interaction.inventory.StackSelection.Companion.selectStack +import com.lambda.interaction.handlers.BaritoneHandler import com.lambda.interaction.handlers.ContainerHandler.transferByTask +import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.task.Task import net.minecraft.block.BlockState @@ -120,7 +120,7 @@ sealed class BreakResult : BuildResult() { context(task: Task<*>, _: AutomatedSafeContext) override fun resolve() { selectStack { - isItem(badItem).not() + inverted { isItem(badItem) } }.transferByTask(HotbarContainer)?.softFail()?.execute(task) } diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/GenericResult.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/GenericResult.kt index a9b550507..9762b191d 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/GenericResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/GenericResult.kt @@ -20,15 +20,15 @@ package com.lambda.interaction.construction.simulation.result.results import baritone.api.pathing.goals.GoalNear import com.lambda.context.AutomatedSafeContext import com.lambda.graphics.mc.RenderBuilder -import com.lambda.interaction.handlers.BaritoneHandler import com.lambda.interaction.construction.simulation.result.BuildResult import com.lambda.interaction.construction.simulation.result.ComparableResult import com.lambda.interaction.construction.simulation.result.Drawable import com.lambda.interaction.construction.simulation.result.Navigable import com.lambda.interaction.construction.simulation.result.Rank import com.lambda.interaction.construction.simulation.result.Resolvable -import com.lambda.interaction.inventory.StackSelection +import com.lambda.interaction.handlers.BaritoneHandler import com.lambda.interaction.handlers.ContainerHandler.transferByTask +import com.lambda.interaction.inventory.StackSelection import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.task.Task import net.minecraft.client.data.TextureMap.side diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/InteractResult.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/InteractResult.kt index f0f9f5932..a05e9f570 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/InteractResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/InteractResult.kt @@ -20,7 +20,6 @@ package com.lambda.interaction.construction.simulation.result.results import baritone.api.pathing.goals.GoalBlock import baritone.api.pathing.goals.GoalInverted import com.lambda.graphics.mc.RenderBuilder -import com.lambda.interaction.handlers.BaritoneHandler import com.lambda.interaction.construction.simulation.context.InteractContext import com.lambda.interaction.construction.simulation.result.BuildResult import com.lambda.interaction.construction.simulation.result.Contextual @@ -28,6 +27,7 @@ import com.lambda.interaction.construction.simulation.result.Dependent import com.lambda.interaction.construction.simulation.result.Drawable import com.lambda.interaction.construction.simulation.result.Navigable import com.lambda.interaction.construction.simulation.result.Rank +import com.lambda.interaction.handlers.BaritoneHandler import net.minecraft.block.BlockState import net.minecraft.entity.Entity import net.minecraft.item.Item diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/PreSimResult.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/PreSimResult.kt index d7a320f2b..efa0cbaf6 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/PreSimResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/PreSimResult.kt @@ -19,12 +19,12 @@ package com.lambda.interaction.construction.simulation.result.results import baritone.api.pathing.goals.GoalBlock import com.lambda.graphics.mc.RenderBuilder -import com.lambda.interaction.handlers.BaritoneHandler import com.lambda.interaction.construction.simulation.result.BuildResult import com.lambda.interaction.construction.simulation.result.ComparableResult import com.lambda.interaction.construction.simulation.result.Drawable import com.lambda.interaction.construction.simulation.result.Navigable import com.lambda.interaction.construction.simulation.result.Rank +import com.lambda.interaction.handlers.BaritoneHandler import net.minecraft.block.BlockState import net.minecraft.util.math.BlockPos import java.awt.Color diff --git a/src/main/kotlin/com/lambda/interaction/handlers/ContainerHandler.kt b/src/main/kotlin/com/lambda/interaction/handlers/ContainerHandler.kt index 733931f37..0398fc6cb 100644 --- a/src/main/kotlin/com/lambda/interaction/handlers/ContainerHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handlers/ContainerHandler.kt @@ -25,7 +25,7 @@ import com.lambda.event.events.PlayerEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.inventory.ContainerSelection import com.lambda.interaction.inventory.StackSelection -import com.lambda.interaction.inventory.StackSelection.Companion.select +import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.select import com.lambda.interaction.inventory.container.Container import com.lambda.interaction.inventory.container.containers.ChestContainer import com.lambda.interaction.inventory.container.containers.EnderChestContainer diff --git a/src/main/kotlin/com/lambda/interaction/handlers/FriendHandler.kt b/src/main/kotlin/com/lambda/interaction/handlers/FriendHandler.kt index a27a7d4bb..9afa8a5d3 100644 --- a/src/main/kotlin/com/lambda/interaction/handlers/FriendHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handlers/FriendHandler.kt @@ -34,7 +34,7 @@ import com.mojang.authlib.GameProfile import net.minecraft.entity.player.PlayerEntity import net.minecraft.text.Text import java.awt.Color -import java.util.UUID +import java.util.* object FriendHandler : Config( "friends", diff --git a/src/main/kotlin/com/lambda/interaction/handlers/GlideHandler.kt b/src/main/kotlin/com/lambda/interaction/handlers/GlideHandler.kt index 8f3df5c02..af39eafcd 100644 --- a/src/main/kotlin/com/lambda/interaction/handlers/GlideHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handlers/GlideHandler.kt @@ -20,7 +20,9 @@ package com.lambda.interaction.handlers import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.inventory.StackSelection.Companion.selectStack +import com.lambda.interaction.inventory.StackAndSlot +import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack +import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer import com.lambda.module.modules.combat.AutoArmor import com.lambda.module.modules.movement.elytrafly.ElytraFly import com.lambda.module.modules.movement.elytrafly.ElytraFly.mode @@ -30,7 +32,6 @@ import com.lambda.module.modules.player.AutoElytraSwap.glideDelay import com.lambda.util.EnchantmentUtils.getEnchantment import com.lambda.util.player.PlayerUtils.canGlideWithChestPiece import com.lambda.util.player.PlayerUtils.canStartGliding -import com.lambda.util.player.SlotUtils.hotbarAndInventorySlots import net.minecraft.component.DataComponentTypes import net.minecraft.enchantment.Enchantments import net.minecraft.entity.attribute.EntityAttributes @@ -41,36 +42,40 @@ import net.minecraft.registry.tag.ItemTags object GlideHandler { val ELYTRA_SELECTION = selectStack { - sortByDescending { - it.getEnchantment(Enchantments.UNBREAKING) - }.thenByDescending { - it.getEnchantment(Enchantments.MENDING) - } isItem(Items.ELYTRA) - .and { it.damage < it.maxDamage } + custom { stack, _ -> stack.damage < stack.maxDamage } + sortedWith { + compareByDescending> { + it.stack.getEnchantment(Enchantments.UNBREAKING) + }.thenByDescending { + it.stack.getEnchantment(Enchantments.MENDING) + } + } } val CHESTPLATE_SELECTION = selectStack { - sortByDescending { - it.getOrDefault(DataComponentTypes.ATTRIBUTE_MODIFIERS, null) - ?.modifiers - ?.find { modifier -> modifier.attribute == EntityAttributes.ARMOR } - ?.modifier?.value - ?: 0.0 - }.thenByDescending { - it.getOrDefault(DataComponentTypes.ATTRIBUTE_MODIFIERS, null) - ?.modifiers - ?.find { modifier -> modifier.attribute == EntityAttributes.ARMOR_TOUGHNESS } - ?.modifier?.value - ?: 0.0 - }.thenByDescending { - it.getEnchantment(Enchantments.UNBREAKING) - }.thenByDescending { - it.getEnchantment(Enchantments.MENDING) - } hasTag(ItemTags.CHEST_ARMOR) - .and { it.item != Items.ELYTRA } - .and { it.damage < it.maxDamage } + inverted { isItem(Items.ELYTRA) } + custom { stack, _ -> stack.damage < stack.maxDamage } + sortedWith { + compareByDescending> { + it.stack.getOrDefault(DataComponentTypes.ATTRIBUTE_MODIFIERS, null) + ?.modifiers + ?.find { modifier -> modifier.attribute == EntityAttributes.ARMOR } + ?.modifier?.value + ?: 0.0 + }.thenByDescending { + it.stack.getOrDefault(DataComponentTypes.ATTRIBUTE_MODIFIERS, null) + ?.modifiers + ?.find { modifier -> modifier.attribute == EntityAttributes.ARMOR_TOUGHNESS } + ?.modifier?.value + ?: 0.0 + }.thenByDescending { + it.stack.getEnchantment(Enchantments.UNBREAKING) + }.thenByDescending { + it.stack.getEnchantment(Enchantments.MENDING) + } + } } @JvmStatic val overridingGlide @@ -84,7 +89,10 @@ object GlideHandler { init { listen({ -1000 }) { - if (!player.isGliding && pendingGlides.isEmpty()) AutoElytraSwap.restore() + if (pendingGlides.isEmpty()) { + if (swapped && !player.isGliding) AutoElytraSwap.restore() + return@listen + } tickPendingGlides() } @@ -163,7 +171,7 @@ object GlideHandler { val fakeFly = ElytraFly.isEnabled && ElytraFly.fakeFly val canGlideAlready = player.canGlideWithChestPiece() != fakeFly if (!autoSwapChecking || canGlideAlready) return canGlideAlready - return player.hotbarAndInventorySlots.let { slots -> + return HotbarAndInventoryContainer.slots.let { slots -> if (fakeFly) CHESTPLATE_SELECTION.filterSlots(slots).isNotEmpty() else ELYTRA_SELECTION.filterSlots(slots).isNotEmpty() } diff --git a/src/main/kotlin/com/lambda/interaction/handlers/breaking/BrokenBlockHandler.kt b/src/main/kotlin/com/lambda/interaction/handlers/breaking/BrokenBlockHandler.kt index 7d91d6e59..b34ccf2c6 100644 --- a/src/main/kotlin/com/lambda/interaction/handlers/breaking/BrokenBlockHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handlers/breaking/BrokenBlockHandler.kt @@ -24,11 +24,11 @@ import com.lambda.event.events.EntityEvent import com.lambda.event.events.WorldEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.construction.simulation.processing.ProcessorRegistry +import com.lambda.interaction.handlers.breaking.RebreakHandler.rebreak import com.lambda.interaction.managers.PostActionHandler +import com.lambda.interaction.managers.breaking.BreakInfo import com.lambda.interaction.managers.breaking.BreakManager.lastPosStarted import com.lambda.interaction.managers.breaking.BreakManager.matchesBlockItem -import com.lambda.interaction.handlers.breaking.RebreakHandler.rebreak -import com.lambda.interaction.managers.breaking.BreakInfo import com.lambda.module.modules.client.Client import com.lambda.module.modules.client.Client.verboseDebug import com.lambda.threading.runGameScheduled diff --git a/src/main/kotlin/com/lambda/interaction/handlers/breaking/RebreakHandler.kt b/src/main/kotlin/com/lambda/interaction/handlers/breaking/RebreakHandler.kt index ee4a0fdb4..122414983 100644 --- a/src/main/kotlin/com/lambda/interaction/handlers/breaking/RebreakHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handlers/breaking/RebreakHandler.kt @@ -24,13 +24,13 @@ 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.interaction.construction.simulation.context.BreakContext -import com.lambda.interaction.handlers.packet.PacketLimitHandler -import com.lambda.interaction.handlers.packet.PacketType import com.lambda.interaction.handlers.breaking.BrokenBlockHandler.destroyBlock -import com.lambda.interaction.managers.breaking.BreakManager.calcBreakDelta import com.lambda.interaction.handlers.breaking.RebreakHandler.rebreak +import com.lambda.interaction.handlers.packet.PacketLimitHandler +import com.lambda.interaction.handlers.packet.PacketType import com.lambda.interaction.managers.breaking.BreakInfo import com.lambda.interaction.managers.breaking.BreakManager +import com.lambda.interaction.managers.breaking.BreakManager.calcBreakDelta import com.lambda.interaction.managers.breaking.BreakRequest import com.lambda.threading.runSafeAutomated import com.lambda.util.player.PlayerUtils.swingHand diff --git a/src/main/kotlin/com/lambda/interaction/inventory/StackSelection.kt b/src/main/kotlin/com/lambda/interaction/inventory/StackSelection.kt index daa5231cb..31534912f 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/StackSelection.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/StackSelection.kt @@ -17,9 +17,8 @@ package com.lambda.interaction.inventory -import com.lambda.interaction.inventory.StackSelection.Companion.StackSelectionDsl import com.lambda.util.EnchantmentUtils.getEnchantment -import com.lambda.util.item.ItemStackUtils.shulkerBoxContents +import com.lambda.util.item.ItemStackUtils.shulkerBoxStacks import com.lambda.util.item.ItemUtils import net.minecraft.block.Block import net.minecraft.block.BlockState @@ -33,288 +32,214 @@ import net.minecraft.registry.RegistryKey import net.minecraft.registry.tag.TagKey import net.minecraft.screen.slot.Slot import java.util.* -import kotlin.reflect.KClass -/** - * [StackSelection] is a class that holds a predicate for matching [ItemStack]s. - */ @Suppress("unused") -@StackSelectionDsl -class StackSelection { - var selector: (ItemStack) -> Boolean = EVERYTHING - var comparator: Comparator = NO_COMPARE - var count: Int = DEFAULT_AMOUNT - var inShulkerBox: Boolean = false - - var item: Item? = null - var itemClass: KClass? = null - private var damage: Int? = null - var itemStack: ItemStack? = null - val stackSize: Int - get() = optimalStack?.maxCount ?: 64 - - val optimalStack: ItemStack? - get() = itemStack ?: item?.let { ItemStack(it, count) } - - /** - * Filters the given [stacks], sorts them with the [comparator] and returns the first value - */ - fun bestItemMatch(stacks: List): ItemStack? = filterStacks(stacks).firstOrNull() - - fun matches(stack: ItemStack): Boolean = filterStack(stack) - - fun filterStack(stack: ItemStack) = - if (inShulkerBox) stack.shulkerBoxContents.any { selector(it) } - else selector(stack) - - fun filterSlot(slot: Slot): Boolean = filterStack(slot.stack) - - fun filterStacks(stacks: List): List = - stacks.filter(::filterStack).sortedWith(comparator) - - fun filterSlots(slots: List): List = - slots.filter(::filterSlot).sortedWith { slot, slot2 -> comparator.compare(slot.stack, slot2.stack) } - - fun > sortBy(selector: (ItemStack) -> R?): StackSelection = apply { - comparator = compareBy(selector) - } - - fun > sortByDescending(selector: (ItemStack) -> R?): StackSelection = apply { - comparator = compareByDescending(selector) - } - - fun > thenBy(selector: (ItemStack) -> R?): StackSelection = apply { - check(comparator != NO_COMPARE) { "No comparator specified" } - comparator = comparator.thenBy(selector) - } - - fun > thenByDescending(selector: (ItemStack) -> R?): StackSelection = apply { - check(comparator != NO_COMPARE) { "No comparator specified" } - comparator = comparator.thenByDescending(selector) - } - - fun sortWith(custom: Comparator): StackSelection = apply { - comparator = custom - } - - fun reversed(): StackSelection = apply { - comparator = comparator.reversed() - } - - /** - * returns a function that finds a shulker box to push matching items into. - */ - val findShulkerToPush = { stack: ItemStack -> - stack.shulkerBoxContents.let { inventory -> - if (inventory.all { selector(it) || it.isEmpty }) { - val storableItems = inventory.sumOf { -// if (it.isEmpty) item.itemStackLimit else it.maxStackSize - it.count - if (it.isEmpty) item?.maxCount ?: 0 else 0 - } - - if (storableItems > 0) storableItems else null - } else { - null - } - } - } - - /** - * returns a function that finds a shulker box to pull matching items from. - */ - val findShulkerToPull = { slot: Slot -> - slot.stack.shulkerBoxContents.let { inventory -> - val usableItems = inventory.sumOf { if (selector(it)) it.count else 0 } - - if (usableItems > 0) slot to usableItems else null - } - } - - fun any() = EVERYTHING - fun none() = NOTHING - - /** - * [isItem] returns a predicate that matches a specific [Item]. - * @param item The [Item] to be matched. - * @return A predicate that matches the [Item]. - */ - fun isItem(item: Item): (ItemStack) -> Boolean { - this.item = item - return { it.item == item } - } - - /** - * Returns a predicate that matches if the `ItemStack`'s item is one of the specified items in the collection. - * - * @param items The collection of `Item` instances to match against. - * @return A predicate that checks if the `ItemStack`'s item is contained in the provided collection. - */ - fun isOneOfItems(items: Collection): (ItemStack) -> Boolean = { it.item in items } - - fun isNoneOfItems(items: Collection): (ItemStack) -> Boolean = isOneOfItems(items).not() - - /** - * Returns a predicate that checks if a given `ItemStack` exists within the provided collection of `ItemStack`s. - * - * @param stacks A collection of `ItemStack` instances to be checked against. - * @return A predicate that evaluates to `true` if the given `ItemStack` is within the specified collection, otherwise `false`. - */ - fun isOneOfStacks(stacks: Collection): (ItemStack) -> Boolean = stacks::contains - - fun isEfficientForBreaking(blockState: BlockState): (ItemStack) -> Boolean = { itemStack -> - val hasEfficientTool = efficientToolCache.getOrPut(blockState) { - ItemUtils.tools.any { it.getMiningSpeed(it.defaultStack, blockState) > 1f } - } - if (hasEfficientTool) itemStack.item.getMiningSpeed(itemStack, blockState) > 1f - else true - } - - fun isSuitableForBreaking(blockState: BlockState): (ItemStack) -> Boolean = { !blockState.isToolRequired || it.isSuitableFor(blockState) } - - fun hasTag(tag: TagKey): (ItemStack) -> Boolean = { it.isIn(tag) } - - fun hasUseAction(action: UseAction): (ItemStack) -> Boolean = { it.useAction == action } - - fun isTool(): (ItemStack) -> Boolean = hasComponent(DataComponentTypes.TOOL) - - fun isFood(): (ItemStack) -> Boolean = hasComponent(DataComponentTypes.FOOD) - - fun hasComponent(type: ComponentType<*>): (ItemStack) -> Boolean = { it.components.contains(type) } - - /** - * [isItem] returns a predicate that matches a specific [Item] instance. - * @param T The instance of [Item] to be matched. - * @return A predicate that matches the [Item]. - */ - inline fun isItem(): (ItemStack) -> Boolean { - itemClass = T::class - return { it.item is T } - } - - /** - * [isBlock] returns a predicate that matches a specific [Block]. - * @param block The [Block] to be matched. - * @return A predicate that matches the [Block]. - */ - fun isBlock(block: Block): (ItemStack) -> Boolean { - item = block.asItem() - return { it.item == block.asItem() } - } - - /** - * [isItemStack] returns a predicate that matches a specific [ItemStack]. - * @param stack The [ItemStack] to be matched. - * @return A predicate that matches the [ItemStack]. - */ - fun isItemStack(stack: ItemStack): (ItemStack) -> Boolean { - this.itemStack = stack - return { ItemStack.areEqual(it, stack) } - } - - /** - * [hasDamage] returns a predicate that matches a specific damage value. - * @param damage The damage value to be matched. - * @return A predicate that matches the damage value. - */ - fun hasDamage(damage: Int): (ItemStack) -> Boolean { - this.damage = damage - return { it.damage == damage } - } - - /** - * [hasEnchantment] returns a predicate that matches a specific [Enchantment] and level. - * @param enchantment The [Enchantment] to be matched. - * @param level The level to be matched (if -1 will look for any level above 0). - * @return A predicate that matches the [Enchantment] and `level`. - */ - fun hasEnchantment(enchantment: RegistryKey, level: Int = -1): (ItemStack) -> Boolean = { - if (level < 0) { - it.getEnchantment(enchantment) > 0 - } else { - it.getEnchantment(enchantment) == level - } - } - - /** - * Returns the negation of the original predicate. - * @return A new predicate that matches if the original predicate does not match. - */ - fun ((ItemStack) -> Boolean).not(): (ItemStack) -> Boolean = { !this(it) } - - /** - * Combines two predicates using the logical AND operator. - * @param otherPredicate The second predicate. - * @return A new predicate that matches if both inputs predicate match. - */ - infix fun ((ItemStack) -> Boolean).and(otherPredicate: (ItemStack) -> Boolean): (ItemStack) -> Boolean = { this(it) && otherPredicate(it) } - - /** - * Combines two predicates using the logical OR operator. - * @param otherPredicate The second predicate. - * @return A new predicate that matches if either input predicate matches. - */ - infix fun ((ItemStack) -> Boolean).or(otherPredicate: (ItemStack) -> Boolean): (ItemStack) -> Boolean = { this(it) || otherPredicate(it) } - - fun ((ItemStack) -> Boolean).andIf(predicate: Boolean, otherPredicate: () -> (ItemStack) -> Boolean): (ItemStack) -> Boolean = - if (predicate) { { this(it) && otherPredicate()(it) } } else this - - override fun toString() = buildString { - append("selection of ${count}x ") - item?.let { append(it.name.string) } - itemClass?.let { append(it.simpleName) } - itemStack?.let { append(it.name.string) } - damage?.let { append(" with damage $it") } - when (selector) { - EVERYTHING -> append(" everything") - NOTHING -> append(" nothing") - else -> append(" custom predicate") - } - if (inShulkerBox) append(" in shulker box") - if (comparator != NO_COMPARE) append(" sorted by custom comparator") - } - - companion object { - @DslMarker - annotation class StackSelectionDsl - - const val DEFAULT_AMOUNT = 1 - - val FULL_SHULKERS: (ItemStack) -> Boolean = { stack -> stack.shulkerBoxContents.none { it.isEmpty } } - val EMPTY_SHULKERS: (ItemStack) -> Boolean = { stack -> stack.shulkerBoxContents.all { it.isEmpty } } - val EVERYTHING: (ItemStack) -> Boolean = { true } - val NOTHING: (ItemStack) -> Boolean = { false } - val NO_COMPARE: Comparator = Comparator { _, _ -> 0 } - - val efficientToolCache: MutableMap = Collections.synchronizedMap(mutableMapOf()) - - @StackSelectionDsl - fun Item.select() = selectStack { isItem(this@select) } - - @StackSelectionDsl - fun ItemStack.select() = selectStack { isItemStack(this@select) } - - @StackSelectionDsl - @JvmName("selectStacks") - fun Collection.select() = selectStack { isOneOfStacks(this@select) } - - @StackSelectionDsl - @JvmName("selectItems") - fun Collection.select() = selectStack { isOneOfItems(this@select) } - - @StackSelectionDsl - fun ((ItemStack) -> Boolean).select() = selectStack { this@select } - - @StackSelectionDsl - fun selectStack( - count: Int = DEFAULT_AMOUNT, - inShulkerBox: Boolean = false, - sorter: Comparator = NO_COMPARE, - block: StackSelection.() -> (ItemStack) -> Boolean = { EVERYTHING }, - ) = StackSelection().apply { - selector = block() - comparator = sorter - this.count = count - this.inShulkerBox = inShulkerBox - } - } +class StackSelection( + val count: Int = 0, + val item: Item? = null, + val itemStack: ItemStack? = null, + val inShulkerBox: Boolean = false, + val comparator: Comparator> = compareBy { it.stack.count }, + val selector: (ItemStack, Slot?) -> Boolean, +) { + val optimalStack = itemStack ?: item?.let { ItemStack(it, count) } + + @StackSelectionMarker + fun bestMatch(stacks: List) = filterStacks(stacks).firstOrNull() + @StackSelectionMarker + fun bestMatch(slots: List) = filterSlots(slots).firstOrNull() + + @StackSelectionMarker + fun matches(stack: ItemStack) = filterStack(stack) + @StackSelectionMarker + fun matches(slot: Slot) = filterSlot(slot) + + @StackSelectionMarker + fun filterStack(stack: ItemStack) = + if (inShulkerBox) stack.shulkerBoxStacks.any { selector(it, null) } + else selector(stack, null) + @StackSelectionMarker + fun filterSlot(slot: Slot): Boolean = + if (inShulkerBox) slot.stack.shulkerBoxStacks.any { selector(it, null) } + else selector(slot.stack, slot) + + @StackSelectionMarker + fun filterStacks(stacks: List): List = + stacks + .filter(::filterStack) + .map { StackAndSlot(it, null) } + .sortedWith(comparator) + .map { it.stack } + @StackSelectionMarker + fun filterSlots(slots: List): List = + slots + .filter(::filterSlot) + .map { StackAndSlot(it.stack, it) } + .sortedWith(comparator) + .map { it.slot } + + companion object { + val EVERYTHING = StackSelection { _, _ -> true } + val NOTHING = StackSelection { _, _ -> false } + } +} + +@DslMarker +private annotation class StackSelectionMarker + +@Suppress("unused") +@StackSelectionMarker +class StackSelectionBuilder(private val count: Int = 0) { + private var selector: (ItemStack, Slot?) -> Boolean = { _, _ -> true } + private var item: Item? = null + private var stackByRef = false + private var itemStack: ItemStack? = null + private var comparator: Comparator> = compareBy { it.stack.count } + private var inShulkerBoxesOnly: Boolean = false + private var invertNewSelectors = false + + fun isItem(item: Item) { + this.item = item + } + + inline fun isItem() { + val kClass = T::class + appendSelector { stack, _ -> stack::class == kClass } + } + + fun isItemStack(stack: ItemStack) { + this.itemStack = stack + appendSelector { s, _ -> ItemStack.areEqual(s, stack) } + } + + fun isItemStackByRef(stack: ItemStack) { + this.itemStack = stack + appendSelector { s, _ -> s === stack } + } + + fun inShulkerBoxesOnly() { + inShulkerBoxesOnly = true + } + + fun isOneOfItems(items: Collection) { + appendSelector { stack, _ -> items.contains(stack.item) } + } + + fun isNoneOfItems(items: Collection) { + appendSelector { stack, _ -> !items.contains(stack.item) } + } + + fun isOneOfStacks(stacks: Collection) { + appendSelector { stack, _ -> stacks.contains(stack) } + } + + fun isNoneOfStacks(stacks: Collection) { + appendSelector { stack, _ -> !stacks.contains(stack) } + } + + fun isEfficientForBreaking(blockState: BlockState) { + appendSelector { itemStack, _ -> + val hasEfficientTool = efficientToolCache.getOrPut(blockState) { + ItemUtils.tools.any { it.getMiningSpeed(it.defaultStack, blockState) > 1f } + } + if (hasEfficientTool) itemStack.item.getMiningSpeed(itemStack, blockState) > 1f + else true + } + } + + fun isSuitableForBreaking(blockState: BlockState) { + appendSelector { itemStack, _ -> + !blockState.isToolRequired || itemStack.isSuitableFor(blockState) + } + } + + fun hasTag(tag: TagKey) { + appendSelector { stack, _ -> stack.isIn(tag) } + } + + fun hasUseAction(action: UseAction) { + appendSelector { stack, _ -> stack.useAction == action } + } + + fun isTool() = hasComponent(DataComponentTypes.TOOL) + + fun isFood() = hasComponent(DataComponentTypes.FOOD) + + fun hasComponent(type: ComponentType<*>) { + appendSelector { stack, _ -> + stack.components.contains(type) + } + } + + fun isBlock(block: Block) { + item = block.asItem() + appendSelector { stack, _ -> stack.item == block.asItem() } + } + + fun hasDamage(damage: Int) { + appendSelector { stack, _ -> stack.damage == damage } + } + + fun hasEnchantment(enchantment: RegistryKey, level: Int = -1) { + appendSelector { stack, _ -> + if (level < 0) { + stack.getEnchantment(enchantment) > 0 + } else { + stack.getEnchantment(enchantment) == level + } + } + } + + fun inverted(block: () -> Unit) { + invertNewSelectors = true + block() + invertNewSelectors = false + } + + fun custom(predicate: (ItemStack, Slot?) -> Boolean) { + appendSelector { stack, slot -> predicate(stack, slot) } + } + + fun sortedWith(comparator: Comparator>) { + this.comparator = comparator + } + + fun sortedWith(comparatorSupplier: () -> Comparator>) { + this.comparator = comparatorSupplier() + } + + @PublishedApi + internal fun appendSelector(selector: (ItemStack, Slot?) -> Boolean) { + val invert = invertNewSelectors + this.selector = { stack, slot -> + this.selector(stack, slot) && (selector(stack, slot) xor invert) + } + } + + private fun build() = + StackSelection( + count, + item, + itemStack, + inShulkerBoxesOnly, + comparator, + selector + ) + + companion object { + private val efficientToolCache: MutableMap = Collections.synchronizedMap(mutableMapOf()) + + @StackSelectionMarker + fun Item.select(count: Int = 0) = selectStack(count) { isItem(this@select) } + + @StackSelectionMarker + fun ItemStack.select(count: Int = 0) = selectStack(count) { isItemStack(this@select) } + + fun selectStack( + count: Int = 0, + builder: StackSelectionBuilder.() -> Unit + ) = StackSelectionBuilder(count).apply(builder).build() + } } + +data class StackAndSlot(val stack: ItemStack, val slot: T) \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/Container.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/Container.kt index 9e514a27d..46a0ad7e5 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/Container.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/Container.kt @@ -22,15 +22,15 @@ import com.lambda.context.AutomatedSafeContext import com.lambda.context.SafeContext import com.lambda.event.EventFlow.post import com.lambda.event.events.ContainerEvent -import com.lambda.interaction.managers.inventory.InventoryRequest -import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest import com.lambda.interaction.inventory.StackSelection import com.lambda.interaction.inventory.container.containers.ShulkerBoxContainer +import com.lambda.interaction.managers.inventory.InventoryRequest +import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest import com.lambda.task.tasks.ContainerTransferTask import com.lambda.util.Nameable import com.lambda.util.item.ItemStackUtils.count import com.lambda.util.item.ItemStackUtils.empty -import com.lambda.util.item.ItemStackUtils.shulkerBoxContents +import com.lambda.util.item.ItemStackUtils.shulkerBoxStacks import com.lambda.util.item.ItemStackUtils.spaceLeft import com.lambda.util.item.ItemUtils import com.lambda.util.item.ItemUtils.toItemCount @@ -104,7 +104,7 @@ abstract class Container( it.stack.item in ItemUtils.shulkerBoxes }.map { slot -> ShulkerBoxContainer( - slot.stack.shulkerBoxContents, + slot.stack.shulkerBoxStacks, containedIn = this@Container, shulkerSlot = slot ) @@ -159,7 +159,7 @@ abstract class Container( context(_: SafeContext) open fun spaceAvailable(selection: StackSelection) = - matchingStacks(selection).spaceLeft + stacks.empty * selection.stackSize + matchingStacks(selection).spaceLeft + stacks.empty * selection.count context(_: AutomatedSafeContext) open fun getReplaceableSlot() = slots.sortedWith(replaceSorter).firstOrNull() @@ -173,6 +173,7 @@ abstract class Container( OffHand, Hotbar, Inventory, + HotbarAndInventory, Armor, Player, Creative, diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/ArmorContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/ArmorContainer.kt index 061298208..479ce919e 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/ArmorContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/ArmorContainer.kt @@ -27,7 +27,6 @@ import com.lambda.util.text.literal import net.minecraft.item.ItemStack import net.minecraft.screen.slot.Slot -@Suppress("unused") object ArmorContainer : Container(Rank.Armor) { context(safeContext: SafeContext) override val slots: List diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/ChestContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/ChestContainer.kt index cbbf7c616..dc2f69b1e 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/ChestContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/ChestContainer.kt @@ -20,8 +20,8 @@ package com.lambda.interaction.inventory.container.containers import com.lambda.context.AutomatedSafeContext import com.lambda.context.SafeContext import com.lambda.interaction.handlers.ContainerHandler -import com.lambda.interaction.inventory.container.ExternalContainer import com.lambda.interaction.inventory.container.Container +import com.lambda.interaction.inventory.container.ExternalContainer import com.lambda.task.Task import com.lambda.task.TaskGenerator import com.lambda.task.tasks.OpenContainerTask diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/CreativeContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/CreativeContainer.kt index 3c00c1a6a..9c9ff74db 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/CreativeContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/CreativeContainer.kt @@ -18,9 +18,9 @@ package com.lambda.interaction.inventory.container.containers import com.lambda.context.SafeContext -import com.lambda.interaction.managers.inventory.InventoryRequest import com.lambda.interaction.inventory.StackSelection import com.lambda.interaction.inventory.container.Container +import com.lambda.interaction.managers.inventory.InventoryRequest import com.lambda.util.text.buildText import com.lambda.util.text.literal import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/EnderChestContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/EnderChestContainer.kt index a7b6993fa..4b74135cf 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/EnderChestContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/EnderChestContainer.kt @@ -19,11 +19,11 @@ package com.lambda.interaction.inventory.container.containers import com.lambda.context.AutomatedSafeContext import com.lambda.context.SafeContext -import com.lambda.interaction.inventory.StackSelection.Companion.select import com.lambda.interaction.handlers.ContainerHandler import com.lambda.interaction.handlers.ContainerHandler.findSlotsWithMaterial -import com.lambda.interaction.inventory.container.ExternalContainer +import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.select import com.lambda.interaction.inventory.container.Container +import com.lambda.interaction.inventory.container.ExternalContainer import com.lambda.task.TaskGenerator import com.lambda.task.tasks.BuildTask.Companion.breakAndCollectBlock import com.lambda.task.tasks.OpenContainerTask diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/HotbarAndInventoryContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/HotbarAndInventoryContainer.kt new file mode 100644 index 000000000..c2dc8c52c --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/HotbarAndInventoryContainer.kt @@ -0,0 +1,39 @@ +/* + * 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.inventory.container.containers + +import com.lambda.Lambda.mc +import com.lambda.context.SafeContext +import com.lambda.interaction.inventory.container.Container +import com.lambda.util.player.SlotUtils.hotbarAndInventorySlots +import com.lambda.util.player.SlotUtils.hotbarAndInventoryStacks +import com.lambda.util.text.buildText +import com.lambda.util.text.literal +import net.minecraft.item.ItemStack +import net.minecraft.screen.slot.Slot + +object HotbarAndInventoryContainer : Container(Rank.HotbarAndInventory) { + context(safeContext: SafeContext) + override val slots: List + get() = safeContext.player.hotbarAndInventorySlots + override var stacks: List + get() = mc.player?.hotbarAndInventoryStacks ?: emptyList() + set(_) {} + + override val description = buildText { literal("Hotbar and Inventory") } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/HotbarContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/HotbarContainer.kt index 1e62c37aa..f3db002c0 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/HotbarContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/HotbarContainer.kt @@ -19,8 +19,8 @@ package com.lambda.interaction.inventory.container.containers import com.lambda.Lambda.mc import com.lambda.context.SafeContext -import com.lambda.interaction.managers.inventory.InventoryRequest import com.lambda.interaction.inventory.container.Container +import com.lambda.interaction.managers.inventory.InventoryRequest import com.lambda.util.player.SlotUtils.hotbarSlots import com.lambda.util.player.SlotUtils.hotbarStacks import com.lambda.util.text.buildText diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/MainHandContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/MainHandContainer.kt index 018859489..839c1fa6d 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/MainHandContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/MainHandContainer.kt @@ -19,8 +19,8 @@ package com.lambda.interaction.inventory.container.containers import com.lambda.Lambda.mc import com.lambda.context.SafeContext -import com.lambda.interaction.managers.inventory.InventoryRequest import com.lambda.interaction.inventory.container.Container +import com.lambda.interaction.managers.inventory.InventoryRequest import com.lambda.util.player.SlotUtils.mainHandSlots import com.lambda.util.text.buildText import com.lambda.util.text.literal diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/OffHandContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/OffHandContainer.kt index f48df2fd4..74f9f252a 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/OffHandContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/OffHandContainer.kt @@ -19,8 +19,8 @@ package com.lambda.interaction.inventory.container.containers import com.lambda.Lambda.mc import com.lambda.context.SafeContext -import com.lambda.interaction.managers.inventory.InventoryRequest import com.lambda.interaction.inventory.container.Container +import com.lambda.interaction.managers.inventory.InventoryRequest import com.lambda.util.player.SlotUtils.offHandSlots import com.lambda.util.text.buildText import com.lambda.util.text.literal diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/ShulkerBoxContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/ShulkerBoxContainer.kt index 5a644647b..8cb68f275 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/ShulkerBoxContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/ShulkerBoxContainer.kt @@ -20,8 +20,8 @@ package com.lambda.interaction.inventory.container.containers import com.lambda.context.AutomatedSafeContext import com.lambda.context.SafeContext import com.lambda.interaction.handlers.ContainerHandler -import com.lambda.interaction.inventory.container.ExternalContainer import com.lambda.interaction.inventory.container.Container +import com.lambda.interaction.inventory.container.ExternalContainer import com.lambda.task.TaskGenerator import com.lambda.task.tasks.BuildTask.Companion.breakAndCollectBlock import com.lambda.task.tasks.OpenContainerTask diff --git a/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakManager.kt b/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakManager.kt index 551e48286..daae64b81 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakManager.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakManager.kt @@ -34,11 +34,18 @@ import com.lambda.interaction.construction.simulation.BuildSimulator.simulate import com.lambda.interaction.construction.simulation.context.BreakContext import com.lambda.interaction.construction.simulation.result.results.BreakResult import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.managers.Manager -import com.lambda.interaction.managers.ManagerUtils.isPosBlocked +import com.lambda.interaction.handlers.breaking.BrokenBlockHandler.destroyBlock +import com.lambda.interaction.handlers.breaking.BrokenBlockHandler.pendingActions +import com.lambda.interaction.handlers.breaking.BrokenBlockHandler.setPendingConfigs +import com.lambda.interaction.handlers.breaking.BrokenBlockHandler.startPending +import com.lambda.interaction.handlers.breaking.RebreakHandler +import com.lambda.interaction.handlers.breaking.RebreakHandler.getRebreakPotential +import com.lambda.interaction.handlers.breaking.RebreakResult import com.lambda.interaction.handlers.packet.PacketLimitHandler import com.lambda.interaction.handlers.packet.PacketType -import com.lambda.interaction.handlers.breaking.RebreakHandler +import com.lambda.interaction.inventory.StackSelection +import com.lambda.interaction.managers.Manager +import com.lambda.interaction.managers.ManagerUtils.isPosBlocked import com.lambda.interaction.managers.PositionBlocking import com.lambda.interaction.managers.breaking.BreakInfo.BreakType.Primary import com.lambda.interaction.managers.breaking.BreakInfo.BreakType.Rebreak @@ -63,18 +70,10 @@ import com.lambda.interaction.managers.breaking.BreakManager.rotationRequest import com.lambda.interaction.managers.breaking.BreakManager.simulateAbandoned import com.lambda.interaction.managers.breaking.BreakManager.updateBreakProgress import com.lambda.interaction.managers.breaking.BreakManager.updatePreProcessing -import com.lambda.interaction.handlers.breaking.BrokenBlockHandler.destroyBlock -import com.lambda.interaction.handlers.breaking.BrokenBlockHandler.pendingActions -import com.lambda.interaction.handlers.breaking.BrokenBlockHandler.setPendingConfigs -import com.lambda.interaction.handlers.breaking.BrokenBlockHandler.startPending -import com.lambda.interaction.handlers.breaking.RebreakHandler.getRebreakPotential -import com.lambda.interaction.handlers.breaking.RebreakResult import com.lambda.interaction.managers.breaking.SwapInfo.Companion.getSwapInfo import com.lambda.interaction.managers.hotbar.HotbarRequest import com.lambda.interaction.managers.interacting.InteractManager import com.lambda.interaction.managers.rotating.RotationRequest -import com.lambda.interaction.inventory.StackSelection -import com.lambda.interaction.inventory.StackSelection.Companion.select import com.lambda.threading.runGameScheduled import com.lambda.threading.runSafe import com.lambda.threading.runSafeAutomated @@ -141,7 +140,7 @@ object BreakManager : Manager( .lastOrNull { it.breakConfig.doubleBreak || it.type == Secondary }?.context?.itemSelection - ?: StackSelection.EVERYTHING.select() + ?: StackSelection.EVERYTHING override val blockedPositions get() = activeInfos.map { it.context.blockPos } + pendingActions.map { it.context.blockPos } diff --git a/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakRequest.kt b/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakRequest.kt index 583afaae8..312e473d5 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakRequest.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakRequest.kt @@ -28,7 +28,7 @@ import com.lambda.interaction.construction.simulation.result.Dependent import com.lambda.interaction.construction.simulation.result.results.BreakResult import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.managers.Request -import com.lambda.interaction.managers.breaking.BreakRequest.Companion.breakRequest +import com.lambda.interaction.managers.breaking.BreakRequest.BreakRequestMarker import com.lambda.threading.runSafe import com.lambda.util.BlockUtils.blockState import com.lambda.util.BlockUtils.isEmpty @@ -40,7 +40,7 @@ import net.minecraft.util.math.BlockPos * * The callbacks can be used to keep track of the break progress. * - * A private constructor is used to force use of the cleaner [BreakRequestDsl] builder. This is + * A private constructor is used to force use of the cleaner [BreakRequestMarker] builder. This is * accessed through the [breakRequest] method. * * @param contexts A collection of [BreakContext]'s gathered from the BuildSimulator. @@ -48,75 +48,98 @@ import net.minecraft.util.math.BlockPos * * @see com.lambda.interaction.construction.simulation.BuildSimulator */ -data class BreakRequest private constructor( +data class BreakRequest( val contexts: Collection, val pendingInteractions: MutableCollection, + override val nowOrNothing: Boolean, private val automated: Automated, - override val nowOrNothing: Boolean = false + val onStart: (SafeContext.(BlockPos) -> Unit)?, + val onUpdate: (SafeContext.(BlockPos) -> Unit)?, + val onStop: (SafeContext.(BlockPos) -> Unit)?, + val onCancel: (SafeContext.(BlockPos) -> Unit)?, + val onItemDrop: (SafeContext.(ItemEntity) -> Unit)?, + val onReBreakStart: (SafeContext.(BlockPos) -> Unit)?, + val onReBreak: (SafeContext.(BlockPos) -> Unit)? ) : Request(), Automated by automated { override val requestId = ++requestCount override val tickStageMask get() = breakConfig.tickStageMask - var onStart: (SafeContext.(BlockPos) -> Unit)? = null - var onUpdate: (SafeContext.(BlockPos) -> Unit)? = null - var onStop: (SafeContext.(BlockPos) -> Unit)? = null - var onCancel: (SafeContext.(BlockPos) -> Unit)? = null - var onItemDrop: (SafeContext.(ItemEntity) -> Unit)? = null - var onReBreakStart: (SafeContext.(BlockPos) -> Unit)? = null - var onReBreak: (SafeContext.(BlockPos) -> Unit)? = null - override val done: Boolean get() = runSafe { contexts.all { blockState(it.blockPos).isEmpty } } == true @DslMarker - annotation class BreakRequestDsl + annotation class BreakRequestMarker - @BreakRequestDsl + @BreakRequestMarker override fun submit(queueIfMismatchedStage: Boolean) = BreakManager.request(this, queueIfMismatchedStage) - @BreakRequestDsl - class BreakRequestBuilder( - contexts: Collection, - pendingInteractions: MutableCollection, - nowOrNothing: Boolean, - automated: Automated - ) { - val request = BreakRequest(contexts, pendingInteractions, automated, nowOrNothing) - - fun onStart(callback: SafeContext.(BlockPos) -> Unit) { - request.onStart = callback - } - - fun onUpdate(callback: SafeContext.(BlockPos) -> Unit) { - request.onUpdate = callback - } - - fun onStop(callback: SafeContext.(BlockPos) -> Unit) { - request.onStop = callback - } - - fun onCancel(callback: SafeContext.(BlockPos) -> Unit) { - request.onCancel = callback - } - - fun onItemDrop(callback: SafeContext.(ItemEntity) -> Unit) { - request.onItemDrop = callback - } - - fun onReBreakStart(callback: SafeContext.(BlockPos) -> Unit) { - request.onReBreakStart = callback - } - - fun onReBreak(callback: SafeContext.(BlockPos) -> Unit) { - request.onReBreak = callback - } - } - companion object { var requestCount = 0 + } +} + +@BreakRequestMarker +class BreakRequestBuilder( + private val contexts: Collection, + private val pendingInteractions: MutableCollection, + private val nowOrNothing: Boolean, + private val automated: Automated +) { + private var onStart: (SafeContext.(BlockPos) -> Unit)? = null + private var onUpdate: (SafeContext.(BlockPos) -> Unit)? = null + private var onStop: (SafeContext.(BlockPos) -> Unit)? = null + private var onCancel: (SafeContext.(BlockPos) -> Unit)? = null + private var onItemDrop: (SafeContext.(ItemEntity) -> Unit)? = null + private var onReBreakStart: (SafeContext.(BlockPos) -> Unit)? = null + private var onReBreak: (SafeContext.(BlockPos) -> Unit)? = null + + fun onStart(callback: SafeContext.(BlockPos) -> Unit) { + onStart = callback + } + + fun onUpdate(callback: SafeContext.(BlockPos) -> Unit) { + onUpdate = callback + } + + fun onStop(callback: SafeContext.(BlockPos) -> Unit) { + onStop = callback + } - @BreakRequestDsl + fun onCancel(callback: SafeContext.(BlockPos) -> Unit) { + onCancel = callback + } + + fun onItemDrop(callback: SafeContext.(ItemEntity) -> Unit) { + onItemDrop = callback + } + + fun onReBreakStart(callback: SafeContext.(BlockPos) -> Unit) { + onReBreakStart = callback + } + + fun onReBreak(callback: SafeContext.(BlockPos) -> Unit) { + onReBreak = callback + } + + @BreakRequestMarker + private fun build() = + BreakRequest( + contexts, + pendingInteractions, + nowOrNothing, + automated, + onStart, + onUpdate, + onStop, + onCancel, + onItemDrop, + onReBreakStart, + onReBreak + ) + + companion object { + @BreakRequestMarker @JvmName("breakRequest1") fun AutomatedSafeContext.breakRequest( positions: Collection, @@ -128,7 +151,7 @@ data class BreakRequest private constructor( .simulate() .breakRequest(pendingInteractions, nowOrNothing, builder) - @BreakRequestDsl + @BreakRequestMarker @JvmName("breakRequest2") context(automated: Automated) fun Collection.breakRequest( @@ -138,7 +161,7 @@ data class BreakRequest private constructor( ) = asSequence() .breakRequest(pendingInteractions, nowOrNothing, builder) - @BreakRequestDsl + @BreakRequestMarker @JvmName("breakRequest3") context(automated: Automated) fun Sequence.breakRequest( @@ -153,7 +176,7 @@ data class BreakRequest private constructor( .takeIf { it.isNotEmpty() } ?.let { automated.breakRequest(it, pendingInteractions, nowOrNothing, builder) } - @BreakRequestDsl + @BreakRequestMarker @JvmName("breakRequest4") fun Automated.breakRequest( contexts: Collection, @@ -163,8 +186,5 @@ data class BreakRequest private constructor( ) = BreakRequestBuilder( contexts, pendingInteractions, nowOrNothing, this ).apply { builder?.invoke(this) }.build() - - @BreakRequestDsl - private fun BreakRequestBuilder.build(): BreakRequest = request } } diff --git a/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractManager.kt b/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractManager.kt index 442e05e38..b016527b6 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractManager.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractManager.kt @@ -29,10 +29,13 @@ 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.interaction.construction.simulation.context.InteractContext -import com.lambda.interaction.managers.Manager -import com.lambda.interaction.managers.ManagerUtils.isPosBlocked +import com.lambda.interaction.handlers.interacting.InteractedBlockHandler.pendingActions +import com.lambda.interaction.handlers.interacting.InteractedBlockHandler.setPendingConfigs +import com.lambda.interaction.handlers.interacting.InteractedBlockHandler.startPending import com.lambda.interaction.handlers.packet.PacketLimitHandler import com.lambda.interaction.handlers.packet.PacketType +import com.lambda.interaction.managers.Manager +import com.lambda.interaction.managers.ManagerUtils.isPosBlocked import com.lambda.interaction.managers.PositionBlocking import com.lambda.interaction.managers.breaking.BreakManager import com.lambda.interaction.managers.hotbar.HotbarRequest @@ -41,9 +44,6 @@ import com.lambda.interaction.managers.interacting.InteractManager.maxInteractio import com.lambda.interaction.managers.interacting.InteractManager.populateFrom import com.lambda.interaction.managers.interacting.InteractManager.potentialInteractions import com.lambda.interaction.managers.interacting.InteractManager.processRequest -import com.lambda.interaction.handlers.interacting.InteractedBlockHandler.pendingActions -import com.lambda.interaction.handlers.interacting.InteractedBlockHandler.setPendingConfigs -import com.lambda.interaction.handlers.interacting.InteractedBlockHandler.startPending import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest import com.lambda.module.modules.world.AutoSign.signWriteDelay import com.lambda.threading.runConcurrent diff --git a/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractRequest.kt b/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractRequest.kt index 80d6a859f..b5baa4a98 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractRequest.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractRequest.kt @@ -25,52 +25,63 @@ import com.lambda.interaction.construction.simulation.result.BuildResult import com.lambda.interaction.construction.simulation.result.Dependent import com.lambda.interaction.construction.simulation.result.results.InteractResult import com.lambda.interaction.managers.Request +import com.lambda.interaction.managers.interacting.InteractRequest.PlaceRequestMarker import com.lambda.threading.runSafe import com.lambda.util.BlockUtils.blockState import com.lambda.util.BlockUtils.matches import net.minecraft.util.math.BlockPos -data class InteractRequest private constructor( +data class InteractRequest( val contexts: Collection, val pendingInteractions: MutableCollection, - private val automated: Automated, override val nowOrNothing: Boolean = false, + private val automated: Automated, + val onPlace: (SafeContext.(BlockPos) -> Unit)? ) : Request(), Automated by automated { override val requestId = ++requestCount override val tickStageMask get() = interactConfig.tickStageMask - var onPlace: (SafeContext.(BlockPos) -> Unit)? = null - override val done: Boolean get() = runSafe { contexts.all { it.expectedState.matches(blockState(it.blockPos)) } } == true @DslMarker - annotation class PlaceRequestDsl + annotation class PlaceRequestMarker - @PlaceRequestDsl + @PlaceRequestMarker override fun submit(queueIfMismatchedStage: Boolean) = InteractManager.request(this, queueIfMismatchedStage) - @PlaceRequestDsl - class PlaceRequestBuilder( - contexts: Collection, - pendingInteractions: MutableCollection, - nowOrNothing: Boolean, - automated: Automated - ) { - val request = InteractRequest(contexts, pendingInteractions, automated, nowOrNothing) + companion object { + var requestCount = 0 + } +} + +@PlaceRequestMarker +class PlaceRequestBuilder( + private val contexts: Collection, + private val pendingInteractions: MutableCollection, + private val nowOrNothing: Boolean, + private val automated: Automated +) { + private var onPlace: (SafeContext.(BlockPos) -> Unit)? = null - fun onPlace(callback: SafeContext.(BlockPos) -> Unit) { - request.onPlace = callback - } + fun onPlace(callback: SafeContext.(BlockPos) -> Unit) { + onPlace = callback } - companion object { - var requestCount = 0 + @PlaceRequestMarker + private fun build() = + InteractRequest( + contexts, + pendingInteractions, + nowOrNothing, + automated, + onPlace + ) - @PlaceRequestDsl + companion object { @JvmName("interactRequest1") context(automated: Automated) fun Collection.interactRequest( @@ -80,7 +91,6 @@ data class InteractRequest private constructor( ) = asSequence() .interactRequest(pendingInteractions, nowOrNothing, builder) - @PlaceRequestDsl @JvmName("interactRequest2") context(automated: Automated) fun Sequence.interactRequest( @@ -95,16 +105,17 @@ data class InteractRequest private constructor( .takeIf { it.isNotEmpty() } ?.let { automated.interactRequest(it, pendingInteractions, nowOrNothing, builder) } - @PlaceRequestDsl @JvmName("interactRequest3") fun Automated.interactRequest( contexts: Collection, pendingInteractions: MutableCollection, nowOrNothing: Boolean = false, builder: (PlaceRequestBuilder.() -> Unit)? = null - ) = PlaceRequestBuilder(contexts, pendingInteractions, nowOrNothing, this).apply { builder?.invoke(this) }.build() - - @PlaceRequestDsl - fun PlaceRequestBuilder.build() = request + ) = PlaceRequestBuilder( + contexts, + pendingInteractions, + nowOrNothing, + this + ).apply { builder?.invoke(this) }.build() } } diff --git a/src/main/kotlin/com/lambda/interaction/managers/rotating/RotationRequest.kt b/src/main/kotlin/com/lambda/interaction/managers/rotating/RotationRequest.kt index be39fa5e5..46b385b1e 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/rotating/RotationRequest.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/rotating/RotationRequest.kt @@ -22,18 +22,19 @@ import com.lambda.context.SafeContext import com.lambda.interaction.handlers.BaritoneHandler import com.lambda.interaction.managers.Request import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.requestCount +import com.lambda.interaction.managers.rotating.IRotationRequest.Full +import com.lambda.interaction.managers.rotating.IRotationRequest.Pitch +import com.lambda.interaction.managers.rotating.IRotationRequest.Yaw import com.lambda.interaction.managers.rotating.Rotation.Companion.dist import com.lambda.interaction.managers.rotating.Rotation.Companion.rotation import com.lambda.interaction.managers.rotating.Rotation.Companion.wrap +import com.lambda.interaction.managers.rotating.RotationRequest.RotationRequestMarker import com.lambda.threading.runSafe import com.lambda.util.collections.UpdatableLazy import com.lambda.util.collections.updatableLazy import kotlin.math.abs import kotlin.math.hypot -@DslMarker -annotation class RotationRequestDsl - abstract class RotationRequest(automated: Automated) : Request(), Automated by automated { override val requestId = requestCount++ override val tickStageMask = automated.rotationConfig.tickStageMask @@ -41,7 +42,10 @@ abstract class RotationRequest(automated: Automated) : Request(), Automated by a abstract infix fun dist(rotation: Rotation): Double - @RotationRequestDsl + @DslMarker + annotation class RotationRequestMarker + + @RotationRequestMarker override fun submit(queueIfMismatchedStage: Boolean) = RotationManager.request(this, queueIfMismatchedStage) } @@ -136,72 +140,61 @@ interface IRotationRequest : Automated { interface PitchRot : IRotationRequest { val pitch: UpdatableLazy } interface FullRot : YawRot, PitchRot { val rotation: UpdatableLazy } - class RotationRequestBuilder { - var pitchBuilder: (SafeContext.() -> Double)? = null - var yawBuilder: (SafeContext.() -> Double)? = null - var rotationBuilder: (SafeContext.() -> Rotation)? = null + companion object { + var requestCount = 0 + } +} + +@RotationRequestMarker +class RotationRequestBuilder { + private var pitchBuilder: (SafeContext.() -> Double)? = null + private var yawBuilder: (SafeContext.() -> Double)? = null + private var rotationBuilder: (SafeContext.() -> Rotation)? = null - @JvmName("yawBuilder1") - @RotationRequestDsl - fun yaw(builder: SafeContext.() -> Double) { yawBuilder = builder } + @JvmName("yawBuilder1") + fun yaw(builder: SafeContext.() -> Double) { yawBuilder = builder } - @JvmName("yawBuilder2") - @RotationRequestDsl - fun yaw(builder: SafeContext.() -> Float) { yawBuilder = { builder().toDouble() } } + @JvmName("yawBuilder2") + fun yaw(builder: SafeContext.() -> Float) { yawBuilder = { builder().toDouble() } } - @RotationRequestDsl - fun yaw(yaw: Double) { yawBuilder = { yaw } } + fun yaw(yaw: Double) { yawBuilder = { yaw } } - @RotationRequestDsl - fun yaw(yaw: Float) { yawBuilder = { yaw.toDouble() } } + fun yaw(yaw: Float) { yawBuilder = { yaw.toDouble() } } - @JvmName("pitchBuilder1") - @RotationRequestDsl - fun pitch(builder: SafeContext.() -> Double) { pitchBuilder = builder } + @JvmName("pitchBuilder1") + fun pitch(builder: SafeContext.() -> Double) { pitchBuilder = builder } - @JvmName("pitchBuilder2") - @RotationRequestDsl - fun pitch(builder: SafeContext.() -> Float) { pitchBuilder = { builder().toDouble() } } + @JvmName("pitchBuilder2") + fun pitch(builder: SafeContext.() -> Float) { pitchBuilder = { builder().toDouble() } } - @RotationRequestDsl - fun pitch(pitch: Double) { pitchBuilder = { pitch } } + fun pitch(pitch: Double) { pitchBuilder = { pitch } } - @RotationRequestDsl - fun pitch(pitch: Float) { pitchBuilder = { pitch.toDouble() } } + fun pitch(pitch: Float) { pitchBuilder = { pitch.toDouble() } } - @RotationRequestDsl - fun rotation(builder: SafeContext.() -> Rotation) { rotationBuilder = builder } + fun rotation(builder: SafeContext.() -> Rotation) { rotationBuilder = builder } - @RotationRequestDsl - fun rotation(yaw: Double, pitch: Double) { rotationBuilder = { Rotation(yaw, pitch) } } + fun rotation(yaw: Double, pitch: Double) { rotationBuilder = { Rotation(yaw, pitch) } } - @RotationRequestDsl - fun rotation(yaw: Float, pitch: Float) { rotationBuilder = { Rotation(yaw, pitch) } } + fun rotation(yaw: Float, pitch: Float) { rotationBuilder = { Rotation(yaw, pitch) } } - @RotationRequestDsl - fun rotation(rotation: Rotation) { rotationBuilder = { rotation } } + fun rotation(rotation: Rotation) { rotationBuilder = { rotation } } + + context(automated: Automated) + private fun build(): RotationRequest { + val yawBuilder = yawBuilder + val pitchBuilder = pitchBuilder + val rotationBuilder = rotationBuilder + return when { + rotationBuilder != null -> Full(automated, rotationBuilder) + yawBuilder != null && pitchBuilder != null -> Full(automated) { Rotation(yawBuilder(), pitchBuilder()) } + yawBuilder != null -> Yaw(automated, yawBuilder) + pitchBuilder != null -> Pitch(automated, pitchBuilder) + else -> throw IllegalArgumentException("Must specify at least one rotation value to build a rotation request") + } } companion object { - var requestCount = 0 - - @RotationRequestDsl fun Automated.rotationRequest(builder: RotationRequestBuilder.() -> Unit) = RotationRequestBuilder().apply(builder).build() - - @RotationRequestDsl - context(automated: Automated) - private fun RotationRequestBuilder.build(): RotationRequest { - val yawBuilder = yawBuilder - val pitchBuilder = pitchBuilder - val rotationBuilder = rotationBuilder - return when { - rotationBuilder != null -> Full(automated, rotationBuilder) - yawBuilder != null && pitchBuilder != null -> Full(automated) { Rotation(yawBuilder(), pitchBuilder()) } - yawBuilder != null -> Yaw(automated, yawBuilder) - pitchBuilder != null -> Pitch(automated, pitchBuilder) - else -> throw IllegalArgumentException("Must specify at least one rotation value to build a rotation request") - } - } } } diff --git a/src/main/kotlin/com/lambda/module/hud/Baritone.kt b/src/main/kotlin/com/lambda/module/hud/Baritone.kt index 5551231da..601602b71 100644 --- a/src/main/kotlin/com/lambda/module/hud/Baritone.kt +++ b/src/main/kotlin/com/lambda/module/hud/Baritone.kt @@ -18,8 +18,8 @@ package com.lambda.module.hud import com.lambda.gui.dsl.ImGuiBuilder -import com.lambda.interaction.handlers.BaritoneHandler import com.lambda.interaction.construction.simulation.BuildGoal +import com.lambda.interaction.handlers.BaritoneHandler import com.lambda.module.HudModule import com.lambda.module.tag.ModuleTag diff --git a/src/main/kotlin/com/lambda/module/modules/combat/AutoArmor.kt b/src/main/kotlin/com/lambda/module/modules/combat/AutoArmor.kt index 395fd1767..dc827d9be 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/AutoArmor.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/AutoArmor.kt @@ -25,13 +25,13 @@ import com.lambda.config.withEdits import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.inventory.container.containers.ArmorContainer +import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.EnchantmentUtils.getEnchantment import com.lambda.util.item.ItemUtils.armorSlot -import com.lambda.util.player.SlotUtils.armorSlots -import com.lambda.util.player.SlotUtils.hotbarAndInventorySlots import net.minecraft.component.DataComponentTypes import net.minecraft.enchantment.Enchantment import net.minecraft.enchantment.Enchantments @@ -98,7 +98,7 @@ object AutoArmor : Module( }.thenByDescending { slot -> slot.stack.getEnchantment(Enchantments.UNBREAKING) + slot.stack.getEnchantment(Enchantments.MENDING) - } + }.thenBy { it.index in 0..8 } init { setDefaultAutomationConfig() @@ -117,9 +117,9 @@ object AutoArmor : Module( fun SafeContext.tick() { tickedThisTick = true - val armorSlots = player.armorSlots + val armorSlots = ArmorContainer.slots - val swappable = player.hotbarAndInventorySlots + val swappable = HotbarAndInventoryContainer.slots .filter { it.stack.isEquipable && (!ignoreBinding || it.stack.getEnchantment(Enchantments.BINDING_CURSE) <= 0) } .sortedWith(SORTER) .distinctBy { it.stack.armorSlot } @@ -142,9 +142,9 @@ object AutoArmor : Module( }.submit() } - context(safeContext: SafeContext) + context(_: SafeContext) private val ItemStack.isEquipable - get() = safeContext.player.armorSlots.any { it.canInsert(this) } + get() = ArmorContainer.slots.any { it.canInsert(this) } private enum class Protection(val enchant: RegistryKey) { Protection(Enchantments.PROTECTION), diff --git a/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt b/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt index 76708df58..348045061 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt @@ -24,8 +24,8 @@ import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.handlers.FriendHandler +import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.select import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest -import com.lambda.interaction.inventory.StackSelection.Companion.select import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.combat.CombatUtils.hasDeadlyCrystal diff --git a/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt b/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt index 2dfdd7150..f5b071e0b 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt @@ -27,14 +27,14 @@ import com.lambda.context.SafeContext import com.lambda.event.events.EntityEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.hotbar.HotbarRequest -import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest -import com.lambda.interaction.managers.rotating.Rotation.Companion.rotationTo -import com.lambda.interaction.managers.rotating.RotationManager -import com.lambda.interaction.inventory.StackSelection.Companion.selectStack import com.lambda.interaction.handlers.ContainerHandler.transfer +import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.interaction.inventory.container.containers.OffHandContainer +import com.lambda.interaction.managers.hotbar.HotbarRequest +import com.lambda.interaction.managers.rotating.Rotation.Companion.rotationTo +import com.lambda.interaction.managers.rotating.RotationManager +import com.lambda.interaction.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.threading.runSafe @@ -55,7 +55,6 @@ import com.lambda.util.math.getHitVec import com.lambda.util.math.minus import com.lambda.util.math.plus import com.lambda.util.player.RotationUtils.getVisibleSurfaces -import com.lambda.util.player.SlotUtils.hotbarStacks import com.lambda.util.world.fastEntitySearch import net.minecraft.block.Blocks import net.minecraft.entity.Entity @@ -361,7 +360,7 @@ object CrystalAura : Module( val entitiesNearby = fastEntitySearch(3.5, pos) val crystals = entitiesNearby.filterIsInstance() - val otherEntities = entitiesNearby - crystals + player + val otherEntities = entitiesNearby - crystals.toSet() + player if (otherEntities.any { it.boundingBox.intersects(crystalBox) @@ -487,14 +486,14 @@ object CrystalAura : Module( (swapHand == Hand.OFF_HAND && player.offHandStack.item != selection.item) ) runSafeAutomated { if (!swap) return@runSafe - var crystalSlot = player.hotbarStacks.indexOfFirst { selection.filterStack(it) } + var crystalSlot = HotbarContainer.stacks.indexOfFirst { selection.filterStack(it) } if (crystalSlot < 0) { val swapTo = when (swapHand) { Hand.MAIN_HAND -> HotbarContainer Hand.OFF_HAND -> OffHandContainer } if (!selection.transfer(swapTo)) return@runSafe - crystalSlot = player.hotbarStacks.indexOfFirst { selection.filterStack(it) } + crystalSlot = HotbarContainer.stacks.indexOfFirst { selection.filterStack(it) } } if (!HotbarRequest(crystalSlot, this).submit().done) return@runSafe } diff --git a/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt b/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt index dc3f79d4a..bc64a3c70 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt @@ -28,9 +28,10 @@ import com.lambda.context.SafeContext import com.lambda.event.events.InventoryEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack +import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.interaction.managers.hotbar.HotbarRequest -import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest -import com.lambda.interaction.inventory.StackSelection.Companion.selectStack +import com.lambda.interaction.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.threading.runSafeAutomated @@ -39,7 +40,6 @@ import com.lambda.util.item.ItemStackUtils.attackDamage import com.lambda.util.item.ItemStackUtils.attackSpeed import com.lambda.util.math.random import com.lambda.util.player.RotationUtils.lookAtEntity -import com.lambda.util.player.SlotUtils.hotbarStacks import net.minecraft.entity.Entity import net.minecraft.item.ItemStack import net.minecraft.network.packet.c2s.play.PlayerInteractEntityC2SPacket @@ -119,12 +119,17 @@ object KillAura : Module( } if (swap) { - val selection = selectStack().sortByDescending { - damageMode.block(this, it) - } - - selection.bestItemMatch(player.hotbarStacks)?.let { bestStack -> - val slotId = player.hotbarStacks.indexOf(bestStack) + val selection = + selectStack { + sortedWith { + compareByDescending { + damageMode.block(this@listen, it.stack) + } + } + } + + selection.bestMatch(HotbarContainer.stacks)?.let { bestStack -> + val slotId = HotbarContainer.stacks.indexOf(bestStack) if (!HotbarRequest(slotId, this@KillAura, nowOrNothing = false).submit().done) return@listen } } diff --git a/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt b/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt index af36ab55f..79848af5d 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt @@ -22,9 +22,10 @@ import com.lambda.config.ConfigEditor.hideBlock import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig import com.lambda.config.withEdits import com.lambda.context.SafeContext -import com.lambda.interaction.handlers.FriendHandler.isFriend import com.lambda.interaction.construction.blueprint.TickingBlueprint.Companion.tickingBlueprint import com.lambda.interaction.construction.verify.TargetState +import com.lambda.interaction.handlers.FriendHandler.isFriend +import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.task.RootTask.run @@ -34,7 +35,6 @@ import com.lambda.util.BlockUtils.blockState import com.lambda.util.extension.shrinkByEpsilon import com.lambda.util.item.ItemUtils.block import com.lambda.util.math.flooredBlockPos -import com.lambda.util.player.SlotUtils.hotbarAndInventoryStacks import com.lambda.util.world.entitySearch import net.minecraft.block.Blocks import net.minecraft.client.network.OtherClientPlayerEntity @@ -69,7 +69,7 @@ object PlayerTrap : Module( onEnable { task = tickingBlueprint { - val block = player.hotbarAndInventoryStacks.firstOrNull { + val block = HotbarAndInventoryContainer.stacks.firstOrNull { it.item is BlockItem && blocks.contains(it.item.block) }?.item?.block ?: return@tickingBlueprint emptyMap() val targetPlayer = if (self) player diff --git a/src/main/kotlin/com/lambda/module/modules/combat/Surround.kt b/src/main/kotlin/com/lambda/module/modules/combat/Surround.kt index 017beec2c..a46767893 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/Surround.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/Surround.kt @@ -23,6 +23,7 @@ import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomat import com.lambda.config.withEdits import com.lambda.interaction.construction.blueprint.TickingBlueprint.Companion.tickingBlueprint import com.lambda.interaction.construction.verify.TargetState +import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer import com.lambda.module.Module import com.lambda.module.modules.combat.PlayerTrap.getTrapPositions import com.lambda.module.tag.ModuleTag @@ -30,7 +31,6 @@ import com.lambda.task.RootTask.run import com.lambda.task.Task import com.lambda.task.tasks.BuildTask.Companion.build import com.lambda.util.item.ItemUtils.block -import com.lambda.util.player.SlotUtils.hotbarAndInventoryStacks import net.minecraft.block.Blocks import net.minecraft.item.BlockItem @@ -59,7 +59,7 @@ object Surround : Module( onEnable { task = tickingBlueprint { - val block = player.hotbarAndInventoryStacks.firstOrNull { + val block = HotbarAndInventoryContainer.stacks.firstOrNull { it.item is BlockItem && blocks.contains(it.item.block) }?.item?.block ?: return@tickingBlueprint emptyMap() getTrapPositions(player) diff --git a/src/main/kotlin/com/lambda/module/modules/combat/autodisconnect/AutoDisconnect.kt b/src/main/kotlin/com/lambda/module/modules/combat/autodisconnect/AutoDisconnect.kt index c4ca17648..d8c912926 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/autodisconnect/AutoDisconnect.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/autodisconnect/AutoDisconnect.kt @@ -26,6 +26,7 @@ import com.lambda.event.events.TickEvent import com.lambda.event.events.WorldEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.handlers.FriendHandler +import com.lambda.interaction.inventory.container.containers.PlayerContainer import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.sound.SoundHandler.playSound @@ -37,10 +38,8 @@ import com.lambda.util.TickTimer import com.lambda.util.combat.CombatUtils.crystalDamage import com.lambda.util.combat.DamageUtils.isFallDeadly import com.lambda.util.extension.fullHealth -import com.lambda.util.extension.getBlockState import com.lambda.util.extension.tickDeltaF import com.lambda.util.player.PlayerUtils.isIn2b2tQueue -import com.lambda.util.player.SlotUtils.allStacks import com.lambda.util.player.SlotUtils.armorSlots import com.lambda.util.text.buildText import com.lambda.util.text.color @@ -389,7 +388,7 @@ object AutoDisconnect : Module( } else null }), Totem("Totem", { totem }, { totemSmart }, { - val totemCount = player.allStacks.count { it.item == Items.TOTEM_OF_UNDYING } + val totemCount = PlayerContainer.stacks.count { it.item == Items.TOTEM_OF_UNDYING } if (totemCount < minTotems) { buildText { literal("Only ") diff --git a/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt b/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt index 599e9dc87..955694f8c 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt @@ -19,7 +19,7 @@ package com.lambda.module.modules.debug import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.inventory.StackSelection.Companion.select +import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.select import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.task.RootTask.run diff --git a/src/main/kotlin/com/lambda/module/modules/debug/RotationTest.kt b/src/main/kotlin/com/lambda/module/modules/debug/RotationTest.kt index 6e8a3e4a0..aeaeb7a3f 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/RotationTest.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/RotationTest.kt @@ -19,7 +19,7 @@ package com.lambda.module.modules.debug import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest +import com.lambda.interaction.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.player.RotationUtils.lookAt diff --git a/src/main/kotlin/com/lambda/module/modules/movement/AutoSpiral.kt b/src/main/kotlin/com/lambda/module/modules/movement/AutoSpiral.kt index ba2af3e0f..7548a0e24 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/AutoSpiral.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/AutoSpiral.kt @@ -22,7 +22,7 @@ import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.handlers.BaritoneHandler -import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest +import com.lambda.interaction.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.BlockPosIterators diff --git a/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt b/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt index 48bc00c39..35bec0adf 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt @@ -29,10 +29,12 @@ import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.handlers.GlideHandler +import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.select +import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack +import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer +import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.interaction.managers.hotbar.HotbarRequest import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest -import com.lambda.interaction.inventory.StackSelection.Companion.select -import com.lambda.interaction.inventory.StackSelection.Companion.selectStack import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.threading.runSafe @@ -41,8 +43,6 @@ import com.lambda.util.KeyCode import com.lambda.util.Mouse import com.lambda.util.player.PlayerUtils import com.lambda.util.player.PlayerUtils.canStartGliding -import com.lambda.util.player.SlotUtils.hotbarAndInventoryStacks -import com.lambda.util.player.SlotUtils.hotbarStacks import net.minecraft.client.network.ClientPlayerEntity import net.minecraft.item.Items import net.minecraft.network.packet.c2s.play.HandSwingC2SPacket @@ -85,7 +85,7 @@ object BetterFirework : Module( val ClientPlayerEntity.hasFireworks: Boolean get() = Items.FIREWORK_ROCKET.select() - .filterStacks(hotbarAndInventoryStacks) + .filterStacks(HotbarAndInventoryContainer.stacks) .isNotEmpty() || offHandStack.item == Items.FIREWORK_ROCKET context(_: SafeContext) @@ -156,11 +156,11 @@ object BetterFirework : Module( * Return true if a firework has been used */ fun SafeContext.startFirework(inventory: Boolean) { - val stack = selectStack(count = 1) { isItem(Items.FIREWORK_ROCKET) } + val stack = selectStack(1) { isItem(Items.FIREWORK_ROCKET) } - stack.bestItemMatch(player.hotbarStacks) + stack.bestMatch(HotbarContainer.stacks) ?.let { - val request = HotbarRequest(player.hotbarStacks.indexOf(it), this@BetterFirework, keepTicks = 0) + val request = HotbarRequest(HotbarContainer.stacks.indexOf(it), this@BetterFirework, keepTicks = 0) .submit(queueIfMismatchedStage = false) if (request.done) { interaction.interactItem(player, Hand.MAIN_HAND) @@ -171,10 +171,10 @@ object BetterFirework : Module( if (!inventory) return - stack.bestItemMatch(player.hotbarAndInventoryStacks) + stack.bestMatch(HotbarContainer.stacks) ?.let { - val swapSlotId = player.hotbarAndInventoryStacks.indexOf(it) - val hotbarSlotToSwapWith = player.hotbarStacks.find { slot -> slot.isEmpty }?.let { slot -> player.hotbarStacks.indexOf(slot) } ?: 8 + val swapSlotId = HotbarAndInventoryContainer.stacks.indexOf(it) + val hotbarSlotToSwapWith = HotbarContainer.stacks.find { slot -> slot.isEmpty }?.let { slot -> HotbarContainer.stacks.indexOf(slot) } ?: 8 inventoryRequest { swap(swapSlotId, hotbarSlotToSwapWith) diff --git a/src/main/kotlin/com/lambda/module/modules/movement/ElytraAltitudeControl.kt b/src/main/kotlin/com/lambda/module/modules/movement/ElytraAltitudeControl.kt index fc5ad0fab..1416e7428 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/ElytraAltitudeControl.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/ElytraAltitudeControl.kt @@ -23,7 +23,7 @@ import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomat import com.lambda.config.withEdits import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest +import com.lambda.interaction.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module import com.lambda.module.modules.movement.BetterFirework.startFirework import com.lambda.module.tag.ModuleTag diff --git a/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt b/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt index 0ccaa9b9e..4e3fa5740 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt @@ -26,7 +26,7 @@ import com.lambda.event.events.ClientEvent import com.lambda.event.events.MovementEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest +import com.lambda.interaction.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.NamedEnum diff --git a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt index 11242cb2b..640ef95e7 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt @@ -25,6 +25,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.handlers.GlideHandler.ELYTRA_SELECTION +import com.lambda.interaction.inventory.container.containers.ArmorContainer +import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer import com.lambda.interaction.managers.hotbar.HotbarRequest import com.lambda.interaction.managers.inventory.InventoryManager import com.lambda.interaction.managers.inventory.InventoryRequest @@ -33,8 +35,6 @@ import com.lambda.module.modules.movement.elytrafly.ElytraFly.FlyMode import com.lambda.module.modules.movement.elytrafly.ElytraFly.fakeFly import com.lambda.threading.runSafe import com.lambda.util.CommunicationUtils.logError -import com.lambda.util.player.SlotUtils.armorSlots -import com.lambda.util.player.SlotUtils.hotbarAndInventorySlots import net.minecraft.entity.Entity import net.minecraft.entity.EquipmentSlot import net.minecraft.item.Items @@ -93,7 +93,7 @@ abstract class ElytraFlyMode( } val elytraInHotbar = elytraSlot.index in 0..8 - val chestSlot = player.armorSlots.getOrNull(1) ?: return false + val chestSlot = ArmorContainer.slots.getOrNull(1) ?: return false if (elytraInHotbar) { val hotbarRequest = HotbarRequest( @@ -126,7 +126,7 @@ abstract class ElytraFlyMode( open fun interrupt() {} fun SafeContext.findElytra(): Slot? = - ELYTRA_SELECTION.filterSlots(player.hotbarAndInventorySlots).minByOrNull { it.index } + ELYTRA_SELECTION.filterSlots(HotbarAndInventoryContainer.slots).minByOrNull { it.index } protected fun SafeContext.startFly() { player.setFlag(Entity.GLIDING_FLAG_INDEX, true) diff --git a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/BounceElytraFly.kt b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/BounceElytraFly.kt index 25dcee4f4..e765ca705 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/BounceElytraFly.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/BounceElytraFly.kt @@ -27,8 +27,8 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.handlers.BaritoneHandler import com.lambda.interaction.handlers.GlideHandler -import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest import com.lambda.interaction.managers.rotating.RotationManager +import com.lambda.interaction.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.hud.Speedometer import com.lambda.module.modules.movement.elytrafly.ElytraFly.FlyMode import com.lambda.module.modules.movement.elytrafly.ElytraFly.fakeFly diff --git a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/GrimControlElytraFly.kt b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/GrimControlElytraFly.kt index d7620c087..22b8e3048 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/GrimControlElytraFly.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/GrimControlElytraFly.kt @@ -18,14 +18,15 @@ package com.lambda.module.modules.movement.elytrafly.modes import com.lambda.config.Config -import com.lambda.context.SafeContext import com.lambda.event.events.PacketEvent import com.lambda.event.events.PlayerPacketEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest +import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.select +import com.lambda.interaction.inventory.container.containers.HotbarContainer +import com.lambda.interaction.inventory.container.containers.InventoryContainer import com.lambda.interaction.managers.rotating.RotationManager -import com.lambda.interaction.inventory.StackSelection.Companion.select +import com.lambda.interaction.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.modules.movement.BetterFirework.startFirework import com.lambda.module.modules.movement.elytrafly.ElytraFly.FlyMode import com.lambda.module.modules.movement.elytrafly.ElytraFlyMode @@ -35,8 +36,6 @@ import com.lambda.util.TickTimer import com.lambda.util.Timer import com.lambda.util.math.MathUtils.toFloat import com.lambda.util.player.PlayerUtils.hasFirework -import com.lambda.util.player.SlotUtils.hotbarStacks -import com.lambda.util.player.SlotUtils.inventoryStacks import net.minecraft.component.DataComponentTypes import net.minecraft.item.ItemStack import net.minecraft.item.Items @@ -135,8 +134,8 @@ class GrimControlElytraFly( } } - private fun SafeContext.findFirework(): ItemStack? { + private fun findFirework(): ItemStack? { val stack = Items.FIREWORK_ROCKET.select() - return stack.bestItemMatch(player.hotbarStacks) ?: if (inventory) stack.bestItemMatch(player.inventoryStacks) else null + return stack.bestMatch(HotbarContainer.stacks) ?: if (inventory) stack.bestMatch(InventoryContainer.stacks) else null } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/module/modules/player/AntiAim.kt b/src/main/kotlin/com/lambda/module/modules/player/AntiAim.kt index 77ba3ca05..b078fcb14 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/AntiAim.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/AntiAim.kt @@ -21,8 +21,8 @@ import com.lambda.config.entries.Setting.Companion.onValueChange import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest import com.lambda.interaction.managers.rotating.Rotation.Companion.rotationTo +import com.lambda.interaction.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.math.distSq diff --git a/src/main/kotlin/com/lambda/module/modules/player/AutoElytraSwap.kt b/src/main/kotlin/com/lambda/module/modules/player/AutoElytraSwap.kt index 091341b9e..b912ec751 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/AutoElytraSwap.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/AutoElytraSwap.kt @@ -22,14 +22,14 @@ import com.lambda.interaction.handlers.GlideHandler.CHESTPLATE_SELECTION import com.lambda.interaction.handlers.GlideHandler.ELYTRA_SELECTION import com.lambda.interaction.handlers.GlideHandler.manuallySwapped import com.lambda.interaction.handlers.GlideHandler.swapped +import com.lambda.interaction.inventory.container.containers.ArmorContainer +import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest import com.lambda.module.Module import com.lambda.module.modules.combat.AutoArmor import com.lambda.module.tag.ModuleTag import com.lambda.util.CommunicationUtils.warn import com.lambda.util.player.PlayerUtils.canGlideWithChestPiece -import com.lambda.util.player.SlotUtils.armorSlots -import com.lambda.util.player.SlotUtils.hotbarAndInventorySlots import net.minecraft.screen.slot.Slot object AutoElytraSwap : Module( @@ -47,7 +47,7 @@ object AutoElytraSwap : Module( context(safeContext: SafeContext) fun manualSwap(elytra: Boolean): Boolean { val swapSlot = - safeContext.player.hotbarAndInventorySlots.let { slots -> + HotbarAndInventoryContainer.slots.let { slots -> if (elytra) ELYTRA_SELECTION.filterSlots(slots) else CHESTPLATE_SELECTION.filterSlots(slots) }.minByOrNull { it.index } ?: run { @@ -59,7 +59,7 @@ object AutoElytraSwap : Module( } private fun SafeContext.swapWithChestplate(swapSlot: Slot): Boolean { - val chestplateSlot = player.armorSlots[1] + val chestplateSlot = ArmorContainer.slots.getOrNull(1) ?: return false return AutoElytraSwap.inventoryRequest { if (swapSlot.index in 0..8) swap(chestplateSlot.id, swapSlot.index) else { diff --git a/src/main/kotlin/com/lambda/module/modules/player/FastBreak.kt b/src/main/kotlin/com/lambda/module/modules/player/FastBreak.kt index e9d37d309..4b0ce1787 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/FastBreak.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/FastBreak.kt @@ -27,7 +27,7 @@ import com.lambda.event.events.PlayerEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.construction.simulation.context.BuildContext -import com.lambda.interaction.managers.breaking.BreakRequest.Companion.breakRequest +import com.lambda.interaction.managers.breaking.BreakRequestBuilder.Companion.breakRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.threading.runSafeAutomated diff --git a/src/main/kotlin/com/lambda/module/modules/player/InventoryMove.kt b/src/main/kotlin/com/lambda/module/modules/player/InventoryMove.kt index 0fdbccd43..833f0a7db 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/InventoryMove.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/InventoryMove.kt @@ -22,8 +22,8 @@ import com.lambda.config.blocks.RotationConfig import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.gui.LambdaScreen -import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest import com.lambda.interaction.managers.rotating.RotationMode +import com.lambda.interaction.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.InputUtils diff --git a/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt b/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt index ad179d3b4..1d8fd9e99 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt @@ -37,7 +37,7 @@ import com.lambda.interaction.construction.simulation.context.BreakContext import com.lambda.interaction.construction.simulation.context.BuildContext import com.lambda.interaction.construction.simulation.result.results.BreakResult import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.managers.breaking.BreakRequest.Companion.breakRequest +import com.lambda.interaction.managers.breaking.BreakRequestBuilder.Companion.breakRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.threading.runSafe diff --git a/src/main/kotlin/com/lambda/module/modules/player/Replay.kt b/src/main/kotlin/com/lambda/module/modules/player/Replay.kt index d5558a631..d0edbc647 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/Replay.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/Replay.kt @@ -29,15 +29,15 @@ import com.lambda.brigadier.CommandResult import com.lambda.config.blocks.RotationConfig import com.lambda.config.settings.complex.KeybindSetting.Companion.onPress import com.lambda.context.SafeContext -import com.lambda.interaction.handlers.TimerHandler import com.lambda.event.EventFlow.lambdaScope import com.lambda.event.events.MovementEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.gui.components.ClickGuiLayout -import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest +import com.lambda.interaction.handlers.TimerHandler import com.lambda.interaction.managers.rotating.Rotation import com.lambda.interaction.managers.rotating.RotationMode +import com.lambda.interaction.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module import com.lambda.module.modules.player.Replay.InputAction.Companion.toAction import com.lambda.module.tag.ModuleTag diff --git a/src/main/kotlin/com/lambda/module/modules/player/RotationLock.kt b/src/main/kotlin/com/lambda/module/modules/player/RotationLock.kt index 426b57c9a..307251bc7 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/RotationLock.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/RotationLock.kt @@ -23,8 +23,8 @@ import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomat import com.lambda.config.withEdits import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest import com.lambda.interaction.managers.rotating.RotationMode +import com.lambda.interaction.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import kotlin.math.roundToInt diff --git a/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt b/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt index 67f369774..664e7363d 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt @@ -24,12 +24,12 @@ import com.lambda.config.withEdits import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.inventory.container.containers.HotbarContainer +import com.lambda.interaction.inventory.container.containers.InventoryContainer import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.item.ItemStackUtils.slotId -import com.lambda.util.player.SlotUtils.hotbarStacks -import com.lambda.util.player.SlotUtils.inventoryStacks import net.minecraft.item.ItemStack import net.minecraft.item.Items @@ -53,7 +53,7 @@ object StackReplenish : Module( listen { if (player.currentScreenHandler.cursorStack.item !== Items.AIR) return@listen - player.hotbarStacks.forEach { stack -> checkReplenish(stack) } + HotbarContainer.stacks.forEach { stack -> checkReplenish(stack) } if (offhand) checkReplenish(player.offHandStack) } } @@ -62,7 +62,7 @@ object StackReplenish : Module( if (stack.count.toFloat() / stack.maxCount >= (minStackPercent.toFloat() / 100)) return if (!stack.isStackable) return - player.inventoryStacks.forEach { invStack -> + InventoryContainer.stacks.forEach { invStack -> if (!ItemStack.areItemsAndComponentsEqual(invStack, stack)) return@forEach val invId = invStack.slotId val completing = stack.count + invStack.count >= stack.maxCount diff --git a/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt b/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt index 160b949c8..a9984aa86 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt @@ -23,14 +23,13 @@ import com.lambda.config.withEdits import com.lambda.event.events.ContainerEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest import com.lambda.interaction.inventory.container.containers.HotbarContainer +import com.lambda.interaction.inventory.container.containers.InventoryContainer +import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.EnchantmentUtils.forEachEnchantment import com.lambda.util.EnchantmentUtils.getEnchantment -import com.lambda.util.player.SlotUtils.hotbarSlots -import com.lambda.util.player.SlotUtils.inventorySlots import net.minecraft.item.ItemStack import net.minecraft.screen.slot.Slot @@ -50,9 +49,9 @@ object ToolSaver : Module( } listen { - val endangeredStacks = player.hotbarSlots.filter { it.stack.isEndangered } + val endangeredStacks = HotbarContainer.slots.filter { it.stack.isEndangered } - val inventorySlots = player.inventorySlots + val inventorySlots = InventoryContainer.slots val swaps = endangeredStacks .mapNotNull { endangered -> val sorter = compareByDescending { swapSlot -> diff --git a/src/main/kotlin/com/lambda/module/modules/render/ContainerPreview.kt b/src/main/kotlin/com/lambda/module/modules/render/ContainerPreview.kt index a5fa8c7f5..2712076b1 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/ContainerPreview.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/ContainerPreview.kt @@ -27,8 +27,8 @@ import com.lambda.module.tag.ModuleTag import com.lambda.threading.runSafe import com.lambda.util.InputUtils.isSatisfied import com.lambda.util.KeyCode -import com.lambda.util.item.ItemStackUtils.bundleContents -import com.lambda.util.item.ItemStackUtils.shulkerBoxContents +import com.lambda.util.item.ItemStackUtils.bundleStacks +import com.lambda.util.item.ItemStackUtils.shulkerBoxStacks import com.lambda.util.item.ItemUtils.bundles import com.lambda.util.item.ItemUtils.shulkerBoxes import com.lambda.util.text.buildText @@ -261,7 +261,7 @@ object ContainerPreview : Module( private fun getContainerContents(stack: ItemStack): List { return when { - isShulkerBox(stack) -> stack.shulkerBoxContents + isShulkerBox(stack) -> stack.shulkerBoxStacks isEnderChest(stack) -> EnderChestContainer.stacks else -> emptyList() } @@ -360,7 +360,7 @@ object ContainerPreview : Module( val hash = container.hashCode() return containerCache.computeIfAbsent(hash) { - val contents = container.shulkerBoxContents + container.bundleContents + val contents = container.shulkerBoxStacks + container.bundleStacks if (contents.isEmpty()) return@computeIfAbsent ContainerPreviewInfo(null, false) val group = contents.filter { stack -> stack.item != Items.AIR } diff --git a/src/main/kotlin/com/lambda/module/modules/render/Freecam.kt b/src/main/kotlin/com/lambda/module/modules/render/Freecam.kt index 856071afd..1ff346388 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/Freecam.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/Freecam.kt @@ -29,9 +29,9 @@ import com.lambda.event.events.PlayerEvent import com.lambda.event.events.RenderEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest import com.lambda.interaction.managers.rotating.Rotation import com.lambda.interaction.managers.rotating.RotationMode +import com.lambda.interaction.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.threading.runSafe diff --git a/src/main/kotlin/com/lambda/module/modules/render/Nametags.kt b/src/main/kotlin/com/lambda/module/modules/render/Nametags.kt index 8368d108d..b097805d2 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/Nametags.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/Nametags.kt @@ -25,12 +25,12 @@ import com.lambda.config.Tab import com.lambda.config.blocks.EntitySelectionSettings import com.lambda.config.blocks.ScreenTextSettings import com.lambda.config.withEdits -import com.lambda.interaction.handlers.FriendHandler.isFriend import com.lambda.graphics.mc.RenderBuilder import com.lambda.graphics.mc.renderer.ImmediateRenderer.Companion.immediateRenderer import com.lambda.graphics.mc.renderer.RendererUtils.worldToScreenNormalized import com.lambda.graphics.text.FontHandler import com.lambda.graphics.util.DynamicAABB.Companion.interpolatedBox +import com.lambda.interaction.handlers.FriendHandler.isFriend import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.threading.runSafe diff --git a/src/main/kotlin/com/lambda/module/modules/render/Tracers.kt b/src/main/kotlin/com/lambda/module/modules/render/Tracers.kt index 7239f74dc..f2d4d074e 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/Tracers.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/Tracers.kt @@ -24,9 +24,9 @@ import com.lambda.config.blocks.EntityColorSettings import com.lambda.config.blocks.EntitySelectionSettings import com.lambda.config.blocks.ScreenLineSettings import com.lambda.config.withEdits -import com.lambda.interaction.handlers.FriendHandler.isFriend import com.lambda.graphics.mc.renderer.ImmediateRenderer.Companion.immediateRenderer import com.lambda.graphics.mc.renderer.RendererUtils.worldToScreenNormalized +import com.lambda.interaction.handlers.FriendHandler.isFriend import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.threading.runSafe diff --git a/src/main/kotlin/com/lambda/module/modules/world/AirPlace.kt b/src/main/kotlin/com/lambda/module/modules/world/AirPlace.kt index bfac64ee2..182d49707 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/AirPlace.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/AirPlace.kt @@ -33,7 +33,7 @@ import com.lambda.interaction.construction.simulation.BuildSimulator.simulate import com.lambda.interaction.construction.simulation.context.BuildContext import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.managers.interacting.InteractRequest -import com.lambda.interaction.managers.interacting.InteractRequest.Companion.interactRequest +import com.lambda.interaction.managers.interacting.PlaceRequestBuilder.Companion.interactRequest import com.lambda.interaction.managers.rotating.Rotation.Companion.rotation import com.lambda.module.Module import com.lambda.module.tag.ModuleTag diff --git a/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt b/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt index 2d541772f..7335d5399 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt @@ -34,11 +34,13 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.graphics.mc.renderer.ImmediateRenderer.Companion.immediateRenderer import com.lambda.graphics.util.DirectionMask -import com.lambda.interaction.handlers.BaritoneHandler import com.lambda.interaction.construction.verify.TargetState +import com.lambda.interaction.handlers.BaritoneHandler +import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack +import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer +import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.interaction.managers.hotbar.HotbarRequest import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest -import com.lambda.interaction.inventory.StackSelection.Companion.selectStack import com.lambda.module.Module import com.lambda.module.modules.world.AutoPortal.PosHandler.currAnchorPos import com.lambda.module.modules.world.AutoPortal.PosHandler.obiPositions @@ -58,8 +60,6 @@ import com.lambda.util.extension.tickDelta import com.lambda.util.math.lerp import com.lambda.util.math.setAlpha import com.lambda.util.math.vec3d -import com.lambda.util.player.SlotUtils.hotbarAndInventorySlots -import com.lambda.util.player.SlotUtils.hotbarSlots import net.minecraft.block.Blocks import net.minecraft.item.FlintAndSteelItem import net.minecraft.item.Items @@ -326,7 +326,7 @@ object AutoPortal : Module( val sel = selectStack(1) { isItem() } - val hotbarStack = sel.filterSlots(player.hotbarSlots).firstOrNull() + val hotbarStack = sel.filterSlots(HotbarContainer.slots).firstOrNull() if (hotbarStack != null) { val request = HotbarRequest( hotbarStack.index, @@ -338,14 +338,14 @@ object AutoPortal : Module( } val invSlot = - if (inventory) sel.filterSlots(player.hotbarAndInventorySlots).firstOrNull() + if (inventory) sel.filterSlots(HotbarAndInventoryContainer.slots).firstOrNull() else null if (invSlot == null) { failure("No Flint and Steel!") return } val hotbarSlotToSwapWith = - player.hotbarSlots.find { slot -> + HotbarContainer.slots.find { slot -> slot.stack.isEmpty }?.index ?: 8 diff --git a/src/main/kotlin/com/lambda/module/modules/world/AutoVillagerCycle.kt b/src/main/kotlin/com/lambda/module/modules/world/AutoVillagerCycle.kt index 4ee65b7c1..bb68c9222 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/AutoVillagerCycle.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/AutoVillagerCycle.kt @@ -30,7 +30,7 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.construction.blueprint.Blueprint.Companion.toStructure import com.lambda.interaction.construction.blueprint.StaticBlueprint.Companion.toBlueprint import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest +import com.lambda.interaction.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.sound.SoundHandler.playSound diff --git a/src/main/kotlin/com/lambda/module/modules/world/HighwayTools.kt b/src/main/kotlin/com/lambda/module/modules/world/HighwayTools.kt index d03db7fd6..c288468d9 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/HighwayTools.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/HighwayTools.kt @@ -20,10 +20,10 @@ package com.lambda.module.modules.world import com.lambda.config.ConfigEditor.editTypedSettings import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig import com.lambda.config.withEdits -import com.lambda.interaction.handlers.BaritoneHandler import com.lambda.interaction.construction.blueprint.Blueprint.Companion.emptyStructure import com.lambda.interaction.construction.blueprint.PropagatingBlueprint.Companion.propagatingBlueprint import com.lambda.interaction.construction.verify.TargetState +import com.lambda.interaction.handlers.BaritoneHandler import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.task.RootTask.run diff --git a/src/main/kotlin/com/lambda/module/modules/world/MapDownloader.kt b/src/main/kotlin/com/lambda/module/modules/world/MapDownloader.kt index b4de4c5e7..4c6ed0869 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/MapDownloader.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/MapDownloader.kt @@ -19,12 +19,12 @@ package com.lambda.module.modules.world import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.inventory.container.containers.PlayerContainer import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.FileUtils.locationBoundDirectory import com.lambda.util.FolderRegistry import com.lambda.util.StringUtils.hashString -import com.lambda.util.player.SlotUtils.allStacks import com.lambda.util.world.entitySearch import net.minecraft.block.MapColor import net.minecraft.entity.decoration.ItemFrameEntity @@ -43,7 +43,7 @@ object MapDownloader : Module( listen { val mapStates = entitySearch(128.0) .mapNotNull { FilledMapItem.getMapState(it.heldItemStack, world) } + - player.allStacks.mapNotNull { FilledMapItem.getMapState(it, world) } + PlayerContainer.stacks.mapNotNull { FilledMapItem.getMapState(it, world) } mapStates.forEach { map -> val name = map.hash 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 32a761990..07fc716de 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/Scaffold.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/Scaffold.kt @@ -31,9 +31,9 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.construction.simulation.BuildSimulator.simulate import com.lambda.interaction.construction.simulation.context.BuildContext import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.managers.interacting.InteractRequest.Companion.interactRequest -import com.lambda.interaction.inventory.StackSelection.Companion.selectStack +import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack import com.lambda.interaction.inventory.container.containers.HotbarContainer +import com.lambda.interaction.managers.interacting.PlaceRequestBuilder.Companion.interactRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.threading.runSafeAutomated @@ -82,7 +82,9 @@ object Scaffold : Module( listen { val selection = selectStack { - { it.blockItem.let { blockItem -> blockItem != null && blockItem.block !in blacklistedBlocks } } + custom { stack, _ -> + stack.blockItem.let { blockItem -> blockItem != null && blockItem.block !in blacklistedBlocks } + } } val stack = player.mainHandStack.takeIf { selection.filterStack(it) } ?: selection.filterStacks(HotbarContainer.stacks).firstOrNull() ?: return@listen 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 5ae411744..cb38bb2a3 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt @@ -39,15 +39,19 @@ 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.TickedRenderer.Companion.tickedRenderer -import com.lambda.interaction.handlers.BaritoneHandler import com.lambda.interaction.construction.verify.TargetState +import com.lambda.interaction.handlers.BaritoneHandler +import com.lambda.interaction.inventory.container.containers.EnderChestContainer +import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer +import com.lambda.interaction.inventory.container.containers.HotbarContainer +import com.lambda.interaction.inventory.container.containers.InventoryContainer +import com.lambda.interaction.inventory.container.containers.OffHandContainer 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.Rotation.Companion.dist import com.lambda.interaction.managers.rotating.RotationManager -import com.lambda.interaction.inventory.container.containers.EnderChestContainer +import com.lambda.interaction.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.task.RootTask.run @@ -68,12 +72,6 @@ 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.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 @@ -460,7 +458,7 @@ object StashMover : Module( MoverState.DispensingPearl -> handleDispensingPearl() MoverState.AwaitingPearl -> checkTimerProgress(MoverState.DispensingPearl, pearlButtonTimeout) { - if (player.hotbarAndInventoryStacks.any { it.item === Items.ENDER_PEARL }) { + if (HotbarAndInventoryContainer.stacks.any { it.item === Items.ENDER_PEARL }) { pearlThrown = false moverState = MoverState.ThrowingPearl true @@ -507,7 +505,7 @@ 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 }) { + if (finished && HotbarAndInventoryContainer.stacks.all { it.isEmpty }) { success(finishedMessage) return@listen } @@ -526,12 +524,12 @@ object StashMover : Module( moverState = MoverState.OpeningPullContainer return } - if (!moveFromContainerToContainer(screenHandler.containerSlots, player.hotbarAndInventoryStacks)) return + if (!moveFromContainerToContainer(screenHandler.containerSlots, HotbarAndInventoryContainer.stacks)) return pullContainer?.let { container -> if (screenHandler.containerStacks.all { it.isEmpty }) { pullContainers.remove(container) pulledContainers.add(container) - if (player.hotbarAndInventoryStacks.any { it.isEmpty }) { + if (HotbarAndInventoryContainer.stacks.any { it.isEmpty }) { moverState = MoverState.OpeningPullContainer return } @@ -549,7 +547,7 @@ object StashMover : Module( moverState = MoverState.OpeningPutEnderChest return } - if (moveFromContainerToContainer(player.hotbarAndInventorySlots, screenHandler.containerStacks)) { + if (moveFromContainerToContainer(HotbarAndInventoryContainer.slots, screenHandler.containerStacks)) { moverState = MoverState.OpeningPullContainer } } @@ -587,7 +585,7 @@ object StashMover : Module( rotation(rotation) }.submit() if (!rotationRequest.done || rotation dist RotationManager.serverRotation > 0.001) return - val throwSlots = player.hotbarAndInventorySlots.filter { !it.stack.isEmpty } + val throwSlots = HotbarAndInventoryContainer.slots.filter { !it.stack.isEmpty } if (throwSlots.isNotEmpty()) { val inventoryRequest = inventoryRequest(settleForLess = true) { throwSlots.forEach { slot -> @@ -604,12 +602,12 @@ object StashMover : Module( moverState = MoverState.OpeningPutContainer return } - if (!moveFromContainerToContainer(player.hotbarAndInventorySlots, screenHandler.containerStacks)) return + if (!moveFromContainerToContainer(HotbarAndInventoryContainer.slots, screenHandler.containerStacks)) return putContainer?.let { container -> if (screenHandler.containerStacks.all { !it.isEmpty }) { putContainers.remove(container) filledContainers.add(container) - if (player.hotbarAndInventoryStacks.any { !it.isEmpty }) { + if (HotbarAndInventoryContainer.stacks.any { !it.isEmpty }) { moverState = MoverState.OpeningPutContainer return } @@ -623,7 +621,7 @@ object StashMover : Module( moverState = MoverState.OpeningPullEnderChest return } - if (moveFromContainerToContainer(screenHandler.containerSlots, player.hotbarAndInventoryStacks)) { + if (moveFromContainerToContainer(screenHandler.containerSlots, HotbarAndInventoryContainer.stacks)) { putOrThrowItems() } } @@ -635,9 +633,9 @@ object StashMover : Module( return } if (BaritoneHandler.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 }) { + if (HotbarContainer.stacks.none { it.isEmpty }) { + val firstSlot = HotbarContainer.slots.getOrNull(0) ?: run { failWithLog("No first slot? This shouldn't occur."); return } + if (InventoryContainer.stacks.any { it.isEmpty }) { inventoryRequest { quickMove(firstSlot.id) }.submit() return } else if (player.offHandStack.isEmpty) { @@ -666,11 +664,11 @@ object StashMover : Module( if (pearlThrown) { if (!player.offHandStack.isEmpty) { - if (player.hotbarAndInventoryStacks.none { it.isEmpty }) { + if (HotbarAndInventoryContainer.stacks.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 } + val offhandSlot = OffHandContainer.slots.firstOrNull() ?: run { failWithLog("No offhand slot? This shouldn't occur."); return } inventoryRequest { quickMove(offhandSlot.id) }.submit() } putOrThrowItems() @@ -683,7 +681,7 @@ object StashMover : Module( }.submit() if (!rotationRequest.done) return if (player.mainHandStack.item != Items.ENDER_PEARL) { - val hotbarSlot = player.hotbarSlots.firstOrNull { it.stack.item === Items.ENDER_PEARL } + val hotbarSlot = HotbarContainer.slots.firstOrNull { it.stack.item === Items.ENDER_PEARL } if (hotbarSlot != null) { val hotbarRequest = HotbarRequest(hotbarSlot.index, StashMover, nowOrNothing = false).submit() if (!hotbarRequest.done) return diff --git a/src/main/kotlin/com/lambda/task/tasks/AcquireMaterialTask.kt b/src/main/kotlin/com/lambda/task/tasks/AcquireMaterialTask.kt index 72aec175b..de9264b21 100644 --- a/src/main/kotlin/com/lambda/task/tasks/AcquireMaterialTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/AcquireMaterialTask.kt @@ -19,9 +19,9 @@ package com.lambda.task.tasks import com.lambda.context.Automated import com.lambda.context.SafeContext -import com.lambda.interaction.inventory.StackSelection import com.lambda.interaction.handlers.ContainerHandler import com.lambda.interaction.handlers.ContainerHandler.findContainerWithMaterial +import com.lambda.interaction.inventory.StackSelection import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.task.Task import com.lambda.threading.runSafeAutomated diff --git a/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt b/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt index 9f5fb31cb..044ecd6bf 100644 --- a/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt @@ -26,7 +26,6 @@ import com.lambda.context.SafeContext import com.lambda.event.events.PacketEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.handlers.BaritoneHandler import com.lambda.interaction.construction.blueprint.Blueprint import com.lambda.interaction.construction.blueprint.Blueprint.Companion.toStructure import com.lambda.interaction.construction.blueprint.PropagatingBlueprint @@ -47,8 +46,10 @@ import com.lambda.interaction.construction.simulation.result.results.GenericResu import com.lambda.interaction.construction.simulation.result.results.InteractResult import com.lambda.interaction.construction.simulation.result.results.PreSimResult import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.managers.breaking.BreakRequest.Companion.breakRequest -import com.lambda.interaction.managers.interacting.InteractRequest.Companion.interactRequest +import com.lambda.interaction.handlers.BaritoneHandler +import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer +import com.lambda.interaction.managers.breaking.BreakRequestBuilder.Companion.breakRequest +import com.lambda.interaction.managers.interacting.PlaceRequestBuilder.Companion.interactRequest import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest import com.lambda.module.modules.client.Client import com.lambda.task.Task @@ -63,7 +64,6 @@ import com.lambda.util.FormattingUtils.format import com.lambda.util.extension.Structure import com.lambda.util.extension.playerSlots import com.lambda.util.math.dist -import com.lambda.util.player.SlotUtils.hotbarAndInventoryStacks import kotlinx.coroutines.Job import kotlinx.coroutines.runBlocking import net.minecraft.block.BlockState @@ -322,7 +322,7 @@ class BuildTask private constructor( return@let true } - if (player.hotbarAndInventoryStacks.none { it.isEmpty }) { + if (HotbarAndInventoryContainer.stacks.none { it.isEmpty }) { val stackToThrow = player.currentScreenHandler.playerSlots.firstOrNull { it.stack.item in inventoryConfig.disposables } ?: run { diff --git a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt index ee24da55f..e71aa3f42 100644 --- a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt @@ -22,11 +22,11 @@ import com.lambda.event.EventFlow.post import com.lambda.event.events.ContainerEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest import com.lambda.interaction.inventory.StackSelection -import com.lambda.interaction.inventory.container.ExternalContainer import com.lambda.interaction.inventory.container.Container +import com.lambda.interaction.inventory.container.ExternalContainer import com.lambda.interaction.inventory.container.containers.InventoryContainer +import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest import com.lambda.task.Task import com.lambda.threading.runSafeAutomated diff --git a/src/main/kotlin/com/lambda/task/tasks/EatTask.kt b/src/main/kotlin/com/lambda/task/tasks/EatTask.kt index 99c636fae..3ab6bf685 100644 --- a/src/main/kotlin/com/lambda/task/tasks/EatTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/EatTask.kt @@ -23,9 +23,9 @@ import com.lambda.context.Automated import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.hotbar.HotbarRequest import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.interaction.inventory.container.containers.InventoryContainer +import com.lambda.interaction.managers.hotbar.HotbarRequest import com.lambda.task.Task import com.lambda.threading.runSafeAutomated import net.minecraft.item.ItemStack diff --git a/src/main/kotlin/com/lambda/task/tasks/OpenContainerTask.kt b/src/main/kotlin/com/lambda/task/tasks/OpenContainerTask.kt index 3877f6267..a5c181286 100644 --- a/src/main/kotlin/com/lambda/task/tasks/OpenContainerTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/OpenContainerTask.kt @@ -23,7 +23,7 @@ import com.lambda.event.events.InventoryEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.handlers.BaritoneHandler -import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest +import com.lambda.interaction.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.task.Task import com.lambda.threading.runSafeAutomated import com.lambda.util.TickTimer diff --git a/src/main/kotlin/com/lambda/util/item/ItemStackUtils.kt b/src/main/kotlin/com/lambda/util/item/ItemStackUtils.kt index 9e39a0cbd..867acc01d 100644 --- a/src/main/kotlin/com/lambda/util/item/ItemStackUtils.kt +++ b/src/main/kotlin/com/lambda/util/item/ItemStackUtils.kt @@ -111,11 +111,11 @@ object ItemStackUtils { return listOf(copyWithCount(maxCount), copyWithCount(remainder)) } - val ItemStack.shulkerBoxContents: List by cacheable { stack -> + val ItemStack.shulkerBoxStacks: List by cacheable { stack -> stack.components.get(DataComponentTypes.CONTAINER)?.stream()?.toList() ?: emptyList() } - val ItemStack.bundleContents: List by cacheable { stack -> + val ItemStack.bundleStacks: List by cacheable { stack -> stack.components.get(DataComponentTypes.BUNDLE_CONTENTS)?.stream()?.toList() ?: emptyList() } diff --git a/src/main/kotlin/com/lambda/util/item/ItemUtils.kt b/src/main/kotlin/com/lambda/util/item/ItemUtils.kt index 836aa1dba..a3485c5ea 100644 --- a/src/main/kotlin/com/lambda/util/item/ItemUtils.kt +++ b/src/main/kotlin/com/lambda/util/item/ItemUtils.kt @@ -18,7 +18,7 @@ package com.lambda.util.item import com.lambda.context.SafeContext -import com.lambda.util.player.SlotUtils.armorSlots +import com.lambda.interaction.inventory.container.containers.ArmorContainer import net.minecraft.block.Block import net.minecraft.component.DataComponentTypes import net.minecraft.item.BlockItem @@ -150,7 +150,7 @@ object ItemUtils { context(safeContext: SafeContext) val ItemStack.armorSlot get() = - safeContext.player.armorSlots.firstOrNull { it.canInsert(this) } + ArmorContainer.slots.firstOrNull { it.canInsert(this) } fun Int.toItemCount(): String { if (this < 0) { From 3b043d9604bce0bf2ba9b292b1c495aa22a8eb8c Mon Sep 17 00:00:00 2001 From: beanbag44 <107891830+beanbag44@users.noreply.github.com> Date: Thu, 9 Jul 2026 18:32:45 +0100 Subject: [PATCH 03/43] immutable container selections with builder and general cleanup --- .../lambda/config/blocks/InventoryConfig.kt | 3 +- .../simulation/checks/BreakSim.kt | 2 +- .../simulation/checks/InteractSim.kt | 4 +- .../interaction/handlers/ContainerHandler.kt | 2 +- .../interaction/handlers/GlideHandler.kt | 4 +- .../inventory/ContainerSelection.kt | 136 +++++----- .../interaction/inventory/StackSelection.kt | 43 ++-- .../inventory/container/Container.kt | 43 +--- .../inventory/container/ExternalContainer.kt | 2 + .../container/containers/ArmorContainer.kt | 4 +- .../container/containers/ChestContainer.kt | 9 +- .../container/containers/CreativeContainer.kt | 23 +- .../containers/EnderChestContainer.kt | 17 +- .../containers/HotbarAndInventoryContainer.kt | 4 +- .../container/containers/HotbarContainer.kt | 9 +- .../containers/InventoryContainer.kt | 4 +- .../container/containers/MainHandContainer.kt | 9 +- .../container/containers/OffHandContainer.kt | 9 +- .../container/containers/PlayerContainer.kt | 4 +- .../containers/ShulkerBoxContainer.kt | 4 +- .../container/containers/StashContainer.kt | 3 - .../managers/breaking/BreakManager.kt | 13 +- .../managers/breaking/BreakRequest.kt | 15 +- .../managers/hotbar/HotbarManager.kt | 6 +- .../managers/hotbar/HotbarRequest.kt | 35 ++- .../managers/interacting/InteractManager.kt | 9 +- .../managers/interacting/InteractRequest.kt | 15 +- .../managers/inventory/InventoryRequest.kt | 232 +++++++++--------- .../managers/rotating/RotationRequest.kt | 19 +- .../lambda/module/modules/combat/AutoArmor.kt | 2 +- .../lambda/module/modules/combat/AutoTotem.kt | 6 +- .../module/modules/combat/CrystalAura.kt | 8 +- .../lambda/module/modules/combat/KillAura.kt | 4 +- .../lambda/module/modules/debug/SilentSwap.kt | 4 +- .../module/modules/movement/BetterFirework.kt | 63 +++-- .../movement/elytrafly/ElytraFlyMode.kt | 21 +- .../elytrafly/modes/GrimControlElytraFly.kt | 15 +- .../module/modules/player/AutoElytraSwap.kt | 13 +- .../module/modules/player/InventoryResync.kt | 2 +- .../module/modules/player/StackReplenish.kt | 2 +- .../lambda/module/modules/player/ToolSaver.kt | 2 +- .../lambda/module/modules/world/AutoPortal.kt | 35 ++- .../lambda/module/modules/world/Scaffold.kt | 4 +- .../lambda/module/modules/world/StashMover.kt | 10 +- .../kotlin/com/lambda/task/tasks/BuildTask.kt | 2 +- .../task/tasks/ContainerTransferTask.kt | 45 ++-- .../kotlin/com/lambda/task/tasks/EatTask.kt | 16 +- .../kotlin/com/lambda/util/PacketUtils.kt | 2 +- 48 files changed, 458 insertions(+), 480 deletions(-) diff --git a/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt b/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt index c60f8b4b8..18be96b21 100644 --- a/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt +++ b/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt @@ -20,6 +20,7 @@ package com.lambda.config.blocks import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.interaction.inventory.ContainerSelection +import com.lambda.interaction.inventory.ContainerSelectionBuilder.Companion.selectContainer import com.lambda.interaction.inventory.StackSelection import com.lambda.interaction.inventory.container.Container import com.lambda.util.Describable @@ -39,7 +40,7 @@ interface InventoryConfig { val accessStashes: Boolean val containerSelection: ContainerSelection - get() = ContainerSelection.selectContainer { + get() = selectContainer { val allowedContainers = buildSet { addAll(Container.Rank.entries) if (!accessShulkerBoxes) remove(Container.Rank.ShulkerBox) diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt index ffea00e15..0fe648ead 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt @@ -29,7 +29,7 @@ import com.lambda.interaction.construction.simulation.result.results.BreakResult import com.lambda.interaction.construction.simulation.result.results.GenericResult import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.handlers.ContainerHandler.findContainersWithMaterial -import com.lambda.interaction.inventory.ContainerSelection.Companion.selectContainer +import com.lambda.interaction.inventory.ContainerSelectionBuilder.Companion.selectContainer import com.lambda.interaction.inventory.StackAndSlot import com.lambda.interaction.inventory.StackSelection import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack 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 f5d4b848e..e81c88b73 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 @@ -28,7 +28,7 @@ import com.lambda.interaction.construction.simulation.result.results.GenericResu import com.lambda.interaction.construction.simulation.result.results.InteractResult import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.handlers.ContainerHandler.findContainersWithMaterial -import com.lambda.interaction.inventory.ContainerSelection.Companion.selectContainer +import com.lambda.interaction.inventory.ContainerSelectionBuilder.Companion.selectContainer import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.select import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack import com.lambda.interaction.inventory.container.Container @@ -217,7 +217,7 @@ class InteractSim private constructor(simInfo: InteractSimInfo) result(GenericResult.WrongItemSelection(pos, stackSelection, player.mainHandStack)) return null } - return stackSelection.filterSlots(container.slots).run { + return stackSelection.filter(container.slots).run { firstOrNull { it.index == player.inventory.selectedSlot } ?: firstOrNull() } diff --git a/src/main/kotlin/com/lambda/interaction/handlers/ContainerHandler.kt b/src/main/kotlin/com/lambda/interaction/handlers/ContainerHandler.kt index 0398fc6cb..b71db2c36 100644 --- a/src/main/kotlin/com/lambda/interaction/handlers/ContainerHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handlers/ContainerHandler.kt @@ -146,7 +146,7 @@ object ContainerHandler : Loadable { containerSelection: ContainerSelection = automatedSafeContext.inventoryConfig.containerSelection ): List = findContainersWithMaterial(containerSelection) - .flatMap { filterSlots(it.slots) } + .flatMap { filter(it.slots) } context(automatedSafeContext: AutomatedSafeContext) diff --git a/src/main/kotlin/com/lambda/interaction/handlers/GlideHandler.kt b/src/main/kotlin/com/lambda/interaction/handlers/GlideHandler.kt index af39eafcd..73157e79f 100644 --- a/src/main/kotlin/com/lambda/interaction/handlers/GlideHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handlers/GlideHandler.kt @@ -172,8 +172,8 @@ object GlideHandler { val canGlideAlready = player.canGlideWithChestPiece() != fakeFly if (!autoSwapChecking || canGlideAlready) return canGlideAlready return HotbarAndInventoryContainer.slots.let { slots -> - if (fakeFly) CHESTPLATE_SELECTION.filterSlots(slots).isNotEmpty() - else ELYTRA_SELECTION.filterSlots(slots).isNotEmpty() + if (fakeFly) CHESTPLATE_SELECTION.filter(slots).isNotEmpty() + else ELYTRA_SELECTION.filter(slots).isNotEmpty() } } diff --git a/src/main/kotlin/com/lambda/interaction/inventory/ContainerSelection.kt b/src/main/kotlin/com/lambda/interaction/inventory/ContainerSelection.kt index 5d0948fb0..f0e405e92 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/ContainerSelection.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/ContainerSelection.kt @@ -17,81 +17,89 @@ package com.lambda.interaction.inventory -import com.lambda.context.SafeContext import com.lambda.interaction.inventory.container.Container /** * ContainerSelection is a class that holds a predicate for matching MaterialContainers. - * It can be combined using "and", "or", etc. */ -class ContainerSelection { - private var selector: (Container) -> Boolean = { true } +@Suppress("unused") +class ContainerSelection( + val selector: (Container) -> Boolean, + val comparator: Comparator = compareBy { it.rank } +) { + @ContainerSelectionMarker + fun bestMatch(containers: Iterable) = filter(containers).firstOrNull() - /** - * Tests whether the provided container matches this selection. - */ - @ContainerSelectionDsl + @ContainerSelectionMarker fun matches(container: Container): Boolean = selector(container) - /** - * Returns a function that matches containers having at least one stack - * which matches the given StackSelection. - */ - @ContainerSelectionDsl - context(_: SafeContext) - fun matches(stackSelection: StackSelection): (Container) -> Boolean = - { container -> container.matchingSlots(stackSelection).isNotEmpty() } - - /** - * Returns a function that checks whether a given MaterialContainer matches the criteria - * defined in the provided ContainerSelection. - */ - @ContainerSelectionDsl - fun matches(containerSelection: ContainerSelection): (Container) -> Boolean = - { container -> containerSelection.matches(container) } - - /** - * Returns a function that matches containers whose rank is any of the types provided. - */ - @ContainerSelectionDsl - fun ofAnyType(vararg types: Container.Rank): (Container) -> Boolean = - { container -> types.contains(container.rank) } - - /** - * Returns a function that matches containers whose rank is not any of the types provided. - */ - @ContainerSelectionDsl - fun noneOfType(vararg types: Container.Rank): (Container) -> Boolean = - { container -> !types.contains(container.rank) } - - /** - * Returns a function that combines two container predicates using logical AND. - */ - @ContainerSelectionDsl - infix fun ((Container) -> Boolean).and(other: (Container) -> Boolean): (Container) -> Boolean = - { container -> this(container) && other(container) } - - /** - * Returns a function that combines two container predicates using logical OR. - */ - @ContainerSelectionDsl - infix fun ((Container) -> Boolean).or(other: (Container) -> Boolean): (Container) -> Boolean = - { container -> this(container) || other(container) } - - /** - * Returns a function that negates the current selection predicate. - */ - @ContainerSelectionDsl - fun ((Container) -> Boolean).negate(): (Container) -> Boolean = - { container -> !this(container) } + @ContainerSelectionMarker + fun filter(containers: Iterable) = + containers + .filter(selector) + .sortedWith(comparator) companion object { - @DslMarker - annotation class ContainerSelectionDsl + val EVERYTHING = ContainerSelection({ true }) + val NOTHING = ContainerSelection({ false }) + } +} + +@DslMarker +annotation class ContainerSelectionMarker + +@Suppress("unused") +@ContainerSelectionMarker +class ContainerSelectionBuilder private constructor() { + private var selector: (Container) -> Boolean = { true } + private var comparator: Comparator = compareBy { it.rank } + private var invertNewSelectors = false + + fun ofAnyType(vararg types: Container.Rank) { + appendSelector { container -> types.contains(container.rank) } + } + + fun noneOfType(vararg types: Container.Rank) { + appendSelector { container -> !types.contains(container.rank) } + } + + fun matches(stackSelection: StackSelection) { + appendSelector { container -> container.matchingSlots(stackSelection).isNotEmpty() } + } + + fun matches(containerSelection: ContainerSelection) { + appendSelector { container -> containerSelection.matches(container) } + } - @ContainerSelectionDsl + fun custom(predicate: (Container) -> Boolean) { + appendSelector { predicate(it) } + } + + fun inverted(block: () -> Boolean) { + invertNewSelectors = true + block() + invertNewSelectors = false + } + + fun sortedWith(comparator: Comparator) { + this.comparator = comparator + } + + fun sortedWith(comparatorSupplier: () -> Comparator) { + this.comparator = comparatorSupplier() + } + + private fun appendSelector(selector: (Container) -> Boolean) { + val invert = invertNewSelectors + val currentSelector = this.selector + this.selector = { currentSelector(it) && selector(it) xor invert } + } + + private fun build() = ContainerSelection(selector, comparator) + + companion object { fun selectContainer( - block: ContainerSelection.() -> (Container) -> Boolean - ): ContainerSelection = ContainerSelection().apply { selector = block() } + builder: ContainerSelectionBuilder.() -> Unit + ) = ContainerSelectionBuilder().apply(builder).build() } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/inventory/StackSelection.kt b/src/main/kotlin/com/lambda/interaction/inventory/StackSelection.kt index 31534912f..81360e2f5 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/StackSelection.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/StackSelection.kt @@ -45,35 +45,32 @@ class StackSelection( val optimalStack = itemStack ?: item?.let { ItemStack(it, count) } @StackSelectionMarker - fun bestMatch(stacks: List) = filterStacks(stacks).firstOrNull() + fun bestMatch(stacks: Iterable) = filter(stacks).firstOrNull() @StackSelectionMarker - fun bestMatch(slots: List) = filterSlots(slots).firstOrNull() + fun bestMatch(slots: Iterable) = filter(slots).firstOrNull() @StackSelectionMarker - fun matches(stack: ItemStack) = filterStack(stack) - @StackSelectionMarker - fun matches(slot: Slot) = filterSlot(slot) - - @StackSelectionMarker - fun filterStack(stack: ItemStack) = + fun matches(stack: ItemStack) = if (inShulkerBox) stack.shulkerBoxStacks.any { selector(it, null) } else selector(stack, null) @StackSelectionMarker - fun filterSlot(slot: Slot): Boolean = + fun matches(slot: Slot): Boolean = if (inShulkerBox) slot.stack.shulkerBoxStacks.any { selector(it, null) } else selector(slot.stack, slot) @StackSelectionMarker - fun filterStacks(stacks: List): List = + @JvmName("filter1") + fun filter(stacks: Iterable) = stacks - .filter(::filterStack) + .filter(::matches) .map { StackAndSlot(it, null) } .sortedWith(comparator) .map { it.stack } @StackSelectionMarker - fun filterSlots(slots: List): List = + @JvmName("filter2") + fun filter(slots: Iterable) = slots - .filter(::filterSlot) + .filter(::matches) .map { StackAndSlot(it.stack, it) } .sortedWith(comparator) .map { it.slot } @@ -89,7 +86,7 @@ private annotation class StackSelectionMarker @Suppress("unused") @StackSelectionMarker -class StackSelectionBuilder(private val count: Int = 0) { +class StackSelectionBuilder private constructor(private val count: Int = 0) { private var selector: (ItemStack, Slot?) -> Boolean = { _, _ -> true } private var item: Item? = null private var stackByRef = false @@ -100,6 +97,7 @@ class StackSelectionBuilder(private val count: Int = 0) { fun isItem(item: Item) { this.item = item + appendSelector { stack, _ -> stack.item == item } } inline fun isItem() { @@ -190,16 +188,20 @@ class StackSelectionBuilder(private val count: Int = 0) { } } - fun inverted(block: () -> Unit) { - invertNewSelectors = true - block() - invertNewSelectors = false + fun isEmpty() { + appendSelector { stack, _ -> stack.isEmpty} } fun custom(predicate: (ItemStack, Slot?) -> Boolean) { appendSelector { stack, slot -> predicate(stack, slot) } } + fun inverted(block: () -> Unit) { + invertNewSelectors = true + block() + invertNewSelectors = false + } + fun sortedWith(comparator: Comparator>) { this.comparator = comparator } @@ -211,8 +213,9 @@ class StackSelectionBuilder(private val count: Int = 0) { @PublishedApi internal fun appendSelector(selector: (ItemStack, Slot?) -> Boolean) { val invert = invertNewSelectors + val currentSelector = this.selector this.selector = { stack, slot -> - this.selector(stack, slot) && (selector(stack, slot) xor invert) + currentSelector(stack, slot) && selector(stack, slot) xor invert } } @@ -227,7 +230,7 @@ class StackSelectionBuilder(private val count: Int = 0) { ) companion object { - private val efficientToolCache: MutableMap = Collections.synchronizedMap(mutableMapOf()) + private val efficientToolCache = Collections.synchronizedMap(mutableMapOf()) @StackSelectionMarker fun Item.select(count: Int = 0) = selectStack(count) { isItem(this@select) } diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/Container.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/Container.kt index 46a0ad7e5..4375e43ba 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/Container.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/Container.kt @@ -24,8 +24,8 @@ import com.lambda.event.EventFlow.post import com.lambda.event.events.ContainerEvent import com.lambda.interaction.inventory.StackSelection import com.lambda.interaction.inventory.container.containers.ShulkerBoxContainer -import com.lambda.interaction.managers.inventory.InventoryRequest -import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest +import com.lambda.interaction.managers.inventory.InvRequestBuilder +import com.lambda.interaction.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.task.tasks.ContainerTransferTask import com.lambda.util.Nameable import com.lambda.util.item.ItemStackUtils.count @@ -49,7 +49,6 @@ import net.minecraft.text.Text abstract class Container( val rank: Rank ) : Nameable, Comparable { - context(_: SafeContext) abstract val slots: List abstract var stacks: List open val swapMethodPriority = 0 @@ -114,11 +113,11 @@ abstract class Container( this.stacks = slots } - context(_: SafeContext) - open fun InventoryRequest.InvRequestBuilder.transfer(fromHere: Slot, toSlot: Slot) { - if (fromHere.stack.isEmpty) pickupAndPlace(toSlot.id, fromHere.id) + context(safeContext: SafeContext) + open fun InvRequestBuilder.transfer(fromHere: Slot, toSlot: Slot) { + if (fromHere.stack.isEmpty) moveSlot(toSlot.id, fromHere.id) else { - pickupAndPlace(fromHere.id, toSlot.id) + moveSlot(fromHere.id, toSlot.id) if (!toSlot.stack.isEmpty) pickup(fromHere.id) } } @@ -136,37 +135,24 @@ abstract class Container( }.submit().done } - context(automatedSafeContext: AutomatedSafeContext) + context(automated: Automated) fun transferByTask(stackSelection: StackSelection, destination: Container, failIfNoMaterial: Boolean = false) = - ContainerTransferTask(this, destination, stackSelection, automatedSafeContext, failIfNoMaterial) - - protected fun InventoryRequest.InvRequestBuilder.pickupAndPlace(fromId: Int, toId: Int) { - pickup(fromId) - pickup(toId) - } + ContainerTransferTask(this, destination, stackSelection, automated, failIfNoMaterial) - context(_: SafeContext) - open fun matchingStacks(selection: StackSelection) = - selection.filterStacks(stacks) - - context(_: SafeContext) - open fun matchingSlots(selection: StackSelection) = - selection.filterSlots(slots) + open fun matchingStacks(selection: StackSelection) = selection.filter(stacks) + open fun matchingSlots(selection: StackSelection) = selection.filter(slots) - context(_: SafeContext) open fun materialAvailable(selection: StackSelection) = matchingStacks(selection).count - context(_: SafeContext) open fun spaceAvailable(selection: StackSelection) = matchingStacks(selection).spaceLeft + stacks.empty * selection.count - context(_: AutomatedSafeContext) + context(_: Automated) open fun getReplaceableSlot() = slots.sortedWith(replaceSorter).firstOrNull() - context(_: SafeContext) open fun getSlot(stackSelection: StackSelection): Slot? = - stackSelection.filterSlots(slots).firstOrNull() + stackSelection.filter(slots).firstOrNull() enum class Rank { MainHand, @@ -183,8 +169,5 @@ abstract class Container( Stash } - override fun compareTo(other: Container) = - compareBy { - it.rank - }.compare(this, other) + override fun compareTo(other: Container) = compareBy { it.rank }.compare(this, other) } diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/ExternalContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/ExternalContainer.kt index 9d0bf3f14..da25cd005 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/ExternalContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/ExternalContainer.kt @@ -19,9 +19,11 @@ package com.lambda.interaction.inventory.container import com.lambda.context.AutomatedSafeContext import com.lambda.task.Task +import com.lambda.task.Task.Ta5kBuilder import com.lambda.task.TaskGenerator interface ExternalContainer { + @Ta5kBuilder context(_: AutomatedSafeContext) fun accessThen(exitAfter: Boolean = true, taskGenerator: TaskGenerator): Task<*>? } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/ArmorContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/ArmorContainer.kt index 479ce919e..8222ae5d1 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/ArmorContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/ArmorContainer.kt @@ -18,7 +18,6 @@ package com.lambda.interaction.inventory.container.containers import com.lambda.Lambda.mc -import com.lambda.context.SafeContext import com.lambda.interaction.inventory.container.Container import com.lambda.util.player.SlotUtils.armorSlots import com.lambda.util.player.SlotUtils.armorStacks @@ -28,9 +27,8 @@ import net.minecraft.item.ItemStack import net.minecraft.screen.slot.Slot object ArmorContainer : Container(Rank.Armor) { - context(safeContext: SafeContext) override val slots: List - get() = safeContext.player.armorSlots + get() = mc.player?.armorSlots ?: emptyList() override var stacks: List get() = mc.player?.armorStacks ?: emptyList() set(_) {} diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/ChestContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/ChestContainer.kt index dc2f69b1e..343b26cda 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/ChestContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/ChestContainer.kt @@ -17,8 +17,8 @@ package com.lambda.interaction.inventory.container.containers +import com.lambda.Lambda.mc import com.lambda.context.AutomatedSafeContext -import com.lambda.context.SafeContext import com.lambda.interaction.handlers.ContainerHandler import com.lambda.interaction.inventory.container.Container import com.lambda.interaction.inventory.container.ExternalContainer @@ -39,12 +39,11 @@ data class ChestContainer( val blockPos: BlockPos, val containedInStash: StashContainer? = null ) : Container(Rank.Chest), ExternalContainer { - context(safeContext: SafeContext) override val slots get(): List = - if (ContainerHandler.lastInteractedBlockEntity is ChestBlockEntity) - safeContext.player.currentScreenHandler.containerSlots - else emptyList() + if (ContainerHandler.lastInteractedBlockEntity is ChestBlockEntity) { + mc.player?.currentScreenHandler?.containerSlots ?: emptyList() + } else emptyList() override val description = buildText { diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/CreativeContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/CreativeContainer.kt index 9c9ff74db..eb8f6f0f3 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/CreativeContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/CreativeContainer.kt @@ -17,10 +17,11 @@ package com.lambda.interaction.inventory.container.containers +import com.lambda.Lambda.mc import com.lambda.context.SafeContext import com.lambda.interaction.inventory.StackSelection import com.lambda.interaction.inventory.container.Container -import com.lambda.interaction.managers.inventory.InventoryRequest +import com.lambda.interaction.managers.inventory.InvRequestBuilder import com.lambda.util.text.buildText import com.lambda.util.text.literal import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen @@ -31,22 +32,19 @@ import net.minecraft.screen.PlayerScreenHandler import net.minecraft.screen.slot.Slot data object CreativeContainer : Container(Rank.Creative) { - context(_: SafeContext) - override val slots: List - get() = emptyList() + override val slots = emptyList() override var stacks = emptyList() override val swapMethodPriority = 11 override val description = buildText { literal("Creative") } - context(safeContext : SafeContext) - override fun InventoryRequest.InvRequestBuilder.transfer(fromHere: Slot, toSlot: Slot) { + context(safeContext: SafeContext) + override fun InvRequestBuilder.transfer(fromHere: Slot, toSlot: Slot) { clickCreativeStack(fromHere.stack, toSlot.id) - safeContext.player.currentScreenHandler.slots[toSlot.id].stack = fromHere.stack + safeContext.player.currentScreenHandler.slots.getOrNull(toSlot.id)?.stack = fromHere.stack } - context(safeContext: SafeContext) override fun getSlot(stackSelection: StackSelection) = stackSelection.optimalStack?.let { stack -> Slot( @@ -60,15 +58,12 @@ data object CreativeContainer : Container(Rank.Creative) { ) } - context(safeContext: SafeContext) override fun materialAvailable(selection: StackSelection): Int = - if (safeContext.player.isCreative && correctScreenHandler && selection.optimalStack != null) Int.MAX_VALUE else -1 + if (mc.player?.isCreative == true && correctScreenHandler && selection.optimalStack != null) Int.MAX_VALUE else -1 - context(safeContext: SafeContext) override fun spaceAvailable(selection: StackSelection): Int = - if (safeContext.player.isCreative && correctScreenHandler && selection.optimalStack != null) Int.MAX_VALUE else -1 + if (mc.player?.isCreative == true && correctScreenHandler && selection.optimalStack != null) Int.MAX_VALUE else -1 - context(safeContext: SafeContext) private val correctScreenHandler - get() = safeContext.player.currentScreenHandler is PlayerScreenHandler || safeContext.player.currentScreenHandler is CreativeInventoryScreen.CreativeScreenHandler + get() = mc.player?.currentScreenHandler is PlayerScreenHandler || mc.player?.currentScreenHandler is CreativeInventoryScreen.CreativeScreenHandler } diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/EnderChestContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/EnderChestContainer.kt index 4b74135cf..627b31f77 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/EnderChestContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/EnderChestContainer.kt @@ -17,8 +17,8 @@ package com.lambda.interaction.inventory.container.containers +import com.lambda.Lambda.mc import com.lambda.context.AutomatedSafeContext -import com.lambda.context.SafeContext import com.lambda.interaction.handlers.ContainerHandler import com.lambda.interaction.handlers.ContainerHandler.findSlotsWithMaterial import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.select @@ -36,12 +36,11 @@ import net.minecraft.item.ItemStack import net.minecraft.item.Items object EnderChestContainer : Container(Rank.EnderChest), ExternalContainer { - context(safeContext: SafeContext) override val slots get() = - if (ContainerHandler.lastInteractedBlockEntity is EnderChestBlockEntity) - safeContext.player.currentScreenHandler.containerSlots - else emptyList() + if (ContainerHandler.lastInteractedBlockEntity is EnderChestBlockEntity) { + mc.player?.currentScreenHandler?.containerSlots ?: emptyList() + } else emptyList() override var stacks = emptyList() override val description = buildText { literal("Ender Chest") } @@ -49,14 +48,16 @@ object EnderChestContainer : Container(Rank.EnderChest), ExternalContainer { context(automatedSafeContext: AutomatedSafeContext) override fun accessThen(exitAfter: Boolean, taskGenerator: TaskGenerator) = Items.ENDER_CHEST - .select() + .select(1) .findSlotsWithMaterial() .firstOrNull()?.let { slot -> PlaceContainerTask(slot, automatedSafeContext).then { pos -> OpenContainerTask(pos, automatedSafeContext).then { taskGenerator.invoke(automatedSafeContext, Unit).thenOrNull { - if (exitAfter) automatedSafeContext.breakAndCollectBlock(pos, lifeMaintenance = false) - else null + if (exitAfter) { + player.closeHandledScreen() + automatedSafeContext.breakAndCollectBlock(pos, lifeMaintenance = false) + } else null } } } diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/HotbarAndInventoryContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/HotbarAndInventoryContainer.kt index c2dc8c52c..b5998dbec 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/HotbarAndInventoryContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/HotbarAndInventoryContainer.kt @@ -18,7 +18,6 @@ package com.lambda.interaction.inventory.container.containers import com.lambda.Lambda.mc -import com.lambda.context.SafeContext import com.lambda.interaction.inventory.container.Container import com.lambda.util.player.SlotUtils.hotbarAndInventorySlots import com.lambda.util.player.SlotUtils.hotbarAndInventoryStacks @@ -28,9 +27,8 @@ import net.minecraft.item.ItemStack import net.minecraft.screen.slot.Slot object HotbarAndInventoryContainer : Container(Rank.HotbarAndInventory) { - context(safeContext: SafeContext) override val slots: List - get() = safeContext.player.hotbarAndInventorySlots + get() = mc.player?.hotbarAndInventorySlots ?: emptyList() override var stacks: List get() = mc.player?.hotbarAndInventoryStacks ?: emptyList() set(_) {} diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/HotbarContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/HotbarContainer.kt index f3db002c0..a4e2cd9fd 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/HotbarContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/HotbarContainer.kt @@ -20,7 +20,7 @@ package com.lambda.interaction.inventory.container.containers import com.lambda.Lambda.mc import com.lambda.context.SafeContext import com.lambda.interaction.inventory.container.Container -import com.lambda.interaction.managers.inventory.InventoryRequest +import com.lambda.interaction.managers.inventory.InvRequestBuilder import com.lambda.util.player.SlotUtils.hotbarSlots import com.lambda.util.player.SlotUtils.hotbarStacks import com.lambda.util.text.buildText @@ -28,9 +28,8 @@ import com.lambda.util.text.literal import net.minecraft.screen.slot.Slot object HotbarContainer : Container(Rank.Hotbar) { - context(safeContext: SafeContext) override val slots: List - get() = safeContext.player.hotbarSlots + get() = mc.player?.hotbarSlots ?: emptyList() override var stacks get() = mc.player?.hotbarStacks ?: emptyList() set(_) {} @@ -40,7 +39,7 @@ object HotbarContainer : Container(Rank.Hotbar) { override val description = buildText { literal("Hotbar") } context(safeContext: SafeContext) - override fun InventoryRequest.InvRequestBuilder.transfer(fromHere: Slot, toSlot: Slot) { - swap(toSlot.id, safeContext.player.hotbarSlots.indexOf(fromHere)) + override fun InvRequestBuilder.transfer(fromHere: Slot, toSlot: Slot) { + swapWithHotbar(toSlot.id, safeContext.player.hotbarSlots.indexOf(fromHere)) } } diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/InventoryContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/InventoryContainer.kt index 7cc269548..c54b101e5 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/InventoryContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/InventoryContainer.kt @@ -18,7 +18,6 @@ package com.lambda.interaction.inventory.container.containers import com.lambda.Lambda.mc -import com.lambda.context.SafeContext import com.lambda.interaction.inventory.container.Container import com.lambda.util.player.SlotUtils.inventorySlots import com.lambda.util.player.SlotUtils.inventoryStacks @@ -28,9 +27,8 @@ import net.minecraft.item.ItemStack import net.minecraft.screen.slot.Slot object InventoryContainer : Container(Rank.Inventory) { - context(safeContext: SafeContext) override val slots: List - get() = safeContext.player.inventorySlots + get() = mc.player?.inventorySlots ?: emptyList() override var stacks: List get() = mc.player?.inventoryStacks ?: emptyList() set(_) {} diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/MainHandContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/MainHandContainer.kt index 839c1fa6d..ecbdff3cf 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/MainHandContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/MainHandContainer.kt @@ -20,7 +20,7 @@ package com.lambda.interaction.inventory.container.containers import com.lambda.Lambda.mc import com.lambda.context.SafeContext import com.lambda.interaction.inventory.container.Container -import com.lambda.interaction.managers.inventory.InventoryRequest +import com.lambda.interaction.managers.inventory.InvRequestBuilder import com.lambda.util.player.SlotUtils.mainHandSlots import com.lambda.util.text.buildText import com.lambda.util.text.literal @@ -28,9 +28,8 @@ import net.minecraft.item.ItemStack import net.minecraft.screen.slot.Slot object MainHandContainer : Container(Rank.MainHand) { - context(safeContext: SafeContext) override val slots: List - get() = safeContext.player.mainHandSlots + get() = mc.player?.mainHandSlots ?: emptyList() override var stacks: List get() = mc.player?.mainHandStack?.let { listOf(it) } ?: emptyList() set(_) {} @@ -40,7 +39,7 @@ object MainHandContainer : Container(Rank.MainHand) { override val description = buildText { literal("MainHand") } context(safeContext: SafeContext) - override fun InventoryRequest.InvRequestBuilder.transfer(fromHere: Slot, toSlot: Slot) { - swap(toSlot.id, safeContext.player.inventory.selectedSlot) + override fun InvRequestBuilder.transfer(fromHere: Slot, toSlot: Slot) { + swapWithHotbar(toSlot.id, safeContext.player.inventory.selectedSlot) } } diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/OffHandContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/OffHandContainer.kt index 74f9f252a..9746bb349 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/OffHandContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/OffHandContainer.kt @@ -20,7 +20,7 @@ package com.lambda.interaction.inventory.container.containers import com.lambda.Lambda.mc import com.lambda.context.SafeContext import com.lambda.interaction.inventory.container.Container -import com.lambda.interaction.managers.inventory.InventoryRequest +import com.lambda.interaction.managers.inventory.InvRequestBuilder import com.lambda.util.player.SlotUtils.offHandSlots import com.lambda.util.text.buildText import com.lambda.util.text.literal @@ -28,9 +28,8 @@ import net.minecraft.item.ItemStack import net.minecraft.screen.slot.Slot object OffHandContainer : Container(Rank.OffHand) { - context(safeContext: SafeContext) override val slots: List - get() = safeContext.player.offHandSlots + get() = mc.player?.offHandSlots ?: emptyList() override var stacks: List get() = mc.player?.offHandStack?.let { listOf(it) } ?: emptyList() set(_) {} @@ -40,7 +39,7 @@ object OffHandContainer : Container(Rank.OffHand) { override val description = buildText { literal("OffHand") } context(_: SafeContext) - override fun InventoryRequest.InvRequestBuilder.transfer(fromHere: Slot, toSlot: Slot) { - swap(toSlot.id, 40) + override fun InvRequestBuilder.transfer(fromHere: Slot, toSlot: Slot) { + swapWithHotbar(toSlot.id, 40) } } diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/PlayerContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/PlayerContainer.kt index 1fbe889ec..f33a461a5 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/PlayerContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/PlayerContainer.kt @@ -18,7 +18,6 @@ package com.lambda.interaction.inventory.container.containers import com.lambda.Lambda.mc -import com.lambda.context.SafeContext import com.lambda.interaction.inventory.container.Container import com.lambda.util.player.SlotUtils.allSlots import com.lambda.util.player.SlotUtils.allStacks @@ -29,9 +28,8 @@ import net.minecraft.screen.slot.Slot @Suppress("unused") object PlayerContainer : Container(Rank.Player) { - context(safeContext: SafeContext) override val slots: List - get() = safeContext.player.allSlots + get() = mc.player?.allSlots ?: emptyList() override var stacks: List get() = mc.player?.allStacks ?: emptyList() set(_) {} diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/ShulkerBoxContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/ShulkerBoxContainer.kt index 8cb68f275..c3faed9fc 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/ShulkerBoxContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/ShulkerBoxContainer.kt @@ -17,6 +17,7 @@ package com.lambda.interaction.inventory.container.containers +import com.lambda.Lambda.mc import com.lambda.context.AutomatedSafeContext import com.lambda.context.SafeContext import com.lambda.interaction.handlers.ContainerHandler @@ -41,11 +42,10 @@ data class ShulkerBoxContainer( val containedIn: Container, val shulkerSlot: Slot, ) : Container(Rank.ShulkerBox), ExternalContainer { - context(safeContext: SafeContext) override val slots get(): List = if (ContainerHandler.lastInteractedBlockEntity is ShulkerBoxBlockEntity) - safeContext.player.currentScreenHandler.containerSlots + mc.player?.currentScreenHandler?.containerSlots ?: emptyList() else emptyList() override val description = diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/StashContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/StashContainer.kt index 6c23b7d5b..1dc78c499 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/StashContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/StashContainer.kt @@ -17,7 +17,6 @@ package com.lambda.interaction.inventory.container.containers -import com.lambda.context.SafeContext import com.lambda.interaction.inventory.StackSelection import com.lambda.interaction.inventory.container.Container import com.lambda.util.math.roundedBlockPos @@ -32,7 +31,6 @@ data class StashContainer( val chests: Set, val pos: Box, ) : Container(Rank.Stash) { - context(_: SafeContext) override val slots: List get() = chests.flatMap { it.slots } override var stacks: List @@ -44,7 +42,6 @@ data class StashContainer( highlighted(pos.center.roundedBlockPos.toShortString()) } - context(_: SafeContext) override fun materialAvailable(selection: StackSelection): Int = chests.sumOf { it.materialAvailable(selection) diff --git a/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakManager.kt b/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakManager.kt index daae64b81..d8f35e129 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakManager.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakManager.kt @@ -72,6 +72,7 @@ import com.lambda.interaction.managers.breaking.BreakManager.updateBreakProgress import com.lambda.interaction.managers.breaking.BreakManager.updatePreProcessing import com.lambda.interaction.managers.breaking.SwapInfo.Companion.getSwapInfo import com.lambda.interaction.managers.hotbar.HotbarRequest +import com.lambda.interaction.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest import com.lambda.interaction.managers.interacting.InteractManager import com.lambda.interaction.managers.rotating.RotationRequest import com.lambda.threading.runGameScheduled @@ -447,12 +448,10 @@ object BreakManager : Manager( val serverSwapTicks = max(first.breakConfig.serverSwapTicks, last.breakConfig.serverSwapTicks) hotbarRequest = with(last) { - HotbarRequest( - context.hotbarIndex, - request, - request.hotbarConfig.keepTicks.coerceAtLeast(minKeepTicks), - request.hotbarConfig.swapPause.coerceAtLeast(serverSwapTicks - 1) - ).submit(false) + request.hotbarRequest(context.hotbarIndex) { + keepTicks(request.hotbarConfig.keepTicks.coerceAtLeast(minKeepTicks)) + swapPause(request.hotbarConfig.swapPause.coerceAtLeast(serverSwapTicks - 1)) + }.submit(false) } return @@ -474,7 +473,7 @@ object BreakManager : Manager( breaks.forEach { ctx -> if (breaksThisTick >= maxBreaksThisTick) return false - if (!currentStackSelection.filterStack(player.inventory.getStack(ctx.hotbarIndex))) return@forEach + if (!currentStackSelection.matches(player.inventory.getStack(ctx.hotbarIndex))) return@forEach initNewBreak(ctx, request) ?: return false breaks.remove(ctx) diff --git a/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakRequest.kt b/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakRequest.kt index 312e473d5..2e160c1bb 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakRequest.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakRequest.kt @@ -28,7 +28,6 @@ import com.lambda.interaction.construction.simulation.result.Dependent import com.lambda.interaction.construction.simulation.result.results.BreakResult import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.managers.Request -import com.lambda.interaction.managers.breaking.BreakRequest.BreakRequestMarker import com.lambda.threading.runSafe import com.lambda.util.BlockUtils.blockState import com.lambda.util.BlockUtils.isEmpty @@ -67,20 +66,21 @@ data class BreakRequest( override val done: Boolean get() = runSafe { contexts.all { blockState(it.blockPos).isEmpty } } == true - @DslMarker - annotation class BreakRequestMarker - @BreakRequestMarker override fun submit(queueIfMismatchedStage: Boolean) = BreakManager.request(this, queueIfMismatchedStage) companion object { var requestCount = 0 + private set } } +@DslMarker +annotation class BreakRequestMarker + @BreakRequestMarker -class BreakRequestBuilder( +class BreakRequestBuilder private constructor( private val contexts: Collection, private val pendingInteractions: MutableCollection, private val nowOrNothing: Boolean, @@ -122,7 +122,6 @@ class BreakRequestBuilder( onReBreak = callback } - @BreakRequestMarker private fun build() = BreakRequest( contexts, @@ -139,7 +138,6 @@ class BreakRequestBuilder( ) companion object { - @BreakRequestMarker @JvmName("breakRequest1") fun AutomatedSafeContext.breakRequest( positions: Collection, @@ -151,7 +149,6 @@ class BreakRequestBuilder( .simulate() .breakRequest(pendingInteractions, nowOrNothing, builder) - @BreakRequestMarker @JvmName("breakRequest2") context(automated: Automated) fun Collection.breakRequest( @@ -161,7 +158,6 @@ class BreakRequestBuilder( ) = asSequence() .breakRequest(pendingInteractions, nowOrNothing, builder) - @BreakRequestMarker @JvmName("breakRequest3") context(automated: Automated) fun Sequence.breakRequest( @@ -176,7 +172,6 @@ class BreakRequestBuilder( .takeIf { it.isNotEmpty() } ?.let { automated.breakRequest(it, pendingInteractions, nowOrNothing, builder) } - @BreakRequestMarker @JvmName("breakRequest4") fun Automated.breakRequest( contexts: Collection, diff --git a/src/main/kotlin/com/lambda/interaction/managers/hotbar/HotbarManager.kt b/src/main/kotlin/com/lambda/interaction/managers/hotbar/HotbarManager.kt index ab53fe97c..3feb3275c 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/hotbar/HotbarManager.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/hotbar/HotbarManager.kt @@ -41,11 +41,7 @@ import net.minecraft.item.ItemStack */ object HotbarManager : Manager( 1, - onOpen = { - if (activeRequest != null) { - setActiveSlot() - } - }, + onOpen = { if (activeRequest != null) setActiveSlot() }, onClose = { checkResetSwap() } ) { private var activeRequest: HotbarRequest? = null diff --git a/src/main/kotlin/com/lambda/interaction/managers/hotbar/HotbarRequest.kt b/src/main/kotlin/com/lambda/interaction/managers/hotbar/HotbarRequest.kt index 331086e9e..f16a1d6f3 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/hotbar/HotbarRequest.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/hotbar/HotbarRequest.kt @@ -22,10 +22,10 @@ import com.lambda.interaction.managers.Request class HotbarRequest( val slot: Int, - automated: Automated, var keepTicks: Int = automated.hotbarConfig.keepTicks, val swapPause: Int = automated.hotbarConfig.swapPause, - override val nowOrNothing: Boolean = true + override val nowOrNothing: Boolean = true, + automated: Automated ) : Request(), Automated by automated { override val requestId = ++requestCount override val tickStageMask get() = hotbarConfig.tickStageMask @@ -36,10 +36,41 @@ class HotbarRequest( override val done: Boolean get() = slot == HotbarManager.activeSlot && swapPauseAge >= swapPause + @HotbarRequestMarker override fun submit(queueIfMismatchedStage: Boolean) = HotbarManager.request(this, queueIfMismatchedStage) companion object { var requestCount = 0 + private set } } + +@DslMarker +private annotation class HotbarRequestMarker + +@Suppress("unused") +@HotbarRequestMarker +class HotbarRequestBuilder private constructor( + private val slot: Int, + private val nowOrNothing: Boolean, + private val automated: Automated, +) { + private var keepTicks: Int = automated.hotbarConfig.keepTicks + private var swapPause: Int = automated.hotbarConfig.swapPause + + fun keepTicks(keepTicks: Int) { + this.keepTicks = keepTicks + } + + fun swapPause(swapPause: Int) { + this.swapPause = swapPause + } + + private fun build() = HotbarRequest(slot, keepTicks, swapPause, nowOrNothing, automated) + + companion object { + fun Automated.hotbarRequest(slot: Int, nowOrNothing: Boolean = false, builder: (HotbarRequestBuilder.() -> Unit)? = null) = + HotbarRequestBuilder(slot, nowOrNothing, this).apply { builder?.invoke(this) }.build() + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractManager.kt b/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractManager.kt index b016527b6..56fa03e4e 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractManager.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractManager.kt @@ -38,13 +38,13 @@ import com.lambda.interaction.managers.Manager import com.lambda.interaction.managers.ManagerUtils.isPosBlocked import com.lambda.interaction.managers.PositionBlocking import com.lambda.interaction.managers.breaking.BreakManager -import com.lambda.interaction.managers.hotbar.HotbarRequest +import com.lambda.interaction.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest import com.lambda.interaction.managers.interacting.InteractManager.activeRequest import com.lambda.interaction.managers.interacting.InteractManager.maxInteractionsThisTick import com.lambda.interaction.managers.interacting.InteractManager.populateFrom import com.lambda.interaction.managers.interacting.InteractManager.potentialInteractions import com.lambda.interaction.managers.interacting.InteractManager.processRequest -import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest +import com.lambda.interaction.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.modules.world.AutoSign.signWriteDelay import com.lambda.threading.runConcurrent import com.lambda.threading.runSafeAutomated @@ -162,10 +162,7 @@ object InteractManager : Manager( if (!canInteractThisTick()) break val firstInteraction = potentialInteractions.first() if (player.inventory.selectedSlot != firstInteraction.hotbarIndex) { - val hotbarRequest = HotbarRequest( - firstInteraction.hotbarIndex, - this - ).submit(queueIfMismatchedStage = false) + val hotbarRequest = hotbarRequest(firstInteraction.hotbarIndex).submit(false) if (!hotbarRequest.done) break } var interactResult: InteractResult? = null diff --git a/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractRequest.kt b/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractRequest.kt index b5baa4a98..3132cda20 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractRequest.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractRequest.kt @@ -25,7 +25,6 @@ import com.lambda.interaction.construction.simulation.result.BuildResult import com.lambda.interaction.construction.simulation.result.Dependent import com.lambda.interaction.construction.simulation.result.results.InteractResult import com.lambda.interaction.managers.Request -import com.lambda.interaction.managers.interacting.InteractRequest.PlaceRequestMarker import com.lambda.threading.runSafe import com.lambda.util.BlockUtils.blockState import com.lambda.util.BlockUtils.matches @@ -46,20 +45,21 @@ data class InteractRequest( contexts.all { it.expectedState.matches(blockState(it.blockPos)) } } == true - @DslMarker - annotation class PlaceRequestMarker - - @PlaceRequestMarker + @InteractRequestMarker override fun submit(queueIfMismatchedStage: Boolean) = InteractManager.request(this, queueIfMismatchedStage) companion object { var requestCount = 0 + private set } } -@PlaceRequestMarker -class PlaceRequestBuilder( +@DslMarker +annotation class InteractRequestMarker + +@InteractRequestMarker +class PlaceRequestBuilder private constructor( private val contexts: Collection, private val pendingInteractions: MutableCollection, private val nowOrNothing: Boolean, @@ -71,7 +71,6 @@ class PlaceRequestBuilder( onPlace = callback } - @PlaceRequestMarker private fun build() = InteractRequest( contexts, diff --git a/src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryRequest.kt b/src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryRequest.kt index 277eb2faf..5e3c84e92 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryRequest.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryRequest.kt @@ -39,7 +39,7 @@ import net.minecraft.util.math.Direction * @property settleForLess A flag indicating whether to settle for partial completion of the request. * @property mustPerform A flag indicating whether the request must be performed regardless of conditions as long as the tick stage is valid. */ -class InventoryRequest private constructor( +class InventoryRequest( val actions: List, val settleForLess: Boolean, val mustPerform: Boolean, @@ -55,128 +55,124 @@ class InventoryRequest private constructor( override fun submit(queueIfMismatchedStage: Boolean) = InventoryManager.request(this, queueIfMismatchedStage) - @DslMarker - private annotation class InvRequestDsl + companion object { + var requestCount = 0 + private set + } +} - @Suppress("unused") - @InvRequestDsl - class InvRequestBuilder(val settleForLess: Boolean, val mustPerform: Boolean) { - val actions = mutableListOf() - var onComplete: (SafeContext.() -> Unit)? = null - - fun click(slotId: Int, button: Int, actionType: SlotActionType) { - InventoryAction.Inventory { clickSlot(slotId, button, actionType) }.addToActions() - } - - fun resyncInventory() { - InventoryAction.Inventory { - connection.sendPacket { - val sh = player.currentScreenHandler - ClickSlotC2SPacket( - sh.syncId, - -1, -1, 0, - SlotActionType.CLONE, - Int2ObjectOpenHashMap(), - ItemStackHash.fromItemStack(sh.cursorStack, connection.componentHasher) - ) - } - }.addToActions() - } - - fun pickFromInventory(slotId: Int) { - InventoryAction.Inventory { - clickSlot(slotId, player.inventory.selectedSlot, SlotActionType.SWAP) - }.addToActions() - } - - fun dropItemInHand(entireStack: Boolean = true) { - InventoryAction.Inventory { player.dropSelectedItem(entireStack) }.addToActions() - } - - fun swapHands() { - InventoryAction.Player { - val offhandStack = player.getStackInHand(Hand.OFF_HAND) - player.setStackInHand(Hand.OFF_HAND, player.getStackInHand(Hand.MAIN_HAND)) - player.setStackInHand(Hand.MAIN_HAND, offhandStack) - connection.sendPacket { - PlayerActionC2SPacket( - PlayerActionC2SPacket.Action.SWAP_ITEM_WITH_OFFHAND, - BlockPos.ORIGIN, - Direction.DOWN - ) - } - }.addToActions() - } - - fun clickCreativeStack(stack: ItemStack, slotId: Int) { - InventoryAction.Inventory { interaction.clickCreativeStack(stack, slotId) }.addToActions() - } - - fun pickup(slotId: Int, button: Int = 0) = click(slotId, button, SlotActionType.PICKUP) - - // Quick move action (Shift-click) - fun quickMove(slotId: Int) = click(slotId, 0, SlotActionType.QUICK_MOVE) - - fun swap(slotId: Int, hotbarSlot: Int) = click(slotId, hotbarSlot, SlotActionType.SWAP) - - // Clone action (Creative mode) - fun clone(slotId: Int) = click(slotId, 2, SlotActionType.CLONE) - - // Throw stack or single item - fun throwStack(slotId: Int) = click(slotId, 1, SlotActionType.THROW) - - fun throwSingle(slotId: Int) = click(slotId, 0, SlotActionType.THROW) - - // Quick craft action - fun quickCraftStart(slotId: Int) = click(slotId, 0, SlotActionType.QUICK_CRAFT) - - fun quickCraftDrag(slotId: Int) = click(slotId, 1, SlotActionType.QUICK_CRAFT) - - fun quickCraftEnd(slotId: Int) = click(slotId, 2, SlotActionType.QUICK_CRAFT) - - // Pickup all items (double-click) - fun pickupAll(slotId: Int) = click(slotId, 0, SlotActionType.PICKUP_ALL) - - // Helper function: Move items from one slot to another - fun moveSlot(fromSlotId: Int, toSlotId: Int, button: Int = 0) { - pickup(fromSlotId, button) - pickup(toSlotId, button) - } - - // Helper function: Split a stack into two - fun splitStack(slotId: Int, targetSlotId: Int) { - pickup(slotId, 1) // Pickup half the stack - pickup(targetSlotId, 0) // Place it in the target slot - } - - // Helper function: Merge stacks - fun mergeStacks(sourceSlotId: Int, targetSlotId: Int) { - pickup(sourceSlotId, 0) - pickup(targetSlotId, 0) - } - - fun action(action: SafeContext.() -> Unit) { - InventoryAction.Other(action).addToActions() - } - - fun onComplete(callback: SafeContext.() -> Unit) { - onComplete = callback - } - - private fun InventoryAction.addToActions() { - actions.add(this) - } +@DslMarker +annotation class InvRequestDsl + +@Suppress("unused") +@InvRequestDsl +class InvRequestBuilder private constructor( + private val settleForLess: Boolean, + private val mustPerform: Boolean +) { + private val actions = mutableListOf() + private var onComplete: (SafeContext.() -> Unit)? = null + + fun click(slotId: Int, button: Int, actionType: SlotActionType) { + InventoryAction.Inventory { clickSlot(slotId, button, actionType) }.addToActions() } - companion object { - var requestCount = 0 + fun resyncInventory() { + InventoryAction.Inventory { + connection.sendPacket { + val sh = player.currentScreenHandler + ClickSlotC2SPacket( + sh.syncId, + -1, -1, 0, + SlotActionType.CLONE, + Int2ObjectOpenHashMap(), + ItemStackHash.fromItemStack(sh.cursorStack, connection.componentHasher) + ) + } + }.addToActions() + } + + fun pickFromInventory(slotId: Int) { + InventoryAction.Inventory { + clickSlot(slotId, player.inventory.selectedSlot, SlotActionType.SWAP) + }.addToActions() + } + + fun dropItemInHand(entireStack: Boolean = true) { + InventoryAction.Inventory { player.dropSelectedItem(entireStack) }.addToActions() + } + + fun swapHands() { + InventoryAction.Player { + val offhandStack = player.getStackInHand(Hand.OFF_HAND) + player.setStackInHand(Hand.OFF_HAND, player.getStackInHand(Hand.MAIN_HAND)) + player.setStackInHand(Hand.MAIN_HAND, offhandStack) + connection.sendPacket { + PlayerActionC2SPacket( + PlayerActionC2SPacket.Action.SWAP_ITEM_WITH_OFFHAND, + BlockPos.ORIGIN, + Direction.DOWN + ) + } + }.addToActions() + } + + fun clickCreativeStack(stack: ItemStack, slotId: Int) { + InventoryAction.Inventory { interaction.clickCreativeStack(stack, slotId) }.addToActions() + } + + fun pickup(slotId: Int, button: Int = 0) = click(slotId, button, SlotActionType.PICKUP) + + fun quickMove(slotId: Int) = click(slotId, 0, SlotActionType.QUICK_MOVE) + + fun swapWithHotbar(slotId: Int, hotbarSlot: Int) = click(slotId, hotbarSlot, SlotActionType.SWAP) + + fun clone(slotId: Int) = click(slotId, 2, SlotActionType.CLONE) - @InvRequestDsl + fun throwStack(slotId: Int) = click(slotId, 1, SlotActionType.THROW) + + fun throwSingle(slotId: Int) = click(slotId, 0, SlotActionType.THROW) + + fun quickCraftStart(slotId: Int) = click(slotId, 0, SlotActionType.QUICK_CRAFT) + + fun quickCraftDrag(slotId: Int) = click(slotId, 1, SlotActionType.QUICK_CRAFT) + + fun quickCraftEnd(slotId: Int) = click(slotId, 2, SlotActionType.QUICK_CRAFT) + + fun pickupAll(slotId: Int) = click(slotId, 0, SlotActionType.PICKUP_ALL) + + fun moveSlot(fromSlotId: Int, toSlotId: Int, button: Int = 0) { + pickup(fromSlotId, button) + pickup(toSlotId, button) + } + + fun splitStack(slotId: Int, targetSlotId: Int) { + pickup(slotId, 1) + pickup(targetSlotId, 0) + } + + fun mergeStacks(sourceSlotId: Int, targetSlotId: Int) { + pickup(sourceSlotId, 0) + pickup(targetSlotId, 0) + } + + fun action(action: SafeContext.() -> Unit) { + InventoryAction.Other(action).addToActions() + } + + fun onComplete(callback: SafeContext.() -> Unit) { + onComplete = callback + } + + private fun InventoryAction.addToActions() { + actions.add(this) + } + + context(automated: Automated) + private fun build() = InventoryRequest(actions, settleForLess, mustPerform, automated, onComplete = onComplete) + + companion object { fun Automated.inventoryRequest(settleForLess: Boolean = false, mustPerform: Boolean = false, builder: InvRequestBuilder.() -> Unit) = InvRequestBuilder(settleForLess, mustPerform).apply(builder).build() - - @InvRequestDsl - context(automated: Automated) - private fun InvRequestBuilder.build() = InventoryRequest(actions, settleForLess, mustPerform, automated, onComplete = onComplete) } } diff --git a/src/main/kotlin/com/lambda/interaction/managers/rotating/RotationRequest.kt b/src/main/kotlin/com/lambda/interaction/managers/rotating/RotationRequest.kt index 46b385b1e..518c98465 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/rotating/RotationRequest.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/rotating/RotationRequest.kt @@ -21,14 +21,12 @@ import com.lambda.context.Automated import com.lambda.context.SafeContext import com.lambda.interaction.handlers.BaritoneHandler import com.lambda.interaction.managers.Request -import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.requestCount import com.lambda.interaction.managers.rotating.IRotationRequest.Full import com.lambda.interaction.managers.rotating.IRotationRequest.Pitch import com.lambda.interaction.managers.rotating.IRotationRequest.Yaw import com.lambda.interaction.managers.rotating.Rotation.Companion.dist import com.lambda.interaction.managers.rotating.Rotation.Companion.rotation import com.lambda.interaction.managers.rotating.Rotation.Companion.wrap -import com.lambda.interaction.managers.rotating.RotationRequest.RotationRequestMarker import com.lambda.threading.runSafe import com.lambda.util.collections.UpdatableLazy import com.lambda.util.collections.updatableLazy @@ -42,14 +40,19 @@ abstract class RotationRequest(automated: Automated) : Request(), Automated by a abstract infix fun dist(rotation: Rotation): Double - @DslMarker - annotation class RotationRequestMarker - @RotationRequestMarker override fun submit(queueIfMismatchedStage: Boolean) = RotationManager.request(this, queueIfMismatchedStage) + + companion object { + var requestCount = 0 + private set + } } +@DslMarker +annotation class RotationRequestMarker + interface IRotationRequest : Automated { var keepTicks: Int var decayTicks: Int @@ -139,14 +142,10 @@ interface IRotationRequest : Automated { interface YawRot : IRotationRequest { val yaw: UpdatableLazy } interface PitchRot : IRotationRequest { val pitch: UpdatableLazy } interface FullRot : YawRot, PitchRot { val rotation: UpdatableLazy } - - companion object { - var requestCount = 0 - } } @RotationRequestMarker -class RotationRequestBuilder { +class RotationRequestBuilder private constructor() { private var pitchBuilder: (SafeContext.() -> Double)? = null private var yawBuilder: (SafeContext.() -> Double)? = null private var rotationBuilder: (SafeContext.() -> Rotation)? = null diff --git a/src/main/kotlin/com/lambda/module/modules/combat/AutoArmor.kt b/src/main/kotlin/com/lambda/module/modules/combat/AutoArmor.kt index dc827d9be..aa0a248a2 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/AutoArmor.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/AutoArmor.kt @@ -27,7 +27,7 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.inventory.container.containers.ArmorContainer import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer -import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest +import com.lambda.interaction.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.EnchantmentUtils.getEnchantment diff --git a/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt b/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt index 348045061..3ca0ee386 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt @@ -25,7 +25,7 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.handlers.FriendHandler import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.select -import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest +import com.lambda.interaction.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.combat.CombatUtils.hasDeadlyCrystal @@ -66,7 +66,7 @@ object AutoTotem : Module( if ((!ignoreWhenHolding || !player.isHolding(Items.TOTEM_OF_UNDYING)) && player.offHandStack.item != Items.TOTEM_OF_UNDYING) { Items.TOTEM_OF_UNDYING.select() - .filterSlots(player.currentScreenHandler.slots) + .filter(player.currentScreenHandler.slots) .takeIf { it.isNotEmpty() } ?.let { totems -> val cursor = player.currentScreenHandler.cursorStack @@ -75,7 +75,7 @@ object AutoTotem : Module( inventoryRequest { targetSlot?.let { pickup(it.id, 0) } - swap(totems.first().id, 40) + swapWithHotbar(totems.first().id, 40) }.submit() } } diff --git a/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt b/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt index f5b071e0b..d20b8b8a8 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt @@ -31,7 +31,7 @@ import com.lambda.interaction.handlers.ContainerHandler.transfer import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.interaction.inventory.container.containers.OffHandContainer -import com.lambda.interaction.managers.hotbar.HotbarRequest +import com.lambda.interaction.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest import com.lambda.interaction.managers.rotating.Rotation.Companion.rotationTo import com.lambda.interaction.managers.rotating.RotationManager import com.lambda.interaction.managers.rotating.RotationRequestBuilder.Companion.rotationRequest @@ -486,16 +486,16 @@ object CrystalAura : Module( (swapHand == Hand.OFF_HAND && player.offHandStack.item != selection.item) ) runSafeAutomated { if (!swap) return@runSafe - var crystalSlot = HotbarContainer.stacks.indexOfFirst { selection.filterStack(it) } + var crystalSlot = HotbarContainer.stacks.indexOfFirst { selection.matches(it) } if (crystalSlot < 0) { val swapTo = when (swapHand) { Hand.MAIN_HAND -> HotbarContainer Hand.OFF_HAND -> OffHandContainer } if (!selection.transfer(swapTo)) return@runSafe - crystalSlot = HotbarContainer.stacks.indexOfFirst { selection.filterStack(it) } + crystalSlot = HotbarContainer.stacks.indexOfFirst { selection.matches(it) } } - if (!HotbarRequest(crystalSlot, this).submit().done) return@runSafe + if (!hotbarRequest(crystalSlot).submit().done) return@runSafe } placeTimer.runSafeIfPassed(placeDelay.milliseconds) { diff --git a/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt b/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt index bc64a3c70..8ed274c82 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt @@ -30,7 +30,7 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack import com.lambda.interaction.inventory.container.containers.HotbarContainer -import com.lambda.interaction.managers.hotbar.HotbarRequest +import com.lambda.interaction.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest import com.lambda.interaction.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag @@ -130,7 +130,7 @@ object KillAura : Module( selection.bestMatch(HotbarContainer.stacks)?.let { bestStack -> val slotId = HotbarContainer.stacks.indexOf(bestStack) - if (!HotbarRequest(slotId, this@KillAura, nowOrNothing = false).submit().done) return@listen + if (!hotbarRequest(slotId).submit().done) return@listen } } diff --git a/src/main/kotlin/com/lambda/module/modules/debug/SilentSwap.kt b/src/main/kotlin/com/lambda/module/modules/debug/SilentSwap.kt index a475f0c58..17a70165e 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/SilentSwap.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/SilentSwap.kt @@ -22,7 +22,7 @@ import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomat import com.lambda.config.withEdits import com.lambda.event.events.PlayerEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.hotbar.HotbarRequest +import com.lambda.interaction.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.CommunicationUtils.info @@ -39,7 +39,7 @@ object SilentSwap : Module( } listen { - if (!HotbarRequest(0, this@SilentSwap).submit().done) { + if (!hotbarRequest(0).submit().done) { it.cancel() return@listen } diff --git a/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt b/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt index 35bec0adf..05118f58b 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt @@ -33,8 +33,9 @@ import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.select import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer import com.lambda.interaction.inventory.container.containers.HotbarContainer -import com.lambda.interaction.managers.hotbar.HotbarRequest -import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest +import com.lambda.interaction.inventory.container.containers.InventoryContainer +import com.lambda.interaction.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest +import com.lambda.interaction.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.threading.runSafe @@ -85,7 +86,7 @@ object BetterFirework : Module( val ClientPlayerEntity.hasFireworks: Boolean get() = Items.FIREWORK_ROCKET.select() - .filterStacks(HotbarAndInventoryContainer.stacks) + .filter(HotbarAndInventoryContainer.stacks) .isNotEmpty() || offHandStack.item == Items.FIREWORK_ROCKET context(_: SafeContext) @@ -156,39 +157,35 @@ object BetterFirework : Module( * Return true if a firework has been used */ fun SafeContext.startFirework(inventory: Boolean) { - val stack = selectStack(1) { isItem(Items.FIREWORK_ROCKET) } - - stack.bestMatch(HotbarContainer.stacks) - ?.let { - val request = HotbarRequest(HotbarContainer.stacks.indexOf(it), this@BetterFirework, keepTicks = 0) - .submit(queueIfMismatchedStage = false) - if (request.done) { - interaction.interactItem(player, Hand.MAIN_HAND) - sendSwing() - } - return - } + val selection = Items.FIREWORK_ROCKET.select(1) + + val hotbarMatch = selection.bestMatch(HotbarContainer.slots) + if (hotbarMatch != null) { + swapAndFirework(hotbarMatch.index) + return + } if (!inventory) return - stack.bestMatch(HotbarContainer.stacks) - ?.let { - val swapSlotId = HotbarAndInventoryContainer.stacks.indexOf(it) - val hotbarSlotToSwapWith = HotbarContainer.stacks.find { slot -> slot.isEmpty }?.let { slot -> HotbarContainer.stacks.indexOf(slot) } ?: 8 - - inventoryRequest { - swap(swapSlotId, hotbarSlotToSwapWith) - action { - val request = HotbarRequest(hotbarSlotToSwapWith, this@BetterFirework, keepTicks = 0, nowOrNothing = true) - .submit(queueIfMismatchedStage = false) - if (request.done) { - interaction.interactItem(player, Hand.MAIN_HAND) - sendSwing() - } - } - swap(swapSlotId, hotbarSlotToSwapWith) - }.submit() - } + val inventoryMatch = selection.bestMatch(InventoryContainer.slots) ?: return + val hotbarSlot = selectStack { isEmpty() }.bestMatch(HotbarContainer.slots)?.index ?: 8 + + inventoryRequest { + swapWithHotbar(inventoryMatch.id, hotbarSlot) + action { swapAndFirework(hotbarSlot) } + swapWithHotbar(inventoryMatch.id, hotbarSlot) + }.submit() + } + + private fun SafeContext.swapAndFirework(hotbarSlot: Int) { + val request = + hotbarRequest(hotbarSlot, true) { + keepTicks(0) + }.submit(false) + if (request.done) { + interaction.interactItem(player, Hand.MAIN_HAND) + sendSwing() + } } enum class TakeoffState { diff --git a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt index 640ef95e7..edc0e2bc0 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt @@ -27,10 +27,10 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.handlers.GlideHandler.ELYTRA_SELECTION import com.lambda.interaction.inventory.container.containers.ArmorContainer import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer -import com.lambda.interaction.managers.hotbar.HotbarRequest +import com.lambda.interaction.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest +import com.lambda.interaction.managers.inventory.InvRequestBuilder +import com.lambda.interaction.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.interaction.managers.inventory.InventoryManager -import com.lambda.interaction.managers.inventory.InventoryRequest -import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest import com.lambda.module.modules.movement.elytrafly.ElytraFly.FlyMode import com.lambda.module.modules.movement.elytrafly.ElytraFly.fakeFly import com.lambda.threading.runSafe @@ -96,15 +96,13 @@ abstract class ElytraFlyMode( val chestSlot = ArmorContainer.slots.getOrNull(1) ?: return false if (elytraInHotbar) { - val hotbarRequest = HotbarRequest( - elytraSlot.index, - ElytraFly, - keepTicks = 0, - nowOrNothing = true - ).submit() + val hotbarRequest = + hotbarRequest(elytraSlot.index) { + keepTicks(0) + }.submit() if (!hotbarRequest.done) return false } - fun InventoryRequest.InvRequestBuilder.swapChest() { + fun InvRequestBuilder.swapChest() { if (elytraInHotbar) { interaction.interactItem(player, Hand.MAIN_HAND) InventoryManager.indexInventoryChanges() @@ -125,8 +123,7 @@ abstract class ElytraFlyMode( open fun interrupt() {} - fun SafeContext.findElytra(): Slot? = - ELYTRA_SELECTION.filterSlots(HotbarAndInventoryContainer.slots).minByOrNull { it.index } + fun findElytra(): Slot? = ELYTRA_SELECTION.filter(HotbarAndInventoryContainer.slots).minByOrNull { it.index } protected fun SafeContext.startFly() { player.setFlag(Entity.GLIDING_FLAG_INDEX, true) diff --git a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/GrimControlElytraFly.kt b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/GrimControlElytraFly.kt index 22b8e3048..7c3875b0b 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/GrimControlElytraFly.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/GrimControlElytraFly.kt @@ -121,11 +121,22 @@ class GrimControlElytraFly( val rot = RotationManager.activeRotation val flipFlop = rotFlipFlop.toFloat() * 0.0001f rotFlipFlop = !rotFlipFlop - event.packet = PlayerMoveC2SPacket.Full(player.pos, rot.yawF + flipFlop, rot.pitchF, player.isOnGround, player.horizontalCollision) + event.packet = + PlayerMoveC2SPacket.Full( + player.pos, + rot.yawF + flipFlop, + rot.pitchF, + player.isOnGround, + player.horizontalCollision + ) } listen { event -> - if (event.packet !is PlayerMoveC2SPacket || !player.isGliding || flipFlopMode.isFlipFlopping(hasFirework) || moving) return@listen + if (event.packet !is PlayerMoveC2SPacket || + !player.isGliding || + flipFlopMode.isFlipFlopping(hasFirework) || + moving + ) return@listen if (stillTickTimer.hasSurpassed(packetGap)) { stillTickTimer.reset() return@listen diff --git a/src/main/kotlin/com/lambda/module/modules/player/AutoElytraSwap.kt b/src/main/kotlin/com/lambda/module/modules/player/AutoElytraSwap.kt index b912ec751..407de5e5d 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/AutoElytraSwap.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/AutoElytraSwap.kt @@ -24,7 +24,7 @@ import com.lambda.interaction.handlers.GlideHandler.manuallySwapped import com.lambda.interaction.handlers.GlideHandler.swapped import com.lambda.interaction.inventory.container.containers.ArmorContainer import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer -import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest +import com.lambda.interaction.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module import com.lambda.module.modules.combat.AutoArmor import com.lambda.module.tag.ModuleTag @@ -44,24 +44,23 @@ object AutoElytraSwap : Module( onDisable { restore() } } - context(safeContext: SafeContext) fun manualSwap(elytra: Boolean): Boolean { val swapSlot = HotbarAndInventoryContainer.slots.let { slots -> - if (elytra) ELYTRA_SELECTION.filterSlots(slots) - else CHESTPLATE_SELECTION.filterSlots(slots) + if (elytra) ELYTRA_SELECTION.filter(slots) + else CHESTPLATE_SELECTION.filter(slots) }.minByOrNull { it.index } ?: run { AutoElytraSwap.warn("The required armor piece was not found for AutoElytraSwap to work.") return false } - return safeContext.swapWithChestplate(swapSlot) + return swapWithChestplate(swapSlot) } - private fun SafeContext.swapWithChestplate(swapSlot: Slot): Boolean { + private fun swapWithChestplate(swapSlot: Slot): Boolean { val chestplateSlot = ArmorContainer.slots.getOrNull(1) ?: return false return AutoElytraSwap.inventoryRequest { - if (swapSlot.index in 0..8) swap(chestplateSlot.id, swapSlot.index) + if (swapSlot.index in 0..8) swapWithHotbar(chestplateSlot.id, swapSlot.index) else { moveSlot(swapSlot.id, chestplateSlot.id) if (!chestplateSlot.stack.isEmpty) pickup(swapSlot.id) diff --git a/src/main/kotlin/com/lambda/module/modules/player/InventoryResync.kt b/src/main/kotlin/com/lambda/module/modules/player/InventoryResync.kt index c432a001d..ba421be95 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/InventoryResync.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/InventoryResync.kt @@ -19,7 +19,7 @@ package com.lambda.module.modules.player import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest +import com.lambda.interaction.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.TickTimer diff --git a/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt b/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt index 664e7363d..3a2ba080c 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt @@ -26,7 +26,7 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.interaction.inventory.container.containers.InventoryContainer -import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest +import com.lambda.interaction.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.item.ItemStackUtils.slotId diff --git a/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt b/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt index a9984aa86..6df4bcb7d 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt @@ -25,7 +25,7 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.interaction.inventory.container.containers.InventoryContainer -import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest +import com.lambda.interaction.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.EnchantmentUtils.forEachEnchantment diff --git a/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt b/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt index 7335d5399..6e2970b72 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt @@ -39,8 +39,8 @@ import com.lambda.interaction.handlers.BaritoneHandler import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer import com.lambda.interaction.inventory.container.containers.HotbarContainer -import com.lambda.interaction.managers.hotbar.HotbarRequest -import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest +import com.lambda.interaction.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest +import com.lambda.interaction.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module import com.lambda.module.modules.world.AutoPortal.PosHandler.currAnchorPos import com.lambda.module.modules.world.AutoPortal.PosHandler.obiPositions @@ -326,19 +326,18 @@ object AutoPortal : Module( val sel = selectStack(1) { isItem() } - val hotbarStack = sel.filterSlots(HotbarContainer.slots).firstOrNull() + val hotbarStack = sel.filter(HotbarContainer.slots).firstOrNull() if (hotbarStack != null) { - val request = HotbarRequest( - hotbarStack.index, - this@AutoPortal, - keepTicks = 0 - ).submit(queueIfMismatchedStage = false) + val request = + hotbarRequest(hotbarStack.index) { + keepTicks(0) + }.submit(false) if (request.done) block() return } val invSlot = - if (inventory) sel.filterSlots(HotbarAndInventoryContainer.slots).firstOrNull() + if (inventory) sel.filter(HotbarAndInventoryContainer.slots).firstOrNull() else null if (invSlot == null) { failure("No Flint and Steel!") @@ -350,19 +349,15 @@ object AutoPortal : Module( }?.index ?: 8 inventoryRequest { - swap(invSlot.id, hotbarSlotToSwapWith) + swapWithHotbar(invSlot.id, hotbarSlotToSwapWith) action { - val request = HotbarRequest( - hotbarSlotToSwapWith, - this@AutoPortal, - keepTicks = 0, - nowOrNothing = true - ).submit(queueIfMismatchedStage = false) - if (request.done) { - block() - } + val request = + hotbarRequest(hotbarSlotToSwapWith) { + keepTicks(0) + }.submit(false) + if (request.done) block() } - swap(invSlot.id, hotbarSlotToSwapWith) + swapWithHotbar(invSlot.id, hotbarSlotToSwapWith) }.submit() } } 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 07fc716de..1708ba5c4 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/Scaffold.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/Scaffold.kt @@ -86,8 +86,8 @@ object Scaffold : Module( stack.blockItem.let { blockItem -> blockItem != null && blockItem.block !in blacklistedBlocks } } } - val stack = player.mainHandStack.takeIf { selection.filterStack(it) } - ?: selection.filterStacks(HotbarContainer.stacks).firstOrNull() ?: return@listen + val stack = player.mainHandStack.takeIf { selection.matches(it) } + ?: selection.filter(HotbarContainer.stacks).firstOrNull() ?: return@listen val playerSupport = player.blockPos.down() val alreadySupported = blockState(playerSupport).hasSolidTopSurface(world, playerSupport, player) if (alreadySupported) return@listen 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 cb38bb2a3..814e556bb 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt @@ -46,8 +46,8 @@ import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryC import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.interaction.inventory.container.containers.InventoryContainer import com.lambda.interaction.inventory.container.containers.OffHandContainer -import com.lambda.interaction.managers.hotbar.HotbarRequest -import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest +import com.lambda.interaction.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest +import com.lambda.interaction.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.interaction.managers.rotating.Rotation import com.lambda.interaction.managers.rotating.Rotation.Companion.dist import com.lambda.interaction.managers.rotating.RotationManager @@ -639,7 +639,7 @@ object StashMover : Module( inventoryRequest { quickMove(firstSlot.id) }.submit() return } else if (player.offHandStack.isEmpty) { - inventoryRequest { swap(firstSlot.id, 40) }.submit() + inventoryRequest { swapWithHotbar(firstSlot.id, 40) }.submit() return } failWithLog("No free slots for an ender pearl!") @@ -683,7 +683,7 @@ object StashMover : Module( if (player.mainHandStack.item != Items.ENDER_PEARL) { val hotbarSlot = HotbarContainer.slots.firstOrNull { it.stack.item === Items.ENDER_PEARL } if (hotbarSlot != null) { - val hotbarRequest = HotbarRequest(hotbarSlot.index, StashMover, nowOrNothing = false).submit() + val hotbarRequest = hotbarRequest(hotbarSlot.index).submit() if (!hotbarRequest.done) return } else { val inventorySlot = player.allSlots.firstOrNull { it.stack.item === Items.ENDER_PEARL } @@ -691,7 +691,7 @@ object StashMover : Module( failWithLog("No pearl in inventory!") return } - inventoryRequest { swap(inventorySlot.id, 0) }.submit() + inventoryRequest { swapWithHotbar(inventorySlot.id, 0) }.submit() return } } diff --git a/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt b/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt index 044ecd6bf..31888b729 100644 --- a/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt @@ -50,7 +50,7 @@ import com.lambda.interaction.handlers.BaritoneHandler import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer import com.lambda.interaction.managers.breaking.BreakRequestBuilder.Companion.breakRequest import com.lambda.interaction.managers.interacting.PlaceRequestBuilder.Companion.interactRequest -import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest +import com.lambda.interaction.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.modules.client.Client import com.lambda.task.Task import com.lambda.task.tasks.EatTask.Companion.eat diff --git a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt index e71aa3f42..c06883dee 100644 --- a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt @@ -25,8 +25,8 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.inventory.StackSelection import com.lambda.interaction.inventory.container.Container import com.lambda.interaction.inventory.container.ExternalContainer -import com.lambda.interaction.inventory.container.containers.InventoryContainer -import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest +import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer +import com.lambda.interaction.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.task.Task import com.lambda.threading.runSafeAutomated @@ -39,40 +39,30 @@ class ContainerTransferTask( ) : Task(), Automated by automated { override val name = "Transferring $stackSelection from $fromContainer to $toContainer" - private var delegateTask: Task<*>? = null - init { listen { - if (delegateTask?.isCompleted == true) { - success() - return@listen - } runSafeAutomated { - val fromExternal = fromContainer as? ExternalContainer val slots = fromContainer.slots val toSlots = toContainer.slots - fromExternal.takeIf { slots.isEmpty() }?.let { fromExternal -> + if (fromContainer is ExternalContainer && slots.isEmpty()) { if (toContainer is ExternalContainer && toSlots.isEmpty()) { - fromContainer.transferByTask(stackSelection, InventoryContainer).finally { - fromContainer = InventoryContainer - }.execute(this@ContainerTransferTask) - return@listen - } - delegateTask = fromExternal.accessThen { - fromContainer.transferByTask(stackSelection, toContainer) - }?.execute(this@ContainerTransferTask) ?: run { - checkFail() + fromContainer + .transferByTask(stackSelection, HotbarAndInventoryContainer) + .finally { fromContainer = HotbarAndInventoryContainer } + .execute(this@ContainerTransferTask) return@listen } return@listen } if (toContainer is ExternalContainer && toSlots.isEmpty()) { - delegateTask = toContainer.accessThen { - fromContainer.transferByTask(stackSelection, toContainer) - }?.execute(this@ContainerTransferTask) ?: run { - checkFail() - return@listen - } + toContainer + .accessThen { + fromContainer.transferByTask(stackSelection, toContainer) + }?.finally { success() } + ?.execute(this@ContainerTransferTask) ?: run { + checkFail() + return@listen + } return@listen } @@ -97,9 +87,10 @@ class ContainerTransferTask( private fun checkFail(): Boolean = - failIfNoMaterial.also { + if (failIfNoMaterial) { failure(NoMaterialAccessException(stackSelection)) - } + true + } else false private class NoMaterialAccessException(stackSelection: StackSelection) : IllegalStateException("Unable to access $stackSelection.") } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/task/tasks/EatTask.kt b/src/main/kotlin/com/lambda/task/tasks/EatTask.kt index 3ab6bf685..d09163dc6 100644 --- a/src/main/kotlin/com/lambda/task/tasks/EatTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/EatTask.kt @@ -25,7 +25,7 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.interaction.inventory.container.containers.InventoryContainer -import com.lambda.interaction.managers.hotbar.HotbarRequest +import com.lambda.interaction.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest import com.lambda.task.Task import com.lambda.threading.runSafeAutomated import net.minecraft.item.ItemStack @@ -57,17 +57,15 @@ class EatTask @Ta5kBuilder constructor( } val foodFinder = reason.selector() - val hotbarSlot = foodFinder.filterSlots(HotbarContainer.slots).firstOrNull() + val hotbarSlot = foodFinder.filter(HotbarContainer.slots).firstOrNull() if (hotbarSlot != null) { - val request = HotbarRequest( - hotbarSlot.index, - this@EatTask, - keepTicks = hotbarConfig.keepTicks.coerceAtLeast(1), - nowOrNothing = false - ).submit() + val request = + hotbarRequest(hotbarSlot.index) { + keepTicks(hotbarConfig.keepTicks.coerceAtLeast(1)) + }.submit() if (!request.done) return@listen } else { - val inventorySlot = foodFinder.filterSlots(InventoryContainer.slots).firstOrNull() + val inventorySlot = foodFinder.filter(InventoryContainer.slots).firstOrNull() if (inventorySlot != null) runSafeAutomated { InventoryContainer.transfer(foodFinder, HotbarContainer) } diff --git a/src/main/kotlin/com/lambda/util/PacketUtils.kt b/src/main/kotlin/com/lambda/util/PacketUtils.kt index 22d95044a..2d8652302 100644 --- a/src/main/kotlin/com/lambda/util/PacketUtils.kt +++ b/src/main/kotlin/com/lambda/util/PacketUtils.kt @@ -27,7 +27,7 @@ object PacketUtils { /** * Sends a packet through the regular packet pipeline */ - fun ClientPlayNetworkHandler.sendPacket(block: () -> Packet<*>) = connection.send(block()) + fun ClientPlayNetworkHandler.sendPacket(packetSupplier: () -> Packet<*>) = connection.send(packetSupplier()) /** * Sends a packet to the server without notifying the client. From a38c0bf547e66b38fa18d16552c0228f272fbaf0 Mon Sep 17 00:00:00 2001 From: beanbag44 <107891830+beanbag44@users.noreply.github.com> Date: Sun, 12 Jul 2026 22:37:23 +0100 Subject: [PATCH 04/43] should be finished container transfer task setup. Need to finish external container stuff. placed shulker boxes, single chests, double chests, maybe bundles, etc --- .../lambda/config/blocks/InventoryConfig.kt | 4 +- .../simulation/checks/BreakSim.kt | 6 +- .../construction/verify/StateMatcher.kt | 2 + .../construction/verify/TargetState.kt | 25 +++++- .../interaction/handlers/ContainerHandler.kt | 10 +-- .../inventory/ContainerSelection.kt | 2 +- .../inventory/container/Container.kt | 66 +++++++++++----- .../inventory/container/ExternalContainer.kt | 8 -- .../container/containers/ArmorContainer.kt | 2 + .../container/containers/CreativeContainer.kt | 6 +- .../{ => external}/ChestContainer.kt | 55 +++++++------ .../{ => external}/EnderChestContainer.kt | 39 +++++----- .../external/PlacedShulkerBoxContainer.kt | 37 +++++++++ .../{ => external}/ShulkerBoxContainer.kt | 29 +++---- .../{ => external}/StashContainer.kt | 6 +- .../module/modules/debug/ContainerTest.kt | 2 +- .../module/modules/render/ContainerPreview.kt | 2 +- .../lambda/module/modules/world/AutoPortal.kt | 6 +- .../lambda/module/modules/world/StashMover.kt | 2 +- src/main/kotlin/com/lambda/task/Task.kt | 28 +++---- .../task/tasks/AcquirePlacedBlockTask.kt | 74 ++++++++++++++++++ ...AcquireMaterialTask.kt => AcquireStack.kt} | 6 +- .../task/tasks/ContainerTransferTask.kt | 77 ++++++++----------- .../kotlin/com/lambda/task/tasks/NoopTask.kt | 34 ++++++++ 24 files changed, 355 insertions(+), 173 deletions(-) rename src/main/kotlin/com/lambda/interaction/inventory/container/containers/{ => external}/ChestContainer.kt (56%) rename src/main/kotlin/com/lambda/interaction/inventory/container/containers/{ => external}/EnderChestContainer.kt (67%) create mode 100644 src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/PlacedShulkerBoxContainer.kt rename src/main/kotlin/com/lambda/interaction/inventory/container/containers/{ => external}/ShulkerBoxContainer.kt (70%) rename src/main/kotlin/com/lambda/interaction/inventory/container/containers/{ => external}/StashContainer.kt (89%) create mode 100644 src/main/kotlin/com/lambda/task/tasks/AcquirePlacedBlockTask.kt rename src/main/kotlin/com/lambda/task/tasks/{AcquireMaterialTask.kt => AcquireStack.kt} (92%) create mode 100644 src/main/kotlin/com/lambda/task/tasks/NoopTask.kt diff --git a/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt b/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt index 18be96b21..a24a0db62 100644 --- a/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt +++ b/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt @@ -62,11 +62,11 @@ interface InventoryConfig { fun materialComparator(selection: StackSelection) = when (this) { WithMaxItems -> compareBy { it.rank } - .thenByDescending { it.materialAvailable(selection) } + .thenByDescending { it.stackCount(selection) } .thenBy { it.name } WithMinItems -> compareBy { it.rank } - .thenBy { it.materialAvailable(selection) } + .thenBy { it.stackCount(selection) } .thenBy { it.name } } diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt index 0fe648ead..2b15101ad 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt @@ -161,13 +161,13 @@ class BreakSim private constructor(simInfo: SimInfo) } } - val silentSwapSelection = selectContainer { + val containerSelection = selectContainer { ofAnyType(Container.Rank.Hotbar) } val hotbarCandidates = stackSelection - .findContainersWithMaterial(silentSwapSelection) - .flatMap { it.matchingStacks(stackSelection) } + .findContainersWithMaterial(containerSelection) + .flatMap { stackSelection.filter(it.stacks) } if (hotbarCandidates.isEmpty()) { result(GenericResult.WrongItemSelection(pos, stackSelection, player.mainHandStack)) return null diff --git a/src/main/kotlin/com/lambda/interaction/construction/verify/StateMatcher.kt b/src/main/kotlin/com/lambda/interaction/construction/verify/StateMatcher.kt index 9f30d19e2..7c5d72c22 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/verify/StateMatcher.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/verify/StateMatcher.kt @@ -34,7 +34,9 @@ interface StateMatcher { context(automatedSafeContext: AutomatedSafeContext) fun getStack(pos: BlockPos): ItemStack + context(automatedSafeContext: AutomatedSafeContext) fun getState(pos: BlockPos): BlockState + fun isEmpty(): Boolean } diff --git a/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt b/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt index 91cb32184..1ac813f5f 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt @@ -176,12 +176,33 @@ sealed class TargetState : StateMatcher { ignoredProperties: Collection> ) = state.block == block - context(automatedSafeContext: AutomatedSafeContext) + context(_: AutomatedSafeContext) override fun getStack(pos: BlockPos): ItemStack = itemStack context(_: AutomatedSafeContext) override fun getState(pos: BlockPos): BlockState = block.defaultState - override fun isEmpty() = false + override fun isEmpty() = block.defaultState.isEmpty + } + + data class SpecificStack(val itemStack: ItemStack) : TargetState() { + override fun toString() = "Specific stack of ${itemStack.item.name.string.capitalize()}" + + private val block = itemStack.item.block + + context(safeContext: SafeContext) + override fun matches( + state: BlockState, + pos: BlockPos, + ignoredProperties: Collection> + ) = state.block == block + + context(_: AutomatedSafeContext) + override fun getStack(pos: BlockPos) = itemStack + + context(_: AutomatedSafeContext) + override fun getState(pos: BlockPos): BlockState = block.defaultState + + override fun isEmpty() = block.defaultState.isEmpty } } diff --git a/src/main/kotlin/com/lambda/interaction/handlers/ContainerHandler.kt b/src/main/kotlin/com/lambda/interaction/handlers/ContainerHandler.kt index b71db2c36..e1db759de 100644 --- a/src/main/kotlin/com/lambda/interaction/handlers/ContainerHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handlers/ContainerHandler.kt @@ -27,8 +27,8 @@ import com.lambda.interaction.inventory.ContainerSelection import com.lambda.interaction.inventory.StackSelection import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.select import com.lambda.interaction.inventory.container.Container -import com.lambda.interaction.inventory.container.containers.ChestContainer -import com.lambda.interaction.inventory.container.containers.EnderChestContainer +import com.lambda.interaction.inventory.container.containers.external.ChestContainer +import com.lambda.interaction.inventory.container.containers.external.EnderChestContainer import com.lambda.util.BlockUtils.blockEntity import com.lambda.util.ReflectionUtils import com.lambda.util.extension.containerStacks @@ -86,7 +86,7 @@ object ContainerHandler : Loadable { } context(_: SafeContext) - fun containers() = containers.flatMap { setOf(it) + it.shulkerContainer }.sorted() + fun containers() = containers.flatMap { setOf(it) + it.shulkerContainers }.sorted() context(automatedSafeContext: AutomatedSafeContext) fun StackSelection.transfer(destination: Container) = @@ -119,7 +119,7 @@ object ContainerHandler : Loadable { ): List = containers() .filter { containerSelection.matches(it) } - .filter { it.materialAvailable(this) >= count } + .filter { it.stackCount(this) >= count } .sortedWith(automatedSafeContext.inventoryConfig.providerPriority.materialComparator(this)) context(automatedSafeContext: AutomatedSafeContext) @@ -151,7 +151,7 @@ object ContainerHandler : Loadable { context(automatedSafeContext: AutomatedSafeContext) fun findDisposable() = containers().find { container -> - automatedSafeContext.inventoryConfig.disposables.any { container.materialAvailable(it.asItem().select()) > 0 } + automatedSafeContext.inventoryConfig.disposables.any { container.stackCount(it.asItem().select()) > 0 } } class NoContainerFound(selection: StackSelection) : Exception("No container found matching $selection") diff --git a/src/main/kotlin/com/lambda/interaction/inventory/ContainerSelection.kt b/src/main/kotlin/com/lambda/interaction/inventory/ContainerSelection.kt index f0e405e92..328255230 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/ContainerSelection.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/ContainerSelection.kt @@ -64,7 +64,7 @@ class ContainerSelectionBuilder private constructor() { } fun matches(stackSelection: StackSelection) { - appendSelector { container -> container.matchingSlots(stackSelection).isNotEmpty() } + appendSelector { container -> stackSelection.filter(container.slots).isNotEmpty() } } fun matches(containerSelection: ContainerSelection) { diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/Container.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/Container.kt index 4375e43ba..666442a33 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/Container.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/Container.kt @@ -23,10 +23,14 @@ import com.lambda.context.SafeContext import com.lambda.event.EventFlow.post import com.lambda.event.events.ContainerEvent import com.lambda.interaction.inventory.StackSelection -import com.lambda.interaction.inventory.container.containers.ShulkerBoxContainer +import com.lambda.interaction.inventory.container.containers.external.ShulkerBoxContainer import com.lambda.interaction.managers.inventory.InvRequestBuilder import com.lambda.interaction.managers.inventory.InvRequestBuilder.Companion.inventoryRequest +import com.lambda.task.Task.Ta5kBuilder +import com.lambda.task.TaskGenerator +import com.lambda.task.TaskOrNullGenerator import com.lambda.task.tasks.ContainerTransferTask +import com.lambda.task.tasks.NoopTask.Companion.noop import com.lambda.util.Nameable import com.lambda.util.item.ItemStackUtils.count import com.lambda.util.item.ItemStackUtils.empty @@ -74,7 +78,7 @@ abstract class Container( fun TextBuilder.stock(selection: StackSelection) { literal("\n") literal("Contains ") - val available = materialAvailable(selection) + val available = stackCount(selection) highlighted(if (available == Int.MAX_VALUE) "∞" else available.toItemCount()) literal(" of ") highlighted("${selection.optimalStack?.name?.string}") @@ -96,23 +100,48 @@ abstract class Container( override val name: String get() = buildText { text(description) }.string - context(_: SafeContext) - val shulkerContainer + //ToDo: Bundles + val shulkerContainers get() = - slots.filter { + slots.takeIf { + it.isEmpty() + }?.filter { it.stack.item in ItemUtils.shulkerBoxes - }.map { slot -> + }?.map { slot -> ShulkerBoxContainer( slot.stack.shulkerBoxStacks, containedIn = this@Container, shulkerSlot = slot ) - }.toSet() + }?.toSet() + ?: stacks.map { stack -> + ShulkerBoxContainer( + stack.shulkerBoxStacks, + containedIn = this@Container, + shulkerSlot = null + ) + }.toSet() + + open val isAccessed get() = true fun update(slots: List) { this.stacks = slots } + @Ta5kBuilder + context(automatedSafeContext: AutomatedSafeContext) + open fun accessThen( + closeAfter: Boolean = true, + afterClose: TaskOrNullGenerator? = null, + afterOpen: TaskGenerator = { noop() } + ) = with(automatedSafeContext) { + afterOpen(Unit).also { + if (closeAfter && afterClose != null) { + it.thenOrNull { afterClose(Unit) } + } + } + } + context(safeContext: SafeContext) open fun InvRequestBuilder.transfer(fromHere: Slot, toSlot: Slot) { if (fromHere.stack.isEmpty) moveSlot(toSlot.id, fromHere.id) @@ -123,10 +152,10 @@ abstract class Container( } context(automatedSafeContext: AutomatedSafeContext) - fun transfer(stackSelection: StackSelection, destination: Container): Boolean = + fun transfer(selection: StackSelection, destination: Container): Boolean = with(automatedSafeContext) { - val fromSlot = getSlot(stackSelection) ?: return false - val toSlot = destination.getReplaceableSlot() ?: return false + val fromSlot = getSlot(selection) ?: return false + val toSlot = destination.getReplaceSlot() ?: return false val transferEvent = ContainerEvent.Transfer(fromSlot, toSlot, this@Container, destination) if (transferEvent.post().isCanceled()) return false return inventoryRequest { @@ -137,22 +166,18 @@ abstract class Container( context(automated: Automated) fun transferByTask(stackSelection: StackSelection, destination: Container, failIfNoMaterial: Boolean = false) = - ContainerTransferTask(this, destination, stackSelection, automated, failIfNoMaterial) - - open fun matchingStacks(selection: StackSelection) = selection.filter(stacks) - open fun matchingSlots(selection: StackSelection) = selection.filter(slots) + ContainerTransferTask(this, destination, stackSelection, failIfNoMaterial, automated) - open fun materialAvailable(selection: StackSelection) = - matchingStacks(selection).count + open fun stackCount(selection: StackSelection) = + selection.filter(stacks).count open fun spaceAvailable(selection: StackSelection) = - matchingStacks(selection).spaceLeft + stacks.empty * selection.count + selection.filter(stacks).spaceLeft + stacks.empty context(_: Automated) - open fun getReplaceableSlot() = slots.sortedWith(replaceSorter).firstOrNull() + open fun getReplaceSlot() = slots.sortedWith(replaceSorter).firstOrNull() - open fun getSlot(stackSelection: StackSelection): Slot? = - stackSelection.filter(slots).firstOrNull() + open fun getSlot(selection: StackSelection) = selection.bestMatch(slots) enum class Rank { MainHand, @@ -165,6 +190,7 @@ abstract class Container( Creative, ShulkerBox, EnderChest, + PlacedShulkerBox, Chest, Stash } diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/ExternalContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/ExternalContainer.kt index da25cd005..0e395e9bc 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/ExternalContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/ExternalContainer.kt @@ -17,13 +17,5 @@ package com.lambda.interaction.inventory.container -import com.lambda.context.AutomatedSafeContext -import com.lambda.task.Task -import com.lambda.task.Task.Ta5kBuilder -import com.lambda.task.TaskGenerator - interface ExternalContainer { - @Ta5kBuilder - context(_: AutomatedSafeContext) - fun accessThen(exitAfter: Boolean = true, taskGenerator: TaskGenerator): Task<*>? } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/ArmorContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/ArmorContainer.kt index 8222ae5d1..b0bafe110 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/ArmorContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/ArmorContainer.kt @@ -34,4 +34,6 @@ object ArmorContainer : Container(Rank.Armor) { set(_) {} override val description = buildText { literal("Armor") } + + override val isAccessed get() = mc.player?.currentScreenHandler?.syncId == 0 } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/CreativeContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/CreativeContainer.kt index eb8f6f0f3..dc115fe6d 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/CreativeContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/CreativeContainer.kt @@ -45,8 +45,8 @@ data object CreativeContainer : Container(Rank.Creative) { safeContext.player.currentScreenHandler.slots.getOrNull(toSlot.id)?.stack = fromHere.stack } - override fun getSlot(stackSelection: StackSelection) = - stackSelection.optimalStack?.let { stack -> + override fun getSlot(selection: StackSelection) = + selection.optimalStack?.let { stack -> Slot( object : SingleStackInventory { override fun getStack() = stack @@ -58,7 +58,7 @@ data object CreativeContainer : Container(Rank.Creative) { ) } - override fun materialAvailable(selection: StackSelection): Int = + override fun stackCount(selection: StackSelection): Int = if (mc.player?.isCreative == true && correctScreenHandler && selection.optimalStack != null) Int.MAX_VALUE else -1 override fun spaceAvailable(selection: StackSelection): Int = diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/ChestContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ChestContainer.kt similarity index 56% rename from src/main/kotlin/com/lambda/interaction/inventory/container/containers/ChestContainer.kt rename to src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ChestContainer.kt index 343b26cda..586b77644 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/ChestContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ChestContainer.kt @@ -15,15 +15,15 @@ * along with this program. If not, see . */ -package com.lambda.interaction.inventory.container.containers +package com.lambda.interaction.inventory.container.containers.external -import com.lambda.Lambda.mc +import com.lambda.Lambda import com.lambda.context.AutomatedSafeContext import com.lambda.interaction.handlers.ContainerHandler import com.lambda.interaction.inventory.container.Container import com.lambda.interaction.inventory.container.ExternalContainer import com.lambda.task.Task -import com.lambda.task.TaskGenerator +import com.lambda.task.TaskOrNullGenerator import com.lambda.task.tasks.OpenContainerTask import com.lambda.util.extension.containerSlots import com.lambda.util.text.buildText @@ -31,36 +31,41 @@ import com.lambda.util.text.highlighted import com.lambda.util.text.literal import net.minecraft.block.entity.ChestBlockEntity import net.minecraft.item.ItemStack +import net.minecraft.screen.ScreenHandler import net.minecraft.screen.slot.Slot import net.minecraft.util.math.BlockPos data class ChestContainer( - override var stacks: List, - val blockPos: BlockPos, - val containedInStash: StashContainer? = null + override var stacks: List, + val blockPos: BlockPos, + val containedInStash: StashContainer? = null ) : Container(Rank.Chest), ExternalContainer { override val slots get(): List = - if (ContainerHandler.lastInteractedBlockEntity is ChestBlockEntity) { - mc.player?.currentScreenHandler?.containerSlots ?: emptyList() - } else emptyList() + ContainerHandler.lastInteractedBlockEntity?.let { blockEntity -> + if (blockEntity is ChestBlockEntity && blockEntity.pos == blockPos) { + Lambda.mc.player?.currentScreenHandler?.containerSlots + } else emptyList() + } ?: emptyList() override val description = - buildText { - literal("Chest at ") - highlighted(blockPos.toShortString()) - containedInStash?.let { stash -> - literal(" (contained in ") - highlighted(stash.name) - literal(")") - } - } + buildText { + literal("Chest at ") + highlighted(blockPos.toShortString()) + containedInStash?.let { stash -> + literal(" (contained in ") + highlighted(stash.name) + literal(")") + } + } context(automatedSafeContext: AutomatedSafeContext) - override fun accessThen(exitAfter: Boolean, taskGenerator: TaskGenerator): Task<*> = - OpenContainerTask(blockPos, automatedSafeContext).then { - taskGenerator.invoke(automatedSafeContext, Unit).finally { - if (exitAfter) automatedSafeContext.player.closeHandledScreen() - } - } -} + override fun accessThen( + exitAfter: Boolean, + afterOpen: TaskOrNullGenerator = { null }, + afterClose: TaskOrNullGenerator = { null } + ): Task<*> = + OpenContainerTask(blockPos, automatedSafeContext) + .thenOrNull(afterOpen) + .then { if (exitAfter) automatedSafeContext.player.closeHandledScreen() } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/EnderChestContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/EnderChestContainer.kt similarity index 67% rename from src/main/kotlin/com/lambda/interaction/inventory/container/containers/EnderChestContainer.kt rename to src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/EnderChestContainer.kt index 627b31f77..f769bd802 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/EnderChestContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/EnderChestContainer.kt @@ -15,25 +15,24 @@ * along with this program. If not, see . */ -package com.lambda.interaction.inventory.container.containers +package com.lambda.interaction.inventory.container.containers.external import com.lambda.Lambda.mc import com.lambda.context.AutomatedSafeContext import com.lambda.interaction.handlers.ContainerHandler -import com.lambda.interaction.handlers.ContainerHandler.findSlotsWithMaterial -import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.select import com.lambda.interaction.inventory.container.Container import com.lambda.interaction.inventory.container.ExternalContainer import com.lambda.task.TaskGenerator +import com.lambda.task.TaskOrNullGenerator +import com.lambda.task.tasks.AcquirePlacedBlockTask import com.lambda.task.tasks.BuildTask.Companion.breakAndCollectBlock import com.lambda.task.tasks.OpenContainerTask -import com.lambda.task.tasks.PlaceContainerTask import com.lambda.util.extension.containerSlots import com.lambda.util.text.buildText import com.lambda.util.text.literal +import net.minecraft.block.Blocks import net.minecraft.block.entity.EnderChestBlockEntity import net.minecraft.item.ItemStack -import net.minecraft.item.Items object EnderChestContainer : Container(Rank.EnderChest), ExternalContainer { override val slots @@ -46,20 +45,22 @@ object EnderChestContainer : Container(Rank.EnderChest), ExternalContainer { override val description = buildText { literal("Ender Chest") } context(automatedSafeContext: AutomatedSafeContext) - override fun accessThen(exitAfter: Boolean, taskGenerator: TaskGenerator) = - Items.ENDER_CHEST - .select(1) - .findSlotsWithMaterial() - .firstOrNull()?.let { slot -> - PlaceContainerTask(slot, automatedSafeContext).then { pos -> - OpenContainerTask(pos, automatedSafeContext).then { - taskGenerator.invoke(automatedSafeContext, Unit).thenOrNull { - if (exitAfter) { - player.closeHandledScreen() - automatedSafeContext.breakAndCollectBlock(pos, lifeMaintenance = false) - } else null - } - } + override fun accessThen( + closeAfter: Boolean, + afterClose: TaskOrNullGenerator?, + afterOpen: TaskGenerator + ) = + AcquirePlacedBlockTask( + Blocks.ENDER_CHEST, + automated = automatedSafeContext + ).then { pos -> + OpenContainerTask(pos, automatedSafeContext).then { + afterOpen.invoke(automatedSafeContext, Unit).thenOrNull { + if (closeAfter) { + player.closeHandledScreen() + automatedSafeContext.breakAndCollectBlock(pos, lifeMaintenance = false) + } else null } } + } } diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/PlacedShulkerBoxContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/PlacedShulkerBoxContainer.kt new file mode 100644 index 000000000..d4f1cca4e --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/PlacedShulkerBoxContainer.kt @@ -0,0 +1,37 @@ +/* + * 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.inventory.container.containers.external + +import com.lambda.interaction.inventory.container.Container +import net.minecraft.item.ItemStack +import net.minecraft.screen.slot.Slot +import net.minecraft.text.Text +import net.minecraft.util.math.BlockPos + +class PlacedShulkerBoxContainer( + val blockPos: BlockPos, + val +) : Container(Rank.PlacedShulkerBox) { + override val slots: List + get() = TODO("Not yet implemented") + override var stacks: List + get() = TODO("Not yet implemented") + set(value) {} + override val description: Text + get() = TODO("Not yet implemented") +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/ShulkerBoxContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ShulkerBoxContainer.kt similarity index 70% rename from src/main/kotlin/com/lambda/interaction/inventory/container/containers/ShulkerBoxContainer.kt rename to src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ShulkerBoxContainer.kt index c3faed9fc..eace9fcb3 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/ShulkerBoxContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ShulkerBoxContainer.kt @@ -15,15 +15,17 @@ * along with this program. If not, see . */ -package com.lambda.interaction.inventory.container.containers +package com.lambda.interaction.inventory.container.containers.external import com.lambda.Lambda.mc import com.lambda.context.AutomatedSafeContext import com.lambda.context.SafeContext -import com.lambda.interaction.handlers.ContainerHandler +import com.lambda.interaction.handlers.ContainerHandler.lastInteractedBlockEntity import com.lambda.interaction.inventory.container.Container import com.lambda.interaction.inventory.container.ExternalContainer +import com.lambda.task.Task import com.lambda.task.TaskGenerator +import com.lambda.task.TaskOrNullGenerator import com.lambda.task.tasks.BuildTask.Companion.breakAndCollectBlock import com.lambda.task.tasks.OpenContainerTask import com.lambda.task.tasks.PlaceContainerTask @@ -35,22 +37,21 @@ import com.lambda.util.text.literal import net.minecraft.block.entity.ShulkerBoxBlockEntity import net.minecraft.item.ItemStack import net.minecraft.screen.slot.Slot -import net.minecraft.util.math.BlockPos data class ShulkerBoxContainer( override var stacks: List, val containedIn: Container, - val shulkerSlot: Slot, + val shulkerSlot: Slot?, ) : Container(Rank.ShulkerBox), ExternalContainer { override val slots get(): List = - if (ContainerHandler.lastInteractedBlockEntity is ShulkerBoxBlockEntity) + if (lastInteractedBlockEntity is ShulkerBoxBlockEntity) mc.player?.currentScreenHandler?.containerSlots ?: emptyList() else emptyList() override val description = buildText { - highlighted(shulkerSlot.stack.name.string) + highlighted(shulkerSlot?.stack?.name?.string ?: "Shulker Box") literal(" in ") highlighted(containedIn.name) literal(" in slot ") @@ -60,17 +61,19 @@ data class ShulkerBoxContainer( context(_: SafeContext) private val slotInContainer: Int get() = containedIn.slots.indexOf(shulkerSlot) - private var placePos = BlockPos.ORIGIN + override val isAccessed get() = lastInteractedBlockEntity is ShulkerBoxBlockEntity context(automatedSafeContext: AutomatedSafeContext) - override fun accessThen(exitAfter: Boolean, taskGenerator: TaskGenerator) = - PlaceContainerTask(shulkerSlot, automatedSafeContext).then { pos -> - placePos = pos + override fun accessThen(closeAfter: Boolean, afterClose: TaskOrNullGenerator?, afterOpen: TaskGenerator): Task<*> { + return PlaceContainerTask(shulkerSlot, automatedSafeContext).then { pos -> OpenContainerTask(pos, automatedSafeContext).then { - taskGenerator.invoke(automatedSafeContext, Unit).thenOrNull { - if (exitAfter) automatedSafeContext.breakAndCollectBlock(placePos) - else null + afterOpen.invoke(automatedSafeContext, Unit).thenOrNull { + if (closeAfter) { + player.closeHandledScreen() + automatedSafeContext.breakAndCollectBlock(pos) + } else null } } } + } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/StashContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/StashContainer.kt similarity index 89% rename from src/main/kotlin/com/lambda/interaction/inventory/container/containers/StashContainer.kt rename to src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/StashContainer.kt index 1dc78c499..b425a7e66 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/StashContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/StashContainer.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.inventory.container.containers +package com.lambda.interaction.inventory.container.containers.external import com.lambda.interaction.inventory.StackSelection import com.lambda.interaction.inventory.container.Container @@ -42,8 +42,8 @@ data class StashContainer( highlighted(pos.center.roundedBlockPos.toShortString()) } - override fun materialAvailable(selection: StackSelection): Int = + override fun stackCount(selection: StackSelection): Int = chests.sumOf { - it.materialAvailable(selection) + it.stackCount(selection) } } diff --git a/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt b/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt index 955694f8c..898e5b046 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt @@ -23,7 +23,7 @@ import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.select import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.task.RootTask.run -import com.lambda.task.tasks.AcquireMaterialTask.Companion.acquire +import com.lambda.task.tasks.AcquireStack.Companion.acquire import net.minecraft.item.Items @Suppress("unused") diff --git a/src/main/kotlin/com/lambda/module/modules/render/ContainerPreview.kt b/src/main/kotlin/com/lambda/module/modules/render/ContainerPreview.kt index 2712076b1..deef6f9ce 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/ContainerPreview.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/ContainerPreview.kt @@ -21,7 +21,7 @@ import com.lambda.Lambda.mc import com.lambda.config.Tab import com.lambda.config.entries.Setting.Companion.onValueChange import com.lambda.config.settings.complex.Bind -import com.lambda.interaction.inventory.container.containers.EnderChestContainer +import com.lambda.interaction.inventory.container.containers.external.EnderChestContainer import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.threading.runSafe diff --git a/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt b/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt index 6e2970b72..d36d5d538 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt @@ -102,11 +102,9 @@ object AutoPortal : Module( .thenOrNull { if (light) LightTask(currAnchorPos.up(), walkIn) else null - } - .finally { + }.finally { buildTask = null - } - .run() + }.run() } private val corners by setting("Corners", false) private val light by setting("Light", true, "Attempts to automatically light the portal after building") 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 814e556bb..92304d9a5 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt @@ -41,7 +41,7 @@ import com.lambda.event.listener.UnsafeListener.Companion.listenUnsafe import com.lambda.graphics.mc.renderer.TickedRenderer.Companion.tickedRenderer import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.handlers.BaritoneHandler -import com.lambda.interaction.inventory.container.containers.EnderChestContainer +import com.lambda.interaction.inventory.container.containers.external.EnderChestContainer import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.interaction.inventory.container.containers.InventoryContainer diff --git a/src/main/kotlin/com/lambda/task/Task.kt b/src/main/kotlin/com/lambda/task/Task.kt index 764e13a91..bd2f0dbaa 100644 --- a/src/main/kotlin/com/lambda/task/Task.kt +++ b/src/main/kotlin/com/lambda/task/Task.kt @@ -34,8 +34,8 @@ import kotlin.time.DurationUnit import kotlin.time.toDuration typealias TaskGenerator = SafeContext.(R) -> Task<*> -typealias TaskGeneratorOrNull = SafeContext.(R) -> Task<*>? -typealias TaskGeneratorUnit = SafeContext.(R) -> Unit +typealias TaskOrNullGenerator = SafeContext.(R) -> Task<*>? +typealias TaskUnitGenerator = SafeContext.(R) -> Unit @Suppress("unused") abstract class Task : Nameable, Muteable { @@ -50,11 +50,11 @@ abstract class Task : Nameable, Muteable { val size: Int get() = subTasks.sumOf { it.size } + 1 private var nextTask: TaskGenerator? = null - private var nextTaskOrNull: TaskGeneratorOrNull? = null - private var onFinish: TaskGeneratorUnit? = null + private var nextTaskOrNull: TaskOrNullGenerator? = null + private var onFinish: TaskUnitGenerator? = null private var onFail: TaskGenerator? = null - private var onFailOrNull: TaskGeneratorOrNull? = null + private var onFailOrNull: TaskOrNullGenerator? = null private var softFail = false enum class State { @@ -249,7 +249,7 @@ abstract class Task : Nameable, Muteable { @Ta5kBuilder infix fun then(task: Task<*>): Task { require(task != this) { "Cannot link a task to itself" } - nextTask = { task } + then { task } return this } @@ -287,7 +287,7 @@ abstract class Task : Nameable, Muteable { */ @Ta5kBuilder fun then(taskGenerator: TaskGenerator): Task { - require(nextTask == null) { "Cannot link multiple tasks to a single task" } + require(nextTask == null && nextTaskOrNull == null) { "Cannot link multiple tasks to a single task" } nextTask = taskGenerator return this } @@ -297,7 +297,7 @@ abstract class Task : Nameable, Muteable { * * This method specifies the next task to be executed after the current task * completes successfully. The next task is generated dynamically using the provided - * [TaskGeneratorOrNull]. This allows chaining tasks together flexibly, + * [TaskOrNullGenerator]. This allows chaining tasks together flexibly, * where the next task is conditionally determined or null. * * @param taskGenerator A function that generates the next task based on the @@ -307,23 +307,23 @@ abstract class Task : Nameable, Muteable { * @return The current task instance (`Task`) to allow method chaining. */ @Ta5kBuilder - fun thenOrNull(taskGenerator: TaskGeneratorOrNull): Task { - require(nextTask == null) { "Cannot link multiple tasks to a single task" } + fun thenOrNull(taskGenerator: TaskOrNullGenerator): Task { + require(nextTask == null && nextTaskOrNull == null) { "Cannot link multiple tasks to a single task" } nextTaskOrNull = taskGenerator return this } @Ta5kBuilder fun onFail(taskGenerator: TaskGenerator): Task { - require(onFail == null) { "Cannot have multiple onFail callbacks on a single task" } + require(onFail == null && onFailOrNull == null) { "Cannot have multiple onFail callbacks on a single task" } onFail = taskGenerator softFail() return this } @Ta5kBuilder - fun onFailOrNull(taskGenerator: TaskGeneratorOrNull): Task { - require(onFailOrNull == null) { "Cannot have multiple onFailOrNull callbacks on a single task" } + fun onFailOrNull(taskGenerator: TaskOrNullGenerator): Task { + require(onFail == null && onFailOrNull == null) { "Cannot have multiple onFail callbacks on a single task" } onFailOrNull = taskGenerator softFail() return this @@ -347,7 +347,7 @@ abstract class Task : Nameable, Muteable { * @return The current task instance (`Task`) to allow method chaining. */ @Ta5kBuilder - fun finally(onFinish: TaskGeneratorUnit): Task { + fun finally(onFinish: TaskUnitGenerator): Task { require(this.onFinish == null) { "Cannot link multiple finally blocks to a single task" } this.onFinish = onFinish return this diff --git a/src/main/kotlin/com/lambda/task/tasks/AcquirePlacedBlockTask.kt b/src/main/kotlin/com/lambda/task/tasks/AcquirePlacedBlockTask.kt new file mode 100644 index 000000000..086700731 --- /dev/null +++ b/src/main/kotlin/com/lambda/task/tasks/AcquirePlacedBlockTask.kt @@ -0,0 +1,74 @@ +/* + * 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.task.tasks + +import com.lambda.context.Automated +import com.lambda.context.SafeContext +import com.lambda.event.events.TickEvent +import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.handlers.ContainerHandler.findSlotsWithMaterial +import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.select +import com.lambda.task.Task +import com.lambda.threading.runSafeAutomated +import com.lambda.util.BlockUtils.blockPos +import com.lambda.util.BlockUtils.blockState +import net.minecraft.block.Block +import net.minecraft.item.Items +import net.minecraft.util.math.BlockPos + +class AcquirePlacedBlockTask @Ta5kBuilder constructor( + val block: Block, + val maxSearchRadius: Int = 10, + automated: Automated +) : Task(), Automated by automated { + override val name get() = "Acquiring placed ${block.name.string}" + + init { + listen { + runSafeAutomated { + Items.ENDER_CHEST + .select(1) + .findSlotsWithMaterial() + .firstOrNull()?.let { slot -> + PlaceContainerTask(slot, this).finally { + success(it) + }.execute(this@AcquirePlacedBlockTask) + return@listen + } + } + + failure("No ender chest found in range or hotbar/inventory!") + return@listen + } + } + + override fun SafeContext.onStart() { + checkInRange()?.let { success(it) } + } + + private fun SafeContext.checkInRange() = + BlockPos.iterateOutwards( + player.blockPos, + maxSearchRadius, + maxSearchRadius, + maxSearchRadius + ).map { it.blockPos } + .firstOrNull { blockPos -> + blockState(blockPos).block == block + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/task/tasks/AcquireMaterialTask.kt b/src/main/kotlin/com/lambda/task/tasks/AcquireStack.kt similarity index 92% rename from src/main/kotlin/com/lambda/task/tasks/AcquireMaterialTask.kt rename to src/main/kotlin/com/lambda/task/tasks/AcquireStack.kt index de9264b21..a28f1c336 100644 --- a/src/main/kotlin/com/lambda/task/tasks/AcquireMaterialTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/AcquireStack.kt @@ -26,7 +26,7 @@ import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.task.Task import com.lambda.threading.runSafeAutomated -class AcquireMaterialTask @Ta5kBuilder constructor( +class AcquireStack @Ta5kBuilder constructor( val selection: StackSelection, automated: Automated ) : Task(), Automated by automated { @@ -39,7 +39,7 @@ class AcquireMaterialTask @Ta5kBuilder constructor( ?.transferByTask(selection, HotbarContainer) ?.finally { success(selection) - }?.execute(this@AcquireMaterialTask) + }?.execute(this@AcquireStack) ?: failure(ContainerHandler.NoContainerFound(selection)) // ToDo: Create crafting path } } @@ -47,6 +47,6 @@ class AcquireMaterialTask @Ta5kBuilder constructor( companion object { @Ta5kBuilder fun Automated.acquire(selection: () -> StackSelection) = - AcquireMaterialTask(selection(), this) + AcquireStack(selection(), this) } } diff --git a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt index c06883dee..03ce1c3ec 100644 --- a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt @@ -18,15 +18,12 @@ package com.lambda.task.tasks import com.lambda.context.Automated -import com.lambda.event.EventFlow.post -import com.lambda.event.events.ContainerEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.inventory.StackSelection import com.lambda.interaction.inventory.container.Container import com.lambda.interaction.inventory.container.ExternalContainer import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer -import com.lambda.interaction.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.task.Task import com.lambda.threading.runSafeAutomated @@ -34,63 +31,53 @@ class ContainerTransferTask( private var fromContainer: Container, private val toContainer: Container, private val stackSelection: StackSelection, - automated: Automated, - private val failIfNoMaterial: Boolean = false + private val failIfNoStack: Boolean = false, + automated: Automated ) : Task(), Automated by automated { override val name = "Transferring $stackSelection from $fromContainer to $toContainer" init { listen { runSafeAutomated { - val slots = fromContainer.slots - val toSlots = toContainer.slots - if (fromContainer is ExternalContainer && slots.isEmpty()) { - if (toContainer is ExternalContainer && toSlots.isEmpty()) { - fromContainer - .transferByTask(stackSelection, HotbarAndInventoryContainer) - .finally { fromContainer = HotbarAndInventoryContainer } - .execute(this@ContainerTransferTask) - return@listen - } - return@listen - } - if (toContainer is ExternalContainer && toSlots.isEmpty()) { - toContainer - .accessThen { - fromContainer.transferByTask(stackSelection, toContainer) - }?.finally { success() } - ?.execute(this@ContainerTransferTask) ?: run { - checkFail() - return@listen + if (fromContainer is ExternalContainer && toContainer is ExternalContainer) { + fromContainer.accessThen( + true, + { + HotbarAndInventoryContainer.transferByTask(stackSelection, toContainer) + .finally { success() } } + ) { + fromContainer.transferByTask(stackSelection, HotbarAndInventoryContainer) + }.execute(this@ContainerTransferTask) return@listen } - fromContainer.getSlot(stackSelection)?.let { fromSlot -> - toContainer.getReplaceableSlot()?.let { toSlot -> - val transferEvent = ContainerEvent.Transfer(fromSlot, toSlot, fromContainer, toContainer) - if (transferEvent.post().isCanceled()) failure("Transfer prevented by an internal interruption") - inventoryRequest { - if (fromContainer.swapMethodPriority > toContainer.swapMethodPriority) - with(fromContainer) { transfer(fromSlot, toSlot) } - else with(toContainer) { transfer(toSlot, fromSlot) } - onComplete { success() } - }.submit() - return@listen + fromContainer.accessThen { + toContainer.accessThen { + TransferTask() } - } - - checkFail() + }.execute(this@ContainerTransferTask) } } } + private inner class TransferTask : Task() { + override val name = "Transferring" - private fun checkFail(): Boolean = - if (failIfNoMaterial) { - failure(NoMaterialAccessException(stackSelection)) - true - } else false + init { + listen { + runSafeAutomated { + val transferSuccessful = fromContainer.transfer(stackSelection, toContainer) + if (transferSuccessful) success() + else checkFail() + } + } + } - private class NoMaterialAccessException(stackSelection: StackSelection) : IllegalStateException("Unable to access $stackSelection.") + private fun checkFail() { + if (failIfNoStack) failure(NoMaterialAccessException(stackSelection)) + } + + private inner class NoMaterialAccessException(stackSelection: StackSelection) : IllegalStateException("Unable to access $stackSelection.") + } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/task/tasks/NoopTask.kt b/src/main/kotlin/com/lambda/task/tasks/NoopTask.kt new file mode 100644 index 000000000..bc7b870c5 --- /dev/null +++ b/src/main/kotlin/com/lambda/task/tasks/NoopTask.kt @@ -0,0 +1,34 @@ +/* + * 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.task.tasks + +import com.lambda.context.SafeContext +import com.lambda.task.Task + +class NoopTask : Task() { + override val name = "No-operation task" + + override fun SafeContext.onStart() { + success() + } + + companion object { + @Ta5kBuilder + fun noop() = NoopTask() + } +} \ No newline at end of file From 401722735c1fcfc25b7e4efae3f3570b25c39bc3 Mon Sep 17 00:00:00 2001 From: beanbag44 <107891830+beanbag44@users.noreply.github.com> Date: Wed, 15 Jul 2026 11:06:25 +0100 Subject: [PATCH 05/43] lotta changes, still a lotta broken stuff --- .../lambda/mixin/MinecraftClientMixin.java | 2 +- .../ClientPlayInteractionManagerMixin.java | 6 +- .../mixin/entity/ClientPlayerEntityMixin.java | 4 +- .../com/lambda/mixin/entity/EntityMixin.java | 4 +- .../mixin/entity/HandledScreensMixin.java | 2 +- .../mixin/entity/LivingEntityMixin.java | 6 +- .../mixin/entity/PlayerEntityMixin.java | 6 +- .../mixin/entity/PlayerInventoryMixin.java | 2 +- .../ClientPlayNetworkHandlerMixin.java | 2 +- .../com/lambda/mixin/render/CameraMixin.java | 2 +- .../render/LivingEntityRendererMixin.java | 4 +- .../mixin/render/PlayerListHudMixin.java | 2 +- .../lambda/mixin/world/DirectionMixin.java | 4 +- .../lambda/command/commands/FriendCommand.kt | 2 +- .../command/commands/TransferCommand.kt | 12 +- .../com/lambda/config/ConfigCategory.kt | 2 +- .../com/lambda/config/blocks/BuildConfig.kt | 4 +- .../config/blocks/EntityColorSettings.kt | 2 +- .../lambda/config/blocks/InventoryConfig.kt | 2 - .../lambda/config/blocks/RotationConfig.kt | 4 +- .../lambda/config/blocks/RotationSettings.kt | 2 +- .../lambda/config/blocks/TargetingSettings.kt | 8 +- .../com/lambda/event/events/RotationEvent.kt | 2 +- src/main/kotlin/com/lambda/gui/MenuBar.kt | 2 +- .../construction/simulation/Sim.kt | 2 +- .../simulation/checks/BreakSim.kt | 10 +- .../simulation/checks/InteractSim.kt | 12 +- .../simulation/context/BreakContext.kt | 2 +- .../simulation/context/BuildContext.kt | 2 +- .../simulation/context/InteractContext.kt | 4 +- .../simulation/result/Contextual.kt | 4 +- .../simulation/result/results/BreakResult.kt | 11 +- .../result/results/GenericResult.kt | 9 +- .../result/results/InteractResult.kt | 2 +- .../simulation/result/results/PreSimResult.kt | 2 +- .../construction/verify/TargetState.kt | 2 +- .../PostActionHandler.kt | 5 +- .../{ => handler}/handlers/BaritoneHandler.kt | 2 +- .../handlers/ContainerHandler.kt | 130 ++++++++++-------- .../{ => handler}/handlers/FriendHandler.kt | 2 +- .../{ => handler}/handlers/GlideHandler.kt | 2 +- .../{ => handler}/handlers/PingHandler.kt | 2 +- .../{ => handler}/handlers/TimerHandler.kt | 2 +- .../handlers/breaking/BrokenBlockHandler.kt | 12 +- .../handlers/breaking/RebreakHandler.kt | 22 +-- .../handlers/breaking/RebreakResult.kt | 4 +- .../interacting/InteractedBlockHandler.kt | 8 +- .../handlers/packet/PacketLimitHandler.kt | 2 +- .../interaction/inventory/ContainerMarker.kt | 21 +++ .../inventory/ContainerSelection.kt | 11 +- .../interaction/inventory/StackSelection.kt | 21 ++- .../inventory/container/Container.kt | 48 ++++--- .../inventory/container/NestedContainer.kt | 24 ++++ .../inventory/container/PlacedContainer.kt | 24 ++++ .../container/containers/CreativeContainer.kt | 2 +- .../container/containers/HotbarContainer.kt | 2 +- .../container/containers/MainHandContainer.kt | 2 +- .../container/containers/OffHandContainer.kt | 2 +- .../containers/external/ChestContainer.kt | 53 ++++--- .../external/EnderChestContainer.kt | 60 +++++--- .../external/PlacedShulkerBoxContainer.kt | 66 +++++++-- .../external/ShulkerBoxContainer.kt | 63 +++++---- .../{managers => manager}/ActionInfo.kt | 2 +- .../{managers => manager}/Manager.kt | 4 +- .../{managers => manager}/ManagerUtils.kt | 2 +- .../{managers => manager}/PositionBlocking.kt | 2 +- .../{managers => manager}/Request.kt | 2 +- .../managers/breaking/BreakInfo.kt | 16 +-- .../managers/breaking/BreakManager.kt | 82 +++++------ .../managers/breaking/BreakRequest.kt | 4 +- .../managers/breaking/SwapInfo.kt | 10 +- .../managers/hotbar/HotbarManager.kt | 14 +- .../managers/hotbar/HotbarRequest.kt | 5 +- .../managers/interacting/InteractInfo.kt | 4 +- .../managers/interacting/InteractManager.kt | 34 ++--- .../managers/interacting/InteractRequest.kt | 4 +- .../managers/inventory/InventoryAction.kt | 2 +- .../managers/inventory/InventoryManager.kt | 18 +-- .../managers/inventory/InventoryRequest.kt | 5 +- .../managers/rotating/Rotation.kt | 2 +- .../managers/rotating/RotationManager.kt | 14 +- .../managers/rotating/RotationMode.kt | 2 +- .../managers/rotating/RotationRequest.kt | 18 +-- .../kotlin/com/lambda/module/hud/Baritone.kt | 2 +- .../lambda/module/modules/chat/AntiSpam.kt | 2 +- .../lambda/module/modules/chat/CustomChat.kt | 2 +- .../lambda/module/modules/chat/FancyChat.kt | 2 +- .../module/modules/chat/FriendHighlight.kt | 2 +- .../lambda/module/modules/combat/AutoArmor.kt | 2 +- .../lambda/module/modules/combat/AutoTotem.kt | 4 +- .../lambda/module/modules/combat/Criticals.kt | 4 +- .../module/modules/combat/CrystalAura.kt | 10 +- .../lambda/module/modules/combat/KillAura.kt | 4 +- .../module/modules/combat/PlayerTrap.kt | 2 +- .../combat/autodisconnect/AutoDisconnect.kt | 10 +- .../module/modules/debug/BaritoneTest.kt | 2 +- .../module/modules/debug/ContainerTest.kt | 4 +- .../module/modules/debug/RotationTest.kt | 2 +- .../lambda/module/modules/debug/SilentSwap.kt | 2 +- .../module/modules/movement/AutoSpiral.kt | 4 +- .../module/modules/movement/BetterFirework.kt | 6 +- .../modules/movement/ElytraAltitudeControl.kt | 2 +- .../lambda/module/modules/movement/Speed.kt | 2 +- .../module/modules/movement/TargetStrafe.kt | 2 +- .../movement/elytrafly/ElytraFlyMode.kt | 10 +- .../movement/elytrafly/ObstaclePassingMode.kt | 2 +- .../elytrafly/modes/BounceElytraFly.kt | 8 +- .../elytrafly/modes/GrimControlElytraFly.kt | 4 +- .../lambda/module/modules/player/AntiAim.kt | 4 +- .../module/modules/player/AutoElytraSwap.kt | 10 +- .../module/modules/player/ClickFriend.kt | 8 +- .../lambda/module/modules/player/FastBreak.kt | 2 +- .../module/modules/player/InventoryMove.kt | 4 +- .../module/modules/player/InventoryResync.kt | 2 +- .../module/modules/player/InventoryTweaks.kt | 4 +- .../module/modules/player/PacketMine.kt | 2 +- .../lambda/module/modules/player/Replay.kt | 8 +- .../module/modules/player/RotationLock.kt | 4 +- .../module/modules/player/StackReplenish.kt | 2 +- .../lambda/module/modules/player/ToolSaver.kt | 2 +- .../lambda/module/modules/render/FreeLook.kt | 4 +- .../lambda/module/modules/render/Freecam.kt | 6 +- .../lambda/module/modules/render/Nametags.kt | 2 +- .../lambda/module/modules/render/Tracers.kt | 2 +- .../lambda/module/modules/world/AirPlace.kt | 6 +- .../lambda/module/modules/world/AutoPortal.kt | 6 +- .../module/modules/world/AutoVillagerCycle.kt | 2 +- .../module/modules/world/HighwayTools.kt | 2 +- .../lambda/module/modules/world/Scaffold.kt | 2 +- .../lambda/module/modules/world/StashMover.kt | 14 +- .../task/tasks/AcquirePlacedBlockTask.kt | 13 +- .../{AcquireStack.kt => AcquireStackTask.kt} | 26 ++-- .../kotlin/com/lambda/task/tasks/BuildTask.kt | 15 +- .../task/tasks/ContainerTransferTask.kt | 57 +++++++- .../kotlin/com/lambda/task/tasks/EatTask.kt | 3 +- .../kotlin/com/lambda/task/tasks/NoopTask.kt | 2 +- .../lambda/task/tasks/OpenContainerTask.kt | 18 ++- .../lambda/task/tasks/PlaceContainerTask.kt | 12 +- .../com/lambda/task/tasks/SimpleActionTask.kt | 36 +++++ .../com/lambda/task/tasks/SlotByCacheTask.kt | 36 +++++ .../kotlin/com/lambda/util/PlaceDirection.kt | 2 +- .../com/lambda/util/PlayerBuildLayerUtils.kt | 2 +- .../com/lambda/util/extension/Entity.kt | 2 +- .../kotlin/com/lambda/util/math/Linear.kt | 2 +- .../com/lambda/util/player/MovementUtils.kt | 2 +- .../com/lambda/util/player/PlayerUtils.kt | 2 +- .../com/lambda/util/player/RotationUtils.kt | 6 +- .../com/lambda/util/player/SlotUtils.kt | 7 + .../player/prediction/PredictionEntity.kt | 2 +- .../util/player/prediction/PredictionTick.kt | 2 +- .../lambda/util/world/raycast/RayCastUtils.kt | 2 +- src/test/kotlin/PlaceDirectionTest.kt | 2 +- src/test/kotlin/RotationTest.kt | 12 +- 153 files changed, 899 insertions(+), 546 deletions(-) rename src/main/kotlin/com/lambda/interaction/{managers => handler}/PostActionHandler.kt (91%) rename src/main/kotlin/com/lambda/interaction/{ => handler}/handlers/BaritoneHandler.kt (99%) rename src/main/kotlin/com/lambda/interaction/{ => handler}/handlers/ContainerHandler.kt (57%) rename src/main/kotlin/com/lambda/interaction/{ => handler}/handlers/FriendHandler.kt (99%) rename src/main/kotlin/com/lambda/interaction/{ => handler}/handlers/GlideHandler.kt (99%) rename src/main/kotlin/com/lambda/interaction/{ => handler}/handlers/PingHandler.kt (97%) rename src/main/kotlin/com/lambda/interaction/{ => handler}/handlers/TimerHandler.kt (96%) rename src/main/kotlin/com/lambda/interaction/{ => handler}/handlers/breaking/BrokenBlockHandler.kt (93%) rename src/main/kotlin/com/lambda/interaction/{ => handler}/handlers/breaking/RebreakHandler.kt (83%) rename src/main/kotlin/com/lambda/interaction/{ => handler}/handlers/breaking/RebreakResult.kt (87%) rename src/main/kotlin/com/lambda/interaction/{ => handler}/handlers/interacting/InteractedBlockHandler.kt (91%) rename src/main/kotlin/com/lambda/interaction/{ => handler}/handlers/packet/PacketLimitHandler.kt (97%) create mode 100644 src/main/kotlin/com/lambda/interaction/inventory/ContainerMarker.kt create mode 100644 src/main/kotlin/com/lambda/interaction/inventory/container/NestedContainer.kt create mode 100644 src/main/kotlin/com/lambda/interaction/inventory/container/PlacedContainer.kt rename src/main/kotlin/com/lambda/interaction/{managers => manager}/ActionInfo.kt (96%) rename src/main/kotlin/com/lambda/interaction/{managers => manager}/Manager.kt (97%) rename src/main/kotlin/com/lambda/interaction/{managers => manager}/ManagerUtils.kt (96%) rename src/main/kotlin/com/lambda/interaction/{managers => manager}/PositionBlocking.kt (96%) rename src/main/kotlin/com/lambda/interaction/{managers => manager}/Request.kt (97%) rename src/main/kotlin/com/lambda/interaction/{ => manager}/managers/breaking/BreakInfo.kt (89%) rename src/main/kotlin/com/lambda/interaction/{ => manager}/managers/breaking/BreakManager.kt (89%) rename src/main/kotlin/com/lambda/interaction/{ => manager}/managers/breaking/BreakRequest.kt (98%) rename src/main/kotlin/com/lambda/interaction/{ => manager}/managers/breaking/SwapInfo.kt (87%) rename src/main/kotlin/com/lambda/interaction/{ => manager}/managers/hotbar/HotbarManager.kt (91%) rename src/main/kotlin/com/lambda/interaction/{ => manager}/managers/hotbar/HotbarRequest.kt (93%) rename src/main/kotlin/com/lambda/interaction/{ => manager}/managers/interacting/InteractInfo.kt (91%) rename src/main/kotlin/com/lambda/interaction/{ => manager}/managers/interacting/InteractManager.kt (91%) rename src/main/kotlin/com/lambda/interaction/{ => manager}/managers/interacting/InteractRequest.kt (97%) rename src/main/kotlin/com/lambda/interaction/{ => manager}/managers/inventory/InventoryAction.kt (95%) rename src/main/kotlin/com/lambda/interaction/{ => manager}/managers/inventory/InventoryManager.kt (93%) rename src/main/kotlin/com/lambda/interaction/{ => manager}/managers/inventory/InventoryRequest.kt (97%) rename src/main/kotlin/com/lambda/interaction/{ => manager}/managers/rotating/Rotation.kt (99%) rename src/main/kotlin/com/lambda/interaction/{ => manager}/managers/rotating/RotationManager.kt (95%) rename src/main/kotlin/com/lambda/interaction/{ => manager}/managers/rotating/RotationMode.kt (96%) rename src/main/kotlin/com/lambda/interaction/{ => manager}/managers/rotating/RotationRequest.kt (90%) rename src/main/kotlin/com/lambda/task/tasks/{AcquireStack.kt => AcquireStackTask.kt} (66%) create mode 100644 src/main/kotlin/com/lambda/task/tasks/SimpleActionTask.kt create mode 100644 src/main/kotlin/com/lambda/task/tasks/SlotByCacheTask.kt diff --git a/src/main/java/com/lambda/mixin/MinecraftClientMixin.java b/src/main/java/com/lambda/mixin/MinecraftClientMixin.java index 30a4dd4e5..a503297cf 100644 --- a/src/main/java/com/lambda/mixin/MinecraftClientMixin.java +++ b/src/main/java/com/lambda/mixin/MinecraftClientMixin.java @@ -17,7 +17,7 @@ package com.lambda.mixin; -import com.lambda.interaction.handlers.TimerHandler; +import com.lambda.interaction.handler.handlers.TimerHandler; import com.lambda.event.EventFlow; import com.lambda.event.events.*; import com.lambda.gui.DearImGui; diff --git a/src/main/java/com/lambda/mixin/entity/ClientPlayInteractionManagerMixin.java b/src/main/java/com/lambda/mixin/entity/ClientPlayInteractionManagerMixin.java index dbdb9a9d2..e13b3f459 100644 --- a/src/main/java/com/lambda/mixin/entity/ClientPlayInteractionManagerMixin.java +++ b/src/main/java/com/lambda/mixin/entity/ClientPlayInteractionManagerMixin.java @@ -20,9 +20,9 @@ import com.lambda.event.EventFlow; import com.lambda.event.events.InventoryEvent; import com.lambda.event.events.PlayerEvent; -import com.lambda.interaction.handlers.BaritoneHandler; -import com.lambda.interaction.managers.inventory.InventoryManager; -import com.lambda.interaction.managers.rotating.RotationManager; +import com.lambda.interaction.handler.handlers.BaritoneHandler; +import com.lambda.interaction.manager.managers.inventory.InventoryManager; +import com.lambda.interaction.manager.managers.rotating.RotationManager; import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; diff --git a/src/main/java/com/lambda/mixin/entity/ClientPlayerEntityMixin.java b/src/main/java/com/lambda/mixin/entity/ClientPlayerEntityMixin.java index c6e112c36..1688ed7e8 100644 --- a/src/main/java/com/lambda/mixin/entity/ClientPlayerEntityMixin.java +++ b/src/main/java/com/lambda/mixin/entity/ClientPlayerEntityMixin.java @@ -19,8 +19,8 @@ import com.lambda.event.EventFlow; import com.lambda.event.events.*; -import com.lambda.interaction.handlers.BaritoneHandler; -import com.lambda.interaction.managers.rotating.RotationManager; +import com.lambda.interaction.handler.handlers.BaritoneHandler; +import com.lambda.interaction.manager.managers.rotating.RotationManager; import com.lambda.module.modules.movement.NoJumpCooldown; import com.lambda.module.modules.movement.elytrafly.ElytraFly; import com.lambda.module.modules.player.PortalGui; diff --git a/src/main/java/com/lambda/mixin/entity/EntityMixin.java b/src/main/java/com/lambda/mixin/entity/EntityMixin.java index 2c31d2ff8..99fb142f6 100644 --- a/src/main/java/com/lambda/mixin/entity/EntityMixin.java +++ b/src/main/java/com/lambda/mixin/entity/EntityMixin.java @@ -20,8 +20,8 @@ import com.lambda.event.EventFlow; import com.lambda.event.events.EntityEvent; import com.lambda.event.events.PlayerEvent; -import com.lambda.interaction.handlers.BaritoneHandler; -import com.lambda.interaction.managers.rotating.RotationManager; +import com.lambda.interaction.handler.handlers.BaritoneHandler; +import com.lambda.interaction.manager.managers.rotating.RotationManager; import com.lambda.module.modules.movement.elytrafly.ElytraFly; import com.lambda.module.modules.render.NoRender; import com.lambda.util.math.Vec2d; diff --git a/src/main/java/com/lambda/mixin/entity/HandledScreensMixin.java b/src/main/java/com/lambda/mixin/entity/HandledScreensMixin.java index 079ea9411..106e0bbb0 100644 --- a/src/main/java/com/lambda/mixin/entity/HandledScreensMixin.java +++ b/src/main/java/com/lambda/mixin/entity/HandledScreensMixin.java @@ -17,7 +17,7 @@ package com.lambda.mixin.entity; -import com.lambda.interaction.managers.inventory.InventoryManager; +import com.lambda.interaction.manager.managers.inventory.InventoryManager; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.screen.ingame.HandledScreens; import net.minecraft.screen.ScreenHandler; diff --git a/src/main/java/com/lambda/mixin/entity/LivingEntityMixin.java b/src/main/java/com/lambda/mixin/entity/LivingEntityMixin.java index 64d1c93ff..10ab80259 100644 --- a/src/main/java/com/lambda/mixin/entity/LivingEntityMixin.java +++ b/src/main/java/com/lambda/mixin/entity/LivingEntityMixin.java @@ -20,9 +20,9 @@ import com.lambda.Lambda; import com.lambda.event.EventFlow; import com.lambda.event.events.MovementEvent; -import com.lambda.interaction.handlers.BaritoneHandler; -import com.lambda.interaction.handlers.GlideHandler; -import com.lambda.interaction.managers.rotating.RotationManager; +import com.lambda.interaction.handler.handlers.BaritoneHandler; +import com.lambda.interaction.handler.handlers.GlideHandler; +import com.lambda.interaction.manager.managers.rotating.RotationManager; import com.lambda.module.modules.movement.Velocity; import com.lambda.module.modules.movement.elytrafly.ElytraFly; import com.lambda.module.modules.render.ViewModel; diff --git a/src/main/java/com/lambda/mixin/entity/PlayerEntityMixin.java b/src/main/java/com/lambda/mixin/entity/PlayerEntityMixin.java index 8a0fc6d0d..c7722b2bc 100644 --- a/src/main/java/com/lambda/mixin/entity/PlayerEntityMixin.java +++ b/src/main/java/com/lambda/mixin/entity/PlayerEntityMixin.java @@ -20,9 +20,9 @@ import com.lambda.Lambda; import com.lambda.event.EventFlow; import com.lambda.event.events.MovementEvent; -import com.lambda.interaction.handlers.BaritoneHandler; -import com.lambda.interaction.handlers.GlideHandler; -import com.lambda.interaction.managers.rotating.RotationManager; +import com.lambda.interaction.handler.handlers.BaritoneHandler; +import com.lambda.interaction.handler.handlers.GlideHandler; +import com.lambda.interaction.manager.managers.rotating.RotationManager; import com.lambda.module.modules.player.Reach; import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; diff --git a/src/main/java/com/lambda/mixin/entity/PlayerInventoryMixin.java b/src/main/java/com/lambda/mixin/entity/PlayerInventoryMixin.java index 26c4c0a91..e538912bd 100644 --- a/src/main/java/com/lambda/mixin/entity/PlayerInventoryMixin.java +++ b/src/main/java/com/lambda/mixin/entity/PlayerInventoryMixin.java @@ -18,7 +18,7 @@ package com.lambda.mixin.entity; import com.lambda.Lambda; -import com.lambda.interaction.managers.hotbar.HotbarManager; +import com.lambda.interaction.manager.managers.hotbar.HotbarManager; import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import net.minecraft.entity.player.PlayerInventory; import org.objectweb.asm.Opcodes; diff --git a/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java b/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java index 8107b5e38..9a295f9eb 100644 --- a/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java +++ b/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java @@ -22,7 +22,7 @@ import com.lambda.event.events.ChatEvent; import com.lambda.event.events.InventoryEvent; import com.lambda.event.events.WorldEvent; -import com.lambda.interaction.managers.inventory.InventoryManager; +import com.lambda.interaction.manager.managers.inventory.InventoryManager; import com.lambda.module.modules.movement.Velocity; import com.lambda.module.modules.player.NoForceRotate; import com.lambda.module.modules.render.NoRender; diff --git a/src/main/java/com/lambda/mixin/render/CameraMixin.java b/src/main/java/com/lambda/mixin/render/CameraMixin.java index 38f629489..e6ca7231f 100644 --- a/src/main/java/com/lambda/mixin/render/CameraMixin.java +++ b/src/main/java/com/lambda/mixin/render/CameraMixin.java @@ -17,7 +17,7 @@ package com.lambda.mixin.render; -import com.lambda.interaction.managers.rotating.RotationManager; +import com.lambda.interaction.manager.managers.rotating.RotationManager; import com.lambda.module.modules.render.CameraTweaks; import com.lambda.module.modules.render.FreeLook; import com.lambda.module.modules.render.Freecam; diff --git a/src/main/java/com/lambda/mixin/render/LivingEntityRendererMixin.java b/src/main/java/com/lambda/mixin/render/LivingEntityRendererMixin.java index dc18dc35a..86ffe2d5e 100644 --- a/src/main/java/com/lambda/mixin/render/LivingEntityRendererMixin.java +++ b/src/main/java/com/lambda/mixin/render/LivingEntityRendererMixin.java @@ -18,8 +18,8 @@ package com.lambda.mixin.render; import com.lambda.Lambda; -import com.lambda.interaction.handlers.BaritoneHandler; -import com.lambda.interaction.managers.rotating.RotationManager; +import com.lambda.interaction.handler.handlers.BaritoneHandler; +import com.lambda.interaction.manager.managers.rotating.RotationManager; import com.lambda.module.modules.render.Nametags; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; diff --git a/src/main/java/com/lambda/mixin/render/PlayerListHudMixin.java b/src/main/java/com/lambda/mixin/render/PlayerListHudMixin.java index eefaba6f1..f6e090956 100644 --- a/src/main/java/com/lambda/mixin/render/PlayerListHudMixin.java +++ b/src/main/java/com/lambda/mixin/render/PlayerListHudMixin.java @@ -17,7 +17,7 @@ package com.lambda.mixin.render; -import com.lambda.interaction.handlers.FriendHandler; +import com.lambda.interaction.handler.handlers.FriendHandler; import com.lambda.module.modules.render.ExtraTab; import com.lambda.util.text.TextBuilder; import com.lambda.util.text.TextDslKt; diff --git a/src/main/java/com/lambda/mixin/world/DirectionMixin.java b/src/main/java/com/lambda/mixin/world/DirectionMixin.java index 765248bb3..da8698f2e 100644 --- a/src/main/java/com/lambda/mixin/world/DirectionMixin.java +++ b/src/main/java/com/lambda/mixin/world/DirectionMixin.java @@ -17,8 +17,8 @@ package com.lambda.mixin.world; -import com.lambda.interaction.handlers.BaritoneHandler; -import com.lambda.interaction.managers.rotating.RotationManager; +import com.lambda.interaction.handler.handlers.BaritoneHandler; +import com.lambda.interaction.manager.managers.rotating.RotationManager; import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import net.minecraft.entity.Entity; import net.minecraft.util.math.Direction; diff --git a/src/main/kotlin/com/lambda/command/commands/FriendCommand.kt b/src/main/kotlin/com/lambda/command/commands/FriendCommand.kt index 8d3a6e6af..3de171a5f 100644 --- a/src/main/kotlin/com/lambda/command/commands/FriendCommand.kt +++ b/src/main/kotlin/com/lambda/command/commands/FriendCommand.kt @@ -29,7 +29,7 @@ import com.lambda.brigadier.executeWithResult import com.lambda.brigadier.required import com.lambda.command.LambdaCommand import com.lambda.config.categories.FriendCategory -import com.lambda.interaction.handlers.FriendHandler +import com.lambda.interaction.handler.handlers.FriendHandler import com.lambda.network.mojang.getProfile import com.lambda.threading.runIO import com.lambda.util.CommunicationUtils.info diff --git a/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt b/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt index 6027680c4..86db90836 100644 --- a/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt +++ b/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt @@ -28,9 +28,9 @@ import com.lambda.brigadier.executeWithResult import com.lambda.brigadier.required import com.lambda.command.LambdaCommand import com.lambda.config.automation.AutomationConfig -import com.lambda.interaction.handlers.ContainerHandler -import com.lambda.interaction.handlers.ContainerHandler.findContainersWithMaterial -import com.lambda.interaction.handlers.ContainerHandler.findContainersWithSpace +import com.lambda.interaction.handler.handlers.ContainerHandler +import com.lambda.interaction.handler.handlers.ContainerHandler.findContainers +import com.lambda.interaction.handler.handlers.ContainerHandler.findContainersWithSpace import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack import com.lambda.task.RootTask import com.lambda.task.Task @@ -55,7 +55,7 @@ object TransferCommand : LambdaCommand( isItem(stack(ctx).value().item) } AutomationConfig.DEFAULT.runSafeAutomated { - val containers = selection.findContainersWithMaterial() + val containers = selection.findContainers() val indexedContainers = containers.withIndex() suggestMatching( @@ -96,11 +96,11 @@ object TransferCommand : LambdaCommand( isItem(stack().value().item) } AutomationConfig.DEFAULT.runSafeAutomated { - val fromContainer = ContainerHandler.containers().find { + val fromContainer = ContainerHandler.filteredContainers().find { it.name == from().value().split(".").last().trim() } ?: return@executeWithResult failure("From container not found") - val toContainer = ContainerHandler.containers().find { + val toContainer = ContainerHandler.filteredContainers().find { it.name == to().value().split(".").last().trim() } ?: return@executeWithResult failure("To container not found") diff --git a/src/main/kotlin/com/lambda/config/ConfigCategory.kt b/src/main/kotlin/com/lambda/config/ConfigCategory.kt index 989cd5c31..fc181fc12 100644 --- a/src/main/kotlin/com/lambda/config/ConfigCategory.kt +++ b/src/main/kotlin/com/lambda/config/ConfigCategory.kt @@ -25,7 +25,7 @@ import com.lambda.config.migration.ConfigMigrationHandler import com.lambda.core.Loadable import com.lambda.event.events.ClientEvent import com.lambda.event.listener.UnsafeListener.Companion.listenUnsafe -import com.lambda.interaction.handlers.BaritoneHandler.primary +import com.lambda.interaction.handler.handlers.BaritoneHandler.primary import com.lambda.threading.runIO import com.lambda.util.CommunicationUtils.info import com.lambda.util.CommunicationUtils.logError diff --git a/src/main/kotlin/com/lambda/config/blocks/BuildConfig.kt b/src/main/kotlin/com/lambda/config/blocks/BuildConfig.kt index 7e98fc835..6e7922008 100644 --- a/src/main/kotlin/com/lambda/config/blocks/BuildConfig.kt +++ b/src/main/kotlin/com/lambda/config/blocks/BuildConfig.kt @@ -17,8 +17,8 @@ package com.lambda.config.blocks -import com.lambda.interaction.managers.rotating.Rotation.Companion.dist -import com.lambda.interaction.managers.rotating.RotationManager +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.dist +import com.lambda.interaction.manager.managers.rotating.RotationManager import com.lambda.util.Describable import com.lambda.util.NamedEnum import com.lambda.util.math.distSq diff --git a/src/main/kotlin/com/lambda/config/blocks/EntityColorSettings.kt b/src/main/kotlin/com/lambda/config/blocks/EntityColorSettings.kt index 4545e7d1c..ea27b3e0b 100644 --- a/src/main/kotlin/com/lambda/config/blocks/EntityColorSettings.kt +++ b/src/main/kotlin/com/lambda/config/blocks/EntityColorSettings.kt @@ -20,7 +20,7 @@ package com.lambda.config.blocks import com.lambda.config.Config import com.lambda.config.ConfigBlock import com.lambda.context.SafeContext -import com.lambda.interaction.handlers.FriendHandler.isFriend +import com.lambda.interaction.handler.handlers.FriendHandler.isFriend import com.lambda.util.EntityUtils.EntityGroup import com.lambda.util.EntityUtils.entityGroup import com.lambda.util.extension.blockColor diff --git a/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt b/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt index a24a0db62..be5c27139 100644 --- a/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt +++ b/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt @@ -58,7 +58,6 @@ interface InventoryConfig { WithMinItems("With Min Items", "Pick containers with the fewest matching items (or least space) first; useful for topping off or clearing leftovers."), WithMaxItems("With Max Items", "Pick containers with the most matching items (or most space) first; ideal for bulk moves with fewer transfers."); - context(_: SafeContext) fun materialComparator(selection: StackSelection) = when (this) { WithMaxItems -> compareBy { it.rank } @@ -70,7 +69,6 @@ interface InventoryConfig { .thenBy { it.name } } - context(_: SafeContext) fun spaceComparator(selection: StackSelection) = when (this) { WithMaxItems -> compareBy { it.rank } diff --git a/src/main/kotlin/com/lambda/config/blocks/RotationConfig.kt b/src/main/kotlin/com/lambda/config/blocks/RotationConfig.kt index a20530cf4..3f13dfa0b 100644 --- a/src/main/kotlin/com/lambda/config/blocks/RotationConfig.kt +++ b/src/main/kotlin/com/lambda/config/blocks/RotationConfig.kt @@ -18,8 +18,8 @@ package com.lambda.config.blocks import com.lambda.event.events.TickEvent -import com.lambda.interaction.managers.rotating.RotationManager -import com.lambda.interaction.managers.rotating.RotationMode +import com.lambda.interaction.manager.managers.rotating.RotationManager +import com.lambda.interaction.manager.managers.rotating.RotationMode interface RotationConfig { /** diff --git a/src/main/kotlin/com/lambda/config/blocks/RotationSettings.kt b/src/main/kotlin/com/lambda/config/blocks/RotationSettings.kt index cdaaf397c..c927e65a5 100644 --- a/src/main/kotlin/com/lambda/config/blocks/RotationSettings.kt +++ b/src/main/kotlin/com/lambda/config/blocks/RotationSettings.kt @@ -21,7 +21,7 @@ import com.lambda.config.Config import com.lambda.config.ConfigBlock import com.lambda.event.events.TickEvent import com.lambda.event.events.TickEvent.Companion.ALL_STAGES -import com.lambda.interaction.managers.rotating.RotationMode +import com.lambda.interaction.manager.managers.rotating.RotationMode import kotlin.math.PI import kotlin.math.abs import kotlin.math.cos diff --git a/src/main/kotlin/com/lambda/config/blocks/TargetingSettings.kt b/src/main/kotlin/com/lambda/config/blocks/TargetingSettings.kt index 9c75d3008..b5a96aa53 100644 --- a/src/main/kotlin/com/lambda/config/blocks/TargetingSettings.kt +++ b/src/main/kotlin/com/lambda/config/blocks/TargetingSettings.kt @@ -22,10 +22,10 @@ import com.lambda.config.ConfigBlock import com.lambda.config.ConfigEditor.hide import com.lambda.config.withEdits import com.lambda.context.SafeContext -import com.lambda.interaction.handlers.FriendHandler.isFriend -import com.lambda.interaction.managers.rotating.Rotation.Companion.dist -import com.lambda.interaction.managers.rotating.Rotation.Companion.rotation -import com.lambda.interaction.managers.rotating.Rotation.Companion.rotationTo +import com.lambda.interaction.handler.handlers.FriendHandler.isFriend +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.dist +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotation +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotationTo import com.lambda.threading.runSafe import com.lambda.util.extension.fullHealth import com.lambda.util.math.distSq diff --git a/src/main/kotlin/com/lambda/event/events/RotationEvent.kt b/src/main/kotlin/com/lambda/event/events/RotationEvent.kt index c6f6f2a8a..c807c825e 100644 --- a/src/main/kotlin/com/lambda/event/events/RotationEvent.kt +++ b/src/main/kotlin/com/lambda/event/events/RotationEvent.kt @@ -18,7 +18,7 @@ package com.lambda.event.events import com.lambda.event.Event -import com.lambda.interaction.managers.rotating.RotationRequest +import com.lambda.interaction.manager.managers.rotating.RotationRequest import net.minecraft.client.input.Input sealed class RotationEvent { diff --git a/src/main/kotlin/com/lambda/gui/MenuBar.kt b/src/main/kotlin/com/lambda/gui/MenuBar.kt index 2b205382b..30c164420 100644 --- a/src/main/kotlin/com/lambda/gui/MenuBar.kt +++ b/src/main/kotlin/com/lambda/gui/MenuBar.kt @@ -41,7 +41,7 @@ import com.lambda.imgui.flag.ImGuiCol import com.lambda.imgui.flag.ImGuiStyleVar import com.lambda.imgui.flag.ImGuiWindowFlags import com.lambda.imgui.type.ImBoolean -import com.lambda.interaction.handlers.BaritoneHandler +import com.lambda.interaction.handler.handlers.BaritoneHandler import com.lambda.module.ModuleRegistry import com.lambda.module.ModuleRegistry.moduleNameMap import com.lambda.module.tag.ModuleTag diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/Sim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/Sim.kt index 5c9532b84..2bcf25681 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/Sim.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/Sim.kt @@ -20,7 +20,7 @@ package com.lambda.interaction.construction.simulation import com.lambda.interaction.construction.simulation.processing.PreProcessingData import com.lambda.interaction.construction.simulation.result.BuildResult import com.lambda.interaction.construction.simulation.result.results.GenericResult -import com.lambda.interaction.managers.rotating.Rotation.Companion.rotationTo +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotationTo import com.lambda.util.math.distSq import com.lambda.util.math.vec3d import com.lambda.util.player.CheckedHit diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt index 2b15101ad..a2ff2c62f 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt @@ -28,15 +28,15 @@ import com.lambda.interaction.construction.simulation.result.BuildResult import com.lambda.interaction.construction.simulation.result.results.BreakResult import com.lambda.interaction.construction.simulation.result.results.GenericResult import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.handlers.ContainerHandler.findContainersWithMaterial +import com.lambda.interaction.handler.handlers.ContainerHandler.findContainers import com.lambda.interaction.inventory.ContainerSelectionBuilder.Companion.selectContainer import com.lambda.interaction.inventory.StackAndSlot import com.lambda.interaction.inventory.StackSelection import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack import com.lambda.interaction.inventory.container.Container -import com.lambda.interaction.managers.hotbar.HotbarManager -import com.lambda.interaction.managers.rotating.RotationManager -import com.lambda.interaction.managers.rotating.RotationRequestBuilder.Companion.rotationRequest +import com.lambda.interaction.manager.managers.hotbar.HotbarManager +import com.lambda.interaction.manager.managers.rotating.RotationManager +import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.util.BlockUtils.blockState import com.lambda.util.BlockUtils.calcItemBlockBreakingDelta import com.lambda.util.BlockUtils.instantBreakable @@ -166,7 +166,7 @@ class BreakSim private constructor(simInfo: SimInfo) } val hotbarCandidates = stackSelection - .findContainersWithMaterial(containerSelection) + .findContainers(containerSelection) .flatMap { stackSelection.filter(it.stacks) } if (hotbarCandidates.isEmpty()) { result(GenericResult.WrongItemSelection(pos, stackSelection, player.mainHandStack)) 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 e81c88b73..46ea402dc 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 @@ -27,15 +27,15 @@ import com.lambda.interaction.construction.simulation.result.BuildResult import com.lambda.interaction.construction.simulation.result.results.GenericResult import com.lambda.interaction.construction.simulation.result.results.InteractResult import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.handlers.ContainerHandler.findContainersWithMaterial +import com.lambda.interaction.handler.handlers.ContainerHandler.findContainers import com.lambda.interaction.inventory.ContainerSelectionBuilder.Companion.selectContainer import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.select import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack import com.lambda.interaction.inventory.container.Container -import com.lambda.interaction.managers.rotating.Rotation -import com.lambda.interaction.managers.rotating.Rotation.Companion.rotation -import com.lambda.interaction.managers.rotating.RotationManager -import com.lambda.interaction.managers.rotating.RotationRequestBuilder.Companion.rotationRequest +import com.lambda.interaction.manager.managers.rotating.Rotation +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotation +import com.lambda.interaction.manager.managers.rotating.RotationManager +import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.util.BlockUtils.blockState import com.lambda.util.EntityUtils.getPositionsWithinHitboxXZ import com.lambda.util.PlaceDirection @@ -213,7 +213,7 @@ class InteractSim private constructor(simInfo: InteractSimInfo) val stackSelection = item?.select() ?: selectStack { sortedWith { compareByDescending { it.stack.inventoryIndex == player.inventory.selectedSlot } } } val containerSelection = selectContainer { ofAnyType(Container.Rank.Hotbar) } - val container = stackSelection.findContainersWithMaterial(containerSelection).firstOrNull() ?: run { + val container = stackSelection.findContainers(containerSelection).firstOrNull() ?: run { result(GenericResult.WrongItemSelection(pos, stackSelection, player.mainHandStack)) return null } diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/context/BreakContext.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/context/BreakContext.kt index 5fbdb6fa7..a11a43628 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/context/BreakContext.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/context/BreakContext.kt @@ -20,7 +20,7 @@ package com.lambda.interaction.construction.simulation.context import com.lambda.context.Automated import com.lambda.graphics.mc.RenderBuilder import com.lambda.interaction.inventory.StackSelection -import com.lambda.interaction.managers.rotating.RotationRequest +import com.lambda.interaction.manager.managers.rotating.RotationRequest import com.lambda.threading.runSafe import com.lambda.util.BlockUtils.emptyState import net.minecraft.block.BlockState diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/context/BuildContext.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/context/BuildContext.kt index 676d99b47..de4669021 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/context/BuildContext.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/context/BuildContext.kt @@ -20,7 +20,7 @@ package com.lambda.interaction.construction.simulation.context import com.lambda.config.blocks.ActionConfig import com.lambda.context.Automated import com.lambda.interaction.construction.simulation.result.Drawable -import com.lambda.interaction.managers.rotating.RotationRequest +import com.lambda.interaction.manager.managers.rotating.RotationRequest import com.lambda.threading.runSafe import net.minecraft.block.BlockState import net.minecraft.util.hit.BlockHitResult diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/context/InteractContext.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/context/InteractContext.kt index a431da618..6ab4617b6 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/context/InteractContext.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/context/InteractContext.kt @@ -20,8 +20,8 @@ package com.lambda.interaction.construction.simulation.context import com.lambda.context.Automated import com.lambda.graphics.mc.RenderBuilder import com.lambda.interaction.construction.simulation.processing.PreProcessingInfo -import com.lambda.interaction.managers.interacting.InteractRequest -import com.lambda.interaction.managers.rotating.RotationRequest +import com.lambda.interaction.manager.managers.interacting.InteractRequest +import com.lambda.interaction.manager.managers.rotating.RotationRequest import net.minecraft.block.BlockState import net.minecraft.util.hit.BlockHitResult import net.minecraft.util.math.BlockPos diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/Contextual.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/Contextual.kt index 9f25ed94f..56a7395f3 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/Contextual.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/Contextual.kt @@ -21,8 +21,8 @@ import com.lambda.config.blocks.ActionConfig import com.lambda.interaction.construction.simulation.context.BreakContext import com.lambda.interaction.construction.simulation.context.BuildContext import com.lambda.interaction.construction.simulation.context.InteractContext -import com.lambda.interaction.managers.hotbar.HotbarManager -import com.lambda.interaction.managers.rotating.RotationManager +import com.lambda.interaction.manager.managers.hotbar.HotbarManager +import com.lambda.interaction.manager.managers.rotating.RotationManager import com.lambda.threading.runSafe import com.lambda.util.BlockUtils import net.minecraft.block.Blocks diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt index 1ac15b8ed..bbfda574b 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt @@ -31,11 +31,11 @@ import com.lambda.interaction.construction.simulation.result.Drawable import com.lambda.interaction.construction.simulation.result.Navigable import com.lambda.interaction.construction.simulation.result.Rank import com.lambda.interaction.construction.simulation.result.Resolvable -import com.lambda.interaction.handlers.BaritoneHandler -import com.lambda.interaction.handlers.ContainerHandler.transferByTask +import com.lambda.interaction.handler.handlers.BaritoneHandler import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.task.Task +import com.lambda.task.tasks.ContainerTransferTask.Companion.transfer import net.minecraft.block.BlockState import net.minecraft.item.Item import net.minecraft.util.math.BlockPos @@ -119,9 +119,10 @@ sealed class BreakResult : BuildResult() { context(task: Task<*>, _: AutomatedSafeContext) override fun resolve() { - selectStack { - inverted { isItem(badItem) } - }.transferByTask(HotbarContainer)?.softFail()?.execute(task) + selectStack { inverted { isItem(badItem) } } + .transfer(HotbarContainer) + ?.softFail() + ?.execute(task) } override fun compareResult(other: ComparableResult) = diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/GenericResult.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/GenericResult.kt index 9762b191d..4918d61f5 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/GenericResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/GenericResult.kt @@ -26,11 +26,11 @@ import com.lambda.interaction.construction.simulation.result.Drawable import com.lambda.interaction.construction.simulation.result.Navigable import com.lambda.interaction.construction.simulation.result.Rank import com.lambda.interaction.construction.simulation.result.Resolvable -import com.lambda.interaction.handlers.BaritoneHandler -import com.lambda.interaction.handlers.ContainerHandler.transferByTask +import com.lambda.interaction.handler.handlers.BaritoneHandler import com.lambda.interaction.inventory.StackSelection import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.task.Task +import com.lambda.task.tasks.ContainerTransferTask.Companion.transfer import net.minecraft.client.data.TextureMap.side import net.minecraft.item.ItemStack import net.minecraft.util.math.BlockPos @@ -100,7 +100,10 @@ sealed class GenericResult : BuildResult() { context(task: Task<*>, _: AutomatedSafeContext) override fun resolve() { - neededSelection.transferByTask(HotbarContainer)?.softFail()?.execute(task) + neededSelection + .transfer(HotbarContainer) + ?.softFail() + ?.execute(task) } override fun RenderBuilder.render() { diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/InteractResult.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/InteractResult.kt index a05e9f570..dccd49963 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/InteractResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/InteractResult.kt @@ -27,7 +27,7 @@ import com.lambda.interaction.construction.simulation.result.Dependent import com.lambda.interaction.construction.simulation.result.Drawable import com.lambda.interaction.construction.simulation.result.Navigable import com.lambda.interaction.construction.simulation.result.Rank -import com.lambda.interaction.handlers.BaritoneHandler +import com.lambda.interaction.handler.handlers.BaritoneHandler import net.minecraft.block.BlockState import net.minecraft.entity.Entity import net.minecraft.item.Item diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/PreSimResult.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/PreSimResult.kt index efa0cbaf6..62d15fb0f 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/PreSimResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/PreSimResult.kt @@ -24,7 +24,7 @@ import com.lambda.interaction.construction.simulation.result.ComparableResult import com.lambda.interaction.construction.simulation.result.Drawable import com.lambda.interaction.construction.simulation.result.Navigable import com.lambda.interaction.construction.simulation.result.Rank -import com.lambda.interaction.handlers.BaritoneHandler +import com.lambda.interaction.handler.handlers.BaritoneHandler import net.minecraft.block.BlockState import net.minecraft.util.math.BlockPos import java.awt.Color diff --git a/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt b/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt index 1ac813f5f..f6ccb54ee 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt @@ -19,7 +19,7 @@ package com.lambda.interaction.construction.verify import com.lambda.context.AutomatedSafeContext import com.lambda.context.SafeContext -import com.lambda.interaction.handlers.ContainerHandler.findDisposable +import com.lambda.interaction.handler.handlers.ContainerHandler.findDisposable import com.lambda.util.BlockUtils.blockState import com.lambda.util.BlockUtils.emptyState import com.lambda.util.BlockUtils.isEmpty diff --git a/src/main/kotlin/com/lambda/interaction/managers/PostActionHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/PostActionHandler.kt similarity index 91% rename from src/main/kotlin/com/lambda/interaction/managers/PostActionHandler.kt rename to src/main/kotlin/com/lambda/interaction/handler/PostActionHandler.kt index b1d2b7a44..5994bb01e 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/PostActionHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/PostActionHandler.kt @@ -15,14 +15,15 @@ * along with this program. If not, see . */ -package com.lambda.interaction.managers +package com.lambda.interaction.handler import com.lambda.context.Automated import com.lambda.event.events.ConnectionEvent 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.interaction.handlers.breaking.BrokenBlockHandler +import com.lambda.interaction.handler.handlers.breaking.BrokenBlockHandler +import com.lambda.interaction.manager.ActionInfo import com.lambda.util.collections.LimitedDecayQueue /** diff --git a/src/main/kotlin/com/lambda/interaction/handlers/BaritoneHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/BaritoneHandler.kt similarity index 99% rename from src/main/kotlin/com/lambda/interaction/handlers/BaritoneHandler.kt rename to src/main/kotlin/com/lambda/interaction/handler/handlers/BaritoneHandler.kt index ff939f26c..b0959604f 100644 --- a/src/main/kotlin/com/lambda/interaction/handlers/BaritoneHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/BaritoneHandler.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.handlers +package com.lambda.interaction.handler.handlers import baritone.api.BaritoneAPI import baritone.api.IBaritone diff --git a/src/main/kotlin/com/lambda/interaction/handlers/ContainerHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt similarity index 57% rename from src/main/kotlin/com/lambda/interaction/handlers/ContainerHandler.kt rename to src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt index e1db759de..e7b91ede3 100644 --- a/src/main/kotlin/com/lambda/interaction/handlers/ContainerHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt @@ -15,29 +15,30 @@ * along with this program. If not, see . */ -package com.lambda.interaction.handlers +package com.lambda.interaction.handler.handlers +import com.lambda.context.Automated import com.lambda.context.AutomatedSafeContext -import com.lambda.context.SafeContext import com.lambda.core.Loadable import com.lambda.event.events.InventoryEvent import com.lambda.event.events.PlayerEvent import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.inventory.ContainerMarker import com.lambda.interaction.inventory.ContainerSelection import com.lambda.interaction.inventory.StackSelection import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.select import com.lambda.interaction.inventory.container.Container import com.lambda.interaction.inventory.container.containers.external.ChestContainer import com.lambda.interaction.inventory.container.containers.external.EnderChestContainer +import com.lambda.task.Task.Ta5kBuilder import com.lambda.util.BlockUtils.blockEntity -import com.lambda.util.ReflectionUtils +import com.lambda.util.ReflectionUtils.getInstances import com.lambda.util.extension.containerStacks import net.minecraft.block.entity.BlockEntity import net.minecraft.block.entity.ChestBlockEntity import net.minecraft.block.entity.EnderChestBlockEntity import net.minecraft.screen.GenericContainerScreenHandler import net.minecraft.screen.ScreenHandlerType -import net.minecraft.screen.slot.Slot // ToDo: Make this a Configurable to save container caches. Should use a cached region based storage system. @Suppress("unused") @@ -45,9 +46,23 @@ object ContainerHandler : Loadable { private val containers: List get() = compileContainers + runtimeContainers - private val compileContainers = ReflectionUtils.getInstances() + private val compileContainers = getInstances() private val runtimeContainers = mutableSetOf() + context(automated: Automated) + val filteredContainers + get() = + containers + .flatMap { setOf(it) + it.shulkerContainers } + .filter { automated.inventoryConfig.containerSelection.matches(it) } + .sorted() + + val allContainers + get() = + containers + .flatMap { setOf(it) + it.shulkerContainers } + .sorted() + var lastInteractedBlockEntity: BlockEntity? = null override fun load() = "Loaded ${compileContainers.size} containers" @@ -78,81 +93,76 @@ object ContainerHandler : Loadable { .filterIsInstance() .find { it.blockPos == block.pos - }?.update(stacks) ?: runtimeContainers.add(ChestContainer(stacks, block.pos)) + }?.update(stacks) ?: runtimeContainers.add(ChestContainer(block.pos, stacks)) } } lastInteractedBlockEntity = null } } - context(_: SafeContext) - fun containers() = containers.flatMap { setOf(it) + it.shulkerContainers }.sorted() - + @ContainerMarker context(automatedSafeContext: AutomatedSafeContext) fun StackSelection.transfer(destination: Container) = with(automatedSafeContext) { - findContainerWithMaterial( - inventoryConfig.containerSelection - )?.transfer(this@transfer, destination) + findContainer(inventoryConfig.containerSelection) + ?.transfer(this@transfer, destination) ?: false } - context(automatedSafeContext: AutomatedSafeContext) - fun StackSelection.transferByTask(destination: Container) = - with(automatedSafeContext) { - findContainerWithMaterial()?.transferByTask(this@transferByTask, destination) - } - - context(_: SafeContext) - fun findContainer( - block: (Container) -> Boolean, - ): Container? = containers().find(block) - - context(automatedSafeContext: AutomatedSafeContext) - fun StackSelection.findContainerWithMaterial( - containerSelection: ContainerSelection = automatedSafeContext.inventoryConfig.containerSelection - ): Container? = findContainersWithMaterial(containerSelection).firstOrNull() - - context(automatedSafeContext: AutomatedSafeContext) - fun StackSelection.findContainersWithMaterial( - containerSelection: ContainerSelection = automatedSafeContext.inventoryConfig.containerSelection, - ): List = - containers() + @ContainerMarker + context(_: Automated) + fun findContainer(block: (Container) -> Boolean) = filteredContainers.find(block) + + @ContainerMarker + context(automated: Automated) + fun StackSelection.findContainer( + containerSelection: ContainerSelection = automated.inventoryConfig.containerSelection + ) = findContainers(containerSelection).firstOrNull() + + @ContainerMarker + context(automated: Automated) + fun StackSelection.findContainers( + containerSelection: ContainerSelection = automated.inventoryConfig.containerSelection + ) = + filteredContainers .filter { containerSelection.matches(it) } .filter { it.stackCount(this) >= count } - .sortedWith(automatedSafeContext.inventoryConfig.providerPriority.materialComparator(this)) + .sortedWith(automated.inventoryConfig.providerPriority.materialComparator(this)) - context(automatedSafeContext: AutomatedSafeContext) + @ContainerMarker + context(automated: Automated) fun StackSelection.findContainerWithSpace( - containerSelection: ContainerSelection = automatedSafeContext.inventoryConfig.containerSelection - ): Container? = findContainersWithSpace(containerSelection).firstOrNull() + containerSelection: ContainerSelection = automated.inventoryConfig.containerSelection + ) = findContainersWithSpace(containerSelection).firstOrNull() - context(automatedSafeContext: AutomatedSafeContext) + @ContainerMarker + context(automated: Automated) fun StackSelection.findContainersWithSpace( - containerSelection: ContainerSelection = automatedSafeContext.inventoryConfig.containerSelection - ): List = - containers() + containerSelection: ContainerSelection = automated.inventoryConfig.containerSelection + ) = + filteredContainers .filter { containerSelection.matches(it) } .filter { it.spaceAvailable(this) >= count } - .sortedWith(automatedSafeContext.inventoryConfig.providerPriority.spaceComparator(this)) - - context(automatedSafeContext: AutomatedSafeContext) - fun StackSelection.findSlotWithMaterial( - containerSelection: ContainerSelection = automatedSafeContext.inventoryConfig.containerSelection - ) = findSlotsWithMaterial(containerSelection).firstOrNull() - - context(automatedSafeContext: AutomatedSafeContext) - fun StackSelection.findSlotsWithMaterial( - containerSelection: ContainerSelection = automatedSafeContext.inventoryConfig.containerSelection - ): List = - findContainersWithMaterial(containerSelection) - .flatMap { filter(it.slots) } - - - context(automatedSafeContext: AutomatedSafeContext) - fun findDisposable() = containers().find { container -> - automatedSafeContext.inventoryConfig.disposables.any { container.stackCount(it.asItem().select()) > 0 } - } + .sortedWith(automated.inventoryConfig.providerPriority.spaceComparator(this)) + + @ContainerMarker + context(automated: Automated) + fun StackSelection.findSlot( + containerSelection: ContainerSelection = automated.inventoryConfig.containerSelection + ) = findSlots(containerSelection).firstOrNull() + + @ContainerMarker + context(automated: Automated) + fun StackSelection.findSlots( + containerSelection: ContainerSelection = automated.inventoryConfig.containerSelection + ) = findContainers(containerSelection).flatMap { filter(it.slots) } + + @ContainerMarker + context(automated: Automated) + fun findDisposable() = + filteredContainers.find { container -> + automated.inventoryConfig.disposables.any { container.stackCount(it.asItem().select(1)) > 0 } + } class NoContainerFound(selection: StackSelection) : Exception("No container found matching $selection") } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/handlers/FriendHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/FriendHandler.kt similarity index 99% rename from src/main/kotlin/com/lambda/interaction/handlers/FriendHandler.kt rename to src/main/kotlin/com/lambda/interaction/handler/handlers/FriendHandler.kt index 9afa8a5d3..b2c02b37e 100644 --- a/src/main/kotlin/com/lambda/interaction/handlers/FriendHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/FriendHandler.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.handlers +package com.lambda.interaction.handler.handlers import com.lambda.Lambda import com.lambda.command.CommandRegistry diff --git a/src/main/kotlin/com/lambda/interaction/handlers/GlideHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/GlideHandler.kt similarity index 99% rename from src/main/kotlin/com/lambda/interaction/handlers/GlideHandler.kt rename to src/main/kotlin/com/lambda/interaction/handler/handlers/GlideHandler.kt index 73157e79f..56fe6eb36 100644 --- a/src/main/kotlin/com/lambda/interaction/handlers/GlideHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/GlideHandler.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.handlers +package com.lambda.interaction.handler.handlers import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent diff --git a/src/main/kotlin/com/lambda/interaction/handlers/PingHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/PingHandler.kt similarity index 97% rename from src/main/kotlin/com/lambda/interaction/handlers/PingHandler.kt rename to src/main/kotlin/com/lambda/interaction/handler/handlers/PingHandler.kt index 6913519a0..e8720a75e 100644 --- a/src/main/kotlin/com/lambda/interaction/handlers/PingHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/PingHandler.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.handlers +package com.lambda.interaction.handler.handlers import com.lambda.core.Loadable import com.lambda.event.events.PacketEvent diff --git a/src/main/kotlin/com/lambda/interaction/handlers/TimerHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/TimerHandler.kt similarity index 96% rename from src/main/kotlin/com/lambda/interaction/handlers/TimerHandler.kt rename to src/main/kotlin/com/lambda/interaction/handler/handlers/TimerHandler.kt index 0bd755ea9..d9d620358 100644 --- a/src/main/kotlin/com/lambda/interaction/handlers/TimerHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/TimerHandler.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.handlers +package com.lambda.interaction.handler.handlers import com.lambda.core.Loadable import com.lambda.event.EventFlow.post diff --git a/src/main/kotlin/com/lambda/interaction/handlers/breaking/BrokenBlockHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/BrokenBlockHandler.kt similarity index 93% rename from src/main/kotlin/com/lambda/interaction/handlers/breaking/BrokenBlockHandler.kt rename to src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/BrokenBlockHandler.kt index b34ccf2c6..3086d5130 100644 --- a/src/main/kotlin/com/lambda/interaction/handlers/breaking/BrokenBlockHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/BrokenBlockHandler.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.handlers.breaking +package com.lambda.interaction.handler.handlers.breaking import com.lambda.config.automation.AutomationConfig.Companion.DEFAULT import com.lambda.config.blocks.BreakConfig.BreakConfirmationMode @@ -24,11 +24,11 @@ import com.lambda.event.events.EntityEvent import com.lambda.event.events.WorldEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.construction.simulation.processing.ProcessorRegistry -import com.lambda.interaction.handlers.breaking.RebreakHandler.rebreak -import com.lambda.interaction.managers.PostActionHandler -import com.lambda.interaction.managers.breaking.BreakInfo -import com.lambda.interaction.managers.breaking.BreakManager.lastPosStarted -import com.lambda.interaction.managers.breaking.BreakManager.matchesBlockItem +import com.lambda.interaction.handler.handlers.breaking.RebreakHandler.rebreak +import com.lambda.interaction.handler.PostActionHandler +import com.lambda.interaction.manager.managers.breaking.BreakInfo +import com.lambda.interaction.manager.managers.breaking.BreakManager.lastPosStarted +import com.lambda.interaction.manager.managers.breaking.BreakManager.matchesBlockItem import com.lambda.module.modules.client.Client import com.lambda.module.modules.client.Client.verboseDebug import com.lambda.threading.runGameScheduled diff --git a/src/main/kotlin/com/lambda/interaction/handlers/breaking/RebreakHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/RebreakHandler.kt similarity index 83% rename from src/main/kotlin/com/lambda/interaction/handlers/breaking/RebreakHandler.kt rename to src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/RebreakHandler.kt index 122414983..4091947d9 100644 --- a/src/main/kotlin/com/lambda/interaction/handlers/breaking/RebreakHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/RebreakHandler.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.handlers.breaking +package com.lambda.interaction.handler.handlers.breaking import com.lambda.config.blocks.BreakConfig import com.lambda.context.SafeContext @@ -24,20 +24,20 @@ 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.interaction.construction.simulation.context.BreakContext -import com.lambda.interaction.handlers.breaking.BrokenBlockHandler.destroyBlock -import com.lambda.interaction.handlers.breaking.RebreakHandler.rebreak -import com.lambda.interaction.handlers.packet.PacketLimitHandler -import com.lambda.interaction.handlers.packet.PacketType -import com.lambda.interaction.managers.breaking.BreakInfo -import com.lambda.interaction.managers.breaking.BreakManager -import com.lambda.interaction.managers.breaking.BreakManager.calcBreakDelta -import com.lambda.interaction.managers.breaking.BreakRequest +import com.lambda.interaction.handler.handlers.breaking.BrokenBlockHandler.destroyBlock +import com.lambda.interaction.handler.handlers.breaking.RebreakHandler.rebreak +import com.lambda.interaction.handler.handlers.packet.PacketLimitHandler +import com.lambda.interaction.handler.handlers.packet.PacketType +import com.lambda.interaction.manager.managers.breaking.BreakInfo +import com.lambda.interaction.manager.managers.breaking.BreakManager +import com.lambda.interaction.manager.managers.breaking.BreakManager.calcBreakDelta +import com.lambda.interaction.manager.managers.breaking.BreakRequest import com.lambda.threading.runSafeAutomated import com.lambda.util.player.PlayerUtils.swingHand import net.minecraft.util.Hand /** - * Designed to track the latest primary-broken [com.lambda.interaction.managers.breaking.BreakInfo] in order to exploit a flaw in Minecraft's code that allows + * Designed to track the latest primary-broken [BreakInfo] in order to exploit a flaw in Minecraft's code that allows * the user to break any block placed in said position using the progress from the previously broken block. */ object RebreakHandler { @@ -60,7 +60,7 @@ object RebreakHandler { /** * Tests to see if the [BreakInfo] can be accepted. If not, nothing happens. Otherwise, - * the [rebreak] is set, and the [com.lambda.interaction.managers.breaking.BreakRequest.onReBreakStart] callback is invoked. + * the [rebreak] is set, and the [BreakRequest.onReBreakStart] callback is invoked. */ context(safeContext: SafeContext) fun offerRebreak(info: BreakInfo) { diff --git a/src/main/kotlin/com/lambda/interaction/handlers/breaking/RebreakResult.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/RebreakResult.kt similarity index 87% rename from src/main/kotlin/com/lambda/interaction/handlers/breaking/RebreakResult.kt rename to src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/RebreakResult.kt index 27dc43bbb..c98b4a6bf 100644 --- a/src/main/kotlin/com/lambda/interaction/handlers/breaking/RebreakResult.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/RebreakResult.kt @@ -15,9 +15,9 @@ * along with this program. If not, see . */ -package com.lambda.interaction.handlers.breaking +package com.lambda.interaction.handler.handlers.breaking -import com.lambda.interaction.managers.breaking.BreakInfo +import com.lambda.interaction.manager.managers.breaking.BreakInfo sealed class RebreakResult { data object Ignored : RebreakResult() diff --git a/src/main/kotlin/com/lambda/interaction/handlers/interacting/InteractedBlockHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/interacting/InteractedBlockHandler.kt similarity index 91% rename from src/main/kotlin/com/lambda/interaction/handlers/interacting/InteractedBlockHandler.kt rename to src/main/kotlin/com/lambda/interaction/handler/handlers/interacting/InteractedBlockHandler.kt index b1c1db359..59aeaa843 100644 --- a/src/main/kotlin/com/lambda/interaction/handlers/interacting/InteractedBlockHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/interacting/InteractedBlockHandler.kt @@ -15,15 +15,15 @@ * along with this program. If not, see . */ -package com.lambda.interaction.handlers.interacting +package com.lambda.interaction.handler.handlers.interacting import com.lambda.config.automation.AutomationConfig import com.lambda.config.blocks.InteractConfig import com.lambda.event.events.WorldEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.PostActionHandler -import com.lambda.interaction.managers.interacting.InteractInfo -import com.lambda.interaction.managers.interacting.InteractManager.placeSound +import com.lambda.interaction.handler.PostActionHandler +import com.lambda.interaction.manager.managers.interacting.InteractInfo +import com.lambda.interaction.manager.managers.interacting.InteractManager.placeSound import com.lambda.module.modules.client.Client import com.lambda.threading.runSafe import com.lambda.util.BlockUtils.matches diff --git a/src/main/kotlin/com/lambda/interaction/handlers/packet/PacketLimitHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/packet/PacketLimitHandler.kt similarity index 97% rename from src/main/kotlin/com/lambda/interaction/handlers/packet/PacketLimitHandler.kt rename to src/main/kotlin/com/lambda/interaction/handler/handlers/packet/PacketLimitHandler.kt index aa11a5d9e..2b6fdf428 100644 --- a/src/main/kotlin/com/lambda/interaction/handlers/packet/PacketLimitHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/packet/PacketLimitHandler.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.handlers.packet +package com.lambda.interaction.handler.handlers.packet import com.lambda.config.blocks.BuildConfig import com.lambda.context.Automated diff --git a/src/main/kotlin/com/lambda/interaction/inventory/ContainerMarker.kt b/src/main/kotlin/com/lambda/interaction/inventory/ContainerMarker.kt new file mode 100644 index 000000000..fa8982066 --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/inventory/ContainerMarker.kt @@ -0,0 +1,21 @@ +/* + * 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.inventory + +@DslMarker +annotation class ContainerMarker \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/inventory/ContainerSelection.kt b/src/main/kotlin/com/lambda/interaction/inventory/ContainerSelection.kt index 328255230..332783fd3 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/ContainerSelection.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/ContainerSelection.kt @@ -27,13 +27,13 @@ class ContainerSelection( val selector: (Container) -> Boolean, val comparator: Comparator = compareBy { it.rank } ) { - @ContainerSelectionMarker + @ContainerMarker fun bestMatch(containers: Iterable) = filter(containers).firstOrNull() - @ContainerSelectionMarker + @ContainerMarker fun matches(container: Container): Boolean = selector(container) - @ContainerSelectionMarker + @ContainerMarker fun filter(containers: Iterable) = containers .filter(selector) @@ -45,11 +45,8 @@ class ContainerSelection( } } -@DslMarker -annotation class ContainerSelectionMarker - @Suppress("unused") -@ContainerSelectionMarker +@ContainerMarker class ContainerSelectionBuilder private constructor() { private var selector: (Container) -> Boolean = { true } private var comparator: Comparator = compareBy { it.rank } diff --git a/src/main/kotlin/com/lambda/interaction/inventory/StackSelection.kt b/src/main/kotlin/com/lambda/interaction/inventory/StackSelection.kt index 81360e2f5..9e59af8b7 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/StackSelection.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/StackSelection.kt @@ -44,21 +44,21 @@ class StackSelection( ) { val optimalStack = itemStack ?: item?.let { ItemStack(it, count) } - @StackSelectionMarker + @ContainerMarker fun bestMatch(stacks: Iterable) = filter(stacks).firstOrNull() - @StackSelectionMarker + @ContainerMarker fun bestMatch(slots: Iterable) = filter(slots).firstOrNull() - @StackSelectionMarker + @ContainerMarker fun matches(stack: ItemStack) = if (inShulkerBox) stack.shulkerBoxStacks.any { selector(it, null) } else selector(stack, null) - @StackSelectionMarker + @ContainerMarker fun matches(slot: Slot): Boolean = if (inShulkerBox) slot.stack.shulkerBoxStacks.any { selector(it, null) } else selector(slot.stack, slot) - @StackSelectionMarker + @ContainerMarker @JvmName("filter1") fun filter(stacks: Iterable) = stacks @@ -66,7 +66,7 @@ class StackSelection( .map { StackAndSlot(it, null) } .sortedWith(comparator) .map { it.stack } - @StackSelectionMarker + @ContainerMarker @JvmName("filter2") fun filter(slots: Iterable) = slots @@ -81,11 +81,8 @@ class StackSelection( } } -@DslMarker -private annotation class StackSelectionMarker - @Suppress("unused") -@StackSelectionMarker +@ContainerMarker class StackSelectionBuilder private constructor(private val count: Int = 0) { private var selector: (ItemStack, Slot?) -> Boolean = { _, _ -> true } private var item: Item? = null @@ -232,10 +229,10 @@ class StackSelectionBuilder private constructor(private val count: Int = 0) { companion object { private val efficientToolCache = Collections.synchronizedMap(mutableMapOf()) - @StackSelectionMarker + @ContainerMarker fun Item.select(count: Int = 0) = selectStack(count) { isItem(this@select) } - @StackSelectionMarker + @ContainerMarker fun ItemStack.select(count: Int = 0) = selectStack(count) { isItemStack(this@select) } fun selectStack( diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/Container.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/Container.kt index 666442a33..1053ea0c6 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/Container.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/Container.kt @@ -24,13 +24,13 @@ import com.lambda.event.EventFlow.post import com.lambda.event.events.ContainerEvent import com.lambda.interaction.inventory.StackSelection import com.lambda.interaction.inventory.container.containers.external.ShulkerBoxContainer -import com.lambda.interaction.managers.inventory.InvRequestBuilder -import com.lambda.interaction.managers.inventory.InvRequestBuilder.Companion.inventoryRequest +import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder +import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.task.Task.Ta5kBuilder import com.lambda.task.TaskGenerator import com.lambda.task.TaskOrNullGenerator import com.lambda.task.tasks.ContainerTransferTask -import com.lambda.task.tasks.NoopTask.Companion.noop +import com.lambda.task.tasks.NoopTask.Companion.noopTask import com.lambda.util.Nameable import com.lambda.util.item.ItemStackUtils.count import com.lambda.util.item.ItemStackUtils.empty @@ -111,14 +111,14 @@ abstract class Container( ShulkerBoxContainer( slot.stack.shulkerBoxStacks, containedIn = this@Container, - shulkerSlot = slot + slot = slot ) }?.toSet() ?: stacks.map { stack -> ShulkerBoxContainer( stack.shulkerBoxStacks, containedIn = this@Container, - shulkerSlot = null + slot = null ) }.toSet() @@ -133,7 +133,7 @@ abstract class Container( open fun accessThen( closeAfter: Boolean = true, afterClose: TaskOrNullGenerator? = null, - afterOpen: TaskGenerator = { noop() } + afterOpen: TaskGenerator = { noopTask() } ) = with(automatedSafeContext) { afterOpen(Unit).also { if (closeAfter && afterClose != null) { @@ -142,6 +142,28 @@ abstract class Container( } } + context(automatedSafeContext: AutomatedSafeContext) + fun transfer(selection: StackSelection, toContainer: Container): Boolean = + with(automatedSafeContext) { + val (fromSlot, toSlot) = getTransferSlots(selection, toContainer) + if (fromSlot == null || toSlot == null) return false + return transfer(fromSlot, toSlot, toContainer) + } + + context(_: Automated) + fun getTransferSlots(selection: StackSelection, destination: Container): Pair = + Pair(getSlot(selection), destination.getReplaceSlot()) + + context(automatedSafeContext: AutomatedSafeContext) + fun transfer(fromSlot: Slot, toSlot: Slot, toContainer: Container): Boolean { + val transferEvent = ContainerEvent.Transfer(fromSlot, toSlot, this@Container, toContainer) + if (transferEvent.post().isCanceled()) return false + return automatedSafeContext.inventoryRequest { + if (swapMethodPriority > toContainer.swapMethodPriority) transfer(fromSlot, toSlot) + else with(toContainer) { transfer(toSlot, fromSlot) } + }.submit().done + } + context(safeContext: SafeContext) open fun InvRequestBuilder.transfer(fromHere: Slot, toSlot: Slot) { if (fromHere.stack.isEmpty) moveSlot(toSlot.id, fromHere.id) @@ -151,19 +173,6 @@ abstract class Container( } } - context(automatedSafeContext: AutomatedSafeContext) - fun transfer(selection: StackSelection, destination: Container): Boolean = - with(automatedSafeContext) { - val fromSlot = getSlot(selection) ?: return false - val toSlot = destination.getReplaceSlot() ?: return false - val transferEvent = ContainerEvent.Transfer(fromSlot, toSlot, this@Container, destination) - if (transferEvent.post().isCanceled()) return false - return inventoryRequest { - if (swapMethodPriority > destination.swapMethodPriority) transfer(fromSlot, toSlot) - else with(destination) { transfer(toSlot, fromSlot) } - }.submit().done - } - context(automated: Automated) fun transferByTask(stackSelection: StackSelection, destination: Container, failIfNoMaterial: Boolean = false) = ContainerTransferTask(this, destination, stackSelection, failIfNoMaterial, automated) @@ -191,6 +200,7 @@ abstract class Container( ShulkerBox, EnderChest, PlacedShulkerBox, + PlacedEnderChest, Chest, Stash } diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/NestedContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/NestedContainer.kt new file mode 100644 index 000000000..94f48fffa --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/NestedContainer.kt @@ -0,0 +1,24 @@ +/* + * 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.inventory.container + +import net.minecraft.screen.slot.Slot + +interface NestedContainer { + val slotCache: Slot +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/PlacedContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/PlacedContainer.kt new file mode 100644 index 000000000..7d6d9d051 --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/PlacedContainer.kt @@ -0,0 +1,24 @@ +/* + * 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.inventory.container + +import com.lambda.interaction.inventory.container.containers.external.StashContainer + +interface PlacedContainer { + val stash: StashContainer? +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/CreativeContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/CreativeContainer.kt index dc115fe6d..fd0e0135f 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/CreativeContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/CreativeContainer.kt @@ -21,7 +21,7 @@ import com.lambda.Lambda.mc import com.lambda.context.SafeContext import com.lambda.interaction.inventory.StackSelection import com.lambda.interaction.inventory.container.Container -import com.lambda.interaction.managers.inventory.InvRequestBuilder +import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder import com.lambda.util.text.buildText import com.lambda.util.text.literal import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/HotbarContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/HotbarContainer.kt index a4e2cd9fd..9949c3d88 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/HotbarContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/HotbarContainer.kt @@ -20,7 +20,7 @@ package com.lambda.interaction.inventory.container.containers import com.lambda.Lambda.mc import com.lambda.context.SafeContext import com.lambda.interaction.inventory.container.Container -import com.lambda.interaction.managers.inventory.InvRequestBuilder +import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder import com.lambda.util.player.SlotUtils.hotbarSlots import com.lambda.util.player.SlotUtils.hotbarStacks import com.lambda.util.text.buildText diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/MainHandContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/MainHandContainer.kt index ecbdff3cf..c87db7077 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/MainHandContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/MainHandContainer.kt @@ -20,7 +20,7 @@ package com.lambda.interaction.inventory.container.containers import com.lambda.Lambda.mc import com.lambda.context.SafeContext import com.lambda.interaction.inventory.container.Container -import com.lambda.interaction.managers.inventory.InvRequestBuilder +import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder import com.lambda.util.player.SlotUtils.mainHandSlots import com.lambda.util.text.buildText import com.lambda.util.text.literal diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/OffHandContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/OffHandContainer.kt index 9746bb349..cbe16ada8 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/OffHandContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/OffHandContainer.kt @@ -20,7 +20,7 @@ package com.lambda.interaction.inventory.container.containers import com.lambda.Lambda.mc import com.lambda.context.SafeContext import com.lambda.interaction.inventory.container.Container -import com.lambda.interaction.managers.inventory.InvRequestBuilder +import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder import com.lambda.util.player.SlotUtils.offHandSlots import com.lambda.util.text.buildText import com.lambda.util.text.literal diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ChestContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ChestContainer.kt index 586b77644..5a2cd8de9 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ChestContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ChestContainer.kt @@ -17,34 +17,42 @@ package com.lambda.interaction.inventory.container.containers.external -import com.lambda.Lambda +import com.lambda.Lambda.mc import com.lambda.context.AutomatedSafeContext -import com.lambda.interaction.handlers.ContainerHandler +import com.lambda.interaction.handler.handlers.ContainerHandler.lastInteractedBlockEntity import com.lambda.interaction.inventory.container.Container import com.lambda.interaction.inventory.container.ExternalContainer +import com.lambda.interaction.inventory.container.PlacedContainer import com.lambda.task.Task +import com.lambda.task.Task.Ta5kBuilder +import com.lambda.task.TaskGenerator import com.lambda.task.TaskOrNullGenerator +import com.lambda.task.tasks.NoopTask.Companion.noopTask import com.lambda.task.tasks.OpenContainerTask +import com.lambda.task.tasks.OpenContainerTask.Companion.openContainer +import com.lambda.task.tasks.SimpleActionTask.Companion.simpleAction import com.lambda.util.extension.containerSlots import com.lambda.util.text.buildText import com.lambda.util.text.highlighted import com.lambda.util.text.literal +import net.minecraft.block.ChestBlock import net.minecraft.block.entity.ChestBlockEntity import net.minecraft.item.ItemStack import net.minecraft.screen.ScreenHandler +import net.minecraft.screen.ScreenHandlerType import net.minecraft.screen.slot.Slot import net.minecraft.util.math.BlockPos data class ChestContainer( - override var stacks: List, val blockPos: BlockPos, - val containedInStash: StashContainer? = null -) : Container(Rank.Chest), ExternalContainer { + override var stacks: List, + override val stash: StashContainer? = null +) : Container(Rank.Chest), ExternalContainer, PlacedContainer { override val slots get(): List = - ContainerHandler.lastInteractedBlockEntity?.let { blockEntity -> + lastInteractedBlockEntity?.let { blockEntity -> if (blockEntity is ChestBlockEntity && blockEntity.pos == blockPos) { - Lambda.mc.player?.currentScreenHandler?.containerSlots + mc.player?.currentScreenHandler?.containerSlots } else emptyList() } ?: emptyList() @@ -52,20 +60,31 @@ data class ChestContainer( buildText { literal("Chest at ") highlighted(blockPos.toShortString()) - containedInStash?.let { stash -> + stash?.let { stash -> literal(" (contained in ") highlighted(stash.name) literal(")") } } - context(automatedSafeContext: AutomatedSafeContext) - override fun accessThen( - exitAfter: Boolean, - afterOpen: TaskOrNullGenerator = { null }, - afterClose: TaskOrNullGenerator = { null } - ): Task<*> = - OpenContainerTask(blockPos, automatedSafeContext) - .thenOrNull(afterOpen) - .then { if (exitAfter) automatedSafeContext.player.closeHandledScreen() } + override val isAccessed + get() = lastInteractedBlockEntity?.let { blockEntity -> + blockEntity.pos == blockPos && + blockEntity.cachedState.block is ChestBlock && + mc.player?.currentScreenHandler?.type == ScreenHandlerType.GENERIC_9X3 + } ?: false + + context(_: AutomatedSafeContext) + override fun accessThen( + closeAfter: Boolean, + afterClose: TaskOrNullGenerator?, + afterOpen: TaskGenerator + ) = + openContainer(blockPos).then { + afterOpen(Unit).thenOrNull { + if (closeAfter) { + simpleAction("Close inventory") { player.closeHandledScreen() } + } else null + } + } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/EnderChestContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/EnderChestContainer.kt index f769bd802..f81b87ddf 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/EnderChestContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/EnderChestContainer.kt @@ -19,48 +19,74 @@ package com.lambda.interaction.inventory.container.containers.external import com.lambda.Lambda.mc import com.lambda.context.AutomatedSafeContext -import com.lambda.interaction.handlers.ContainerHandler +import com.lambda.interaction.handler.handlers.ContainerHandler.lastInteractedBlockEntity +import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.select import com.lambda.interaction.inventory.container.Container import com.lambda.interaction.inventory.container.ExternalContainer import com.lambda.task.TaskGenerator import com.lambda.task.TaskOrNullGenerator -import com.lambda.task.tasks.AcquirePlacedBlockTask -import com.lambda.task.tasks.BuildTask.Companion.breakAndCollectBlock +import com.lambda.task.tasks.AcquireStackTask.Companion.acquireStack +import com.lambda.task.tasks.BuildTask.Companion.breakAndCollect +import com.lambda.task.tasks.NoopTask.Companion.noopTask import com.lambda.task.tasks.OpenContainerTask +import com.lambda.task.tasks.OpenContainerTask.Companion.openContainer +import com.lambda.task.tasks.PlaceContainerTask.Companion.placeContainer +import com.lambda.task.tasks.SimpleActionTask.Companion.simpleAction import com.lambda.util.extension.containerSlots import com.lambda.util.text.buildText import com.lambda.util.text.literal -import net.minecraft.block.Blocks import net.minecraft.block.entity.EnderChestBlockEntity import net.minecraft.item.ItemStack +import net.minecraft.item.Items +import net.minecraft.screen.ScreenHandlerType +import kotlin.comparisons.then object EnderChestContainer : Container(Rank.EnderChest), ExternalContainer { override val slots get() = - if (ContainerHandler.lastInteractedBlockEntity is EnderChestBlockEntity) { - mc.player?.currentScreenHandler?.containerSlots ?: emptyList() - } else emptyList() + if (isAccessed) mc.player?.currentScreenHandler?.containerSlots ?: emptyList() + else emptyList() override var stacks = emptyList() override val description = buildText { literal("Ender Chest") } + override val isAccessed + get() = + lastInteractedBlockEntity?.let { blockEntity -> + blockEntity is EnderChestBlockEntity && + mc.player?.currentScreenHandler?.type == ScreenHandlerType.GENERIC_9X3 + } ?: false + context(automatedSafeContext: AutomatedSafeContext) override fun accessThen( closeAfter: Boolean, afterClose: TaskOrNullGenerator?, afterOpen: TaskGenerator ) = - AcquirePlacedBlockTask( - Blocks.ENDER_CHEST, - automated = automatedSafeContext - ).then { pos -> - OpenContainerTask(pos, automatedSafeContext).then { - afterOpen.invoke(automatedSafeContext, Unit).thenOrNull { - if (closeAfter) { - player.closeHandledScreen() - automatedSafeContext.breakAndCollectBlock(pos, lifeMaintenance = false) - } else null + with(automatedSafeContext) { + if (!isAccessed) { + acquireStack(Items.ENDER_CHEST.select()).then { slot -> + placeContainer(slot).then { pos -> + openContainer(pos).then { + afterOpen(Unit).thenOrNull { + if (closeAfter) { + simpleAction("Close inventory") { player.closeHandledScreen() }.then { + breakAndCollect(pos, lifeMaintenance = false).thenOrNull { + afterClose?.invoke(this, Unit) + } + } + } else null + } + } + } } + } else afterOpen(Unit).thenOrNull { + if (closeAfter) { + simpleAction("Close inventory") { mc.player?.closeHandledScreen() }.thenOrNull { + afterClose?.invoke(this, Unit) + } + } else null } } + } diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/PlacedShulkerBoxContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/PlacedShulkerBoxContainer.kt index d4f1cca4e..697a52783 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/PlacedShulkerBoxContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/PlacedShulkerBoxContainer.kt @@ -17,21 +17,69 @@ package com.lambda.interaction.inventory.container.containers.external +import com.lambda.Lambda.mc +import com.lambda.context.AutomatedSafeContext +import com.lambda.interaction.handler.handlers.ContainerHandler.lastInteractedBlockEntity import com.lambda.interaction.inventory.container.Container +import com.lambda.interaction.inventory.container.PlacedContainer +import com.lambda.task.TaskGenerator +import com.lambda.task.TaskOrNullGenerator +import com.lambda.task.tasks.NoopTask.Companion.noopTask +import com.lambda.task.tasks.OpenContainerTask +import com.lambda.task.tasks.SimpleActionTask.Companion.simpleAction +import com.lambda.util.extension.containerSlots +import com.lambda.util.text.buildText +import com.lambda.util.text.highlighted +import com.lambda.util.text.literal +import net.minecraft.block.Block import net.minecraft.item.ItemStack +import net.minecraft.screen.ScreenHandlerType import net.minecraft.screen.slot.Slot -import net.minecraft.text.Text import net.minecraft.util.math.BlockPos class PlacedShulkerBoxContainer( val blockPos: BlockPos, - val -) : Container(Rank.PlacedShulkerBox) { - override val slots: List - get() = TODO("Not yet implemented") + val block: Block, + override val stash: StashContainer?, override var stacks: List - get() = TODO("Not yet implemented") - set(value) {} - override val description: Text - get() = TODO("Not yet implemented") +) : Container(Rank.PlacedShulkerBox), PlacedContainer { + override val slots: List + get() = + if (isAccessed) mc.player?.currentScreenHandler?.containerSlots ?: emptyList() + else emptyList() + + override val description = + buildText { + literal("Placed shulker box at ") + highlighted(blockPos.toShortString()) + stash?.let { stash -> + literal(" (contained in ") + highlighted(stash.name) + literal(")") + } + } + + override val isAccessed + get() = lastInteractedBlockEntity?.let { blockEntity -> + blockEntity.pos == blockPos && + blockEntity.cachedState.block == block && + mc.player?.currentScreenHandler?.type == ScreenHandlerType.SHULKER_BOX + } ?: false + + context(automatedSafeContext: AutomatedSafeContext) + override fun accessThen( + closeAfter: Boolean, + afterClose: TaskOrNullGenerator?, + afterOpen: TaskGenerator + ) = + run { + if (!isAccessed) OpenContainerTask(blockPos, automated = automatedSafeContext) + else noopTask() + }.then { + afterOpen(Unit).thenOrNull { + if (closeAfter) { + simpleAction("Close Inventory") { automatedSafeContext.player.closeHandledScreen() } + } else null + } + } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ShulkerBoxContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ShulkerBoxContainer.kt index eace9fcb3..9d4091153 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ShulkerBoxContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ShulkerBoxContainer.kt @@ -20,15 +20,16 @@ package com.lambda.interaction.inventory.container.containers.external import com.lambda.Lambda.mc import com.lambda.context.AutomatedSafeContext import com.lambda.context.SafeContext -import com.lambda.interaction.handlers.ContainerHandler.lastInteractedBlockEntity +import com.lambda.interaction.handler.handlers.ContainerHandler.lastInteractedBlockEntity import com.lambda.interaction.inventory.container.Container import com.lambda.interaction.inventory.container.ExternalContainer -import com.lambda.task.Task +import com.lambda.interaction.inventory.container.NestedContainer import com.lambda.task.TaskGenerator import com.lambda.task.TaskOrNullGenerator -import com.lambda.task.tasks.BuildTask.Companion.breakAndCollectBlock -import com.lambda.task.tasks.OpenContainerTask -import com.lambda.task.tasks.PlaceContainerTask +import com.lambda.task.tasks.BuildTask.Companion.breakAndCollect +import com.lambda.task.tasks.OpenContainerTask.Companion.openContainer +import com.lambda.task.tasks.PlaceContainerTask.Companion.placeContainer +import com.lambda.task.tasks.SimpleActionTask.Companion.simpleAction import com.lambda.threading.runSafe import com.lambda.util.extension.containerSlots import com.lambda.util.text.buildText @@ -36,44 +37,56 @@ import com.lambda.util.text.highlighted import com.lambda.util.text.literal import net.minecraft.block.entity.ShulkerBoxBlockEntity import net.minecraft.item.ItemStack +import net.minecraft.screen.ScreenHandlerType import net.minecraft.screen.slot.Slot +import net.minecraft.util.math.BlockPos data class ShulkerBoxContainer( override var stacks: List, val containedIn: Container, - val shulkerSlot: Slot?, -) : Container(Rank.ShulkerBox), ExternalContainer { + override val slotCache: Slot, +) : Container(Rank.ShulkerBox), ExternalContainer, NestedContainer { override val slots get(): List = - if (lastInteractedBlockEntity is ShulkerBoxBlockEntity) - mc.player?.currentScreenHandler?.containerSlots ?: emptyList() + if (isAccessed) mc.player?.currentScreenHandler?.containerSlots ?: emptyList() else emptyList() override val description = buildText { - highlighted(shulkerSlot?.stack?.name?.string ?: "Shulker Box") + highlighted(slotCache.stack.name.string) literal(" in ") highlighted(containedIn.name) - literal(" in slot ") - highlighted("${runSafe { slotInContainer }}") + literal(" in slot $slotCache") } - context(_: SafeContext) - private val slotInContainer: Int get() = containedIn.slots.indexOf(shulkerSlot) + private var placed = false + private var blockPos: BlockPos? = null - override val isAccessed get() = lastInteractedBlockEntity is ShulkerBoxBlockEntity + override val isAccessed + get() = + lastInteractedBlockEntity?.let { blockEntity -> + blockEntity is ShulkerBoxBlockEntity && + blockEntity.pos == blockPos && + mc.player?.currentScreenHandler?.type == ScreenHandlerType.SHULKER_BOX + } ?: false context(automatedSafeContext: AutomatedSafeContext) - override fun accessThen(closeAfter: Boolean, afterClose: TaskOrNullGenerator?, afterOpen: TaskGenerator): Task<*> { - return PlaceContainerTask(shulkerSlot, automatedSafeContext).then { pos -> - OpenContainerTask(pos, automatedSafeContext).then { - afterOpen.invoke(automatedSafeContext, Unit).thenOrNull { - if (closeAfter) { - player.closeHandledScreen() - automatedSafeContext.breakAndCollectBlock(pos) - } else null - } + override fun accessThen( + closeAfter: Boolean, + afterClose: TaskOrNullGenerator?, + afterOpen: TaskGenerator + ) = + with(automatedSafeContext) { + if (!isAccessed) { + + } else afterOpen(Unit).thenOrNull { + if (closeAfter) { + simpleAction("Close inventory") { player.closeHandledScreen() }.then { + breakAndCollect(blockPos, lifeMaintenance = false).thenOrNull { + afterClose?.invoke(this, Unit) + } + } + } else null } } - } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/managers/ActionInfo.kt b/src/main/kotlin/com/lambda/interaction/manager/ActionInfo.kt similarity index 96% rename from src/main/kotlin/com/lambda/interaction/managers/ActionInfo.kt rename to src/main/kotlin/com/lambda/interaction/manager/ActionInfo.kt index 6d457e8d3..dfaf5548e 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/ActionInfo.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/ActionInfo.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.managers +package com.lambda.interaction.manager import com.lambda.interaction.construction.simulation.context.BuildContext diff --git a/src/main/kotlin/com/lambda/interaction/managers/Manager.kt b/src/main/kotlin/com/lambda/interaction/manager/Manager.kt similarity index 97% rename from src/main/kotlin/com/lambda/interaction/managers/Manager.kt rename to src/main/kotlin/com/lambda/interaction/manager/Manager.kt index 7da159c1f..842806fce 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/Manager.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/Manager.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.managers +package com.lambda.interaction.manager import com.lambda.context.Automated import com.lambda.context.AutomatedSafeContext @@ -25,7 +25,7 @@ import com.lambda.event.Event import com.lambda.event.events.TickEvent import com.lambda.event.events.TickEvent.Companion.ALL_STAGES import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.ManagerUtils.accumulatedManagerPriority +import com.lambda.interaction.manager.managers.ManagerUtils.accumulatedManagerPriority import com.lambda.threading.runSafeAutomated import kotlin.reflect.KClass diff --git a/src/main/kotlin/com/lambda/interaction/managers/ManagerUtils.kt b/src/main/kotlin/com/lambda/interaction/manager/ManagerUtils.kt similarity index 96% rename from src/main/kotlin/com/lambda/interaction/managers/ManagerUtils.kt rename to src/main/kotlin/com/lambda/interaction/manager/ManagerUtils.kt index 9a5f193bd..ae2b3589c 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/ManagerUtils.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/ManagerUtils.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.managers +package com.lambda.interaction.manager import com.lambda.util.ReflectionUtils.getInstances import net.minecraft.util.math.BlockPos diff --git a/src/main/kotlin/com/lambda/interaction/managers/PositionBlocking.kt b/src/main/kotlin/com/lambda/interaction/manager/PositionBlocking.kt similarity index 96% rename from src/main/kotlin/com/lambda/interaction/managers/PositionBlocking.kt rename to src/main/kotlin/com/lambda/interaction/manager/PositionBlocking.kt index 14c3bf240..e10a9d2ba 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/PositionBlocking.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/PositionBlocking.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.managers +package com.lambda.interaction.manager import net.minecraft.util.math.BlockPos diff --git a/src/main/kotlin/com/lambda/interaction/managers/Request.kt b/src/main/kotlin/com/lambda/interaction/manager/Request.kt similarity index 97% rename from src/main/kotlin/com/lambda/interaction/managers/Request.kt rename to src/main/kotlin/com/lambda/interaction/manager/Request.kt index 61fa20be2..9bdcd58c6 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/Request.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/Request.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.managers +package com.lambda.interaction.manager import com.lambda.context.Automated import com.lambda.event.events.TickEvent diff --git a/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakInfo.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakInfo.kt similarity index 89% rename from src/main/kotlin/com/lambda/interaction/managers/breaking/BreakInfo.kt rename to src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakInfo.kt index bae597df4..9c5149243 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakInfo.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakInfo.kt @@ -15,18 +15,18 @@ * along with this program. If not, see . */ -package com.lambda.interaction.managers.breaking +package com.lambda.interaction.manager.managers.breaking import com.lambda.config.blocks.BreakConfig import com.lambda.context.SafeContext import com.lambda.interaction.construction.simulation.context.BreakContext -import com.lambda.interaction.handlers.breaking.RebreakHandler -import com.lambda.interaction.managers.ActionInfo -import com.lambda.interaction.managers.breaking.BreakInfo.BreakType.Primary -import com.lambda.interaction.managers.breaking.BreakInfo.BreakType.Rebreak -import com.lambda.interaction.managers.breaking.BreakInfo.BreakType.RedundantSecondary -import com.lambda.interaction.managers.breaking.BreakInfo.BreakType.Secondary -import com.lambda.interaction.managers.breaking.BreakManager.calcBreakDelta +import com.lambda.interaction.handler.handlers.breaking.RebreakHandler +import com.lambda.interaction.manager.managers.ActionInfo +import com.lambda.interaction.manager.managers.breaking.BreakInfo.BreakType.Primary +import com.lambda.interaction.manager.managers.breaking.BreakInfo.BreakType.Rebreak +import com.lambda.interaction.manager.managers.breaking.BreakInfo.BreakType.RedundantSecondary +import com.lambda.interaction.manager.managers.breaking.BreakInfo.BreakType.Secondary +import com.lambda.interaction.manager.managers.breaking.BreakManager.calcBreakDelta import com.lambda.threading.runSafeAutomated import com.lambda.util.Describable import com.lambda.util.NamedEnum diff --git a/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakManager.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakManager.kt similarity index 89% rename from src/main/kotlin/com/lambda/interaction/managers/breaking/BreakManager.kt rename to src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakManager.kt index d8f35e129..bfd6eea7c 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakManager.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakManager.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.managers.breaking +package com.lambda.interaction.manager.managers.breaking import com.lambda.config.blocks.BreakConfig import com.lambda.config.blocks.BreakConfig.BreakConfirmationMode @@ -34,47 +34,47 @@ import com.lambda.interaction.construction.simulation.BuildSimulator.simulate import com.lambda.interaction.construction.simulation.context.BreakContext import com.lambda.interaction.construction.simulation.result.results.BreakResult import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.handlers.breaking.BrokenBlockHandler.destroyBlock -import com.lambda.interaction.handlers.breaking.BrokenBlockHandler.pendingActions -import com.lambda.interaction.handlers.breaking.BrokenBlockHandler.setPendingConfigs -import com.lambda.interaction.handlers.breaking.BrokenBlockHandler.startPending -import com.lambda.interaction.handlers.breaking.RebreakHandler -import com.lambda.interaction.handlers.breaking.RebreakHandler.getRebreakPotential -import com.lambda.interaction.handlers.breaking.RebreakResult -import com.lambda.interaction.handlers.packet.PacketLimitHandler -import com.lambda.interaction.handlers.packet.PacketType +import com.lambda.interaction.handler.handlers.breaking.BrokenBlockHandler.destroyBlock +import com.lambda.interaction.handler.handlers.breaking.BrokenBlockHandler.pendingActions +import com.lambda.interaction.handler.handlers.breaking.BrokenBlockHandler.setPendingConfigs +import com.lambda.interaction.handler.handlers.breaking.BrokenBlockHandler.startPending +import com.lambda.interaction.handler.handlers.breaking.RebreakHandler +import com.lambda.interaction.handler.handlers.breaking.RebreakHandler.getRebreakPotential +import com.lambda.interaction.handler.handlers.breaking.RebreakResult +import com.lambda.interaction.handler.handlers.packet.PacketLimitHandler +import com.lambda.interaction.handler.handlers.packet.PacketType import com.lambda.interaction.inventory.StackSelection -import com.lambda.interaction.managers.Manager -import com.lambda.interaction.managers.ManagerUtils.isPosBlocked -import com.lambda.interaction.managers.PositionBlocking -import com.lambda.interaction.managers.breaking.BreakInfo.BreakType.Primary -import com.lambda.interaction.managers.breaking.BreakInfo.BreakType.Rebreak -import com.lambda.interaction.managers.breaking.BreakInfo.BreakType.RedundantSecondary -import com.lambda.interaction.managers.breaking.BreakInfo.BreakType.Secondary -import com.lambda.interaction.managers.breaking.BreakManager.abandonedBreak -import com.lambda.interaction.managers.breaking.BreakManager.activeInfos -import com.lambda.interaction.managers.breaking.BreakManager.activeRequest -import com.lambda.interaction.managers.breaking.BreakManager.breakInfos -import com.lambda.interaction.managers.breaking.BreakManager.breaks -import com.lambda.interaction.managers.breaking.BreakManager.canAccept -import com.lambda.interaction.managers.breaking.BreakManager.checkForCancels -import com.lambda.interaction.managers.breaking.BreakManager.handlePreProcessing -import com.lambda.interaction.managers.breaking.BreakManager.hotbarRequest -import com.lambda.interaction.managers.breaking.BreakManager.initNewBreak -import com.lambda.interaction.managers.breaking.BreakManager.maxBreaksThisTick -import com.lambda.interaction.managers.breaking.BreakManager.nullify -import com.lambda.interaction.managers.breaking.BreakManager.populateFrom -import com.lambda.interaction.managers.breaking.BreakManager.processNewBreak -import com.lambda.interaction.managers.breaking.BreakManager.processRequest -import com.lambda.interaction.managers.breaking.BreakManager.rotationRequest -import com.lambda.interaction.managers.breaking.BreakManager.simulateAbandoned -import com.lambda.interaction.managers.breaking.BreakManager.updateBreakProgress -import com.lambda.interaction.managers.breaking.BreakManager.updatePreProcessing -import com.lambda.interaction.managers.breaking.SwapInfo.Companion.getSwapInfo -import com.lambda.interaction.managers.hotbar.HotbarRequest -import com.lambda.interaction.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest -import com.lambda.interaction.managers.interacting.InteractManager -import com.lambda.interaction.managers.rotating.RotationRequest +import com.lambda.interaction.manager.managers.Manager +import com.lambda.interaction.manager.managers.ManagerUtils.isPosBlocked +import com.lambda.interaction.manager.managers.PositionBlocking +import com.lambda.interaction.manager.managers.breaking.BreakInfo.BreakType.Primary +import com.lambda.interaction.manager.managers.breaking.BreakInfo.BreakType.Rebreak +import com.lambda.interaction.manager.managers.breaking.BreakInfo.BreakType.RedundantSecondary +import com.lambda.interaction.manager.managers.breaking.BreakInfo.BreakType.Secondary +import com.lambda.interaction.manager.managers.breaking.BreakManager.abandonedBreak +import com.lambda.interaction.manager.managers.breaking.BreakManager.activeInfos +import com.lambda.interaction.manager.managers.breaking.BreakManager.activeRequest +import com.lambda.interaction.manager.managers.breaking.BreakManager.breakInfos +import com.lambda.interaction.manager.managers.breaking.BreakManager.breaks +import com.lambda.interaction.manager.managers.breaking.BreakManager.canAccept +import com.lambda.interaction.manager.managers.breaking.BreakManager.checkForCancels +import com.lambda.interaction.manager.managers.breaking.BreakManager.handlePreProcessing +import com.lambda.interaction.manager.managers.breaking.BreakManager.hotbarRequest +import com.lambda.interaction.manager.managers.breaking.BreakManager.initNewBreak +import com.lambda.interaction.manager.managers.breaking.BreakManager.maxBreaksThisTick +import com.lambda.interaction.manager.managers.breaking.BreakManager.nullify +import com.lambda.interaction.manager.managers.breaking.BreakManager.populateFrom +import com.lambda.interaction.manager.managers.breaking.BreakManager.processNewBreak +import com.lambda.interaction.manager.managers.breaking.BreakManager.processRequest +import com.lambda.interaction.manager.managers.breaking.BreakManager.rotationRequest +import com.lambda.interaction.manager.managers.breaking.BreakManager.simulateAbandoned +import com.lambda.interaction.manager.managers.breaking.BreakManager.updateBreakProgress +import com.lambda.interaction.manager.managers.breaking.BreakManager.updatePreProcessing +import com.lambda.interaction.manager.managers.breaking.SwapInfo.Companion.getSwapInfo +import com.lambda.interaction.manager.managers.hotbar.HotbarRequest +import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest +import com.lambda.interaction.manager.managers.interacting.InteractManager +import com.lambda.interaction.manager.managers.rotating.RotationRequest import com.lambda.threading.runGameScheduled import com.lambda.threading.runSafe import com.lambda.threading.runSafeAutomated diff --git a/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakRequest.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakRequest.kt similarity index 98% rename from src/main/kotlin/com/lambda/interaction/managers/breaking/BreakRequest.kt rename to src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakRequest.kt index 2e160c1bb..0a284cb3a 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakRequest.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakRequest.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.managers.breaking +package com.lambda.interaction.manager.managers.breaking import com.lambda.context.Automated import com.lambda.context.AutomatedSafeContext @@ -27,7 +27,7 @@ import com.lambda.interaction.construction.simulation.result.BuildResult import com.lambda.interaction.construction.simulation.result.Dependent import com.lambda.interaction.construction.simulation.result.results.BreakResult import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.managers.Request +import com.lambda.interaction.manager.managers.Request import com.lambda.threading.runSafe import com.lambda.util.BlockUtils.blockState import com.lambda.util.BlockUtils.isEmpty diff --git a/src/main/kotlin/com/lambda/interaction/managers/breaking/SwapInfo.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/SwapInfo.kt similarity index 87% rename from src/main/kotlin/com/lambda/interaction/managers/breaking/SwapInfo.kt rename to src/main/kotlin/com/lambda/interaction/manager/managers/breaking/SwapInfo.kt index 390c4caa5..269ef29f5 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/breaking/SwapInfo.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/SwapInfo.kt @@ -15,16 +15,16 @@ * along with this program. If not, see . */ -package com.lambda.interaction.managers.breaking +package com.lambda.interaction.manager.managers.breaking import com.lambda.config.automation.AutomationConfig.Companion.DEFAULT import com.lambda.config.blocks.BreakConfig import com.lambda.context.Automated import com.lambda.context.SafeContext -import com.lambda.interaction.handlers.breaking.RebreakHandler -import com.lambda.interaction.managers.breaking.BreakInfo.BreakType.Primary -import com.lambda.interaction.managers.breaking.BreakInfo.BreakType.Secondary -import com.lambda.interaction.managers.breaking.BreakManager.calcBreakDelta +import com.lambda.interaction.handler.handlers.breaking.RebreakHandler +import com.lambda.interaction.manager.managers.breaking.BreakInfo.BreakType.Primary +import com.lambda.interaction.manager.managers.breaking.BreakInfo.BreakType.Secondary +import com.lambda.interaction.manager.managers.breaking.BreakManager.calcBreakDelta import com.lambda.threading.runSafeAutomated /** diff --git a/src/main/kotlin/com/lambda/interaction/managers/hotbar/HotbarManager.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/hotbar/HotbarManager.kt similarity index 91% rename from src/main/kotlin/com/lambda/interaction/managers/hotbar/HotbarManager.kt rename to src/main/kotlin/com/lambda/interaction/manager/managers/hotbar/HotbarManager.kt index 3feb3275c..76c69b950 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/hotbar/HotbarManager.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/hotbar/HotbarManager.kt @@ -15,19 +15,19 @@ * along with this program. If not, see . */ -package com.lambda.interaction.managers.hotbar +package com.lambda.interaction.manager.managers.hotbar import com.lambda.config.blocks.HotbarConfig.SwapMode import com.lambda.context.AutomatedSafeContext import com.lambda.context.SafeContext 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.hotbar.HotbarManager.activeRequest -import com.lambda.interaction.managers.hotbar.HotbarManager.activeSlot -import com.lambda.interaction.managers.hotbar.HotbarManager.checkResetSwap -import com.lambda.interaction.managers.hotbar.HotbarManager.setActiveRequest -import com.lambda.interaction.managers.hotbar.HotbarManager.setActiveSlot +import com.lambda.interaction.manager.Manager +import com.lambda.interaction.manager.managers.hotbar.HotbarManager.activeRequest +import com.lambda.interaction.manager.managers.hotbar.HotbarManager.activeSlot +import com.lambda.interaction.manager.managers.hotbar.HotbarManager.checkResetSwap +import com.lambda.interaction.manager.managers.hotbar.HotbarManager.setActiveRequest +import com.lambda.interaction.manager.managers.hotbar.HotbarManager.setActiveSlot import com.lambda.threading.runSafe import net.minecraft.item.ItemStack diff --git a/src/main/kotlin/com/lambda/interaction/managers/hotbar/HotbarRequest.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/hotbar/HotbarRequest.kt similarity index 93% rename from src/main/kotlin/com/lambda/interaction/managers/hotbar/HotbarRequest.kt rename to src/main/kotlin/com/lambda/interaction/manager/managers/hotbar/HotbarRequest.kt index f16a1d6f3..682ec2993 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/hotbar/HotbarRequest.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/hotbar/HotbarRequest.kt @@ -15,10 +15,11 @@ * along with this program. If not, see . */ -package com.lambda.interaction.managers.hotbar +package com.lambda.interaction.manager.managers.hotbar import com.lambda.context.Automated -import com.lambda.interaction.managers.Request +import com.lambda.interaction.manager.Request +import com.lambda.interaction.manager.managers.Request class HotbarRequest( val slot: Int, diff --git a/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractInfo.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractInfo.kt similarity index 91% rename from src/main/kotlin/com/lambda/interaction/managers/interacting/InteractInfo.kt rename to src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractInfo.kt index b4e813267..c5efd746c 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractInfo.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractInfo.kt @@ -15,13 +15,13 @@ * along with this program. If not, see . */ -package com.lambda.interaction.managers.interacting +package com.lambda.interaction.manager.managers.interacting import com.lambda.config.blocks.InteractConfig import com.lambda.context.SafeContext import com.lambda.interaction.construction.simulation.context.BuildContext import com.lambda.interaction.construction.simulation.context.InteractContext -import com.lambda.interaction.managers.ActionInfo +import com.lambda.interaction.manager.managers.ActionInfo import net.minecraft.util.math.BlockPos data class InteractInfo( diff --git a/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractManager.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractManager.kt similarity index 91% rename from src/main/kotlin/com/lambda/interaction/managers/interacting/InteractManager.kt rename to src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractManager.kt index 56fa03e4e..2b7983e23 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractManager.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractManager.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.managers.interacting +package com.lambda.interaction.manager.managers.interacting import com.lambda.config.blocks.InteractConfig import com.lambda.config.blocks.InteractConfig.AirPlaceMode @@ -29,22 +29,22 @@ 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.interaction.construction.simulation.context.InteractContext -import com.lambda.interaction.handlers.interacting.InteractedBlockHandler.pendingActions -import com.lambda.interaction.handlers.interacting.InteractedBlockHandler.setPendingConfigs -import com.lambda.interaction.handlers.interacting.InteractedBlockHandler.startPending -import com.lambda.interaction.handlers.packet.PacketLimitHandler -import com.lambda.interaction.handlers.packet.PacketType -import com.lambda.interaction.managers.Manager -import com.lambda.interaction.managers.ManagerUtils.isPosBlocked -import com.lambda.interaction.managers.PositionBlocking -import com.lambda.interaction.managers.breaking.BreakManager -import com.lambda.interaction.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest -import com.lambda.interaction.managers.interacting.InteractManager.activeRequest -import com.lambda.interaction.managers.interacting.InteractManager.maxInteractionsThisTick -import com.lambda.interaction.managers.interacting.InteractManager.populateFrom -import com.lambda.interaction.managers.interacting.InteractManager.potentialInteractions -import com.lambda.interaction.managers.interacting.InteractManager.processRequest -import com.lambda.interaction.managers.inventory.InvRequestBuilder.Companion.inventoryRequest +import com.lambda.interaction.handler.handlers.interacting.InteractedBlockHandler.pendingActions +import com.lambda.interaction.handler.handlers.interacting.InteractedBlockHandler.setPendingConfigs +import com.lambda.interaction.handler.handlers.interacting.InteractedBlockHandler.startPending +import com.lambda.interaction.handler.handlers.packet.PacketLimitHandler +import com.lambda.interaction.handler.handlers.packet.PacketType +import com.lambda.interaction.manager.managers.Manager +import com.lambda.interaction.manager.managers.ManagerUtils.isPosBlocked +import com.lambda.interaction.manager.managers.PositionBlocking +import com.lambda.interaction.manager.managers.breaking.BreakManager +import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest +import com.lambda.interaction.manager.managers.interacting.InteractManager.activeRequest +import com.lambda.interaction.manager.managers.interacting.InteractManager.maxInteractionsThisTick +import com.lambda.interaction.manager.managers.interacting.InteractManager.populateFrom +import com.lambda.interaction.manager.managers.interacting.InteractManager.potentialInteractions +import com.lambda.interaction.manager.managers.interacting.InteractManager.processRequest +import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.modules.world.AutoSign.signWriteDelay import com.lambda.threading.runConcurrent import com.lambda.threading.runSafeAutomated diff --git a/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractRequest.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractRequest.kt similarity index 97% rename from src/main/kotlin/com/lambda/interaction/managers/interacting/InteractRequest.kt rename to src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractRequest.kt index 3132cda20..4aeb87bef 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractRequest.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractRequest.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.managers.interacting +package com.lambda.interaction.manager.managers.interacting import com.lambda.context.Automated import com.lambda.context.SafeContext @@ -24,7 +24,7 @@ import com.lambda.interaction.construction.simulation.context.InteractContext import com.lambda.interaction.construction.simulation.result.BuildResult import com.lambda.interaction.construction.simulation.result.Dependent import com.lambda.interaction.construction.simulation.result.results.InteractResult -import com.lambda.interaction.managers.Request +import com.lambda.interaction.manager.managers.Request import com.lambda.threading.runSafe import com.lambda.util.BlockUtils.blockState import com.lambda.util.BlockUtils.matches diff --git a/src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryAction.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryAction.kt similarity index 95% rename from src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryAction.kt rename to src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryAction.kt index 33f4559a6..ebf22474f 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryAction.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryAction.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.managers.inventory +package com.lambda.interaction.manager.managers.inventory import com.lambda.context.SafeContext diff --git a/src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryManager.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryManager.kt similarity index 93% rename from src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryManager.kt rename to src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryManager.kt index f89841caf..ee90518eb 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryManager.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryManager.kt @@ -15,21 +15,21 @@ * along with this program. If not, see . */ -package com.lambda.interaction.managers.inventory +package com.lambda.interaction.manager.managers.inventory import com.lambda.context.AutomatedSafeContext import com.lambda.context.SafeContext import com.lambda.event.events.PacketEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.handlers.packet.PacketLimitHandler.canSendPackets -import com.lambda.interaction.handlers.packet.PacketLimitHandler.sentPackets -import com.lambda.interaction.handlers.packet.PacketType -import com.lambda.interaction.managers.Manager -import com.lambda.interaction.managers.inventory.InventoryManager.actions -import com.lambda.interaction.managers.inventory.InventoryManager.activeRequest -import com.lambda.interaction.managers.inventory.InventoryManager.alteredSlots -import com.lambda.interaction.managers.inventory.InventoryManager.processActiveRequest +import com.lambda.interaction.handler.handlers.packet.PacketLimitHandler.canSendPackets +import com.lambda.interaction.handler.handlers.packet.PacketLimitHandler.sentPackets +import com.lambda.interaction.handler.handlers.packet.PacketType +import com.lambda.interaction.manager.Manager +import com.lambda.interaction.manager.managers.inventory.InventoryManager.actions +import com.lambda.interaction.manager.managers.inventory.InventoryManager.activeRequest +import com.lambda.interaction.manager.managers.inventory.InventoryManager.alteredSlots +import com.lambda.interaction.manager.managers.inventory.InventoryManager.processActiveRequest import com.lambda.module.modules.client.Client import com.lambda.threading.runSafe import com.lambda.threading.runSafeAutomated diff --git a/src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryRequest.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryRequest.kt similarity index 97% rename from src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryRequest.kt rename to src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryRequest.kt index 5e3c84e92..82d81e619 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryRequest.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryRequest.kt @@ -15,11 +15,12 @@ * along with this program. If not, see . */ -package com.lambda.interaction.managers.inventory +package com.lambda.interaction.manager.managers.inventory import com.lambda.context.Automated import com.lambda.context.SafeContext -import com.lambda.interaction.managers.Request +import com.lambda.interaction.manager.Request +import com.lambda.interaction.manager.managers.Request import com.lambda.util.PacketUtils.sendPacket import com.lambda.util.player.SlotUtils.clickSlot import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap diff --git a/src/main/kotlin/com/lambda/interaction/managers/rotating/Rotation.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/rotating/Rotation.kt similarity index 99% rename from src/main/kotlin/com/lambda/interaction/managers/rotating/Rotation.kt rename to src/main/kotlin/com/lambda/interaction/manager/managers/rotating/Rotation.kt index c464fb7c9..fa530996b 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/rotating/Rotation.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/rotating/Rotation.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.managers.rotating +package com.lambda.interaction.manager.managers.rotating import com.lambda.threading.runSafe import com.lambda.util.math.MathUtils.toDegree diff --git a/src/main/kotlin/com/lambda/interaction/managers/rotating/RotationManager.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/rotating/RotationManager.kt similarity index 95% rename from src/main/kotlin/com/lambda/interaction/managers/rotating/RotationManager.kt rename to src/main/kotlin/com/lambda/interaction/manager/managers/rotating/RotationManager.kt index 93851e71f..52d946aa5 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/rotating/RotationManager.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/rotating/RotationManager.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.managers.rotating +package com.lambda.interaction.manager.managers.rotating import com.lambda.Lambda.mc import com.lambda.context.AutomatedSafeContext @@ -28,12 +28,12 @@ import com.lambda.event.events.TickEvent import com.lambda.event.events.TickEvent.Companion.ALL_STAGES import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.event.listener.UnsafeListener.Companion.listenUnsafe -import com.lambda.interaction.managers.Manager -import com.lambda.interaction.managers.rotating.Rotation.Companion.slerpPitch -import com.lambda.interaction.managers.rotating.Rotation.Companion.slerpYaw -import com.lambda.interaction.managers.rotating.RotationManager.activeRotation -import com.lambda.interaction.managers.rotating.RotationManager.serverRotation -import com.lambda.interaction.managers.rotating.RotationManager.updateActiveRotation +import com.lambda.interaction.manager.Manager +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.slerpPitch +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.slerpYaw +import com.lambda.interaction.manager.managers.rotating.RotationManager.activeRotation +import com.lambda.interaction.manager.managers.rotating.RotationManager.serverRotation +import com.lambda.interaction.manager.managers.rotating.RotationManager.updateActiveRotation import com.lambda.threading.runGameScheduled import com.lambda.threading.runSafe import com.lambda.util.extension.rotation diff --git a/src/main/kotlin/com/lambda/interaction/managers/rotating/RotationMode.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/rotating/RotationMode.kt similarity index 96% rename from src/main/kotlin/com/lambda/interaction/managers/rotating/RotationMode.kt rename to src/main/kotlin/com/lambda/interaction/manager/managers/rotating/RotationMode.kt index 5d0cae02e..0ce450f52 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/rotating/RotationMode.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/rotating/RotationMode.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.managers.rotating +package com.lambda.interaction.manager.managers.rotating import com.lambda.util.Describable import com.lambda.util.NamedEnum diff --git a/src/main/kotlin/com/lambda/interaction/managers/rotating/RotationRequest.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/rotating/RotationRequest.kt similarity index 90% rename from src/main/kotlin/com/lambda/interaction/managers/rotating/RotationRequest.kt rename to src/main/kotlin/com/lambda/interaction/manager/managers/rotating/RotationRequest.kt index 518c98465..12c43a884 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/rotating/RotationRequest.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/rotating/RotationRequest.kt @@ -15,18 +15,18 @@ * along with this program. If not, see . */ -package com.lambda.interaction.managers.rotating +package com.lambda.interaction.manager.managers.rotating import com.lambda.context.Automated import com.lambda.context.SafeContext -import com.lambda.interaction.handlers.BaritoneHandler -import com.lambda.interaction.managers.Request -import com.lambda.interaction.managers.rotating.IRotationRequest.Full -import com.lambda.interaction.managers.rotating.IRotationRequest.Pitch -import com.lambda.interaction.managers.rotating.IRotationRequest.Yaw -import com.lambda.interaction.managers.rotating.Rotation.Companion.dist -import com.lambda.interaction.managers.rotating.Rotation.Companion.rotation -import com.lambda.interaction.managers.rotating.Rotation.Companion.wrap +import com.lambda.interaction.handler.handlers.BaritoneHandler +import com.lambda.interaction.manager.Request +import com.lambda.interaction.manager.managers.rotating.IRotationRequest.Full +import com.lambda.interaction.manager.managers.rotating.IRotationRequest.Pitch +import com.lambda.interaction.manager.managers.rotating.IRotationRequest.Yaw +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.dist +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotation +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.wrap import com.lambda.threading.runSafe import com.lambda.util.collections.UpdatableLazy import com.lambda.util.collections.updatableLazy diff --git a/src/main/kotlin/com/lambda/module/hud/Baritone.kt b/src/main/kotlin/com/lambda/module/hud/Baritone.kt index 601602b71..438671ece 100644 --- a/src/main/kotlin/com/lambda/module/hud/Baritone.kt +++ b/src/main/kotlin/com/lambda/module/hud/Baritone.kt @@ -19,7 +19,7 @@ package com.lambda.module.hud import com.lambda.gui.dsl.ImGuiBuilder import com.lambda.interaction.construction.simulation.BuildGoal -import com.lambda.interaction.handlers.BaritoneHandler +import com.lambda.interaction.handler.handlers.BaritoneHandler import com.lambda.module.HudModule import com.lambda.module.tag.ModuleTag diff --git a/src/main/kotlin/com/lambda/module/modules/chat/AntiSpam.kt b/src/main/kotlin/com/lambda/module/modules/chat/AntiSpam.kt index 9f2199c93..25c6dab7f 100644 --- a/src/main/kotlin/com/lambda/module/modules/chat/AntiSpam.kt +++ b/src/main/kotlin/com/lambda/module/modules/chat/AntiSpam.kt @@ -22,7 +22,7 @@ import com.lambda.config.ConfigBlock import com.lambda.config.Group import com.lambda.event.events.ChatEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.handlers.FriendHandler +import com.lambda.interaction.handler.handlers.FriendHandler import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.ChatUtils.addresses diff --git a/src/main/kotlin/com/lambda/module/modules/chat/CustomChat.kt b/src/main/kotlin/com/lambda/module/modules/chat/CustomChat.kt index 2c68b2abd..3357ecf63 100644 --- a/src/main/kotlin/com/lambda/module/modules/chat/CustomChat.kt +++ b/src/main/kotlin/com/lambda/module/modules/chat/CustomChat.kt @@ -21,7 +21,7 @@ import com.google.common.collect.Comparators.min import com.lambda.command.CommandRegistry.prefix import com.lambda.event.events.ChatEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.handlers.BaritoneHandler +import com.lambda.interaction.handler.handlers.BaritoneHandler import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.NamedEnum diff --git a/src/main/kotlin/com/lambda/module/modules/chat/FancyChat.kt b/src/main/kotlin/com/lambda/module/modules/chat/FancyChat.kt index b25e12a60..c9220aeb6 100644 --- a/src/main/kotlin/com/lambda/module/modules/chat/FancyChat.kt +++ b/src/main/kotlin/com/lambda/module/modules/chat/FancyChat.kt @@ -20,7 +20,7 @@ package com.lambda.module.modules.chat import com.lambda.command.CommandRegistry.prefix import com.lambda.event.events.ChatEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.handlers.BaritoneHandler +import com.lambda.interaction.handler.handlers.BaritoneHandler import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.ChatUtils.toBlue diff --git a/src/main/kotlin/com/lambda/module/modules/chat/FriendHighlight.kt b/src/main/kotlin/com/lambda/module/modules/chat/FriendHighlight.kt index 72d48f3b2..604d03a0f 100644 --- a/src/main/kotlin/com/lambda/module/modules/chat/FriendHighlight.kt +++ b/src/main/kotlin/com/lambda/module/modules/chat/FriendHighlight.kt @@ -20,7 +20,7 @@ package com.lambda.module.modules.chat import com.lambda.config.entries.Setting.Companion.onValueChange import com.lambda.event.events.ChatEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.handlers.FriendHandler +import com.lambda.interaction.handler.handlers.FriendHandler import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.sound.SoundHandler.playSound diff --git a/src/main/kotlin/com/lambda/module/modules/combat/AutoArmor.kt b/src/main/kotlin/com/lambda/module/modules/combat/AutoArmor.kt index aa0a248a2..1fffa8804 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/AutoArmor.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/AutoArmor.kt @@ -27,7 +27,7 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.inventory.container.containers.ArmorContainer import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer -import com.lambda.interaction.managers.inventory.InvRequestBuilder.Companion.inventoryRequest +import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.EnchantmentUtils.getEnchantment diff --git a/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt b/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt index 3ca0ee386..00d00c49f 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt @@ -23,9 +23,9 @@ import com.lambda.config.withEdits import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.handlers.FriendHandler +import com.lambda.interaction.handler.handlers.FriendHandler import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.select -import com.lambda.interaction.managers.inventory.InvRequestBuilder.Companion.inventoryRequest +import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.combat.CombatUtils.hasDeadlyCrystal diff --git a/src/main/kotlin/com/lambda/module/modules/combat/Criticals.kt b/src/main/kotlin/com/lambda/module/modules/combat/Criticals.kt index a4c848b0d..bc7822c21 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/Criticals.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/Criticals.kt @@ -20,8 +20,8 @@ package com.lambda.module.modules.combat import com.lambda.context.SafeContext import com.lambda.event.events.PlayerEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.rotating.Rotation -import com.lambda.interaction.managers.rotating.Rotation.Companion.rotationTo +import com.lambda.interaction.manager.managers.rotating.Rotation +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotationTo import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.PacketUtils.sendPacket diff --git a/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt b/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt index d20b8b8a8..f9d86b24e 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt @@ -27,14 +27,14 @@ import com.lambda.context.SafeContext import com.lambda.event.events.EntityEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.handlers.ContainerHandler.transfer +import com.lambda.interaction.handler.handlers.ContainerHandler.transfer import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.interaction.inventory.container.containers.OffHandContainer -import com.lambda.interaction.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest -import com.lambda.interaction.managers.rotating.Rotation.Companion.rotationTo -import com.lambda.interaction.managers.rotating.RotationManager -import com.lambda.interaction.managers.rotating.RotationRequestBuilder.Companion.rotationRequest +import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotationTo +import com.lambda.interaction.manager.managers.rotating.RotationManager +import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.threading.runSafe diff --git a/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt b/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt index 8ed274c82..a470a32fd 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt @@ -30,8 +30,8 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack import com.lambda.interaction.inventory.container.containers.HotbarContainer -import com.lambda.interaction.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest -import com.lambda.interaction.managers.rotating.RotationRequestBuilder.Companion.rotationRequest +import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest +import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.threading.runSafeAutomated diff --git a/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt b/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt index 79848af5d..9758e1a2f 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt @@ -24,7 +24,7 @@ import com.lambda.config.withEdits import com.lambda.context.SafeContext import com.lambda.interaction.construction.blueprint.TickingBlueprint.Companion.tickingBlueprint import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.handlers.FriendHandler.isFriend +import com.lambda.interaction.handler.handlers.FriendHandler.isFriend import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer import com.lambda.module.Module import com.lambda.module.tag.ModuleTag diff --git a/src/main/kotlin/com/lambda/module/modules/combat/autodisconnect/AutoDisconnect.kt b/src/main/kotlin/com/lambda/module/modules/combat/autodisconnect/AutoDisconnect.kt index c479ac4b9..a72292411 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/autodisconnect/AutoDisconnect.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/autodisconnect/AutoDisconnect.kt @@ -26,7 +26,7 @@ import com.lambda.event.events.TickEvent import com.lambda.event.events.WorldEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.gui.components.ClickGuiLayout -import com.lambda.interaction.handlers.FriendHandler +import com.lambda.interaction.handler.handlers.FriendHandler import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.sound.SoundHandler.playSound @@ -41,10 +41,10 @@ import com.lambda.util.combat.CombatUtils.crystalDamage import com.lambda.util.combat.DamageUtils.isFallDeadly import com.lambda.util.extension.fullHealth import com.lambda.util.extension.tickDeltaF -import com.lambda.util.item.ItemStackUtils.bundleContents -import com.lambda.util.item.ItemStackUtils.shulkerBoxContents -import com.lambda.util.player.PlayerUtils.isIn2b2tQueue +import com.lambda.util.item.ItemStackUtils.bundleStacks +import com.lambda.util.item.ItemStackUtils.shulkerBoxStacks import com.lambda.util.player.MovementUtils.moveDelta +import com.lambda.util.player.PlayerUtils.isIn2b2tQueue import com.lambda.util.player.SlotUtils.allStacks import com.lambda.util.player.SlotUtils.armorSlots import com.lambda.util.player.SlotUtils.hotbarStacks @@ -706,7 +706,7 @@ object AutoDisconnect : Module( private fun itemRow(stack: ItemStack, label: String? = null, selected: Boolean = false): DetailSection { val header = itemHeader(stack, label, selected) val enchantments = stack.forEachEnchantment { entry, level -> Enchantment.getName(entry, level) } - val contents = (stack.shulkerBoxContents + stack.bundleContents).filter { !it.isEmpty } + val contents = (stack.shulkerBoxStacks + stack.bundleStacks).filter { !it.isEmpty } if (enchantments.isEmpty() && contents.isEmpty()) { return DetailSection.TextSection(header) diff --git a/src/main/kotlin/com/lambda/module/modules/debug/BaritoneTest.kt b/src/main/kotlin/com/lambda/module/modules/debug/BaritoneTest.kt index 0edf97ccc..759fc8253 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/BaritoneTest.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/BaritoneTest.kt @@ -20,7 +20,7 @@ package com.lambda.module.modules.debug import baritone.api.pathing.goals.GoalXZ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.handlers.BaritoneHandler +import com.lambda.interaction.handler.handlers.BaritoneHandler import com.lambda.module.Module import com.lambda.module.tag.ModuleTag diff --git a/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt b/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt index 898e5b046..fc2597653 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt @@ -23,7 +23,7 @@ import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.select import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.task.RootTask.run -import com.lambda.task.tasks.AcquireStack.Companion.acquire +import com.lambda.task.tasks.AcquireStackTask.Companion.acquireStack import net.minecraft.item.Items @Suppress("unused") @@ -38,7 +38,7 @@ object ContainerTest : Module( } onEnable { - acquire { + acquireStack { Items.OBSIDIAN.select() }.run() } diff --git a/src/main/kotlin/com/lambda/module/modules/debug/RotationTest.kt b/src/main/kotlin/com/lambda/module/modules/debug/RotationTest.kt index aeaeb7a3f..1648c632e 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/RotationTest.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/RotationTest.kt @@ -19,7 +19,7 @@ package com.lambda.module.modules.debug import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.rotating.RotationRequestBuilder.Companion.rotationRequest +import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.player.RotationUtils.lookAt diff --git a/src/main/kotlin/com/lambda/module/modules/debug/SilentSwap.kt b/src/main/kotlin/com/lambda/module/modules/debug/SilentSwap.kt index 17a70165e..f13616f8c 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/SilentSwap.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/SilentSwap.kt @@ -22,7 +22,7 @@ import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomat import com.lambda.config.withEdits import com.lambda.event.events.PlayerEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest +import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.CommunicationUtils.info diff --git a/src/main/kotlin/com/lambda/module/modules/movement/AutoSpiral.kt b/src/main/kotlin/com/lambda/module/modules/movement/AutoSpiral.kt index 7548a0e24..a026b7fa7 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/AutoSpiral.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/AutoSpiral.kt @@ -21,8 +21,8 @@ import baritone.api.pathing.goals.GoalXZ import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.handlers.BaritoneHandler -import com.lambda.interaction.managers.rotating.RotationRequestBuilder.Companion.rotationRequest +import com.lambda.interaction.handler.handlers.BaritoneHandler +import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.BlockPosIterators diff --git a/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt b/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt index 05118f58b..177d6d9f6 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt @@ -28,14 +28,14 @@ import com.lambda.config.withEdits import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.handlers.GlideHandler +import com.lambda.interaction.handler.handlers.GlideHandler import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.select import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.interaction.inventory.container.containers.InventoryContainer -import com.lambda.interaction.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest -import com.lambda.interaction.managers.inventory.InvRequestBuilder.Companion.inventoryRequest +import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest +import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.threading.runSafe diff --git a/src/main/kotlin/com/lambda/module/modules/movement/ElytraAltitudeControl.kt b/src/main/kotlin/com/lambda/module/modules/movement/ElytraAltitudeControl.kt index 1416e7428..92282461c 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/ElytraAltitudeControl.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/ElytraAltitudeControl.kt @@ -23,7 +23,7 @@ import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomat import com.lambda.config.withEdits import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.rotating.RotationRequestBuilder.Companion.rotationRequest +import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module import com.lambda.module.modules.movement.BetterFirework.startFirework import com.lambda.module.tag.ModuleTag diff --git a/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt b/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt index 4e3fa5740..aba6b353e 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt @@ -26,7 +26,7 @@ import com.lambda.event.events.ClientEvent import com.lambda.event.events.MovementEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.rotating.RotationRequestBuilder.Companion.rotationRequest +import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.NamedEnum diff --git a/src/main/kotlin/com/lambda/module/modules/movement/TargetStrafe.kt b/src/main/kotlin/com/lambda/module/modules/movement/TargetStrafe.kt index 57ece652e..91b65ab2a 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/TargetStrafe.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/TargetStrafe.kt @@ -20,7 +20,7 @@ package com.lambda.module.modules.movement import com.lambda.event.events.RotationEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.rotating.Rotation.Companion.rotationTo +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotationTo import com.lambda.module.Module import com.lambda.module.modules.combat.KillAura import com.lambda.module.tag.ModuleTag diff --git a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt index edc0e2bc0..f56939f61 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt @@ -24,13 +24,13 @@ import com.lambda.event.Muteable import com.lambda.event.events.PacketEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.handlers.GlideHandler.ELYTRA_SELECTION +import com.lambda.interaction.handler.handlers.GlideHandler.ELYTRA_SELECTION import com.lambda.interaction.inventory.container.containers.ArmorContainer import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer -import com.lambda.interaction.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest -import com.lambda.interaction.managers.inventory.InvRequestBuilder -import com.lambda.interaction.managers.inventory.InvRequestBuilder.Companion.inventoryRequest -import com.lambda.interaction.managers.inventory.InventoryManager +import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest +import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder +import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest +import com.lambda.interaction.manager.managers.inventory.InventoryManager import com.lambda.module.modules.movement.elytrafly.ElytraFly.FlyMode import com.lambda.module.modules.movement.elytrafly.ElytraFly.fakeFly import com.lambda.threading.runSafe diff --git a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ObstaclePassingMode.kt b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ObstaclePassingMode.kt index 3159c59b8..04c828d87 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ObstaclePassingMode.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ObstaclePassingMode.kt @@ -21,7 +21,7 @@ import baritone.api.pathing.goals.GoalGetToBlock import com.lambda.config.Config import com.lambda.config.ConfigBlock import com.lambda.context.SafeContext -import com.lambda.interaction.handlers.BaritoneHandler +import com.lambda.interaction.handler.handlers.BaritoneHandler import com.lambda.module.hud.Speedometer import com.lambda.module.modules.movement.elytrafly.ElytraFly.FlyMode import com.lambda.threading.runGameScheduled diff --git a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/BounceElytraFly.kt b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/BounceElytraFly.kt index 1b33c12c7..67e4b7544 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/BounceElytraFly.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/BounceElytraFly.kt @@ -25,10 +25,10 @@ import com.lambda.event.events.MovementEvent import com.lambda.event.events.PacketEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.handlers.BaritoneHandler -import com.lambda.interaction.handlers.GlideHandler -import com.lambda.interaction.managers.rotating.RotationManager -import com.lambda.interaction.managers.rotating.RotationRequestBuilder.Companion.rotationRequest +import com.lambda.interaction.handler.handlers.BaritoneHandler +import com.lambda.interaction.handler.handlers.GlideHandler +import com.lambda.interaction.manager.managers.rotating.RotationManager +import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.hud.Speedometer import com.lambda.module.modules.movement.elytrafly.ElytraFly.FlyMode import com.lambda.module.modules.movement.elytrafly.ElytraFly.fakeFly diff --git a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/GrimControlElytraFly.kt b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/GrimControlElytraFly.kt index 7c3875b0b..bd92459d2 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/GrimControlElytraFly.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/GrimControlElytraFly.kt @@ -25,8 +25,8 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.select import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.interaction.inventory.container.containers.InventoryContainer -import com.lambda.interaction.managers.rotating.RotationManager -import com.lambda.interaction.managers.rotating.RotationRequestBuilder.Companion.rotationRequest +import com.lambda.interaction.manager.managers.rotating.RotationManager +import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.modules.movement.BetterFirework.startFirework import com.lambda.module.modules.movement.elytrafly.ElytraFly.FlyMode import com.lambda.module.modules.movement.elytrafly.ElytraFlyMode diff --git a/src/main/kotlin/com/lambda/module/modules/player/AntiAim.kt b/src/main/kotlin/com/lambda/module/modules/player/AntiAim.kt index b078fcb14..645239f00 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/AntiAim.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/AntiAim.kt @@ -21,8 +21,8 @@ import com.lambda.config.entries.Setting.Companion.onValueChange import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.rotating.Rotation.Companion.rotationTo -import com.lambda.interaction.managers.rotating.RotationRequestBuilder.Companion.rotationRequest +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotationTo +import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.math.distSq diff --git a/src/main/kotlin/com/lambda/module/modules/player/AutoElytraSwap.kt b/src/main/kotlin/com/lambda/module/modules/player/AutoElytraSwap.kt index 407de5e5d..92fc44463 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/AutoElytraSwap.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/AutoElytraSwap.kt @@ -18,13 +18,13 @@ package com.lambda.module.modules.player import com.lambda.context.SafeContext -import com.lambda.interaction.handlers.GlideHandler.CHESTPLATE_SELECTION -import com.lambda.interaction.handlers.GlideHandler.ELYTRA_SELECTION -import com.lambda.interaction.handlers.GlideHandler.manuallySwapped -import com.lambda.interaction.handlers.GlideHandler.swapped +import com.lambda.interaction.handler.handlers.GlideHandler.CHESTPLATE_SELECTION +import com.lambda.interaction.handler.handlers.GlideHandler.ELYTRA_SELECTION +import com.lambda.interaction.handler.handlers.GlideHandler.manuallySwapped +import com.lambda.interaction.handler.handlers.GlideHandler.swapped import com.lambda.interaction.inventory.container.containers.ArmorContainer import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer -import com.lambda.interaction.managers.inventory.InvRequestBuilder.Companion.inventoryRequest +import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module import com.lambda.module.modules.combat.AutoArmor import com.lambda.module.tag.ModuleTag diff --git a/src/main/kotlin/com/lambda/module/modules/player/ClickFriend.kt b/src/main/kotlin/com/lambda/module/modules/player/ClickFriend.kt index 5bbfb4bf3..402260cd5 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/ClickFriend.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/ClickFriend.kt @@ -20,10 +20,10 @@ package com.lambda.module.modules.player import com.lambda.config.settings.complex.Bind import com.lambda.config.settings.complex.KeybindSetting.Companion.onPress import com.lambda.context.SafeContext -import com.lambda.interaction.handlers.FriendHandler -import com.lambda.interaction.handlers.FriendHandler.befriend -import com.lambda.interaction.handlers.FriendHandler.isFriend -import com.lambda.interaction.handlers.FriendHandler.unfriend +import com.lambda.interaction.handler.handlers.FriendHandler +import com.lambda.interaction.handler.handlers.FriendHandler.befriend +import com.lambda.interaction.handler.handlers.FriendHandler.isFriend +import com.lambda.interaction.handler.handlers.FriendHandler.unfriend import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.CommunicationUtils.info diff --git a/src/main/kotlin/com/lambda/module/modules/player/FastBreak.kt b/src/main/kotlin/com/lambda/module/modules/player/FastBreak.kt index 4b0ce1787..ab1265f27 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/FastBreak.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/FastBreak.kt @@ -27,7 +27,7 @@ import com.lambda.event.events.PlayerEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.construction.simulation.context.BuildContext -import com.lambda.interaction.managers.breaking.BreakRequestBuilder.Companion.breakRequest +import com.lambda.interaction.manager.managers.breaking.BreakRequestBuilder.Companion.breakRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.threading.runSafeAutomated diff --git a/src/main/kotlin/com/lambda/module/modules/player/InventoryMove.kt b/src/main/kotlin/com/lambda/module/modules/player/InventoryMove.kt index 833f0a7db..b65300f3b 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/InventoryMove.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/InventoryMove.kt @@ -22,8 +22,8 @@ import com.lambda.config.blocks.RotationConfig import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.gui.LambdaScreen -import com.lambda.interaction.managers.rotating.RotationMode -import com.lambda.interaction.managers.rotating.RotationRequestBuilder.Companion.rotationRequest +import com.lambda.interaction.manager.managers.rotating.RotationMode +import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.InputUtils diff --git a/src/main/kotlin/com/lambda/module/modules/player/InventoryResync.kt b/src/main/kotlin/com/lambda/module/modules/player/InventoryResync.kt index ba421be95..59d0909c7 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/InventoryResync.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/InventoryResync.kt @@ -19,7 +19,7 @@ package com.lambda.module.modules.player import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.inventory.InvRequestBuilder.Companion.inventoryRequest +import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.TickTimer diff --git a/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt b/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt index a9abb1ef8..ace2216cc 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt @@ -27,7 +27,7 @@ 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.breakAndCollectBlock +import com.lambda.task.tasks.BuildTask.Companion.breakAndCollect import com.lambda.task.tasks.OpenContainerTask import com.lambda.task.tasks.PlaceContainerTask import com.lambda.util.item.ItemUtils.shulkerBoxes @@ -72,7 +72,7 @@ object InventoryTweaks : Module( if (event.screenHandler != lastOpenScreen) return@listen lastOpenScreen = null placedPos?.let { - lastBreak = breakAndCollectBlock(it).run() + lastBreak = breakAndCollect(it).run() placedPos = null } } diff --git a/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt b/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt index 1d8fd9e99..4653b5e59 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt @@ -37,7 +37,7 @@ import com.lambda.interaction.construction.simulation.context.BreakContext import com.lambda.interaction.construction.simulation.context.BuildContext import com.lambda.interaction.construction.simulation.result.results.BreakResult import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.managers.breaking.BreakRequestBuilder.Companion.breakRequest +import com.lambda.interaction.manager.managers.breaking.BreakRequestBuilder.Companion.breakRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.threading.runSafe diff --git a/src/main/kotlin/com/lambda/module/modules/player/Replay.kt b/src/main/kotlin/com/lambda/module/modules/player/Replay.kt index d0edbc647..9ebf59a3b 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/Replay.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/Replay.kt @@ -34,10 +34,10 @@ import com.lambda.event.events.MovementEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.gui.components.ClickGuiLayout -import com.lambda.interaction.handlers.TimerHandler -import com.lambda.interaction.managers.rotating.Rotation -import com.lambda.interaction.managers.rotating.RotationMode -import com.lambda.interaction.managers.rotating.RotationRequestBuilder.Companion.rotationRequest +import com.lambda.interaction.handler.handlers.TimerHandler +import com.lambda.interaction.manager.managers.rotating.Rotation +import com.lambda.interaction.manager.managers.rotating.RotationMode +import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module import com.lambda.module.modules.player.Replay.InputAction.Companion.toAction import com.lambda.module.tag.ModuleTag diff --git a/src/main/kotlin/com/lambda/module/modules/player/RotationLock.kt b/src/main/kotlin/com/lambda/module/modules/player/RotationLock.kt index 307251bc7..843bf0061 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/RotationLock.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/RotationLock.kt @@ -23,8 +23,8 @@ import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomat import com.lambda.config.withEdits import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.rotating.RotationMode -import com.lambda.interaction.managers.rotating.RotationRequestBuilder.Companion.rotationRequest +import com.lambda.interaction.manager.managers.rotating.RotationMode +import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import kotlin.math.roundToInt diff --git a/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt b/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt index 3a2ba080c..b1a90ca64 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt @@ -26,7 +26,7 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.interaction.inventory.container.containers.InventoryContainer -import com.lambda.interaction.managers.inventory.InvRequestBuilder.Companion.inventoryRequest +import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.item.ItemStackUtils.slotId diff --git a/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt b/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt index 6df4bcb7d..c11621cc4 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt @@ -25,7 +25,7 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.interaction.inventory.container.containers.InventoryContainer -import com.lambda.interaction.managers.inventory.InvRequestBuilder.Companion.inventoryRequest +import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.EnchantmentUtils.forEachEnchantment diff --git a/src/main/kotlin/com/lambda/module/modules/render/FreeLook.kt b/src/main/kotlin/com/lambda/module/modules/render/FreeLook.kt index a67af3e35..5541edce9 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/FreeLook.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/FreeLook.kt @@ -20,8 +20,8 @@ package com.lambda.module.modules.render import com.lambda.Lambda.mc import com.lambda.event.events.PlayerEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.rotating.Rotation -import com.lambda.interaction.managers.rotating.RotationManager +import com.lambda.interaction.manager.managers.rotating.Rotation +import com.lambda.interaction.manager.managers.rotating.RotationManager import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.extension.rotation diff --git a/src/main/kotlin/com/lambda/module/modules/render/Freecam.kt b/src/main/kotlin/com/lambda/module/modules/render/Freecam.kt index 1ff346388..d6f1f05fb 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/Freecam.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/Freecam.kt @@ -29,9 +29,9 @@ import com.lambda.event.events.PlayerEvent import com.lambda.event.events.RenderEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.rotating.Rotation -import com.lambda.interaction.managers.rotating.RotationMode -import com.lambda.interaction.managers.rotating.RotationRequestBuilder.Companion.rotationRequest +import com.lambda.interaction.manager.managers.rotating.Rotation +import com.lambda.interaction.manager.managers.rotating.RotationMode +import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.threading.runSafe diff --git a/src/main/kotlin/com/lambda/module/modules/render/Nametags.kt b/src/main/kotlin/com/lambda/module/modules/render/Nametags.kt index b097805d2..90458cb1d 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/Nametags.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/Nametags.kt @@ -30,7 +30,7 @@ import com.lambda.graphics.mc.renderer.ImmediateRenderer.Companion.immediateRend import com.lambda.graphics.mc.renderer.RendererUtils.worldToScreenNormalized import com.lambda.graphics.text.FontHandler import com.lambda.graphics.util.DynamicAABB.Companion.interpolatedBox -import com.lambda.interaction.handlers.FriendHandler.isFriend +import com.lambda.interaction.handler.handlers.FriendHandler.isFriend import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.threading.runSafe diff --git a/src/main/kotlin/com/lambda/module/modules/render/Tracers.kt b/src/main/kotlin/com/lambda/module/modules/render/Tracers.kt index f2d4d074e..914ba8ad4 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/Tracers.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/Tracers.kt @@ -26,7 +26,7 @@ import com.lambda.config.blocks.ScreenLineSettings import com.lambda.config.withEdits import com.lambda.graphics.mc.renderer.ImmediateRenderer.Companion.immediateRenderer import com.lambda.graphics.mc.renderer.RendererUtils.worldToScreenNormalized -import com.lambda.interaction.handlers.FriendHandler.isFriend +import com.lambda.interaction.handler.handlers.FriendHandler.isFriend import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.threading.runSafe diff --git a/src/main/kotlin/com/lambda/module/modules/world/AirPlace.kt b/src/main/kotlin/com/lambda/module/modules/world/AirPlace.kt index 182d49707..894e92a98 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/AirPlace.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/AirPlace.kt @@ -32,9 +32,9 @@ import com.lambda.graphics.mc.renderer.TickedRenderer.Companion.tickedRenderer import com.lambda.interaction.construction.simulation.BuildSimulator.simulate import com.lambda.interaction.construction.simulation.context.BuildContext import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.managers.interacting.InteractRequest -import com.lambda.interaction.managers.interacting.PlaceRequestBuilder.Companion.interactRequest -import com.lambda.interaction.managers.rotating.Rotation.Companion.rotation +import com.lambda.interaction.manager.managers.interacting.InteractRequest +import com.lambda.interaction.manager.managers.interacting.PlaceRequestBuilder.Companion.interactRequest +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotation import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.threading.runSafeAutomated diff --git a/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt b/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt index d36d5d538..73c860bf0 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt @@ -35,12 +35,12 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.graphics.mc.renderer.ImmediateRenderer.Companion.immediateRenderer import com.lambda.graphics.util.DirectionMask import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.handlers.BaritoneHandler +import com.lambda.interaction.handler.handlers.BaritoneHandler import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer import com.lambda.interaction.inventory.container.containers.HotbarContainer -import com.lambda.interaction.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest -import com.lambda.interaction.managers.inventory.InvRequestBuilder.Companion.inventoryRequest +import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest +import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module import com.lambda.module.modules.world.AutoPortal.PosHandler.currAnchorPos import com.lambda.module.modules.world.AutoPortal.PosHandler.obiPositions diff --git a/src/main/kotlin/com/lambda/module/modules/world/AutoVillagerCycle.kt b/src/main/kotlin/com/lambda/module/modules/world/AutoVillagerCycle.kt index bb68c9222..edcb11719 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/AutoVillagerCycle.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/AutoVillagerCycle.kt @@ -30,7 +30,7 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.construction.blueprint.Blueprint.Companion.toStructure import com.lambda.interaction.construction.blueprint.StaticBlueprint.Companion.toBlueprint import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.managers.rotating.RotationRequestBuilder.Companion.rotationRequest +import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.sound.SoundHandler.playSound diff --git a/src/main/kotlin/com/lambda/module/modules/world/HighwayTools.kt b/src/main/kotlin/com/lambda/module/modules/world/HighwayTools.kt index c288468d9..82aa2a647 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/HighwayTools.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/HighwayTools.kt @@ -23,7 +23,7 @@ import com.lambda.config.withEdits import com.lambda.interaction.construction.blueprint.Blueprint.Companion.emptyStructure import com.lambda.interaction.construction.blueprint.PropagatingBlueprint.Companion.propagatingBlueprint import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.handlers.BaritoneHandler +import com.lambda.interaction.handler.handlers.BaritoneHandler import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.task.RootTask.run 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 1708ba5c4..3850596d5 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/Scaffold.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/Scaffold.kt @@ -33,7 +33,7 @@ import com.lambda.interaction.construction.simulation.context.BuildContext import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack import com.lambda.interaction.inventory.container.containers.HotbarContainer -import com.lambda.interaction.managers.interacting.PlaceRequestBuilder.Companion.interactRequest +import com.lambda.interaction.manager.managers.interacting.PlaceRequestBuilder.Companion.interactRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.threading.runSafeAutomated 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 92304d9a5..a18c1d5b3 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt @@ -40,18 +40,18 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.event.listener.UnsafeListener.Companion.listenUnsafe import com.lambda.graphics.mc.renderer.TickedRenderer.Companion.tickedRenderer import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.handlers.BaritoneHandler +import com.lambda.interaction.handler.handlers.BaritoneHandler import com.lambda.interaction.inventory.container.containers.external.EnderChestContainer import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.interaction.inventory.container.containers.InventoryContainer import com.lambda.interaction.inventory.container.containers.OffHandContainer -import com.lambda.interaction.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest -import com.lambda.interaction.managers.inventory.InvRequestBuilder.Companion.inventoryRequest -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.managers.rotating.RotationRequestBuilder.Companion.rotationRequest +import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest +import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest +import com.lambda.interaction.manager.managers.rotating.Rotation +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.dist +import com.lambda.interaction.manager.managers.rotating.RotationManager +import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.task.RootTask.run diff --git a/src/main/kotlin/com/lambda/task/tasks/AcquirePlacedBlockTask.kt b/src/main/kotlin/com/lambda/task/tasks/AcquirePlacedBlockTask.kt index 086700731..cf705440a 100644 --- a/src/main/kotlin/com/lambda/task/tasks/AcquirePlacedBlockTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/AcquirePlacedBlockTask.kt @@ -21,7 +21,7 @@ import com.lambda.context.Automated import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.handlers.ContainerHandler.findSlotsWithMaterial +import com.lambda.interaction.handler.handlers.ContainerHandler.findSlots import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.select import com.lambda.task.Task import com.lambda.threading.runSafeAutomated @@ -43,7 +43,7 @@ class AcquirePlacedBlockTask @Ta5kBuilder constructor( runSafeAutomated { Items.ENDER_CHEST .select(1) - .findSlotsWithMaterial() + .findSlots() .firstOrNull()?.let { slot -> PlaceContainerTask(slot, this).finally { success(it) @@ -71,4 +71,13 @@ class AcquirePlacedBlockTask @Ta5kBuilder constructor( .firstOrNull { blockPos -> blockState(blockPos).block == block } + + companion object { + @Ta5kBuilder + context(automated: Automated) + fun acquirePlacedBlock( + block: Block, + maxSearchRadius: Int = 10 + ) = AcquirePlacedBlockTask(block, maxSearchRadius, automated) + } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/task/tasks/AcquireStack.kt b/src/main/kotlin/com/lambda/task/tasks/AcquireStackTask.kt similarity index 66% rename from src/main/kotlin/com/lambda/task/tasks/AcquireStack.kt rename to src/main/kotlin/com/lambda/task/tasks/AcquireStackTask.kt index a28f1c336..dbc2b6a57 100644 --- a/src/main/kotlin/com/lambda/task/tasks/AcquireStack.kt +++ b/src/main/kotlin/com/lambda/task/tasks/AcquireStackTask.kt @@ -19,34 +19,40 @@ package com.lambda.task.tasks import com.lambda.context.Automated import com.lambda.context.SafeContext -import com.lambda.interaction.handlers.ContainerHandler -import com.lambda.interaction.handlers.ContainerHandler.findContainerWithMaterial +import com.lambda.interaction.handler.handlers.ContainerHandler +import com.lambda.interaction.handler.handlers.ContainerHandler.findContainer import com.lambda.interaction.inventory.StackSelection import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.task.Task import com.lambda.threading.runSafeAutomated +import net.minecraft.screen.slot.Slot -class AcquireStack @Ta5kBuilder constructor( +class AcquireStackTask @Ta5kBuilder constructor( val selection: StackSelection, automated: Automated -) : Task(), Automated by automated { +) : Task(), Automated by automated { override val name: String get() = "Acquiring $selection" override fun SafeContext.onStart() { runSafeAutomated { - selection.findContainerWithMaterial() + selection.findContainer() ?.transferByTask(selection, HotbarContainer) - ?.finally { - success(selection) - }?.execute(this@AcquireStack) + ?.finally { slot -> success(slot) } + ?.execute(this@AcquireStackTask) ?: failure(ContainerHandler.NoContainerFound(selection)) // ToDo: Create crafting path } } companion object { @Ta5kBuilder - fun Automated.acquire(selection: () -> StackSelection) = - AcquireStack(selection(), this) + context(automated: Automated) + fun acquireStack(selection: StackSelection) = + AcquireStackTask(selection, automated) + + @Ta5kBuilder + context(automated: Automated) + fun acquireStack(selection: () -> StackSelection) = + AcquireStackTask(selection(), automated) } } diff --git a/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt b/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt index 31888b729..1f8e52634 100644 --- a/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt @@ -46,11 +46,11 @@ import com.lambda.interaction.construction.simulation.result.results.GenericResu import com.lambda.interaction.construction.simulation.result.results.InteractResult import com.lambda.interaction.construction.simulation.result.results.PreSimResult import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.handlers.BaritoneHandler +import com.lambda.interaction.handler.handlers.BaritoneHandler import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer -import com.lambda.interaction.managers.breaking.BreakRequestBuilder.Companion.breakRequest -import com.lambda.interaction.managers.interacting.PlaceRequestBuilder.Companion.interactRequest -import com.lambda.interaction.managers.inventory.InvRequestBuilder.Companion.inventoryRequest +import com.lambda.interaction.manager.managers.breaking.BreakRequestBuilder.Companion.breakRequest +import com.lambda.interaction.manager.managers.interacting.PlaceRequestBuilder.Companion.interactRequest +import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.modules.client.Client import com.lambda.task.Task import com.lambda.task.tasks.EatTask.Companion.eat @@ -78,7 +78,7 @@ import java.util.concurrent.ConcurrentLinkedQueue import kotlin.coroutines.cancellation.CancellationException import kotlin.math.sqrt -class BuildTask private constructor( +class BuildTask @Ta5kBuilder private constructor( private val blueprint: Blueprint, private val finishOnDone: Boolean, private val collectDrops: Boolean, @@ -377,7 +377,8 @@ class BuildTask private constructor( ) = BuildTask(this, finishOnDone, collectDrops, lifeMaintenance, async, automated, buildResultFilter) @Ta5kBuilder - fun Automated.breakAndCollectBlock( + context(automated: Automated) + fun breakAndCollect( blockPos: BlockPos, finishOnDone: Boolean = true, lifeMaintenance: Boolean = false, @@ -385,7 +386,7 @@ class BuildTask private constructor( buildResultFilter: SafeContext.(BuildResult) -> Boolean = { true }, ) = BuildTask( blockPos.toStructure(TargetState.Empty).toBlueprint(), - finishOnDone, true, lifeMaintenance, async, this, buildResultFilter + finishOnDone, true, lifeMaintenance, async, automated, buildResultFilter ) } } diff --git a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt index 03ce1c3ec..a495f49c9 100644 --- a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt @@ -20,20 +20,22 @@ package com.lambda.task.tasks import com.lambda.context.Automated import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.handler.handlers.ContainerHandler.findContainer import com.lambda.interaction.inventory.StackSelection import com.lambda.interaction.inventory.container.Container import com.lambda.interaction.inventory.container.ExternalContainer import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer import com.lambda.task.Task import com.lambda.threading.runSafeAutomated +import net.minecraft.screen.slot.Slot -class ContainerTransferTask( +class ContainerTransferTask @Ta5kBuilder constructor( private var fromContainer: Container, private val toContainer: Container, private val stackSelection: StackSelection, private val failIfNoStack: Boolean = false, automated: Automated -) : Task(), Automated by automated { +) : Task(), Automated by automated { override val name = "Transferring $stackSelection from $fromContainer to $toContainer" init { @@ -44,7 +46,7 @@ class ContainerTransferTask( true, { HotbarAndInventoryContainer.transferByTask(stackSelection, toContainer) - .finally { success() } + .finally { slot -> success(slot) } } ) { fromContainer.transferByTask(stackSelection, HotbarAndInventoryContainer) @@ -54,21 +56,32 @@ class ContainerTransferTask( fromContainer.accessThen { toContainer.accessThen { - TransferTask() + TransferTask().finally { slot -> + success(slot) + } } }.execute(this@ContainerTransferTask) } } } - private inner class TransferTask : Task() { + private inner class TransferTask : Task() { override val name = "Transferring" init { listen { runSafeAutomated { - val transferSuccessful = fromContainer.transfer(stackSelection, toContainer) - if (transferSuccessful) success() + val (fromSlot, toSlot) = fromContainer.getTransferSlots(stackSelection, toContainer) + if (fromSlot == null) { + checkFail() + return@listen + } + if (toSlot == null) { + failure("Unable to find a slot to transfer to.") + return@listen + } + val transferSuccessful = fromContainer.transfer(fromSlot, toSlot, toContainer) + if (transferSuccessful) success(toSlot) else checkFail() } } @@ -80,4 +93,34 @@ class ContainerTransferTask( private inner class NoMaterialAccessException(stackSelection: StackSelection) : IllegalStateException("Unable to access $stackSelection.") } + + companion object { + @Ta5kBuilder + context(automated: Automated) + fun transfer( + fromContainer: Container, + toContainer: Container, + stackSelection: StackSelection, + failIfNoStack: Boolean = false + ) = ContainerTransferTask(fromContainer, toContainer, stackSelection, failIfNoStack, automated) + + @Ta5kBuilder + context(automated: Automated) + fun transfer( + stackSelection: StackSelection, + toContainer: Container, + failIfNoStack: Boolean = false + ) = stackSelection.findContainer()?.let { + ContainerTransferTask(it, toContainer, stackSelection, failIfNoStack, automated) + } + + @Ta5kBuilder + context(automated: Automated) + fun StackSelection.transfer( + toContainer: Container, + failIfNoStack: Boolean = false + ) = findContainer()?.let { + ContainerTransferTask(it, toContainer, this, failIfNoStack, automated) + } + } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/task/tasks/EatTask.kt b/src/main/kotlin/com/lambda/task/tasks/EatTask.kt index d09163dc6..9e086f5e4 100644 --- a/src/main/kotlin/com/lambda/task/tasks/EatTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/EatTask.kt @@ -25,7 +25,8 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.interaction.inventory.container.containers.InventoryContainer -import com.lambda.interaction.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest +import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest +import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest import com.lambda.task.Task import com.lambda.threading.runSafeAutomated import net.minecraft.item.ItemStack diff --git a/src/main/kotlin/com/lambda/task/tasks/NoopTask.kt b/src/main/kotlin/com/lambda/task/tasks/NoopTask.kt index bc7b870c5..47c34dea6 100644 --- a/src/main/kotlin/com/lambda/task/tasks/NoopTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/NoopTask.kt @@ -29,6 +29,6 @@ class NoopTask : Task() { companion object { @Ta5kBuilder - fun noop() = NoopTask() + fun noopTask() = NoopTask() } } \ 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 a5c181286..bff0253e0 100644 --- a/src/main/kotlin/com/lambda/task/tasks/OpenContainerTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/OpenContainerTask.kt @@ -22,8 +22,8 @@ import com.lambda.context.Automated import com.lambda.event.events.InventoryEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.handlers.BaritoneHandler -import com.lambda.interaction.managers.rotating.RotationRequestBuilder.Companion.rotationRequest +import com.lambda.interaction.handler.handlers.BaritoneHandler +import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.task.Task import com.lambda.threading.runSafeAutomated import com.lambda.util.TickTimer @@ -36,9 +36,9 @@ import net.minecraft.util.math.Direction class OpenContainerTask @Ta5kBuilder constructor( private val blockPos: BlockPos, - private val automated: Automated, private val waitForSlotLoad: Boolean = true, - private val sides: Set = Direction.entries.toSet() + private val sides: Set = Direction.entries.toSet(), + private val automated: Automated ) : Task(), Automated by automated { override val name get() = "${containerState.description()} at ${blockPos.toShortString()}" @@ -109,4 +109,14 @@ class OpenContainerTask @Ta5kBuilder constructor( containerState = State.Opening } } + + companion object { + @Ta5kBuilder + context(automated: Automated) + fun openContainer( + blockPos: BlockPos, + waitForSlotLoad: Boolean = true, + sides: Set = Direction.entries.toSet() + ) = OpenContainerTask(blockPos, waitForSlotLoad, sides, automated) + } } diff --git a/src/main/kotlin/com/lambda/task/tasks/PlaceContainerTask.kt b/src/main/kotlin/com/lambda/task/tasks/PlaceContainerTask.kt index 185ae5da4..96e807e4d 100644 --- a/src/main/kotlin/com/lambda/task/tasks/PlaceContainerTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/PlaceContainerTask.kt @@ -25,7 +25,7 @@ import com.lambda.interaction.construction.simulation.BuildSimulator.simulate import com.lambda.interaction.construction.simulation.result.results.GenericResult import com.lambda.interaction.construction.simulation.result.results.InteractResult import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.managers.ManagerUtils +import com.lambda.interaction.manager.ManagerUtils import com.lambda.task.Task import com.lambda.task.tasks.BuildTask.Companion.build import com.lambda.threading.runSafeAutomated @@ -96,4 +96,14 @@ class PlaceContainerTask @Ta5kBuilder constructor( } else -> false } + + companion object { + @Ta5kBuilder + context(automated: Automated) + fun placeContainer(slot: Slot) = PlaceContainerTask(slot, automated) + + @Ta5kBuilder + context(automated: Automated) + fun placeContainer(slot: () -> Slot) = PlaceContainerTask(slot(), automated) + } } diff --git a/src/main/kotlin/com/lambda/task/tasks/SimpleActionTask.kt b/src/main/kotlin/com/lambda/task/tasks/SimpleActionTask.kt new file mode 100644 index 000000000..27b7ad071 --- /dev/null +++ b/src/main/kotlin/com/lambda/task/tasks/SimpleActionTask.kt @@ -0,0 +1,36 @@ +/* + * 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.task.tasks + +import com.lambda.context.SafeContext +import com.lambda.task.Task + +class SimpleActionTask @Ta5kBuilder constructor( + override val name: String, + val action: () -> Unit +) : Task() { + override fun SafeContext.onStart() { + action() + success() + } + + companion object { + @Ta5kBuilder + fun simpleAction(name: String, action: () -> Unit) = SimpleActionTask(name, action) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/task/tasks/SlotByCacheTask.kt b/src/main/kotlin/com/lambda/task/tasks/SlotByCacheTask.kt new file mode 100644 index 000000000..057b98d41 --- /dev/null +++ b/src/main/kotlin/com/lambda/task/tasks/SlotByCacheTask.kt @@ -0,0 +1,36 @@ +/* + * 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.task.tasks + +import com.lambda.context.SafeContext +import com.lambda.task.Task +import com.lambda.util.player.SlotUtils.matches +import net.minecraft.screen.slot.Slot + +class SlotByCacheTask @Ta5kBuilder constructor( + val slotCache: Slot +) : Task() { + override val name = "Finding slot by cache: $slotCache" + + override fun SafeContext.onStart() { + val slots = player.currentScreenHandler.slots + val slot = slots.find { it matches slotCache } + if (slot != null) success(slot) + else failure("Failed to find matching slot by cache: $slotCache") + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/util/PlaceDirection.kt b/src/main/kotlin/com/lambda/util/PlaceDirection.kt index b6e3b0484..bf0483a63 100644 --- a/src/main/kotlin/com/lambda/util/PlaceDirection.kt +++ b/src/main/kotlin/com/lambda/util/PlaceDirection.kt @@ -17,7 +17,7 @@ package com.lambda.util -import com.lambda.interaction.managers.rotating.Rotation +import com.lambda.interaction.manager.managers.rotating.Rotation import net.minecraft.util.math.MathHelper import net.minecraft.util.math.Vec3i diff --git a/src/main/kotlin/com/lambda/util/PlayerBuildLayerUtils.kt b/src/main/kotlin/com/lambda/util/PlayerBuildLayerUtils.kt index 86d0375b3..96b029a8e 100644 --- a/src/main/kotlin/com/lambda/util/PlayerBuildLayerUtils.kt +++ b/src/main/kotlin/com/lambda/util/PlayerBuildLayerUtils.kt @@ -18,7 +18,7 @@ package com.lambda.util import com.lambda.context.SafeContext -import com.lambda.interaction.handlers.BaritoneHandler +import com.lambda.interaction.handler.handlers.BaritoneHandler import com.lambda.util.BlockUtils.blockState import com.lambda.util.BlockUtils.isNotEmpty import com.lambda.util.math.MathUtils.ceilToInt diff --git a/src/main/kotlin/com/lambda/util/extension/Entity.kt b/src/main/kotlin/com/lambda/util/extension/Entity.kt index 50f761620..94ee5c188 100644 --- a/src/main/kotlin/com/lambda/util/extension/Entity.kt +++ b/src/main/kotlin/com/lambda/util/extension/Entity.kt @@ -17,7 +17,7 @@ package com.lambda.util.extension -import com.lambda.interaction.managers.rotating.Rotation +import com.lambda.interaction.manager.managers.rotating.Rotation import net.minecraft.entity.Entity import net.minecraft.entity.LivingEntity import net.minecraft.util.math.Vec3d diff --git a/src/main/kotlin/com/lambda/util/math/Linear.kt b/src/main/kotlin/com/lambda/util/math/Linear.kt index fe0360966..de0ab8c2e 100644 --- a/src/main/kotlin/com/lambda/util/math/Linear.kt +++ b/src/main/kotlin/com/lambda/util/math/Linear.kt @@ -17,7 +17,7 @@ package com.lambda.util.math -import com.lambda.interaction.managers.rotating.Rotation +import com.lambda.interaction.manager.managers.rotating.Rotation import net.minecraft.util.math.Box import net.minecraft.util.math.Vec3d import java.awt.Color diff --git a/src/main/kotlin/com/lambda/util/player/MovementUtils.kt b/src/main/kotlin/com/lambda/util/player/MovementUtils.kt index 33f266cd2..2adf4de83 100644 --- a/src/main/kotlin/com/lambda/util/player/MovementUtils.kt +++ b/src/main/kotlin/com/lambda/util/player/MovementUtils.kt @@ -18,7 +18,7 @@ package com.lambda.util.player import com.lambda.context.SafeContext -import com.lambda.interaction.managers.rotating.RotationManager +import com.lambda.interaction.manager.managers.rotating.RotationManager import com.lambda.util.math.MathUtils.toDegree import com.lambda.util.math.MathUtils.toDouble import com.lambda.util.math.MathUtils.toRadian diff --git a/src/main/kotlin/com/lambda/util/player/PlayerUtils.kt b/src/main/kotlin/com/lambda/util/player/PlayerUtils.kt index f381daac1..574394b7a 100644 --- a/src/main/kotlin/com/lambda/util/player/PlayerUtils.kt +++ b/src/main/kotlin/com/lambda/util/player/PlayerUtils.kt @@ -19,7 +19,7 @@ package com.lambda.util.player import com.lambda.config.blocks.BuildConfig import com.lambda.context.SafeContext -import com.lambda.interaction.handlers.GlideHandler +import com.lambda.interaction.handler.handlers.GlideHandler import com.lambda.util.extension.getBlockState import com.lambda.util.player.MovementUtils.sneaking import com.lambda.util.world.fastEntitySearch diff --git a/src/main/kotlin/com/lambda/util/player/RotationUtils.kt b/src/main/kotlin/com/lambda/util/player/RotationUtils.kt index 840cf5670..cb0af20ec 100644 --- a/src/main/kotlin/com/lambda/util/player/RotationUtils.kt +++ b/src/main/kotlin/com/lambda/util/player/RotationUtils.kt @@ -23,9 +23,9 @@ import com.lambda.context.SafeContext import com.lambda.interaction.construction.simulation.processing.PreProcessingData import com.lambda.interaction.construction.verify.ScanMode import com.lambda.interaction.construction.verify.SurfaceScan -import com.lambda.interaction.managers.rotating.Rotation -import com.lambda.interaction.managers.rotating.Rotation.Companion.rotationTo -import com.lambda.interaction.managers.rotating.RotationManager +import com.lambda.interaction.manager.managers.rotating.Rotation +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotationTo +import com.lambda.interaction.manager.managers.rotating.RotationManager import com.lambda.module.modules.client.Client import com.lambda.util.BlockUtils.blockState import com.lambda.util.PlaceDirection diff --git a/src/main/kotlin/com/lambda/util/player/SlotUtils.kt b/src/main/kotlin/com/lambda/util/player/SlotUtils.kt index 81e89d0c2..65a60cb96 100644 --- a/src/main/kotlin/com/lambda/util/player/SlotUtils.kt +++ b/src/main/kotlin/com/lambda/util/player/SlotUtils.kt @@ -62,4 +62,11 @@ object SlotUtils { val syncId = player.currentScreenHandler?.syncId ?: return interaction.clickSlot(syncId, slotId, button, actionType, player) } + + infix fun Slot.matches(slot: Slot) = + index == slot.index && + inventory::class == slot.inventory::class && + id == slot.id && + x == slot.x && + y == slot.y } diff --git a/src/main/kotlin/com/lambda/util/player/prediction/PredictionEntity.kt b/src/main/kotlin/com/lambda/util/player/prediction/PredictionEntity.kt index 17a9a4d7c..a4a6e3bc6 100644 --- a/src/main/kotlin/com/lambda/util/player/prediction/PredictionEntity.kt +++ b/src/main/kotlin/com/lambda/util/player/prediction/PredictionEntity.kt @@ -19,7 +19,7 @@ package com.lambda.util.player.prediction import com.lambda.Lambda.mc import com.lambda.context.SafeContext -import com.lambda.interaction.managers.rotating.Rotation +import com.lambda.interaction.manager.managers.rotating.Rotation import com.lambda.module.modules.movement.SafeWalk.isNearLedge import com.lambda.threading.runSafe import com.lambda.util.BlockUtils.blockState diff --git a/src/main/kotlin/com/lambda/util/player/prediction/PredictionTick.kt b/src/main/kotlin/com/lambda/util/player/prediction/PredictionTick.kt index 074a6c69d..3d05d802c 100644 --- a/src/main/kotlin/com/lambda/util/player/prediction/PredictionTick.kt +++ b/src/main/kotlin/com/lambda/util/player/prediction/PredictionTick.kt @@ -17,7 +17,7 @@ package com.lambda.util.player.prediction -import com.lambda.interaction.managers.rotating.Rotation +import com.lambda.interaction.manager.managers.rotating.Rotation import net.minecraft.util.math.Box import net.minecraft.util.math.Vec3d diff --git a/src/main/kotlin/com/lambda/util/world/raycast/RayCastUtils.kt b/src/main/kotlin/com/lambda/util/world/raycast/RayCastUtils.kt index 343b0e8ca..91ac2263e 100644 --- a/src/main/kotlin/com/lambda/util/world/raycast/RayCastUtils.kt +++ b/src/main/kotlin/com/lambda/util/world/raycast/RayCastUtils.kt @@ -19,7 +19,7 @@ package com.lambda.util.world.raycast import com.lambda.Lambda.mc import com.lambda.context.SafeContext -import com.lambda.interaction.managers.rotating.Rotation +import com.lambda.interaction.manager.managers.rotating.Rotation import com.lambda.util.math.distSq import net.minecraft.client.network.ClientPlayerEntity import net.minecraft.entity.Entity diff --git a/src/test/kotlin/PlaceDirectionTest.kt b/src/test/kotlin/PlaceDirectionTest.kt index 02b5fb6e3..15c94436e 100644 --- a/src/test/kotlin/PlaceDirectionTest.kt +++ b/src/test/kotlin/PlaceDirectionTest.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -import com.lambda.interaction.managers.rotating.Rotation +import com.lambda.interaction.manager.managers.rotating.Rotation import com.lambda.util.PlaceDirection import kotlin.test.Test import kotlin.test.assertEquals diff --git a/src/test/kotlin/RotationTest.kt b/src/test/kotlin/RotationTest.kt index f57a3c33e..7c182d8c1 100644 --- a/src/test/kotlin/RotationTest.kt +++ b/src/test/kotlin/RotationTest.kt @@ -15,12 +15,12 @@ * along with this program. If not, see . */ -import com.lambda.interaction.managers.rotating.Rotation -import com.lambda.interaction.managers.rotating.Rotation.Companion.angleDifference -import com.lambda.interaction.managers.rotating.Rotation.Companion.dist -import com.lambda.interaction.managers.rotating.Rotation.Companion.lerp -import com.lambda.interaction.managers.rotating.Rotation.Companion.slerp -import com.lambda.interaction.managers.rotating.Rotation.Companion.wrap +import com.lambda.interaction.manager.managers.rotating.Rotation +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.angleDifference +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.dist +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.lerp +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.slerp +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.wrap import kotlin.math.abs import kotlin.math.hypot import kotlin.test.Test From bc1f3725843609033fccb74d7c6bdbad414812e2 Mon Sep 17 00:00:00 2001 From: beanbag44 <107891830+beanbag44@users.noreply.github.com> Date: Tue, 28 Jul 2026 16:56:33 +0100 Subject: [PATCH 06/43] cleanup, still broken. Need to rework task chaining --- .../command/commands/TransferCommand.kt | 4 +- .../lambda/config/blocks/InventoryConfig.kt | 1 - .../interaction/handler/PostActionHandler.kt | 11 +- .../handler/handlers/ContainerHandler.kt | 1 - .../handlers/breaking/BrokenBlockHandler.kt | 2 +- .../handlers/breaking/RebreakHandler.kt | 2 +- .../interaction/inventory/StackSelection.kt | 11 ++ .../inventory/container/Container.kt | 28 ++-- .../containers/external/ChestContainer.kt | 25 ++-- .../external/EnderChestContainer.kt | 53 ++++---- .../external/PlacedShulkerBoxContainer.kt | 31 +++-- .../external/ShulkerBoxContainer.kt | 53 +++++--- .../com/lambda/interaction/manager/Manager.kt | 2 +- .../manager/managers/breaking/BreakInfo.kt | 2 +- .../manager/managers/breaking/BreakManager.kt | 8 +- .../manager/managers/breaking/BreakRequest.kt | 3 +- .../manager/managers/hotbar/HotbarRequest.kt | 1 - .../managers/interacting/InteractInfo.kt | 2 +- .../managers/interacting/InteractManager.kt | 29 ++-- .../managers/interacting/InteractRequest.kt | 2 +- .../managers/inventory/InventoryRequest.kt | 1 - .../containers/EnderChestContainer.kt | 0 .../module/modules/player/EndermanLook.kt | 4 +- .../module/modules/player/InventoryTweaks.kt | 4 +- .../lambda/module/modules/player/Replay.kt | 1 - .../lambda/module/modules/world/AutoPortal.kt | 1 - .../lambda/module/modules/world/StashMover.kt | 10 +- src/main/kotlin/com/lambda/task/RootTask.kt | 2 +- src/main/kotlin/com/lambda/task/Task.kt | 125 ++++++++++++++++-- .../kotlin/com/lambda/task/tasks/BuildTask.kt | 1 - .../task/tasks/ContainerTransferTask.kt | 11 +- .../kotlin/com/lambda/task/tasks/EatTask.kt | 1 - .../kotlin/com/lambda/task/tasks/NoopTask.kt | 2 +- .../lambda/task/tasks/PlaceContainerTask.kt | 14 +- .../com/lambda/task/tasks/SlotByCacheTask.kt | 5 + .../com/lambda/util/player/SlotUtils.kt | 3 +- 36 files changed, 298 insertions(+), 158 deletions(-) delete mode 100644 src/main/kotlin/com/lambda/interaction/material/container/containers/EnderChestContainer.kt diff --git a/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt b/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt index 86db90836..a9e046b44 100644 --- a/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt +++ b/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt @@ -96,11 +96,11 @@ object TransferCommand : LambdaCommand( isItem(stack().value().item) } AutomationConfig.DEFAULT.runSafeAutomated { - val fromContainer = ContainerHandler.filteredContainers().find { + val fromContainer = ContainerHandler.filteredContainers.find { it.name == from().value().split(".").last().trim() } ?: return@executeWithResult failure("From container not found") - val toContainer = ContainerHandler.filteredContainers().find { + val toContainer = ContainerHandler.filteredContainers.find { it.name == to().value().split(".").last().trim() } ?: return@executeWithResult failure("To container not found") diff --git a/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt b/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt index be5c27139..964f4732c 100644 --- a/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt +++ b/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt @@ -17,7 +17,6 @@ package com.lambda.config.blocks -import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.interaction.inventory.ContainerSelection import com.lambda.interaction.inventory.ContainerSelectionBuilder.Companion.selectContainer diff --git a/src/main/kotlin/com/lambda/interaction/handler/PostActionHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/PostActionHandler.kt index 5994bb01e..0c64982fb 100644 --- a/src/main/kotlin/com/lambda/interaction/handler/PostActionHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/PostActionHandler.kt @@ -22,7 +22,6 @@ import com.lambda.event.events.ConnectionEvent 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.interaction.handler.handlers.breaking.BrokenBlockHandler import com.lambda.interaction.manager.ActionInfo import com.lambda.util.collections.LimitedDecayQueue @@ -52,8 +51,10 @@ abstract class PostActionHandler { pendingInteractionsList.remove(context) } - fun Automated.setPendingConfigs() { - BrokenBlockHandler.pendingActions.setSizeLimit(buildConfig.maxPendingActions) - BrokenBlockHandler.pendingActions.setDecayTime(buildConfig.actionTimeout * 50L) - } + context(automated: Automated) + fun setPendingConfigs() = + with(automated) { + pendingActions.setSizeLimit(buildConfig.maxPendingActions) + pendingActions.setDecayTime(buildConfig.actionTimeout * 50L) + } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt index e7b91ede3..a14699fc4 100644 --- a/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt @@ -30,7 +30,6 @@ import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.select import com.lambda.interaction.inventory.container.Container import com.lambda.interaction.inventory.container.containers.external.ChestContainer import com.lambda.interaction.inventory.container.containers.external.EnderChestContainer -import com.lambda.task.Task.Ta5kBuilder import com.lambda.util.BlockUtils.blockEntity import com.lambda.util.ReflectionUtils.getInstances import com.lambda.util.extension.containerStacks diff --git a/src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/BrokenBlockHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/BrokenBlockHandler.kt index 6477e2996..681358860 100644 --- a/src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/BrokenBlockHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/BrokenBlockHandler.kt @@ -24,8 +24,8 @@ import com.lambda.event.events.EntityEvent import com.lambda.event.events.WorldEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.construction.simulation.processing.ProcessorRegistry -import com.lambda.interaction.handler.handlers.breaking.RebreakHandler.rebreak import com.lambda.interaction.handler.PostActionHandler +import com.lambda.interaction.handler.handlers.breaking.RebreakHandler.reBreak import com.lambda.interaction.manager.managers.breaking.BreakInfo import com.lambda.interaction.manager.managers.breaking.BreakManager.lastPosStarted import com.lambda.interaction.manager.managers.breaking.BreakManager.matchesBlockItem diff --git a/src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/RebreakHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/RebreakHandler.kt index eddb0987c..84c82a11f 100644 --- a/src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/RebreakHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/RebreakHandler.kt @@ -25,7 +25,7 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.event.listener.UnsafeListener.Companion.listenUnsafe import com.lambda.interaction.construction.simulation.context.BreakContext import com.lambda.interaction.handler.handlers.breaking.BrokenBlockHandler.destroyBlock -import com.lambda.interaction.handler.handlers.breaking.RebreakHandler.rebreak +import com.lambda.interaction.handler.handlers.breaking.RebreakHandler.reBreak import com.lambda.interaction.handler.handlers.packet.PacketLimitHandler import com.lambda.interaction.handler.handlers.packet.PacketType import com.lambda.interaction.manager.managers.breaking.BreakInfo diff --git a/src/main/kotlin/com/lambda/interaction/inventory/StackSelection.kt b/src/main/kotlin/com/lambda/interaction/inventory/StackSelection.kt index 9e59af8b7..e83152448 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/StackSelection.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/StackSelection.kt @@ -20,6 +20,7 @@ package com.lambda.interaction.inventory import com.lambda.util.EnchantmentUtils.getEnchantment import com.lambda.util.item.ItemStackUtils.shulkerBoxStacks import com.lambda.util.item.ItemUtils +import com.lambda.util.player.SlotUtils.matches import net.minecraft.block.Block import net.minecraft.block.BlockState import net.minecraft.component.ComponentType @@ -189,6 +190,16 @@ class StackSelectionBuilder private constructor(private val count: Int = 0) { appendSelector { stack, _ -> stack.isEmpty} } + fun isSlot(slot: Slot) { + appendSelector { _, s -> + s != null && s matches slot + } + } + + fun isSlotByReference(slot: Slot) { + appendSelector { _, s -> s === slot } + } + fun custom(predicate: (ItemStack, Slot?) -> Boolean) { appendSelector { stack, slot -> predicate(stack, slot) } } diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/Container.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/Container.kt index 1053ea0c6..195b4c803 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/Container.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/Container.kt @@ -26,11 +26,10 @@ import com.lambda.interaction.inventory.StackSelection import com.lambda.interaction.inventory.container.containers.external.ShulkerBoxContainer import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest +import com.lambda.task.Task.Companion.taskOrSkipOrNull import com.lambda.task.Task.Ta5kBuilder -import com.lambda.task.TaskGenerator import com.lambda.task.TaskOrNullGenerator import com.lambda.task.tasks.ContainerTransferTask -import com.lambda.task.tasks.NoopTask.Companion.noopTask import com.lambda.util.Nameable import com.lambda.util.item.ItemStackUtils.count import com.lambda.util.item.ItemStackUtils.empty @@ -110,15 +109,15 @@ abstract class Container( }?.map { slot -> ShulkerBoxContainer( slot.stack.shulkerBoxStacks, - containedIn = this@Container, - slot = slot + this@Container, + slot ) }?.toSet() ?: stacks.map { stack -> ShulkerBoxContainer( stack.shulkerBoxStacks, - containedIn = this@Container, - slot = null + this@Container, + null ) }.toSet() @@ -130,15 +129,14 @@ abstract class Container( @Ta5kBuilder context(automatedSafeContext: AutomatedSafeContext) - open fun accessThen( + open fun accessThen( closeAfter: Boolean = true, - afterClose: TaskOrNullGenerator? = null, - afterOpen: TaskGenerator = { noopTask() } + afterOpen: TaskOrNullGenerator = { null }, + afterClose: TaskOrNullGenerator = { null } ) = with(automatedSafeContext) { - afterOpen(Unit).also { - if (closeAfter && afterClose != null) { - it.thenOrNull { afterClose(Unit) } - } + taskOrSkipOrNull({ afterOpen(Unit) }) { result -> + if (closeAfter) afterClose(result) + else null } } @@ -174,8 +172,8 @@ abstract class Container( } context(automated: Automated) - fun transferByTask(stackSelection: StackSelection, destination: Container, failIfNoMaterial: Boolean = false) = - ContainerTransferTask(this, destination, stackSelection, failIfNoMaterial, automated) + fun transferByTask(stackSelection: StackSelection, toContainer: Container, failIfNoStack: Boolean = true) = + ContainerTransferTask(this, toContainer, stackSelection, failIfNoStack, automated) open fun stackCount(selection: StackSelection) = selection.filter(stacks).count diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ChestContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ChestContainer.kt index 5a2cd8de9..9830a1e24 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ChestContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ChestContainer.kt @@ -23,12 +23,8 @@ import com.lambda.interaction.handler.handlers.ContainerHandler.lastInteractedBl import com.lambda.interaction.inventory.container.Container import com.lambda.interaction.inventory.container.ExternalContainer import com.lambda.interaction.inventory.container.PlacedContainer -import com.lambda.task.Task -import com.lambda.task.Task.Ta5kBuilder -import com.lambda.task.TaskGenerator +import com.lambda.task.Task.Companion.taskOrSkipOrNull import com.lambda.task.TaskOrNullGenerator -import com.lambda.task.tasks.NoopTask.Companion.noopTask -import com.lambda.task.tasks.OpenContainerTask import com.lambda.task.tasks.OpenContainerTask.Companion.openContainer import com.lambda.task.tasks.SimpleActionTask.Companion.simpleAction import com.lambda.util.extension.containerSlots @@ -38,7 +34,6 @@ import com.lambda.util.text.literal import net.minecraft.block.ChestBlock import net.minecraft.block.entity.ChestBlockEntity import net.minecraft.item.ItemStack -import net.minecraft.screen.ScreenHandler import net.minecraft.screen.ScreenHandlerType import net.minecraft.screen.slot.Slot import net.minecraft.util.math.BlockPos @@ -74,14 +69,20 @@ data class ChestContainer( mc.player?.currentScreenHandler?.type == ScreenHandlerType.GENERIC_9X3 } ?: false - context(_: AutomatedSafeContext) - override fun accessThen( + context(automatedSafeContext: AutomatedSafeContext) + override fun accessThen( closeAfter: Boolean, - afterClose: TaskOrNullGenerator?, - afterOpen: TaskGenerator + afterOpen: TaskOrNullGenerator, + afterClose: TaskOrNullGenerator ) = - openContainer(blockPos).then { - afterOpen(Unit).thenOrNull { + with(automatedSafeContext) { + taskOrSkipOrNull( + optional = { + openContainer(blockPos).thenOrNull { + afterOpen(Unit) + } + } + ) { if (closeAfter) { simpleAction("Close inventory") { player.closeHandledScreen() } } else null diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/EnderChestContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/EnderChestContainer.kt index f81b87ddf..a046e168a 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/EnderChestContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/EnderChestContainer.kt @@ -23,12 +23,11 @@ import com.lambda.interaction.handler.handlers.ContainerHandler.lastInteractedBl import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.select import com.lambda.interaction.inventory.container.Container import com.lambda.interaction.inventory.container.ExternalContainer -import com.lambda.task.TaskGenerator +import com.lambda.task.Task.Companion.taskOrSkipOrNull +import com.lambda.task.Task.Companion.wrappedTask import com.lambda.task.TaskOrNullGenerator import com.lambda.task.tasks.AcquireStackTask.Companion.acquireStack import com.lambda.task.tasks.BuildTask.Companion.breakAndCollect -import com.lambda.task.tasks.NoopTask.Companion.noopTask -import com.lambda.task.tasks.OpenContainerTask import com.lambda.task.tasks.OpenContainerTask.Companion.openContainer import com.lambda.task.tasks.PlaceContainerTask.Companion.placeContainer import com.lambda.task.tasks.SimpleActionTask.Companion.simpleAction @@ -39,7 +38,7 @@ import net.minecraft.block.entity.EnderChestBlockEntity import net.minecraft.item.ItemStack import net.minecraft.item.Items import net.minecraft.screen.ScreenHandlerType -import kotlin.comparisons.then +import net.minecraft.util.math.BlockPos object EnderChestContainer : Container(Rank.EnderChest), ExternalContainer { override val slots @@ -58,34 +57,40 @@ object EnderChestContainer : Container(Rank.EnderChest), ExternalContainer { } ?: false context(automatedSafeContext: AutomatedSafeContext) - override fun accessThen( + override fun accessThen( closeAfter: Boolean, - afterClose: TaskOrNullGenerator?, - afterOpen: TaskGenerator + afterOpen: TaskOrNullGenerator, + afterClose: TaskOrNullGenerator ) = with(automatedSafeContext) { - if (!isAccessed) { - acquireStack(Items.ENDER_CHEST.select()).then { slot -> - placeContainer(slot).then { pos -> - openContainer(pos).then { - afterOpen(Unit).thenOrNull { - if (closeAfter) { - simpleAction("Close inventory") { player.closeHandledScreen() }.then { - breakAndCollect(pos, lifeMaintenance = false).thenOrNull { - afterClose?.invoke(this, Unit) - } + taskOrSkipOrNull( + optional = { + if (isAccessed) null + else { + wrappedTask("Setup Ender Chest") { success -> + acquireStack(Items.ENDER_CHEST.select(1)).then { slot -> + placeContainer(slot).then { pos -> + openContainer(pos).finally { + success(pos) } - } else null + } } } } } - } else afterOpen(Unit).thenOrNull { - if (closeAfter) { - simpleAction("Close inventory") { mc.player?.closeHandledScreen() }.thenOrNull { - afterClose?.invoke(this, Unit) - } - } else null + ) { pos -> + taskOrSkipOrNull({ afterOpen(Unit) }) { afterOpenResult -> + if (closeAfter) { + simpleAction("Close inventory") { player.closeHandledScreen() }.thenOrNull { + taskOrSkipOrNull( + optional = { + if (pos != null) breakAndCollect(pos) + else null + } + ) { afterClose(afterOpenResult) } + } + } else null + } } } diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/PlacedShulkerBoxContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/PlacedShulkerBoxContainer.kt index 697a52783..009e58def 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/PlacedShulkerBoxContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/PlacedShulkerBoxContainer.kt @@ -22,9 +22,8 @@ import com.lambda.context.AutomatedSafeContext import com.lambda.interaction.handler.handlers.ContainerHandler.lastInteractedBlockEntity import com.lambda.interaction.inventory.container.Container import com.lambda.interaction.inventory.container.PlacedContainer -import com.lambda.task.TaskGenerator +import com.lambda.task.Task.Companion.taskOrSkipOrNull import com.lambda.task.TaskOrNullGenerator -import com.lambda.task.tasks.NoopTask.Companion.noopTask import com.lambda.task.tasks.OpenContainerTask import com.lambda.task.tasks.SimpleActionTask.Companion.simpleAction import com.lambda.util.extension.containerSlots @@ -67,19 +66,25 @@ class PlacedShulkerBoxContainer( } ?: false context(automatedSafeContext: AutomatedSafeContext) - override fun accessThen( + override fun accessThen( closeAfter: Boolean, - afterClose: TaskOrNullGenerator?, - afterOpen: TaskGenerator + afterOpen: TaskOrNullGenerator, + afterClose: TaskOrNullGenerator ) = - run { - if (!isAccessed) OpenContainerTask(blockPos, automated = automatedSafeContext) - else noopTask() - }.then { - afterOpen(Unit).thenOrNull { - if (closeAfter) { - simpleAction("Close Inventory") { automatedSafeContext.player.closeHandledScreen() } - } else null + with(automatedSafeContext) { + taskOrSkipOrNull( + optional = { + if (!isAccessed) OpenContainerTask(blockPos, automated = automatedSafeContext) + else null + } + ) { + taskOrSkipOrNull({ afterOpen(Unit) }) { result -> + if (closeAfter) { + simpleAction("Close Inventory") { + automatedSafeContext.player.closeHandledScreen() + }.thenOrNull { afterClose(result) } + } else null + } } } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ShulkerBoxContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ShulkerBoxContainer.kt index 9d4091153..01a4b312c 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ShulkerBoxContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ShulkerBoxContainer.kt @@ -19,18 +19,19 @@ package com.lambda.interaction.inventory.container.containers.external import com.lambda.Lambda.mc import com.lambda.context.AutomatedSafeContext -import com.lambda.context.SafeContext import com.lambda.interaction.handler.handlers.ContainerHandler.lastInteractedBlockEntity +import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack import com.lambda.interaction.inventory.container.Container import com.lambda.interaction.inventory.container.ExternalContainer import com.lambda.interaction.inventory.container.NestedContainer -import com.lambda.task.TaskGenerator +import com.lambda.interaction.inventory.container.containers.HotbarContainer +import com.lambda.task.Task.Companion.nullableWrappedTask +import com.lambda.task.Task.Companion.taskOrSkipOrNull import com.lambda.task.TaskOrNullGenerator import com.lambda.task.tasks.BuildTask.Companion.breakAndCollect import com.lambda.task.tasks.OpenContainerTask.Companion.openContainer import com.lambda.task.tasks.PlaceContainerTask.Companion.placeContainer import com.lambda.task.tasks.SimpleActionTask.Companion.simpleAction -import com.lambda.threading.runSafe import com.lambda.util.extension.containerSlots import com.lambda.util.text.buildText import com.lambda.util.text.highlighted @@ -59,7 +60,6 @@ data class ShulkerBoxContainer( literal(" in slot $slotCache") } - private var placed = false private var blockPos: BlockPos? = null override val isAccessed @@ -71,22 +71,45 @@ data class ShulkerBoxContainer( } ?: false context(automatedSafeContext: AutomatedSafeContext) - override fun accessThen( + override fun accessThen( closeAfter: Boolean, - afterClose: TaskOrNullGenerator?, - afterOpen: TaskGenerator + afterOpen: TaskOrNullGenerator, + afterClose: TaskOrNullGenerator ) = with(automatedSafeContext) { - if (!isAccessed) { - - } else afterOpen(Unit).thenOrNull { - if (closeAfter) { - simpleAction("Close inventory") { player.closeHandledScreen() }.then { - breakAndCollect(blockPos, lifeMaintenance = false).thenOrNull { - afterClose?.invoke(this, Unit) + taskOrSkipOrNull( + optional = { + if (isAccessed) null + else nullableWrappedTask("Setup Shulker Box") { success -> + containedIn.accessThen( + afterOpen = { + if (player.currentScreenHandler.syncId == 0) null + else { + val selection = selectStack { isSlot(slotCache) } + containedIn.transferByTask(selection, HotbarContainer) + } + } + ) { slot -> + slot?.let { s -> + placeContainer(s).then { pos -> + openContainer(pos).finally { + success(pos) + } + } + } } } - } else null + } + ) { pos -> + taskOrSkipOrNull({ afterOpen(Unit) }) { result -> + if (closeAfter && pos != null) { + simpleAction("Close inventory") { player.closeHandledScreen() }.then { + breakAndCollect(pos).thenOrNull { + afterClose(result) + } + } + } else null + } } } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/manager/Manager.kt b/src/main/kotlin/com/lambda/interaction/manager/Manager.kt index 842806fce..cab1aa21f 100644 --- a/src/main/kotlin/com/lambda/interaction/manager/Manager.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/Manager.kt @@ -25,7 +25,7 @@ import com.lambda.event.Event import com.lambda.event.events.TickEvent import com.lambda.event.events.TickEvent.Companion.ALL_STAGES import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.manager.managers.ManagerUtils.accumulatedManagerPriority +import com.lambda.interaction.manager.ManagerUtils.accumulatedManagerPriority import com.lambda.threading.runSafeAutomated import kotlin.reflect.KClass diff --git a/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakInfo.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakInfo.kt index a25920312..d1830a959 100644 --- a/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakInfo.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakInfo.kt @@ -22,7 +22,7 @@ import com.lambda.config.blocks.BreakConfig.BreakMode import com.lambda.context.SafeContext import com.lambda.interaction.construction.simulation.context.BreakContext import com.lambda.interaction.handler.handlers.breaking.RebreakHandler -import com.lambda.interaction.manager.managers.ActionInfo +import com.lambda.interaction.manager.ActionInfo import com.lambda.interaction.manager.managers.breaking.BreakInfo.BreakType.Primary import com.lambda.interaction.manager.managers.breaking.BreakInfo.BreakType.Rebreak import com.lambda.interaction.manager.managers.breaking.BreakInfo.BreakType.RedundantSecondary diff --git a/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakManager.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakManager.kt index c30170404..61d7d1234 100644 --- a/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakManager.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakManager.kt @@ -34,9 +34,9 @@ import com.lambda.interaction.construction.simulation.BuildSimulator.simulate import com.lambda.interaction.construction.simulation.context.BreakContext import com.lambda.interaction.construction.simulation.result.results.BreakResult import com.lambda.interaction.construction.verify.TargetState +import com.lambda.interaction.handler.handlers.breaking.BrokenBlockHandler import com.lambda.interaction.handler.handlers.breaking.BrokenBlockHandler.destroyBlock import com.lambda.interaction.handler.handlers.breaking.BrokenBlockHandler.pendingActions -import com.lambda.interaction.handler.handlers.breaking.BrokenBlockHandler.setPendingConfigs import com.lambda.interaction.handler.handlers.breaking.BrokenBlockHandler.startPending import com.lambda.interaction.handler.handlers.breaking.RebreakHandler import com.lambda.interaction.handler.handlers.breaking.RebreakHandler.getRebreakPotential @@ -45,10 +45,8 @@ import com.lambda.interaction.handler.handlers.packet.PacketLimitHandler import com.lambda.interaction.handler.handlers.packet.PacketType import com.lambda.interaction.inventory.StackSelection import com.lambda.interaction.manager.Manager +import com.lambda.interaction.manager.ManagerUtils.isPosBlocked import com.lambda.interaction.manager.PositionBlocking -import com.lambda.interaction.manager.managers.Manager -import com.lambda.interaction.manager.managers.ManagerUtils.isPosBlocked -import com.lambda.interaction.manager.managers.PositionBlocking import com.lambda.interaction.manager.managers.breaking.BreakInfo.BreakType.Primary import com.lambda.interaction.manager.managers.breaking.BreakInfo.BreakType.Rebreak import com.lambda.interaction.manager.managers.breaking.BreakInfo.BreakType.RedundantSecondary @@ -524,7 +522,7 @@ object BreakManager : Manager( } primaryBreak = breakInfo - setPendingConfigs() + BrokenBlockHandler.setPendingConfigs() return primaryBreak } diff --git a/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakRequest.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakRequest.kt index 0a284cb3a..8b03b2c3f 100644 --- a/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakRequest.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakRequest.kt @@ -27,7 +27,8 @@ import com.lambda.interaction.construction.simulation.result.BuildResult import com.lambda.interaction.construction.simulation.result.Dependent import com.lambda.interaction.construction.simulation.result.results.BreakResult import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.manager.managers.Request +import com.lambda.interaction.manager.Request +import com.lambda.interaction.manager.managers.breaking.BreakRequestBuilder.Companion.breakRequest import com.lambda.threading.runSafe import com.lambda.util.BlockUtils.blockState import com.lambda.util.BlockUtils.isEmpty diff --git a/src/main/kotlin/com/lambda/interaction/manager/managers/hotbar/HotbarRequest.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/hotbar/HotbarRequest.kt index 682ec2993..904c32489 100644 --- a/src/main/kotlin/com/lambda/interaction/manager/managers/hotbar/HotbarRequest.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/hotbar/HotbarRequest.kt @@ -19,7 +19,6 @@ package com.lambda.interaction.manager.managers.hotbar import com.lambda.context.Automated import com.lambda.interaction.manager.Request -import com.lambda.interaction.manager.managers.Request class HotbarRequest( val slot: Int, diff --git a/src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractInfo.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractInfo.kt index c5efd746c..a0401782e 100644 --- a/src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractInfo.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractInfo.kt @@ -21,7 +21,7 @@ import com.lambda.config.blocks.InteractConfig import com.lambda.context.SafeContext import com.lambda.interaction.construction.simulation.context.BuildContext import com.lambda.interaction.construction.simulation.context.InteractContext -import com.lambda.interaction.manager.managers.ActionInfo +import com.lambda.interaction.manager.ActionInfo import net.minecraft.util.math.BlockPos data class InteractInfo( diff --git a/src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractManager.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractManager.kt index 2b7983e23..4c57ca26a 100644 --- a/src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractManager.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractManager.kt @@ -18,7 +18,6 @@ package com.lambda.interaction.manager.managers.interacting import com.lambda.config.blocks.InteractConfig -import com.lambda.config.blocks.InteractConfig.AirPlaceMode import com.lambda.context.Automated import com.lambda.context.AutomatedSafeContext import com.lambda.context.SafeContext @@ -29,14 +28,13 @@ 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.interaction.construction.simulation.context.InteractContext -import com.lambda.interaction.handler.handlers.interacting.InteractedBlockHandler.pendingActions -import com.lambda.interaction.handler.handlers.interacting.InteractedBlockHandler.setPendingConfigs +import com.lambda.interaction.handler.handlers.interacting.InteractedBlockHandler import com.lambda.interaction.handler.handlers.interacting.InteractedBlockHandler.startPending import com.lambda.interaction.handler.handlers.packet.PacketLimitHandler import com.lambda.interaction.handler.handlers.packet.PacketType -import com.lambda.interaction.manager.managers.Manager -import com.lambda.interaction.manager.managers.ManagerUtils.isPosBlocked -import com.lambda.interaction.manager.managers.PositionBlocking +import com.lambda.interaction.manager.Manager +import com.lambda.interaction.manager.ManagerUtils.isPosBlocked +import com.lambda.interaction.manager.PositionBlocking import com.lambda.interaction.manager.managers.breaking.BreakManager import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest import com.lambda.interaction.manager.managers.interacting.InteractManager.activeRequest @@ -45,7 +43,7 @@ import com.lambda.interaction.manager.managers.interacting.InteractManager.popul import com.lambda.interaction.manager.managers.interacting.InteractManager.potentialInteractions import com.lambda.interaction.manager.managers.interacting.InteractManager.processRequest import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest -import com.lambda.module.modules.world.AutoSign.signWriteDelay +import com.lambda.module.modules.world.AutoSign import com.lambda.threading.runConcurrent import com.lambda.threading.runSafeAutomated import com.lambda.threading.runSafeGameScheduled @@ -69,7 +67,6 @@ import net.minecraft.network.packet.c2s.play.PlayerInteractBlockC2SPacket import net.minecraft.network.packet.c2s.play.UpdateSignC2SPacket import net.minecraft.sound.SoundCategory import net.minecraft.util.ActionResult -import net.minecraft.util.ActionResult.PassToDefaultBlockAction import net.minecraft.util.Hand import net.minecraft.util.hit.BlockHitResult import net.minecraft.util.math.BlockPos @@ -90,7 +87,7 @@ object InteractManager : Manager( private val ClientPlayerEntity.validSneak get() = isSneaking == shouldSneak override val blockedPositions - get() = pendingActions.map { it.context.blockPos } + get() = InteractedBlockHandler.pendingActions.map { it.context.blockPos } private var cancellingSignScreens = 0 @@ -128,7 +125,7 @@ object InteractManager : Manager( } /** - * Accepts, and processes the request, as long as the current [activeRequest] is null, and the [BreakManager] has not + * Accepts, and processes the request, as long as the current [activeRequest] is null, and the [com.lambda.interaction.manager.managers.breaking.BreakManager] has not * been active this tick. If nowOrNothing is true, the request is cleared after the first process. * * @see processRequest @@ -166,7 +163,7 @@ object InteractManager : Manager( if (!hotbarRequest.done) break } var interactResult: InteractResult? = null - if (interactConfig.airPlace == AirPlaceMode.Grim) { + if (interactConfig.airPlace == InteractConfig.AirPlaceMode.Grim) { val inventoryRequest = inventoryRequest { swapHands() action { interactResult = performInteractions(request) } @@ -194,7 +191,7 @@ object InteractManager : Manager( if (!player.validSneak) return InteractResult.CompleteFailure if (tickStage !in interactConfig.tickStageMask) return InteractResult.CompleteFailure - val hand = if (interactConfig.airPlace == AirPlaceMode.Grim) Hand.OFF_HAND else Hand.MAIN_HAND + val hand = if (interactConfig.airPlace == InteractConfig.AirPlaceMode.Grim) Hand.OFF_HAND else Hand.MAIN_HAND val actionResult = if (ctx.preProcessingInfo.placing) placeBlock(ctx, request, hand) else interaction.interactBlock(player, if (ctx.preProcessingInfo.item != null) hand else Hand.MAIN_HAND, ctx.hitResult) @@ -214,7 +211,7 @@ object InteractManager : Manager( if (ctx.preProcessingInfo.placing && expectedState.block is AbstractSignBlock) { cancellingSignScreens++ runConcurrent { - delay(signWriteDelay) + delay(AutoSign.signWriteDelay) runSafeGameScheduled { connection.sendPacket { UpdateSignC2SPacket(ctx.blockPos, true, "", "", "", "") @@ -243,7 +240,7 @@ object InteractManager : Manager( * @see isPosBlocked */ private fun Automated.populateFrom(request: InteractRequest) { - setPendingConfigs() + InteractedBlockHandler.setPendingConfigs() potentialInteractions = request.contexts .distinctBy { it.blockPos } .filter { !isPosBlocked(it.blockPos) } @@ -287,7 +284,7 @@ object InteractManager : Manager( val actionResult = blockState.onUseWithItem(player.getStackInHand(hand), world, player, hand, hitResult) if (actionResult.isAccepted) return actionResult - if (actionResult is PassToDefaultBlockAction && hand == Hand.MAIN_HAND) { + if (actionResult is ActionResult.PassToDefaultBlockAction && hand == Hand.MAIN_HAND) { val actionResult2 = blockState.onUse(world, player, hitResult) if (actionResult2.isAccepted) return actionResult2 } @@ -411,4 +408,4 @@ object InteractManager : Manager( WrongSlot, CompleteFailure } -} +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractRequest.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractRequest.kt index 4aeb87bef..bc025b4d6 100644 --- a/src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractRequest.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractRequest.kt @@ -24,7 +24,7 @@ import com.lambda.interaction.construction.simulation.context.InteractContext import com.lambda.interaction.construction.simulation.result.BuildResult import com.lambda.interaction.construction.simulation.result.Dependent import com.lambda.interaction.construction.simulation.result.results.InteractResult -import com.lambda.interaction.manager.managers.Request +import com.lambda.interaction.manager.Request import com.lambda.threading.runSafe import com.lambda.util.BlockUtils.blockState import com.lambda.util.BlockUtils.matches diff --git a/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryRequest.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryRequest.kt index 82d81e619..3a7521c21 100644 --- a/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryRequest.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryRequest.kt @@ -20,7 +20,6 @@ package com.lambda.interaction.manager.managers.inventory import com.lambda.context.Automated import com.lambda.context.SafeContext import com.lambda.interaction.manager.Request -import com.lambda.interaction.manager.managers.Request import com.lambda.util.PacketUtils.sendPacket import com.lambda.util.player.SlotUtils.clickSlot import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap diff --git a/src/main/kotlin/com/lambda/interaction/material/container/containers/EnderChestContainer.kt b/src/main/kotlin/com/lambda/interaction/material/container/containers/EnderChestContainer.kt deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/main/kotlin/com/lambda/module/modules/player/EndermanLook.kt b/src/main/kotlin/com/lambda/module/modules/player/EndermanLook.kt index 2920b0328..64b24ffb5 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/EndermanLook.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/EndermanLook.kt @@ -23,8 +23,8 @@ import com.lambda.config.withEdits import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest -import com.lambda.interaction.managers.rotating.Rotation.Companion.rotationTo +import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotationTo +import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.math.distSq diff --git a/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt b/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt index ace2216cc..5004341e5 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt @@ -28,7 +28,7 @@ import com.lambda.module.tag.ModuleTag import com.lambda.task.RootTask.run import com.lambda.task.Task import com.lambda.task.tasks.BuildTask.Companion.breakAndCollect -import com.lambda.task.tasks.OpenContainerTask +import com.lambda.task.tasks.OpenContainerTask.Companion.openContainer import com.lambda.task.tasks.PlaceContainerTask import com.lambda.util.item.ItemUtils.shulkerBoxes import net.minecraft.item.Items @@ -62,7 +62,7 @@ object InventoryTweaks : Module( lastOpenScreen = null placeAndOpen = PlaceContainerTask(slot, this@InventoryTweaks).then { placePos -> placedPos = placePos - OpenContainerTask(placePos, this@InventoryTweaks).finally { screenHandler -> + openContainer(placePos).finally { screenHandler -> lastOpenScreen = screenHandler } }.run() diff --git a/src/main/kotlin/com/lambda/module/modules/player/Replay.kt b/src/main/kotlin/com/lambda/module/modules/player/Replay.kt index 698c00f3b..9ebf59a3b 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/Replay.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/Replay.kt @@ -34,7 +34,6 @@ import com.lambda.event.events.MovementEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.gui.components.ClickGuiLayout -import com.lambda.interaction.handlers.TimerHandler import com.lambda.interaction.handler.handlers.TimerHandler import com.lambda.interaction.manager.managers.rotating.Rotation import com.lambda.interaction.manager.managers.rotating.RotationMode diff --git a/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt b/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt index 87d2289e0..73c860bf0 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt @@ -35,7 +35,6 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.graphics.mc.renderer.ImmediateRenderer.Companion.immediateRenderer import com.lambda.graphics.util.DirectionMask import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.handlers.BaritoneHandler import com.lambda.interaction.handler.handlers.BaritoneHandler import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer 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 b37d23072..e9be28de2 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt @@ -38,13 +38,12 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.event.listener.UnsafeListener.Companion.listenUnsafe import com.lambda.graphics.mc.renderer.TickedRenderer.Companion.tickedRenderer import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.handlers.BaritoneHandler import com.lambda.interaction.handler.handlers.BaritoneHandler -import com.lambda.interaction.inventory.container.containers.external.EnderChestContainer import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.interaction.inventory.container.containers.InventoryContainer import com.lambda.interaction.inventory.container.containers.OffHandContainer +import com.lambda.interaction.inventory.container.containers.external.EnderChestContainer import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.interaction.manager.managers.rotating.Rotation @@ -56,7 +55,7 @@ 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.task.tasks.OpenContainerTask.Companion.openContainer import com.lambda.threading.runSafeAutomated import com.lambda.util.BlockUtils.blockEntity import com.lambda.util.BlockUtils.blockState @@ -748,10 +747,7 @@ object StashMover : Module( return } - OpenContainerTask( - pos, - StashMover - ).finally { + openContainer(pos).finally { finally(pos) }.execute(this@MoverBot) } diff --git a/src/main/kotlin/com/lambda/task/RootTask.kt b/src/main/kotlin/com/lambda/task/RootTask.kt index bc868991f..5c5c6af9d 100644 --- a/src/main/kotlin/com/lambda/task/RootTask.kt +++ b/src/main/kotlin/com/lambda/task/RootTask.kt @@ -29,7 +29,7 @@ object RootTask : Task() { } @Ta5kBuilder - fun Task<*>.run(task: TaskGenerator) { + fun Task<*>.run(task: TaskGenerator) { runSafe { task(Unit).execute(this@run) } diff --git a/src/main/kotlin/com/lambda/task/Task.kt b/src/main/kotlin/com/lambda/task/Task.kt index bd2f0dbaa..90c5be7fe 100644 --- a/src/main/kotlin/com/lambda/task/Task.kt +++ b/src/main/kotlin/com/lambda/task/Task.kt @@ -25,6 +25,8 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.modules.client.Client import com.lambda.module.modules.client.Client.verboseDebug +import com.lambda.task.Task.Companion.taskOrSkip +import com.lambda.task.Task.Companion.taskOrSkipOrNull import com.lambda.threading.runSafe import com.lambda.util.CommunicationUtils.logError import com.lambda.util.Nameable @@ -33,8 +35,8 @@ import kotlin.time.Duration.Companion.milliseconds import kotlin.time.DurationUnit import kotlin.time.toDuration -typealias TaskGenerator = SafeContext.(R) -> Task<*> -typealias TaskOrNullGenerator = SafeContext.(R) -> Task<*>? +typealias TaskGenerator = SafeContext.(R) -> Task +typealias TaskOrNullGenerator = SafeContext.(R) -> Task? typealias TaskUnitGenerator = SafeContext.(R) -> Unit @Suppress("unused") @@ -49,12 +51,12 @@ abstract class Task : Nameable, Muteable { val isCompleted get() = state == State.Completed val size: Int get() = subTasks.sumOf { it.size } + 1 - private var nextTask: TaskGenerator? = null - private var nextTaskOrNull: TaskOrNullGenerator? = null + private var nextTask: TaskGenerator? = null + private var nextTaskOrNull: TaskOrNullGenerator? = null private var onFinish: TaskUnitGenerator? = null - private var onFail: TaskGenerator? = null - private var onFailOrNull: TaskOrNullGenerator? = null + private var onFail: TaskGenerator? = null + private var onFailOrNull: TaskOrNullGenerator? = null private var softFail = false enum class State { @@ -286,8 +288,7 @@ abstract class Task : Nameable, Muteable { * @return The current task instance (`Task`) to allow method chaining. */ @Ta5kBuilder - fun then(taskGenerator: TaskGenerator): Task { - require(nextTask == null && nextTaskOrNull == null) { "Cannot link multiple tasks to a single task" } + fun then(taskGenerator: TaskGenerator): Task { nextTask = taskGenerator return this } @@ -307,14 +308,42 @@ abstract class Task : Nameable, Muteable { * @return The current task instance (`Task`) to allow method chaining. */ @Ta5kBuilder - fun thenOrNull(taskGenerator: TaskOrNullGenerator): Task { - require(nextTask == null && nextTaskOrNull == null) { "Cannot link multiple tasks to a single task" } + fun thenOrNull(taskGenerator: TaskOrNullGenerator): Task { nextTaskOrNull = taskGenerator return this } + /** + * Chains an optional step whose typed result is forwarded to a mandatory continuation. + * + * If the [optional] generator returns a non-null [Task] of type [T] that completes + * with a result, the [then] continuation receives that result. If the [optional] + * generator returns null (step skipped), the [then] continuation receives null. + * + * This allows downstream steps to use the output of an optional step without + * storing state outside the task chain. + * + * @param T The result type of the optional step. + * @param optional A generator that may produce a task, or null to skip. + * @param then A continuation that receives the optional step's result as [T?]. + * @return The current task instance (`Task`) to allow method chaining. + */ + @Ta5kBuilder + fun thenOrSkip( + optional: TaskOrNullGenerator, + then: TaskOrNullGenerator + ): Task { + thenOrNull { result -> + val step = optional(result) + step?.thenOrNull { stepResult -> + then(stepResult) + } ?: then(null) + } + return this + } + @Ta5kBuilder - fun onFail(taskGenerator: TaskGenerator): Task { + fun onFail(taskGenerator: TaskGenerator): Task { require(onFail == null && onFailOrNull == null) { "Cannot have multiple onFail callbacks on a single task" } onFail = taskGenerator softFail() @@ -322,7 +351,7 @@ abstract class Task : Nameable, Muteable { } @Ta5kBuilder - fun onFailOrNull(taskGenerator: TaskOrNullGenerator): Task { + fun onFailOrNull(taskGenerator: TaskOrNullGenerator): Task { require(onFail == null && onFailOrNull == null) { "Cannot have multiple onFail callbacks on a single task" } onFailOrNull = taskGenerator softFail() @@ -372,4 +401,76 @@ abstract class Task : Nameable, Muteable { appendTaskTree(it, level + 1) } } + + companion object { + @Ta5kBuilder + fun SafeContext.taskOrSkip( + optional: TaskOrNullGenerator, + then: TaskGenerator + ): Task<*> { + val step = optional(Unit) + return step?.thenOrNull { stepResult -> + then(stepResult) + } ?: then(null) + } + + @Ta5kBuilder + fun SafeContext.taskOrSkipOrNull( + optional: TaskOrNullGenerator, + then: TaskOrNullGenerator + ): Task<*>? { + val step = optional(Unit) + return step?.thenOrNull { stepResult -> + then(stepResult) + } ?: then(null) + } + + /** + * Creates a task that runs a chain of subtasks and emits a custom result. + * + * Use this when you need an intermediate result (e.g. a [net.minecraft.util.math.BlockPos] from a + * placement step) to be the result type of the returned task, allowing it + * to flow through [taskOrSkip] / [thenOrSkip] / [taskOrSkipOrNull] as a nullable scoped value. + * + * The [builder] receives a `success` callback. Call `success(value)` when + * the chain reaches the point where the desired result is available. + * + * ```kotlin + * wrappedTask("Setup Ender Chest") { success -> + * acquireStack(selection).then { slot -> + * placeContainer(slot).then { pos -> + * openContainer(pos).finally { success(pos) } + * } + * } + * } + * ``` + */ + @Ta5kBuilder + fun wrappedTask( + name: String, + builder: SafeContext.(success: (R) -> Unit) -> Task<*> + ) = object : Task() { + override val name = name + private val self = this + + override fun SafeContext.onStart() { + val chain = builder { result -> self.success(result) } + chain.execute(self) + } + } + + @Ta5kBuilder + fun nullableWrappedTask( + name: String, + builder: SafeContext.(success: (R?) -> Unit) -> Task<*>? + ) = object : Task() { + override val name = name + private val self = this + + override fun SafeContext.onStart() { + val chain = builder { result -> self.success(result) } + chain?.execute(self) ?: self.success(null) + } + } + } } diff --git a/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt b/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt index 0f802c600..1f8e52634 100644 --- a/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt @@ -46,7 +46,6 @@ import com.lambda.interaction.construction.simulation.result.results.GenericResu import com.lambda.interaction.construction.simulation.result.results.InteractResult import com.lambda.interaction.construction.simulation.result.results.PreSimResult import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.handlers.BaritoneHandler import com.lambda.interaction.handler.handlers.BaritoneHandler import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer import com.lambda.interaction.manager.managers.breaking.BreakRequestBuilder.Companion.breakRequest diff --git a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt index a495f49c9..024f56bad 100644 --- a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt @@ -43,14 +43,13 @@ class ContainerTransferTask @Ta5kBuilder constructor( runSafeAutomated { if (fromContainer is ExternalContainer && toContainer is ExternalContainer) { fromContainer.accessThen( - true, - { - HotbarAndInventoryContainer.transferByTask(stackSelection, toContainer) - .finally { slot -> success(slot) } + afterOpen = { + fromContainer.transferByTask(stackSelection, HotbarAndInventoryContainer, failIfNoStack) } ) { - fromContainer.transferByTask(stackSelection, HotbarAndInventoryContainer) - }.execute(this@ContainerTransferTask) + HotbarAndInventoryContainer.transferByTask(stackSelection, toContainer, failIfNoStack) + .finally { slot -> success(slot) } + }?.execute(this@ContainerTransferTask) return@listen } diff --git a/src/main/kotlin/com/lambda/task/tasks/EatTask.kt b/src/main/kotlin/com/lambda/task/tasks/EatTask.kt index 9e086f5e4..0677ba082 100644 --- a/src/main/kotlin/com/lambda/task/tasks/EatTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/EatTask.kt @@ -26,7 +26,6 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.interaction.inventory.container.containers.InventoryContainer import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest -import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest import com.lambda.task.Task import com.lambda.threading.runSafeAutomated import net.minecraft.item.ItemStack diff --git a/src/main/kotlin/com/lambda/task/tasks/NoopTask.kt b/src/main/kotlin/com/lambda/task/tasks/NoopTask.kt index 47c34dea6..011031a41 100644 --- a/src/main/kotlin/com/lambda/task/tasks/NoopTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/NoopTask.kt @@ -20,7 +20,7 @@ package com.lambda.task.tasks import com.lambda.context.SafeContext import com.lambda.task.Task -class NoopTask : Task() { +class NoopTask @Ta5kBuilder constructor() : Task() { override val name = "No-operation task" override fun SafeContext.onStart() { diff --git a/src/main/kotlin/com/lambda/task/tasks/PlaceContainerTask.kt b/src/main/kotlin/com/lambda/task/tasks/PlaceContainerTask.kt index 96e807e4d..065f6bfa5 100644 --- a/src/main/kotlin/com/lambda/task/tasks/PlaceContainerTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/PlaceContainerTask.kt @@ -41,12 +41,17 @@ import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Direction class PlaceContainerTask @Ta5kBuilder constructor( - val slot: Slot, + val slot: Slot?, automated: Automated ) : Task(), Automated by automated { - override val name: String get() = "Placing container ${slot.stack.name.string}" + override val name: String get() = "Placing container ${slot?.stack?.name?.string}" override fun SafeContext.onStart() { + if (slot == null) { + failure("No slot provided") + return + } + val results = runSafeAutomated { BlockPos.iterateOutwards(player.blockPos, 4, 3, 4) .map { it.blockPos } @@ -97,13 +102,14 @@ class PlaceContainerTask @Ta5kBuilder constructor( else -> false } + @Suppress("unused") companion object { @Ta5kBuilder context(automated: Automated) - fun placeContainer(slot: Slot) = PlaceContainerTask(slot, automated) + fun placeContainer(slot: Slot?) = PlaceContainerTask(slot, automated) @Ta5kBuilder context(automated: Automated) - fun placeContainer(slot: () -> Slot) = PlaceContainerTask(slot(), automated) + fun placeContainer(slot: () -> Slot?) = PlaceContainerTask(slot(), automated) } } diff --git a/src/main/kotlin/com/lambda/task/tasks/SlotByCacheTask.kt b/src/main/kotlin/com/lambda/task/tasks/SlotByCacheTask.kt index 057b98d41..4402252b7 100644 --- a/src/main/kotlin/com/lambda/task/tasks/SlotByCacheTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/SlotByCacheTask.kt @@ -33,4 +33,9 @@ class SlotByCacheTask @Ta5kBuilder constructor( if (slot != null) success(slot) else failure("Failed to find matching slot by cache: $slotCache") } + + companion object { + @Ta5kBuilder + fun slotByCache(slotCache: Slot) = SlotByCacheTask(slotCache) + } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/util/player/SlotUtils.kt b/src/main/kotlin/com/lambda/util/player/SlotUtils.kt index 65a60cb96..c8cece603 100644 --- a/src/main/kotlin/com/lambda/util/player/SlotUtils.kt +++ b/src/main/kotlin/com/lambda/util/player/SlotUtils.kt @@ -68,5 +68,6 @@ object SlotUtils { inventory::class == slot.inventory::class && id == slot.id && x == slot.x && - y == slot.y + y == slot.y && + ItemStack.areEqual(stack, slot.stack) } From 4b3d4b40f470cd60ab972f7f399c4c0cc054c84a Mon Sep 17 00:00:00 2001 From: beanbag44 <107891830+beanbag44@users.noreply.github.com> Date: Sun, 23 Aug 2026 16:55:25 +0100 Subject: [PATCH 07/43] initial merge --- .../inventory/container/Container.kt | 7 +- .../containers/external/ChestContainer.kt | 8 +- .../external/EnderChestContainer.kt | 10 +- .../external/PlacedShulkerBoxContainer.kt | 8 +- .../external/ShulkerBoxContainer.kt | 10 +- .../module/modules/movement/AutoWalk.kt | 4 +- .../modules/movement/elytrafly/ElytraFly.kt | 8 +- .../movement/elytrafly/GrimFireworkBox.kt | 2 +- .../lambda/module/modules/player/LineLock.kt | 8 +- src/main/kotlin/com/lambda/task/Task.kt | 112 +----------------- 10 files changed, 36 insertions(+), 141 deletions(-) diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/Container.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/Container.kt index 195b4c803..31ea7d518 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/Container.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/Container.kt @@ -26,10 +26,9 @@ import com.lambda.interaction.inventory.StackSelection import com.lambda.interaction.inventory.container.containers.external.ShulkerBoxContainer import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest -import com.lambda.task.Task.Companion.taskOrSkipOrNull import com.lambda.task.Task.Ta5kBuilder -import com.lambda.task.TaskOrNullGenerator import com.lambda.task.tasks.ContainerTransferTask +import com.lambda.task.wrappers.TaskOrNullSupplier import com.lambda.util.Nameable import com.lambda.util.item.ItemStackUtils.count import com.lambda.util.item.ItemStackUtils.empty @@ -131,8 +130,8 @@ abstract class Container( context(automatedSafeContext: AutomatedSafeContext) open fun accessThen( closeAfter: Boolean = true, - afterOpen: TaskOrNullGenerator = { null }, - afterClose: TaskOrNullGenerator = { null } + afterOpen: TaskOrNullSupplier = { null }, + afterClose: TaskOrNullSupplier = { null } ) = with(automatedSafeContext) { taskOrSkipOrNull({ afterOpen(Unit) }) { result -> if (closeAfter) afterClose(result) diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ChestContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ChestContainer.kt index 9830a1e24..23625a41d 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ChestContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ChestContainer.kt @@ -23,10 +23,10 @@ import com.lambda.interaction.handler.handlers.ContainerHandler.lastInteractedBl import com.lambda.interaction.inventory.container.Container import com.lambda.interaction.inventory.container.ExternalContainer import com.lambda.interaction.inventory.container.PlacedContainer -import com.lambda.task.Task.Companion.taskOrSkipOrNull -import com.lambda.task.TaskOrNullGenerator import com.lambda.task.tasks.OpenContainerTask.Companion.openContainer import com.lambda.task.tasks.SimpleActionTask.Companion.simpleAction +import com.lambda.task.wrappers.TaskOrNullSupplier +import com.lambda.task.wrappers.thenOrNull import com.lambda.util.extension.containerSlots import com.lambda.util.text.buildText import com.lambda.util.text.highlighted @@ -72,8 +72,8 @@ data class ChestContainer( context(automatedSafeContext: AutomatedSafeContext) override fun accessThen( closeAfter: Boolean, - afterOpen: TaskOrNullGenerator, - afterClose: TaskOrNullGenerator + afterOpen: TaskOrNullSupplier, + afterClose: TaskOrNullSupplier ) = with(automatedSafeContext) { taskOrSkipOrNull( diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/EnderChestContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/EnderChestContainer.kt index a046e168a..0e1c1f4cd 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/EnderChestContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/EnderChestContainer.kt @@ -23,14 +23,14 @@ import com.lambda.interaction.handler.handlers.ContainerHandler.lastInteractedBl import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.select import com.lambda.interaction.inventory.container.Container import com.lambda.interaction.inventory.container.ExternalContainer -import com.lambda.task.Task.Companion.taskOrSkipOrNull -import com.lambda.task.Task.Companion.wrappedTask -import com.lambda.task.TaskOrNullGenerator import com.lambda.task.tasks.AcquireStackTask.Companion.acquireStack import com.lambda.task.tasks.BuildTask.Companion.breakAndCollect import com.lambda.task.tasks.OpenContainerTask.Companion.openContainer import com.lambda.task.tasks.PlaceContainerTask.Companion.placeContainer import com.lambda.task.tasks.SimpleActionTask.Companion.simpleAction +import com.lambda.task.wrappers.TaskOrNullSupplier +import com.lambda.task.wrappers.then +import com.lambda.task.wrappers.thenOrNull import com.lambda.util.extension.containerSlots import com.lambda.util.text.buildText import com.lambda.util.text.literal @@ -59,8 +59,8 @@ object EnderChestContainer : Container(Rank.EnderChest), ExternalContainer { context(automatedSafeContext: AutomatedSafeContext) override fun accessThen( closeAfter: Boolean, - afterOpen: TaskOrNullGenerator, - afterClose: TaskOrNullGenerator + afterOpen: TaskOrNullSupplier, + afterClose: TaskOrNullSupplier ) = with(automatedSafeContext) { taskOrSkipOrNull( diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/PlacedShulkerBoxContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/PlacedShulkerBoxContainer.kt index 009e58def..1bd6bff54 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/PlacedShulkerBoxContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/PlacedShulkerBoxContainer.kt @@ -22,10 +22,10 @@ import com.lambda.context.AutomatedSafeContext import com.lambda.interaction.handler.handlers.ContainerHandler.lastInteractedBlockEntity import com.lambda.interaction.inventory.container.Container import com.lambda.interaction.inventory.container.PlacedContainer -import com.lambda.task.Task.Companion.taskOrSkipOrNull -import com.lambda.task.TaskOrNullGenerator import com.lambda.task.tasks.OpenContainerTask import com.lambda.task.tasks.SimpleActionTask.Companion.simpleAction +import com.lambda.task.wrappers.TaskOrNullSupplier +import com.lambda.task.wrappers.thenOrNull import com.lambda.util.extension.containerSlots import com.lambda.util.text.buildText import com.lambda.util.text.highlighted @@ -68,8 +68,8 @@ class PlacedShulkerBoxContainer( context(automatedSafeContext: AutomatedSafeContext) override fun accessThen( closeAfter: Boolean, - afterOpen: TaskOrNullGenerator, - afterClose: TaskOrNullGenerator + afterOpen: TaskOrNullSupplier, + afterClose: TaskOrNullSupplier ) = with(automatedSafeContext) { taskOrSkipOrNull( diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ShulkerBoxContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ShulkerBoxContainer.kt index 01a4b312c..18c45fea5 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ShulkerBoxContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ShulkerBoxContainer.kt @@ -25,13 +25,13 @@ import com.lambda.interaction.inventory.container.Container import com.lambda.interaction.inventory.container.ExternalContainer import com.lambda.interaction.inventory.container.NestedContainer import com.lambda.interaction.inventory.container.containers.HotbarContainer -import com.lambda.task.Task.Companion.nullableWrappedTask -import com.lambda.task.Task.Companion.taskOrSkipOrNull -import com.lambda.task.TaskOrNullGenerator import com.lambda.task.tasks.BuildTask.Companion.breakAndCollect import com.lambda.task.tasks.OpenContainerTask.Companion.openContainer import com.lambda.task.tasks.PlaceContainerTask.Companion.placeContainer import com.lambda.task.tasks.SimpleActionTask.Companion.simpleAction +import com.lambda.task.wrappers.TaskOrNullSupplier +import com.lambda.task.wrappers.then +import com.lambda.task.wrappers.thenOrNull import com.lambda.util.extension.containerSlots import com.lambda.util.text.buildText import com.lambda.util.text.highlighted @@ -73,8 +73,8 @@ data class ShulkerBoxContainer( context(automatedSafeContext: AutomatedSafeContext) override fun accessThen( closeAfter: Boolean, - afterOpen: TaskOrNullGenerator, - afterClose: TaskOrNullGenerator + afterOpen: TaskOrNullSupplier, + afterClose: TaskOrNullSupplier ) = with(automatedSafeContext) { taskOrSkipOrNull( diff --git a/src/main/kotlin/com/lambda/module/modules/movement/AutoWalk.kt b/src/main/kotlin/com/lambda/module/modules/movement/AutoWalk.kt index 2c5492a24..d0dc18219 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/AutoWalk.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/AutoWalk.kt @@ -22,8 +22,8 @@ import com.lambda.context.SafeContext import com.lambda.event.events.MovementEvent import com.lambda.event.events.PacketEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.breaking.BreakManager -import com.lambda.interaction.managers.interacting.InteractManager +import com.lambda.interaction.manager.managers.breaking.BreakManager +import com.lambda.interaction.manager.managers.interacting.InteractManager import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.math.MathUtils.toDouble diff --git a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFly.kt b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFly.kt index 4ea420a64..9026773f5 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFly.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFly.kt @@ -17,19 +17,19 @@ package com.lambda.module.modules.movement.elytrafly +import com.lambda.config.ConfigEditor.editSetting +import com.lambda.config.ConfigEditor.forEachSetting +import com.lambda.config.ConfigEditor.hideAllExcept import com.lambda.config.Tab import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig -import com.lambda.config.editSetting import com.lambda.config.entries.Setting.Companion.onValueChange -import com.lambda.config.forEachSetting -import com.lambda.config.hideAllExcept import com.lambda.config.withEdits import com.lambda.event.events.ClientEvent import com.lambda.event.events.MovementEvent 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.rotating.RotationManager +import com.lambda.interaction.manager.managers.rotating.RotationManager import com.lambda.module.Module import com.lambda.module.modules.movement.elytrafly.modes.BounceElytraFly import com.lambda.module.modules.movement.elytrafly.modes.GeneralElytraFly diff --git a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/GrimFireworkBox.kt b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/GrimFireworkBox.kt index dd3344672..c70ead991 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/GrimFireworkBox.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/GrimFireworkBox.kt @@ -18,7 +18,7 @@ package com.lambda.module.modules.movement.elytrafly import com.lambda.context.SafeContext -import com.lambda.interaction.managers.rotating.RotationManager +import com.lambda.interaction.manager.managers.rotating.RotationManager import net.minecraft.util.math.MathHelper import net.minecraft.util.math.Vec3d import kotlin.math.asin diff --git a/src/main/kotlin/com/lambda/module/modules/player/LineLock.kt b/src/main/kotlin/com/lambda/module/modules/player/LineLock.kt index df99b71f8..b1105ff51 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/LineLock.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/LineLock.kt @@ -17,17 +17,17 @@ package com.lambda.module.modules.player +import com.lambda.config.ConfigEditor.editSetting +import com.lambda.config.ConfigEditor.hideAllExcept import com.lambda.config.Group import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig -import com.lambda.config.editSetting -import com.lambda.config.hideAllExcept import com.lambda.config.withEdits import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.graphics.mc.renderer.ImmediateRenderer.Companion.immediateRenderer -import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest -import com.lambda.interaction.managers.rotating.RotationMode +import com.lambda.interaction.manager.managers.rotating.RotationMode +import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.threading.runSafe diff --git a/src/main/kotlin/com/lambda/task/Task.kt b/src/main/kotlin/com/lambda/task/Task.kt index 6071df718..109b3c1c4 100644 --- a/src/main/kotlin/com/lambda/task/Task.kt +++ b/src/main/kotlin/com/lambda/task/Task.kt @@ -25,8 +25,6 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.modules.client.Client.verboseDebug import com.lambda.task.wrappers.onFail -import com.lambda.task.Task.Companion.taskOrSkip -import com.lambda.task.Task.Companion.taskOrSkipOrNull import com.lambda.threading.runSafe import com.lambda.util.CommunicationUtils.logError import com.lambda.util.Nameable @@ -44,7 +42,6 @@ abstract class Task : Nameable, Muteable { override val isMuted: Boolean get() = state == State.Paused || state == State.Init var age = 0 private val depth: Int get() = parent?.depth?.plus(1) ?: 0 - val isCompleted get() = state == State.Completed val size: Int get() = subTasks.sumOf { it.size } + 1 private val successCallbacks = mutableListOf Unit>() @@ -222,13 +219,13 @@ abstract class Task : Nameable, Muteable { } @Ta5kBuilder - protected open fun onSubTaskCompletion(subTask: Task<*>) { - activate() + protected open fun onSubTaskFailure(subTask: Task<*>, cause: Throwable) { + failure(cause) } @Ta5kBuilder - protected open fun onSubTaskFailure(subTask: Task<*>, cause: Throwable) { - failure(cause) + protected open fun onSubTaskCompletion(subTask: Task<*>) { + activate() } /** @@ -240,35 +237,6 @@ abstract class Task : Nameable, Muteable { return this } - /** - * Chains an optional step whose typed result is forwarded to a mandatory continuation. - * - * If the [optional] generator returns a non-null [Task] of type [T] that completes - * with a result, the [then] continuation receives that result. If the [optional] - * generator returns null (step skipped), the [then] continuation receives null. - * - * This allows downstream steps to use the output of an optional step without - * storing state outside the task chain. - * - * @param T The result type of the optional step. - * @param optional A generator that may produce a task, or null to skip. - * @param then A continuation that receives the optional step's result as [T?]. - * @return The current task instance (`Task`) to allow method chaining. - */ - @Ta5kBuilder - fun thenOrSkip( - optional: TaskOrNullGenerator, - then: TaskOrNullGenerator - ): Task { - thenOrNull { result -> - val step = optional(result) - step?.thenOrNull { stepResult -> - then(stepResult) - } ?: then(null) - } - return this - } - /** * Registers a callback for if the task fails. * @@ -305,76 +273,4 @@ abstract class Task : Nameable, Muteable { appendTaskTree(it, level + 1) } } - - companion object { - @Ta5kBuilder - fun SafeContext.taskOrSkip( - optional: TaskOrNullGenerator, - then: TaskGenerator - ): Task<*> { - val step = optional(Unit) - return step?.thenOrNull { stepResult -> - then(stepResult) - } ?: then(null) - } - - @Ta5kBuilder - fun SafeContext.taskOrSkipOrNull( - optional: TaskOrNullGenerator, - then: TaskOrNullGenerator - ): Task<*>? { - val step = optional(Unit) - return step?.thenOrNull { stepResult -> - then(stepResult) - } ?: then(null) - } - - /** - * Creates a task that runs a chain of subtasks and emits a custom result. - * - * Use this when you need an intermediate result (e.g. a [net.minecraft.util.math.BlockPos] from a - * placement step) to be the result type of the returned task, allowing it - * to flow through [taskOrSkip] / [thenOrSkip] / [taskOrSkipOrNull] as a nullable scoped value. - * - * The [builder] receives a `success` callback. Call `success(value)` when - * the chain reaches the point where the desired result is available. - * - * ```kotlin - * wrappedTask("Setup Ender Chest") { success -> - * acquireStack(selection).then { slot -> - * placeContainer(slot).then { pos -> - * openContainer(pos).finally { success(pos) } - * } - * } - * } - * ``` - */ - @Ta5kBuilder - fun wrappedTask( - name: String, - builder: SafeContext.(success: (R) -> Unit) -> Task<*> - ) = object : Task() { - override val name = name - private val self = this - - override fun SafeContext.onStart() { - val chain = builder { result -> self.success(result) } - chain.execute(self) - } - } - - @Ta5kBuilder - fun nullableWrappedTask( - name: String, - builder: SafeContext.(success: (R?) -> Unit) -> Task<*>? - ) = object : Task() { - override val name = name - private val self = this - - override fun SafeContext.onStart() { - val chain = builder { result -> self.success(result) } - chain?.execute(self) ?: self.success(null) - } - } - } } From d2966cf4e3f5be6510f2ae9e6868bdf806272779 Mon Sep 17 00:00:00 2001 From: beanbag44 <107891830+beanbag44@users.noreply.github.com> Date: Fri, 28 Aug 2026 21:35:43 +0100 Subject: [PATCH 08/43] idk, a bunch of stuff --- .../brigadier/argument/BlockArguments.kt | 20 +-- .../brigadier/argument/GeometricArguments.kt | 25 ++- .../brigadier/argument/IdentifierArguments.kt | 60 +++---- .../brigadier/argument/ItemArguments.kt | 30 ++-- .../brigadier/argument/MiscArguments.kt | 25 ++- .../lambda/brigadier/argument/NBTArguments.kt | 30 ++-- .../brigadier/argument/PlayerArguments.kt | 70 ++------ .../brigadier/argument/PrimitiveArguments.kt | 50 +++--- .../lambda/command/commands/BuildCommand.kt | 2 +- .../lambda/config/blocks/InventoryConfig.kt | 21 +-- .../lambda/config/blocks/InventorySettings.kt | 17 +- .../config/settings/complex/KeybindSetting.kt | 1 - src/main/kotlin/com/lambda/gui/MenuBar.kt | 1 - .../kotlin/com/lambda/gui/dsl/ImGuiBuilder.kt | 4 +- .../simulation/result/results/BreakResult.kt | 3 +- .../result/results/GenericResult.kt | 3 +- .../handler/handlers/ContainerHandler.kt | 4 +- .../interaction/inventory/StackSelection.kt | 13 +- .../inventory/container/Container.kt | 15 +- .../inventory/container/ExternalContainer.kt | 3 +- .../inventory/container/NestedContainer.kt | 2 +- .../inventory/container/OpenContainerTask.kt} | 21 ++- .../container/OpenedContainerContext.kt | 40 +++++ .../containers/external/ChestContainer.kt | 50 +++--- .../external/EnderChestContainer.kt | 101 ++++++----- .../external/PlacedShulkerBoxContainer.kt | 48 +++-- .../external/ShulkerBoxContainer.kt | 99 ++++++----- .../manager/managers/breaking/BreakRequest.kt | 68 +++---- .../managers/interacting/InteractRequest.kt | 47 ++--- .../module/modules/combat/PlayerTrap.kt | 2 +- .../lambda/module/modules/combat/Surround.kt | 2 +- .../module/modules/debug/ContainerTest.kt | 2 +- .../module/modules/movement/TargetStrafe.kt | 4 +- .../modules/movement/elytrafly/ElytraFly.kt | 6 +- .../elytrafly/modes/GrimControlElytraFly.kt | 4 - .../lambda/module/modules/player/AutoEat.kt | 4 +- .../lambda/module/modules/player/FastBreak.kt | 2 +- .../module/modules/player/InventoryTweaks.kt | 8 +- .../lambda/module/modules/player/LineLock.kt | 4 +- .../module/modules/player/StackReplenish.kt | 2 +- .../lambda/module/modules/world/AirPlace.kt | 2 +- .../lambda/module/modules/world/AutoPortal.kt | 4 +- .../module/modules/world/AutoVillagerCycle.kt | 4 +- .../module/modules/world/HighwayTools.kt | 2 +- .../com/lambda/module/modules/world/Nuker.kt | 2 +- .../lambda/module/modules/world/Printer.kt | 2 +- .../lambda/module/modules/world/Scaffold.kt | 2 +- .../lambda/module/modules/world/StashMover.kt | 7 +- src/main/kotlin/com/lambda/task/RootTask.kt | 3 +- src/main/kotlin/com/lambda/task/Task.kt | 10 +- .../task/tasks/AcquirePlacedBlockTask.kt | 51 +++--- .../com/lambda/task/tasks/AcquireStackTask.kt | 27 ++- .../kotlin/com/lambda/task/tasks/BuildTask.kt | 100 +++++------ .../task/tasks/ContainerTransferTask.kt | 73 ++++---- .../kotlin/com/lambda/task/tasks/EatTask.kt | 13 +- .../lambda/task/tasks/FindPlacedBlockTask.kt | 45 +++++ .../lambda/task/tasks/OpenContainerTask.kt | 21 ++- .../lambda/task/tasks/PlaceContainerTask.kt | 25 ++- .../com/lambda/task/tasks/SimpleActionTask.kt | 13 +- .../com/lambda/task/tasks/SlotByCacheTask.kt | 11 +- .../wrappers/ActionTasks.kt} | 27 ++- .../lambda/task/tasks/wrappers/BranchTasks.kt | 103 +++++++++++ .../lambda/task/tasks/wrappers/OnFailTasks.kt | 167 ++++++++++++++++++ .../lambda/task/tasks/wrappers/ResultTasks.kt | 57 ++++++ .../{ => tasks}/wrappers/SequenceTasks.kt | 58 ++++-- .../task/{ => tasks}/wrappers/SoftFailTask.kt | 7 +- .../com/lambda/task/wrappers/RecoveryTasks.kt | 92 ---------- .../com/lambda/util/world/WorldUtils.kt | 14 +- 68 files changed, 1049 insertions(+), 806 deletions(-) rename src/main/kotlin/com/lambda/{task/tasks/NoopTask.kt => interaction/inventory/container/OpenContainerTask.kt} (60%) create mode 100644 src/main/kotlin/com/lambda/interaction/inventory/container/OpenedContainerContext.kt create mode 100644 src/main/kotlin/com/lambda/task/tasks/FindPlacedBlockTask.kt rename src/main/kotlin/com/lambda/task/{wrappers/ActionTask.kt => tasks/wrappers/ActionTasks.kt} (69%) create mode 100644 src/main/kotlin/com/lambda/task/tasks/wrappers/BranchTasks.kt create mode 100644 src/main/kotlin/com/lambda/task/tasks/wrappers/OnFailTasks.kt create mode 100644 src/main/kotlin/com/lambda/task/tasks/wrappers/ResultTasks.kt rename src/main/kotlin/com/lambda/task/{ => tasks}/wrappers/SequenceTasks.kt (58%) rename src/main/kotlin/com/lambda/task/{ => tasks}/wrappers/SoftFailTask.kt (89%) delete mode 100644 src/main/kotlin/com/lambda/task/wrappers/RecoveryTasks.kt diff --git a/src/main/kotlin/com/lambda/brigadier/argument/BlockArguments.kt b/src/main/kotlin/com/lambda/brigadier/argument/BlockArguments.kt index bafbb787a..9c0034f3f 100644 --- a/src/main/kotlin/com/lambda/brigadier/argument/BlockArguments.kt +++ b/src/main/kotlin/com/lambda/brigadier/argument/BlockArguments.kt @@ -37,9 +37,8 @@ import java.util.function.Predicate * @see BlockPredicateArgumentType.getBlockPredicate */ @BrigadierDsl -fun DefaultArgumentReader.value(): Predicate { - return BlockPredicateArgumentType.getBlockPredicate(context.assumeSourceNotUsed(), name) -} +fun DefaultArgumentReader.value(): Predicate = + BlockPredicateArgumentType.getBlockPredicate(context.assumeSourceNotUsed(), name) /** * Reads the [BlockStateArgument] value of the argument in @@ -48,9 +47,8 @@ fun DefaultArgumentReader.value(): Predicate.value(): BlockStateArgument { - return BlockStateArgumentType.getBlockState(context.assumeSourceNotUsed(), name) -} +fun DefaultArgumentReader.value(): BlockStateArgument = + BlockStateArgumentType.getBlockState(context.assumeSourceNotUsed(), name) /** * Creates a block predicate argument with [name] as the parameter name. @@ -62,9 +60,8 @@ fun DefaultArgumentReader.value(): BlockStateArgument { fun blockPredicate( name: String, registryAccess: CommandRegistryAccess, -): DefaultArgumentConstructor { - return argument(name, BlockPredicateArgumentType.blockPredicate(registryAccess)) -} +): DefaultArgumentConstructor = + argument(name, BlockPredicateArgumentType.blockPredicate(registryAccess)) /** * Creates a block state argument with [name] as the parameter name. @@ -76,6 +73,5 @@ fun blockPredicate( fun blockState( name: String, registryAccess: CommandRegistryAccess, -): DefaultArgumentConstructor { - return argument(name, BlockStateArgumentType.blockState(registryAccess)) -} +): DefaultArgumentConstructor = + argument(name, BlockStateArgumentType.blockState(registryAccess)) diff --git a/src/main/kotlin/com/lambda/brigadier/argument/GeometricArguments.kt b/src/main/kotlin/com/lambda/brigadier/argument/GeometricArguments.kt index 6c2117456..065bb8e0c 100644 --- a/src/main/kotlin/com/lambda/brigadier/argument/GeometricArguments.kt +++ b/src/main/kotlin/com/lambda/brigadier/argument/GeometricArguments.kt @@ -167,9 +167,8 @@ fun rotation(name: String): DefaultArgumentConstructor swizzle( name: String, -): DefaultArgumentConstructor { - return argument(name, SwizzleArgumentType.swizzle()) -} +): DefaultArgumentConstructor = + argument(name, SwizzleArgumentType.swizzle()) /** * Creates a block pos argument with [name] as the parameter name. @@ -177,9 +176,8 @@ fun swizzle( @BrigadierDsl fun blockPos( name: String, -): DefaultArgumentConstructor { - return argument(name, BlockPosArgumentType.blockPos()) -} +): DefaultArgumentConstructor = + argument(name, BlockPosArgumentType.blockPos()) /** * Creates a column pos argument with [name] as the parameter name. @@ -187,9 +185,8 @@ fun blockPos( @BrigadierDsl fun columnPos( name: String, -): DefaultArgumentConstructor { - return argument(name, ColumnPosArgumentType.columnPos()) -} +): DefaultArgumentConstructor = + argument(name, ColumnPosArgumentType.columnPos()) /** * Creates a vec2 argument with [name] as the parameter name. @@ -200,9 +197,8 @@ fun columnPos( fun vec2( name: String, centerIntegers: Boolean = false, -): DefaultArgumentConstructor { - return argument(name, Vec2ArgumentType.vec2(centerIntegers)) -} +): DefaultArgumentConstructor = + argument(name, Vec2ArgumentType.vec2(centerIntegers)) /** * Creates a vec3 argument with [name] as the parameter name. @@ -213,6 +209,5 @@ fun vec2( fun vec3( name: String, centerIntegers: Boolean = false, -): DefaultArgumentConstructor { - return argument(name, Vec3ArgumentType.vec3(centerIntegers)) -} +): DefaultArgumentConstructor = + argument(name, Vec3ArgumentType.vec3(centerIntegers)) diff --git a/src/main/kotlin/com/lambda/brigadier/argument/IdentifierArguments.kt b/src/main/kotlin/com/lambda/brigadier/argument/IdentifierArguments.kt index c88a39cb3..d2a3b0218 100644 --- a/src/main/kotlin/com/lambda/brigadier/argument/IdentifierArguments.kt +++ b/src/main/kotlin/com/lambda/brigadier/argument/IdentifierArguments.kt @@ -27,8 +27,6 @@ import com.lambda.brigadier.assumeSourceNotUsed import net.minecraft.advancement.AdvancementEntry import net.minecraft.command.argument.IdentifierArgumentType import net.minecraft.command.argument.RegistryKeyArgumentType -import net.minecraft.loot.condition.LootCondition -import net.minecraft.loot.function.LootFunction import net.minecraft.recipe.RecipeEntry import net.minecraft.server.command.ServerCommandSource import net.minecraft.util.Identifier @@ -41,9 +39,8 @@ import net.minecraft.util.Identifier */ @JvmName("valueIdentifierArg") @BrigadierDsl -fun DefaultArgumentReader.value(): Identifier { - return IdentifierArgumentType.getIdentifier(context.assumeSourceNotUsed(), name) -} +fun DefaultArgumentReader.value(): Identifier = + IdentifierArgumentType.getIdentifier(context.assumeSourceNotUsed(), name) /** * Reads the [Identifier] value from the @@ -53,22 +50,16 @@ fun DefaultArgumentReader.value(): Identifier { * @see RegistryKeyArgumentType.getAdvancementEntry */ @BrigadierDsl -fun ArgumentReader< - ServerCommandSource, - DefaultArgumentDescriptor< - IdentifierArgumentType - > - >.asAdvancement(): AdvancementEntry { - return RegistryKeyArgumentType.getAdvancementEntry(context, name) -} +fun ArgumentReader>.asAdvancement(): AdvancementEntry = + RegistryKeyArgumentType.getAdvancementEntry(context, name) -/** - * Reads the [Identifier] value from the - * argument in the receiver [ArgumentReader] - * as a [LootCondition]. - * - * @see IdentifierArgumentType.getPredicateArgument - */ +///** +// * Reads the [Identifier] value from the +// * argument in the receiver [ArgumentReader] +// * as a [LootCondition]. +// * +// * @see IdentifierArgumentType.getPredicateArgument +// */ //@BrigadierDsl //fun ArgumentReader< // ServerCommandSource, @@ -79,13 +70,13 @@ fun ArgumentReader< // return IdentifierArgumentType.getPredicateArgument(context, name) //} -/** - * Reads the [Identifier] value from the - * argument in the receiver [ArgumentReader] - * as a [LootFunction]. - * - * @see IdentifierArgumentType.getItemModifierArgument - */ +///** +// * Reads the [Identifier] value from the +// * argument in the receiver [ArgumentReader] +// * as a [LootFunction]. +// * +// * @see IdentifierArgumentType.getItemModifierArgument +// */ //@BrigadierDsl //fun ArgumentReader< // ServerCommandSource, @@ -104,14 +95,8 @@ fun ArgumentReader< * @see IdentifierArgumentType.getRecipeArgument */ @BrigadierDsl -fun ArgumentReader< - ServerCommandSource, - DefaultArgumentDescriptor< - IdentifierArgumentType - > - >.asRecipe(): RecipeEntry<*> { - return RegistryKeyArgumentType.getRecipeEntry(context, name) -} +fun ArgumentReader>.asRecipe(): RecipeEntry<*> = + RegistryKeyArgumentType.getRecipeEntry(context, name) /** * Creates an identifier argument with [name] as the parameter name. @@ -119,6 +104,5 @@ fun ArgumentReader< @BrigadierDsl fun identifier( name: String, -): DefaultArgumentConstructor { - return argument(name, IdentifierArgumentType.identifier()) -} +): DefaultArgumentConstructor = + argument(name, IdentifierArgumentType.identifier()) diff --git a/src/main/kotlin/com/lambda/brigadier/argument/ItemArguments.kt b/src/main/kotlin/com/lambda/brigadier/argument/ItemArguments.kt index 846cdb2e3..ecaa559ef 100644 --- a/src/main/kotlin/com/lambda/brigadier/argument/ItemArguments.kt +++ b/src/main/kotlin/com/lambda/brigadier/argument/ItemArguments.kt @@ -46,9 +46,8 @@ import java.util.function.Predicate */ @JvmName("valueItemPredicateArg") @BrigadierDsl -fun DefaultArgumentReader.value(): Predicate { - return ItemPredicateArgumentType.getItemStackPredicate(context.assumeSourceNotUsed(), name) -} +fun DefaultArgumentReader.value(): Predicate = + ItemPredicateArgumentType.getItemStackPredicate(context.assumeSourceNotUsed(), name) /** * Reads the integer value from the @@ -58,9 +57,8 @@ fun DefaultArgumentReader.value(): Predicate.value(): Int { - return ItemSlotArgumentType.getItemSlot(context.assumeSourceNotUsed(), name) -} +fun DefaultArgumentReader.value(): Int = + ItemSlotArgumentType.getItemSlot(context.assumeSourceNotUsed(), name) /** * Reads the [ItemStackArgument] value from the @@ -70,9 +68,8 @@ fun DefaultArgumentReader.value(): Int { */ @JvmName("valueItemStackArg") @BrigadierDsl -fun DefaultArgumentReader.value(): ItemStackArgument { - return ItemStackArgumentType.getItemStackArgument(context, name) -} +fun DefaultArgumentReader.value(): ItemStackArgument = + ItemStackArgumentType.getItemStackArgument(context, name) /** * Creates an item predicate argument with [name] as the parameter name. @@ -83,9 +80,8 @@ fun DefaultArgumentReader.value(): ItemStackArgument { fun itemPredicate( name: String, context: CommandRegistryAccess, -): DefaultArgumentConstructor { - return argument(name, ItemPredicateArgumentType.itemPredicate(context)) -} +): DefaultArgumentConstructor = + argument(name, ItemPredicateArgumentType.itemPredicate(context)) /** * Creates an item slot argument with [name] as the parameter name. @@ -93,9 +89,8 @@ fun itemPredicate( @BrigadierDsl fun itemSlot( name: String, -): DefaultArgumentConstructor { - return argument(name, ItemSlotArgumentType.itemSlot()) -} +): DefaultArgumentConstructor = + argument(name, ItemSlotArgumentType.itemSlot()) /** * Creates an item stack argument with [name] as the parameter name. @@ -106,6 +101,5 @@ fun itemSlot( fun itemStack( name: String, context: CommandRegistryAccess, -): DefaultArgumentConstructor { - return argument(name, ItemStackArgumentType.itemStack(context)) -} +): DefaultArgumentConstructor = + argument(name, ItemStackArgumentType.itemStack(context)) diff --git a/src/main/kotlin/com/lambda/brigadier/argument/MiscArguments.kt b/src/main/kotlin/com/lambda/brigadier/argument/MiscArguments.kt index 3deb92a56..7a2c1f498 100644 --- a/src/main/kotlin/com/lambda/brigadier/argument/MiscArguments.kt +++ b/src/main/kotlin/com/lambda/brigadier/argument/MiscArguments.kt @@ -48,9 +48,8 @@ object LiteralDescriptor : ArgumentDescriptor> */ @JvmName("valueTimeArg") @BrigadierDsl -fun DefaultArgumentReader.value(): Int { - return IntegerArgumentType.getInteger(context, name) -} // TimeArgumentType does not provide an accessor, defaulting to int +fun DefaultArgumentReader.value(): Int = + IntegerArgumentType.getInteger(context, name) // TimeArgumentType does not provide an accessor, defaulting to int /** * Reads the [UUID] value from the @@ -60,9 +59,8 @@ fun DefaultArgumentReader.value(): Int { */ @JvmName("valueUuidArg") @BrigadierDsl -fun DefaultArgumentReader.value(): UUID { - return UuidArgumentType.getUuid(context.assumeSourceNotUsed(), name) -} +fun DefaultArgumentReader.value(): UUID = + UuidArgumentType.getUuid(context.assumeSourceNotUsed(), name) /** * Creates a time argument with [name] as the parameter name. @@ -73,9 +71,8 @@ fun DefaultArgumentReader.value(): UUID { fun time( name: String, minimumTicks: Int = 0, -): DefaultArgumentConstructor { - return argument(name, TimeArgumentType.time(minimumTicks)) -} +): DefaultArgumentConstructor = + argument(name, TimeArgumentType.time(minimumTicks)) /** * Creates a UUID argument with [name] as the parameter name. @@ -83,9 +80,8 @@ fun time( @BrigadierDsl fun uuid( name: String, -): RequiredArgumentConstructor> { - return argument(name, UuidArgumentType.uuid()) -} +): RequiredArgumentConstructor> = + argument(name, UuidArgumentType.uuid()) /** * Creates a literal argument with [name] as the literal. @@ -98,6 +94,5 @@ fun uuid( @BrigadierDsl fun literal( name: String, -): ArgumentConstructor, LiteralDescriptor> { - return ArgumentConstructor(LiteralArgumentBuilder.literal(name), name, LiteralDescriptor) -} +): ArgumentConstructor, LiteralDescriptor> = + ArgumentConstructor(LiteralArgumentBuilder.literal(name), name, LiteralDescriptor) diff --git a/src/main/kotlin/com/lambda/brigadier/argument/NBTArguments.kt b/src/main/kotlin/com/lambda/brigadier/argument/NBTArguments.kt index 08122cc82..41a7c13ac 100644 --- a/src/main/kotlin/com/lambda/brigadier/argument/NBTArguments.kt +++ b/src/main/kotlin/com/lambda/brigadier/argument/NBTArguments.kt @@ -45,9 +45,8 @@ import net.minecraft.nbt.NbtElement */ @JvmName("valueNbtCompoundArg") @BrigadierDsl -fun DefaultArgumentReader.value(): NbtCompound { - return NbtCompoundArgumentType.getNbtCompound(context, name) -} +fun DefaultArgumentReader.value(): NbtCompound = + NbtCompoundArgumentType.getNbtCompound(context, name) /** * Reads the [NbtElement] value from the @@ -57,9 +56,8 @@ fun DefaultArgumentReader.value(): NbtCompound { */ @JvmName("valueNbtElementArg") @BrigadierDsl -fun DefaultArgumentReader.value(): NbtElement { - return NbtElementArgumentType.getNbtElement(context, name) -} +fun DefaultArgumentReader.value(): NbtElement = + NbtElementArgumentType.getNbtElement(context, name) /** * Reads the [NbtPath] value from the @@ -69,9 +67,8 @@ fun DefaultArgumentReader.value(): NbtElement { */ @JvmName("valueNbtPathArg") @BrigadierDsl -fun DefaultArgumentReader.value(): NbtPath { - return NbtPathArgumentType.getNbtPath(context.assumeSourceNotUsed(), name) -} +fun DefaultArgumentReader.value(): NbtPath = + NbtPathArgumentType.getNbtPath(context.assumeSourceNotUsed(), name) /** * Creates a nbt compound argument with [name] as the parameter name. @@ -79,9 +76,8 @@ fun DefaultArgumentReader.value(): NbtPath { @BrigadierDsl fun nbtCompound( name: String, -): DefaultArgumentConstructor { - return argument(name, NbtCompoundArgumentType.nbtCompound()) -} +): DefaultArgumentConstructor = + argument(name, NbtCompoundArgumentType.nbtCompound()) /** * Creates an NBT element argument with [name] as the parameter name. @@ -89,9 +85,8 @@ fun nbtCompound( @BrigadierDsl fun nbtElement( name: String, -): DefaultArgumentConstructor { - return argument(name, NbtElementArgumentType.nbtElement()) -} +): DefaultArgumentConstructor = + argument(name, NbtElementArgumentType.nbtElement()) /** * Creates an NBT path argument with [name] as the parameter name. @@ -99,6 +94,5 @@ fun nbtElement( @BrigadierDsl fun nbtPath( name: String, -): DefaultArgumentConstructor { - return argument(name, NbtPathArgumentType.nbtPath()) -} +): DefaultArgumentConstructor = + argument(name, NbtPathArgumentType.nbtPath()) diff --git a/src/main/kotlin/com/lambda/brigadier/argument/PlayerArguments.kt b/src/main/kotlin/com/lambda/brigadier/argument/PlayerArguments.kt index 102021f26..36169393c 100644 --- a/src/main/kotlin/com/lambda/brigadier/argument/PlayerArguments.kt +++ b/src/main/kotlin/com/lambda/brigadier/argument/PlayerArguments.kt @@ -65,14 +65,8 @@ object ListPlayerArgumentDescriptor : ArgumentDescriptor */ @JvmName("valueGameProfileArg") @BrigadierDsl -fun ArgumentReader< - ServerCommandSource, - DefaultArgumentDescriptor< - GameProfileArgumentType - > - >.value(): Collection { - return GameProfileArgumentType.getProfileArgument(context, name) -} +fun ArgumentReader>.value(): Collection = + GameProfileArgumentType.getProfileArgument(context, name) /** * Reads the [Team] value from the @@ -82,14 +76,8 @@ fun ArgumentReader< */ @JvmName("valueTeamArg") @BrigadierDsl -fun ArgumentReader< - ServerCommandSource, - DefaultArgumentDescriptor< - TeamArgumentType - > - >.value(): Team { - return TeamArgumentType.getTeam(context, name) -} +fun ArgumentReader>.value(): Team = + TeamArgumentType.getTeam(context, name) /** * Reads the [ServerPlayerEntity] value from the @@ -99,12 +87,8 @@ fun ArgumentReader< */ @JvmName("valuePlayerArg") @BrigadierDsl -fun ArgumentReader< - ServerCommandSource, - SinglePlayerArgumentDescriptor - >.value(): ServerPlayerEntity { - return EntityArgumentType.getPlayer(context, name) -} +fun ArgumentReader.value(): ServerPlayerEntity = + EntityArgumentType.getPlayer(context, name) /** * Reads the collection of players from the argument in @@ -116,12 +100,8 @@ fun ArgumentReader< */ @JvmName("requiredPlayerArg") @BrigadierDsl -fun ArgumentReader< - ServerCommandSource, - ListPlayerArgumentDescriptor - >.required(): Collection { - return EntityArgumentType.getPlayers(context, name) -} +fun ArgumentReader.required(): Collection = + EntityArgumentType.getPlayers(context, name) /** * Reads the collection of players from the argument in @@ -133,12 +113,8 @@ fun ArgumentReader< */ @JvmName("optionalPlayerArg") @BrigadierDsl -fun ArgumentReader< - ServerCommandSource, - ListPlayerArgumentDescriptor - >.optional(): Collection { - return EntityArgumentType.getOptionalPlayers(context, name) -} +fun ArgumentReader.optional(): Collection = + EntityArgumentType.getOptionalPlayers(context, name) /** * Creates a game profile argument with [name] as the parameter name. @@ -146,9 +122,8 @@ fun ArgumentReader< @BrigadierDsl fun gameProfile( name: String, -): DefaultArgumentConstructor { - return argument(name, GameProfileArgumentType.gameProfile()) -} +): DefaultArgumentConstructor = + argument(name, GameProfileArgumentType.gameProfile()) /** * Creates a team argument with [name] as the parameter name. @@ -156,9 +131,8 @@ fun gameProfile( @BrigadierDsl fun team( name: String, -): DefaultArgumentConstructor { - return argument(name, TeamArgumentType.team()) -} +): DefaultArgumentConstructor = + argument(name, TeamArgumentType.team()) /** * Creates a player selector argument with [name] as the parameter name. @@ -166,12 +140,8 @@ fun team( @BrigadierDsl fun player( name: String, -): RequiredArgumentConstructor< - S, - SinglePlayerArgumentDescriptor - > { - return argument(name, EntityArgumentType.player(), SinglePlayerArgumentDescriptor) -} +): RequiredArgumentConstructor = + argument(name, EntityArgumentType.player(), SinglePlayerArgumentDescriptor) /** * Creates a multiple player selector argument with [name] as the parameter name. @@ -179,9 +149,5 @@ fun player( @BrigadierDsl fun players( name: String, -): RequiredArgumentConstructor< - S, - ListPlayerArgumentDescriptor - > { - return argument(name, EntityArgumentType.players(), ListPlayerArgumentDescriptor) -} +): RequiredArgumentConstructor = + argument(name, EntityArgumentType.players(), ListPlayerArgumentDescriptor) diff --git a/src/main/kotlin/com/lambda/brigadier/argument/PrimitiveArguments.kt b/src/main/kotlin/com/lambda/brigadier/argument/PrimitiveArguments.kt index 6544a31f3..258676e5c 100644 --- a/src/main/kotlin/com/lambda/brigadier/argument/PrimitiveArguments.kt +++ b/src/main/kotlin/com/lambda/brigadier/argument/PrimitiveArguments.kt @@ -42,9 +42,8 @@ import com.mojang.brigadier.arguments.LongArgumentType */ @JvmName("valueBoolArg") @BrigadierDsl -fun DefaultArgumentReader.value(): Boolean { - return BoolArgumentType.getBool(context, name) -} +fun DefaultArgumentReader.value(): Boolean = + BoolArgumentType.getBool(context, name) /** * Reads the boolean value from the argument in @@ -54,9 +53,8 @@ fun DefaultArgumentReader.value(): Boolean { */ @JvmName("valueDoubleArg") @BrigadierDsl -fun DefaultArgumentReader.value(): Double { - return DoubleArgumentType.getDouble(context, name) -} +fun DefaultArgumentReader.value(): Double = + DoubleArgumentType.getDouble(context, name) /** * Reads the float value from the argument in @@ -66,9 +64,8 @@ fun DefaultArgumentReader.value(): Double { */ @JvmName("valueFloatArg") @BrigadierDsl -fun DefaultArgumentReader.value(): Float { - return FloatArgumentType.getFloat(context, name) -} +fun DefaultArgumentReader.value(): Float = + FloatArgumentType.getFloat(context, name) /** * Reads the integer value from the argument in @@ -78,9 +75,8 @@ fun DefaultArgumentReader.value(): Float { */ @JvmName("valueIntArg") @BrigadierDsl -fun DefaultArgumentReader.value(): Int { - return IntegerArgumentType.getInteger(context, name) -} +fun DefaultArgumentReader.value(): Int = + IntegerArgumentType.getInteger(context, name) /** * Reads the long value from the argument in @@ -90,9 +86,8 @@ fun DefaultArgumentReader.value(): Int { */ @JvmName("valueLongArg") @BrigadierDsl -fun DefaultArgumentReader.value(): Long { - return LongArgumentType.getLong(context, name) -} +fun DefaultArgumentReader.value(): Long = + LongArgumentType.getLong(context, name) /** * Creates a boolean argument with [name] as the parameter name. @@ -100,9 +95,8 @@ fun DefaultArgumentReader.value(): Long { @BrigadierDsl fun boolean( name: String, -): DefaultArgumentConstructor { - return argument(name, BoolArgumentType.bool()) -} +): DefaultArgumentConstructor = + argument(name, BoolArgumentType.bool()) /** * Creates a double argument with [name] as the parameter name. @@ -115,9 +109,8 @@ fun double( name: String, min: Double = -Double.MAX_VALUE, max: Double = Double.MAX_VALUE, -): DefaultArgumentConstructor { - return argument(name, DoubleArgumentType.doubleArg(min, max)) -} +): DefaultArgumentConstructor = + argument(name, DoubleArgumentType.doubleArg(min, max)) /** * Creates a float argument with [name] as the parameter name. @@ -130,9 +123,8 @@ fun float( name: String, min: Float = -Float.MAX_VALUE, max: Float = Float.MAX_VALUE, -): DefaultArgumentConstructor { - return argument(name, FloatArgumentType.floatArg(min, max)) -} +): DefaultArgumentConstructor = + argument(name, FloatArgumentType.floatArg(min, max)) /** * Creates an integer argument with [name] as the parameter name. @@ -145,9 +137,8 @@ fun integer( name: String, min: Int = -Int.MAX_VALUE, max: Int = Int.MAX_VALUE, -): DefaultArgumentConstructor { - return argument(name, IntegerArgumentType.integer(min, max)) -} +): DefaultArgumentConstructor = + argument(name, IntegerArgumentType.integer(min, max)) /** * Creates a long argument with [name] as the parameter name. @@ -160,6 +151,5 @@ fun long( name: String, min: Long = -Long.MAX_VALUE, max: Long = Long.MAX_VALUE, -): DefaultArgumentConstructor { - return argument(name, LongArgumentType.longArg(min, max)) -} +): DefaultArgumentConstructor = + argument(name, LongArgumentType.longArg(min, max)) diff --git a/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt b/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt index 51e93f749..71f458664 100644 --- a/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt +++ b/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt @@ -31,7 +31,7 @@ import com.lambda.interaction.construction.blueprint.Blueprint.Companion.toStruc import com.lambda.interaction.construction.blueprint.StaticBlueprint.Companion.toBlueprint import com.lambda.task.RootTask.run import com.lambda.task.tasks.BuildTask -import com.lambda.task.tasks.BuildTask.Companion.build +import com.lambda.task.tasks.build import com.lambda.threading.runSafe import com.lambda.util.CommunicationUtils.info import com.lambda.util.extension.CommandBuilder diff --git a/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt b/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt index 964f4732c..399677cdb 100644 --- a/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt +++ b/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt @@ -29,26 +29,15 @@ import net.minecraft.item.Item interface InventoryConfig { val tickStageMask: Collection val disposables: Collection - val swapWithDisposables: Boolean - val providerPriority: Priority + val accessPriority: Priority val storePriority: Priority - val accessShulkerBoxes: Boolean - val accessChests: Boolean - val accessEnderChest: Boolean - val accessStashes: Boolean + val allowedContainers: Collection + + val enderChestSearchRadius: Int val containerSelection: ContainerSelection - get() = selectContainer { - val allowedContainers = buildSet { - addAll(Container.Rank.entries) - if (!accessShulkerBoxes) remove(Container.Rank.ShulkerBox) - if (!accessEnderChest) remove(Container.Rank.EnderChest) - if (!accessChests) remove(Container.Rank.Chest) - if (!accessStashes) remove(Container.Rank.Stash) - } - ofAnyType(*allowedContainers.toTypedArray()) - } + get() = selectContainer { ofAnyType(*allowedContainers.toTypedArray()) } enum class Priority( override val displayName: String, diff --git a/src/main/kotlin/com/lambda/config/blocks/InventorySettings.kt b/src/main/kotlin/com/lambda/config/blocks/InventorySettings.kt index ee64c7e13..34b5e2751 100644 --- a/src/main/kotlin/com/lambda/config/blocks/InventorySettings.kt +++ b/src/main/kotlin/com/lambda/config/blocks/InventorySettings.kt @@ -21,22 +21,19 @@ import com.lambda.config.Config import com.lambda.config.ConfigBlock import com.lambda.config.Group import com.lambda.event.events.TickEvent.Companion.ALL_STAGES +import com.lambda.interaction.inventory.container.Container import com.lambda.util.item.ItemUtils class InventorySettings(override val c: Config) : InventoryConfig, ConfigBlock { companion object { - private const val CONTAINER_GROUP = "Container" - private const val ACCESS_GROUP = "Access" + const val ENDER_CHEST_GROUP = "Ender Chest" } override val tickStageMask by c.setting("Inventory Stage Mask", ALL_STAGES.toSet(), description = "The sub-tick timing at which inventory actions are performed", displayClassName = true) - @Group(CONTAINER_GROUP) override val disposables by c.setting("Disposables", ItemUtils.defaultDisposables, description = "Items that will be ignored when checking for a free slot") - @Group(CONTAINER_GROUP) override val swapWithDisposables by c.setting("Swap With Disposables", true, "Swap items with disposable ones") - @Group(CONTAINER_GROUP) override val providerPriority by c.setting("Provider Priority", InventoryConfig.Priority.WithMinItems, "What container to prefer when retrieving the item from") - @Group(CONTAINER_GROUP) override val storePriority by c.setting("Store Priority", InventoryConfig.Priority.WithMinItems, "What container to prefer when storing the item to") + override val disposables by c.setting("Disposables", ItemUtils.defaultDisposables, description = "Items that will be ignored when checking for a free slot") + override val accessPriority by c.setting("Access Priority", InventoryConfig.Priority.WithMinItems, "What container to prefer when retrieving the item from") + override val storePriority by c.setting("Store Priority", InventoryConfig.Priority.WithMinItems, "What container to prefer when storing the item to") + override val allowedContainers by c.setting("Allowed Containers", Container.Rank.entries, description = "What containers are accessible") - @Group(ACCESS_GROUP) override val accessShulkerBoxes by c.setting("Access Shulker Boxes", false, "Allow access to the player's shulker boxes") - @Group(ACCESS_GROUP) override val accessChests by c.setting("Access Chests", false, "Allow access to the player's normal chests") - @Group(ACCESS_GROUP) override val accessEnderChest by c.setting("Access Ender Chest", false, "Allow access to the player's ender chest") - @Group(ACCESS_GROUP) override val accessStashes by c.setting("Access Stashes", false, "Allow access to the player's stashes") + @Group(ENDER_CHEST_GROUP) override val enderChestSearchRadius by c.setting("Initial Search Radius", 10, 0..20, unit = " blocks", description = "Initial search radius to scan for an already placed ender chest. If one isn't found, one will be placed. If an ender chest isn't found in the inventory, it scans the rest of the loaded area to find an already placed one") } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/config/settings/complex/KeybindSetting.kt b/src/main/kotlin/com/lambda/config/settings/complex/KeybindSetting.kt index ed2c94041..00629b99f 100644 --- a/src/main/kotlin/com/lambda/config/settings/complex/KeybindSetting.kt +++ b/src/main/kotlin/com/lambda/config/settings/complex/KeybindSetting.kt @@ -36,7 +36,6 @@ import com.lambda.event.Muteable import com.lambda.event.events.ButtonEvent import com.lambda.event.listener.UnsafeListener.Companion.listenUnsafe import com.lambda.gui.dsl.ImGuiBuilder -import com.lambda.imgui.ImGui.isMouseClicked import com.lambda.imgui.flag.ImGuiCol import com.lambda.imgui.flag.ImGuiHoveredFlags import com.lambda.imgui.flag.ImGuiMouseButton diff --git a/src/main/kotlin/com/lambda/gui/MenuBar.kt b/src/main/kotlin/com/lambda/gui/MenuBar.kt index 30c164420..2c5550ff2 100644 --- a/src/main/kotlin/com/lambda/gui/MenuBar.kt +++ b/src/main/kotlin/com/lambda/gui/MenuBar.kt @@ -36,7 +36,6 @@ import com.lambda.gui.components.QuickSearch import com.lambda.gui.components.SettingsWidget.buildConfigSettingsContext import com.lambda.gui.dsl.ImGuiBuilder import com.lambda.imgui.ImGui -import com.lambda.imgui.ImGui.closeCurrentPopup import com.lambda.imgui.flag.ImGuiCol import com.lambda.imgui.flag.ImGuiStyleVar import com.lambda.imgui.flag.ImGuiWindowFlags diff --git a/src/main/kotlin/com/lambda/gui/dsl/ImGuiBuilder.kt b/src/main/kotlin/com/lambda/gui/dsl/ImGuiBuilder.kt index 449b8bc40..5ba9f8b16 100644 --- a/src/main/kotlin/com/lambda/gui/dsl/ImGuiBuilder.kt +++ b/src/main/kotlin/com/lambda/gui/dsl/ImGuiBuilder.kt @@ -20,6 +20,8 @@ package com.lambda.gui.dsl import com.lambda.gui.components.ClickGuiLayout +import com.lambda.gui.dsl.ImGuiBuilder.onItemClick +import com.lambda.gui.dsl.ImGuiBuilder.onItemHover import com.lambda.gui.dsl.ImGuiBuilder.openPopup import com.lambda.gui.dsl.ImGuiBuilder.text import com.lambda.imgui.ImDrawList @@ -108,10 +110,8 @@ import com.lambda.imgui.ImGui.isItemEdited import com.lambda.imgui.ImGui.isItemFocused import com.lambda.imgui.ImGui.isItemHovered import com.lambda.imgui.ImGui.isItemToggledOpen -import com.lambda.imgui.ImGui.isMouseClicked import com.lambda.imgui.ImGui.isWindowAppearing import com.lambda.imgui.ImGui.isWindowCollapsed -import com.lambda.imgui.ImGui.isWindowHovered import com.lambda.imgui.ImGui.plotHistogram import com.lambda.imgui.ImGui.plotLines import com.lambda.imgui.ImGui.popFont diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt index 6de41dc5a..3be7609de 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt @@ -35,8 +35,7 @@ import com.lambda.interaction.handler.handlers.BaritoneHandler import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.task.Task -import com.lambda.task.tasks.ContainerTransferTask.Companion.transfer -import com.lambda.task.wrappers.softFail +import com.lambda.task.tasks.wrappers.softFail import net.minecraft.block.BlockState import net.minecraft.item.Item import net.minecraft.util.math.BlockPos diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/GenericResult.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/GenericResult.kt index 1ef911d16..fe897f082 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/GenericResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/GenericResult.kt @@ -30,8 +30,7 @@ import com.lambda.interaction.handler.handlers.BaritoneHandler import com.lambda.interaction.inventory.StackSelection import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.task.Task -import com.lambda.task.wrappers.softFail -import com.lambda.task.tasks.ContainerTransferTask.Companion.transfer +import com.lambda.task.tasks.wrappers.softFail import net.minecraft.client.data.TextureMap.side import net.minecraft.item.ItemStack import net.minecraft.util.math.BlockPos diff --git a/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt index a14699fc4..770ea2850 100644 --- a/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt @@ -126,7 +126,7 @@ object ContainerHandler : Loadable { filteredContainers .filter { containerSelection.matches(it) } .filter { it.stackCount(this) >= count } - .sortedWith(automated.inventoryConfig.providerPriority.materialComparator(this)) + .sortedWith(automated.inventoryConfig.accessPriority.materialComparator(this)) @ContainerMarker context(automated: Automated) @@ -142,7 +142,7 @@ object ContainerHandler : Loadable { filteredContainers .filter { containerSelection.matches(it) } .filter { it.spaceAvailable(this) >= count } - .sortedWith(automated.inventoryConfig.providerPriority.spaceComparator(this)) + .sortedWith(automated.inventoryConfig.accessPriority.spaceComparator(this)) @ContainerMarker context(automated: Automated) diff --git a/src/main/kotlin/com/lambda/interaction/inventory/StackSelection.kt b/src/main/kotlin/com/lambda/interaction/inventory/StackSelection.kt index e83152448..2be088cc1 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/StackSelection.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/StackSelection.kt @@ -17,6 +17,7 @@ package com.lambda.interaction.inventory +import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack import com.lambda.util.EnchantmentUtils.getEnchantment import com.lambda.util.item.ItemStackUtils.shulkerBoxStacks import com.lambda.util.item.ItemUtils @@ -34,6 +35,12 @@ import net.minecraft.registry.tag.TagKey import net.minecraft.screen.slot.Slot import java.util.* +@ContainerMarker +fun Item.select(count: Int = 0) = selectStack(count) { isItem(this@select) } + +@ContainerMarker +fun ItemStack.select(count: Int = 0) = selectStack(count) { isItemStack(this@select) } + @Suppress("unused") class StackSelection( val count: Int = 0, @@ -240,12 +247,6 @@ class StackSelectionBuilder private constructor(private val count: Int = 0) { companion object { private val efficientToolCache = Collections.synchronizedMap(mutableMapOf()) - @ContainerMarker - fun Item.select(count: Int = 0) = selectStack(count) { isItem(this@select) } - - @ContainerMarker - fun ItemStack.select(count: Int = 0) = selectStack(count) { isItemStack(this@select) } - fun selectStack( count: Int = 0, builder: StackSelectionBuilder.() -> Unit diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/Container.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/Container.kt index 31ea7d518..c09a5b6db 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/Container.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/Container.kt @@ -28,7 +28,6 @@ import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.task.Task.Ta5kBuilder import com.lambda.task.tasks.ContainerTransferTask -import com.lambda.task.wrappers.TaskOrNullSupplier import com.lambda.util.Nameable import com.lambda.util.item.ItemStackUtils.count import com.lambda.util.item.ItemStackUtils.empty @@ -127,17 +126,9 @@ abstract class Container( } @Ta5kBuilder - context(automatedSafeContext: AutomatedSafeContext) - open fun accessThen( - closeAfter: Boolean = true, - afterOpen: TaskOrNullSupplier = { null }, - afterClose: TaskOrNullSupplier = { null } - ) = with(automatedSafeContext) { - taskOrSkipOrNull({ afterOpen(Unit) }) { result -> - if (closeAfter) afterClose(result) - else null - } - } + context(automated: Automated) + open fun access(): OpenContainerTask<*> = + PlayerOpenContainerTask(description, ::isAccessed) context(automatedSafeContext: AutomatedSafeContext) fun transfer(selection: StackSelection, toContainer: Container): Boolean = diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/ExternalContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/ExternalContainer.kt index 0e395e9bc..bad12fdb3 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/ExternalContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/ExternalContainer.kt @@ -17,5 +17,4 @@ package com.lambda.interaction.inventory.container -interface ExternalContainer { -} \ No newline at end of file +interface ExternalContainer \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/NestedContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/NestedContainer.kt index 94f48fffa..67f861999 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/NestedContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/NestedContainer.kt @@ -20,5 +20,5 @@ package com.lambda.interaction.inventory.container import net.minecraft.screen.slot.Slot interface NestedContainer { - val slotCache: Slot + val slotCache: Slot? } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/task/tasks/NoopTask.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/OpenContainerTask.kt similarity index 60% rename from src/main/kotlin/com/lambda/task/tasks/NoopTask.kt rename to src/main/kotlin/com/lambda/interaction/inventory/container/OpenContainerTask.kt index 011031a41..2954b10a3 100644 --- a/src/main/kotlin/com/lambda/task/tasks/NoopTask.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/OpenContainerTask.kt @@ -15,20 +15,23 @@ * along with this program. If not, see . */ -package com.lambda.task.tasks +package com.lambda.interaction.inventory.container import com.lambda.context.SafeContext import com.lambda.task.Task +import net.minecraft.text.Text -class NoopTask @Ta5kBuilder constructor() : Task() { - override val name = "No-operation task" +abstract class OpenContainerTask @Ta5kBuilder internal constructor( + description: Text +) : Task() { + override val name = "accessing container: ${description.string}" +} +class PlayerOpenContainerTask @Ta5kBuilder internal constructor( + description: Text, + private val isAccessed: () -> Boolean +) : OpenContainerTask(description) { override fun SafeContext.onStart() { - success() - } - - companion object { - @Ta5kBuilder - fun noopTask() = NoopTask() + success(BasicOpenedContainerContext(isAccessed)) } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/OpenedContainerContext.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/OpenedContainerContext.kt new file mode 100644 index 000000000..5b615bb86 --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/OpenedContainerContext.kt @@ -0,0 +1,40 @@ +/* + * 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.inventory.container + +import com.lambda.context.Automated +import com.lambda.task.Task +import com.lambda.task.Task.Ta5kBuilder +import com.lambda.task.tasks.simpleAction + +interface OpenedContainerContext { + @Ta5kBuilder + context(automated: Automated) + fun close(): Task<*> +} + +open class BasicOpenedContainerContext( + private val isAccessed: () -> Boolean +) : OpenedContainerContext { + context(_: Automated) + final override fun close() = + simpleAction("Closing container") { + if (!isAccessed()) return@simpleAction + player.closeScreen() + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ChestContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ChestContainer.kt index 23625a41d..1b454ba7d 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ChestContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ChestContainer.kt @@ -18,15 +18,17 @@ package com.lambda.interaction.inventory.container.containers.external import com.lambda.Lambda.mc -import com.lambda.context.AutomatedSafeContext +import com.lambda.context.Automated +import com.lambda.context.SafeContext import com.lambda.interaction.handler.handlers.ContainerHandler.lastInteractedBlockEntity +import com.lambda.interaction.inventory.container.BasicOpenedContainerContext import com.lambda.interaction.inventory.container.Container import com.lambda.interaction.inventory.container.ExternalContainer +import com.lambda.interaction.inventory.container.OpenContainerTask import com.lambda.interaction.inventory.container.PlacedContainer -import com.lambda.task.tasks.OpenContainerTask.Companion.openContainer -import com.lambda.task.tasks.SimpleActionTask.Companion.simpleAction -import com.lambda.task.wrappers.TaskOrNullSupplier -import com.lambda.task.wrappers.thenOrNull +import com.lambda.task.Task.Ta5kBuilder +import com.lambda.task.tasks.openContainer +import com.lambda.task.tasks.wrappers.taskOrNull import com.lambda.util.extension.containerSlots import com.lambda.util.text.buildText import com.lambda.util.text.highlighted @@ -34,6 +36,7 @@ import com.lambda.util.text.literal import net.minecraft.block.ChestBlock import net.minecraft.block.entity.ChestBlockEntity import net.minecraft.item.ItemStack +import net.minecraft.screen.ScreenHandler import net.minecraft.screen.ScreenHandlerType import net.minecraft.screen.slot.Slot import net.minecraft.util.math.BlockPos @@ -69,23 +72,26 @@ data class ChestContainer( mc.player?.currentScreenHandler?.type == ScreenHandlerType.GENERIC_9X3 } ?: false - context(automatedSafeContext: AutomatedSafeContext) - override fun accessThen( - closeAfter: Boolean, - afterOpen: TaskOrNullSupplier, - afterClose: TaskOrNullSupplier - ) = - with(automatedSafeContext) { - taskOrSkipOrNull( - optional = { - openContainer(blockPos).thenOrNull { - afterOpen(Unit) - } + @Ta5kBuilder + context(automated: Automated) + override fun access() = AccessChestTask(automated) + + inner class AccessChestTask @Ta5kBuilder internal constructor( + automated: Automated + ) : OpenContainerTask(description), Automated by automated { + override fun SafeContext.onStart() { + taskOrNull { + if (isAccessed) null + else openContainer(blockPos).onSuccess { sh -> + success(OpenedChestContext(sh)) } - ) { - if (closeAfter) { - simpleAction("Close inventory") { player.closeHandledScreen() } - } else null - } + }.execute(this@AccessChestTask) } + } + + inner class OpenedChestContext( + val screenHandler: ScreenHandler, + ) : BasicOpenedContainerContext(::isAccessed) { + val blockPos = this@ChestContainer.blockPos + } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/EnderChestContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/EnderChestContainer.kt index 0e1c1f4cd..d6e71869b 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/EnderChestContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/EnderChestContainer.kt @@ -18,25 +18,33 @@ package com.lambda.interaction.inventory.container.containers.external import com.lambda.Lambda.mc -import com.lambda.context.AutomatedSafeContext +import com.lambda.context.Automated +import com.lambda.context.SafeContext import com.lambda.interaction.handler.handlers.ContainerHandler.lastInteractedBlockEntity -import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.select import com.lambda.interaction.inventory.container.Container import com.lambda.interaction.inventory.container.ExternalContainer -import com.lambda.task.tasks.AcquireStackTask.Companion.acquireStack -import com.lambda.task.tasks.BuildTask.Companion.breakAndCollect -import com.lambda.task.tasks.OpenContainerTask.Companion.openContainer -import com.lambda.task.tasks.PlaceContainerTask.Companion.placeContainer -import com.lambda.task.tasks.SimpleActionTask.Companion.simpleAction -import com.lambda.task.wrappers.TaskOrNullSupplier -import com.lambda.task.wrappers.then -import com.lambda.task.wrappers.thenOrNull +import com.lambda.interaction.inventory.container.OpenContainerTask +import com.lambda.interaction.inventory.container.OpenedContainerContext +import com.lambda.interaction.inventory.select +import com.lambda.task.Task.Ta5kBuilder +import com.lambda.task.tasks.acquireStack +import com.lambda.task.tasks.breakAndCollect +import com.lambda.task.tasks.findBlock +import com.lambda.task.tasks.openContainer +import com.lambda.task.tasks.placeContainer +import com.lambda.task.tasks.wrappers.actionTask +import com.lambda.task.tasks.wrappers.successTask +import com.lambda.task.tasks.wrappers.taskOrNull +import com.lambda.task.tasks.wrappers.then +import com.lambda.task.tasks.wrappers.withBranch import com.lambda.util.extension.containerSlots import com.lambda.util.text.buildText import com.lambda.util.text.literal +import net.minecraft.block.Blocks import net.minecraft.block.entity.EnderChestBlockEntity import net.minecraft.item.ItemStack import net.minecraft.item.Items +import net.minecraft.screen.ScreenHandler import net.minecraft.screen.ScreenHandlerType import net.minecraft.util.math.BlockPos @@ -56,42 +64,49 @@ object EnderChestContainer : Container(Rank.EnderChest), ExternalContainer { mc.player?.currentScreenHandler?.type == ScreenHandlerType.GENERIC_9X3 } ?: false - context(automatedSafeContext: AutomatedSafeContext) - override fun accessThen( - closeAfter: Boolean, - afterOpen: TaskOrNullSupplier, - afterClose: TaskOrNullSupplier - ) = - with(automatedSafeContext) { - taskOrSkipOrNull( - optional = { - if (isAccessed) null - else { - wrappedTask("Setup Ender Chest") { success -> - acquireStack(Items.ENDER_CHEST.select(1)).then { slot -> - placeContainer(slot).then { pos -> - openContainer(pos).finally { - success(pos) - } + @Ta5kBuilder + context(automated: Automated) + override fun access() = OpenEnderChestContainerTask(automated) + + class OpenEnderChestContainerTask @Ta5kBuilder internal constructor( + automated: Automated + ) : OpenContainerTask(description), Automated by automated { + override fun SafeContext.onStart() { + taskOrNull { + if (isAccessed) null + else findBlock(Blocks.ENDER_CHEST, inventoryConfig.enderChestSearchRadius).withBranch( + onSuccess = { pos -> + openContainer(pos).onSuccess { sh -> + success(OpenedEnderChestContext(sh, pos, false)) + } + }, + onFailure = { + acquireStack(Items.ENDER_CHEST.select(1)) + .then { placeContainer(it) } + .then { pos -> + openContainer(pos).onSuccess { sh -> + success(OpenedEnderChestContext(sh, pos, true)) } } - } } - } - ) { pos -> - taskOrSkipOrNull({ afterOpen(Unit) }) { afterOpenResult -> - if (closeAfter) { - simpleAction("Close inventory") { player.closeHandledScreen() }.thenOrNull { - taskOrSkipOrNull( - optional = { - if (pos != null) breakAndCollect(pos) - else null - } - ) { afterClose(afterOpenResult) } - } - } else null - } - } + ) + }.execute(this@OpenEnderChestContainerTask) } + } + class OpenedEnderChestContext( + val screenHandler: ScreenHandler, + val blockPos: BlockPos, + val placed: Boolean + ) : OpenedContainerContext { + context(_: Automated) + override fun close() = + taskOrNull { + if (isAccessed) actionTask { player.closeScreen() } + else null + }.then { + if (placed) breakAndCollect(blockPos) + else successTask() + } + } } diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/PlacedShulkerBoxContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/PlacedShulkerBoxContainer.kt index 1bd6bff54..cb9d53de0 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/PlacedShulkerBoxContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/PlacedShulkerBoxContainer.kt @@ -18,14 +18,16 @@ package com.lambda.interaction.inventory.container.containers.external import com.lambda.Lambda.mc -import com.lambda.context.AutomatedSafeContext +import com.lambda.context.Automated +import com.lambda.context.SafeContext import com.lambda.interaction.handler.handlers.ContainerHandler.lastInteractedBlockEntity +import com.lambda.interaction.inventory.container.BasicOpenedContainerContext import com.lambda.interaction.inventory.container.Container +import com.lambda.interaction.inventory.container.OpenContainerTask import com.lambda.interaction.inventory.container.PlacedContainer -import com.lambda.task.tasks.OpenContainerTask -import com.lambda.task.tasks.SimpleActionTask.Companion.simpleAction -import com.lambda.task.wrappers.TaskOrNullSupplier -import com.lambda.task.wrappers.thenOrNull +import com.lambda.task.Task.Ta5kBuilder +import com.lambda.task.tasks.openContainer +import com.lambda.task.tasks.wrappers.taskOrNull import com.lambda.util.extension.containerSlots import com.lambda.util.text.buildText import com.lambda.util.text.highlighted @@ -65,26 +67,20 @@ class PlacedShulkerBoxContainer( mc.player?.currentScreenHandler?.type == ScreenHandlerType.SHULKER_BOX } ?: false - context(automatedSafeContext: AutomatedSafeContext) - override fun accessThen( - closeAfter: Boolean, - afterOpen: TaskOrNullSupplier, - afterClose: TaskOrNullSupplier - ) = - with(automatedSafeContext) { - taskOrSkipOrNull( - optional = { - if (!isAccessed) OpenContainerTask(blockPos, automated = automatedSafeContext) - else null - } - ) { - taskOrSkipOrNull({ afterOpen(Unit) }) { result -> - if (closeAfter) { - simpleAction("Close Inventory") { - automatedSafeContext.player.closeHandledScreen() - }.thenOrNull { afterClose(result) } - } else null - } - } + @Ta5kBuilder + context(automated: Automated) + override fun access() = OpenPlacedShulkerBoxTask(automated) + + inner class OpenPlacedShulkerBoxTask @Ta5kBuilder internal constructor( + automated: Automated + ) : OpenContainerTask(description), Automated by automated { + override fun SafeContext.onStart() { + taskOrNull { + if (isAccessed) null + else openContainer(blockPos) + }.onSuccess { + success(BasicOpenedContainerContext(::isAccessed)) + }.execute(this@OpenPlacedShulkerBoxTask) } + } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ShulkerBoxContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ShulkerBoxContainer.kt index 18c45fea5..71c79b4f8 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ShulkerBoxContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ShulkerBoxContainer.kt @@ -18,26 +18,32 @@ package com.lambda.interaction.inventory.container.containers.external import com.lambda.Lambda.mc -import com.lambda.context.AutomatedSafeContext +import com.lambda.context.Automated +import com.lambda.context.SafeContext import com.lambda.interaction.handler.handlers.ContainerHandler.lastInteractedBlockEntity import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack import com.lambda.interaction.inventory.container.Container import com.lambda.interaction.inventory.container.ExternalContainer import com.lambda.interaction.inventory.container.NestedContainer +import com.lambda.interaction.inventory.container.OpenContainerTask +import com.lambda.interaction.inventory.container.OpenedContainerContext import com.lambda.interaction.inventory.container.containers.HotbarContainer -import com.lambda.task.tasks.BuildTask.Companion.breakAndCollect -import com.lambda.task.tasks.OpenContainerTask.Companion.openContainer -import com.lambda.task.tasks.PlaceContainerTask.Companion.placeContainer -import com.lambda.task.tasks.SimpleActionTask.Companion.simpleAction -import com.lambda.task.wrappers.TaskOrNullSupplier -import com.lambda.task.wrappers.then -import com.lambda.task.wrappers.thenOrNull +import com.lambda.interaction.inventory.container.containers.PlayerContainer +import com.lambda.task.Task.Ta5kBuilder +import com.lambda.task.tasks.breakAndCollect +import com.lambda.task.tasks.openContainer +import com.lambda.task.tasks.placeContainer +import com.lambda.task.tasks.transfer +import com.lambda.task.tasks.wrappers.actionTask +import com.lambda.task.tasks.wrappers.taskOrNull +import com.lambda.task.tasks.wrappers.then import com.lambda.util.extension.containerSlots import com.lambda.util.text.buildText import com.lambda.util.text.highlighted import com.lambda.util.text.literal import net.minecraft.block.entity.ShulkerBoxBlockEntity import net.minecraft.item.ItemStack +import net.minecraft.screen.ScreenHandler import net.minecraft.screen.ScreenHandlerType import net.minecraft.screen.slot.Slot import net.minecraft.util.math.BlockPos @@ -70,46 +76,45 @@ data class ShulkerBoxContainer( mc.player?.currentScreenHandler?.type == ScreenHandlerType.SHULKER_BOX } ?: false - context(automatedSafeContext: AutomatedSafeContext) - override fun accessThen( - closeAfter: Boolean, - afterOpen: TaskOrNullSupplier, - afterClose: TaskOrNullSupplier - ) = - with(automatedSafeContext) { - taskOrSkipOrNull( - optional = { - if (isAccessed) null - else nullableWrappedTask("Setup Shulker Box") { success -> - containedIn.accessThen( - afterOpen = { - if (player.currentScreenHandler.syncId == 0) null - else { - val selection = selectStack { isSlot(slotCache) } - containedIn.transferByTask(selection, HotbarContainer) - } - } - ) { slot -> - slot?.let { s -> - placeContainer(s).then { pos -> - openContainer(pos).finally { - success(pos) - } - } - } + @Ta5kBuilder + context(automated: Automated) + override fun access() = OpenShulkerBoxTask(automated) + + inner class OpenShulkerBoxTask @Ta5kBuilder internal constructor( + automated: Automated + ) : OpenContainerTask(description), Automated by automated { + override fun SafeContext.onStart() { + transfer( + selectStack { isSlot(slotCache) }, + containedIn, + HotbarContainer + ).then { slot -> placeContainer(slot) } + .then { pos -> + openContainer(pos).onSuccess { sh -> + success(OpenedShulkerBoxContext(pos, sh, containedIn)) } } - } - ) { pos -> - taskOrSkipOrNull({ afterOpen(Unit) }) { result -> - if (closeAfter && pos != null) { - simpleAction("Close inventory") { player.closeHandledScreen() }.then { - breakAndCollect(pos).thenOrNull { - afterClose(result) - } - } - } else null - } - } + .execute(this@OpenShulkerBoxTask) } + } + + inner class OpenedShulkerBoxContext( + val blockPos: BlockPos, + val screenHandler: ScreenHandler, + val fromContainer: Container + ) : OpenedContainerContext { + context(automated: Automated) + override fun close() = + taskOrNull { + if (!isAccessed) null + else actionTask { player.closeScreen() } + }.then { breakAndCollect(blockPos) } + .then { slot -> + transfer( + selectStack { isSlot(slot) }, + PlayerContainer, + fromContainer + ) + } + } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakRequest.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakRequest.kt index 946250be3..f52910054 100644 --- a/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakRequest.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakRequest.kt @@ -35,6 +35,40 @@ import com.lambda.util.BlockUtils.isEmpty import net.minecraft.entity.ItemEntity import net.minecraft.util.math.BlockPos +@JvmName("breakRequest1") +fun AutomatedSafeContext.breakRequest( + positions: Collection, + pendingInteractions: MutableCollection, + nowOrNothing: Boolean = false, + builder: (BreakRequestBuilder.() -> Unit)? = null +) = positions + .associateWith { TargetState.Empty } + .sim() + .breakRequest(pendingInteractions, nowOrNothing, builder) + +@JvmName("breakRequest2") +context(automated: Automated) +fun Collection.breakRequest( + pendingInteractions: MutableCollection, + nowOrNothing: Boolean = false, + builder: (BreakRequestBuilder.() -> Unit)? = null +) = asSequence() + .breakRequest(pendingInteractions, nowOrNothing, builder) + +@JvmName("breakRequest3") +context(automated: Automated) +fun Sequence.breakRequest( + pendingInteractions: MutableCollection, + nowOrNothing: Boolean = false, + builder: (BreakRequestBuilder.() -> Unit)? = null +) = map { if (it is Dependent) it.lastDependency else it } + .filterIsInstance() + .sorted() + .map { it.context } + .toSet() + .takeIf { it.isNotEmpty() } + ?.let { automated.breakRequest(it, pendingInteractions, nowOrNothing, builder) } + /** * Contains the information necessary for initializing and continuing breaks within the [BreakManager]. * @@ -139,40 +173,6 @@ class BreakRequestBuilder private constructor( ) companion object { - @JvmName("breakRequest1") - fun AutomatedSafeContext.breakRequest( - positions: Collection, - pendingInteractions: MutableCollection, - nowOrNothing: Boolean = false, - builder: (BreakRequestBuilder.() -> Unit)? = null - ) = positions - .associateWith { TargetState.Empty } - .sim() - .breakRequest(pendingInteractions, nowOrNothing, builder) - - @JvmName("breakRequest2") - context(automated: Automated) - fun Collection.breakRequest( - pendingInteractions: MutableCollection, - nowOrNothing: Boolean = false, - builder: (BreakRequestBuilder.() -> Unit)? = null - ) = asSequence() - .breakRequest(pendingInteractions, nowOrNothing, builder) - - @JvmName("breakRequest3") - context(automated: Automated) - fun Sequence.breakRequest( - pendingInteractions: MutableCollection, - nowOrNothing: Boolean = false, - builder: (BreakRequestBuilder.() -> Unit)? = null - ) = map { if (it is Dependent) it.lastDependency else it } - .filterIsInstance() - .sorted() - .map { it.context } - .toSet() - .takeIf { it.isNotEmpty() } - ?.let { automated.breakRequest(it, pendingInteractions, nowOrNothing, builder) } - @JvmName("breakRequest4") fun Automated.breakRequest( contexts: Collection, diff --git a/src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractRequest.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractRequest.kt index bc025b4d6..a0d9d7849 100644 --- a/src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractRequest.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractRequest.kt @@ -25,11 +25,35 @@ import com.lambda.interaction.construction.simulation.result.BuildResult import com.lambda.interaction.construction.simulation.result.Dependent import com.lambda.interaction.construction.simulation.result.results.InteractResult import com.lambda.interaction.manager.Request +import com.lambda.interaction.manager.managers.interacting.PlaceRequestBuilder.Companion.interactRequest import com.lambda.threading.runSafe import com.lambda.util.BlockUtils.blockState import com.lambda.util.BlockUtils.matches import net.minecraft.util.math.BlockPos +@JvmName("interactRequest1") +context(automated: Automated) +fun Collection.interactRequest( + pendingInteractions: MutableCollection, + nowOrNothing: Boolean = false, + builder: (PlaceRequestBuilder.() -> Unit)? = null +) = asSequence() + .interactRequest(pendingInteractions, nowOrNothing, builder) + +@JvmName("interactRequest2") +context(automated: Automated) +fun Sequence.interactRequest( + pendingInteractions: MutableCollection, + nowOrNothing: Boolean = false, + builder: (PlaceRequestBuilder.() -> Unit)? = null +) = map { if (it is Dependent) it.lastDependency else it } + .filterIsInstance() + .sorted() + .map { it.context } + .toSet() + .takeIf { it.isNotEmpty() } + ?.let { automated.interactRequest(it, pendingInteractions, nowOrNothing, builder) } + data class InteractRequest( val contexts: Collection, val pendingInteractions: MutableCollection, @@ -81,29 +105,6 @@ class PlaceRequestBuilder private constructor( ) companion object { - @JvmName("interactRequest1") - context(automated: Automated) - fun Collection.interactRequest( - pendingInteractions: MutableCollection, - nowOrNothing: Boolean = false, - builder: (PlaceRequestBuilder.() -> Unit)? = null - ) = asSequence() - .interactRequest(pendingInteractions, nowOrNothing, builder) - - @JvmName("interactRequest2") - context(automated: Automated) - fun Sequence.interactRequest( - pendingInteractions: MutableCollection, - nowOrNothing: Boolean = false, - builder: (PlaceRequestBuilder.() -> Unit)? = null - ) = map { if (it is Dependent) it.lastDependency else it } - .filterIsInstance() - .sorted() - .map { it.context } - .toSet() - .takeIf { it.isNotEmpty() } - ?.let { automated.interactRequest(it, pendingInteractions, nowOrNothing, builder) } - @JvmName("interactRequest3") fun Automated.interactRequest( contexts: Collection, diff --git a/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt b/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt index d927b241a..1bd195dc3 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt @@ -30,7 +30,7 @@ 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.build import com.lambda.util.BlockUtils.blockState import com.lambda.util.extension.shrinkByEpsilon import com.lambda.util.item.ItemUtils.block diff --git a/src/main/kotlin/com/lambda/module/modules/combat/Surround.kt b/src/main/kotlin/com/lambda/module/modules/combat/Surround.kt index 5eaba40b2..394a9b8f6 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/Surround.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/Surround.kt @@ -29,7 +29,7 @@ import com.lambda.module.modules.combat.PlayerTrap.getTrapPositions 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.build import com.lambda.util.item.ItemUtils.block import net.minecraft.block.Blocks import net.minecraft.item.BlockItem diff --git a/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt b/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt index fc2597653..ddc1d9205 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt @@ -23,7 +23,7 @@ import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.select import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.task.RootTask.run -import com.lambda.task.tasks.AcquireStackTask.Companion.acquireStack +import com.lambda.task.tasks.acquireStack import net.minecraft.item.Items @Suppress("unused") diff --git a/src/main/kotlin/com/lambda/module/modules/movement/TargetStrafe.kt b/src/main/kotlin/com/lambda/module/modules/movement/TargetStrafe.kt index 175c60513..71b8159dd 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/TargetStrafe.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/TargetStrafe.kt @@ -17,9 +17,9 @@ package com.lambda.module.modules.movement -import com.lambda.config.ConfigEditor.forEachSetting -import com.lambda.config.ConfigEditor.hideAllExcept import com.lambda.config.blocks.WorldLineSettings +import com.lambda.config.forEachSetting +import com.lambda.config.hideAllExcept import com.lambda.config.withEdits import com.lambda.context.SafeContext import com.lambda.event.events.MovementEvent diff --git a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFly.kt b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFly.kt index 9026773f5..a55f96e3e 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFly.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFly.kt @@ -17,12 +17,12 @@ package com.lambda.module.modules.movement.elytrafly -import com.lambda.config.ConfigEditor.editSetting -import com.lambda.config.ConfigEditor.forEachSetting -import com.lambda.config.ConfigEditor.hideAllExcept import com.lambda.config.Tab import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.editSetting import com.lambda.config.entries.Setting.Companion.onValueChange +import com.lambda.config.forEachSetting +import com.lambda.config.hideAllExcept import com.lambda.config.withEdits import com.lambda.event.events.ClientEvent import com.lambda.event.events.MovementEvent diff --git a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/GrimControlElytraFly.kt b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/GrimControlElytraFly.kt index 5adcbb206..b84478fbe 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/GrimControlElytraFly.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/GrimControlElytraFly.kt @@ -37,10 +37,6 @@ import com.lambda.module.modules.render.Freecam import com.lambda.util.NamedEnum import com.lambda.util.TickTimer import com.lambda.util.math.MathUtils.toFloat -import com.lambda.util.player.PlayerUtils.hasFirework -import net.minecraft.component.DataComponentTypes -import com.lambda.util.player.SlotUtils.hotbarStacks -import com.lambda.util.player.SlotUtils.inventoryStacks import net.minecraft.item.ItemStack import net.minecraft.item.Items import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket diff --git a/src/main/kotlin/com/lambda/module/modules/player/AutoEat.kt b/src/main/kotlin/com/lambda/module/modules/player/AutoEat.kt index e8ea02ee0..e1e3af19c 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/AutoEat.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/AutoEat.kt @@ -27,8 +27,8 @@ 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.EatTask.Companion.eat -import com.lambda.task.wrappers.thenAction +import com.lambda.task.tasks.eat +import com.lambda.task.tasks.wrappers.thenAction import com.lambda.threading.runSafeAutomated @Suppress("unused") diff --git a/src/main/kotlin/com/lambda/module/modules/player/FastBreak.kt b/src/main/kotlin/com/lambda/module/modules/player/FastBreak.kt index 996ae234c..b0270222a 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/FastBreak.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/FastBreak.kt @@ -27,7 +27,7 @@ import com.lambda.event.events.PlayerEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.construction.simulation.context.BuildContext -import com.lambda.interaction.manager.managers.breaking.BreakRequestBuilder.Companion.breakRequest +import com.lambda.interaction.manager.managers.breaking.breakRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.threading.runSafeAutomated diff --git a/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt b/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt index c6232d72e..a3339ba7a 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt @@ -27,11 +27,11 @@ 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.breakAndCollect -import com.lambda.task.tasks.OpenContainerTask.Companion.openContainer import com.lambda.task.tasks.PlaceContainerTask -import com.lambda.task.wrappers.then -import com.lambda.task.wrappers.thenAction +import com.lambda.task.tasks.breakAndCollect +import com.lambda.task.tasks.openContainer +import com.lambda.task.tasks.wrappers.then +import com.lambda.task.tasks.wrappers.thenAction import com.lambda.util.item.ItemUtils.shulkerBoxes import net.minecraft.item.Items import net.minecraft.screen.ScreenHandler diff --git a/src/main/kotlin/com/lambda/module/modules/player/LineLock.kt b/src/main/kotlin/com/lambda/module/modules/player/LineLock.kt index b1105ff51..5f254bce7 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/LineLock.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/LineLock.kt @@ -17,10 +17,10 @@ package com.lambda.module.modules.player -import com.lambda.config.ConfigEditor.editSetting -import com.lambda.config.ConfigEditor.hideAllExcept import com.lambda.config.Group import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.editSetting +import com.lambda.config.hideAllExcept import com.lambda.config.withEdits import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent diff --git a/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt b/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt index 80f0e5401..dabfd131b 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt @@ -47,7 +47,7 @@ object StackReplenish : Module( .withEdits { hideAllExcept(::inventoryConfig) inventoryConfig.apply { - hide(::disposables, ::swapWithDisposables, ::providerPriority, ::storePriority) + hide(::disposables, ::accessPriority, ::storePriority) } } diff --git a/src/main/kotlin/com/lambda/module/modules/world/AirPlace.kt b/src/main/kotlin/com/lambda/module/modules/world/AirPlace.kt index 850110aab..b29ce5000 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/AirPlace.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/AirPlace.kt @@ -33,7 +33,7 @@ import com.lambda.interaction.construction.simulation.context.BuildContext import com.lambda.interaction.construction.simulation.sim import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.manager.managers.interacting.InteractRequest -import com.lambda.interaction.manager.managers.interacting.PlaceRequestBuilder.Companion.interactRequest +import com.lambda.interaction.manager.managers.interacting.interactRequest import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotation import com.lambda.module.Module import com.lambda.module.tag.ModuleTag diff --git a/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt b/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt index 97bd0b005..8bde75a8b 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt @@ -49,8 +49,8 @@ import com.lambda.module.modules.world.AutoPortal.PosHandler.prevAnchorPos 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.wrappers.thenOrNull +import com.lambda.task.tasks.build +import com.lambda.task.tasks.wrappers.thenOrNull import com.lambda.threading.runSafe import com.lambda.util.BlockUtils.blockState import com.lambda.util.BlockUtils.isEmpty diff --git a/src/main/kotlin/com/lambda/module/modules/world/AutoVillagerCycle.kt b/src/main/kotlin/com/lambda/module/modules/world/AutoVillagerCycle.kt index 651b27adb..75e1ba526 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/AutoVillagerCycle.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/AutoVillagerCycle.kt @@ -36,8 +36,8 @@ import com.lambda.module.tag.ModuleTag import com.lambda.sound.SoundHandler.playSound import com.lambda.task.RootTask.run import com.lambda.task.Task -import com.lambda.task.tasks.BuildTask.Companion.build -import com.lambda.task.wrappers.thenAction +import com.lambda.task.tasks.build +import com.lambda.task.tasks.wrappers.thenAction import com.lambda.threading.runSafeAutomated import com.lambda.util.BlockUtils.blockState import com.lambda.util.BlockUtils.isEmpty diff --git a/src/main/kotlin/com/lambda/module/modules/world/HighwayTools.kt b/src/main/kotlin/com/lambda/module/modules/world/HighwayTools.kt index 62da6b69f..0972a2a43 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/HighwayTools.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/HighwayTools.kt @@ -28,7 +28,7 @@ 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.build import com.lambda.util.CommunicationUtils.info import com.lambda.util.Describable import com.lambda.util.NamedEnum diff --git a/src/main/kotlin/com/lambda/module/modules/world/Nuker.kt b/src/main/kotlin/com/lambda/module/modules/world/Nuker.kt index d58d517a3..026b3851d 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/Nuker.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/Nuker.kt @@ -28,7 +28,7 @@ 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.build import com.lambda.util.BlockUtils.blockPos import com.lambda.util.PlayerBuildLayerUtils.FlattenMode import com.lambda.util.PlayerBuildLayerUtils.isInBaritoneSelection diff --git a/src/main/kotlin/com/lambda/module/modules/world/Printer.kt b/src/main/kotlin/com/lambda/module/modules/world/Printer.kt index 97e290420..f2e2332e8 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/Printer.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/Printer.kt @@ -36,7 +36,7 @@ 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.build import com.lambda.util.BlockUtils.blockPos import com.lambda.util.CommunicationUtils.logError import com.lambda.util.Describable 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 5a584b65b..dd30460a3 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/Scaffold.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/Scaffold.kt @@ -32,7 +32,7 @@ import com.lambda.interaction.construction.simulation.sim import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack import com.lambda.interaction.inventory.container.containers.HotbarContainer -import com.lambda.interaction.manager.managers.interacting.PlaceRequestBuilder.Companion.interactRequest +import com.lambda.interaction.manager.managers.interacting.interactRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.threading.runSafeAutomated 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 8e89c99d4..0bdd2d006 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt @@ -54,10 +54,9 @@ 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.task.wrappers.thenAction -import com.lambda.task.tasks.OpenContainerTask.Companion.openContainer +import com.lambda.task.tasks.build +import com.lambda.task.tasks.openContainer +import com.lambda.task.tasks.wrappers.thenAction import com.lambda.threading.runSafeAutomated import com.lambda.util.BlockUtils.blockEntity import com.lambda.util.BlockUtils.blockState diff --git a/src/main/kotlin/com/lambda/task/RootTask.kt b/src/main/kotlin/com/lambda/task/RootTask.kt index cd73c3aa1..fac5f6208 100644 --- a/src/main/kotlin/com/lambda/task/RootTask.kt +++ b/src/main/kotlin/com/lambda/task/RootTask.kt @@ -17,7 +17,6 @@ package com.lambda.task -import com.lambda.task.wrappers.TaskSupplier import com.lambda.threading.runSafe object RootTask : Task() { @@ -30,7 +29,7 @@ object RootTask : Task() { } @Ta5kBuilder - fun Task<*>.run(task: TaskSupplier) { + fun Task<*>.run(task: TaskGenerator) { runSafe { task(Unit).execute(this@run) } diff --git a/src/main/kotlin/com/lambda/task/Task.kt b/src/main/kotlin/com/lambda/task/Task.kt index 109b3c1c4..8aaba1110 100644 --- a/src/main/kotlin/com/lambda/task/Task.kt +++ b/src/main/kotlin/com/lambda/task/Task.kt @@ -24,7 +24,7 @@ import com.lambda.event.Muteable import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.modules.client.Client.verboseDebug -import com.lambda.task.wrappers.onFail +import com.lambda.task.tasks.wrappers.withRecovery import com.lambda.threading.runSafe import com.lambda.util.CommunicationUtils.logError import com.lambda.util.Nameable @@ -240,7 +240,7 @@ abstract class Task : Nameable, Muteable { /** * Registers a callback for if the task fails. * - * This could be mistaken for [onFail] which is used to wrap the given task with another task that runs a recovery task if this one fails. + * This could be mistaken for [withRecovery] which is used to wrap the given task with another task that runs a recovery task if this one fails. */ @Ta5kBuilder fun onFailure(callback: SafeContext.(Throwable) -> Unit): Task { @@ -274,3 +274,9 @@ abstract class Task : Nameable, Muteable { } } } + +typealias TaskGenerator = SafeContext.(R) -> Task +typealias TaskOrNullGenerator = SafeContext.(R) -> Task? + +typealias TaskSupplier = SafeContext.() -> Task +typealias TaskOrNullSupplier = SafeContext.() -> Task? \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/task/tasks/AcquirePlacedBlockTask.kt b/src/main/kotlin/com/lambda/task/tasks/AcquirePlacedBlockTask.kt index cf705440a..30a0dbf73 100644 --- a/src/main/kotlin/com/lambda/task/tasks/AcquirePlacedBlockTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/AcquirePlacedBlockTask.kt @@ -24,16 +24,23 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.handler.handlers.ContainerHandler.findSlots import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.select import com.lambda.task.Task +import com.lambda.task.Task.Ta5kBuilder +import com.lambda.task.tasks.wrappers.softFail import com.lambda.threading.runSafeAutomated -import com.lambda.util.BlockUtils.blockPos -import com.lambda.util.BlockUtils.blockState +import com.lambda.util.BlockUtils.item import net.minecraft.block.Block -import net.minecraft.item.Items import net.minecraft.util.math.BlockPos -class AcquirePlacedBlockTask @Ta5kBuilder constructor( +@Ta5kBuilder +context(automated: Automated) +fun acquirePlacedBlock( + block: Block, + maxSearchRadius: Int = 10 +) = AcquirePlacedBlockTask(block, maxSearchRadius, automated) + +class AcquirePlacedBlockTask @Ta5kBuilder internal constructor( val block: Block, - val maxSearchRadius: Int = 10, + val searchRadius: Int = 10, automated: Automated ) : Task(), Automated by automated { override val name get() = "Acquiring placed ${block.name.string}" @@ -41,43 +48,25 @@ class AcquirePlacedBlockTask @Ta5kBuilder constructor( init { listen { runSafeAutomated { - Items.ENDER_CHEST + block.item .select(1) .findSlots() .firstOrNull()?.let { slot -> - PlaceContainerTask(slot, this).finally { - success(it) - }.execute(this@AcquirePlacedBlockTask) + PlaceContainerTask(slot, this) + .onSuccess { success(it) } + .execute(this@AcquirePlacedBlockTask) return@listen } } - failure("No ender chest found in range or hotbar/inventory!") + failure("No $block found in range or hotbar/inventory!") return@listen } } override fun SafeContext.onStart() { - checkInRange()?.let { success(it) } - } - - private fun SafeContext.checkInRange() = - BlockPos.iterateOutwards( - player.blockPos, - maxSearchRadius, - maxSearchRadius, - maxSearchRadius - ).map { it.blockPos } - .firstOrNull { blockPos -> - blockState(blockPos).block == block - } - - companion object { - @Ta5kBuilder - context(automated: Automated) - fun acquirePlacedBlock( - block: Block, - maxSearchRadius: Int = 10 - ) = AcquirePlacedBlockTask(block, maxSearchRadius, automated) + findBlock(block, searchRadius) + .softFail() + .execute(this@AcquirePlacedBlockTask) } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/task/tasks/AcquireStackTask.kt b/src/main/kotlin/com/lambda/task/tasks/AcquireStackTask.kt index 20d22e8af..167f4c542 100644 --- a/src/main/kotlin/com/lambda/task/tasks/AcquireStackTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/AcquireStackTask.kt @@ -24,11 +24,22 @@ import com.lambda.interaction.handler.handlers.ContainerHandler.findContainer import com.lambda.interaction.inventory.StackSelection import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.task.Task -import com.lambda.task.wrappers.thenAction +import com.lambda.task.Task.Ta5kBuilder +import com.lambda.task.tasks.wrappers.thenAction import com.lambda.threading.runSafeAutomated import net.minecraft.screen.slot.Slot -class AcquireStackTask @Ta5kBuilder constructor( +@Ta5kBuilder +context(automated: Automated) +fun acquireStack(selection: StackSelection) = + AcquireStackTask(selection, automated) + +@Ta5kBuilder +context(automated: Automated) +fun acquireStack(selection: () -> StackSelection) = + AcquireStackTask(selection(), automated) + +class AcquireStackTask @Ta5kBuilder internal constructor( val selection: StackSelection, automated: Automated ) : Task(), Automated by automated { @@ -44,16 +55,4 @@ class AcquireStackTask @Ta5kBuilder constructor( ?: failure(ContainerHandler.NoContainerFound(selection)) // ToDo: Create crafting path } } - - companion object { - @Ta5kBuilder - context(automated: Automated) - fun acquireStack(selection: StackSelection) = - AcquireStackTask(selection, automated) - - @Ta5kBuilder - context(automated: Automated) - fun acquireStack(selection: () -> StackSelection) = - AcquireStackTask(selection(), automated) - } } diff --git a/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt b/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt index c4dbb71d7..5053c5649 100644 --- a/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt @@ -48,13 +48,13 @@ import com.lambda.interaction.construction.simulation.sim import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.handler.handlers.BaritoneHandler import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer -import com.lambda.interaction.manager.managers.breaking.BreakRequestBuilder.Companion.breakRequest -import com.lambda.interaction.manager.managers.interacting.PlaceRequestBuilder.Companion.interactRequest +import com.lambda.interaction.manager.managers.breaking.breakRequest +import com.lambda.interaction.manager.managers.interacting.interactRequest import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.modules.client.Client import com.lambda.task.Task -import com.lambda.task.tasks.EatTask.Companion.eat -import com.lambda.task.wrappers.thenAction +import com.lambda.task.Task.Ta5kBuilder +import com.lambda.task.tasks.wrappers.thenAction import com.lambda.threading.runConcurrent import com.lambda.threading.runSafe import com.lambda.threading.runSafeAutomated @@ -79,7 +79,50 @@ import java.util.concurrent.ConcurrentLinkedQueue import kotlin.coroutines.cancellation.CancellationException import kotlin.math.sqrt -class BuildTask @Ta5kBuilder private constructor( +@Ta5kBuilder +fun Automated.build( + finishOnDone: Boolean = true, + collectDrops: Boolean = buildConfig.collectDrops, + lifeMaintenance: Boolean = false, + async: Boolean = false, + buildResultFilter: SafeContext.(BuildResult) -> Boolean = { true }, + blueprint: () -> Blueprint +) = BuildTask(blueprint(), finishOnDone, collectDrops, lifeMaintenance, async, this, buildResultFilter) + +@Ta5kBuilder +context(automated: Automated) +fun Structure.build( + finishOnDone: Boolean = true, + collectDrops: Boolean = automated.buildConfig.collectDrops, + lifeMaintenance: Boolean = false, + async: Boolean = false, + buildResultFilter: SafeContext.(BuildResult) -> Boolean = { true }, +) = BuildTask(toBlueprint(), finishOnDone, collectDrops, lifeMaintenance, async, automated, buildResultFilter) + +@Ta5kBuilder +context(automated: Automated) +fun Blueprint.build( + finishOnDone: Boolean = true, + collectDrops: Boolean = automated.buildConfig.collectDrops, + lifeMaintenance: Boolean = false, + async: Boolean = false, + buildResultFilter: SafeContext.(BuildResult) -> Boolean = { true }, +) = BuildTask(this, finishOnDone, collectDrops, lifeMaintenance, async, automated, buildResultFilter) + +@Ta5kBuilder +context(automated: Automated) +fun breakAndCollect( + blockPos: BlockPos, + finishOnDone: Boolean = true, + lifeMaintenance: Boolean = false, + async: Boolean = false, + buildResultFilter: SafeContext.(BuildResult) -> Boolean = { true }, +) = BuildTask( + blockPos.toStructure(TargetState.Empty).toBlueprint(), + finishOnDone, true, lifeMaintenance, async, automated, buildResultFilter +) + +class BuildTask @Ta5kBuilder internal constructor( private val blueprint: Blueprint, private val finishOnDone: Boolean, private val collectDrops: Boolean, @@ -340,54 +383,9 @@ class BuildTask @Ta5kBuilder private constructor( true } ?: false - fun iteratePropagating() = + private fun iteratePropagating() = if (blueprint is PropagatingBlueprint) { blueprint.next() ?: failure("Failed to propagate the next blueprint") true } else false - - companion object { - @Ta5kBuilder - fun Automated.build( - finishOnDone: Boolean = true, - collectDrops: Boolean = buildConfig.collectDrops, - lifeMaintenance: Boolean = false, - async: Boolean = false, - buildResultFilter: SafeContext.(BuildResult) -> Boolean = { true }, - blueprint: () -> Blueprint - ) = BuildTask(blueprint(), finishOnDone, collectDrops, lifeMaintenance, async, this, buildResultFilter) - - @Ta5kBuilder - context(automated: Automated) - fun Structure.build( - finishOnDone: Boolean = true, - collectDrops: Boolean = automated.buildConfig.collectDrops, - lifeMaintenance: Boolean = false, - async: Boolean = false, - buildResultFilter: SafeContext.(BuildResult) -> Boolean = { true }, - ) = BuildTask(toBlueprint(), finishOnDone, collectDrops, lifeMaintenance, async, automated, buildResultFilter) - - @Ta5kBuilder - context(automated: Automated) - fun Blueprint.build( - finishOnDone: Boolean = true, - collectDrops: Boolean = automated.buildConfig.collectDrops, - lifeMaintenance: Boolean = false, - async: Boolean = false, - buildResultFilter: SafeContext.(BuildResult) -> Boolean = { true }, - ) = BuildTask(this, finishOnDone, collectDrops, lifeMaintenance, async, automated, buildResultFilter) - - @Ta5kBuilder - context(automated: Automated) - fun breakAndCollect( - blockPos: BlockPos, - finishOnDone: Boolean = true, - lifeMaintenance: Boolean = false, - async: Boolean = false, - buildResultFilter: SafeContext.(BuildResult) -> Boolean = { true }, - ) = BuildTask( - blockPos.toStructure(TargetState.Empty).toBlueprint(), - finishOnDone, true, lifeMaintenance, async, automated, buildResultFilter - ) - } } diff --git a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt index fafcc2e6f..6207e8af2 100644 --- a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt @@ -26,11 +26,38 @@ import com.lambda.interaction.inventory.container.Container import com.lambda.interaction.inventory.container.ExternalContainer import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer import com.lambda.task.Task -import com.lambda.task.wrappers.thenAction +import com.lambda.task.Task.Ta5kBuilder +import com.lambda.task.tasks.wrappers.thenAction import com.lambda.threading.runSafeAutomated import net.minecraft.screen.slot.Slot -class ContainerTransferTask @Ta5kBuilder constructor( +@Ta5kBuilder +context(automated: Automated) +fun transfer( + stackSelection: StackSelection, + fromContainer: Container, + toContainer: Container, + failIfNoStack: Boolean = true +) = ContainerTransferTask(fromContainer, toContainer, stackSelection, failIfNoStack, automated) + +@Ta5kBuilder +context(automated: Automated) +@JvmName("transferByStackSelection") +fun transfer( + stackSelection: StackSelection, + toContainer: Container, + failIfNoStack: Boolean = true +) = transfer(stackSelection, stackSelection.findContainer() ?: HotbarAndInventoryContainer, toContainer, failIfNoStack) + +@Ta5kBuilder +context(automated: Automated) +@JvmName("transferExt") +fun StackSelection.transferTo( + toContainer: Container, + failIfNoStack: Boolean = true +) = transfer(this, findContainer() ?: HotbarAndInventoryContainer, toContainer, failIfNoStack) + +class ContainerTransferTask @Ta5kBuilder internal constructor( private var fromContainer: Container, private val toContainer: Container, private val stackSelection: StackSelection, @@ -43,24 +70,24 @@ class ContainerTransferTask @Ta5kBuilder constructor( listen { runSafeAutomated { if (fromContainer is ExternalContainer && toContainer is ExternalContainer) { - fromContainer.accessThen( + fromContainer.accessThen( afterOpen = { fromContainer.transferByTask(stackSelection, HotbarAndInventoryContainer, failIfNoStack) } ) { HotbarAndInventoryContainer.transferByTask(stackSelection, toContainer, failIfNoStack) - .finally { slot -> success(slot) } + .thenAction { slot -> success(slot) } }?.execute(this@ContainerTransferTask) return@listen } - fromContainer.accessThen { - toContainer.accessThen { - TransferTask().finally { slot -> + fromContainer.accessThen { + toContainer.accessThen { + TransferTask().thenAction { slot -> success(slot) } } - }.execute(this@ContainerTransferTask) + }?.execute(this@ContainerTransferTask) } } } @@ -93,34 +120,4 @@ class ContainerTransferTask @Ta5kBuilder constructor( private inner class NoMaterialAccessException(stackSelection: StackSelection) : IllegalStateException("Unable to access $stackSelection.") } - - companion object { - @Ta5kBuilder - context(automated: Automated) - fun transfer( - fromContainer: Container, - toContainer: Container, - stackSelection: StackSelection, - failIfNoStack: Boolean = false - ) = ContainerTransferTask(fromContainer, toContainer, stackSelection, failIfNoStack, automated) - - @Ta5kBuilder - context(automated: Automated) - fun transfer( - stackSelection: StackSelection, - toContainer: Container, - failIfNoStack: Boolean = false - ) = stackSelection.findContainer()?.let { - ContainerTransferTask(it, toContainer, stackSelection, failIfNoStack, automated) - } - - @Ta5kBuilder - context(automated: Automated) - fun StackSelection.transfer( - toContainer: Container, - failIfNoStack: Boolean = false - ) = findContainer()?.let { - ContainerTransferTask(it, toContainer, this, failIfNoStack, automated) - } - } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/task/tasks/EatTask.kt b/src/main/kotlin/com/lambda/task/tasks/EatTask.kt index 0677ba082..c68f4ae24 100644 --- a/src/main/kotlin/com/lambda/task/tasks/EatTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/EatTask.kt @@ -27,12 +27,17 @@ import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.interaction.inventory.container.containers.InventoryContainer import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest import com.lambda.task.Task +import com.lambda.task.Task.Ta5kBuilder import com.lambda.threading.runSafeAutomated import net.minecraft.item.ItemStack import net.minecraft.util.ActionResult import net.minecraft.util.Hand -class EatTask @Ta5kBuilder constructor( +@Ta5kBuilder +context(automated: Automated) +fun eat() = EatTask(automated) + +class EatTask @Ta5kBuilder internal constructor( automated: Automated ) : Task(), Automated by automated { override val name: String @@ -94,10 +99,4 @@ class EatTask @Ta5kBuilder constructor( } } } - - companion object { - @Ta5kBuilder - context(automated: Automated) - fun eat() = EatTask(automated) - } } diff --git a/src/main/kotlin/com/lambda/task/tasks/FindPlacedBlockTask.kt b/src/main/kotlin/com/lambda/task/tasks/FindPlacedBlockTask.kt new file mode 100644 index 000000000..7ec969db0 --- /dev/null +++ b/src/main/kotlin/com/lambda/task/tasks/FindPlacedBlockTask.kt @@ -0,0 +1,45 @@ +/* + * 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.task.tasks + +import com.lambda.context.SafeContext +import com.lambda.task.Task +import com.lambda.task.Task.Ta5kBuilder +import com.lambda.util.world.blockSearch +import net.minecraft.block.Block +import net.minecraft.util.math.BlockPos + +@Ta5kBuilder +fun findBlock(block: Block, searchRadius: Int) = FindPlacedBlockTask(block, searchRadius) + +class FindPlacedBlockTask( + private val block: Block, + private val searchRadius: Int +) : Task() { + override val name = "Finding placed block: $block" + + override fun SafeContext.onStart() { + val pos = + blockSearch(searchRadius, player.blockPos) { _, state -> + state.isOf(block) + }.keys.firstOrNull() + + if (pos != null) success(pos) + else failure(IllegalStateException("Could not find block $block")) + } +} \ 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 acb95a451..be2dbea17 100644 --- a/src/main/kotlin/com/lambda/task/tasks/OpenContainerTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/OpenContainerTask.kt @@ -25,6 +25,7 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.handler.handlers.BaritoneHandler import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.task.Task +import com.lambda.task.Task.Ta5kBuilder import com.lambda.threading.runSafeAutomated import com.lambda.util.TickTimer import com.lambda.util.player.RotationUtils.lookAtBlock @@ -34,7 +35,15 @@ import net.minecraft.util.Hand import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Direction -class OpenContainerTask @Ta5kBuilder constructor( +@Ta5kBuilder +context(automated: Automated) +fun openContainer( + blockPos: BlockPos, + waitForSlotLoad: Boolean = true, + sides: Set = Direction.entries.toSet() +) = OpenContainerTask(blockPos, waitForSlotLoad, sides, automated) + +class OpenContainerTask @Ta5kBuilder internal constructor( private val blockPos: BlockPos, private val waitForSlotLoad: Boolean = true, private val sides: Set = Direction.entries.toSet(), @@ -112,14 +121,4 @@ class OpenContainerTask @Ta5kBuilder constructor( containerState = State.Opening } } - - companion object { - @Ta5kBuilder - context(automated: Automated) - fun openContainer( - blockPos: BlockPos, - waitForSlotLoad: Boolean = true, - sides: Set = Direction.entries.toSet() - ) = OpenContainerTask(blockPos, waitForSlotLoad, sides, automated) - } } diff --git a/src/main/kotlin/com/lambda/task/tasks/PlaceContainerTask.kt b/src/main/kotlin/com/lambda/task/tasks/PlaceContainerTask.kt index 667b8230a..92a451718 100644 --- a/src/main/kotlin/com/lambda/task/tasks/PlaceContainerTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/PlaceContainerTask.kt @@ -27,8 +27,8 @@ import com.lambda.interaction.construction.simulation.sim import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.manager.ManagerUtils import com.lambda.task.Task -import com.lambda.task.tasks.BuildTask.Companion.build -import com.lambda.task.wrappers.thenAction +import com.lambda.task.Task.Ta5kBuilder +import com.lambda.task.tasks.wrappers.thenAction import com.lambda.threading.runSafeAutomated import com.lambda.util.BlockUtils.blockPos import com.lambda.util.item.ItemUtils.shulkerBoxes @@ -41,7 +41,15 @@ import net.minecraft.screen.slot.Slot import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Direction -class PlaceContainerTask @Ta5kBuilder constructor( +@Ta5kBuilder +context(automated: Automated) +fun placeContainer(slot: Slot?) = PlaceContainerTask(slot, automated) + +@Ta5kBuilder +context(automated: Automated) +fun placeContainer(slot: () -> Slot?) = PlaceContainerTask(slot(), automated) + +class PlaceContainerTask @Ta5kBuilder internal constructor( val slot: Slot?, automated: Automated ) : Task(), Automated by automated { @@ -102,15 +110,4 @@ class PlaceContainerTask @Ta5kBuilder constructor( } else -> false } - - @Suppress("unused") - companion object { - @Ta5kBuilder - context(automated: Automated) - fun placeContainer(slot: Slot?) = PlaceContainerTask(slot, automated) - - @Ta5kBuilder - context(automated: Automated) - fun placeContainer(slot: () -> Slot?) = PlaceContainerTask(slot(), automated) - } } diff --git a/src/main/kotlin/com/lambda/task/tasks/SimpleActionTask.kt b/src/main/kotlin/com/lambda/task/tasks/SimpleActionTask.kt index 27b7ad071..5493c61cb 100644 --- a/src/main/kotlin/com/lambda/task/tasks/SimpleActionTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/SimpleActionTask.kt @@ -19,18 +19,17 @@ package com.lambda.task.tasks import com.lambda.context.SafeContext import com.lambda.task.Task +import com.lambda.task.Task.Ta5kBuilder -class SimpleActionTask @Ta5kBuilder constructor( +@Ta5kBuilder +fun simpleAction(name: String, action: SafeContext.() -> Unit) = SimpleActionTask(name, action) + +class SimpleActionTask @Ta5kBuilder internal constructor( override val name: String, - val action: () -> Unit + val action: SafeContext.() -> Unit ) : Task() { override fun SafeContext.onStart() { action() success() } - - companion object { - @Ta5kBuilder - fun simpleAction(name: String, action: () -> Unit) = SimpleActionTask(name, action) - } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/task/tasks/SlotByCacheTask.kt b/src/main/kotlin/com/lambda/task/tasks/SlotByCacheTask.kt index 4402252b7..867777ba5 100644 --- a/src/main/kotlin/com/lambda/task/tasks/SlotByCacheTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/SlotByCacheTask.kt @@ -19,10 +19,14 @@ package com.lambda.task.tasks import com.lambda.context.SafeContext import com.lambda.task.Task +import com.lambda.task.Task.Ta5kBuilder import com.lambda.util.player.SlotUtils.matches import net.minecraft.screen.slot.Slot -class SlotByCacheTask @Ta5kBuilder constructor( +@Ta5kBuilder +fun slotByCache(slotCache: Slot) = SlotByCacheTask(slotCache) + +class SlotByCacheTask @Ta5kBuilder internal constructor( val slotCache: Slot ) : Task() { override val name = "Finding slot by cache: $slotCache" @@ -33,9 +37,4 @@ class SlotByCacheTask @Ta5kBuilder constructor( if (slot != null) success(slot) else failure("Failed to find matching slot by cache: $slotCache") } - - companion object { - @Ta5kBuilder - fun slotByCache(slotCache: Slot) = SlotByCacheTask(slotCache) - } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/task/wrappers/ActionTask.kt b/src/main/kotlin/com/lambda/task/tasks/wrappers/ActionTasks.kt similarity index 69% rename from src/main/kotlin/com/lambda/task/wrappers/ActionTask.kt rename to src/main/kotlin/com/lambda/task/tasks/wrappers/ActionTasks.kt index 01f3af7c2..d1bb7290e 100644 --- a/src/main/kotlin/com/lambda/task/wrappers/ActionTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/wrappers/ActionTasks.kt @@ -15,16 +15,30 @@ * along with this program. If not, see . */ -package com.lambda.task.wrappers +package com.lambda.task.tasks.wrappers import com.lambda.context.SafeContext import com.lambda.task.Task import com.lambda.task.Task.Ta5kBuilder @Ta5kBuilder -infix fun Task.thenAction(action: SafeContext.(R) -> Unit): Task = +fun actionTask(action: SafeContext.() -> R) = + ActionTask(action) + +@Ta5kBuilder +infix fun Task.thenAction(action: SafeContext.(R) -> R2): Task = SequencedActionTask(this, action) +class ActionTask @Ta5kBuilder internal constructor( + private val action: SafeContext.() -> R +) : Task() { + override val name get() = "Performing action" + + override fun SafeContext.onStart() { + success(action()) + } +} + /** * A task that performs a given task ([innerTask]) and then a given [action]. * @@ -32,17 +46,16 @@ infix fun Task.thenAction(action: SafeContext.(R) -> Unit): Task = * * @see thenAction */ -class SequencedActionTask( +class SequencedActionTask @Ta5kBuilder internal constructor( private val innerTask: Task, - private val action: SafeContext.(R) -> Unit, -) : Task() { + private val action: SafeContext.(R) -> R2, +) : Task() { override val name get() = "Performing action after ${innerTask.name}" override fun SafeContext.onStart() { innerTask .onSuccess { result -> - action(this, result) - success(result) + success(action(this, result)) } .execute(this@SequencedActionTask) } diff --git a/src/main/kotlin/com/lambda/task/tasks/wrappers/BranchTasks.kt b/src/main/kotlin/com/lambda/task/tasks/wrappers/BranchTasks.kt new file mode 100644 index 000000000..1228310e9 --- /dev/null +++ b/src/main/kotlin/com/lambda/task/tasks/wrappers/BranchTasks.kt @@ -0,0 +1,103 @@ +/* + * 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.task.tasks.wrappers + +import com.lambda.context.SafeContext +import com.lambda.task.Task +import com.lambda.task.Task.Ta5kBuilder +import com.lambda.task.TaskGenerator +import com.lambda.task.TaskOrNullGenerator + +@Ta5kBuilder +fun Task.withBranch( + onSuccess: TaskGenerator, + onFailure: TaskGenerator +) = BranchTask(this, onSuccess, onFailure) + +@Ta5kBuilder +fun Task.withBranchOrNull( + onSuccess: TaskOrNullGenerator, + onFailure: TaskOrNullGenerator +) = OptionalBranchTask(this, onSuccess, onFailure) + +/** + * A task that strictly branches into one of two tasks based on the success or failure of the [innerTask]. + * + * @see withBranch + */ +class BranchTask @Ta5kBuilder internal constructor( + private val innerTask: Task, + private val onSuccessGenerator: TaskGenerator, + private val onFailureGenerator: TaskGenerator +) : Task() { + override val name get() = "Branching task for ${innerTask.name}" + + override fun SafeContext.onStart() { + innerTask + .onSuccess { result -> + onSuccessGenerator(this, result) + .onSuccess { success(it) } + .execute(this@BranchTask) + } + .onFailure { cause -> + onFailureGenerator(this, cause) + .onSuccess { success(it) } + .execute(this@BranchTask) + } + .execute(this@BranchTask) + } + + override fun onSubTaskFailure(subTask: Task<*>, cause: Throwable) { + if (subTask != innerTask) super.onSubTaskFailure(subTask, cause) + } +} + +/** + * A task that strictly branches into one of two optional tasks based on the success or failure of the [innerTask]. + * + * @see withBranchOrNull + */ +class OptionalBranchTask @Ta5kBuilder internal constructor( + private val innerTask: Task, + private val onSuccessGenerator: TaskOrNullGenerator, + private val onFailureGenerator: TaskOrNullGenerator +) : Task() { + override val name get() = "Optional branching task for ${innerTask.name}" + + override fun SafeContext.onStart() { + innerTask + .onSuccess { result -> + onSuccessGenerator(this, result) + ?.onSuccess { success(it) } + ?.execute(this@OptionalBranchTask) + ?: success(null) + } + .onFailure { cause -> + onFailureGenerator(this, cause) + ?.onSuccess { success(it) } + ?.execute(this@OptionalBranchTask) + ?: failure("Optional onFailure task not present for task: ${innerTask.name}") + } + .execute(this@OptionalBranchTask) + } + + override fun onSubTaskFailure(subTask: Task<*>, cause: Throwable) { + if (subTask != innerTask) super.onSubTaskFailure(subTask, cause) + } +} + diff --git a/src/main/kotlin/com/lambda/task/tasks/wrappers/OnFailTasks.kt b/src/main/kotlin/com/lambda/task/tasks/wrappers/OnFailTasks.kt new file mode 100644 index 000000000..a2b242777 --- /dev/null +++ b/src/main/kotlin/com/lambda/task/tasks/wrappers/OnFailTasks.kt @@ -0,0 +1,167 @@ +/* + * 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.task.tasks.wrappers + +import com.lambda.context.SafeContext +import com.lambda.task.Task +import com.lambda.task.Task.Ta5kBuilder +import com.lambda.task.TaskGenerator +import com.lambda.task.TaskOrNullGenerator +import com.lambda.threading.runSafe + +@Ta5kBuilder +infix fun Task.onFail(handler: TaskGenerator) = + OnFailTask(this, handler) + +@Ta5kBuilder +infix fun Task.onFailOrNull(handler: TaskOrNullGenerator) = + OnFailOrNullTask(this, handler) + +@Ta5kBuilder +infix fun Task.withRecovery(handler: TaskGenerator) = + RecoveryTask(this, handler) + +@Ta5kBuilder +infix fun Task.withRecoveryOrNull(handler: SafeContext.(Throwable) -> Task?) = + OptionalRecoveryTask(this, handler) + +/** + * A task that performs a given [innerTask] with a callback task in the event the [innerTask] fails. + * + * Useful for when you want a certain task to trigger when another task fails. + * If in need of a task that returns the same type to avoid further branching, see [withRecovery] or [withRecoveryOrNull]. + * + * @see onFail + */ +class OnFailTask @Ta5kBuilder internal constructor( + private val innerTask: Task, + private val onFailTaskGenerator: TaskGenerator +) : Task() { + override val name get() = "On fail task for ${innerTask.name}" + + override fun SafeContext.onStart() { + innerTask + .onSuccess { success(it) } + .onFailure { cause -> + onFailTaskGenerator(cause) + .onSuccess { success(null) } + .execute(this@OnFailTask) + } + .execute(this@OnFailTask) + } + + override fun onSubTaskFailure(subTask: Task<*>, cause: Throwable) { + if (subTask != innerTask) super.onSubTaskFailure(subTask, cause) + } +} + +/** + * A task that performs a given [innerTask] with an optional callback task in the event the [innerTask] fails. + * + * Useful for when you want a certain task to trigger based on a predicate when another task fails. + * If in need of a task that returns the same type to avoid further branching, see [withRecovery] or [withRecoveryOrNull]. + * + * @see onFailOrNull + */ +class OnFailOrNullTask @Ta5kBuilder internal constructor( + private val innerTask: Task, + private val onFailTaskGenerator: TaskOrNullGenerator +) : Task() { + override val name get() = "On fail task for ${innerTask.name}" + + override fun SafeContext.onStart() { + innerTask + .onSuccess { success(it) } + .execute(this@OnFailOrNullTask) + } + + override fun onSubTaskFailure(subTask: Task<*>, cause: Throwable) { + if (subTask == innerTask) { + runSafe { + onFailTaskGenerator(cause) + ?.onSuccess { success(null) } + ?.execute(this@OnFailOrNullTask) + ?: super.onSubTaskFailure(subTask, cause) + } + } else super.onSubTaskFailure(subTask, cause) + } +} + +/** + * A task that performs a given [innerTask] with a fallback to the task supplied by [recoveryTaskGenerator] in case of failure. + * Both the [innerTask] and the task generated by [recoveryTaskGenerator] have the same result type. + * + * Useful for if you want to have a backup task to return the same result type in case the first fails. + * This leaves the task branch clean of further branching while providing protection against individual task failure. + * + * @see withRecovery + */ +class RecoveryTask @Ta5kBuilder internal constructor( + private val innerTask: Task, + private val recoveryTaskGenerator: TaskGenerator, +) : Task() { + override val name get() = "Recovery task in case of failure for ${innerTask.name}" + + override fun SafeContext.onStart() { + innerTask + .onSuccess { success(it) } + .onFailure { cause -> + recoveryTaskGenerator(this, cause) + .onSuccess { success(it) } + .execute(this@RecoveryTask) + } + .execute(this@RecoveryTask) + } + + override fun onSubTaskFailure(subTask: Task<*>, cause: Throwable) { + if (subTask != innerTask) super.onSubTaskFailure(subTask, cause) + } +} + +/** + * A task that performs a given [innerTask] with a fallback to the optional task supplied by [recoveryTaskGenerator] in case of failure. + * Both the [innerTask] and the optional task generated by [recoveryTaskGenerator] have the same result type. + * + * Useful for if you want to have an optional backup task to return the same result type in case the first fails. + * This leaves the task branch clean of further branching while providing protection against individual task failure, unless null is returned from [recoveryTaskGenerator]. + * + * @see withRecovery + */ +class OptionalRecoveryTask @Ta5kBuilder internal constructor( + private val innerTask: Task, + private val recoveryTaskGenerator: TaskOrNullGenerator, +) : Task() { + override val name get() = "Optional recovery task in case of failure for ${innerTask.name}" + + override fun SafeContext.onStart() { + innerTask + .onSuccess { success(it) } + .execute(this@OptionalRecoveryTask) + } + + override fun onSubTaskFailure(subTask: Task<*>, cause: Throwable) { + if (subTask == innerTask) { + runSafe { + recoveryTaskGenerator(this, cause) + ?.onSuccess { success(it) } + ?.execute(this@OptionalRecoveryTask) + ?: super.onSubTaskFailure(subTask, cause) + } + } else super.onSubTaskFailure(subTask, cause) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/task/tasks/wrappers/ResultTasks.kt b/src/main/kotlin/com/lambda/task/tasks/wrappers/ResultTasks.kt new file mode 100644 index 000000000..c846d40a6 --- /dev/null +++ b/src/main/kotlin/com/lambda/task/tasks/wrappers/ResultTasks.kt @@ -0,0 +1,57 @@ +/* + * 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.task.tasks.wrappers + +import com.lambda.context.SafeContext +import com.lambda.task.Task +import com.lambda.task.Task.Ta5kBuilder + +@Ta5kBuilder +fun successTask(result: R) = + SuccessTask(result) + +@Ta5kBuilder +fun successTask() = successTask(Unit) + +@Ta5kBuilder +fun failureTask(throwable: R) = + FailureTask(throwable) + +@Ta5kBuilder +fun failureTask(cause: String) = + FailureTask(IllegalStateException(cause)) + +class SuccessTask @Ta5kBuilder internal constructor( + val result: R +) : Task() { + override val name = "Success task" + + override fun SafeContext.onStart() { + success(result) + } +} + +class FailureTask @Ta5kBuilder internal constructor( + val throwable: R +) : Task() { + override val name = "Failure task" + + override fun SafeContext.onStart() { + failure(throwable) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/task/wrappers/SequenceTasks.kt b/src/main/kotlin/com/lambda/task/tasks/wrappers/SequenceTasks.kt similarity index 58% rename from src/main/kotlin/com/lambda/task/wrappers/SequenceTasks.kt rename to src/main/kotlin/com/lambda/task/tasks/wrappers/SequenceTasks.kt index c8726880c..ea00d449d 100644 --- a/src/main/kotlin/com/lambda/task/wrappers/SequenceTasks.kt +++ b/src/main/kotlin/com/lambda/task/tasks/wrappers/SequenceTasks.kt @@ -15,21 +15,22 @@ * along with this program. If not, see . */ -package com.lambda.task.wrappers +package com.lambda.task.tasks.wrappers import com.lambda.context.SafeContext import com.lambda.task.Task import com.lambda.task.Task.Ta5kBuilder +import com.lambda.task.TaskGenerator +import com.lambda.task.TaskOrNullGenerator +import com.lambda.task.TaskOrNullSupplier @Ta5kBuilder -infix fun Task.then(supplier: TaskSupplier) = - SequencedTask(this, supplier) +infix fun Task.then(generator: TaskGenerator) = + SequencedTask(this, generator) @Ta5kBuilder -infix fun Task.then(task: Task): Task { - require(task != this) { "Cannot link a task to itself" } - return SequencedTask(this) { task } -} +infix fun Task.then(task: Task): Task = + SequencedTask(this) { task } @Ta5kBuilder fun Task.then(vararg tasks: Task<*>) = @@ -38,19 +39,22 @@ fun Task.then(vararg tasks: Task<*>) = } @Ta5kBuilder -infix fun Task.thenOrNull(supplier: SafeContext.(R) -> Task?) = - OptionalSequencedTask(this, supplier) +infix fun Task.thenOrNull(generator: TaskOrNullGenerator) = + OptionalSequencedTask(this, generator) + +@Ta5kBuilder +fun taskOrNull(supplier: TaskOrNullSupplier) = TaskOrNullTask(supplier) /** - * A task that sequences the [firstTask] with the next task, supplied by [nextTaskSupplier]. + * A task that sequences the [firstTask] with the next task, supplied by [nextTaskGenerator]. * * Useful for when two tasks must be run sequentially. The result of the first task is fed into the supplier for the second. * * @see then */ -class SequencedTask( +class SequencedTask @Ta5kBuilder internal constructor( private val firstTask: Task, - private val nextTaskSupplier: TaskSupplier, + private val nextTaskGenerator: TaskGenerator, ) : Task() { override val name get() = "Chaining ${firstTask.name}" var second: Task? = null @@ -58,7 +62,7 @@ class SequencedTask( override fun SafeContext.onStart() { firstTask .onSuccess { result -> - second = nextTaskSupplier(this, result) + second = nextTaskGenerator(this, result) .onSuccess { success(it) } .execute(this@SequencedTask) } @@ -67,26 +71,46 @@ class SequencedTask( } /** - * A task that sequences the [firstTask] with the next optional task, supplied by [nextTaskSupplier]. + * A task that sequences the [firstTask] with the next optional task, supplied by [nextTaskGenerator]. * * Useful for when you want to sequence a second task after the first based on a predicate. The result of the first task could help shape that outcome. * * @see thenOrNull */ -class OptionalSequencedTask( +class OptionalSequencedTask @Ta5kBuilder internal constructor( private val firstTask: Task, - private val nextTaskSupplier: TaskOrNullSupplier + private val nextTaskGenerator: TaskOrNullGenerator ) : Task() { override val name get() = "Chaining ${firstTask.name}" override fun SafeContext.onStart() { firstTask .onSuccess { result -> - nextTaskSupplier(this, result) + nextTaskGenerator(this, result) ?.onSuccess { success(it) } ?.execute(this@OptionalSequencedTask) ?: success(null) } .execute(this@OptionalSequencedTask) } +} + +/** + * A task that runs an optional task, supplied by [taskOrNullSupplier]. + * + * Useful for starting a task branch where the first task could or could not be skipped. + * + * @see taskOrNull + */ +class TaskOrNullTask @Ta5kBuilder internal constructor( + private val taskOrNullSupplier: TaskOrNullSupplier +) : Task() { + override val name get() = "Optional task" + + override fun SafeContext.onStart() { + taskOrNullSupplier() + ?.onSuccess { success(it) } + ?.execute(this@TaskOrNullTask) + ?: success(null) + } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/task/wrappers/SoftFailTask.kt b/src/main/kotlin/com/lambda/task/tasks/wrappers/SoftFailTask.kt similarity index 89% rename from src/main/kotlin/com/lambda/task/wrappers/SoftFailTask.kt rename to src/main/kotlin/com/lambda/task/tasks/wrappers/SoftFailTask.kt index 6163a43b0..5a0cb6004 100644 --- a/src/main/kotlin/com/lambda/task/wrappers/SoftFailTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/wrappers/SoftFailTask.kt @@ -15,15 +15,12 @@ * along with this program. If not, see . */ -package com.lambda.task.wrappers +package com.lambda.task.tasks.wrappers import com.lambda.context.SafeContext import com.lambda.task.Task import com.lambda.task.Task.Ta5kBuilder -typealias TaskSupplier = SafeContext.(R) -> Task -typealias TaskOrNullSupplier = SafeContext.(R) -> Task? - @Ta5kBuilder fun Task.softFail(): Task = SoftFailTask(this) @@ -35,7 +32,7 @@ fun Task.softFail(): Task = * * @see softFail */ -class SoftFailTask( +class SoftFailTask @Ta5kBuilder internal constructor( private val innerTask: Task, ) : Task() { override val name get() = "Soft fail protection for ${innerTask.name}" diff --git a/src/main/kotlin/com/lambda/task/wrappers/RecoveryTasks.kt b/src/main/kotlin/com/lambda/task/wrappers/RecoveryTasks.kt deleted file mode 100644 index 990e526f1..000000000 --- a/src/main/kotlin/com/lambda/task/wrappers/RecoveryTasks.kt +++ /dev/null @@ -1,92 +0,0 @@ -/* - * 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.task.wrappers - -import com.lambda.context.SafeContext -import com.lambda.task.Task -import com.lambda.task.Task.Ta5kBuilder -import com.lambda.threading.runSafe - -@Ta5kBuilder -infix fun Task.onFail(handler: SafeContext.(Throwable) -> Task) = - RecoveryTask(this, handler) - -@Ta5kBuilder -infix fun Task.onFailOrNull(handler: SafeContext.(Throwable) -> Task?) = - OptionalRecoveryTask(this, handler) - -/** - * A task that performs a given task ([innerTask]) with a fallback to the task supplied by [recoveryTaskSupplier] in-case of failure. - * - * Useful for if you want to keep the task branch going even in the event an individual task fails. - * - * @see onFail - */ -class RecoveryTask( - private val innerTask: Task, - private val recoveryTaskSupplier: SafeContext.(Throwable) -> Task, -) : Task() { - override val name get() = "Recovery task in case of failure for ${innerTask.name}" - - override fun SafeContext.onStart() { - innerTask - .onSuccess { success(null) } - .execute(this@RecoveryTask) - } - - override fun onSubTaskFailure(subTask: Task<*>, cause: Throwable) { - if (subTask == innerTask) { - runSafe { - recoveryTaskSupplier(this, cause) - .onSuccess { success(it) } - .execute(this@RecoveryTask) - } - } else super.onSubTaskFailure(subTask, cause) - } -} - -/** - * A task that performs a given task ([innerTask]) with a fallback to the nullable task supplied by [recoveryTaskSupplier] in-case of failure. - * - * Useful for if you want to keep the task branch going even in the event an individual task fails, but want to supply the recovery task based on a predicate. - * - * @see onFailOrNull - */ -class OptionalRecoveryTask( - private val innerTask: Task, - private val recoveryTaskSupplier: SafeContext.(Throwable) -> Task?, -) : Task() { - override val name get() = "Optional recovery task in case of failure for ${innerTask.name}" - - override fun SafeContext.onStart() { - innerTask - .onSuccess { success(null) } - .execute(this@OptionalRecoveryTask) - } - - override fun onSubTaskFailure(subTask: Task<*>, cause: Throwable) { - if (subTask == innerTask) { - runSafe { - recoveryTaskSupplier(this, cause) - ?.onSuccess { success(it) } - ?.execute(this@OptionalRecoveryTask) - ?: super.onSubTaskFailure(subTask, cause) - } - } else super.onSubTaskFailure(subTask, cause) - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/util/world/WorldUtils.kt b/src/main/kotlin/com/lambda/util/world/WorldUtils.kt index e9b3e4fe7..a13fa4684 100644 --- a/src/main/kotlin/com/lambda/util/world/WorldUtils.kt +++ b/src/main/kotlin/com/lambda/util/world/WorldUtils.kt @@ -171,13 +171,13 @@ object WorldUtils { range: FastVector, step: FastVector, ) = sequence { - for (x in -range.x..range.x step step.x) { - for (y in -range.y..range.y step step.y) { - for (z in -range.z..range.z step step.z) { - yield(pos plus fastVectorOf(x, y, z)) - } - } - } + (-range.x..range.x step step.x).forEach { x -> + (-range.y..range.y step step.y).forEach { y -> + (-range.z..range.z step step.z).forEach { z -> + yield(pos plus fastVectorOf(x, y, z)) + } + } + } } } From d008a3603ad6f7aa5f8b030fa0da51f693a4d3c3 Mon Sep 17 00:00:00 2001 From: beanbag44 <107891830+beanbag44@users.noreply.github.com> Date: Sat, 29 Aug 2026 19:25:04 +0100 Subject: [PATCH 09/43] cleanup. Last thing left is returning slots for picked up items or something --- .../command/commands/TransferCommand.kt | 12 +- .../lambda/config/blocks/InventoryConfig.kt | 3 +- .../lambda/config/blocks/InventorySettings.kt | 4 +- .../simulation/result/results/BreakResult.kt | 7 +- .../result/results/GenericResult.kt | 7 +- .../construction/simulation/sims/BreakSim.kt | 8 +- .../simulation/sims/InteractSim.kt | 6 +- .../handler/handlers/ContainerHandler.kt | 12 +- .../inventory/ContainerSelection.kt | 5 +- .../inventory/container/Container.kt | 89 ++++----------- .../inventory/container/ContainerRank.kt} | 32 +++--- .../container/OpenedContainerContext.kt | 6 +- .../container/containers/ArmorContainer.kt | 3 +- .../container/containers/CreativeContainer.kt | 5 +- .../containers/HotbarAndInventoryContainer.kt | 3 +- .../container/containers/HotbarContainer.kt | 5 +- .../containers/InventoryContainer.kt | 3 +- .../container/containers/MainHandContainer.kt | 5 +- .../container/containers/OffHandContainer.kt | 5 +- .../container/containers/PlayerContainer.kt | 3 +- .../containers/external/ChestContainer.kt | 12 +- .../external/EnderChestContainer.kt | 19 ++-- .../external/PlacedShulkerBoxContainer.kt | 3 +- .../external/ShulkerBoxContainer.kt | 29 +++-- .../containers/external/StashContainer.kt | 7 +- .../lambda/module/modules/combat/AutoTotem.kt | 2 +- .../module/modules/combat/CrystalAura.kt | 4 +- .../module/modules/debug/ContainerTest.kt | 2 +- .../module/modules/movement/BetterFirework.kt | 2 +- .../elytrafly/modes/GrimControlElytraFly.kt | 2 +- .../task/tasks/AcquirePlacedBlockTask.kt | 2 +- .../com/lambda/task/tasks/AcquireStackTask.kt | 5 +- .../task/tasks/ContainerTransferTask.kt | 103 +++++++++++------- .../kotlin/com/lambda/task/tasks/EatTask.kt | 11 +- .../lambda/task/tasks/FindPlacedBlockTask.kt | 2 +- .../lambda/task/tasks/wrappers/ResultTasks.kt | 57 ---------- 36 files changed, 208 insertions(+), 277 deletions(-) rename src/main/kotlin/com/lambda/{task/tasks/SimpleActionTask.kt => interaction/inventory/container/ContainerRank.kt} (60%) delete mode 100644 src/main/kotlin/com/lambda/task/tasks/wrappers/ResultTasks.kt diff --git a/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt b/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt index a9e046b44..949724141 100644 --- a/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt +++ b/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt @@ -34,6 +34,7 @@ import com.lambda.interaction.handler.handlers.ContainerHandler.findContainersWi import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack import com.lambda.task.RootTask import com.lambda.task.Task +import com.lambda.task.tasks.transfer import com.lambda.threading.runSafeAutomated import com.lambda.util.CommunicationUtils.info import com.lambda.util.extension.CommandBuilder @@ -55,7 +56,7 @@ object TransferCommand : LambdaCommand( isItem(stack(ctx).value().item) } AutomationConfig.DEFAULT.runSafeAutomated { - val containers = selection.findContainers() + val containers = selection.findContainers().toList() val indexedContainers = containers.withIndex() suggestMatching( @@ -65,7 +66,7 @@ object TransferCommand : LambdaCommand( "\"${index + 1}. ${container.name}\"" }, { (_, container) -> - container.description(selection) + container.descriptionAndStock(selection) } ) } ?: builder.buildFuture() @@ -76,7 +77,7 @@ object TransferCommand : LambdaCommand( isItem(stack(ctx).value().item) } AutomationConfig.DEFAULT.runSafeAutomated { - val containers = selection.findContainersWithSpace() + val containers = selection.findContainersWithSpace().toList() val indexedContainers = containers.withIndex() suggestMatching( @@ -86,7 +87,7 @@ object TransferCommand : LambdaCommand( "\"${index + 1}. ${container.name}\"" }, { (_, container) -> - container.description(selection) + container.descriptionAndStock(selection) } ) } ?: builder.buildFuture() @@ -104,7 +105,8 @@ object TransferCommand : LambdaCommand( it.name == to().value().split(".").last().trim() } ?: return@executeWithResult failure("To container not found") - fromContainer.transferByTask(selection, toContainer).execute(RootTask) + transfer(selection, fromContainer, toContainer) + .execute(RootTask) } return@executeWithResult success() } diff --git a/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt b/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt index 399677cdb..3e839a7af 100644 --- a/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt +++ b/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt @@ -22,6 +22,7 @@ import com.lambda.interaction.inventory.ContainerSelection import com.lambda.interaction.inventory.ContainerSelectionBuilder.Companion.selectContainer import com.lambda.interaction.inventory.StackSelection import com.lambda.interaction.inventory.container.Container +import com.lambda.interaction.inventory.container.ContainerRank import com.lambda.util.Describable import com.lambda.util.NamedEnum import net.minecraft.item.Item @@ -32,7 +33,7 @@ interface InventoryConfig { val accessPriority: Priority val storePriority: Priority - val allowedContainers: Collection + val allowedContainers: Collection val enderChestSearchRadius: Int diff --git a/src/main/kotlin/com/lambda/config/blocks/InventorySettings.kt b/src/main/kotlin/com/lambda/config/blocks/InventorySettings.kt index 34b5e2751..f5297f702 100644 --- a/src/main/kotlin/com/lambda/config/blocks/InventorySettings.kt +++ b/src/main/kotlin/com/lambda/config/blocks/InventorySettings.kt @@ -21,7 +21,7 @@ import com.lambda.config.Config import com.lambda.config.ConfigBlock import com.lambda.config.Group import com.lambda.event.events.TickEvent.Companion.ALL_STAGES -import com.lambda.interaction.inventory.container.Container +import com.lambda.interaction.inventory.container.ContainerRank import com.lambda.util.item.ItemUtils class InventorySettings(override val c: Config) : InventoryConfig, ConfigBlock { @@ -33,7 +33,7 @@ class InventorySettings(override val c: Config) : InventoryConfig, ConfigBlock { override val disposables by c.setting("Disposables", ItemUtils.defaultDisposables, description = "Items that will be ignored when checking for a free slot") override val accessPriority by c.setting("Access Priority", InventoryConfig.Priority.WithMinItems, "What container to prefer when retrieving the item from") override val storePriority by c.setting("Store Priority", InventoryConfig.Priority.WithMinItems, "What container to prefer when storing the item to") - override val allowedContainers by c.setting("Allowed Containers", Container.Rank.entries, description = "What containers are accessible") + override val allowedContainers by c.setting("Allowed Containers", ContainerRank.entries, description = "What containers are accessible") @Group(ENDER_CHEST_GROUP) override val enderChestSearchRadius by c.setting("Initial Search Radius", 10, 0..20, unit = " blocks", description = "Initial search radius to scan for an already placed ender chest. If one isn't found, one will be placed. If an ender chest isn't found in the inventory, it scans the rest of the loaded area to find an already placed one") } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt index 3be7609de..913060307 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt @@ -35,6 +35,7 @@ import com.lambda.interaction.handler.handlers.BaritoneHandler import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.task.Task +import com.lambda.task.tasks.transferTo import com.lambda.task.tasks.wrappers.softFail import net.minecraft.block.BlockState import net.minecraft.item.Item @@ -120,9 +121,9 @@ sealed class BreakResult : BuildResult() { context(task: Task<*>, _: AutomatedSafeContext) override fun resolve() { selectStack { inverted { isItem(badItem) } } - .transfer(HotbarContainer) - ?.softFail() - ?.execute(task) + .transferTo(HotbarContainer) + .softFail() + .execute(task) } override fun compareResult(other: ComparableResult) = diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/GenericResult.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/GenericResult.kt index fe897f082..ed5b54356 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/GenericResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/GenericResult.kt @@ -30,6 +30,7 @@ import com.lambda.interaction.handler.handlers.BaritoneHandler import com.lambda.interaction.inventory.StackSelection import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.task.Task +import com.lambda.task.tasks.transferTo import com.lambda.task.tasks.wrappers.softFail import net.minecraft.client.data.TextureMap.side import net.minecraft.item.ItemStack @@ -101,9 +102,9 @@ sealed class GenericResult : BuildResult() { context(task: Task<*>, _: AutomatedSafeContext) override fun resolve() { neededSelection - .transfer(HotbarContainer) - ?.softFail() - ?.execute(task) + .transferTo(HotbarContainer) + .softFail() + .execute(task) } override fun RenderBuilder.render() { diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/BreakSim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/BreakSim.kt index ce5e665b6..74741cd17 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/BreakSim.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/BreakSim.kt @@ -33,7 +33,7 @@ import com.lambda.interaction.inventory.ContainerSelectionBuilder.Companion.sele import com.lambda.interaction.inventory.StackAndSlot import com.lambda.interaction.inventory.StackSelection import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack -import com.lambda.interaction.inventory.container.Container +import com.lambda.interaction.inventory.container.ContainerRank import com.lambda.interaction.manager.managers.hotbar.HotbarManager import com.lambda.interaction.manager.managers.rotating.RotationManager import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest @@ -138,10 +138,9 @@ class BreakSim internal constructor(simInfo: SimInfo) } private fun AutomatedSafeContext.getSwapStack(): Pair? { - // Stack size 0 to account for attacking with an empty hand. Empty slots have stack size 0 val stackSelection = selectStack { if (breakConfig.efficientOnly) isEfficientForBreaking(state) - if(breakConfig.suitableToolsOnly) isSuitableForBreaking(state) + if (breakConfig.suitableToolsOnly) isSuitableForBreaking(state) if (breakConfig.forceSilkTouch) hasEnchantment(Enchantments.SILK_TOUCH) if (breakConfig.forceFortunePickaxe) hasEnchantment(Enchantments.FORTUNE) sortedWith { @@ -156,12 +155,13 @@ class BreakSim internal constructor(simInfo: SimInfo) } val containerSelection = selectContainer { - ofAnyType(Container.Rank.Hotbar) + ofAnyType(ContainerRank.Hotbar) } val hotbarCandidates = stackSelection .findContainers(containerSelection) .flatMap { stackSelection.filter(it.stacks) } + .toList() if (hotbarCandidates.isEmpty()) { result(GenericResult.WrongItemSelection(pos, stackSelection, player.mainHandStack)) return null diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/InteractSim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/InteractSim.kt index 6374e6947..b1b36ef4d 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/InteractSim.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/InteractSim.kt @@ -29,9 +29,9 @@ import com.lambda.interaction.construction.simulation.sim import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.handler.handlers.ContainerHandler.findContainers import com.lambda.interaction.inventory.ContainerSelectionBuilder.Companion.selectContainer -import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.select import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack -import com.lambda.interaction.inventory.container.Container +import com.lambda.interaction.inventory.container.ContainerRank +import com.lambda.interaction.inventory.select import com.lambda.interaction.manager.managers.rotating.Rotation import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotation import com.lambda.interaction.manager.managers.rotating.RotationManager @@ -206,7 +206,7 @@ class InteractSim internal constructor(simInfo: InteractSimInfo) } val stackSelection = item?.select() ?: selectStack { sortedWith { compareByDescending { it.stack.inventoryIndex == player.inventory.selectedSlot } } } - val containerSelection = selectContainer { ofAnyType(Container.Rank.Hotbar) } + val containerSelection = selectContainer { ofAnyType(ContainerRank.Hotbar) } val container = stackSelection.findContainers(containerSelection).firstOrNull() ?: run { result(GenericResult.WrongItemSelection(pos, stackSelection, player.mainHandStack)) return null diff --git a/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt index 770ea2850..39fb480a7 100644 --- a/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt @@ -26,10 +26,10 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.inventory.ContainerMarker import com.lambda.interaction.inventory.ContainerSelection import com.lambda.interaction.inventory.StackSelection -import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.select import com.lambda.interaction.inventory.container.Container import com.lambda.interaction.inventory.container.containers.external.ChestContainer import com.lambda.interaction.inventory.container.containers.external.EnderChestContainer +import com.lambda.interaction.inventory.select import com.lambda.util.BlockUtils.blockEntity import com.lambda.util.ReflectionUtils.getInstances import com.lambda.util.extension.containerStacks @@ -42,8 +42,8 @@ import net.minecraft.screen.ScreenHandlerType // ToDo: Make this a Configurable to save container caches. Should use a cached region based storage system. @Suppress("unused") object ContainerHandler : Loadable { - private val containers: List - get() = compileContainers + runtimeContainers + private val containers: Sequence + get() = compileContainers.asSequence() + runtimeContainers private val compileContainers = getInstances() private val runtimeContainers = mutableSetOf() @@ -52,14 +52,12 @@ object ContainerHandler : Loadable { val filteredContainers get() = containers - .flatMap { setOf(it) + it.shulkerContainers } .filter { automated.inventoryConfig.containerSelection.matches(it) } .sorted() val allContainers get() = containers - .flatMap { setOf(it) + it.shulkerContainers } .sorted() var lastInteractedBlockEntity: BlockEntity? = null @@ -101,10 +99,10 @@ object ContainerHandler : Loadable { @ContainerMarker context(automatedSafeContext: AutomatedSafeContext) - fun StackSelection.transfer(destination: Container) = + fun StackSelection.move(destination: Container) = with(automatedSafeContext) { findContainer(inventoryConfig.containerSelection) - ?.transfer(this@transfer, destination) + ?.move(this@move, destination) ?: false } diff --git a/src/main/kotlin/com/lambda/interaction/inventory/ContainerSelection.kt b/src/main/kotlin/com/lambda/interaction/inventory/ContainerSelection.kt index 332783fd3..ef32c4633 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/ContainerSelection.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/ContainerSelection.kt @@ -18,6 +18,7 @@ package com.lambda.interaction.inventory import com.lambda.interaction.inventory.container.Container +import com.lambda.interaction.inventory.container.ContainerRank /** * ContainerSelection is a class that holds a predicate for matching MaterialContainers. @@ -52,11 +53,11 @@ class ContainerSelectionBuilder private constructor() { private var comparator: Comparator = compareBy { it.rank } private var invertNewSelectors = false - fun ofAnyType(vararg types: Container.Rank) { + fun ofAnyType(vararg types: ContainerRank) { appendSelector { container -> types.contains(container.rank) } } - fun noneOfType(vararg types: Container.Rank) { + fun noneOfType(vararg types: ContainerRank) { appendSelector { container -> !types.contains(container.rank) } } diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/Container.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/Container.kt index c09a5b6db..58563fbcf 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/Container.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/Container.kt @@ -23,17 +23,13 @@ import com.lambda.context.SafeContext import com.lambda.event.EventFlow.post import com.lambda.event.events.ContainerEvent import com.lambda.interaction.inventory.StackSelection -import com.lambda.interaction.inventory.container.containers.external.ShulkerBoxContainer import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.task.Task.Ta5kBuilder -import com.lambda.task.tasks.ContainerTransferTask import com.lambda.util.Nameable import com.lambda.util.item.ItemStackUtils.count import com.lambda.util.item.ItemStackUtils.empty -import com.lambda.util.item.ItemStackUtils.shulkerBoxStacks import com.lambda.util.item.ItemStackUtils.spaceLeft -import com.lambda.util.item.ItemUtils import com.lambda.util.item.ItemUtils.toItemCount import com.lambda.util.text.TextBuilder import com.lambda.util.text.TextDsl @@ -48,8 +44,11 @@ import net.minecraft.text.Text // ToDo: Make jsonable to persistently store them abstract class Container( - val rank: Rank + val rank: ContainerRank ) : Nameable, Comparable { + override val name: String + get() = buildText { text(description) }.string + abstract val slots: List abstract var stacks: List open val swapMethodPriority = 0 @@ -70,6 +69,13 @@ abstract class Container( it.stack.isStackable } + context(_: SafeContext) + fun descriptionAndStock(selection: StackSelection) = + buildText { + text(description) + stock(selection) + } + @TextDsl context(_: SafeContext) fun TextBuilder.stock(selection: StackSelection) { @@ -87,38 +93,6 @@ abstract class Container( highlighted("${selection.optimalStack?.name?.string}") } - context(_: SafeContext) - fun description(selection: StackSelection) = - buildText { - text(description) - stock(selection) - } - - override val name: String - get() = buildText { text(description) }.string - - //ToDo: Bundles - val shulkerContainers - get() = - slots.takeIf { - it.isEmpty() - }?.filter { - it.stack.item in ItemUtils.shulkerBoxes - }?.map { slot -> - ShulkerBoxContainer( - slot.stack.shulkerBoxStacks, - this@Container, - slot - ) - }?.toSet() - ?: stacks.map { stack -> - ShulkerBoxContainer( - stack.shulkerBoxStacks, - this@Container, - null - ) - }.toSet() - open val isAccessed get() = true fun update(slots: List) { @@ -131,29 +105,25 @@ abstract class Container( PlayerOpenContainerTask(description, ::isAccessed) context(automatedSafeContext: AutomatedSafeContext) - fun transfer(selection: StackSelection, toContainer: Container): Boolean = + internal fun move(selection: StackSelection, toContainer: Container): Boolean = with(automatedSafeContext) { val (fromSlot, toSlot) = getTransferSlots(selection, toContainer) if (fromSlot == null || toSlot == null) return false - return transfer(fromSlot, toSlot, toContainer) + swap(fromSlot, toSlot, toContainer) } - context(_: Automated) - fun getTransferSlots(selection: StackSelection, destination: Container): Pair = - Pair(getSlot(selection), destination.getReplaceSlot()) - context(automatedSafeContext: AutomatedSafeContext) - fun transfer(fromSlot: Slot, toSlot: Slot, toContainer: Container): Boolean { + internal fun swap(fromSlot: Slot, toSlot: Slot, toContainer: Container): Boolean { val transferEvent = ContainerEvent.Transfer(fromSlot, toSlot, this@Container, toContainer) if (transferEvent.post().isCanceled()) return false return automatedSafeContext.inventoryRequest { - if (swapMethodPriority > toContainer.swapMethodPriority) transfer(fromSlot, toSlot) - else with(toContainer) { transfer(toSlot, fromSlot) } + if (swapMethodPriority > toContainer.swapMethodPriority) swap(fromSlot, toSlot) + else with(toContainer) { swap(toSlot, fromSlot) } }.submit().done } context(safeContext: SafeContext) - open fun InvRequestBuilder.transfer(fromHere: Slot, toSlot: Slot) { + protected open fun InvRequestBuilder.swap(fromHere: Slot, toSlot: Slot) { if (fromHere.stack.isEmpty) moveSlot(toSlot.id, fromHere.id) else { moveSlot(fromHere.id, toSlot.id) @@ -161,37 +131,20 @@ abstract class Container( } } - context(automated: Automated) - fun transferByTask(stackSelection: StackSelection, toContainer: Container, failIfNoStack: Boolean = true) = - ContainerTransferTask(this, toContainer, stackSelection, failIfNoStack, automated) - open fun stackCount(selection: StackSelection) = selection.filter(stacks).count open fun spaceAvailable(selection: StackSelection) = selection.filter(stacks).spaceLeft + stacks.empty + context(_: Automated) + fun getTransferSlots(selection: StackSelection, destination: Container): Pair = + Pair(getSlot(selection), destination.getReplaceSlot()) + context(_: Automated) open fun getReplaceSlot() = slots.sortedWith(replaceSorter).firstOrNull() open fun getSlot(selection: StackSelection) = selection.bestMatch(slots) - enum class Rank { - MainHand, - OffHand, - Hotbar, - Inventory, - HotbarAndInventory, - Armor, - Player, - Creative, - ShulkerBox, - EnderChest, - PlacedShulkerBox, - PlacedEnderChest, - Chest, - Stash - } - override fun compareTo(other: Container) = compareBy { it.rank }.compare(this, other) } diff --git a/src/main/kotlin/com/lambda/task/tasks/SimpleActionTask.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/ContainerRank.kt similarity index 60% rename from src/main/kotlin/com/lambda/task/tasks/SimpleActionTask.kt rename to src/main/kotlin/com/lambda/interaction/inventory/container/ContainerRank.kt index 5493c61cb..2d50d0c47 100644 --- a/src/main/kotlin/com/lambda/task/tasks/SimpleActionTask.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/ContainerRank.kt @@ -15,21 +15,21 @@ * along with this program. If not, see . */ -package com.lambda.task.tasks +package com.lambda.interaction.inventory.container -import com.lambda.context.SafeContext -import com.lambda.task.Task -import com.lambda.task.Task.Ta5kBuilder - -@Ta5kBuilder -fun simpleAction(name: String, action: SafeContext.() -> Unit) = SimpleActionTask(name, action) - -class SimpleActionTask @Ta5kBuilder internal constructor( - override val name: String, - val action: SafeContext.() -> Unit -) : Task() { - override fun SafeContext.onStart() { - action() - success() - } +enum class ContainerRank { + MainHand, + OffHand, + Hotbar, + Inventory, + HotbarAndInventory, + Armor, + Player, + Creative, + ShulkerBox, + EnderChest, + PlacedShulkerBox, + PlacedEnderChest, + Chest, + Stash } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/OpenedContainerContext.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/OpenedContainerContext.kt index 5b615bb86..7b25339e1 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/OpenedContainerContext.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/OpenedContainerContext.kt @@ -20,7 +20,7 @@ package com.lambda.interaction.inventory.container import com.lambda.context.Automated import com.lambda.task.Task import com.lambda.task.Task.Ta5kBuilder -import com.lambda.task.tasks.simpleAction +import com.lambda.task.tasks.wrappers.actionTask interface OpenedContainerContext { @Ta5kBuilder @@ -33,8 +33,8 @@ open class BasicOpenedContainerContext( ) : OpenedContainerContext { context(_: Automated) final override fun close() = - simpleAction("Closing container") { - if (!isAccessed()) return@simpleAction + actionTask { + if (!isAccessed()) return@actionTask player.closeScreen() } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/ArmorContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/ArmorContainer.kt index b0bafe110..f25f7629e 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/ArmorContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/ArmorContainer.kt @@ -19,6 +19,7 @@ package com.lambda.interaction.inventory.container.containers import com.lambda.Lambda.mc import com.lambda.interaction.inventory.container.Container +import com.lambda.interaction.inventory.container.ContainerRank import com.lambda.util.player.SlotUtils.armorSlots import com.lambda.util.player.SlotUtils.armorStacks import com.lambda.util.text.buildText @@ -26,7 +27,7 @@ import com.lambda.util.text.literal import net.minecraft.item.ItemStack import net.minecraft.screen.slot.Slot -object ArmorContainer : Container(Rank.Armor) { +object ArmorContainer : Container(ContainerRank.Armor) { override val slots: List get() = mc.player?.armorSlots ?: emptyList() override var stacks: List diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/CreativeContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/CreativeContainer.kt index fd0e0135f..e96f0c892 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/CreativeContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/CreativeContainer.kt @@ -21,6 +21,7 @@ import com.lambda.Lambda.mc import com.lambda.context.SafeContext import com.lambda.interaction.inventory.StackSelection import com.lambda.interaction.inventory.container.Container +import com.lambda.interaction.inventory.container.ContainerRank import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder import com.lambda.util.text.buildText import com.lambda.util.text.literal @@ -31,7 +32,7 @@ import net.minecraft.item.ItemStack import net.minecraft.screen.PlayerScreenHandler import net.minecraft.screen.slot.Slot -data object CreativeContainer : Container(Rank.Creative) { +data object CreativeContainer : Container(ContainerRank.Creative) { override val slots = emptyList() override var stacks = emptyList() @@ -40,7 +41,7 @@ data object CreativeContainer : Container(Rank.Creative) { override val description = buildText { literal("Creative") } context(safeContext: SafeContext) - override fun InvRequestBuilder.transfer(fromHere: Slot, toSlot: Slot) { + override fun InvRequestBuilder.swap(fromHere: Slot, toSlot: Slot) { clickCreativeStack(fromHere.stack, toSlot.id) safeContext.player.currentScreenHandler.slots.getOrNull(toSlot.id)?.stack = fromHere.stack } diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/HotbarAndInventoryContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/HotbarAndInventoryContainer.kt index b5998dbec..44a6de2b4 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/HotbarAndInventoryContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/HotbarAndInventoryContainer.kt @@ -19,6 +19,7 @@ package com.lambda.interaction.inventory.container.containers import com.lambda.Lambda.mc import com.lambda.interaction.inventory.container.Container +import com.lambda.interaction.inventory.container.ContainerRank import com.lambda.util.player.SlotUtils.hotbarAndInventorySlots import com.lambda.util.player.SlotUtils.hotbarAndInventoryStacks import com.lambda.util.text.buildText @@ -26,7 +27,7 @@ import com.lambda.util.text.literal import net.minecraft.item.ItemStack import net.minecraft.screen.slot.Slot -object HotbarAndInventoryContainer : Container(Rank.HotbarAndInventory) { +object HotbarAndInventoryContainer : Container(ContainerRank.HotbarAndInventory) { override val slots: List get() = mc.player?.hotbarAndInventorySlots ?: emptyList() override var stacks: List diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/HotbarContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/HotbarContainer.kt index 9949c3d88..9533e52cf 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/HotbarContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/HotbarContainer.kt @@ -20,6 +20,7 @@ package com.lambda.interaction.inventory.container.containers import com.lambda.Lambda.mc import com.lambda.context.SafeContext import com.lambda.interaction.inventory.container.Container +import com.lambda.interaction.inventory.container.ContainerRank import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder import com.lambda.util.player.SlotUtils.hotbarSlots import com.lambda.util.player.SlotUtils.hotbarStacks @@ -27,7 +28,7 @@ import com.lambda.util.text.buildText import com.lambda.util.text.literal import net.minecraft.screen.slot.Slot -object HotbarContainer : Container(Rank.Hotbar) { +object HotbarContainer : Container(ContainerRank.Hotbar) { override val slots: List get() = mc.player?.hotbarSlots ?: emptyList() override var stacks @@ -39,7 +40,7 @@ object HotbarContainer : Container(Rank.Hotbar) { override val description = buildText { literal("Hotbar") } context(safeContext: SafeContext) - override fun InvRequestBuilder.transfer(fromHere: Slot, toSlot: Slot) { + override fun InvRequestBuilder.swap(fromHere: Slot, toSlot: Slot) { swapWithHotbar(toSlot.id, safeContext.player.hotbarSlots.indexOf(fromHere)) } } diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/InventoryContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/InventoryContainer.kt index c54b101e5..d9cec92a0 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/InventoryContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/InventoryContainer.kt @@ -19,6 +19,7 @@ package com.lambda.interaction.inventory.container.containers import com.lambda.Lambda.mc import com.lambda.interaction.inventory.container.Container +import com.lambda.interaction.inventory.container.ContainerRank import com.lambda.util.player.SlotUtils.inventorySlots import com.lambda.util.player.SlotUtils.inventoryStacks import com.lambda.util.text.buildText @@ -26,7 +27,7 @@ import com.lambda.util.text.literal import net.minecraft.item.ItemStack import net.minecraft.screen.slot.Slot -object InventoryContainer : Container(Rank.Inventory) { +object InventoryContainer : Container(ContainerRank.Inventory) { override val slots: List get() = mc.player?.inventorySlots ?: emptyList() override var stacks: List diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/MainHandContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/MainHandContainer.kt index c87db7077..39bcc95b7 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/MainHandContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/MainHandContainer.kt @@ -20,6 +20,7 @@ package com.lambda.interaction.inventory.container.containers import com.lambda.Lambda.mc import com.lambda.context.SafeContext import com.lambda.interaction.inventory.container.Container +import com.lambda.interaction.inventory.container.ContainerRank import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder import com.lambda.util.player.SlotUtils.mainHandSlots import com.lambda.util.text.buildText @@ -27,7 +28,7 @@ import com.lambda.util.text.literal import net.minecraft.item.ItemStack import net.minecraft.screen.slot.Slot -object MainHandContainer : Container(Rank.MainHand) { +object MainHandContainer : Container(ContainerRank.MainHand) { override val slots: List get() = mc.player?.mainHandSlots ?: emptyList() override var stacks: List @@ -39,7 +40,7 @@ object MainHandContainer : Container(Rank.MainHand) { override val description = buildText { literal("MainHand") } context(safeContext: SafeContext) - override fun InvRequestBuilder.transfer(fromHere: Slot, toSlot: Slot) { + override fun InvRequestBuilder.swap(fromHere: Slot, toSlot: Slot) { swapWithHotbar(toSlot.id, safeContext.player.inventory.selectedSlot) } } diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/OffHandContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/OffHandContainer.kt index cbe16ada8..88e65599b 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/OffHandContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/OffHandContainer.kt @@ -20,6 +20,7 @@ package com.lambda.interaction.inventory.container.containers import com.lambda.Lambda.mc import com.lambda.context.SafeContext import com.lambda.interaction.inventory.container.Container +import com.lambda.interaction.inventory.container.ContainerRank import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder import com.lambda.util.player.SlotUtils.offHandSlots import com.lambda.util.text.buildText @@ -27,7 +28,7 @@ import com.lambda.util.text.literal import net.minecraft.item.ItemStack import net.minecraft.screen.slot.Slot -object OffHandContainer : Container(Rank.OffHand) { +object OffHandContainer : Container(ContainerRank.OffHand) { override val slots: List get() = mc.player?.offHandSlots ?: emptyList() override var stacks: List @@ -39,7 +40,7 @@ object OffHandContainer : Container(Rank.OffHand) { override val description = buildText { literal("OffHand") } context(_: SafeContext) - override fun InvRequestBuilder.transfer(fromHere: Slot, toSlot: Slot) { + override fun InvRequestBuilder.swap(fromHere: Slot, toSlot: Slot) { swapWithHotbar(toSlot.id, 40) } } diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/PlayerContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/PlayerContainer.kt index f33a461a5..736d1e787 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/PlayerContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/PlayerContainer.kt @@ -19,6 +19,7 @@ package com.lambda.interaction.inventory.container.containers import com.lambda.Lambda.mc import com.lambda.interaction.inventory.container.Container +import com.lambda.interaction.inventory.container.ContainerRank import com.lambda.util.player.SlotUtils.allSlots import com.lambda.util.player.SlotUtils.allStacks import com.lambda.util.text.buildText @@ -27,7 +28,7 @@ import net.minecraft.item.ItemStack import net.minecraft.screen.slot.Slot @Suppress("unused") -object PlayerContainer : Container(Rank.Player) { +object PlayerContainer : Container(ContainerRank.Player) { override val slots: List get() = mc.player?.allSlots ?: emptyList() override var stacks: List diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ChestContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ChestContainer.kt index 1b454ba7d..06d553d15 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ChestContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ChestContainer.kt @@ -23,6 +23,7 @@ import com.lambda.context.SafeContext import com.lambda.interaction.handler.handlers.ContainerHandler.lastInteractedBlockEntity import com.lambda.interaction.inventory.container.BasicOpenedContainerContext import com.lambda.interaction.inventory.container.Container +import com.lambda.interaction.inventory.container.ContainerRank import com.lambda.interaction.inventory.container.ExternalContainer import com.lambda.interaction.inventory.container.OpenContainerTask import com.lambda.interaction.inventory.container.PlacedContainer @@ -36,7 +37,6 @@ import com.lambda.util.text.literal import net.minecraft.block.ChestBlock import net.minecraft.block.entity.ChestBlockEntity import net.minecraft.item.ItemStack -import net.minecraft.screen.ScreenHandler import net.minecraft.screen.ScreenHandlerType import net.minecraft.screen.slot.Slot import net.minecraft.util.math.BlockPos @@ -45,7 +45,7 @@ data class ChestContainer( val blockPos: BlockPos, override var stacks: List, override val stash: StashContainer? = null -) : Container(Rank.Chest), ExternalContainer, PlacedContainer { +) : Container(ContainerRank.Chest), ExternalContainer, PlacedContainer { override val slots get(): List = lastInteractedBlockEntity?.let { blockEntity -> @@ -82,16 +82,14 @@ data class ChestContainer( override fun SafeContext.onStart() { taskOrNull { if (isAccessed) null - else openContainer(blockPos).onSuccess { sh -> - success(OpenedChestContext(sh)) + else openContainer(blockPos).onSuccess { + success(OpenedChestContext()) } }.execute(this@AccessChestTask) } } - inner class OpenedChestContext( - val screenHandler: ScreenHandler, - ) : BasicOpenedContainerContext(::isAccessed) { + inner class OpenedChestContext : BasicOpenedContainerContext(::isAccessed) { val blockPos = this@ChestContainer.blockPos } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/EnderChestContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/EnderChestContainer.kt index d6e71869b..e91df1cf8 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/EnderChestContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/EnderChestContainer.kt @@ -22,6 +22,7 @@ import com.lambda.context.Automated import com.lambda.context.SafeContext import com.lambda.interaction.handler.handlers.ContainerHandler.lastInteractedBlockEntity import com.lambda.interaction.inventory.container.Container +import com.lambda.interaction.inventory.container.ContainerRank import com.lambda.interaction.inventory.container.ExternalContainer import com.lambda.interaction.inventory.container.OpenContainerTask import com.lambda.interaction.inventory.container.OpenedContainerContext @@ -33,9 +34,9 @@ import com.lambda.task.tasks.findBlock import com.lambda.task.tasks.openContainer import com.lambda.task.tasks.placeContainer import com.lambda.task.tasks.wrappers.actionTask -import com.lambda.task.tasks.wrappers.successTask import com.lambda.task.tasks.wrappers.taskOrNull import com.lambda.task.tasks.wrappers.then +import com.lambda.task.tasks.wrappers.thenOrNull import com.lambda.task.tasks.wrappers.withBranch import com.lambda.util.extension.containerSlots import com.lambda.util.text.buildText @@ -44,11 +45,10 @@ import net.minecraft.block.Blocks import net.minecraft.block.entity.EnderChestBlockEntity import net.minecraft.item.ItemStack import net.minecraft.item.Items -import net.minecraft.screen.ScreenHandler import net.minecraft.screen.ScreenHandlerType import net.minecraft.util.math.BlockPos -object EnderChestContainer : Container(Rank.EnderChest), ExternalContainer { +object EnderChestContainer : Container(ContainerRank.EnderChest), ExternalContainer { override val slots get() = if (isAccessed) mc.player?.currentScreenHandler?.containerSlots ?: emptyList() @@ -76,16 +76,16 @@ object EnderChestContainer : Container(Rank.EnderChest), ExternalContainer { if (isAccessed) null else findBlock(Blocks.ENDER_CHEST, inventoryConfig.enderChestSearchRadius).withBranch( onSuccess = { pos -> - openContainer(pos).onSuccess { sh -> - success(OpenedEnderChestContext(sh, pos, false)) + openContainer(pos).onSuccess { + success(OpenedEnderChestContext(pos, false)) } }, onFailure = { acquireStack(Items.ENDER_CHEST.select(1)) .then { placeContainer(it) } .then { pos -> - openContainer(pos).onSuccess { sh -> - success(OpenedEnderChestContext(sh, pos, true)) + openContainer(pos).onSuccess { + success(OpenedEnderChestContext(pos, true)) } } } @@ -95,7 +95,6 @@ object EnderChestContainer : Container(Rank.EnderChest), ExternalContainer { } class OpenedEnderChestContext( - val screenHandler: ScreenHandler, val blockPos: BlockPos, val placed: Boolean ) : OpenedContainerContext { @@ -104,9 +103,9 @@ object EnderChestContainer : Container(Rank.EnderChest), ExternalContainer { taskOrNull { if (isAccessed) actionTask { player.closeScreen() } else null - }.then { + }.thenOrNull { if (placed) breakAndCollect(blockPos) - else successTask() + else null } } } diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/PlacedShulkerBoxContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/PlacedShulkerBoxContainer.kt index cb9d53de0..224bb09bc 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/PlacedShulkerBoxContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/PlacedShulkerBoxContainer.kt @@ -23,6 +23,7 @@ import com.lambda.context.SafeContext import com.lambda.interaction.handler.handlers.ContainerHandler.lastInteractedBlockEntity import com.lambda.interaction.inventory.container.BasicOpenedContainerContext import com.lambda.interaction.inventory.container.Container +import com.lambda.interaction.inventory.container.ContainerRank import com.lambda.interaction.inventory.container.OpenContainerTask import com.lambda.interaction.inventory.container.PlacedContainer import com.lambda.task.Task.Ta5kBuilder @@ -43,7 +44,7 @@ class PlacedShulkerBoxContainer( val block: Block, override val stash: StashContainer?, override var stacks: List -) : Container(Rank.PlacedShulkerBox), PlacedContainer { +) : Container(ContainerRank.PlacedShulkerBox), PlacedContainer { override val slots: List get() = if (isAccessed) mc.player?.currentScreenHandler?.containerSlots ?: emptyList() diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ShulkerBoxContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ShulkerBoxContainer.kt index 71c79b4f8..3d666905f 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ShulkerBoxContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ShulkerBoxContainer.kt @@ -23,12 +23,13 @@ import com.lambda.context.SafeContext import com.lambda.interaction.handler.handlers.ContainerHandler.lastInteractedBlockEntity import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack import com.lambda.interaction.inventory.container.Container +import com.lambda.interaction.inventory.container.ContainerRank import com.lambda.interaction.inventory.container.ExternalContainer import com.lambda.interaction.inventory.container.NestedContainer import com.lambda.interaction.inventory.container.OpenContainerTask import com.lambda.interaction.inventory.container.OpenedContainerContext +import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer import com.lambda.interaction.inventory.container.containers.HotbarContainer -import com.lambda.interaction.inventory.container.containers.PlayerContainer import com.lambda.task.Task.Ta5kBuilder import com.lambda.task.tasks.breakAndCollect import com.lambda.task.tasks.openContainer @@ -43,7 +44,6 @@ import com.lambda.util.text.highlighted import com.lambda.util.text.literal import net.minecraft.block.entity.ShulkerBoxBlockEntity import net.minecraft.item.ItemStack -import net.minecraft.screen.ScreenHandler import net.minecraft.screen.ScreenHandlerType import net.minecraft.screen.slot.Slot import net.minecraft.util.math.BlockPos @@ -52,7 +52,7 @@ data class ShulkerBoxContainer( override var stacks: List, val containedIn: Container, override val slotCache: Slot, -) : Container(Rank.ShulkerBox), ExternalContainer, NestedContainer { +) : Container(ContainerRank.ShulkerBox), ExternalContainer, NestedContainer { override val slots get(): List = if (isAccessed) mc.player?.currentScreenHandler?.containerSlots ?: emptyList() @@ -84,23 +84,22 @@ data class ShulkerBoxContainer( automated: Automated ) : OpenContainerTask(description), Automated by automated { override fun SafeContext.onStart() { - transfer( - selectStack { isSlot(slotCache) }, - containedIn, - HotbarContainer - ).then { slot -> placeContainer(slot) } - .then { pos -> - openContainer(pos).onSuccess { sh -> - success(OpenedShulkerBoxContext(pos, sh, containedIn)) - } + transfer( + selectStack { isSlot(slotCache) }, + containedIn, + HotbarContainer + ).then { slot -> placeContainer(slot) } + .then { pos -> + openContainer(pos).onSuccess { + success(OpenedShulkerBoxContext(pos, containedIn)) } - .execute(this@OpenShulkerBoxTask) + } + .execute(this@OpenShulkerBoxTask) } } inner class OpenedShulkerBoxContext( val blockPos: BlockPos, - val screenHandler: ScreenHandler, val fromContainer: Container ) : OpenedContainerContext { context(automated: Automated) @@ -112,7 +111,7 @@ data class ShulkerBoxContainer( .then { slot -> transfer( selectStack { isSlot(slot) }, - PlayerContainer, + HotbarAndInventoryContainer, fromContainer ) } diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/StashContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/StashContainer.kt index b425a7e66..8c7063980 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/StashContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/StashContainer.kt @@ -19,6 +19,7 @@ package com.lambda.interaction.inventory.container.containers.external import com.lambda.interaction.inventory.StackSelection import com.lambda.interaction.inventory.container.Container +import com.lambda.interaction.inventory.container.ContainerRank import com.lambda.util.math.roundedBlockPos import com.lambda.util.text.buildText import com.lambda.util.text.highlighted @@ -30,7 +31,7 @@ import net.minecraft.util.math.Box data class StashContainer( val chests: Set, val pos: Box, -) : Container(Rank.Stash) { +) : Container(ContainerRank.Stash) { override val slots: List get() = chests.flatMap { it.slots } override var stacks: List @@ -43,7 +44,5 @@ data class StashContainer( } override fun stackCount(selection: StackSelection): Int = - chests.sumOf { - it.stackCount(selection) - } + chests.sumOf { it.stackCount(selection) } } diff --git a/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt b/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt index 8bcc7f89e..ee8fe19c6 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt @@ -24,7 +24,7 @@ import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.handler.handlers.FriendHandler -import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.select +import com.lambda.interaction.inventory.select import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag diff --git a/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt b/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt index 04b36128a..f2dc2b411 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt @@ -27,7 +27,7 @@ import com.lambda.context.SafeContext import com.lambda.event.events.EntityEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.handler.handlers.ContainerHandler.transfer +import com.lambda.interaction.handler.handlers.ContainerHandler.move import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.interaction.inventory.container.containers.OffHandContainer @@ -492,7 +492,7 @@ object CrystalAura : Module( Hand.MAIN_HAND -> HotbarContainer Hand.OFF_HAND -> OffHandContainer } - if (!selection.transfer(swapTo)) return@runSafe + if (!selection.move(swapTo)) return@runSafe crystalSlot = HotbarContainer.stacks.indexOfFirst { selection.matches(it) } } if (!hotbarRequest(crystalSlot).submit().done) return@runSafe diff --git a/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt b/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt index ddc1d9205..3bb5ebe6f 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt @@ -19,7 +19,7 @@ package com.lambda.module.modules.debug import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.select +import com.lambda.interaction.inventory.select import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.task.RootTask.run diff --git a/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt b/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt index 6455236ac..679ed9a6d 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt @@ -29,11 +29,11 @@ import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.handler.handlers.GlideHandler -import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.select import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.interaction.inventory.container.containers.InventoryContainer +import com.lambda.interaction.inventory.select import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module diff --git a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/GrimControlElytraFly.kt b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/GrimControlElytraFly.kt index b84478fbe..e435fdf57 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/GrimControlElytraFly.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/GrimControlElytraFly.kt @@ -22,9 +22,9 @@ import com.lambda.event.events.PacketEvent import com.lambda.event.events.PlayerPacketEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.select import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.interaction.inventory.container.containers.InventoryContainer +import com.lambda.interaction.inventory.select import com.lambda.interaction.manager.managers.rotating.RotationManager import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.modules.movement.BetterFirework.startFirework diff --git a/src/main/kotlin/com/lambda/task/tasks/AcquirePlacedBlockTask.kt b/src/main/kotlin/com/lambda/task/tasks/AcquirePlacedBlockTask.kt index 30a0dbf73..d8c15f21a 100644 --- a/src/main/kotlin/com/lambda/task/tasks/AcquirePlacedBlockTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/AcquirePlacedBlockTask.kt @@ -22,7 +22,7 @@ import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.handler.handlers.ContainerHandler.findSlots -import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.select +import com.lambda.interaction.inventory.select import com.lambda.task.Task import com.lambda.task.Task.Ta5kBuilder import com.lambda.task.tasks.wrappers.softFail diff --git a/src/main/kotlin/com/lambda/task/tasks/AcquireStackTask.kt b/src/main/kotlin/com/lambda/task/tasks/AcquireStackTask.kt index 167f4c542..4810c7975 100644 --- a/src/main/kotlin/com/lambda/task/tasks/AcquireStackTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/AcquireStackTask.kt @@ -25,7 +25,6 @@ import com.lambda.interaction.inventory.StackSelection import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.task.Task import com.lambda.task.Task.Ta5kBuilder -import com.lambda.task.tasks.wrappers.thenAction import com.lambda.threading.runSafeAutomated import net.minecraft.screen.slot.Slot @@ -49,8 +48,8 @@ class AcquireStackTask @Ta5kBuilder internal constructor( override fun SafeContext.onStart() { runSafeAutomated { selection.findContainer() - ?.transferByTask(selection, HotbarContainer) - ?.thenAction { slot -> success(slot) } + ?.let { transfer(selection, it, HotbarContainer) } + ?.onSuccess { slot -> success(slot) } ?.execute(this@AcquireStackTask) ?: failure(ContainerHandler.NoContainerFound(selection)) // ToDo: Create crafting path } diff --git a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt index 6207e8af2..a32c3974d 100644 --- a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt @@ -18,16 +18,18 @@ package com.lambda.task.tasks import com.lambda.context.Automated +import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.handler.handlers.ContainerHandler.findContainer import com.lambda.interaction.inventory.StackSelection import com.lambda.interaction.inventory.container.Container import com.lambda.interaction.inventory.container.ExternalContainer +import com.lambda.interaction.inventory.container.OpenedContainerContext import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer import com.lambda.task.Task import com.lambda.task.Task.Ta5kBuilder -import com.lambda.task.tasks.wrappers.thenAction +import com.lambda.task.tasks.wrappers.then import com.lambda.threading.runSafeAutomated import net.minecraft.screen.slot.Slot @@ -36,69 +38,94 @@ context(automated: Automated) fun transfer( stackSelection: StackSelection, fromContainer: Container, - toContainer: Container, - failIfNoStack: Boolean = true -) = ContainerTransferTask(fromContainer, toContainer, stackSelection, failIfNoStack, automated) + toContainer: Container +) = ContainerTransferTask(fromContainer, toContainer, stackSelection, automated) @Ta5kBuilder -context(automated: Automated) @JvmName("transferByStackSelection") +context(automated: Automated) fun transfer( stackSelection: StackSelection, - toContainer: Container, - failIfNoStack: Boolean = true -) = transfer(stackSelection, stackSelection.findContainer() ?: HotbarAndInventoryContainer, toContainer, failIfNoStack) + toContainer: Container +) = transfer(stackSelection, stackSelection.findContainer() ?: HotbarAndInventoryContainer, toContainer) @Ta5kBuilder -context(automated: Automated) @JvmName("transferExt") +context(automated: Automated) fun StackSelection.transferTo( - toContainer: Container, - failIfNoStack: Boolean = true -) = transfer(this, findContainer() ?: HotbarAndInventoryContainer, toContainer, failIfNoStack) + toContainer: Container +) = transfer(this, findContainer() ?: HotbarAndInventoryContainer, toContainer) class ContainerTransferTask @Ta5kBuilder internal constructor( private var fromContainer: Container, private val toContainer: Container, - private val stackSelection: StackSelection, - private val failIfNoStack: Boolean = false, + private val selection: StackSelection, automated: Automated ) : Task(), Automated by automated { - override val name = "Transferring $stackSelection from $fromContainer to $toContainer" + override val name = "Transferring $selection from $fromContainer to $toContainer" - init { + override fun SafeContext.onStart() { listen { runSafeAutomated { - if (fromContainer is ExternalContainer && toContainer is ExternalContainer) { - fromContainer.accessThen( - afterOpen = { - fromContainer.transferByTask(stackSelection, HotbarAndInventoryContainer, failIfNoStack) - } - ) { - HotbarAndInventoryContainer.transferByTask(stackSelection, toContainer, failIfNoStack) - .thenAction { slot -> success(slot) } - }?.execute(this@ContainerTransferTask) - return@listen - } + when { + fromContainer.isAccessed && toContainer.isAccessed -> + TransferTask() + .onSuccess { success(it) } + .execute(this@ContainerTransferTask) - fromContainer.accessThen { - toContainer.accessThen { - TransferTask().thenAction { slot -> - success(slot) - } - } - }?.execute(this@ContainerTransferTask) + fromContainer !is ExternalContainer -> + openTransferClose(toContainer, fromContainer, toContainer) + + toContainer !is ExternalContainer -> + openTransferClose(fromContainer, fromContainer, toContainer) + + else -> + fromContainer + .access() + .then { fromCtx -> + transfer(selection, fromContainer, HotbarAndInventoryContainer) + .then { fromCtx.close() } + } + .then { toContainer.access() } + .then { toCtx -> transferAndClose(toCtx, HotbarAndInventoryContainer, toContainer) } + .execute(this@ContainerTransferTask) + } } } } - private inner class TransferTask : Task() { + /** + * Opens [containerToOpen], transfers [selection] from [from] to [to], then closes the opened container. + * + * Used when exactly one of the two containers is external and needs to be accessed. + */ + private fun openTransferClose(containerToOpen: Container, from: Container, to: Container) { + containerToOpen + .access() + .then { ctx -> + transferAndClose(ctx, from, to) + } + .execute(this@ContainerTransferTask) + } + + /** + * Transfers [selection] from [from] to [to], then closes the container via [ctx]. + */ + private fun transferAndClose(ctx: OpenedContainerContext, from: Container, to: Container) = + transfer(selection, from, to) + .then { slot -> + ctx + .close() + .onSuccess { success(slot) } + } + + private inner class TransferTask @Ta5kBuilder constructor() : Task() { override val name = "Transferring" init { listen { runSafeAutomated { - val (fromSlot, toSlot) = fromContainer.getTransferSlots(stackSelection, toContainer) + val (fromSlot, toSlot) = fromContainer.getTransferSlots(selection, toContainer) if (fromSlot == null) { checkFail() return@listen @@ -107,7 +134,7 @@ class ContainerTransferTask @Ta5kBuilder internal constructor( failure("Unable to find a slot to transfer to.") return@listen } - val transferSuccessful = fromContainer.transfer(fromSlot, toSlot, toContainer) + val transferSuccessful = fromContainer.swap(fromSlot, toSlot, toContainer) if (transferSuccessful) success(toSlot) else checkFail() } @@ -115,7 +142,7 @@ class ContainerTransferTask @Ta5kBuilder internal constructor( } private fun checkFail() { - if (failIfNoStack) failure(NoMaterialAccessException(stackSelection)) + failure(NoMaterialAccessException(selection)) } private inner class NoMaterialAccessException(stackSelection: StackSelection) : IllegalStateException("Unable to access $stackSelection.") diff --git a/src/main/kotlin/com/lambda/task/tasks/EatTask.kt b/src/main/kotlin/com/lambda/task/tasks/EatTask.kt index c68f4ae24..33fa0172e 100644 --- a/src/main/kotlin/com/lambda/task/tasks/EatTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/EatTask.kt @@ -61,8 +61,8 @@ class EatTask @Ta5kBuilder internal constructor( return@listen } - val foodFinder = reason.selector() - val hotbarSlot = foodFinder.filter(HotbarContainer.slots).firstOrNull() + val selection = reason.selector() + val hotbarSlot = selection.bestMatch(HotbarContainer.slots) if (hotbarSlot != null) { val request = hotbarRequest(hotbarSlot.index) { @@ -70,9 +70,10 @@ class EatTask @Ta5kBuilder internal constructor( }.submit() if (!request.done) return@listen } else { - val inventorySlot = foodFinder.filter(InventoryContainer.slots).firstOrNull() - if (inventorySlot != null) runSafeAutomated { - InventoryContainer.transfer(foodFinder, HotbarContainer) + if (InventoryContainer.slots.any { selection.matches(it) }) { + runSafeAutomated { + transfer(selection, InventoryContainer, HotbarContainer) + } } if (holdingUse) { mc.options.useKey.isPressed = false diff --git a/src/main/kotlin/com/lambda/task/tasks/FindPlacedBlockTask.kt b/src/main/kotlin/com/lambda/task/tasks/FindPlacedBlockTask.kt index 7ec969db0..8b755ef92 100644 --- a/src/main/kotlin/com/lambda/task/tasks/FindPlacedBlockTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/FindPlacedBlockTask.kt @@ -27,7 +27,7 @@ import net.minecraft.util.math.BlockPos @Ta5kBuilder fun findBlock(block: Block, searchRadius: Int) = FindPlacedBlockTask(block, searchRadius) -class FindPlacedBlockTask( +class FindPlacedBlockTask @Ta5kBuilder internal constructor( private val block: Block, private val searchRadius: Int ) : Task() { diff --git a/src/main/kotlin/com/lambda/task/tasks/wrappers/ResultTasks.kt b/src/main/kotlin/com/lambda/task/tasks/wrappers/ResultTasks.kt deleted file mode 100644 index c846d40a6..000000000 --- a/src/main/kotlin/com/lambda/task/tasks/wrappers/ResultTasks.kt +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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.task.tasks.wrappers - -import com.lambda.context.SafeContext -import com.lambda.task.Task -import com.lambda.task.Task.Ta5kBuilder - -@Ta5kBuilder -fun successTask(result: R) = - SuccessTask(result) - -@Ta5kBuilder -fun successTask() = successTask(Unit) - -@Ta5kBuilder -fun failureTask(throwable: R) = - FailureTask(throwable) - -@Ta5kBuilder -fun failureTask(cause: String) = - FailureTask(IllegalStateException(cause)) - -class SuccessTask @Ta5kBuilder internal constructor( - val result: R -) : Task() { - override val name = "Success task" - - override fun SafeContext.onStart() { - success(result) - } -} - -class FailureTask @Ta5kBuilder internal constructor( - val throwable: R -) : Task() { - override val name = "Failure task" - - override fun SafeContext.onStart() { - failure(throwable) - } -} \ No newline at end of file From 94536451a11e1e527896d55c05a11d090a844666 Mon Sep 17 00:00:00 2001 From: beanbag44 <107891830+beanbag44@users.noreply.github.com> Date: Sat, 29 Aug 2026 20:32:47 +0100 Subject: [PATCH 10/43] fix memory leak (?) and general bugs --- .../lambda/mixin/MinecraftClientMixin.java | 1 - .../mixin/entity/ClientPlayerEntityMixin.java | 2 +- .../ClientPlayNetworkHandlerMixin.java | 2 +- .../simulation/sims/InteractSim.kt | 3 +- .../interaction/inventory/StackSelection.kt | 40 +++++++-- .../external/ShulkerBoxContainer.kt | 13 ++- src/main/kotlin/com/lambda/task/Task.kt | 9 +- .../kotlin/com/lambda/task/tasks/BuildTask.kt | 42 +++------ .../com/lambda/task/tasks/CollectDropsTask.kt | 88 +++++++++++++++++++ .../task/tasks/ContainerTransferTask.kt | 15 +--- 10 files changed, 157 insertions(+), 58 deletions(-) create mode 100644 src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt diff --git a/src/main/java/com/lambda/mixin/MinecraftClientMixin.java b/src/main/java/com/lambda/mixin/MinecraftClientMixin.java index 5bd7ef16f..a503297cf 100644 --- a/src/main/java/com/lambda/mixin/MinecraftClientMixin.java +++ b/src/main/java/com/lambda/mixin/MinecraftClientMixin.java @@ -22,7 +22,6 @@ import com.lambda.event.events.*; import com.lambda.gui.DearImGui; import com.lambda.gui.components.ClickGuiLayout; -import com.lambda.interaction.handlers.TimerHandler; import com.lambda.module.modules.movement.BetterFirework; import com.lambda.module.modules.player.Interact; import com.lambda.module.modules.player.InventoryMove; diff --git a/src/main/java/com/lambda/mixin/entity/ClientPlayerEntityMixin.java b/src/main/java/com/lambda/mixin/entity/ClientPlayerEntityMixin.java index e1036c4c3..136c9d2d4 100644 --- a/src/main/java/com/lambda/mixin/entity/ClientPlayerEntityMixin.java +++ b/src/main/java/com/lambda/mixin/entity/ClientPlayerEntityMixin.java @@ -56,7 +56,7 @@ import java.util.Objects; import static com.lambda.Lambda.getMc; -import static com.lambda.interaction.managers.rotating.Rotation.dist; +import static com.lambda.interaction.manager.managers.rotating.Rotation.dist; @Mixin(value = ClientPlayerEntity.class, priority = Integer.MAX_VALUE) public abstract class ClientPlayerEntityMixin extends AbstractClientPlayerEntity { diff --git a/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java b/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java index 7976b71bf..54a5b4ef3 100644 --- a/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java +++ b/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java @@ -24,7 +24,7 @@ import com.lambda.event.events.PlayerEvent; import com.lambda.event.events.WorldEvent; import com.lambda.interaction.manager.managers.inventory.InventoryManager; -import com.lambda.interaction.managers.rotating.RotationManager; +import com.lambda.interaction.manager.managers.rotating.RotationManager; import com.lambda.module.modules.movement.Velocity; import com.lambda.module.modules.render.NoRender; import com.llamalad7.mixinextras.injector.ModifyExpressionValue; diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/InteractSim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/InteractSim.kt index b1b36ef4d..d46136cbe 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/InteractSim.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/InteractSim.kt @@ -60,6 +60,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.Items import net.minecraft.screen.slot.Slot import net.minecraft.state.property.Properties import net.minecraft.util.Hand @@ -204,7 +205,7 @@ class InteractSim internal constructor(simInfo: InteractSimInfo) supervisorScope.cancel() return null } - val stackSelection = item?.select() + val stackSelection = item?.select(if (item == Items.AIR) 0 else 1) ?: selectStack { sortedWith { compareByDescending { it.stack.inventoryIndex == player.inventory.selectedSlot } } } val containerSelection = selectContainer { ofAnyType(ContainerRank.Hotbar) } val container = stackSelection.findContainers(containerSelection).firstOrNull() ?: run { diff --git a/src/main/kotlin/com/lambda/interaction/inventory/StackSelection.kt b/src/main/kotlin/com/lambda/interaction/inventory/StackSelection.kt index 2be088cc1..ce1a4f5ae 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/StackSelection.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/StackSelection.kt @@ -33,6 +33,7 @@ import net.minecraft.item.consume.UseAction import net.minecraft.registry.RegistryKey import net.minecraft.registry.tag.TagKey import net.minecraft.screen.slot.Slot +import net.minecraft.text.Text import java.util.* @ContainerMarker @@ -96,7 +97,7 @@ class StackSelectionBuilder private constructor(private val count: Int = 0) { private var item: Item? = null private var stackByRef = false private var itemStack: ItemStack? = null - private var comparator: Comparator> = compareBy { it.stack.count } + private var comparator: Comparator>? = null private var inShulkerBoxesOnly: Boolean = false private var invertNewSelectors = false @@ -132,6 +133,16 @@ class StackSelectionBuilder private constructor(private val count: Int = 0) { appendSelector { stack, _ -> !items.contains(stack.item) } } + fun isShulkerBox() { + appendSelector { stack, _ -> stack.item in ItemUtils.shulkerBoxes } + } + + fun hasCustomName(name: Text) { + appendSelector { stack, _ -> + stack.get(DataComponentTypes.CUSTOM_NAME)?.string == name.string + } + } + fun isOneOfStacks(stacks: Collection) { appendSelector { stack, _ -> stacks.contains(stack) } } @@ -217,12 +228,31 @@ class StackSelectionBuilder private constructor(private val count: Int = 0) { invertNewSelectors = false } - fun sortedWith(comparator: Comparator>) { - this.comparator = comparator + fun sortedWith(newComparator: Comparator>) { + comparator = comparator?.thenComparing(newComparator) ?: newComparator } fun sortedWith(comparatorSupplier: () -> Comparator>) { - this.comparator = comparatorSupplier() + val newComparator = comparatorSupplier() + comparator = comparator?.thenComparing(newComparator) ?: newComparator + } + + fun sortedByBestContentMatch(expectedContents: List) { + val expectedFrequencies = expectedContents + .filter { !it.isEmpty } + .groupingBy { it.item } + .eachCount() + sortedWith( + compareByDescending { stackAndSlot -> + val currentFrequencies = stackAndSlot.stack.shulkerBoxStacks + .filter { !it.isEmpty } + .groupingBy { it.item } + .eachCount() + expectedFrequencies.asSequence().fold(0) { acc, (item, count) -> + acc + minOf(count, currentFrequencies[item] ?: 0) + } + } + ) } @PublishedApi @@ -240,7 +270,7 @@ class StackSelectionBuilder private constructor(private val count: Int = 0) { item, itemStack, inShulkerBoxesOnly, - comparator, + comparator ?: compareBy { it.stack.count }, selector ) diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ShulkerBoxContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ShulkerBoxContainer.kt index 3d666905f..78030e4cf 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ShulkerBoxContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ShulkerBoxContainer.kt @@ -42,6 +42,7 @@ import com.lambda.util.extension.containerSlots import com.lambda.util.text.buildText import com.lambda.util.text.highlighted import com.lambda.util.text.literal +import com.lambda.util.item.ItemStackUtils.shulkerBoxStacks import net.minecraft.block.entity.ShulkerBoxBlockEntity import net.minecraft.item.ItemStack import net.minecraft.screen.ScreenHandlerType @@ -102,15 +103,23 @@ data class ShulkerBoxContainer( val blockPos: BlockPos, val fromContainer: Container ) : OpenedContainerContext { + private val shulkerItem = slotCache.stack.item + private val customName = slotCache.stack.get(net.minecraft.component.DataComponentTypes.CUSTOM_NAME) + private val originalContents = slotCache.stack.shulkerBoxStacks + context(automated: Automated) override fun close() = taskOrNull { if (!isAccessed) null else actionTask { player.closeScreen() } }.then { breakAndCollect(blockPos) } - .then { slot -> + .then { transfer( - selectStack { isSlot(slot) }, + selectStack { + isItem(shulkerItem) + if (customName != null) hasCustomName(customName) + sortedByBestContentMatch(originalContents) + }, HotbarAndInventoryContainer, fromContainer ) diff --git a/src/main/kotlin/com/lambda/task/Task.kt b/src/main/kotlin/com/lambda/task/Task.kt index 8aaba1110..3ae287ed5 100644 --- a/src/main/kotlin/com/lambda/task/Task.kt +++ b/src/main/kotlin/com/lambda/task/Task.kt @@ -36,7 +36,6 @@ import kotlin.time.toDuration @Suppress("unused") abstract class Task : Nameable, Muteable { var parent: Task<*>? = null - var parentPausing = false val subTasks = mutableListOf>() var state = State.Init override val isMuted: Boolean get() = state == State.Paused || state == State.Init @@ -118,7 +117,6 @@ abstract class Task : Nameable, Muteable { parent = owner if (verboseDebug) LOG.info("${owner.name} started $name") if (pauseParent) { - parentPausing = true if (verboseDebug) LOG.info("$name pausing parent ${owner.name}") if (owner !is RootTask) owner.pause() } @@ -131,7 +129,6 @@ abstract class Task : Nameable, Muteable { protected fun success(result: Result) { unsubscribe() state = State.Completed - parent?.subTasks?.remove(this) parent?.onSubTaskSuccess(this) parent?.onSubTaskCompletion(this) @@ -140,6 +137,8 @@ abstract class Task : Nameable, Muteable { successCallbacks.forEach { it.invoke(this, result) } completionCallbacks.forEach { it.invoke(this) } } + + parent?.subTasks?.remove(this) } @Ta5kBuilder @@ -176,6 +175,8 @@ abstract class Task : Nameable, Muteable { failureCallbacks.forEach { it.invoke(this, e) } completionCallbacks.forEach { it.invoke(this) } } + + parent?.subTasks?.remove(this) } @Ta5kBuilder @@ -201,7 +202,7 @@ abstract class Task : Nameable, Muteable { runSafe { onCancel() } cancelSubTasks() if (removeFromParent) parent?.subTasks?.remove(this) - if (parentPausing) parent?.activate() + parent?.activate() if (this is RootTask) return if (state == State.Completed || state == State.Cancelled) return state = State.Cancelled diff --git a/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt b/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt index 5053c5649..fc6ee0651 100644 --- a/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt @@ -17,7 +17,6 @@ package com.lambda.task.tasks -import baritone.api.pathing.goals.GoalBlock import com.lambda.Lambda.LOG import com.lambda.config.blocks.EatConfig.Companion.reasonEating import com.lambda.context.Automated @@ -47,10 +46,8 @@ import com.lambda.interaction.construction.simulation.result.results.PreSimResul import com.lambda.interaction.construction.simulation.sim import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.handler.handlers.BaritoneHandler -import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer import com.lambda.interaction.manager.managers.breaking.breakRequest import com.lambda.interaction.manager.managers.interacting.interactRequest -import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.modules.client.Client import com.lambda.task.Task import com.lambda.task.Task.Ta5kBuilder @@ -63,7 +60,6 @@ import com.lambda.util.EntityUtils.getClosestPointTo import com.lambda.util.EntityUtils.getPositionsWithinBox import com.lambda.util.FormattingUtils.format import com.lambda.util.extension.Structure -import com.lambda.util.extension.playerSlots import com.lambda.util.math.dist import kotlinx.coroutines.Job import kotlinx.coroutines.runBlocking @@ -141,6 +137,7 @@ class BuildTask @Ta5kBuilder internal constructor( private var breaks = 0 private val dropsToCollect = mutableSetOf() var eatTask: Task<*>? = null + var collectDropsTask: Task<*>? = null private val onItemDrop: ((item: ItemEntity) -> Unit)? get() = if (collectDrops) { item -> @@ -354,34 +351,15 @@ class BuildTask @Ta5kBuilder internal constructor( } } - private fun SafeContext.collectDrops() = - dropsToCollect - .firstOrNull() - ?.let { itemDrop -> - if (pendingInteractions.isNotEmpty()) return@let true - - if (!world.entities.contains(itemDrop)) { - dropsToCollect.remove(itemDrop) - BaritoneHandler.cancel() - return@let true - } - - if (HotbarAndInventoryContainer.stacks.none { it.isEmpty }) { - val stackToThrow = player.currentScreenHandler.playerSlots.firstOrNull { - it.stack.item in inventoryConfig.disposables - } ?: run { - failure("No item in inventory to throw but inventory is full and cant pick up item drop") - return@let true - } - inventoryRequest { - throwStack(stackToThrow.id) - }.submit() - return@let true - } - - BaritoneHandler.setGoalAndPath(GoalBlock(itemDrop.blockPos)) - true - } ?: false + private fun AutomatedSafeContext.collectDrops(): Boolean { + if (dropsToCollect.isEmpty()) return false + if (pendingInteractions.isNotEmpty()) return true + if (collectDropsTask != null) return true + collectDropsTask = collectDrops(dropsToCollect) + .thenAction { collectDropsTask = null } + .execute(this@BuildTask) + return true + } private fun iteratePropagating() = if (blueprint is PropagatingBlueprint) { diff --git a/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt b/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt new file mode 100644 index 000000000..a3e7f5c45 --- /dev/null +++ b/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt @@ -0,0 +1,88 @@ +/* + * 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.task.tasks + +import baritone.api.pathing.goals.GoalBlock +import com.lambda.context.Automated +import com.lambda.context.SafeContext +import com.lambda.event.events.TickEvent +import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.handler.handlers.BaritoneHandler +import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer +import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest +import com.lambda.task.Task +import com.lambda.task.Task.Ta5kBuilder +import com.lambda.threading.runSafeAutomated +import com.lambda.util.extension.playerSlots +import net.minecraft.entity.ItemEntity + +@Ta5kBuilder +context(automated: Automated) +fun collectDrops(drops: MutableSet) = CollectDropsTask(drops, automated) + +/** + * A task that collects a set of item entity drops by walking to them. + * + * Items are considered collected when they despawn from the world (picked up by the player). + * If inventory is full, disposable items are thrown to make space. + * + * Succeeds when all drops have been collected. + */ +class CollectDropsTask @Ta5kBuilder internal constructor( + private val drops: MutableSet, + automated: Automated +) : Task(), Automated by automated { + override val name get() = "Collecting ${drops.size} item drop(s)" + + override fun SafeContext.onStart() { + listen { + runSafeAutomated { + val target = drops.firstOrNull() ?: run { + BaritoneHandler.cancel() + success() + return@listen + } + + if (!world.entities.contains(target)) { + drops.remove(target) + BaritoneHandler.cancel() + return@listen + } + + if (HotbarAndInventoryContainer.stacks.none { it.isEmpty }) { + val stackToThrow = player.currentScreenHandler.playerSlots.firstOrNull { + it.stack.item in inventoryConfig.disposables + } ?: run { + failure("Inventory is full and no disposable items to throw") + return@listen + } + inventoryRequest { + throwStack(stackToThrow.id) + }.submit() + return@listen + } + + BaritoneHandler.setGoalAndPath(GoalBlock(target.blockPos)) + } + } + } + + override fun SafeContext.onCancel() { + BaritoneHandler.cancel() + } +} diff --git a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt index a32c3974d..52a667374 100644 --- a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt @@ -18,7 +18,6 @@ package com.lambda.task.tasks import com.lambda.context.Automated -import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.handler.handlers.ContainerHandler.findContainer @@ -64,14 +63,13 @@ class ContainerTransferTask @Ta5kBuilder internal constructor( ) : Task(), Automated by automated { override val name = "Transferring $selection from $fromContainer to $toContainer" - override fun SafeContext.onStart() { + init { listen { runSafeAutomated { when { fromContainer.isAccessed && toContainer.isAccessed -> TransferTask() .onSuccess { success(it) } - .execute(this@ContainerTransferTask) fromContainer !is ExternalContainer -> openTransferClose(toContainer, fromContainer, toContainer) @@ -88,8 +86,7 @@ class ContainerTransferTask @Ta5kBuilder internal constructor( } .then { toContainer.access() } .then { toCtx -> transferAndClose(toCtx, HotbarAndInventoryContainer, toContainer) } - .execute(this@ContainerTransferTask) - } + }.execute(this@ContainerTransferTask) } } } @@ -99,14 +96,10 @@ class ContainerTransferTask @Ta5kBuilder internal constructor( * * Used when exactly one of the two containers is external and needs to be accessed. */ - private fun openTransferClose(containerToOpen: Container, from: Container, to: Container) { + private fun openTransferClose(containerToOpen: Container, from: Container, to: Container) = containerToOpen .access() - .then { ctx -> - transferAndClose(ctx, from, to) - } - .execute(this@ContainerTransferTask) - } + .then { ctx -> transferAndClose(ctx, from, to) } /** * Transfers [selection] from [from] to [to], then closes the container via [ctx]. From ccd3a089c7cce47a6014ca2f02bc601b5d78f95c Mon Sep 17 00:00:00 2001 From: beanbag44 <107891830+beanbag44@users.noreply.github.com> Date: Tue, 1 Sep 2026 00:33:28 +0100 Subject: [PATCH 11/43] better container caching, etc --- .../lambda/mixin/MinecraftClientMixin.java | 2 +- .../lambda/command/commands/BuildCommand.kt | 8 +- .../command/commands/TransferCommand.kt | 8 +- .../construction/blueprint/StaticBlueprint.kt | 6 +- .../simulation/result/Resolvable.kt | 6 +- .../simulation/result/results/BreakResult.kt | 11 +- .../result/results/GenericResult.kt | 11 +- .../construction/simulation/sims/BreakSim.kt | 2 +- .../simulation/sims/InteractSim.kt | 2 +- .../construction/verify/TargetState.kt | 2 +- .../handler/handlers/ContainerHandler.kt | 302 ++++++++++++------ .../handlers/breaking/BrokenBlockHandler.kt | 2 +- .../handlers/breaking/RebreakHandler.kt | 14 +- .../handlers/breaking/RebreakResult.kt | 2 +- .../interacting/InteractedBlockHandler.kt | 2 +- .../handlers/packet/PacketLimitHandler.kt | 2 +- .../interaction/inventory/StackSelection.kt | 12 +- .../inventory/container/Container.kt | 4 +- .../inventory/container/NestedContainer.kt | 7 +- .../inventory/container/OpenContainerTask.kt | 2 +- .../container/OpenedContainerContext.kt | 8 +- .../inventory/container/PlacedContainer.kt | 6 +- .../containers/external/ChestContainer.kt | 42 +-- .../external/DoubleChestContainer.kt | 95 ++++++ .../external/EnderChestContainer.kt | 45 ++- .../external/PlacedShulkerBoxContainer.kt | 26 +- .../external/ShulkerBoxContainer.kt | 47 ++- .../manager/managers/breaking/BreakInfo.kt | 6 +- .../module/modules/combat/CrystalAura.kt | 2 +- .../module/modules/combat/PlayerTrap.kt | 5 +- .../lambda/module/modules/combat/Surround.kt | 5 +- .../module/modules/debug/ContainerTest.kt | 7 +- .../lambda/module/modules/player/AutoEat.kt | 6 +- .../module/modules/player/InventoryTweaks.kt | 87 +++-- .../lambda/module/modules/world/AutoPortal.kt | 8 +- .../module/modules/world/AutoVillagerCycle.kt | 8 +- .../module/modules/world/HighwayTools.kt | 4 +- .../com/lambda/module/modules/world/Nuker.kt | 4 +- .../lambda/module/modules/world/Printer.kt | 5 +- .../lambda/module/modules/world/StashMover.kt | 4 +- src/main/kotlin/com/lambda/task/RootTask.kt | 19 +- src/main/kotlin/com/lambda/task/Task.kt | 63 ++-- .../task/tasks/AcquirePlacedBlockTask.kt | 2 +- .../com/lambda/task/tasks/AcquireStackTask.kt | 9 +- .../kotlin/com/lambda/task/tasks/BuildTask.kt | 5 +- .../com/lambda/task/tasks/CollectDropsTask.kt | 24 +- .../task/tasks/ContainerTransferTask.kt | 64 ++-- .../lambda/task/tasks/PlaceContainerTask.kt | 7 +- src/main/kotlin/com/lambda/util/BlockUtils.kt | 5 +- .../com/lambda/util/player/SlotUtils.kt | 8 +- 50 files changed, 632 insertions(+), 401 deletions(-) create mode 100644 src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/DoubleChestContainer.kt diff --git a/src/main/java/com/lambda/mixin/MinecraftClientMixin.java b/src/main/java/com/lambda/mixin/MinecraftClientMixin.java index a503297cf..fe7c740ae 100644 --- a/src/main/java/com/lambda/mixin/MinecraftClientMixin.java +++ b/src/main/java/com/lambda/mixin/MinecraftClientMixin.java @@ -141,7 +141,7 @@ private void onScreenOpen(@Nullable Screen screen, CallbackInfo ci) { } } - @Inject(method = "setScreen", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/Screen;removed()V", shift = At.Shift.AFTER)) + @Inject(method = "setScreen", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/Screen;removed()V", shift = At.Shift.BEFORE)) private void onScreenRemove(@Nullable Screen screen, CallbackInfo ci) { if (currentScreen == null) return; if (currentScreen instanceof ScreenHandlerProvider handledScreen) { diff --git a/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt b/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt index 71f458664..ccd2212ab 100644 --- a/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt +++ b/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt @@ -28,8 +28,8 @@ import com.lambda.command.LambdaCommand import com.lambda.config.automation.AutomationConfig import com.lambda.interaction.construction.StructureRegistry import com.lambda.interaction.construction.blueprint.Blueprint.Companion.toStructure -import com.lambda.interaction.construction.blueprint.StaticBlueprint.Companion.toBlueprint -import com.lambda.task.RootTask.run +import com.lambda.interaction.construction.blueprint.toBlueprint +import com.lambda.task.start import com.lambda.task.tasks.BuildTask import com.lambda.task.tasks.build import com.lambda.threading.runSafe @@ -62,12 +62,12 @@ object BuildCommand : LambdaCommand( .loadStructureByRelativePath(Path.of(pathString)) .let { template -> info("Building structure $pathString with dimensions ${template.size.toShortString()} created by ${template.author}") - lastBuildTask = with(AutomationConfig.Companion.DEFAULT) { + lastBuildTask = with(AutomationConfig.DEFAULT) { template.toStructure() .move(player.blockPos) .toBlueprint() .build() - .run() + .start() } return@executeWithResult success() diff --git a/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt b/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt index 949724141..0e0d4babb 100644 --- a/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt +++ b/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt @@ -29,11 +29,11 @@ import com.lambda.brigadier.required import com.lambda.command.LambdaCommand import com.lambda.config.automation.AutomationConfig import com.lambda.interaction.handler.handlers.ContainerHandler -import com.lambda.interaction.handler.handlers.ContainerHandler.findContainers -import com.lambda.interaction.handler.handlers.ContainerHandler.findContainersWithSpace +import com.lambda.interaction.handler.handlers.findContainers +import com.lambda.interaction.handler.handlers.findContainersWithSpace import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack -import com.lambda.task.RootTask import com.lambda.task.Task +import com.lambda.task.start import com.lambda.task.tasks.transfer import com.lambda.threading.runSafeAutomated import com.lambda.util.CommunicationUtils.info @@ -106,7 +106,7 @@ object TransferCommand : LambdaCommand( } ?: return@executeWithResult failure("To container not found") transfer(selection, fromContainer, toContainer) - .execute(RootTask) + .start() } return@executeWithResult success() } diff --git a/src/main/kotlin/com/lambda/interaction/construction/blueprint/StaticBlueprint.kt b/src/main/kotlin/com/lambda/interaction/construction/blueprint/StaticBlueprint.kt index f06de623e..f5e6e3176 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/blueprint/StaticBlueprint.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/blueprint/StaticBlueprint.kt @@ -23,8 +23,6 @@ data class StaticBlueprint( override val structure: Structure, ) : Blueprint() { override fun toString() = "Static Blueprint at ${center?.toShortString()}" - - companion object { - fun Structure.toBlueprint() = StaticBlueprint(this) - } } + +fun Structure.toBlueprint() = StaticBlueprint(this) diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/Resolvable.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/Resolvable.kt index 533123ef0..496faa8cc 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/Resolvable.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/Resolvable.kt @@ -17,13 +17,13 @@ package com.lambda.interaction.construction.simulation.result -import com.lambda.context.AutomatedSafeContext +import com.lambda.context.Automated import com.lambda.task.Task /** * Represents a [BuildResult] with a resolvable [Task] */ interface Resolvable { - context(task: Task<*>, _: AutomatedSafeContext) - fun resolve() + context(_: Automated) + fun resolve(): Task<*> } diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt index 913060307..769f2b102 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt @@ -19,7 +19,7 @@ package com.lambda.interaction.construction.simulation.result.results import baritone.api.pathing.goals.GoalBlock import baritone.api.pathing.goals.GoalInverted -import com.lambda.context.AutomatedSafeContext +import com.lambda.context.Automated import com.lambda.graphics.mc.RenderBuilder import com.lambda.graphics.util.DirectionMask.mask import com.lambda.interaction.construction.simulation.context.BreakContext @@ -34,9 +34,7 @@ import com.lambda.interaction.construction.simulation.result.Resolvable import com.lambda.interaction.handler.handlers.BaritoneHandler import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack import com.lambda.interaction.inventory.container.containers.HotbarContainer -import com.lambda.task.Task import com.lambda.task.tasks.transferTo -import com.lambda.task.tasks.wrappers.softFail import net.minecraft.block.BlockState import net.minecraft.item.Item import net.minecraft.util.math.BlockPos @@ -118,13 +116,10 @@ sealed class BreakResult : BuildResult() { ) : Resolvable, BreakResult() { override val rank = Rank.BreakItemCantMine - context(task: Task<*>, _: AutomatedSafeContext) - override fun resolve() { + context(_: Automated) + override fun resolve() = selectStack { inverted { isItem(badItem) } } .transferTo(HotbarContainer) - .softFail() - .execute(task) - } override fun compareResult(other: ComparableResult) = when (other) { diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/GenericResult.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/GenericResult.kt index ed5b54356..69dc06081 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/GenericResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/GenericResult.kt @@ -18,7 +18,7 @@ package com.lambda.interaction.construction.simulation.result.results import baritone.api.pathing.goals.GoalNear -import com.lambda.context.AutomatedSafeContext +import com.lambda.context.Automated import com.lambda.graphics.mc.RenderBuilder import com.lambda.interaction.construction.simulation.result.BuildResult import com.lambda.interaction.construction.simulation.result.ComparableResult @@ -29,9 +29,7 @@ import com.lambda.interaction.construction.simulation.result.Resolvable import com.lambda.interaction.handler.handlers.BaritoneHandler import com.lambda.interaction.inventory.StackSelection import com.lambda.interaction.inventory.container.containers.HotbarContainer -import com.lambda.task.Task import com.lambda.task.tasks.transferTo -import com.lambda.task.tasks.wrappers.softFail import net.minecraft.client.data.TextureMap.side import net.minecraft.item.ItemStack import net.minecraft.util.math.BlockPos @@ -99,13 +97,10 @@ sealed class GenericResult : BuildResult() { override val rank = Rank.WrongItem private val color = Color(3, 252, 169, 25) - context(task: Task<*>, _: AutomatedSafeContext) - override fun resolve() { + context(_: Automated) + override fun resolve() = neededSelection .transferTo(HotbarContainer) - .softFail() - .execute(task) - } override fun RenderBuilder.render() { val center = pos.toCenterPos() diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/BreakSim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/BreakSim.kt index 74741cd17..6c179d68c 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/BreakSim.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/BreakSim.kt @@ -28,7 +28,7 @@ import com.lambda.interaction.construction.simulation.result.results.BreakResult import com.lambda.interaction.construction.simulation.result.results.GenericResult import com.lambda.interaction.construction.simulation.sim import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.handler.handlers.ContainerHandler.findContainers +import com.lambda.interaction.handler.handlers.findContainers import com.lambda.interaction.inventory.ContainerSelectionBuilder.Companion.selectContainer import com.lambda.interaction.inventory.StackAndSlot import com.lambda.interaction.inventory.StackSelection diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/InteractSim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/InteractSim.kt index d46136cbe..10d9b0629 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/InteractSim.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/InteractSim.kt @@ -27,7 +27,7 @@ import com.lambda.interaction.construction.simulation.result.results.GenericResu import com.lambda.interaction.construction.simulation.result.results.InteractResult import com.lambda.interaction.construction.simulation.sim import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.handler.handlers.ContainerHandler.findContainers +import com.lambda.interaction.handler.handlers.findContainers import com.lambda.interaction.inventory.ContainerSelectionBuilder.Companion.selectContainer import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack import com.lambda.interaction.inventory.container.ContainerRank diff --git a/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt b/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt index f6ccb54ee..cf594ccb8 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt @@ -19,7 +19,7 @@ package com.lambda.interaction.construction.verify import com.lambda.context.AutomatedSafeContext import com.lambda.context.SafeContext -import com.lambda.interaction.handler.handlers.ContainerHandler.findDisposable +import com.lambda.interaction.handler.handlers.findDisposable import com.lambda.util.BlockUtils.blockState import com.lambda.util.BlockUtils.emptyState import com.lambda.util.BlockUtils.isEmpty diff --git a/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt index 39fb480a7..ced2c4d1c 100644 --- a/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt @@ -19,147 +19,271 @@ package com.lambda.interaction.handler.handlers import com.lambda.context.Automated import com.lambda.context.AutomatedSafeContext +import com.lambda.context.SafeContext import com.lambda.core.Loadable import com.lambda.event.events.InventoryEvent -import com.lambda.event.events.PlayerEvent +import com.lambda.event.events.PacketEvent +import com.lambda.event.events.WorldEvent import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.handler.handlers.ContainerHandler.filteredContainers import com.lambda.interaction.inventory.ContainerMarker import com.lambda.interaction.inventory.ContainerSelection import com.lambda.interaction.inventory.StackSelection import com.lambda.interaction.inventory.container.Container +import com.lambda.interaction.inventory.container.NestedContainer +import com.lambda.interaction.inventory.container.PlacedContainer import com.lambda.interaction.inventory.container.containers.external.ChestContainer +import com.lambda.interaction.inventory.container.containers.external.DoubleChestContainer import com.lambda.interaction.inventory.container.containers.external.EnderChestContainer +import com.lambda.interaction.inventory.container.containers.external.PlacedShulkerBoxContainer +import com.lambda.interaction.inventory.container.containers.external.ShulkerBoxContainer import com.lambda.interaction.inventory.select import com.lambda.util.BlockUtils.blockEntity +import com.lambda.util.BlockUtils.blockState import com.lambda.util.ReflectionUtils.getInstances import com.lambda.util.extension.containerStacks +import com.lambda.util.item.ItemStackUtils.shulkerBoxStacks +import com.lambda.util.item.ItemUtils.shulkerBoxes +import com.lambda.util.player.SlotUtils.typeSafe +import net.minecraft.block.ChestBlock import net.minecraft.block.entity.BlockEntity import net.minecraft.block.entity.ChestBlockEntity import net.minecraft.block.entity.EnderChestBlockEntity -import net.minecraft.screen.GenericContainerScreenHandler +import net.minecraft.block.entity.ShulkerBoxBlockEntity +import net.minecraft.block.enums.ChestType +import net.minecraft.inventory.Inventory +import net.minecraft.network.packet.c2s.play.PlayerInteractBlockC2SPacket +import net.minecraft.screen.ScreenHandler import net.minecraft.screen.ScreenHandlerType +import net.minecraft.state.property.Properties +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.ChunkPos -// ToDo: Make this a Configurable to save container caches. Should use a cached region based storage system. @Suppress("unused") object ContainerHandler : Loadable { private val containers: Sequence - get() = compileContainers.asSequence() + runtimeContainers + get() = compileContainers.asSequence() + + placedContainers.values.asSequence().flatMap { it.values } + + storedContainers.values.asSequence().flatMap { it.values } - private val compileContainers = getInstances() - private val runtimeContainers = mutableSetOf() + val compileContainers = getInstances() + val placedContainers = mutableMapOf>() + val storedContainers = mutableMapOf>() context(automated: Automated) val filteredContainers - get() = - containers - .filter { automated.inventoryConfig.containerSelection.matches(it) } - .sorted() + get() = containers + .filter { automated.inventoryConfig.containerSelection.matches(it) } + .sorted() val allContainers - get() = - containers - .sorted() + get() = containers.sorted() var lastInteractedBlockEntity: BlockEntity? = null override fun load() = "Loaded ${compileContainers.size} containers" init { - listen { - lastInteractedBlockEntity = blockEntity(it.blockHitResult.blockPos) + listen { event -> + val packet = event.packet as? PlayerInteractBlockC2SPacket ?: return@listen + lastInteractedBlockEntity = blockEntity(packet.blockHitResult.blockPos) } listen { event -> - if (event.screenHandler !is GenericContainerScreenHandler) return@listen + val sh = event.screenHandler + onContainerUpdate(sh) + if (sh.syncId != 0) lastInteractedBlockEntity = null + } + listen { onContainerUpdate() } + listen { onContainerUpdate() } + + listen { event -> + val pos = event.pos + val containersInChunk = placedContainers[ChunkPos(pos)] ?: return@listen + val cachedContainer = containersInChunk[pos] ?: return@listen + val currentState = blockState(cachedContainer.pos) + if (currentState.block != event.newState.block) { + containersInChunk.remove(pos) + } + } - val handler = event.screenHandler + listen { event -> + val chunk = event.chunk + val cachedContainers = placedContainers[chunk.pos] ?: return@listen + cachedContainers.values.retainAll { container -> + val matchingEntity = chunk.blockEntities[container.pos] ?: return@retainAll false + blockState(container.pos).block == matchingEntity.cachedState.block + } + } + } - when (val block = lastInteractedBlockEntity) { - is EnderChestBlockEntity -> { - if (handler.type != ScreenHandlerType.GENERIC_9X3) return@listen + private fun SafeContext.onContainerUpdate(sh: ScreenHandler = player.currentScreenHandler) { + val blockEntity = lastInteractedBlockEntity + if (blockEntity != null) updatePlacedContainer(sh, blockEntity) + } - EnderChestContainer.update(handler.containerStacks) + private fun updatePlacedContainer( + sh: ScreenHandler, + blockEntity: BlockEntity + ) { + val pos = blockEntity.pos + when (blockEntity) { + is EnderChestBlockEntity -> { + if (sh.typeSafe != ScreenHandlerType.GENERIC_9X3) return + EnderChestContainer.apply { + update(sh.containerStacks) + scanContainerContents() } + } + + is ChestBlockEntity -> { + val state = blockEntity.cachedState + if (state.block !is ChestBlock) return + + val stacks = blockEntity.stacks + val chestType = state.get(Properties.CHEST_TYPE) ?: return + val chunkPos = ChunkPos(pos) + + if (chestType == ChestType.SINGLE) { + if (sh.typeSafe != ScreenHandlerType.GENERIC_9X3) return + (chunkPos.getPlacedContainer(pos) as? ChestContainer) + ?.update(stacks) + ?: run { chunkPos.setPlacedContainer(pos, ChestContainer(pos, stacks)) } + } else { + if (sh.typeSafe != ScreenHandlerType.GENERIC_9X6) return + val facing = state.get(Properties.HORIZONTAL_FACING) ?: return + val otherPos = + when (chestType) { + ChestType.LEFT -> pos.offset(facing.rotateYClockwise()) + ChestType.RIGHT -> pos.offset(facing.rotateYCounterclockwise()) + } + val leftPos = if (chestType == ChestType.LEFT) pos else otherPos + val leftChunkPos = ChunkPos(leftPos) + val rightPos = if (chestType == ChestType.RIGHT) pos else otherPos + val rightChunkPos = ChunkPos(rightPos) - is ChestBlockEntity -> { - // ToDo: Handle double chests and single chests - if (handler.type != ScreenHandlerType.GENERIC_9X6) return@listen - val stacks = handler.containerStacks + val existing = (leftChunkPos.getPlacedContainer(leftPos) as? DoubleChestContainer) + ?: (rightChunkPos.getPlacedContainer(rightPos) as? DoubleChestContainer) - containers - .filterIsInstance() - .find { - it.blockPos == block.pos - }?.update(stacks) ?: runtimeContainers.add(ChestContainer(block.pos, stacks)) + if (existing != null) { + existing.update(stacks) + leftChunkPos.setPlacedContainer(leftPos, existing) + rightChunkPos.setPlacedContainer(rightPos, existing) + } else { + val doubleChest = DoubleChestContainer(pos, leftPos, rightPos, stacks) + leftChunkPos.setPlacedContainer(leftPos, doubleChest) + rightChunkPos.setPlacedContainer(rightPos, doubleChest) + } } + + chunkPos.getPlacedContainer(pos)?.scanContainerContents() + } + + is ShulkerBoxBlockEntity -> { + if (sh.typeSafe != ScreenHandlerType.SHULKER_BOX) return + val stacks = blockEntity.stacks + val chunkPos = ChunkPos(pos) + (chunkPos.getPlacedContainer(pos) as? PlacedShulkerBoxContainer) + ?.update(stacks) + ?: run { + chunkPos.setPlacedContainer( + pos, + PlacedShulkerBoxContainer(pos, blockEntity.cachedState.block, null, stacks) + ) + } + + chunkPos.getPlacedContainer(pos)?.scanContainerContents() } - lastInteractedBlockEntity = null } } - @ContainerMarker - context(automatedSafeContext: AutomatedSafeContext) - fun StackSelection.move(destination: Container) = - with(automatedSafeContext) { - findContainer(inventoryConfig.containerSelection) - ?.move(this@move, destination) - ?: false + private fun Container.scanContainerContents() { + val storageCache = storedContainers.getOrPut(this) { mutableMapOf() } + slots.forEach { slot -> + val stack = slot.stack + val index = slot.index + if (stack.item in shulkerBoxes) { + storageCache[index] = + ShulkerBoxContainer( + stack.name.string, + stack.item, + stack.shulkerBoxStacks, + this, + index + ).also { it.scanContainerContents() } + } else storageCache.remove(index) } + } - @ContainerMarker - context(_: Automated) - fun findContainer(block: (Container) -> Boolean) = filteredContainers.find(block) + private fun getPlacedContainer(pos: BlockPos) = placedContainers[ChunkPos(pos)]?.get(pos) + private fun ChunkPos.getPlacedContainer(pos: BlockPos) = placedContainers[this]?.get(pos) + private fun setPlacedContainer(pos: BlockPos, container: PlacedContainer) = + placedContainers.getOrPut(ChunkPos(pos)) { mutableMapOf() }.put(pos, container) + private fun ChunkPos.setPlacedContainer(pos: BlockPos, container: PlacedContainer) = + placedContainers.getOrPut(this) { mutableMapOf() }.put(pos, container) - @ContainerMarker - context(automated: Automated) - fun StackSelection.findContainer( - containerSelection: ContainerSelection = automated.inventoryConfig.containerSelection - ) = findContainers(containerSelection).firstOrNull() + private val Inventory.stacks + get() = iterator().asSequence().toList() +} - @ContainerMarker - context(automated: Automated) - fun StackSelection.findContainers( - containerSelection: ContainerSelection = automated.inventoryConfig.containerSelection - ) = - filteredContainers - .filter { containerSelection.matches(it) } - .filter { it.stackCount(this) >= count } - .sortedWith(automated.inventoryConfig.accessPriority.materialComparator(this)) - - @ContainerMarker - context(automated: Automated) - fun StackSelection.findContainerWithSpace( - containerSelection: ContainerSelection = automated.inventoryConfig.containerSelection - ) = findContainersWithSpace(containerSelection).firstOrNull() +@ContainerMarker +context(automatedSafeContext: AutomatedSafeContext) +fun StackSelection.move(destination: Container) = + with(automatedSafeContext) { + findContainer(inventoryConfig.containerSelection) + ?.move(this@move, destination) + ?: false + } - @ContainerMarker - context(automated: Automated) - fun StackSelection.findContainersWithSpace( - containerSelection: ContainerSelection = automated.inventoryConfig.containerSelection - ) = - filteredContainers - .filter { containerSelection.matches(it) } - .filter { it.spaceAvailable(this) >= count } - .sortedWith(automated.inventoryConfig.accessPriority.spaceComparator(this)) - - @ContainerMarker - context(automated: Automated) - fun StackSelection.findSlot( - containerSelection: ContainerSelection = automated.inventoryConfig.containerSelection - ) = findSlots(containerSelection).firstOrNull() +@ContainerMarker +context(_: Automated) +fun findContainer(predicate: (Container) -> Boolean) = filteredContainers.find(predicate) - @ContainerMarker - context(automated: Automated) - fun StackSelection.findSlots( - containerSelection: ContainerSelection = automated.inventoryConfig.containerSelection - ) = findContainers(containerSelection).flatMap { filter(it.slots) } +@ContainerMarker +context(automated: Automated) +fun StackSelection.findContainer( + containerSelection: ContainerSelection = automated.inventoryConfig.containerSelection +) = findContainers(containerSelection).firstOrNull() - @ContainerMarker - context(automated: Automated) - fun findDisposable() = - filteredContainers.find { container -> - automated.inventoryConfig.disposables.any { container.stackCount(it.asItem().select(1)) > 0 } - } +@ContainerMarker +context(automated: Automated) +fun StackSelection.findContainers( + containerSelection: ContainerSelection = automated.inventoryConfig.containerSelection +) = filteredContainers + .filter { containerSelection.matches(it) } + .filter { it.stackCount(this) >= count } + .sortedWith(automated.inventoryConfig.accessPriority.materialComparator(this)) + +@ContainerMarker +context(automated: Automated) +fun StackSelection.findContainerWithSpace( + containerSelection: ContainerSelection = automated.inventoryConfig.containerSelection +) = findContainersWithSpace(containerSelection).firstOrNull() + +@ContainerMarker +context(automated: Automated) +fun StackSelection.findContainersWithSpace( + containerSelection: ContainerSelection = automated.inventoryConfig.containerSelection +) = filteredContainers + .filter { containerSelection.matches(it) } + .filter { it.spaceAvailable(this) >= count } + .sortedWith(automated.inventoryConfig.accessPriority.spaceComparator(this)) + +@ContainerMarker +context(automated: Automated) +fun StackSelection.findSlot( + containerSelection: ContainerSelection = automated.inventoryConfig.containerSelection +) = findSlots(containerSelection).firstOrNull() + +@ContainerMarker +context(automated: Automated) +fun StackSelection.findSlots( + containerSelection: ContainerSelection = automated.inventoryConfig.containerSelection +) = findContainers(containerSelection).flatMap { filter(it.slots) } - class NoContainerFound(selection: StackSelection) : Exception("No container found matching $selection") -} \ No newline at end of file +@ContainerMarker +context(automated: Automated) +fun findDisposable() = + filteredContainers.find { container -> + automated.inventoryConfig.disposables.any { container.stackCount(it.asItem().select(1)) > 0 } + } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/BrokenBlockHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/BrokenBlockHandler.kt index 681358860..bd1241726 100644 --- a/src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/BrokenBlockHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/BrokenBlockHandler.kt @@ -51,7 +51,7 @@ import net.minecraft.util.math.ChunkSectionPos * * @see com.lambda.interaction.managers.breaking.BreakManager */ -object BrokenBlockHandler : PostActionHandler() { +internal object BrokenBlockHandler : PostActionHandler() { override val pendingActions = LimitedDecayQueue( DEFAULT.buildConfig.maxPendingActions, DEFAULT.buildConfig.actionTimeout * 50L ) { info -> diff --git a/src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/RebreakHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/RebreakHandler.kt index 84c82a11f..5817eb84c 100644 --- a/src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/RebreakHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/RebreakHandler.kt @@ -40,7 +40,7 @@ import net.minecraft.util.Hand * Designed to track the latest primary-broken [BreakInfo] in order to exploit a flaw in Minecraft's code that allows * the user to break any block placed in said position using the progress from the previously broken block. */ -object RebreakHandler { +internal object RebreakHandler { var reBreak: BreakInfo? = null init { @@ -134,12 +134,12 @@ object RebreakHandler { RebreakResult.StillBreaking(reBreak) } } +} - enum class RebreakPotential { - Instant, - PartialProgress, - None; +enum class RebreakPotential { + Instant, + PartialProgress, + None; - fun isPossible() = this != None - } + fun isPossible() = this != None } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/RebreakResult.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/RebreakResult.kt index c98b4a6bf..67982cf31 100644 --- a/src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/RebreakResult.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/RebreakResult.kt @@ -19,7 +19,7 @@ package com.lambda.interaction.handler.handlers.breaking import com.lambda.interaction.manager.managers.breaking.BreakInfo -sealed class RebreakResult { +internal sealed class RebreakResult { data object Ignored : RebreakResult() data object Rebroke : RebreakResult() diff --git a/src/main/kotlin/com/lambda/interaction/handler/handlers/interacting/InteractedBlockHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/interacting/InteractedBlockHandler.kt index 59aeaa843..b5dc73358 100644 --- a/src/main/kotlin/com/lambda/interaction/handler/handlers/interacting/InteractedBlockHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/interacting/InteractedBlockHandler.kt @@ -30,7 +30,7 @@ import com.lambda.util.BlockUtils.matches import com.lambda.util.CommunicationUtils.warn import com.lambda.util.collections.LimitedDecayQueue -object InteractedBlockHandler : PostActionHandler() { +internal object InteractedBlockHandler : PostActionHandler() { override val pendingActions = LimitedDecayQueue( AutomationConfig.DEFAULT.buildConfig.maxPendingActions, AutomationConfig.DEFAULT.buildConfig.actionTimeout * 50L diff --git a/src/main/kotlin/com/lambda/interaction/handler/handlers/packet/PacketLimitHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/packet/PacketLimitHandler.kt index 2b6fdf428..6229e1e29 100644 --- a/src/main/kotlin/com/lambda/interaction/handler/handlers/packet/PacketLimitHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/packet/PacketLimitHandler.kt @@ -26,7 +26,7 @@ import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds import kotlin.time.TimeSource -object PacketLimitHandler { +internal object PacketLimitHandler { private val packetLimitMap = PacketType.entries.associateWith { LimitHandler() } init { diff --git a/src/main/kotlin/com/lambda/interaction/inventory/StackSelection.kt b/src/main/kotlin/com/lambda/interaction/inventory/StackSelection.kt index ce1a4f5ae..9e27c08fd 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/StackSelection.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/StackSelection.kt @@ -33,7 +33,6 @@ import net.minecraft.item.consume.UseAction import net.minecraft.registry.RegistryKey import net.minecraft.registry.tag.TagKey import net.minecraft.screen.slot.Slot -import net.minecraft.text.Text import java.util.* @ContainerMarker @@ -137,9 +136,9 @@ class StackSelectionBuilder private constructor(private val count: Int = 0) { appendSelector { stack, _ -> stack.item in ItemUtils.shulkerBoxes } } - fun hasCustomName(name: Text) { + fun hasCustomName(name: String) { appendSelector { stack, _ -> - stack.get(DataComponentTypes.CUSTOM_NAME)?.string == name.string + stack.name.string == name } } @@ -218,6 +217,10 @@ class StackSelectionBuilder private constructor(private val count: Int = 0) { appendSelector { _, s -> s === slot } } + fun inIndex(index: Int) { + appendSelector { _, s -> s?.index == index } + } + fun custom(predicate: (ItemStack, Slot?) -> Boolean) { appendSelector { stack, slot -> predicate(stack, slot) } } @@ -233,8 +236,7 @@ class StackSelectionBuilder private constructor(private val count: Int = 0) { } fun sortedWith(comparatorSupplier: () -> Comparator>) { - val newComparator = comparatorSupplier() - comparator = comparator?.thenComparing(newComparator) ?: newComparator + sortedWith(comparatorSupplier()) } fun sortedByBestContentMatch(expectedContents: List) { diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/Container.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/Container.kt index 58563fbcf..c652128ef 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/Container.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/Container.kt @@ -47,7 +47,7 @@ abstract class Container( val rank: ContainerRank ) : Nameable, Comparable { override val name: String - get() = buildText { text(description) }.string + get() = description.string abstract val slots: List abstract var stacks: List @@ -101,7 +101,7 @@ abstract class Container( @Ta5kBuilder context(automated: Automated) - open fun access(): OpenContainerTask<*> = + open fun access(): OpenContainerTask<*>? = PlayerOpenContainerTask(description, ::isAccessed) context(automatedSafeContext: AutomatedSafeContext) diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/NestedContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/NestedContainer.kt index 67f861999..039fe429f 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/NestedContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/NestedContainer.kt @@ -17,8 +17,7 @@ package com.lambda.interaction.inventory.container -import net.minecraft.screen.slot.Slot - -interface NestedContainer { - val slotCache: Slot? +abstract class NestedContainer(rank: ContainerRank) : Container(rank) { + abstract val containedIn: Container + abstract val index: Int } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/OpenContainerTask.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/OpenContainerTask.kt index 2954b10a3..e871641fa 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/OpenContainerTask.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/OpenContainerTask.kt @@ -23,7 +23,7 @@ import net.minecraft.text.Text abstract class OpenContainerTask @Ta5kBuilder internal constructor( description: Text -) : Task() { +) : Task() { override val name = "accessing container: ${description.string}" } diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/OpenedContainerContext.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/OpenedContainerContext.kt index 7b25339e1..a58ba8990 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/OpenedContainerContext.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/OpenedContainerContext.kt @@ -25,7 +25,7 @@ import com.lambda.task.tasks.wrappers.actionTask interface OpenedContainerContext { @Ta5kBuilder context(automated: Automated) - fun close(): Task<*> + fun close(): Task<*>? } open class BasicOpenedContainerContext( @@ -33,8 +33,6 @@ open class BasicOpenedContainerContext( ) : OpenedContainerContext { context(_: Automated) final override fun close() = - actionTask { - if (!isAccessed()) return@actionTask - player.closeScreen() - } + if (!isAccessed()) null + else actionTask { player.closeScreen() } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/PlacedContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/PlacedContainer.kt index 7d6d9d051..85ab71da3 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/PlacedContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/PlacedContainer.kt @@ -18,7 +18,9 @@ package com.lambda.interaction.inventory.container import com.lambda.interaction.inventory.container.containers.external.StashContainer +import net.minecraft.util.math.BlockPos -interface PlacedContainer { - val stash: StashContainer? +abstract class PlacedContainer(rank: ContainerRank) : Container(rank), ExternalContainer { + abstract val pos: BlockPos + abstract val stash: StashContainer? } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ChestContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ChestContainer.kt index 06d553d15..abdb79b94 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ChestContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ChestContainer.kt @@ -22,42 +22,37 @@ import com.lambda.context.Automated import com.lambda.context.SafeContext import com.lambda.interaction.handler.handlers.ContainerHandler.lastInteractedBlockEntity import com.lambda.interaction.inventory.container.BasicOpenedContainerContext -import com.lambda.interaction.inventory.container.Container import com.lambda.interaction.inventory.container.ContainerRank -import com.lambda.interaction.inventory.container.ExternalContainer import com.lambda.interaction.inventory.container.OpenContainerTask +import com.lambda.interaction.inventory.container.OpenedContainerContext import com.lambda.interaction.inventory.container.PlacedContainer import com.lambda.task.Task.Ta5kBuilder import com.lambda.task.tasks.openContainer -import com.lambda.task.tasks.wrappers.taskOrNull import com.lambda.util.extension.containerSlots +import com.lambda.util.player.SlotUtils.typeSafe import com.lambda.util.text.buildText import com.lambda.util.text.highlighted import com.lambda.util.text.literal import net.minecraft.block.ChestBlock -import net.minecraft.block.entity.ChestBlockEntity import net.minecraft.item.ItemStack import net.minecraft.screen.ScreenHandlerType import net.minecraft.screen.slot.Slot import net.minecraft.util.math.BlockPos data class ChestContainer( - val blockPos: BlockPos, + override val pos: BlockPos, override var stacks: List, override val stash: StashContainer? = null -) : Container(ContainerRank.Chest), ExternalContainer, PlacedContainer { +) : PlacedContainer(ContainerRank.Chest) { override val slots get(): List = - lastInteractedBlockEntity?.let { blockEntity -> - if (blockEntity is ChestBlockEntity && blockEntity.pos == blockPos) { - mc.player?.currentScreenHandler?.containerSlots - } else emptyList() - } ?: emptyList() + if (isAccessed) mc.player?.currentScreenHandler?.containerSlots ?: emptyList() + else emptyList() override val description = buildText { literal("Chest at ") - highlighted(blockPos.toShortString()) + highlighted(pos.toShortString()) stash?.let { stash -> literal(" (contained in ") highlighted(stash.name) @@ -67,29 +62,24 @@ data class ChestContainer( override val isAccessed get() = lastInteractedBlockEntity?.let { blockEntity -> - blockEntity.pos == blockPos && + blockEntity.pos == pos && blockEntity.cachedState.block is ChestBlock && - mc.player?.currentScreenHandler?.type == ScreenHandlerType.GENERIC_9X3 + mc.player?.currentScreenHandler?.typeSafe == ScreenHandlerType.GENERIC_9X3 } ?: false @Ta5kBuilder context(automated: Automated) - override fun access() = AccessChestTask(automated) + override fun access() = + if (isAccessed) null + else AccessChestTask(automated) inner class AccessChestTask @Ta5kBuilder internal constructor( automated: Automated - ) : OpenContainerTask(description), Automated by automated { + ) : OpenContainerTask(description), Automated by automated { override fun SafeContext.onStart() { - taskOrNull { - if (isAccessed) null - else openContainer(blockPos).onSuccess { - success(OpenedChestContext()) - } - }.execute(this@AccessChestTask) + openContainer(pos) + .onSuccess { success(BasicOpenedContainerContext(::isAccessed)) } + .start() } } - - inner class OpenedChestContext : BasicOpenedContainerContext(::isAccessed) { - val blockPos = this@ChestContainer.blockPos - } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/DoubleChestContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/DoubleChestContainer.kt new file mode 100644 index 000000000..3a4a138dd --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/DoubleChestContainer.kt @@ -0,0 +1,95 @@ +/* + * 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.inventory.container.containers.external + +import com.lambda.Lambda.mc +import com.lambda.context.Automated +import com.lambda.context.SafeContext +import com.lambda.interaction.handler.handlers.ContainerHandler.lastInteractedBlockEntity +import com.lambda.interaction.inventory.container.BasicOpenedContainerContext +import com.lambda.interaction.inventory.container.ContainerRank +import com.lambda.interaction.inventory.container.OpenContainerTask +import com.lambda.interaction.inventory.container.PlacedContainer +import com.lambda.task.Task.Ta5kBuilder +import com.lambda.task.tasks.openContainer +import com.lambda.task.tasks.wrappers.withRecovery +import com.lambda.util.extension.containerSlots +import com.lambda.util.math.distSq +import com.lambda.util.player.SlotUtils.typeSafe +import com.lambda.util.text.buildText +import com.lambda.util.text.highlighted +import com.lambda.util.text.literal +import net.minecraft.block.ChestBlock +import net.minecraft.item.ItemStack +import net.minecraft.screen.ScreenHandlerType +import net.minecraft.screen.slot.Slot +import net.minecraft.util.math.BlockPos + +data class DoubleChestContainer( + override val pos: BlockPos, + val leftPos: BlockPos, + val rightPos: BlockPos, + override var stacks: List, + override val stash: StashContainer? = null +) : PlacedContainer(ContainerRank.Chest) { + override val slots + get(): List = + if (isAccessed) mc.player?.currentScreenHandler?.containerSlots ?: emptyList() + else emptyList() + + override val description = + buildText { + literal("Double Chest at ") + highlighted(leftPos.toShortString()) + literal(" and ") + highlighted(rightPos.toShortString()) + stash?.let { stash -> + literal(" (contained in ") + highlighted(stash.name) + literal(")") + } + } + + override val isAccessed + get() = lastInteractedBlockEntity?.let { blockEntity -> + (blockEntity.pos == leftPos || blockEntity.pos == rightPos) && + blockEntity.cachedState.block is ChestBlock && + mc.player?.currentScreenHandler?.typeSafe == ScreenHandlerType.GENERIC_9X6 + } ?: false + + @Ta5kBuilder + context(automated: Automated) + override fun access() = + if (isAccessed) null + else AccessChestTask(automated) + + inner class AccessChestTask @Ta5kBuilder internal constructor( + automated: Automated + ) : OpenContainerTask(description), Automated by automated { + override fun SafeContext.onStart() { + val leftDist = leftPos distSq player.pos + val rightDist = rightPos distSq player.pos + val closestPos = if (leftDist <= rightDist) leftPos else rightPos + val farthestPos = if (leftDist > rightDist) leftPos else rightPos + openContainer(closestPos) + .withRecovery { openContainer(farthestPos) } + .onSuccess { success(BasicOpenedContainerContext(::isAccessed)) } + .start() + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/EnderChestContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/EnderChestContainer.kt index e91df1cf8..38b15b9c4 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/EnderChestContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/EnderChestContainer.kt @@ -39,6 +39,7 @@ import com.lambda.task.tasks.wrappers.then import com.lambda.task.tasks.wrappers.thenOrNull import com.lambda.task.tasks.wrappers.withBranch import com.lambda.util.extension.containerSlots +import com.lambda.util.player.SlotUtils.typeSafe import com.lambda.util.text.buildText import com.lambda.util.text.literal import net.minecraft.block.Blocks @@ -61,42 +62,40 @@ object EnderChestContainer : Container(ContainerRank.EnderChest), ExternalContai get() = lastInteractedBlockEntity?.let { blockEntity -> blockEntity is EnderChestBlockEntity && - mc.player?.currentScreenHandler?.type == ScreenHandlerType.GENERIC_9X3 + mc.player?.currentScreenHandler?.typeSafe == ScreenHandlerType.GENERIC_9X3 } ?: false @Ta5kBuilder context(automated: Automated) - override fun access() = OpenEnderChestContainerTask(automated) + override fun access() = + if (isAccessed) null + else OpenEnderChestContainerTask(automated) class OpenEnderChestContainerTask @Ta5kBuilder internal constructor( automated: Automated ) : OpenContainerTask(description), Automated by automated { override fun SafeContext.onStart() { - taskOrNull { - if (isAccessed) null - else findBlock(Blocks.ENDER_CHEST, inventoryConfig.enderChestSearchRadius).withBranch( - onSuccess = { pos -> - openContainer(pos).onSuccess { - success(OpenedEnderChestContext(pos, false)) - } - }, - onFailure = { - acquireStack(Items.ENDER_CHEST.select(1)) - .then { placeContainer(it) } - .then { pos -> - openContainer(pos).onSuccess { - success(OpenedEnderChestContext(pos, true)) - } - } + findBlock(Blocks.ENDER_CHEST, inventoryConfig.enderChestSearchRadius).withBranch( + onSuccess = { pos -> + openContainer(pos).onSuccess { + success(OpenedEnderChestContext(null)) } - ) - }.execute(this@OpenEnderChestContainerTask) + }, + onFailure = { + acquireStack(Items.ENDER_CHEST.select(1)) + .then { placeContainer(it) } + .then { pos -> + openContainer(pos).onSuccess { + success(OpenedEnderChestContext(pos)) + } + } + } + ).start() } } class OpenedEnderChestContext( - val blockPos: BlockPos, - val placed: Boolean + val blockPos: BlockPos? ) : OpenedContainerContext { context(_: Automated) override fun close() = @@ -104,7 +103,7 @@ object EnderChestContainer : Container(ContainerRank.EnderChest), ExternalContai if (isAccessed) actionTask { player.closeScreen() } else null }.thenOrNull { - if (placed) breakAndCollect(blockPos) + if (blockPos != null) breakAndCollect(blockPos) else null } } diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/PlacedShulkerBoxContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/PlacedShulkerBoxContainer.kt index 224bb09bc..3bf8c2188 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/PlacedShulkerBoxContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/PlacedShulkerBoxContainer.kt @@ -22,14 +22,13 @@ import com.lambda.context.Automated import com.lambda.context.SafeContext import com.lambda.interaction.handler.handlers.ContainerHandler.lastInteractedBlockEntity import com.lambda.interaction.inventory.container.BasicOpenedContainerContext -import com.lambda.interaction.inventory.container.Container import com.lambda.interaction.inventory.container.ContainerRank import com.lambda.interaction.inventory.container.OpenContainerTask import com.lambda.interaction.inventory.container.PlacedContainer import com.lambda.task.Task.Ta5kBuilder import com.lambda.task.tasks.openContainer -import com.lambda.task.tasks.wrappers.taskOrNull import com.lambda.util.extension.containerSlots +import com.lambda.util.player.SlotUtils.typeSafe import com.lambda.util.text.buildText import com.lambda.util.text.highlighted import com.lambda.util.text.literal @@ -40,11 +39,11 @@ import net.minecraft.screen.slot.Slot import net.minecraft.util.math.BlockPos class PlacedShulkerBoxContainer( - val blockPos: BlockPos, + override val pos: BlockPos, val block: Block, override val stash: StashContainer?, override var stacks: List -) : Container(ContainerRank.PlacedShulkerBox), PlacedContainer { +) : PlacedContainer(ContainerRank.PlacedShulkerBox) { override val slots: List get() = if (isAccessed) mc.player?.currentScreenHandler?.containerSlots ?: emptyList() @@ -53,7 +52,7 @@ class PlacedShulkerBoxContainer( override val description = buildText { literal("Placed shulker box at ") - highlighted(blockPos.toShortString()) + highlighted(pos.toShortString()) stash?.let { stash -> literal(" (contained in ") highlighted(stash.name) @@ -63,25 +62,24 @@ class PlacedShulkerBoxContainer( override val isAccessed get() = lastInteractedBlockEntity?.let { blockEntity -> - blockEntity.pos == blockPos && + blockEntity.pos == pos && blockEntity.cachedState.block == block && - mc.player?.currentScreenHandler?.type == ScreenHandlerType.SHULKER_BOX + mc.player?.currentScreenHandler?.typeSafe == ScreenHandlerType.SHULKER_BOX } ?: false @Ta5kBuilder context(automated: Automated) - override fun access() = OpenPlacedShulkerBoxTask(automated) + override fun access() = + if (isAccessed) null + else OpenPlacedShulkerBoxTask(automated) inner class OpenPlacedShulkerBoxTask @Ta5kBuilder internal constructor( automated: Automated ) : OpenContainerTask(description), Automated by automated { override fun SafeContext.onStart() { - taskOrNull { - if (isAccessed) null - else openContainer(blockPos) - }.onSuccess { - success(BasicOpenedContainerContext(::isAccessed)) - }.execute(this@OpenPlacedShulkerBoxTask) + openContainer(pos) + .onSuccess { success(BasicOpenedContainerContext(::isAccessed)) } + .start() } } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ShulkerBoxContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ShulkerBoxContainer.kt index 78030e4cf..a179fe27d 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ShulkerBoxContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ShulkerBoxContainer.kt @@ -20,7 +20,6 @@ package com.lambda.interaction.inventory.container.containers.external import com.lambda.Lambda.mc import com.lambda.context.Automated import com.lambda.context.SafeContext -import com.lambda.interaction.handler.handlers.ContainerHandler.lastInteractedBlockEntity import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack import com.lambda.interaction.inventory.container.Container import com.lambda.interaction.inventory.container.ContainerRank @@ -42,18 +41,19 @@ import com.lambda.util.extension.containerSlots import com.lambda.util.text.buildText import com.lambda.util.text.highlighted import com.lambda.util.text.literal -import com.lambda.util.item.ItemStackUtils.shulkerBoxStacks -import net.minecraft.block.entity.ShulkerBoxBlockEntity +import net.minecraft.item.Item import net.minecraft.item.ItemStack -import net.minecraft.screen.ScreenHandlerType import net.minecraft.screen.slot.Slot import net.minecraft.util.math.BlockPos +import java.util.Objects.hash data class ShulkerBoxContainer( + val itemName: String, + val shulkerItem: Item, override var stacks: List, - val containedIn: Container, - override val slotCache: Slot, -) : Container(ContainerRank.ShulkerBox), ExternalContainer, NestedContainer { + override val containedIn: Container, + override val index: Int, +) : NestedContainer(ContainerRank.ShulkerBox), ExternalContainer { override val slots get(): List = if (isAccessed) mc.player?.currentScreenHandler?.containerSlots ?: emptyList() @@ -61,21 +61,13 @@ data class ShulkerBoxContainer( override val description = buildText { - highlighted(slotCache.stack.name.string) + highlighted(itemName) literal(" in ") highlighted(containedIn.name) - literal(" in slot $slotCache") + literal(" in slot index $index") } - private var blockPos: BlockPos? = null - - override val isAccessed - get() = - lastInteractedBlockEntity?.let { blockEntity -> - blockEntity is ShulkerBoxBlockEntity && - blockEntity.pos == blockPos && - mc.player?.currentScreenHandler?.type == ScreenHandlerType.SHULKER_BOX - } ?: false + override val isAccessed = false @Ta5kBuilder context(automated: Automated) @@ -86,7 +78,7 @@ data class ShulkerBoxContainer( ) : OpenContainerTask(description), Automated by automated { override fun SafeContext.onStart() { transfer( - selectStack { isSlot(slotCache) }, + selectStack { inIndex(index); isItem(shulkerItem) }, containedIn, HotbarContainer ).then { slot -> placeContainer(slot) } @@ -95,7 +87,7 @@ data class ShulkerBoxContainer( success(OpenedShulkerBoxContext(pos, containedIn)) } } - .execute(this@OpenShulkerBoxTask) + .start() } } @@ -103,10 +95,6 @@ data class ShulkerBoxContainer( val blockPos: BlockPos, val fromContainer: Container ) : OpenedContainerContext { - private val shulkerItem = slotCache.stack.item - private val customName = slotCache.stack.get(net.minecraft.component.DataComponentTypes.CUSTOM_NAME) - private val originalContents = slotCache.stack.shulkerBoxStacks - context(automated: Automated) override fun close() = taskOrNull { @@ -117,12 +105,19 @@ data class ShulkerBoxContainer( transfer( selectStack { isItem(shulkerItem) - if (customName != null) hasCustomName(customName) - sortedByBestContentMatch(originalContents) + hasCustomName(itemName) + sortedByBestContentMatch(stacks) }, HotbarAndInventoryContainer, fromContainer ) } } + + override fun hashCode() = hash(containedIn.hashCode(), index) + + override fun equals(other: Any?) = + other is ShulkerBoxContainer && + containedIn == other.containedIn && + index == other.index } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakInfo.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakInfo.kt index d1830a959..f789d0253 100644 --- a/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakInfo.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakInfo.kt @@ -21,7 +21,7 @@ import com.lambda.config.blocks.BreakConfig import com.lambda.config.blocks.BreakConfig.BreakMode import com.lambda.context.SafeContext import com.lambda.interaction.construction.simulation.context.BreakContext -import com.lambda.interaction.handler.handlers.breaking.RebreakHandler +import com.lambda.interaction.handler.handlers.breaking.RebreakPotential import com.lambda.interaction.manager.ActionInfo import com.lambda.interaction.manager.managers.breaking.BreakInfo.BreakType.Primary import com.lambda.interaction.manager.managers.breaking.BreakInfo.BreakType.Rebreak @@ -51,7 +51,7 @@ data class BreakInfo( // Pre Processing var shouldProgress = false - var rebreakPotential = RebreakHandler.RebreakPotential.None + var rebreakPotential = RebreakPotential.None var swapInfo = SwapInfo.EMPTY var swapStack: ItemStack = ItemStack.EMPTY @@ -163,7 +163,5 @@ data class BreakInfo( } } - fun getBreakDelay() = if (!bypassedDelay) 6 else breakConfig.breakDelay - override fun toString() = "$type, ${context.cachedState}, ${context.blockPos}" } diff --git a/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt b/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt index f2dc2b411..bff695058 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt @@ -27,7 +27,7 @@ import com.lambda.context.SafeContext import com.lambda.event.events.EntityEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.handler.handlers.ContainerHandler.move +import com.lambda.interaction.handler.handlers.move import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.interaction.inventory.container.containers.OffHandContainer diff --git a/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt b/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt index 1bd195dc3..1f949ba97 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt @@ -28,8 +28,8 @@ import com.lambda.interaction.handler.handlers.FriendHandler.isFriend import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer 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.start import com.lambda.task.tasks.build import com.lambda.util.BlockUtils.blockState import com.lambda.util.extension.shrinkByEpsilon @@ -78,7 +78,8 @@ object PlayerTrap : Module( player.eyePos.flooredBlockPos ).firstOrNull { friends || !isFriend(it.gameProfile) } ?: return@tickingBlueprint emptyMap() getTrapPositions(targetPlayer).associateWith { TargetState.Block(block) } - }.build(finishOnDone = false).run() + }.build(finishOnDone = false) + .start() } onDisable { task?.cancel(); task = null } } diff --git a/src/main/kotlin/com/lambda/module/modules/combat/Surround.kt b/src/main/kotlin/com/lambda/module/modules/combat/Surround.kt index 394a9b8f6..d54b1285d 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/Surround.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/Surround.kt @@ -27,8 +27,8 @@ import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryC import com.lambda.module.Module import com.lambda.module.modules.combat.PlayerTrap.getTrapPositions import com.lambda.module.tag.ModuleTag -import com.lambda.task.RootTask.run import com.lambda.task.Task +import com.lambda.task.start import com.lambda.task.tasks.build import com.lambda.util.item.ItemUtils.block import net.minecraft.block.Blocks @@ -65,7 +65,8 @@ object Surround : Module( getTrapPositions(player) .filter { it.y <= player.blockPos.y } .associateWith { TargetState.Block(block) } - }.build(finishOnDone = false).run() + }.build(finishOnDone = false) + .start() } onDisable { task?.cancel(); task = null } } diff --git a/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt b/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt index 3bb5ebe6f..d4b479b3c 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt @@ -22,7 +22,7 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.inventory.select import com.lambda.module.Module import com.lambda.module.tag.ModuleTag -import com.lambda.task.RootTask.run +import com.lambda.task.start import com.lambda.task.tasks.acquireStack import net.minecraft.item.Items @@ -38,9 +38,8 @@ object ContainerTest : Module( } onEnable { - acquireStack { - Items.OBSIDIAN.select() - }.run() + acquireStack { Items.OBSIDIAN.select() } + .start() } } } diff --git a/src/main/kotlin/com/lambda/module/modules/player/AutoEat.kt b/src/main/kotlin/com/lambda/module/modules/player/AutoEat.kt index e1e3af19c..6d2997dca 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/AutoEat.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/AutoEat.kt @@ -25,8 +25,8 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen 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.start import com.lambda.task.tasks.eat import com.lambda.task.tasks.wrappers.thenAction import com.lambda.threading.runSafeAutomated @@ -49,7 +49,9 @@ object AutoEat : Module( val reason = runSafeAutomated { reasonEating() } if (eatTask != null || !reason.shouldEat()) return@listen - eatTask = eat().thenAction { eatTask = null }.run() + eatTask = eat() + .thenAction { eatTask = null } + .start() } onDisable { diff --git a/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt b/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt index a3339ba7a..09ad928bc 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt @@ -23,21 +23,21 @@ import com.lambda.config.withEdits import com.lambda.event.events.InventoryEvent import com.lambda.event.events.PlayerEvent import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.handler.handlers.ContainerHandler +import com.lambda.interaction.inventory.container.OpenedContainerContext +import com.lambda.interaction.inventory.container.containers.external.EnderChestContainer 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.PlaceContainerTask -import com.lambda.task.tasks.breakAndCollect -import com.lambda.task.tasks.openContainer +import com.lambda.task.start import com.lambda.task.tasks.wrappers.then -import com.lambda.task.tasks.wrappers.thenAction import com.lambda.util.item.ItemUtils.shulkerBoxes import net.minecraft.item.Items import net.minecraft.screen.ScreenHandler import net.minecraft.screen.slot.SlotActionType -import net.minecraft.util.math.BlockPos +import java.util.* +@Suppress("unused") object InventoryTweaks : Module( name = "InventoryTweaks", tag = ModuleTag.PLAYER, @@ -45,10 +45,10 @@ object InventoryTweaks : Module( private val instantShulker by setting("Instant Shulker", true, description = "Right-click shulker boxes in your inventory to instantly place them and open them.") private val instantEChest by setting("Instant Ender-Chest", true, description = "Right-click ender chests in your inventory to instantly place them and open them.") - private var placedPos: BlockPos? = null - private var placeAndOpen: Task<*>? = null - private var lastBreak: Task<*>? = null - private var lastOpenScreen: ScreenHandler? = null + private var openTask: Task<*>? = null + private val openContexts = LinkedList() + var lastOpenScreen: ScreenHandler? = null + private var isClosing = false init { setDefaultAutomationConfig() @@ -56,32 +56,63 @@ object InventoryTweaks : Module( hideAllExcept(::breakConfig, ::interactConfig, ::inventoryConfig, ::hotbarConfig) } - listen { - if (it.action != SlotActionType.PICKUP || it.button != 1) return@listen - val slot = it.screenHandler.getSlot(it.slot) - if (!(instantShulker && slot.stack.item in shulkerBoxes) && !(instantEChest && slot.stack.item == Items.ENDER_CHEST)) return@listen - it.cancel() - lastOpenScreen = null - placeAndOpen = PlaceContainerTask(slot, this@InventoryTweaks).then { placePos -> - placedPos = placePos - openContainer(placePos).thenAction { screenHandler -> - lastOpenScreen = screenHandler + listen { event -> + if (event.action != SlotActionType.PICKUP || event.button != 1) return@listen + val slot = event.screenHandler.getSlot(event.slot) ?: return@listen + val stack = slot.stack + + when (stack.item) { + in shulkerBoxes if (!instantShulker) -> return@listen + Items.ENDER_CHEST if (!instantEChest) -> return@listen + } + + val targetContainer = + if (stack.item == Items.ENDER_CHEST) EnderChestContainer + else { + ContainerHandler.storedContainers.values + .asSequence() + .flatMap { it.values } + .firstOrNull { container -> container.index == slot.index } + ?: return@listen + } + + event.cancel() + + openTask = targetContainer + .access() + ?.onSuccess { ctx -> + openContexts.push(ctx) + lastOpenScreen = player.currentScreenHandler + openTask = null } - }.run() + ?.start() } listen { event -> - if (event.screenHandler != lastOpenScreen) return@listen - lastOpenScreen = null - placedPos?.let { - lastBreak = breakAndCollect(it).run() - placedPos = null + if (isClosing || openTask != null) return@listen + if (event.screenHandler != lastOpenScreen || openContexts.isEmpty()) return@listen + + isClosing = true + val contextsToClose = mutableListOf() + while (openContexts.isNotEmpty()) { + contextsToClose.add(openContexts.pop()) } + + val closeTask = + contextsToClose.fold?>(null) { acc, ctx -> + val task = ctx.close() ?: return@fold acc + acc?.then(task) ?: task + } + + closeTask + ?.onCompletion { isClosing = false } + ?.start() + ?: run { isClosing = false } } onDisable { - placeAndOpen?.cancel() - lastBreak?.cancel() + openContexts.clear() + isClosing = false } } } diff --git a/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt b/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt index 8bde75a8b..4fa82ba7f 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt @@ -47,8 +47,8 @@ import com.lambda.module.modules.world.AutoPortal.PosHandler.obiPositions import com.lambda.module.modules.world.AutoPortal.PosHandler.portalPositions import com.lambda.module.modules.world.AutoPortal.PosHandler.prevAnchorPos import com.lambda.module.tag.ModuleTag -import com.lambda.task.RootTask.run import com.lambda.task.Task +import com.lambda.task.start import com.lambda.task.tasks.build import com.lambda.task.tasks.wrappers.thenOrNull import com.lambda.threading.runSafe @@ -103,9 +103,9 @@ object AutoPortal : Module( .thenOrNull { if (light) LightTask(currAnchorPos.up(), walkIn) else null - }.onCompletion { - buildTask = null - }.run() + } + .onCompletion { buildTask = null } + .start() } private val corners by setting("Corners", false) private val light by setting("Light", true, "Attempts to automatically light the portal after building") diff --git a/src/main/kotlin/com/lambda/module/modules/world/AutoVillagerCycle.kt b/src/main/kotlin/com/lambda/module/modules/world/AutoVillagerCycle.kt index 75e1ba526..82751cce5 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/AutoVillagerCycle.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/AutoVillagerCycle.kt @@ -28,14 +28,14 @@ import com.lambda.event.events.PacketEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.construction.blueprint.Blueprint.Companion.toStructure -import com.lambda.interaction.construction.blueprint.StaticBlueprint.Companion.toBlueprint +import com.lambda.interaction.construction.blueprint.toBlueprint import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.sound.SoundHandler.playSound -import com.lambda.task.RootTask.run import com.lambda.task.Task +import com.lambda.task.start import com.lambda.task.tasks.build import com.lambda.task.tasks.wrappers.thenAction import com.lambda.threading.runSafeAutomated @@ -220,7 +220,7 @@ object AutoVillagerCycle : Module( .thenAction { switchState(CycleState.OpenVillager) } - .run() + .start() } switchState(CycleState.WaitLectern) } @@ -281,7 +281,7 @@ object AutoVillagerCycle : Module( lecternPos.toStructure(TargetState.Empty) .build(finishOnDone = true) .thenAction { switchState(CycleState.PlaceLectern) } - .run() + .start() } switchState(CycleState.WaitBreak) return diff --git a/src/main/kotlin/com/lambda/module/modules/world/HighwayTools.kt b/src/main/kotlin/com/lambda/module/modules/world/HighwayTools.kt index 0972a2a43..b3c89f8f1 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/HighwayTools.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/HighwayTools.kt @@ -26,8 +26,8 @@ import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.handler.handlers.BaritoneHandler 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.start import com.lambda.task.tasks.build import com.lambda.util.CommunicationUtils.info import com.lambda.util.Describable @@ -131,7 +131,7 @@ object HighwayTools : Module( emptyStructure() } }.build(collectDrops = buildConfig.collectDrops, lifeMaintenance = true) - .run() + .start() } private fun generateSlice(): Structure { diff --git a/src/main/kotlin/com/lambda/module/modules/world/Nuker.kt b/src/main/kotlin/com/lambda/module/modules/world/Nuker.kt index 026b3851d..6f79fafb3 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/Nuker.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/Nuker.kt @@ -26,8 +26,8 @@ import com.lambda.interaction.construction.blueprint.TickingBlueprint.Companion. import com.lambda.interaction.construction.verify.TargetState 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.start import com.lambda.task.tasks.build import com.lambda.util.BlockUtils.blockPos import com.lambda.util.PlayerBuildLayerUtils.FlattenMode @@ -98,7 +98,7 @@ object Nuker : Module( selection }.build(finishOnDone = false, async = async) - .run() + .start() } private fun SafeContext.isWithinDigDirection(pos: BlockPos): Boolean { diff --git a/src/main/kotlin/com/lambda/module/modules/world/Printer.kt b/src/main/kotlin/com/lambda/module/modules/world/Printer.kt index f2e2332e8..6b8639976 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/Printer.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/Printer.kt @@ -34,8 +34,8 @@ import com.lambda.interaction.construction.simulation.result.results.InteractRes import com.lambda.interaction.construction.verify.TargetState 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.start import com.lambda.task.tasks.build import com.lambda.util.BlockUtils.blockPos import com.lambda.util.CommunicationUtils.logError @@ -149,7 +149,8 @@ object Printer : Module( .filter { DataManager.getRenderLayerRange().isPositionWithinRange(it) && inSchematic(it) } .associateWith { TargetState.State(schematicWorld.getBlockState(it)) } .filter { air || !it.value.blockState.isAir } - }.build(finishOnDone = false, async = async) { filterBuildResults(it) }.run() + }.build(finishOnDone = false, async = async) { filterBuildResults(it) } + .start() } /** 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 0bdd2d006..e3b98d24f 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt @@ -52,8 +52,8 @@ import com.lambda.interaction.manager.managers.rotating.RotationManager import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest 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.start import com.lambda.task.tasks.build import com.lambda.task.tasks.openContainer import com.lambda.task.tasks.wrappers.thenAction @@ -361,7 +361,7 @@ object StashMover : Module( ) task = null } - .run() + .start() } fun pauseUnpause() { diff --git a/src/main/kotlin/com/lambda/task/RootTask.kt b/src/main/kotlin/com/lambda/task/RootTask.kt index fac5f6208..db4abda1e 100644 --- a/src/main/kotlin/com/lambda/task/RootTask.kt +++ b/src/main/kotlin/com/lambda/task/RootTask.kt @@ -17,21 +17,12 @@ package com.lambda.task -import com.lambda.threading.runSafe +import com.lambda.task.Task.Ta5kBuilder object RootTask : Task() { override val name get() = "Root Task" - - @Ta5kBuilder - inline fun > T.run(): T { - execute(this@RootTask) - return this - } - - @Ta5kBuilder - fun Task<*>.run(task: TaskGenerator) { - runSafe { - task(Unit).execute(this@run) - } - } } + +@Ta5kBuilder +inline fun > T.start(): T = + apply { execute(RootTask) } diff --git a/src/main/kotlin/com/lambda/task/Task.kt b/src/main/kotlin/com/lambda/task/Task.kt index 3ae287ed5..e17939859 100644 --- a/src/main/kotlin/com/lambda/task/Task.kt +++ b/src/main/kotlin/com/lambda/task/Task.kt @@ -28,21 +28,22 @@ import com.lambda.task.tasks.wrappers.withRecovery import com.lambda.threading.runSafe import com.lambda.util.CommunicationUtils.logError import com.lambda.util.Nameable -import com.lambda.util.StringUtils.capitalize import kotlin.time.Duration.Companion.milliseconds import kotlin.time.DurationUnit import kotlin.time.toDuration @Suppress("unused") abstract class Task : Nameable, Muteable { - var parent: Task<*>? = null - val subTasks = mutableListOf>() var state = State.Init - override val isMuted: Boolean get() = state == State.Paused || state == State.Init var age = 0 + + var parent: Task<*>? = null + val subTasks = mutableListOf>() private val depth: Int get() = parent?.depth?.plus(1) ?: 0 val size: Int get() = subTasks.sumOf { it.size } + 1 + override val isMuted: Boolean get() = state == State.Paused || state == State.Init + private val successCallbacks = mutableListOf Unit>() private val completionCallbacks = mutableListOf Unit>() private val failureCallbacks = mutableListOf Unit>() @@ -59,8 +60,6 @@ abstract class Task : Nameable, Muteable { Cancelled, Failed, Completed; - - val display get() = name.lowercase().capitalize() } init { @@ -98,6 +97,11 @@ abstract class Task : Nameable, Muteable { @Ta5kBuilder protected open fun SafeContext.onCancel() {} + @Ta5kBuilder + context(parentTask: Task<*>) + fun start(pauseParent: Boolean = true) = + this.execute(parentTask, pauseParent) + /** * Executes the current task as a subtask of the specified owner task. * @@ -127,8 +131,10 @@ abstract class Task : Nameable, Muteable { @Ta5kBuilder protected fun success(result: Result) { + if (state != State.Running && state != State.Paused) return unsubscribe() state = State.Completed + cancelSubTasks() parent?.onSubTaskSuccess(this) parent?.onSubTaskCompletion(this) @@ -151,15 +157,16 @@ abstract class Task : Nameable, Muteable { e: Throwable, stacktrace: MutableList> = mutableListOf(), ) { - state = State.Failed + if (state != State.Running && state != State.Failed) return unsubscribe() + state = State.Failed cancelSubTasks() stacktrace.add(this) parent?.onSubTaskFailure(this, e) ?: run { if (!verboseDebug) return@run - val message = + logError( buildString { val first = stacktrace.firstOrNull() ?: return@buildString append("${first.name} failed: ${e.message}\n") @@ -167,7 +174,7 @@ abstract class Task : Nameable, Muteable { append(" -> ${it.name}\n") } } - logError(message) + ) } parent?.onSubTaskCompletion(this) @@ -182,38 +189,40 @@ abstract class Task : Nameable, Muteable { @Ta5kBuilder protected fun failure(message: String) = failure(IllegalStateException(message)) - @Ta5kBuilder - fun activate() { - if (state != State.Paused) return - state = State.Running - } - - @Ta5kBuilder - fun pause() { - if (state != State.Running) return - state = State.Paused - } - @Ta5kBuilder fun cancel() = internalCancel(true) + @Ta5kBuilder private fun internalCancel(removeFromParent: Boolean = true) { + if (state != State.Running && state != State.Paused) return + if (this is RootTask) return unsubscribe() - runSafe { onCancel() } + state = State.Cancelled cancelSubTasks() + runSafe { onCancel() } + if (removeFromParent) parent?.subTasks?.remove(this) parent?.activate() - if (this is RootTask) return - if (state == State.Completed || state == State.Cancelled) return - state = State.Cancelled } @Ta5kBuilder - fun cancelSubTasks() { + private fun cancelSubTasks() { subTasks.forEach { it.internalCancel(removeFromParent = false) } subTasks.clear() } + @Ta5kBuilder + fun activate() { + if (state != State.Paused) return + state = State.Running + } + + @Ta5kBuilder + fun pause() { + if (state != State.Running) return + state = State.Paused + } + @Ta5kBuilder protected open fun onSubTaskSuccess(subTask: Task<*>) { activate() @@ -265,7 +274,7 @@ abstract class Task : Nameable, Muteable { private fun StringBuilder.appendTaskTree(task: Task<*>, level: Int = 0, maxEntries: Int = 10) { if (task.state == State.Cancelled) return - appendLine("${" ".repeat(level * 4)}${task.name}" + if (task !is RootTask) " [${task.state.display}] ${(task.age * 50).milliseconds}" else "") + appendLine("${" ".repeat(level * 4)}${task.name}" + if (task !is RootTask) " [${task.state.name}] ${(task.age * 50).milliseconds}" else "") val left = task.subTasks.size - maxEntries if (left > 0) { appendLine("${" ".repeat((level + 1) * 4)}...and $left more tasks") diff --git a/src/main/kotlin/com/lambda/task/tasks/AcquirePlacedBlockTask.kt b/src/main/kotlin/com/lambda/task/tasks/AcquirePlacedBlockTask.kt index d8c15f21a..1bef1abf5 100644 --- a/src/main/kotlin/com/lambda/task/tasks/AcquirePlacedBlockTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/AcquirePlacedBlockTask.kt @@ -21,7 +21,7 @@ import com.lambda.context.Automated import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.handler.handlers.ContainerHandler.findSlots +import com.lambda.interaction.handler.handlers.findSlots import com.lambda.interaction.inventory.select import com.lambda.task.Task import com.lambda.task.Task.Ta5kBuilder diff --git a/src/main/kotlin/com/lambda/task/tasks/AcquireStackTask.kt b/src/main/kotlin/com/lambda/task/tasks/AcquireStackTask.kt index 4810c7975..e50591500 100644 --- a/src/main/kotlin/com/lambda/task/tasks/AcquireStackTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/AcquireStackTask.kt @@ -19,8 +19,7 @@ package com.lambda.task.tasks import com.lambda.context.Automated import com.lambda.context.SafeContext -import com.lambda.interaction.handler.handlers.ContainerHandler -import com.lambda.interaction.handler.handlers.ContainerHandler.findContainer +import com.lambda.interaction.handler.handlers.findContainer import com.lambda.interaction.inventory.StackSelection import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.task.Task @@ -50,8 +49,10 @@ class AcquireStackTask @Ta5kBuilder internal constructor( selection.findContainer() ?.let { transfer(selection, it, HotbarContainer) } ?.onSuccess { slot -> success(slot) } - ?.execute(this@AcquireStackTask) - ?: failure(ContainerHandler.NoContainerFound(selection)) // ToDo: Create crafting path + ?.start() + ?: failure(NoContainerFound(selection)) // ToDo: Create crafting path } } } + +class NoContainerFound(selection: StackSelection) : Exception("No container found matching $selection") diff --git a/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt b/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt index fc6ee0651..8f64d39b1 100644 --- a/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt @@ -28,8 +28,8 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.construction.blueprint.Blueprint import com.lambda.interaction.construction.blueprint.Blueprint.Companion.toStructure import com.lambda.interaction.construction.blueprint.PropagatingBlueprint -import com.lambda.interaction.construction.blueprint.StaticBlueprint.Companion.toBlueprint import com.lambda.interaction.construction.blueprint.TickingBlueprint +import com.lambda.interaction.construction.blueprint.toBlueprint import com.lambda.interaction.construction.simulation.BuildGoal import com.lambda.interaction.construction.simulation.Simulation.Companion.simulation import com.lambda.interaction.construction.simulation.context.BuildContext @@ -51,6 +51,7 @@ import com.lambda.interaction.manager.managers.interacting.interactRequest import com.lambda.module.modules.client.Client import com.lambda.task.Task import com.lambda.task.Task.Ta5kBuilder +import com.lambda.task.tasks.wrappers.softFail import com.lambda.task.tasks.wrappers.thenAction import com.lambda.threading.runConcurrent import com.lambda.threading.runSafe @@ -347,6 +348,8 @@ class BuildTask @Ta5kBuilder internal constructor( is Resolvable -> { LOG.info("Resolving: ${result.name}") result.resolve() + .softFail() + .start() } } } diff --git a/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt b/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt index a3e7f5c45..7287e2c89 100644 --- a/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt @@ -52,11 +52,12 @@ class CollectDropsTask @Ta5kBuilder internal constructor( override fun SafeContext.onStart() { listen { runSafeAutomated { - val target = drops.firstOrNull() ?: run { - BaritoneHandler.cancel() - success() - return@listen - } + val target = + drops.firstOrNull() ?: run { + BaritoneHandler.cancel() + success() + return@listen + } if (!world.entities.contains(target)) { drops.remove(target) @@ -65,12 +66,13 @@ class CollectDropsTask @Ta5kBuilder internal constructor( } if (HotbarAndInventoryContainer.stacks.none { it.isEmpty }) { - val stackToThrow = player.currentScreenHandler.playerSlots.firstOrNull { - it.stack.item in inventoryConfig.disposables - } ?: run { - failure("Inventory is full and no disposable items to throw") - return@listen - } + val stackToThrow = + player.currentScreenHandler.playerSlots.firstOrNull { + it.stack.item in inventoryConfig.disposables + } ?: run { + failure("Inventory is full and no disposable items to throw") + return@listen + } inventoryRequest { throwStack(stackToThrow.id) }.submit() diff --git a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt index 52a667374..65d05f7d6 100644 --- a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt @@ -18,9 +18,10 @@ package com.lambda.task.tasks import com.lambda.context.Automated +import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.handler.handlers.ContainerHandler.findContainer +import com.lambda.interaction.handler.handlers.findContainer import com.lambda.interaction.inventory.StackSelection import com.lambda.interaction.inventory.container.Container import com.lambda.interaction.inventory.container.ExternalContainer @@ -28,7 +29,9 @@ import com.lambda.interaction.inventory.container.OpenedContainerContext import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer import com.lambda.task.Task import com.lambda.task.Task.Ta5kBuilder +import com.lambda.task.tasks.wrappers.taskOrNull import com.lambda.task.tasks.wrappers.then +import com.lambda.task.tasks.wrappers.thenOrNull import com.lambda.threading.runSafeAutomated import net.minecraft.screen.slot.Slot @@ -63,32 +66,27 @@ class ContainerTransferTask @Ta5kBuilder internal constructor( ) : Task(), Automated by automated { override val name = "Transferring $selection from $fromContainer to $toContainer" - init { - listen { - runSafeAutomated { - when { - fromContainer.isAccessed && toContainer.isAccessed -> - TransferTask() - .onSuccess { success(it) } - - fromContainer !is ExternalContainer -> - openTransferClose(toContainer, fromContainer, toContainer) - - toContainer !is ExternalContainer -> - openTransferClose(fromContainer, fromContainer, toContainer) - - else -> - fromContainer - .access() - .then { fromCtx -> - transfer(selection, fromContainer, HotbarAndInventoryContainer) - .then { fromCtx.close() } - } - .then { toContainer.access() } - .then { toCtx -> transferAndClose(toCtx, HotbarAndInventoryContainer, toContainer) } - }.execute(this@ContainerTransferTask) - } - } + override fun SafeContext.onStart() { + when { + fromContainer.isAccessed && toContainer.isAccessed -> + TransferTask() + .onSuccess { success(it) } + + fromContainer !is ExternalContainer -> + openTransferClose(toContainer, fromContainer, toContainer) + + toContainer !is ExternalContainer -> + openTransferClose(fromContainer, fromContainer, toContainer) + + else -> + taskOrNull { fromContainer.access() } + .then { fromCtx -> + transfer(selection, fromContainer, HotbarAndInventoryContainer) + .thenOrNull { fromCtx?.close() } + } + .thenOrNull { toContainer.access() } + .then { toCtx -> transferAndClose(toCtx, HotbarAndInventoryContainer, toContainer) } + }?.start() } /** @@ -96,20 +94,22 @@ class ContainerTransferTask @Ta5kBuilder internal constructor( * * Used when exactly one of the two containers is external and needs to be accessed. */ + @Ta5kBuilder private fun openTransferClose(containerToOpen: Container, from: Container, to: Container) = containerToOpen .access() - .then { ctx -> transferAndClose(ctx, from, to) } + ?.then { ctx -> transferAndClose(ctx, from, to) } /** * Transfers [selection] from [from] to [to], then closes the container via [ctx]. */ - private fun transferAndClose(ctx: OpenedContainerContext, from: Container, to: Container) = + @Ta5kBuilder + private fun transferAndClose(ctx: OpenedContainerContext?, from: Container, to: Container) = transfer(selection, from, to) - .then { slot -> + .thenOrNull { slot -> ctx - .close() - .onSuccess { success(slot) } + ?.close() + ?.onSuccess { success(slot) } } private inner class TransferTask @Ta5kBuilder constructor() : Task() { diff --git a/src/main/kotlin/com/lambda/task/tasks/PlaceContainerTask.kt b/src/main/kotlin/com/lambda/task/tasks/PlaceContainerTask.kt index 92a451718..9311f9a5b 100644 --- a/src/main/kotlin/com/lambda/task/tasks/PlaceContainerTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/PlaceContainerTask.kt @@ -20,7 +20,7 @@ package com.lambda.task.tasks import com.lambda.context.Automated import com.lambda.context.SafeContext import com.lambda.interaction.construction.blueprint.Blueprint.Companion.toStructure -import com.lambda.interaction.construction.blueprint.StaticBlueprint.Companion.toBlueprint +import com.lambda.interaction.construction.blueprint.toBlueprint import com.lambda.interaction.construction.simulation.result.results.GenericResult import com.lambda.interaction.construction.simulation.result.results.InteractResult import com.lambda.interaction.construction.simulation.sim @@ -28,7 +28,6 @@ import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.manager.ManagerUtils import com.lambda.task.Task import com.lambda.task.Task.Ta5kBuilder -import com.lambda.task.tasks.wrappers.thenAction import com.lambda.threading.runSafeAutomated import com.lambda.util.BlockUtils.blockPos import com.lambda.util.item.ItemUtils.shulkerBoxes @@ -88,8 +87,8 @@ class PlaceContainerTask @Ta5kBuilder internal constructor( .toStructure(TargetState.Stack(slot.stack)) .toBlueprint() .build(finishOnDone = true, collectDrops = false) - .thenAction { success(containerPosition) } - .execute(this@PlaceContainerTask) + .onSuccess { success(containerPosition) } + .start() } private fun SafeContext.canBeOpened( diff --git a/src/main/kotlin/com/lambda/util/BlockUtils.kt b/src/main/kotlin/com/lambda/util/BlockUtils.kt index 5da12fb6b..6ae3b7a78 100644 --- a/src/main/kotlin/com/lambda/util/BlockUtils.kt +++ b/src/main/kotlin/com/lambda/util/BlockUtils.kt @@ -273,9 +273,8 @@ object BlockUtils { fun SafeContext.blockEntity(pos: BlockPos) = world.getBlockEntity(pos) fun BlockState.matches(state: BlockState, ignoredProperties: Collection> = emptySet()) = - this.block == state.block && this.properties.all { - this[it] == state[it] || it in ignoredProperties - } + this.block == state.block && + this.properties.all { this[it] == state[it] || it in ignoredProperties } fun SafeContext.instantBreakable(blockState: BlockState, blockPos: BlockPos, breakThreshold: Float): Boolean { val ticksNeeded = 1 / (blockState.calcBlockBreakingDelta(player, world, blockPos) / breakThreshold) diff --git a/src/main/kotlin/com/lambda/util/player/SlotUtils.kt b/src/main/kotlin/com/lambda/util/player/SlotUtils.kt index c8cece603..740520939 100644 --- a/src/main/kotlin/com/lambda/util/player/SlotUtils.kt +++ b/src/main/kotlin/com/lambda/util/player/SlotUtils.kt @@ -22,6 +22,8 @@ import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen import net.minecraft.client.network.ClientPlayerEntity import net.minecraft.entity.player.PlayerInventory import net.minecraft.item.ItemStack +import net.minecraft.screen.ScreenHandler +import net.minecraft.screen.ScreenHandlerType import net.minecraft.screen.slot.ArmorSlot import net.minecraft.screen.slot.Slot import net.minecraft.screen.slot.SlotActionType @@ -58,6 +60,9 @@ object SlotUtils { val ClientPlayerEntity.inventoryStacks: List get() = inventory.mainStacks.slice(9..35) val ClientPlayerEntity.hotbarAndInventoryStacks: List get() = inventory.mainStacks + val ScreenHandler.typeSafe: ScreenHandlerType<*>? + get() = try { this.type } catch (_: Throwable) { null } + fun SafeContext.clickSlot(slotId: Int, button: Int, actionType: SlotActionType) { val syncId = player.currentScreenHandler?.syncId ?: return interaction.clickSlot(syncId, slotId, button, actionType, player) @@ -68,6 +73,5 @@ object SlotUtils { inventory::class == slot.inventory::class && id == slot.id && x == slot.x && - y == slot.y && - ItemStack.areEqual(stack, slot.stack) + y == slot.y } From 415223087b2acc8ede9e5a88303d40481fc7ebf3 Mon Sep 17 00:00:00 2001 From: beanbag44 <107891830+beanbag44@users.noreply.github.com> Date: Tue, 1 Sep 2026 11:42:23 +0100 Subject: [PATCH 12/43] fixes n stuff --- .../handler/handlers/ContainerHandler.kt | 42 ++++++++++++++----- .../inventory/container/Container.kt | 2 + .../module/modules/player/InventoryTweaks.kt | 8 ++-- src/main/kotlin/com/lambda/task/Task.kt | 16 +++---- 4 files changed, 45 insertions(+), 23 deletions(-) diff --git a/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt index ced2c4d1c..c21085f12 100644 --- a/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt @@ -30,7 +30,6 @@ import com.lambda.interaction.inventory.ContainerMarker import com.lambda.interaction.inventory.ContainerSelection import com.lambda.interaction.inventory.StackSelection import com.lambda.interaction.inventory.container.Container -import com.lambda.interaction.inventory.container.NestedContainer import com.lambda.interaction.inventory.container.PlacedContainer import com.lambda.interaction.inventory.container.containers.external.ChestContainer import com.lambda.interaction.inventory.container.containers.external.DoubleChestContainer @@ -62,13 +61,20 @@ import net.minecraft.util.math.ChunkPos @Suppress("unused") object ContainerHandler : Loadable { private val containers: Sequence - get() = compileContainers.asSequence() + - placedContainers.values.asSequence().flatMap { it.values } + - storedContainers.values.asSequence().flatMap { it.values } + get() { + fun Container.allNested(): Sequence = + sequence { + val nested = storedContainers.values + yieldAll(nested) + nested.forEach { yieldAll(it.allNested()) } + } + val baseContainers = compileContainers.asSequence() + + placedContainers.values.asSequence().flatMap { it.values } + return baseContainers + baseContainers.flatMap { it.allNested() } + } - val compileContainers = getInstances() + val compileContainers = getInstances() val placedContainers = mutableMapOf>() - val storedContainers = mutableMapOf>() context(automated: Automated) val filteredContainers @@ -80,20 +86,32 @@ object ContainerHandler : Loadable { get() = containers.sorted() var lastInteractedBlockEntity: BlockEntity? = null + var pendingInteractedBlockEntity: BlockEntity? = null override fun load() = "Loaded ${compileContainers.size} containers" init { listen { event -> val packet = event.packet as? PlayerInteractBlockC2SPacket ?: return@listen - lastInteractedBlockEntity = blockEntity(packet.blockHitResult.blockPos) + val entity = blockEntity(packet.blockHitResult.blockPos) + pendingInteractedBlockEntity = entity + lastInteractedBlockEntity = entity } listen { event -> val sh = event.screenHandler onContainerUpdate(sh) - if (sh.syncId != 0) lastInteractedBlockEntity = null + if (sh.syncId != 0 && pendingInteractedBlockEntity == null) { + lastInteractedBlockEntity = null + } + } + + listen { + if (it.screenHandler.syncId != 0) { + pendingInteractedBlockEntity = null + } } + listen { onContainerUpdate() } listen { onContainerUpdate() } @@ -198,12 +216,11 @@ object ContainerHandler : Loadable { } private fun Container.scanContainerContents() { - val storageCache = storedContainers.getOrPut(this) { mutableMapOf() } slots.forEach { slot -> val stack = slot.stack val index = slot.index if (stack.item in shulkerBoxes) { - storageCache[index] = + storedContainers[index] = ShulkerBoxContainer( stack.name.string, stack.item, @@ -211,14 +228,17 @@ object ContainerHandler : Loadable { this, index ).also { it.scanContainerContents() } - } else storageCache.remove(index) + } else storedContainers.remove(index) } } private fun getPlacedContainer(pos: BlockPos) = placedContainers[ChunkPos(pos)]?.get(pos) + private fun ChunkPos.getPlacedContainer(pos: BlockPos) = placedContainers[this]?.get(pos) + private fun setPlacedContainer(pos: BlockPos, container: PlacedContainer) = placedContainers.getOrPut(ChunkPos(pos)) { mutableMapOf() }.put(pos, container) + private fun ChunkPos.setPlacedContainer(pos: BlockPos, container: PlacedContainer) = placedContainers.getOrPut(this) { mutableMapOf() }.put(pos, container) diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/Container.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/Container.kt index c652128ef..cc2889eec 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/Container.kt +++ b/src/main/kotlin/com/lambda/interaction/inventory/container/Container.kt @@ -51,6 +51,8 @@ abstract class Container( abstract val slots: List abstract var stacks: List + val storedContainers = mutableMapOf() + open val swapMethodPriority = 0 abstract val description: Text diff --git a/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt b/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt index 09ad928bc..ce8c6a92d 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt @@ -24,6 +24,7 @@ import com.lambda.event.events.InventoryEvent import com.lambda.event.events.PlayerEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.handler.handlers.ContainerHandler +import com.lambda.interaction.inventory.container.NestedContainer import com.lambda.interaction.inventory.container.OpenedContainerContext import com.lambda.interaction.inventory.container.containers.external.EnderChestContainer import com.lambda.module.Module @@ -69,9 +70,8 @@ object InventoryTweaks : Module( val targetContainer = if (stack.item == Items.ENDER_CHEST) EnderChestContainer else { - ContainerHandler.storedContainers.values - .asSequence() - .flatMap { it.values } + ContainerHandler.allContainers + .filterIsInstance() .firstOrNull { container -> container.index == slot.index } ?: return@listen } @@ -85,7 +85,7 @@ object InventoryTweaks : Module( lastOpenScreen = player.currentScreenHandler openTask = null } - ?.start() + openTask?.start() } listen { event -> diff --git a/src/main/kotlin/com/lambda/task/Task.kt b/src/main/kotlin/com/lambda/task/Task.kt index e17939859..599c84373 100644 --- a/src/main/kotlin/com/lambda/task/Task.kt +++ b/src/main/kotlin/com/lambda/task/Task.kt @@ -109,20 +109,20 @@ abstract class Task : Nameable, Muteable { * logs the execution details, and invokes the necessary lifecycle hooks. Additionally, * it manages the state of the parent task and starts any required listeners for execution. * - * @param owner The parent task that will execute this task as a sub task. Must not be the same as this task. + * @param parent The parent task that will execute this task as a sub task. Must not be the same as this task. * @param pauseParent Defines whether the parent task should be paused during the execution of this task. Defaults to `true`. * @return The current task instance as a `Task` to support chaining or further configuration. * @throws IllegalArgumentException if the owner task is the same as the task being executed. */ @Ta5kBuilder - fun execute(owner: Task<*>, pauseParent: Boolean = true): Task { - require(owner != this) { "Cannot execute a task as a sub task of itself" } - owner.subTasks.add(this) - parent = owner - if (verboseDebug) LOG.info("${owner.name} started $name") + fun execute(parent: Task<*>, pauseParent: Boolean = true): Task { + require(parent != this) { "Cannot execute a task as a sub task of itself" } + parent.subTasks.add(this) + this@Task.parent = parent + if (verboseDebug) LOG.info("${parent.name} started $name") if (pauseParent) { - if (verboseDebug) LOG.info("$name pausing parent ${owner.name}") - if (owner !is RootTask) owner.pause() + if (verboseDebug) LOG.info("$name pausing parent ${parent.name}") + if (parent !is RootTask) parent.pause() } state = State.Running runSafe { runCatching { onStart() }.onFailure { failure(it) } } From e393250f9b835464c57c5388291ea75deadfc4e3 Mon Sep 17 00:00:00 2001 From: beanbag44 <107891830+beanbag44@users.noreply.github.com> Date: Thu, 3 Sep 2026 11:42:57 +0100 Subject: [PATCH 13/43] remove inventory package and move container up --- .../command/commands/TransferCommand.kt | 2 +- .../com/lambda/config/blocks/EatConfig.kt | 4 +- .../lambda/config/blocks/InventoryConfig.kt | 24 +- .../lambda/config/blocks/InventorySettings.kt | 6 +- .../com/lambda/event/events/ContainerEvent.kt | 2 +- .../simulation/context/BreakContext.kt | 2 +- .../simulation/result/results/BreakResult.kt | 4 +- .../result/results/GenericResult.kt | 4 +- .../construction/simulation/sims/BreakSim.kt | 12 +- .../simulation/sims/InteractSim.kt | 10 +- .../construction/verify/TargetState.kt | 6 +- .../{inventory => }/container/Container.kt | 16 +- .../ContainerMarker.kt | 2 +- .../ContainerType.kt} | 8 +- .../container/ExternalContainer.kt | 2 +- .../container/NestedContainer.kt | 4 +- .../container/OpenContainerTask.kt | 2 +- .../container/OpenedContainerContext.kt | 2 +- .../container/PlacedContainer.kt | 6 +- .../container/containers/ArmorContainer.kt | 8 +- .../container/containers/CreativeContainer.kt | 10 +- .../containers/CursorContainer.kt} | 18 +- .../container/containers/HotbarContainer.kt | 8 +- .../containers/InventoryContainer.kt | 8 +- .../container/containers/OffHandContainer.kt | 8 +- .../containers/external/ChestContainer.kt | 14 +- .../external/DoubleChestContainer.kt | 12 +- .../external/EnderChestContainer.kt | 16 +- .../external/PlacedShulkerBoxContainer.kt | 12 +- .../external/ShulkerBoxContainer.kt | 27 +- .../containers/external/StashContainer.kt | 10 +- .../selection}/ContainerSelection.kt | 64 ++++- .../selection}/StackSelection.kt | 24 +- .../handler/handlers/ContainerHandler.kt | 104 ++++---- .../handler/handlers/GlideHandler.kt | 11 +- .../containers/HotbarAndInventoryContainer.kt | 38 --- .../container/containers/MainHandContainer.kt | 46 ---- .../manager/managers/breaking/BreakManager.kt | 2 +- .../lambda/module/modules/combat/AutoArmor.kt | 3 +- .../lambda/module/modules/combat/AutoTotem.kt | 3 +- .../module/modules/combat/CrystalAura.kt | 7 +- .../lambda/module/modules/combat/KillAura.kt | 4 +- .../module/modules/combat/PlayerTrap.kt | 1 - .../lambda/module/modules/combat/Surround.kt | 1 - .../module/modules/debug/ContainerTest.kt | 2 +- .../module/modules/movement/BetterFirework.kt | 9 +- .../movement/elytrafly/ElytraFlyMode.kt | 3 +- .../elytrafly/modes/GrimControlElytraFly.kt | 6 +- .../module/modules/player/AutoElytraSwap.kt | 3 +- .../module/modules/player/InventoryTweaks.kt | 11 +- .../module/modules/player/StackReplenish.kt | 4 +- .../lambda/module/modules/player/ToolSaver.kt | 4 +- .../module/modules/render/ContainerPreview.kt | 10 +- .../lambda/module/modules/world/AutoPortal.kt | 5 +- .../module/modules/world/MapDownloader.kt | 1 - .../lambda/module/modules/world/Scaffold.kt | 4 +- .../lambda/module/modules/world/StashMover.kt | 9 +- .../task/tasks/AcquirePlacedBlockTask.kt | 2 +- .../com/lambda/task/tasks/AcquireStackTask.kt | 4 +- .../com/lambda/task/tasks/CollectDropsTask.kt | 1 - .../task/tasks/ContainerTransferTask.kt | 130 +++++---- .../kotlin/com/lambda/task/tasks/EatTask.kt | 4 +- .../lambda/task/tasks/PlaceContainerTask.kt | 4 +- .../kotlin/com/lambda/util/item/ItemUtils.kt | 249 +++++++++--------- 64 files changed, 514 insertions(+), 528 deletions(-) rename src/main/kotlin/com/lambda/interaction/{inventory => }/container/Container.kt (90%) rename src/main/kotlin/com/lambda/interaction/{inventory => container}/ContainerMarker.kt (94%) rename src/main/kotlin/com/lambda/interaction/{inventory/container/ContainerRank.kt => container/ContainerType.kt} (87%) rename src/main/kotlin/com/lambda/interaction/{inventory => }/container/ExternalContainer.kt (93%) rename src/main/kotlin/com/lambda/interaction/{inventory => }/container/NestedContainer.kt (84%) rename src/main/kotlin/com/lambda/interaction/{inventory => }/container/OpenContainerTask.kt (96%) rename src/main/kotlin/com/lambda/interaction/{inventory => }/container/OpenedContainerContext.kt (95%) rename src/main/kotlin/com/lambda/interaction/{inventory => }/container/PlacedContainer.kt (77%) rename src/main/kotlin/com/lambda/interaction/{inventory => }/container/containers/ArmorContainer.kt (84%) rename src/main/kotlin/com/lambda/interaction/{inventory => }/container/containers/CreativeContainer.kt (89%) rename src/main/kotlin/com/lambda/interaction/{inventory/container/containers/PlayerContainer.kt => container/containers/CursorContainer.kt} (62%) rename src/main/kotlin/com/lambda/interaction/{inventory => }/container/containers/HotbarContainer.kt (86%) rename src/main/kotlin/com/lambda/interaction/{inventory => }/container/containers/InventoryContainer.kt (83%) rename src/main/kotlin/com/lambda/interaction/{inventory => }/container/containers/OffHandContainer.kt (85%) rename src/main/kotlin/com/lambda/interaction/{inventory => }/container/containers/external/ChestContainer.kt (85%) rename src/main/kotlin/com/lambda/interaction/{inventory => }/container/containers/external/DoubleChestContainer.kt (88%) rename src/main/kotlin/com/lambda/interaction/{inventory => }/container/containers/external/EnderChestContainer.kt (86%) rename src/main/kotlin/com/lambda/interaction/{inventory => }/container/containers/external/PlacedShulkerBoxContainer.kt (86%) rename src/main/kotlin/com/lambda/interaction/{inventory => }/container/containers/external/ShulkerBoxContainer.kt (81%) rename src/main/kotlin/com/lambda/interaction/{inventory => }/container/containers/external/StashContainer.kt (83%) rename src/main/kotlin/com/lambda/interaction/{inventory => container/selection}/ContainerSelection.kt (56%) rename src/main/kotlin/com/lambda/interaction/{inventory => container/selection}/StackSelection.kt (91%) delete mode 100644 src/main/kotlin/com/lambda/interaction/inventory/container/containers/HotbarAndInventoryContainer.kt delete mode 100644 src/main/kotlin/com/lambda/interaction/inventory/container/containers/MainHandContainer.kt diff --git a/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt b/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt index 0e0d4babb..9077ebe4d 100644 --- a/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt +++ b/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt @@ -28,10 +28,10 @@ import com.lambda.brigadier.executeWithResult import com.lambda.brigadier.required import com.lambda.command.LambdaCommand import com.lambda.config.automation.AutomationConfig +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.selectStack import com.lambda.interaction.handler.handlers.ContainerHandler import com.lambda.interaction.handler.handlers.findContainers import com.lambda.interaction.handler.handlers.findContainersWithSpace -import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack import com.lambda.task.Task import com.lambda.task.start import com.lambda.task.tasks.transfer diff --git a/src/main/kotlin/com/lambda/config/blocks/EatConfig.kt b/src/main/kotlin/com/lambda/config/blocks/EatConfig.kt index 10af94609..721b5470e 100644 --- a/src/main/kotlin/com/lambda/config/blocks/EatConfig.kt +++ b/src/main/kotlin/com/lambda/config/blocks/EatConfig.kt @@ -19,8 +19,8 @@ package com.lambda.config.blocks import com.lambda.context.Automated import com.lambda.context.AutomatedSafeContext -import com.lambda.interaction.inventory.StackAndSlot -import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack +import com.lambda.interaction.container.selection.StackAndSlot +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.selectStack import com.lambda.threading.runSafe import com.lambda.util.Describable import com.lambda.util.NamedEnum diff --git a/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt b/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt index 3e839a7af..5ac44e036 100644 --- a/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt +++ b/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt @@ -18,11 +18,10 @@ package com.lambda.config.blocks import com.lambda.event.events.TickEvent -import com.lambda.interaction.inventory.ContainerSelection -import com.lambda.interaction.inventory.ContainerSelectionBuilder.Companion.selectContainer -import com.lambda.interaction.inventory.StackSelection -import com.lambda.interaction.inventory.container.Container -import com.lambda.interaction.inventory.container.ContainerRank +import com.lambda.interaction.container.Container +import com.lambda.interaction.container.selection.ContainerSelection +import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.selectContainer +import com.lambda.interaction.container.selection.StackSelection import com.lambda.util.Describable import com.lambda.util.NamedEnum import net.minecraft.item.Item @@ -33,13 +32,12 @@ interface InventoryConfig { val accessPriority: Priority val storePriority: Priority - val allowedContainers: Collection - - val enderChestSearchRadius: Int - + val allowedContainers: Collection val containerSelection: ContainerSelection get() = selectContainer { ofAnyType(*allowedContainers.toTypedArray()) } + val enderChestSearchRadius: Int + enum class Priority( override val displayName: String, override val description: String @@ -49,22 +47,22 @@ interface InventoryConfig { fun materialComparator(selection: StackSelection) = when (this) { - WithMaxItems -> compareBy { it.rank } + WithMaxItems -> compareBy { it.type } .thenByDescending { it.stackCount(selection) } .thenBy { it.name } - WithMinItems -> compareBy { it.rank } + WithMinItems -> compareBy { it.type } .thenBy { it.stackCount(selection) } .thenBy { it.name } } fun spaceComparator(selection: StackSelection) = when (this) { - WithMaxItems -> compareBy { it.rank } + WithMaxItems -> compareBy { it.type } .thenByDescending { it.spaceAvailable(selection) } .thenBy { it.name } - WithMinItems -> compareBy { it.rank } + WithMinItems -> compareBy { it.type } .thenBy { it.spaceAvailable(selection) } .thenBy { it.name } } diff --git a/src/main/kotlin/com/lambda/config/blocks/InventorySettings.kt b/src/main/kotlin/com/lambda/config/blocks/InventorySettings.kt index f5297f702..2659c92f1 100644 --- a/src/main/kotlin/com/lambda/config/blocks/InventorySettings.kt +++ b/src/main/kotlin/com/lambda/config/blocks/InventorySettings.kt @@ -21,7 +21,7 @@ import com.lambda.config.Config import com.lambda.config.ConfigBlock import com.lambda.config.Group import com.lambda.event.events.TickEvent.Companion.ALL_STAGES -import com.lambda.interaction.inventory.container.ContainerRank +import com.lambda.interaction.container.ContainerType import com.lambda.util.item.ItemUtils class InventorySettings(override val c: Config) : InventoryConfig, ConfigBlock { @@ -30,10 +30,10 @@ class InventorySettings(override val c: Config) : InventoryConfig, ConfigBlock { } override val tickStageMask by c.setting("Inventory Stage Mask", ALL_STAGES.toSet(), description = "The sub-tick timing at which inventory actions are performed", displayClassName = true) - override val disposables by c.setting("Disposables", ItemUtils.defaultDisposables, description = "Items that will be ignored when checking for a free slot") + override val disposables by c.setting("Disposables", ItemUtils.DEFAULT_DISPOSABLES, description = "Items that will be ignored when checking for a free slot") override val accessPriority by c.setting("Access Priority", InventoryConfig.Priority.WithMinItems, "What container to prefer when retrieving the item from") override val storePriority by c.setting("Store Priority", InventoryConfig.Priority.WithMinItems, "What container to prefer when storing the item to") - override val allowedContainers by c.setting("Allowed Containers", ContainerRank.entries, description = "What containers are accessible") + override val allowedContainers by c.setting("Allowed Containers", ContainerType.entries, description = "What containers are accessible") @Group(ENDER_CHEST_GROUP) override val enderChestSearchRadius by c.setting("Initial Search Radius", 10, 0..20, unit = " blocks", description = "Initial search radius to scan for an already placed ender chest. If one isn't found, one will be placed. If an ender chest isn't found in the inventory, it scans the rest of the loaded area to find an already placed one") } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/event/events/ContainerEvent.kt b/src/main/kotlin/com/lambda/event/events/ContainerEvent.kt index f586dd52a..0bfdb1899 100644 --- a/src/main/kotlin/com/lambda/event/events/ContainerEvent.kt +++ b/src/main/kotlin/com/lambda/event/events/ContainerEvent.kt @@ -19,7 +19,7 @@ package com.lambda.event.events import com.lambda.event.callback.Cancellable import com.lambda.event.callback.ICancellable -import com.lambda.interaction.inventory.container.Container +import com.lambda.interaction.container.Container import net.minecraft.screen.slot.Slot sealed class ContainerEvent { diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/context/BreakContext.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/context/BreakContext.kt index a11a43628..d3b142bcf 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/context/BreakContext.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/context/BreakContext.kt @@ -19,7 +19,7 @@ package com.lambda.interaction.construction.simulation.context import com.lambda.context.Automated import com.lambda.graphics.mc.RenderBuilder -import com.lambda.interaction.inventory.StackSelection +import com.lambda.interaction.container.selection.StackSelection import com.lambda.interaction.manager.managers.rotating.RotationRequest import com.lambda.threading.runSafe import com.lambda.util.BlockUtils.emptyState diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt index 769f2b102..361763443 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt @@ -31,9 +31,9 @@ import com.lambda.interaction.construction.simulation.result.Drawable import com.lambda.interaction.construction.simulation.result.Navigable import com.lambda.interaction.construction.simulation.result.Rank import com.lambda.interaction.construction.simulation.result.Resolvable +import com.lambda.interaction.container.containers.HotbarContainer +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.selectStack import com.lambda.interaction.handler.handlers.BaritoneHandler -import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack -import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.task.tasks.transferTo import net.minecraft.block.BlockState import net.minecraft.item.Item diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/GenericResult.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/GenericResult.kt index 69dc06081..0e06d44da 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/GenericResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/GenericResult.kt @@ -26,9 +26,9 @@ import com.lambda.interaction.construction.simulation.result.Drawable import com.lambda.interaction.construction.simulation.result.Navigable import com.lambda.interaction.construction.simulation.result.Rank import com.lambda.interaction.construction.simulation.result.Resolvable +import com.lambda.interaction.container.containers.HotbarContainer +import com.lambda.interaction.container.selection.StackSelection import com.lambda.interaction.handler.handlers.BaritoneHandler -import com.lambda.interaction.inventory.StackSelection -import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.task.tasks.transferTo import net.minecraft.client.data.TextureMap.side import net.minecraft.item.ItemStack diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/BreakSim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/BreakSim.kt index 6c179d68c..06f5e8c1c 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/BreakSim.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/BreakSim.kt @@ -28,12 +28,12 @@ import com.lambda.interaction.construction.simulation.result.results.BreakResult import com.lambda.interaction.construction.simulation.result.results.GenericResult import com.lambda.interaction.construction.simulation.sim import com.lambda.interaction.construction.verify.TargetState +import com.lambda.interaction.container.ContainerType +import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.selectContainer +import com.lambda.interaction.container.selection.StackAndSlot +import com.lambda.interaction.container.selection.StackSelection +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.selectStack import com.lambda.interaction.handler.handlers.findContainers -import com.lambda.interaction.inventory.ContainerSelectionBuilder.Companion.selectContainer -import com.lambda.interaction.inventory.StackAndSlot -import com.lambda.interaction.inventory.StackSelection -import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack -import com.lambda.interaction.inventory.container.ContainerRank import com.lambda.interaction.manager.managers.hotbar.HotbarManager import com.lambda.interaction.manager.managers.rotating.RotationManager import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest @@ -155,7 +155,7 @@ class BreakSim internal constructor(simInfo: SimInfo) } val containerSelection = selectContainer { - ofAnyType(ContainerRank.Hotbar) + ofAnyType(ContainerType.Hotbar) } val hotbarCandidates = stackSelection diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/InteractSim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/InteractSim.kt index 10d9b0629..341a15e8e 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/InteractSim.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/InteractSim.kt @@ -27,11 +27,11 @@ import com.lambda.interaction.construction.simulation.result.results.GenericResu import com.lambda.interaction.construction.simulation.result.results.InteractResult import com.lambda.interaction.construction.simulation.sim import com.lambda.interaction.construction.verify.TargetState +import com.lambda.interaction.container.ContainerType +import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.selectContainer +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.selectStack +import com.lambda.interaction.container.selection.select import com.lambda.interaction.handler.handlers.findContainers -import com.lambda.interaction.inventory.ContainerSelectionBuilder.Companion.selectContainer -import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack -import com.lambda.interaction.inventory.container.ContainerRank -import com.lambda.interaction.inventory.select import com.lambda.interaction.manager.managers.rotating.Rotation import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotation import com.lambda.interaction.manager.managers.rotating.RotationManager @@ -207,7 +207,7 @@ class InteractSim internal constructor(simInfo: InteractSimInfo) } val stackSelection = item?.select(if (item == Items.AIR) 0 else 1) ?: selectStack { sortedWith { compareByDescending { it.stack.inventoryIndex == player.inventory.selectedSlot } } } - val containerSelection = selectContainer { ofAnyType(ContainerRank.Hotbar) } + val containerSelection = selectContainer { ofAnyType(ContainerType.Hotbar) } val container = stackSelection.findContainers(containerSelection).firstOrNull() ?: run { result(GenericResult.WrongItemSelection(pos, stackSelection, player.mainHandStack)) return null diff --git a/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt b/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt index cf594ccb8..1ba987fc8 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt @@ -19,7 +19,7 @@ package com.lambda.interaction.construction.verify import com.lambda.context.AutomatedSafeContext import com.lambda.context.SafeContext -import com.lambda.interaction.handler.handlers.findDisposable +import com.lambda.interaction.handler.handlers.findContainerWithDisposable import com.lambda.util.BlockUtils.blockState import com.lambda.util.BlockUtils.emptyState import com.lambda.util.BlockUtils.isEmpty @@ -86,7 +86,7 @@ sealed class TargetState : StateMatcher { context(automatedSafeContext: AutomatedSafeContext) override fun getStack(pos: BlockPos) = with(automatedSafeContext) { - findDisposable()?.stacks?.firstOrNull { + findContainerWithDisposable()?.stacks?.firstOrNull { it.item in inventoryConfig.disposables && it.item.block !in replace } ?: ItemStack(Items.NETHERRACK) } @@ -113,7 +113,7 @@ sealed class TargetState : StateMatcher { context(automatedSafeContext: AutomatedSafeContext) override fun getStack(pos: BlockPos) = with(automatedSafeContext) { - findDisposable()?.stacks?.firstOrNull { + findContainerWithDisposable()?.stacks?.firstOrNull { it.item in inventoryConfig.disposables } ?: ItemStack(Items.NETHERRACK) } diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/Container.kt b/src/main/kotlin/com/lambda/interaction/container/Container.kt similarity index 90% rename from src/main/kotlin/com/lambda/interaction/inventory/container/Container.kt rename to src/main/kotlin/com/lambda/interaction/container/Container.kt index cc2889eec..d8dd36dd5 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/Container.kt +++ b/src/main/kotlin/com/lambda/interaction/container/Container.kt @@ -15,14 +15,14 @@ * along with this program. If not, see . */ -package com.lambda.interaction.inventory.container +package com.lambda.interaction.container import com.lambda.context.Automated import com.lambda.context.AutomatedSafeContext import com.lambda.context.SafeContext import com.lambda.event.EventFlow.post import com.lambda.event.events.ContainerEvent -import com.lambda.interaction.inventory.StackSelection +import com.lambda.interaction.container.selection.StackSelection import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.task.Task.Ta5kBuilder @@ -44,7 +44,7 @@ import net.minecraft.text.Text // ToDo: Make jsonable to persistently store them abstract class Container( - val rank: ContainerRank + val type: ContainerType ) : Nameable, Comparable { override val name: String get() = description.string @@ -106,14 +106,6 @@ abstract class Container( open fun access(): OpenContainerTask<*>? = PlayerOpenContainerTask(description, ::isAccessed) - context(automatedSafeContext: AutomatedSafeContext) - internal fun move(selection: StackSelection, toContainer: Container): Boolean = - with(automatedSafeContext) { - val (fromSlot, toSlot) = getTransferSlots(selection, toContainer) - if (fromSlot == null || toSlot == null) return false - swap(fromSlot, toSlot, toContainer) - } - context(automatedSafeContext: AutomatedSafeContext) internal fun swap(fromSlot: Slot, toSlot: Slot, toContainer: Container): Boolean { val transferEvent = ContainerEvent.Transfer(fromSlot, toSlot, this@Container, toContainer) @@ -148,5 +140,5 @@ abstract class Container( open fun getSlot(selection: StackSelection) = selection.bestMatch(slots) - override fun compareTo(other: Container) = compareBy { it.rank }.compare(this, other) + override fun compareTo(other: Container) = compareBy { it.type }.compare(this, other) } diff --git a/src/main/kotlin/com/lambda/interaction/inventory/ContainerMarker.kt b/src/main/kotlin/com/lambda/interaction/container/ContainerMarker.kt similarity index 94% rename from src/main/kotlin/com/lambda/interaction/inventory/ContainerMarker.kt rename to src/main/kotlin/com/lambda/interaction/container/ContainerMarker.kt index fa8982066..9b56dc2c7 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/ContainerMarker.kt +++ b/src/main/kotlin/com/lambda/interaction/container/ContainerMarker.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.inventory +package com.lambda.interaction.container @DslMarker annotation class ContainerMarker \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/ContainerRank.kt b/src/main/kotlin/com/lambda/interaction/container/ContainerType.kt similarity index 87% rename from src/main/kotlin/com/lambda/interaction/inventory/container/ContainerRank.kt rename to src/main/kotlin/com/lambda/interaction/container/ContainerType.kt index 2d50d0c47..cfc76c5f5 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/ContainerRank.kt +++ b/src/main/kotlin/com/lambda/interaction/container/ContainerType.kt @@ -15,16 +15,14 @@ * along with this program. If not, see . */ -package com.lambda.interaction.inventory.container +package com.lambda.interaction.container -enum class ContainerRank { - MainHand, +enum class ContainerType { OffHand, Hotbar, + Cursor, Inventory, - HotbarAndInventory, Armor, - Player, Creative, ShulkerBox, EnderChest, diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/ExternalContainer.kt b/src/main/kotlin/com/lambda/interaction/container/ExternalContainer.kt similarity index 93% rename from src/main/kotlin/com/lambda/interaction/inventory/container/ExternalContainer.kt rename to src/main/kotlin/com/lambda/interaction/container/ExternalContainer.kt index bad12fdb3..500d2f6e2 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/ExternalContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/container/ExternalContainer.kt @@ -15,6 +15,6 @@ * along with this program. If not, see . */ -package com.lambda.interaction.inventory.container +package com.lambda.interaction.container interface ExternalContainer \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/NestedContainer.kt b/src/main/kotlin/com/lambda/interaction/container/NestedContainer.kt similarity index 84% rename from src/main/kotlin/com/lambda/interaction/inventory/container/NestedContainer.kt rename to src/main/kotlin/com/lambda/interaction/container/NestedContainer.kt index 039fe429f..e71c7033e 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/NestedContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/container/NestedContainer.kt @@ -15,9 +15,9 @@ * along with this program. If not, see . */ -package com.lambda.interaction.inventory.container +package com.lambda.interaction.container -abstract class NestedContainer(rank: ContainerRank) : Container(rank) { +abstract class NestedContainer(type: ContainerType) : Container(type), ExternalContainer { abstract val containedIn: Container abstract val index: Int } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/OpenContainerTask.kt b/src/main/kotlin/com/lambda/interaction/container/OpenContainerTask.kt similarity index 96% rename from src/main/kotlin/com/lambda/interaction/inventory/container/OpenContainerTask.kt rename to src/main/kotlin/com/lambda/interaction/container/OpenContainerTask.kt index e871641fa..61a07e5d7 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/OpenContainerTask.kt +++ b/src/main/kotlin/com/lambda/interaction/container/OpenContainerTask.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.inventory.container +package com.lambda.interaction.container import com.lambda.context.SafeContext import com.lambda.task.Task diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/OpenedContainerContext.kt b/src/main/kotlin/com/lambda/interaction/container/OpenedContainerContext.kt similarity index 95% rename from src/main/kotlin/com/lambda/interaction/inventory/container/OpenedContainerContext.kt rename to src/main/kotlin/com/lambda/interaction/container/OpenedContainerContext.kt index a58ba8990..4deafbcb0 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/OpenedContainerContext.kt +++ b/src/main/kotlin/com/lambda/interaction/container/OpenedContainerContext.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.inventory.container +package com.lambda.interaction.container import com.lambda.context.Automated import com.lambda.task.Task diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/PlacedContainer.kt b/src/main/kotlin/com/lambda/interaction/container/PlacedContainer.kt similarity index 77% rename from src/main/kotlin/com/lambda/interaction/inventory/container/PlacedContainer.kt rename to src/main/kotlin/com/lambda/interaction/container/PlacedContainer.kt index 85ab71da3..98c5b8f38 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/PlacedContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/container/PlacedContainer.kt @@ -15,12 +15,12 @@ * along with this program. If not, see . */ -package com.lambda.interaction.inventory.container +package com.lambda.interaction.container -import com.lambda.interaction.inventory.container.containers.external.StashContainer +import com.lambda.interaction.container.containers.external.StashContainer import net.minecraft.util.math.BlockPos -abstract class PlacedContainer(rank: ContainerRank) : Container(rank), ExternalContainer { +abstract class PlacedContainer(type: ContainerType) : Container(type), ExternalContainer { abstract val pos: BlockPos abstract val stash: StashContainer? } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/ArmorContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/ArmorContainer.kt similarity index 84% rename from src/main/kotlin/com/lambda/interaction/inventory/container/containers/ArmorContainer.kt rename to src/main/kotlin/com/lambda/interaction/container/containers/ArmorContainer.kt index f25f7629e..bd54eebf8 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/ArmorContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/container/containers/ArmorContainer.kt @@ -15,11 +15,11 @@ * along with this program. If not, see . */ -package com.lambda.interaction.inventory.container.containers +package com.lambda.interaction.container.containers import com.lambda.Lambda.mc -import com.lambda.interaction.inventory.container.Container -import com.lambda.interaction.inventory.container.ContainerRank +import com.lambda.interaction.container.Container +import com.lambda.interaction.container.ContainerType import com.lambda.util.player.SlotUtils.armorSlots import com.lambda.util.player.SlotUtils.armorStacks import com.lambda.util.text.buildText @@ -27,7 +27,7 @@ import com.lambda.util.text.literal import net.minecraft.item.ItemStack import net.minecraft.screen.slot.Slot -object ArmorContainer : Container(ContainerRank.Armor) { +object ArmorContainer : Container(ContainerType.Armor) { override val slots: List get() = mc.player?.armorSlots ?: emptyList() override var stacks: List diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/CreativeContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/CreativeContainer.kt similarity index 89% rename from src/main/kotlin/com/lambda/interaction/inventory/container/containers/CreativeContainer.kt rename to src/main/kotlin/com/lambda/interaction/container/containers/CreativeContainer.kt index e96f0c892..2c53447d1 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/CreativeContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/container/containers/CreativeContainer.kt @@ -15,13 +15,13 @@ * along with this program. If not, see . */ -package com.lambda.interaction.inventory.container.containers +package com.lambda.interaction.container.containers import com.lambda.Lambda.mc import com.lambda.context.SafeContext -import com.lambda.interaction.inventory.StackSelection -import com.lambda.interaction.inventory.container.Container -import com.lambda.interaction.inventory.container.ContainerRank +import com.lambda.interaction.container.Container +import com.lambda.interaction.container.ContainerType +import com.lambda.interaction.container.selection.StackSelection import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder import com.lambda.util.text.buildText import com.lambda.util.text.literal @@ -32,7 +32,7 @@ import net.minecraft.item.ItemStack import net.minecraft.screen.PlayerScreenHandler import net.minecraft.screen.slot.Slot -data object CreativeContainer : Container(ContainerRank.Creative) { +data object CreativeContainer : Container(ContainerType.Creative) { override val slots = emptyList() override var stacks = emptyList() diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/PlayerContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/CursorContainer.kt similarity index 62% rename from src/main/kotlin/com/lambda/interaction/inventory/container/containers/PlayerContainer.kt rename to src/main/kotlin/com/lambda/interaction/container/containers/CursorContainer.kt index 736d1e787..5211c5d90 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/PlayerContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/container/containers/CursorContainer.kt @@ -15,25 +15,21 @@ * along with this program. If not, see . */ -package com.lambda.interaction.inventory.container.containers +package com.lambda.interaction.container.containers import com.lambda.Lambda.mc -import com.lambda.interaction.inventory.container.Container -import com.lambda.interaction.inventory.container.ContainerRank -import com.lambda.util.player.SlotUtils.allSlots -import com.lambda.util.player.SlotUtils.allStacks +import com.lambda.interaction.container.Container +import com.lambda.interaction.container.ContainerType import com.lambda.util.text.buildText import com.lambda.util.text.literal import net.minecraft.item.ItemStack import net.minecraft.screen.slot.Slot -@Suppress("unused") -object PlayerContainer : Container(ContainerRank.Player) { - override val slots: List - get() = mc.player?.allSlots ?: emptyList() +object CursorContainer : Container(ContainerType.Cursor) { + override val slots: List = emptyList() override var stacks: List - get() = mc.player?.allStacks ?: emptyList() + get() = mc.player?.currentScreenHandler?.cursorStack?.let { listOf(it) } ?: emptyList() set(_) {} - override val description = buildText { literal("Player") } + override val description = buildText { literal("Cursor") } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/HotbarContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/HotbarContainer.kt similarity index 86% rename from src/main/kotlin/com/lambda/interaction/inventory/container/containers/HotbarContainer.kt rename to src/main/kotlin/com/lambda/interaction/container/containers/HotbarContainer.kt index 9533e52cf..3e5ba3744 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/HotbarContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/container/containers/HotbarContainer.kt @@ -15,12 +15,12 @@ * along with this program. If not, see . */ -package com.lambda.interaction.inventory.container.containers +package com.lambda.interaction.container.containers import com.lambda.Lambda.mc import com.lambda.context.SafeContext -import com.lambda.interaction.inventory.container.Container -import com.lambda.interaction.inventory.container.ContainerRank +import com.lambda.interaction.container.Container +import com.lambda.interaction.container.ContainerType import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder import com.lambda.util.player.SlotUtils.hotbarSlots import com.lambda.util.player.SlotUtils.hotbarStacks @@ -28,7 +28,7 @@ import com.lambda.util.text.buildText import com.lambda.util.text.literal import net.minecraft.screen.slot.Slot -object HotbarContainer : Container(ContainerRank.Hotbar) { +object HotbarContainer : Container(ContainerType.Hotbar) { override val slots: List get() = mc.player?.hotbarSlots ?: emptyList() override var stacks diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/InventoryContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/InventoryContainer.kt similarity index 83% rename from src/main/kotlin/com/lambda/interaction/inventory/container/containers/InventoryContainer.kt rename to src/main/kotlin/com/lambda/interaction/container/containers/InventoryContainer.kt index d9cec92a0..f1c56e1cf 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/InventoryContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/container/containers/InventoryContainer.kt @@ -15,11 +15,11 @@ * along with this program. If not, see . */ -package com.lambda.interaction.inventory.container.containers +package com.lambda.interaction.container.containers import com.lambda.Lambda.mc -import com.lambda.interaction.inventory.container.Container -import com.lambda.interaction.inventory.container.ContainerRank +import com.lambda.interaction.container.Container +import com.lambda.interaction.container.ContainerType import com.lambda.util.player.SlotUtils.inventorySlots import com.lambda.util.player.SlotUtils.inventoryStacks import com.lambda.util.text.buildText @@ -27,7 +27,7 @@ import com.lambda.util.text.literal import net.minecraft.item.ItemStack import net.minecraft.screen.slot.Slot -object InventoryContainer : Container(ContainerRank.Inventory) { +object InventoryContainer : Container(ContainerType.Inventory) { override val slots: List get() = mc.player?.inventorySlots ?: emptyList() override var stacks: List diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/OffHandContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/OffHandContainer.kt similarity index 85% rename from src/main/kotlin/com/lambda/interaction/inventory/container/containers/OffHandContainer.kt rename to src/main/kotlin/com/lambda/interaction/container/containers/OffHandContainer.kt index 88e65599b..400af4860 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/OffHandContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/container/containers/OffHandContainer.kt @@ -15,12 +15,12 @@ * along with this program. If not, see . */ -package com.lambda.interaction.inventory.container.containers +package com.lambda.interaction.container.containers import com.lambda.Lambda.mc import com.lambda.context.SafeContext -import com.lambda.interaction.inventory.container.Container -import com.lambda.interaction.inventory.container.ContainerRank +import com.lambda.interaction.container.Container +import com.lambda.interaction.container.ContainerType import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder import com.lambda.util.player.SlotUtils.offHandSlots import com.lambda.util.text.buildText @@ -28,7 +28,7 @@ import com.lambda.util.text.literal import net.minecraft.item.ItemStack import net.minecraft.screen.slot.Slot -object OffHandContainer : Container(ContainerRank.OffHand) { +object OffHandContainer : Container(ContainerType.OffHand) { override val slots: List get() = mc.player?.offHandSlots ?: emptyList() override var stacks: List diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ChestContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/external/ChestContainer.kt similarity index 85% rename from src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ChestContainer.kt rename to src/main/kotlin/com/lambda/interaction/container/containers/external/ChestContainer.kt index abdb79b94..ab545dea2 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ChestContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/container/containers/external/ChestContainer.kt @@ -15,17 +15,17 @@ * along with this program. If not, see . */ -package com.lambda.interaction.inventory.container.containers.external +package com.lambda.interaction.container.containers.external import com.lambda.Lambda.mc import com.lambda.context.Automated import com.lambda.context.SafeContext +import com.lambda.interaction.container.BasicOpenedContainerContext +import com.lambda.interaction.container.ContainerType +import com.lambda.interaction.container.OpenContainerTask +import com.lambda.interaction.container.OpenedContainerContext +import com.lambda.interaction.container.PlacedContainer import com.lambda.interaction.handler.handlers.ContainerHandler.lastInteractedBlockEntity -import com.lambda.interaction.inventory.container.BasicOpenedContainerContext -import com.lambda.interaction.inventory.container.ContainerRank -import com.lambda.interaction.inventory.container.OpenContainerTask -import com.lambda.interaction.inventory.container.OpenedContainerContext -import com.lambda.interaction.inventory.container.PlacedContainer import com.lambda.task.Task.Ta5kBuilder import com.lambda.task.tasks.openContainer import com.lambda.util.extension.containerSlots @@ -43,7 +43,7 @@ data class ChestContainer( override val pos: BlockPos, override var stacks: List, override val stash: StashContainer? = null -) : PlacedContainer(ContainerRank.Chest) { +) : PlacedContainer(ContainerType.Chest) { override val slots get(): List = if (isAccessed) mc.player?.currentScreenHandler?.containerSlots ?: emptyList() diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/DoubleChestContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/external/DoubleChestContainer.kt similarity index 88% rename from src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/DoubleChestContainer.kt rename to src/main/kotlin/com/lambda/interaction/container/containers/external/DoubleChestContainer.kt index 3a4a138dd..f9a9ff386 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/DoubleChestContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/container/containers/external/DoubleChestContainer.kt @@ -15,16 +15,16 @@ * along with this program. If not, see . */ -package com.lambda.interaction.inventory.container.containers.external +package com.lambda.interaction.container.containers.external import com.lambda.Lambda.mc import com.lambda.context.Automated import com.lambda.context.SafeContext +import com.lambda.interaction.container.BasicOpenedContainerContext +import com.lambda.interaction.container.ContainerType +import com.lambda.interaction.container.OpenContainerTask +import com.lambda.interaction.container.PlacedContainer import com.lambda.interaction.handler.handlers.ContainerHandler.lastInteractedBlockEntity -import com.lambda.interaction.inventory.container.BasicOpenedContainerContext -import com.lambda.interaction.inventory.container.ContainerRank -import com.lambda.interaction.inventory.container.OpenContainerTask -import com.lambda.interaction.inventory.container.PlacedContainer import com.lambda.task.Task.Ta5kBuilder import com.lambda.task.tasks.openContainer import com.lambda.task.tasks.wrappers.withRecovery @@ -46,7 +46,7 @@ data class DoubleChestContainer( val rightPos: BlockPos, override var stacks: List, override val stash: StashContainer? = null -) : PlacedContainer(ContainerRank.Chest) { +) : PlacedContainer(ContainerType.Chest) { override val slots get(): List = if (isAccessed) mc.player?.currentScreenHandler?.containerSlots ?: emptyList() diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/EnderChestContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/external/EnderChestContainer.kt similarity index 86% rename from src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/EnderChestContainer.kt rename to src/main/kotlin/com/lambda/interaction/container/containers/external/EnderChestContainer.kt index 38b15b9c4..925cf1aaa 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/EnderChestContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/container/containers/external/EnderChestContainer.kt @@ -15,18 +15,18 @@ * along with this program. If not, see . */ -package com.lambda.interaction.inventory.container.containers.external +package com.lambda.interaction.container.containers.external import com.lambda.Lambda.mc import com.lambda.context.Automated import com.lambda.context.SafeContext +import com.lambda.interaction.container.Container +import com.lambda.interaction.container.ContainerType +import com.lambda.interaction.container.ExternalContainer +import com.lambda.interaction.container.OpenContainerTask +import com.lambda.interaction.container.OpenedContainerContext +import com.lambda.interaction.container.selection.select import com.lambda.interaction.handler.handlers.ContainerHandler.lastInteractedBlockEntity -import com.lambda.interaction.inventory.container.Container -import com.lambda.interaction.inventory.container.ContainerRank -import com.lambda.interaction.inventory.container.ExternalContainer -import com.lambda.interaction.inventory.container.OpenContainerTask -import com.lambda.interaction.inventory.container.OpenedContainerContext -import com.lambda.interaction.inventory.select import com.lambda.task.Task.Ta5kBuilder import com.lambda.task.tasks.acquireStack import com.lambda.task.tasks.breakAndCollect @@ -49,7 +49,7 @@ import net.minecraft.item.Items import net.minecraft.screen.ScreenHandlerType import net.minecraft.util.math.BlockPos -object EnderChestContainer : Container(ContainerRank.EnderChest), ExternalContainer { +object EnderChestContainer : Container(ContainerType.EnderChest), ExternalContainer { override val slots get() = if (isAccessed) mc.player?.currentScreenHandler?.containerSlots ?: emptyList() diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/PlacedShulkerBoxContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/external/PlacedShulkerBoxContainer.kt similarity index 86% rename from src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/PlacedShulkerBoxContainer.kt rename to src/main/kotlin/com/lambda/interaction/container/containers/external/PlacedShulkerBoxContainer.kt index 3bf8c2188..716e2489e 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/PlacedShulkerBoxContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/container/containers/external/PlacedShulkerBoxContainer.kt @@ -15,16 +15,16 @@ * along with this program. If not, see . */ -package com.lambda.interaction.inventory.container.containers.external +package com.lambda.interaction.container.containers.external import com.lambda.Lambda.mc import com.lambda.context.Automated import com.lambda.context.SafeContext +import com.lambda.interaction.container.BasicOpenedContainerContext +import com.lambda.interaction.container.ContainerType +import com.lambda.interaction.container.OpenContainerTask +import com.lambda.interaction.container.PlacedContainer import com.lambda.interaction.handler.handlers.ContainerHandler.lastInteractedBlockEntity -import com.lambda.interaction.inventory.container.BasicOpenedContainerContext -import com.lambda.interaction.inventory.container.ContainerRank -import com.lambda.interaction.inventory.container.OpenContainerTask -import com.lambda.interaction.inventory.container.PlacedContainer import com.lambda.task.Task.Ta5kBuilder import com.lambda.task.tasks.openContainer import com.lambda.util.extension.containerSlots @@ -43,7 +43,7 @@ class PlacedShulkerBoxContainer( val block: Block, override val stash: StashContainer?, override var stacks: List -) : PlacedContainer(ContainerRank.PlacedShulkerBox) { +) : PlacedContainer(ContainerType.PlacedShulkerBox) { override val slots: List get() = if (isAccessed) mc.player?.currentScreenHandler?.containerSlots ?: emptyList() diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ShulkerBoxContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt similarity index 81% rename from src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ShulkerBoxContainer.kt rename to src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt index a179fe27d..d2e67c234 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/ShulkerBoxContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt @@ -15,25 +15,25 @@ * along with this program. If not, see . */ -package com.lambda.interaction.inventory.container.containers.external +package com.lambda.interaction.container.containers.external import com.lambda.Lambda.mc import com.lambda.context.Automated import com.lambda.context.SafeContext -import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack -import com.lambda.interaction.inventory.container.Container -import com.lambda.interaction.inventory.container.ContainerRank -import com.lambda.interaction.inventory.container.ExternalContainer -import com.lambda.interaction.inventory.container.NestedContainer -import com.lambda.interaction.inventory.container.OpenContainerTask -import com.lambda.interaction.inventory.container.OpenedContainerContext -import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer -import com.lambda.interaction.inventory.container.containers.HotbarContainer +import com.lambda.interaction.container.Container +import com.lambda.interaction.container.ContainerType +import com.lambda.interaction.container.ExternalContainer +import com.lambda.interaction.container.NestedContainer +import com.lambda.interaction.container.OpenContainerTask +import com.lambda.interaction.container.OpenedContainerContext +import com.lambda.interaction.container.containers.HotbarContainer +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.selectStack import com.lambda.task.Task.Ta5kBuilder import com.lambda.task.tasks.breakAndCollect import com.lambda.task.tasks.openContainer import com.lambda.task.tasks.placeContainer import com.lambda.task.tasks.transfer +import com.lambda.task.tasks.transferTo import com.lambda.task.tasks.wrappers.actionTask import com.lambda.task.tasks.wrappers.taskOrNull import com.lambda.task.tasks.wrappers.then @@ -53,7 +53,7 @@ data class ShulkerBoxContainer( override var stacks: List, override val containedIn: Container, override val index: Int, -) : NestedContainer(ContainerRank.ShulkerBox), ExternalContainer { +) : NestedContainer(ContainerType.ShulkerBox), ExternalContainer { override val slots get(): List = if (isAccessed) mc.player?.currentScreenHandler?.containerSlots ?: emptyList() @@ -102,6 +102,11 @@ data class ShulkerBoxContainer( else actionTask { player.closeScreen() } }.then { breakAndCollect(blockPos) } .then { + selectStack { + isItem(shulkerItem) + hasCustomName(itemName) + sortedByBestContentMatch(stacks) + }.transferTo() transfer( selectStack { isItem(shulkerItem) diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/StashContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/external/StashContainer.kt similarity index 83% rename from src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/StashContainer.kt rename to src/main/kotlin/com/lambda/interaction/container/containers/external/StashContainer.kt index 8c7063980..bd3420298 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/external/StashContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/container/containers/external/StashContainer.kt @@ -15,11 +15,11 @@ * along with this program. If not, see . */ -package com.lambda.interaction.inventory.container.containers.external +package com.lambda.interaction.container.containers.external -import com.lambda.interaction.inventory.StackSelection -import com.lambda.interaction.inventory.container.Container -import com.lambda.interaction.inventory.container.ContainerRank +import com.lambda.interaction.container.Container +import com.lambda.interaction.container.ContainerType +import com.lambda.interaction.container.selection.StackSelection import com.lambda.util.math.roundedBlockPos import com.lambda.util.text.buildText import com.lambda.util.text.highlighted @@ -31,7 +31,7 @@ import net.minecraft.util.math.Box data class StashContainer( val chests: Set, val pos: Box, -) : Container(ContainerRank.Stash) { +) : Container(ContainerType.Stash) { override val slots: List get() = chests.flatMap { it.slots } override var stacks: List diff --git a/src/main/kotlin/com/lambda/interaction/inventory/ContainerSelection.kt b/src/main/kotlin/com/lambda/interaction/container/selection/ContainerSelection.kt similarity index 56% rename from src/main/kotlin/com/lambda/interaction/inventory/ContainerSelection.kt rename to src/main/kotlin/com/lambda/interaction/container/selection/ContainerSelection.kt index ef32c4633..39f72b7fa 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/ContainerSelection.kt +++ b/src/main/kotlin/com/lambda/interaction/container/selection/ContainerSelection.kt @@ -15,10 +15,18 @@ * along with this program. If not, see . */ -package com.lambda.interaction.inventory - -import com.lambda.interaction.inventory.container.Container -import com.lambda.interaction.inventory.container.ContainerRank +package com.lambda.interaction.container.selection + +import com.lambda.interaction.container.Container +import com.lambda.interaction.container.ContainerMarker +import com.lambda.interaction.container.ContainerType +import com.lambda.interaction.container.containers.ArmorContainer +import com.lambda.interaction.container.containers.CreativeContainer +import com.lambda.interaction.container.containers.CursorContainer +import com.lambda.interaction.container.containers.HotbarContainer +import com.lambda.interaction.container.containers.InventoryContainer +import com.lambda.interaction.container.containers.OffHandContainer +import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.selectContainer /** * ContainerSelection is a class that holds a predicate for matching MaterialContainers. @@ -26,7 +34,9 @@ import com.lambda.interaction.inventory.container.ContainerRank @Suppress("unused") class ContainerSelection( val selector: (Container) -> Boolean, - val comparator: Comparator = compareBy { it.rank } + val containersWhitelist: Collection = emptyList(), + val accessedOnly: Boolean = false, + val comparator: Comparator = compareBy { it.type } ) { @ContainerMarker fun bestMatch(containers: Iterable) = filter(containers).firstOrNull() @@ -36,13 +46,29 @@ class ContainerSelection( @ContainerMarker fun filter(containers: Iterable) = - containers - .filter(selector) + run { + containersWhitelist + .takeIf { it.isNotEmpty() } + ?.filter { it in containers && (!accessedOnly || it.isAccessed) } + ?: containers + }.filter(selector) .sortedWith(comparator) companion object { val EVERYTHING = ContainerSelection({ true }) val NOTHING = ContainerSelection({ false }) + val HOTBAR_AND_INVENTORY = selectContainer { ofAny(HotbarContainer, InventoryContainer) } + val PLAYER = + selectContainer { + ofAny( + HotbarContainer, + InventoryContainer, + OffHandContainer, + CursorContainer, + ArmorContainer, + CreativeContainer + ) + } } } @@ -50,15 +76,22 @@ class ContainerSelection( @ContainerMarker class ContainerSelectionBuilder private constructor() { private var selector: (Container) -> Boolean = { true } - private var comparator: Comparator = compareBy { it.rank } + private var containersWhitelist = mutableListOf() + private var accessedOnly = false + private var comparator: Comparator = compareBy { it.type } private var invertNewSelectors = false - fun ofAnyType(vararg types: ContainerRank) { - appendSelector { container -> types.contains(container.rank) } + fun ofAny(vararg container: Container) { + containersWhitelist.addAll(container) + appendSelector { container -> container in containersWhitelist } + } + + fun ofAnyType(vararg types: ContainerType) { + appendSelector { container -> types.contains(container.type) } } - fun noneOfType(vararg types: ContainerRank) { - appendSelector { container -> !types.contains(container.rank) } + fun noneOfType(vararg types: ContainerType) { + appendSelector { container -> !types.contains(container.type) } } fun matches(stackSelection: StackSelection) { @@ -69,6 +102,11 @@ class ContainerSelectionBuilder private constructor() { appendSelector { container -> containerSelection.matches(container) } } + fun isAccessed() { + accessedOnly = true + appendSelector { it.isAccessed } + } + fun custom(predicate: (Container) -> Boolean) { appendSelector { predicate(it) } } @@ -93,7 +131,7 @@ class ContainerSelectionBuilder private constructor() { this.selector = { currentSelector(it) && selector(it) xor invert } } - private fun build() = ContainerSelection(selector, comparator) + private fun build() = ContainerSelection(selector, containersWhitelist, accessedOnly, comparator) companion object { fun selectContainer( diff --git a/src/main/kotlin/com/lambda/interaction/inventory/StackSelection.kt b/src/main/kotlin/com/lambda/interaction/container/selection/StackSelection.kt similarity index 91% rename from src/main/kotlin/com/lambda/interaction/inventory/StackSelection.kt rename to src/main/kotlin/com/lambda/interaction/container/selection/StackSelection.kt index 9e27c08fd..b1c403288 100644 --- a/src/main/kotlin/com/lambda/interaction/inventory/StackSelection.kt +++ b/src/main/kotlin/com/lambda/interaction/container/selection/StackSelection.kt @@ -15,9 +15,10 @@ * along with this program. If not, see . */ -package com.lambda.interaction.inventory +package com.lambda.interaction.container.selection -import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack +import com.lambda.interaction.container.ContainerMarker +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.selectStack import com.lambda.util.EnchantmentUtils.getEnchantment import com.lambda.util.item.ItemStackUtils.shulkerBoxStacks import com.lambda.util.item.ItemUtils @@ -43,6 +44,7 @@ fun ItemStack.select(count: Int = 0) = selectStack(count) { isItemStack(this@sel @Suppress("unused") class StackSelection( + val whitelistedSlots: Collection = emptyList(), val count: Int = 0, val item: Item? = null, val itemStack: ItemStack? = null, @@ -54,6 +56,7 @@ class StackSelection( @ContainerMarker fun bestMatch(stacks: Iterable) = filter(stacks).firstOrNull() + @ContainerMarker fun bestMatch(slots: Iterable) = filter(slots).firstOrNull() @@ -61,6 +64,7 @@ class StackSelection( fun matches(stack: ItemStack) = if (inShulkerBox) stack.shulkerBoxStacks.any { selector(it, null) } else selector(stack, null) + @ContainerMarker fun matches(slot: Slot): Boolean = if (inShulkerBox) slot.stack.shulkerBoxStacks.any { selector(it, null) } @@ -70,14 +74,19 @@ class StackSelection( @JvmName("filter1") fun filter(stacks: Iterable) = stacks + .let { if (whitelistedSlots.isNotEmpty()) emptyList() else it } .filter(::matches) .map { StackAndSlot(it, null) } .sortedWith(comparator) .map { it.stack } + @ContainerMarker @JvmName("filter2") fun filter(slots: Iterable) = slots + .let { slots -> + if (whitelistedSlots.isNotEmpty()) whitelistedSlots.filter { it in slots } else slots + } .filter(::matches) .map { StackAndSlot(it.stack, it) } .sortedWith(comparator) @@ -92,6 +101,7 @@ class StackSelection( @Suppress("unused") @ContainerMarker class StackSelectionBuilder private constructor(private val count: Int = 0) { + private val whitelistedSlots = mutableListOf() private var selector: (ItemStack, Slot?) -> Boolean = { _, _ -> true } private var item: Item? = null private var stackByRef = false @@ -133,7 +143,7 @@ class StackSelectionBuilder private constructor(private val count: Int = 0) { } fun isShulkerBox() { - appendSelector { stack, _ -> stack.item in ItemUtils.shulkerBoxes } + appendSelector { stack, _ -> stack.item in ItemUtils.SHULKER_BOXES } } fun hasCustomName(name: String) { @@ -153,7 +163,7 @@ class StackSelectionBuilder private constructor(private val count: Int = 0) { fun isEfficientForBreaking(blockState: BlockState) { appendSelector { itemStack, _ -> val hasEfficientTool = efficientToolCache.getOrPut(blockState) { - ItemUtils.tools.any { it.getMiningSpeed(it.defaultStack, blockState) > 1f } + ItemUtils.TOOLS.any { it.getMiningSpeed(it.defaultStack, blockState) > 1f } } if (hasEfficientTool) itemStack.item.getMiningSpeed(itemStack, blockState) > 1f else true @@ -208,12 +218,11 @@ class StackSelectionBuilder private constructor(private val count: Int = 0) { } fun isSlot(slot: Slot) { - appendSelector { _, s -> - s != null && s matches slot - } + appendSelector { _, s -> s != null && s matches slot } } fun isSlotByReference(slot: Slot) { + whitelistedSlots.add(slot) appendSelector { _, s -> s === slot } } @@ -268,6 +277,7 @@ class StackSelectionBuilder private constructor(private val count: Int = 0) { private fun build() = StackSelection( + whitelistedSlots, count, item, itemStack, diff --git a/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt index c21085f12..1740a2eea 100644 --- a/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt @@ -18,31 +18,31 @@ package com.lambda.interaction.handler.handlers import com.lambda.context.Automated -import com.lambda.context.AutomatedSafeContext import com.lambda.context.SafeContext import com.lambda.core.Loadable import com.lambda.event.events.InventoryEvent import com.lambda.event.events.PacketEvent import com.lambda.event.events.WorldEvent import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.container.Container +import com.lambda.interaction.container.ContainerMarker +import com.lambda.interaction.container.PlacedContainer +import com.lambda.interaction.container.containers.external.ChestContainer +import com.lambda.interaction.container.containers.external.DoubleChestContainer +import com.lambda.interaction.container.containers.external.EnderChestContainer +import com.lambda.interaction.container.containers.external.PlacedShulkerBoxContainer +import com.lambda.interaction.container.containers.external.ShulkerBoxContainer +import com.lambda.interaction.container.selection.ContainerSelection +import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.selectContainer +import com.lambda.interaction.container.selection.StackSelection +import com.lambda.interaction.container.selection.select import com.lambda.interaction.handler.handlers.ContainerHandler.filteredContainers -import com.lambda.interaction.inventory.ContainerMarker -import com.lambda.interaction.inventory.ContainerSelection -import com.lambda.interaction.inventory.StackSelection -import com.lambda.interaction.inventory.container.Container -import com.lambda.interaction.inventory.container.PlacedContainer -import com.lambda.interaction.inventory.container.containers.external.ChestContainer -import com.lambda.interaction.inventory.container.containers.external.DoubleChestContainer -import com.lambda.interaction.inventory.container.containers.external.EnderChestContainer -import com.lambda.interaction.inventory.container.containers.external.PlacedShulkerBoxContainer -import com.lambda.interaction.inventory.container.containers.external.ShulkerBoxContainer -import com.lambda.interaction.inventory.select import com.lambda.util.BlockUtils.blockEntity import com.lambda.util.BlockUtils.blockState import com.lambda.util.ReflectionUtils.getInstances import com.lambda.util.extension.containerStacks import com.lambda.util.item.ItemStackUtils.shulkerBoxStacks -import com.lambda.util.item.ItemUtils.shulkerBoxes +import com.lambda.util.item.ItemUtils.SHULKER_BOXES import com.lambda.util.player.SlotUtils.typeSafe import net.minecraft.block.ChestBlock import net.minecraft.block.entity.BlockEntity @@ -60,7 +60,7 @@ import net.minecraft.util.math.ChunkPos @Suppress("unused") object ContainerHandler : Loadable { - private val containers: Sequence + val containers: Sequence get() { fun Container.allNested(): Sequence = sequence { @@ -82,9 +82,6 @@ object ContainerHandler : Loadable { .filter { automated.inventoryConfig.containerSelection.matches(it) } .sorted() - val allContainers - get() = containers.sorted() - var lastInteractedBlockEntity: BlockEntity? = null var pendingInteractedBlockEntity: BlockEntity? = null @@ -158,7 +155,7 @@ object ContainerHandler : Loadable { val state = blockEntity.cachedState if (state.block !is ChestBlock) return - val stacks = blockEntity.stacks + val stacks = blockEntity.stacks() val chestType = state.get(Properties.CHEST_TYPE) ?: return val chunkPos = ChunkPos(pos) @@ -199,7 +196,7 @@ object ContainerHandler : Loadable { is ShulkerBoxBlockEntity -> { if (sh.typeSafe != ScreenHandlerType.SHULKER_BOX) return - val stacks = blockEntity.stacks + val stacks = blockEntity.stacks() val chunkPos = ChunkPos(pos) (chunkPos.getPlacedContainer(pos) as? PlacedShulkerBoxContainer) ?.update(stacks) @@ -219,7 +216,7 @@ object ContainerHandler : Loadable { slots.forEach { slot -> val stack = slot.stack val index = slot.index - if (stack.item in shulkerBoxes) { + if (stack.item in SHULKER_BOXES) { storedContainers[index] = ShulkerBoxContainer( stack.name.string, @@ -242,33 +239,25 @@ object ContainerHandler : Loadable { private fun ChunkPos.setPlacedContainer(pos: BlockPos, container: PlacedContainer) = placedContainers.getOrPut(this) { mutableMapOf() }.put(pos, container) - private val Inventory.stacks - get() = iterator().asSequence().toList() + private fun Inventory.stacks() = iterator().asSequence().toList() } -@ContainerMarker -context(automatedSafeContext: AutomatedSafeContext) -fun StackSelection.move(destination: Container) = - with(automatedSafeContext) { - findContainer(inventoryConfig.containerSelection) - ?.move(this@move, destination) - ?: false - } - @ContainerMarker context(_: Automated) -fun findContainer(predicate: (Container) -> Boolean) = filteredContainers.find(predicate) +fun findContainer( + selection: ContainerSelection +) = filteredContainers.find(selection) @ContainerMarker context(automated: Automated) fun StackSelection.findContainer( - containerSelection: ContainerSelection = automated.inventoryConfig.containerSelection -) = findContainers(containerSelection).firstOrNull() + selection: ContainerSelection = ContainerSelection.EVERYTHING +) = findContainers(selection).firstOrNull() @ContainerMarker context(automated: Automated) fun StackSelection.findContainers( - containerSelection: ContainerSelection = automated.inventoryConfig.containerSelection + containerSelection: ContainerSelection = ContainerSelection.EVERYTHING ) = filteredContainers .filter { containerSelection.matches(it) } .filter { it.stackCount(this) >= count } @@ -277,33 +266,50 @@ fun StackSelection.findContainers( @ContainerMarker context(automated: Automated) fun StackSelection.findContainerWithSpace( - containerSelection: ContainerSelection = automated.inventoryConfig.containerSelection + containerSelection: ContainerSelection = ContainerSelection.EVERYTHING ) = findContainersWithSpace(containerSelection).firstOrNull() @ContainerMarker context(automated: Automated) fun StackSelection.findContainersWithSpace( - containerSelection: ContainerSelection = automated.inventoryConfig.containerSelection + containerSelection: ContainerSelection = ContainerSelection.EVERYTHING ) = filteredContainers .filter { containerSelection.matches(it) } .filter { it.spaceAvailable(this) >= count } .sortedWith(automated.inventoryConfig.accessPriority.spaceComparator(this)) -@ContainerMarker -context(automated: Automated) -fun StackSelection.findSlot( - containerSelection: ContainerSelection = automated.inventoryConfig.containerSelection -) = findSlots(containerSelection).firstOrNull() - @ContainerMarker context(automated: Automated) fun StackSelection.findSlots( - containerSelection: ContainerSelection = automated.inventoryConfig.containerSelection -) = findContainers(containerSelection).flatMap { filter(it.slots) } + containerSelection: ContainerSelection = ContainerSelection.EVERYTHING +) = + findContainers( + selectContainer { + matches(containerSelection) + isAccessed() + } + ).flatMap { filter(it.slots) } @ContainerMarker context(automated: Automated) -fun findDisposable() = - filteredContainers.find { container -> - automated.inventoryConfig.disposables.any { container.stackCount(it.asItem().select(1)) > 0 } - } \ No newline at end of file +fun findContainerWithDisposable( + containerSelection: ContainerSelection = ContainerSelection.EVERYTHING +) = + with(automated) { + findContainer( + selectContainer { + matches(containerSelection) + custom { container -> + inventoryConfig.disposables.any { container.stackCount(it.asItem().select(1)) > 0 } + } + } + ) + } + +private fun getSearchContainers(scope: ContainerSearchScope): Collection { + +} + +private enum class ContainerSearchScope { + +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/handler/handlers/GlideHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/GlideHandler.kt index 56fe6eb36..87225c54f 100644 --- a/src/main/kotlin/com/lambda/interaction/handler/handlers/GlideHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/GlideHandler.kt @@ -20,9 +20,8 @@ package com.lambda.interaction.handler.handlers import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.inventory.StackAndSlot -import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack -import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer +import com.lambda.interaction.container.selection.StackAndSlot +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.selectStack import com.lambda.module.modules.combat.AutoArmor import com.lambda.module.modules.movement.elytrafly.ElytraFly import com.lambda.module.modules.movement.elytrafly.ElytraFly.mode @@ -171,10 +170,8 @@ object GlideHandler { val fakeFly = ElytraFly.isEnabled && ElytraFly.fakeFly val canGlideAlready = player.canGlideWithChestPiece() != fakeFly if (!autoSwapChecking || canGlideAlready) return canGlideAlready - return HotbarAndInventoryContainer.slots.let { slots -> - if (fakeFly) CHESTPLATE_SELECTION.filter(slots).isNotEmpty() - else ELYTRA_SELECTION.filter(slots).isNotEmpty() - } + val selection = if (fakeFly) CHESTPLATE_SELECTION else ELYTRA_SELECTION + return selection.findContainers(HOTBAR_AND_INVENTORY_CONTAINERS) } private class PendingGlideAction( diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/HotbarAndInventoryContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/HotbarAndInventoryContainer.kt deleted file mode 100644 index 44a6de2b4..000000000 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/HotbarAndInventoryContainer.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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.inventory.container.containers - -import com.lambda.Lambda.mc -import com.lambda.interaction.inventory.container.Container -import com.lambda.interaction.inventory.container.ContainerRank -import com.lambda.util.player.SlotUtils.hotbarAndInventorySlots -import com.lambda.util.player.SlotUtils.hotbarAndInventoryStacks -import com.lambda.util.text.buildText -import com.lambda.util.text.literal -import net.minecraft.item.ItemStack -import net.minecraft.screen.slot.Slot - -object HotbarAndInventoryContainer : Container(ContainerRank.HotbarAndInventory) { - override val slots: List - get() = mc.player?.hotbarAndInventorySlots ?: emptyList() - override var stacks: List - get() = mc.player?.hotbarAndInventoryStacks ?: emptyList() - set(_) {} - - override val description = buildText { literal("Hotbar and Inventory") } -} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/MainHandContainer.kt b/src/main/kotlin/com/lambda/interaction/inventory/container/containers/MainHandContainer.kt deleted file mode 100644 index 39bcc95b7..000000000 --- a/src/main/kotlin/com/lambda/interaction/inventory/container/containers/MainHandContainer.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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.inventory.container.containers - -import com.lambda.Lambda.mc -import com.lambda.context.SafeContext -import com.lambda.interaction.inventory.container.Container -import com.lambda.interaction.inventory.container.ContainerRank -import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder -import com.lambda.util.player.SlotUtils.mainHandSlots -import com.lambda.util.text.buildText -import com.lambda.util.text.literal -import net.minecraft.item.ItemStack -import net.minecraft.screen.slot.Slot - -object MainHandContainer : Container(ContainerRank.MainHand) { - override val slots: List - get() = mc.player?.mainHandSlots ?: emptyList() - override var stacks: List - get() = mc.player?.mainHandStack?.let { listOf(it) } ?: emptyList() - set(_) {} - - override val swapMethodPriority = 10 - - override val description = buildText { literal("MainHand") } - - context(safeContext: SafeContext) - override fun InvRequestBuilder.swap(fromHere: Slot, toSlot: Slot) { - swapWithHotbar(toSlot.id, safeContext.player.inventory.selectedSlot) - } -} diff --git a/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakManager.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakManager.kt index 33c04927c..64ab9cd82 100644 --- a/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakManager.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakManager.kt @@ -34,6 +34,7 @@ import com.lambda.interaction.construction.simulation.context.BreakContext import com.lambda.interaction.construction.simulation.result.results.BreakResult import com.lambda.interaction.construction.simulation.sim import com.lambda.interaction.construction.verify.TargetState +import com.lambda.interaction.container.selection.StackSelection import com.lambda.interaction.handler.handlers.breaking.BrokenBlockHandler import com.lambda.interaction.handler.handlers.breaking.BrokenBlockHandler.destroyBlock import com.lambda.interaction.handler.handlers.breaking.BrokenBlockHandler.pendingActions @@ -43,7 +44,6 @@ import com.lambda.interaction.handler.handlers.breaking.RebreakHandler.getRebrea import com.lambda.interaction.handler.handlers.breaking.RebreakResult import com.lambda.interaction.handler.handlers.packet.PacketLimitHandler import com.lambda.interaction.handler.handlers.packet.PacketType -import com.lambda.interaction.inventory.StackSelection import com.lambda.interaction.manager.Manager import com.lambda.interaction.manager.ManagerUtils.isPosBlocked import com.lambda.interaction.manager.PositionBlocking diff --git a/src/main/kotlin/com/lambda/module/modules/combat/AutoArmor.kt b/src/main/kotlin/com/lambda/module/modules/combat/AutoArmor.kt index b338e2602..bbb40e5ce 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/AutoArmor.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/AutoArmor.kt @@ -25,8 +25,7 @@ import com.lambda.config.withEdits import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.inventory.container.containers.ArmorContainer -import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer +import com.lambda.interaction.container.containers.ArmorContainer import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag diff --git a/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt b/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt index ee8fe19c6..40ff8980a 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt @@ -23,8 +23,9 @@ import com.lambda.config.withEdits import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.container.selection.select import com.lambda.interaction.handler.handlers.FriendHandler -import com.lambda.interaction.inventory.select +import com.lambda.interaction.inventory.container.select import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag diff --git a/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt b/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt index bff695058..09a6e4a44 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt @@ -27,10 +27,9 @@ import com.lambda.context.SafeContext import com.lambda.event.events.EntityEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.handler.handlers.move -import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack -import com.lambda.interaction.inventory.container.containers.HotbarContainer -import com.lambda.interaction.inventory.container.containers.OffHandContainer +import com.lambda.interaction.container.containers.HotbarContainer +import com.lambda.interaction.container.containers.OffHandContainer +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.selectStack import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotationTo import com.lambda.interaction.manager.managers.rotating.RotationManager diff --git a/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt b/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt index 5f9713ab1..8bee87050 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt @@ -28,8 +28,8 @@ import com.lambda.context.SafeContext import com.lambda.event.events.InventoryEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack -import com.lambda.interaction.inventory.container.containers.HotbarContainer +import com.lambda.interaction.container.containers.HotbarContainer +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.selectStack import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module diff --git a/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt b/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt index 1f949ba97..761c0bf67 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt @@ -25,7 +25,6 @@ import com.lambda.context.SafeContext import com.lambda.interaction.construction.blueprint.TickingBlueprint.Companion.tickingBlueprint import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.handler.handlers.FriendHandler.isFriend -import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.task.Task diff --git a/src/main/kotlin/com/lambda/module/modules/combat/Surround.kt b/src/main/kotlin/com/lambda/module/modules/combat/Surround.kt index d54b1285d..fe24c73f8 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/Surround.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/Surround.kt @@ -23,7 +23,6 @@ import com.lambda.config.hideBlock import com.lambda.config.withEdits import com.lambda.interaction.construction.blueprint.TickingBlueprint.Companion.tickingBlueprint import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer import com.lambda.module.Module import com.lambda.module.modules.combat.PlayerTrap.getTrapPositions import com.lambda.module.tag.ModuleTag diff --git a/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt b/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt index d4b479b3c..5bcea7fb9 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt @@ -19,7 +19,7 @@ package com.lambda.module.modules.debug import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.inventory.select +import com.lambda.interaction.container.selection.select import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.task.start diff --git a/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt b/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt index 679ed9a6d..2298bf1f2 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt @@ -28,12 +28,11 @@ import com.lambda.config.withEdits import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.container.containers.HotbarContainer +import com.lambda.interaction.container.containers.InventoryContainer +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.selectStack +import com.lambda.interaction.container.selection.select import com.lambda.interaction.handler.handlers.GlideHandler -import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack -import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer -import com.lambda.interaction.inventory.container.containers.HotbarContainer -import com.lambda.interaction.inventory.container.containers.InventoryContainer -import com.lambda.interaction.inventory.select import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module diff --git a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt index c13b358c9..c2f816622 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt @@ -24,9 +24,8 @@ import com.lambda.event.Muteable import com.lambda.event.events.PacketEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.container.containers.ArmorContainer import com.lambda.interaction.handler.handlers.GlideHandler.ELYTRA_SELECTION -import com.lambda.interaction.inventory.container.containers.ArmorContainer -import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest diff --git a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/GrimControlElytraFly.kt b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/GrimControlElytraFly.kt index e435fdf57..bf3bc8b28 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/GrimControlElytraFly.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/GrimControlElytraFly.kt @@ -22,9 +22,9 @@ import com.lambda.event.events.PacketEvent import com.lambda.event.events.PlayerPacketEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.inventory.container.containers.HotbarContainer -import com.lambda.interaction.inventory.container.containers.InventoryContainer -import com.lambda.interaction.inventory.select +import com.lambda.interaction.container.containers.HotbarContainer +import com.lambda.interaction.container.containers.InventoryContainer +import com.lambda.interaction.container.selection.select import com.lambda.interaction.manager.managers.rotating.RotationManager import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.modules.movement.BetterFirework.startFirework diff --git a/src/main/kotlin/com/lambda/module/modules/player/AutoElytraSwap.kt b/src/main/kotlin/com/lambda/module/modules/player/AutoElytraSwap.kt index 92fc44463..2bde07f67 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/AutoElytraSwap.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/AutoElytraSwap.kt @@ -18,12 +18,11 @@ package com.lambda.module.modules.player import com.lambda.context.SafeContext +import com.lambda.interaction.container.containers.ArmorContainer import com.lambda.interaction.handler.handlers.GlideHandler.CHESTPLATE_SELECTION import com.lambda.interaction.handler.handlers.GlideHandler.ELYTRA_SELECTION import com.lambda.interaction.handler.handlers.GlideHandler.manuallySwapped import com.lambda.interaction.handler.handlers.GlideHandler.swapped -import com.lambda.interaction.inventory.container.containers.ArmorContainer -import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module import com.lambda.module.modules.combat.AutoArmor diff --git a/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt b/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt index ce8c6a92d..cb82dbc01 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt @@ -23,20 +23,21 @@ import com.lambda.config.withEdits import com.lambda.event.events.InventoryEvent import com.lambda.event.events.PlayerEvent import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.container.NestedContainer +import com.lambda.interaction.container.OpenedContainerContext +import com.lambda.interaction.container.containers.external.EnderChestContainer import com.lambda.interaction.handler.handlers.ContainerHandler -import com.lambda.interaction.inventory.container.NestedContainer -import com.lambda.interaction.inventory.container.OpenedContainerContext -import com.lambda.interaction.inventory.container.containers.external.EnderChestContainer import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.task.Task import com.lambda.task.start import com.lambda.task.tasks.wrappers.then -import com.lambda.util.item.ItemUtils.shulkerBoxes +import com.lambda.util.item.ItemUtils.SHULKER_BOXES import net.minecraft.item.Items import net.minecraft.screen.ScreenHandler import net.minecraft.screen.slot.SlotActionType import java.util.* +import kotlin.collections.fold @Suppress("unused") object InventoryTweaks : Module( @@ -63,7 +64,7 @@ object InventoryTweaks : Module( val stack = slot.stack when (stack.item) { - in shulkerBoxes if (!instantShulker) -> return@listen + in SHULKER_BOXES if (!instantShulker) -> return@listen Items.ENDER_CHEST if (!instantEChest) -> return@listen } diff --git a/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt b/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt index dabfd131b..fb411189d 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt @@ -24,8 +24,8 @@ import com.lambda.config.withEdits import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.inventory.container.containers.HotbarContainer -import com.lambda.interaction.inventory.container.containers.InventoryContainer +import com.lambda.interaction.container.containers.HotbarContainer +import com.lambda.interaction.container.containers.InventoryContainer import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag diff --git a/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt b/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt index c7746137b..77de50df6 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt @@ -23,8 +23,8 @@ import com.lambda.config.withEdits import com.lambda.event.events.ContainerEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.inventory.container.containers.HotbarContainer -import com.lambda.interaction.inventory.container.containers.InventoryContainer +import com.lambda.interaction.container.containers.HotbarContainer +import com.lambda.interaction.container.containers.InventoryContainer import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag diff --git a/src/main/kotlin/com/lambda/module/modules/render/ContainerPreview.kt b/src/main/kotlin/com/lambda/module/modules/render/ContainerPreview.kt index deef6f9ce..66634e981 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/ContainerPreview.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/ContainerPreview.kt @@ -21,7 +21,7 @@ import com.lambda.Lambda.mc import com.lambda.config.Tab import com.lambda.config.entries.Setting.Companion.onValueChange import com.lambda.config.settings.complex.Bind -import com.lambda.interaction.inventory.container.containers.external.EnderChestContainer +import com.lambda.interaction.container.containers.external.EnderChestContainer import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.threading.runSafe @@ -29,8 +29,8 @@ import com.lambda.util.InputUtils.isSatisfied import com.lambda.util.KeyCode import com.lambda.util.item.ItemStackUtils.bundleStacks import com.lambda.util.item.ItemStackUtils.shulkerBoxStacks -import com.lambda.util.item.ItemUtils.bundles -import com.lambda.util.item.ItemUtils.shulkerBoxes +import com.lambda.util.item.ItemUtils.BUNDLES +import com.lambda.util.item.ItemUtils.SHULKER_BOXES import com.lambda.util.text.buildText import com.lambda.util.text.literal import net.minecraft.block.ShulkerBoxBlock @@ -379,7 +379,7 @@ object ContainerPreview : Module( } @JvmStatic - fun isShulkerBox(stack: ItemStack) = stack.item in shulkerBoxes + fun isShulkerBox(stack: ItemStack) = stack.item in SHULKER_BOXES @JvmStatic fun isEnderChest(stack: ItemStack) = stack.item == Items.ENDER_CHEST && EnderChestContainer.stacks.isNotEmpty() @@ -388,7 +388,7 @@ object ContainerPreview : Module( fun isPreviewableContainer(stack: ItemStack) = isShulkerBox(stack) || isEnderChest(stack) @JvmStatic - fun isBundle(stack: ItemStack) = stack.item in bundles + fun isBundle(stack: ItemStack) = stack.item in BUNDLES @JvmStatic fun drawOnItem(drawContext: DrawContext, state: GuiRenderState, entity: LivingEntity?, world: World?, stack: ItemStack, x: Int, y: Int, seed: Int) { diff --git a/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt b/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt index 4fa82ba7f..e9154a9f8 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt @@ -35,10 +35,9 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.graphics.mc.renderer.ImmediateRenderer.Companion.immediateRenderer import com.lambda.graphics.util.DirectionMask import com.lambda.interaction.construction.verify.TargetState +import com.lambda.interaction.container.containers.HotbarContainer +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.selectStack import com.lambda.interaction.handler.handlers.BaritoneHandler -import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack -import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer -import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module diff --git a/src/main/kotlin/com/lambda/module/modules/world/MapDownloader.kt b/src/main/kotlin/com/lambda/module/modules/world/MapDownloader.kt index 4c6ed0869..7ced3f6a3 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/MapDownloader.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/MapDownloader.kt @@ -19,7 +19,6 @@ package com.lambda.module.modules.world import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.inventory.container.containers.PlayerContainer import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.FileUtils.locationBoundDirectory 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 dd30460a3..b0e8f5c6d 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/Scaffold.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/Scaffold.kt @@ -30,8 +30,8 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.construction.simulation.context.BuildContext import com.lambda.interaction.construction.simulation.sim import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.inventory.StackSelectionBuilder.Companion.selectStack -import com.lambda.interaction.inventory.container.containers.HotbarContainer +import com.lambda.interaction.container.containers.HotbarContainer +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.selectStack import com.lambda.interaction.manager.managers.interacting.interactRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag 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 e3b98d24f..804f3648d 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt @@ -38,12 +38,11 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.event.listener.UnsafeListener.Companion.listenUnsafe import com.lambda.graphics.mc.renderer.TickedRenderer.Companion.tickedRenderer import com.lambda.interaction.construction.verify.TargetState +import com.lambda.interaction.container.containers.HotbarContainer +import com.lambda.interaction.container.containers.InventoryContainer +import com.lambda.interaction.container.containers.OffHandContainer +import com.lambda.interaction.container.containers.external.EnderChestContainer import com.lambda.interaction.handler.handlers.BaritoneHandler -import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer -import com.lambda.interaction.inventory.container.containers.HotbarContainer -import com.lambda.interaction.inventory.container.containers.InventoryContainer -import com.lambda.interaction.inventory.container.containers.OffHandContainer -import com.lambda.interaction.inventory.container.containers.external.EnderChestContainer import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.interaction.manager.managers.rotating.Rotation diff --git a/src/main/kotlin/com/lambda/task/tasks/AcquirePlacedBlockTask.kt b/src/main/kotlin/com/lambda/task/tasks/AcquirePlacedBlockTask.kt index 1bef1abf5..71c0fb03b 100644 --- a/src/main/kotlin/com/lambda/task/tasks/AcquirePlacedBlockTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/AcquirePlacedBlockTask.kt @@ -21,8 +21,8 @@ import com.lambda.context.Automated import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.container.selection.select import com.lambda.interaction.handler.handlers.findSlots -import com.lambda.interaction.inventory.select import com.lambda.task.Task import com.lambda.task.Task.Ta5kBuilder import com.lambda.task.tasks.wrappers.softFail diff --git a/src/main/kotlin/com/lambda/task/tasks/AcquireStackTask.kt b/src/main/kotlin/com/lambda/task/tasks/AcquireStackTask.kt index e50591500..e8db24cd2 100644 --- a/src/main/kotlin/com/lambda/task/tasks/AcquireStackTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/AcquireStackTask.kt @@ -19,9 +19,9 @@ package com.lambda.task.tasks import com.lambda.context.Automated import com.lambda.context.SafeContext +import com.lambda.interaction.container.containers.HotbarContainer +import com.lambda.interaction.container.selection.StackSelection import com.lambda.interaction.handler.handlers.findContainer -import com.lambda.interaction.inventory.StackSelection -import com.lambda.interaction.inventory.container.containers.HotbarContainer import com.lambda.task.Task import com.lambda.task.Task.Ta5kBuilder import com.lambda.threading.runSafeAutomated diff --git a/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt b/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt index 7287e2c89..c165f9000 100644 --- a/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt @@ -23,7 +23,6 @@ import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.handler.handlers.BaritoneHandler -import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.task.Task import com.lambda.task.Task.Ta5kBuilder diff --git a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt index 65d05f7d6..275a02318 100644 --- a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt @@ -21,12 +21,12 @@ import com.lambda.context.Automated import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.container.Container +import com.lambda.interaction.container.ExternalContainer +import com.lambda.interaction.container.OpenedContainerContext +import com.lambda.interaction.container.selection.ContainerSelection +import com.lambda.interaction.container.selection.StackSelection import com.lambda.interaction.handler.handlers.findContainer -import com.lambda.interaction.inventory.StackSelection -import com.lambda.interaction.inventory.container.Container -import com.lambda.interaction.inventory.container.ExternalContainer -import com.lambda.interaction.inventory.container.OpenedContainerContext -import com.lambda.interaction.inventory.container.containers.HotbarAndInventoryContainer import com.lambda.task.Task import com.lambda.task.Task.Ta5kBuilder import com.lambda.task.tasks.wrappers.taskOrNull @@ -38,54 +38,53 @@ import net.minecraft.screen.slot.Slot @Ta5kBuilder context(automated: Automated) fun transfer( - stackSelection: StackSelection, - fromContainer: Container, - toContainer: Container -) = ContainerTransferTask(fromContainer, toContainer, stackSelection, automated) - -@Ta5kBuilder -@JvmName("transferByStackSelection") -context(automated: Automated) -fun transfer( - stackSelection: StackSelection, - toContainer: Container -) = transfer(stackSelection, stackSelection.findContainer() ?: HotbarAndInventoryContainer, toContainer) + selection: StackSelection, + fromSelection: ContainerSelection = ContainerSelection.EVERYTHING, + toSelection: ContainerSelection +) = ContainerTransferTask(selection, fromSelection, toSelection, automated) @Ta5kBuilder @JvmName("transferExt") context(automated: Automated) fun StackSelection.transferTo( - toContainer: Container -) = transfer(this, findContainer() ?: HotbarAndInventoryContainer, toContainer) + fromSelection: ContainerSelection = ContainerSelection.EVERYTHING, + toSelection: ContainerSelection +) = transfer(this, fromSelection, toSelection) class ContainerTransferTask @Ta5kBuilder internal constructor( - private var fromContainer: Container, - private val toContainer: Container, private val selection: StackSelection, + private val fromSelection: ContainerSelection, + private val toSelection: ContainerSelection, automated: Automated ) : Task(), Automated by automated { - override val name = "Transferring $selection from $fromContainer to $toContainer" + override val name = "Transferring $selection from $fromSelection to $toSelection" override fun SafeContext.onStart() { + val fromContainer = findContainer(containerSelection) + ?: run { + failure(IllegalStateException("Could not find container to pull $selection from")) + return + } when { fromContainer.isAccessed && toContainer.isAccessed -> TransferTask() .onSuccess { success(it) } fromContainer !is ExternalContainer -> - openTransferClose(toContainer, fromContainer, toContainer) + openTransferClose(toContainer, toContainer, containerSelection) toContainer !is ExternalContainer -> - openTransferClose(fromContainer, fromContainer, toContainer) + openTransferClose(fromContainer, toContainer, containerSelection) - else -> + else -> { taskOrNull { fromContainer.access() } .then { fromCtx -> - transfer(selection, fromContainer, HotbarAndInventoryContainer) + transfer(selection, toContainer, ContainerSelection.HOTBAR_AND_INVENTORY) .thenOrNull { fromCtx?.close() } } .thenOrNull { toContainer.access() } - .then { toCtx -> transferAndClose(toCtx, HotbarAndInventoryContainer, toContainer) } + .then { toCtx -> transferAndClose(toCtx, toContainer, ContainerSelection.HOTBAR_AND_INVENTORY) } + } }?.start() } @@ -95,49 +94,76 @@ class ContainerTransferTask @Ta5kBuilder internal constructor( * Used when exactly one of the two containers is external and needs to be accessed. */ @Ta5kBuilder - private fun openTransferClose(containerToOpen: Container, from: Container, to: Container) = - containerToOpen - .access() - ?.then { ctx -> transferAndClose(ctx, from, to) } + private fun openTransferClose( + containerToOpen: Container, + to: Container, + containerSelection: ContainerSelection + ) = containerToOpen + .access() + ?.then { ctx -> transferAndClose(ctx, to, containerSelection) } /** * Transfers [selection] from [from] to [to], then closes the container via [ctx]. */ @Ta5kBuilder - private fun transferAndClose(ctx: OpenedContainerContext?, from: Container, to: Container) = - transfer(selection, from, to) - .thenOrNull { slot -> - ctx - ?.close() - ?.onSuccess { success(slot) } - } + private fun transferAndClose( + ctx: OpenedContainerContext?, + fromSelection: ContainerSelection, + toSelection: ContainerSelection + ) = transfer(selection, fromSelection, toSelection) + .thenOrNull { slot -> + ctx + ?.close() + ?.onSuccess { success(slot) } + } private inner class TransferTask @Ta5kBuilder constructor() : Task() { override val name = "Transferring" + private var transferred = 0 + private var lastSlot: Slot? = null + init { listen { runSafeAutomated { - val (fromSlot, toSlot) = fromContainer.getTransferSlots(selection, toContainer) - if (fromSlot == null) { - checkFail() - return@listen + while (selection.count == 0 || transferred < selection.count) { + val (fromSlot, toSlot) = fromContainer.getTransferSlots(selection, toContainer) + + if (fromSlot == null || toSlot == null) { + lastSlot?.let { slot -> + if (selection.count == 0 || transferred >= selection.count) { + success(slot) + return@listen + } + } + if (fromSlot == null) checkFail() + else failure("Unable to find a slot to transfer to.") + return@listen + } + + val moveCount = fromSlot.stack.count + if (!fromContainer.swap(fromSlot, toSlot, toContainer)) { + checkFail() + return@listen + } + + transferred += moveCount + lastSlot = toSlot + + // count == 0 means single stack transfer + if (selection.count == 0) { + success(toSlot) + return@listen + } } - if (toSlot == null) { - failure("Unable to find a slot to transfer to.") - return@listen - } - val transferSuccessful = fromContainer.swap(fromSlot, toSlot, toContainer) - if (transferSuccessful) success(toSlot) - else checkFail() + + lastSlot?.let { success(it) } ?: checkFail() } } } private fun checkFail() { - failure(NoMaterialAccessException(selection)) + failure(IllegalStateException("Unable to access $selection")) } - - private inner class NoMaterialAccessException(stackSelection: StackSelection) : IllegalStateException("Unable to access $stackSelection.") } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/task/tasks/EatTask.kt b/src/main/kotlin/com/lambda/task/tasks/EatTask.kt index 33fa0172e..088a8e79c 100644 --- a/src/main/kotlin/com/lambda/task/tasks/EatTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/EatTask.kt @@ -23,8 +23,8 @@ import com.lambda.context.Automated import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.inventory.container.containers.HotbarContainer -import com.lambda.interaction.inventory.container.containers.InventoryContainer +import com.lambda.interaction.container.containers.HotbarContainer +import com.lambda.interaction.container.containers.InventoryContainer import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest import com.lambda.task.Task import com.lambda.task.Task.Ta5kBuilder diff --git a/src/main/kotlin/com/lambda/task/tasks/PlaceContainerTask.kt b/src/main/kotlin/com/lambda/task/tasks/PlaceContainerTask.kt index 9311f9a5b..58a1f0c88 100644 --- a/src/main/kotlin/com/lambda/task/tasks/PlaceContainerTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/PlaceContainerTask.kt @@ -30,7 +30,7 @@ import com.lambda.task.Task import com.lambda.task.Task.Ta5kBuilder import com.lambda.threading.runSafeAutomated import com.lambda.util.BlockUtils.blockPos -import com.lambda.util.item.ItemUtils.shulkerBoxes +import com.lambda.util.item.ItemUtils.SHULKER_BOXES import com.lambda.util.math.distSq import net.minecraft.block.ChestBlock import net.minecraft.entity.mob.ShulkerEntity @@ -99,7 +99,7 @@ class PlaceContainerTask @Ta5kBuilder internal constructor( Items.ENDER_CHEST -> { !ChestBlock.isChestBlocked(world, blockPos) } - in shulkerBoxes -> { + in SHULKER_BOXES -> { val box = ShulkerEntity .calculateBoundingBox(0.5f, direction, 0.0f, blockPos.toBottomCenterPos()) .offset(blockPos) diff --git a/src/main/kotlin/com/lambda/util/item/ItemUtils.kt b/src/main/kotlin/com/lambda/util/item/ItemUtils.kt index a3485c5ea..361a26c8e 100644 --- a/src/main/kotlin/com/lambda/util/item/ItemUtils.kt +++ b/src/main/kotlin/com/lambda/util/item/ItemUtils.kt @@ -17,8 +17,7 @@ package com.lambda.util.item -import com.lambda.context.SafeContext -import com.lambda.interaction.inventory.container.containers.ArmorContainer +import com.lambda.interaction.container.containers.ArmorContainer import net.minecraft.block.Block import net.minecraft.component.DataComponentTypes import net.minecraft.item.BlockItem @@ -27,128 +26,142 @@ import net.minecraft.item.ItemStack import net.minecraft.item.Items object ItemUtils { - val pickaxes = setOf( - Items.WOODEN_PICKAXE, - Items.STONE_PICKAXE, - Items.IRON_PICKAXE, - Items.GOLDEN_PICKAXE, - Items.DIAMOND_PICKAXE, - Items.NETHERITE_PICKAXE, - ) - - val shovels = setOf( - Items.WOODEN_SHOVEL, - Items.STONE_SHOVEL, - Items.IRON_SHOVEL, - Items.GOLDEN_SHOVEL, - Items.DIAMOND_SHOVEL, - Items.NETHERITE_SHOVEL, - ) - - val axes = setOf( - Items.WOODEN_AXE, - Items.STONE_AXE, - Items.IRON_AXE, - Items.GOLDEN_AXE, - Items.DIAMOND_AXE, - Items.NETHERITE_AXE, - ) - - val hoes = setOf( - Items.WOODEN_HOE, - Items.STONE_HOE, - Items.IRON_HOE, - Items.GOLDEN_HOE, - Items.DIAMOND_HOE, - Items.NETHERITE_HOE, - ) - - val swords = setOf( - Items.WOODEN_SWORD, - Items.STONE_SWORD, - Items.IRON_SWORD, - Items.GOLDEN_SWORD, - Items.DIAMOND_SWORD, - Items.NETHERITE_SWORD, - ) - - val misc = setOf( - Items.SHEARS, - Items.FLINT_AND_STEEL, - ) - - val tools = pickaxes + shovels + axes + hoes + swords + misc - - val shulkerBoxes = setOf( - Items.SHULKER_BOX, - Items.WHITE_SHULKER_BOX, - Items.ORANGE_SHULKER_BOX, - Items.MAGENTA_SHULKER_BOX, - Items.LIGHT_BLUE_SHULKER_BOX, - Items.YELLOW_SHULKER_BOX, - Items.LIME_SHULKER_BOX, - Items.PINK_SHULKER_BOX, - Items.GRAY_SHULKER_BOX, - Items.LIGHT_GRAY_SHULKER_BOX, - Items.CYAN_SHULKER_BOX, - Items.PURPLE_SHULKER_BOX, - Items.BLUE_SHULKER_BOX, - Items.BROWN_SHULKER_BOX, - Items.GREEN_SHULKER_BOX, - Items.RED_SHULKER_BOX, - Items.BLACK_SHULKER_BOX, - ) - - val bundles = setOf( - Items.BUNDLE, - Items.WHITE_BUNDLE, - Items.ORANGE_BUNDLE, - Items.MAGENTA_BUNDLE, - Items.LIGHT_BLUE_BUNDLE, - Items.YELLOW_BUNDLE, - Items.LIME_BUNDLE, - Items.PINK_BUNDLE, - Items.GRAY_BUNDLE, - Items.LIGHT_GRAY_BUNDLE, - Items.CYAN_BUNDLE, - Items.PURPLE_BUNDLE, - Items.BLUE_BUNDLE, - Items.BROWN_BUNDLE, - Items.GREEN_BUNDLE, - Items.RED_BUNDLE, - Items.BLACK_BUNDLE - ) - - val chests = setOf( - Items.CHEST, - Items.TRAPPED_CHEST, - Items.ENDER_CHEST, - Items.BARREL, - ) - - val defaultDisposables = setOf( - Items.DIRT, - Items.GRASS_BLOCK, - Items.COBBLESTONE, - Items.GRANITE, - Items.DIORITE, - Items.ANDESITE, - Items.SANDSTONE, - Items.RED_SANDSTONE, - Items.NETHERRACK, - Items.END_STONE, - Items.STONE, - Items.BASALT, - Items.BLACKSTONE, - Items.COBBLED_DEEPSLATE - ) + val PICKAXES = + setOf( + Items.WOODEN_PICKAXE, + Items.STONE_PICKAXE, + Items.COPPER_PICKAXE, + Items.IRON_PICKAXE, + Items.GOLDEN_PICKAXE, + Items.DIAMOND_PICKAXE, + Items.NETHERITE_PICKAXE, + ) + + val SHOVELS = + setOf( + Items.WOODEN_SHOVEL, + Items.STONE_SHOVEL, + Items.COPPER_SHOVEL, + Items.IRON_SHOVEL, + Items.GOLDEN_SHOVEL, + Items.DIAMOND_SHOVEL, + Items.NETHERITE_SHOVEL, + ) + + val AXES = + setOf( + Items.WOODEN_AXE, + Items.STONE_AXE, + Items.COPPER_AXE, + Items.IRON_AXE, + Items.GOLDEN_AXE, + Items.DIAMOND_AXE, + Items.NETHERITE_AXE, + ) + + val HOES = + setOf( + Items.WOODEN_HOE, + Items.STONE_HOE, + Items.COPPER_HOE, + Items.IRON_HOE, + Items.GOLDEN_HOE, + Items.DIAMOND_HOE, + Items.NETHERITE_HOE, + ) + + val SWORDS = + setOf( + Items.WOODEN_SWORD, + Items.STONE_SWORD, + Items.COPPER_SWORD, + Items.IRON_SWORD, + Items.GOLDEN_SWORD, + Items.DIAMOND_SWORD, + Items.NETHERITE_SWORD, + ) + + val MISC = + setOf( + Items.SHEARS, + Items.FLINT_AND_STEEL, + ) + + val TOOLS = PICKAXES + SHOVELS + AXES + HOES + SWORDS + MISC + + val SHULKER_BOXES = + setOf( + Items.SHULKER_BOX, + Items.WHITE_SHULKER_BOX, + Items.ORANGE_SHULKER_BOX, + Items.MAGENTA_SHULKER_BOX, + Items.LIGHT_BLUE_SHULKER_BOX, + Items.YELLOW_SHULKER_BOX, + Items.LIME_SHULKER_BOX, + Items.PINK_SHULKER_BOX, + Items.GRAY_SHULKER_BOX, + Items.LIGHT_GRAY_SHULKER_BOX, + Items.CYAN_SHULKER_BOX, + Items.PURPLE_SHULKER_BOX, + Items.BLUE_SHULKER_BOX, + Items.BROWN_SHULKER_BOX, + Items.GREEN_SHULKER_BOX, + Items.RED_SHULKER_BOX, + Items.BLACK_SHULKER_BOX, + ) + + val BUNDLES = + setOf( + Items.BUNDLE, + Items.WHITE_BUNDLE, + Items.ORANGE_BUNDLE, + Items.MAGENTA_BUNDLE, + Items.LIGHT_BLUE_BUNDLE, + Items.YELLOW_BUNDLE, + Items.LIME_BUNDLE, + Items.PINK_BUNDLE, + Items.GRAY_BUNDLE, + Items.LIGHT_GRAY_BUNDLE, + Items.CYAN_BUNDLE, + Items.PURPLE_BUNDLE, + Items.BLUE_BUNDLE, + Items.BROWN_BUNDLE, + Items.GREEN_BUNDLE, + Items.RED_BUNDLE, + Items.BLACK_BUNDLE + ) + + val CHESTS = + setOf( + Items.CHEST, + Items.COPPER_CHEST, + Items.TRAPPED_CHEST, + Items.ENDER_CHEST + ) + + val DEFAULT_DISPOSABLES = + setOf( + Items.DIRT, + Items.GRASS_BLOCK, + Items.COBBLESTONE, + Items.GRANITE, + Items.DIORITE, + Items.ANDESITE, + Items.SANDSTONE, + Items.RED_SANDSTONE, + Items.NETHERRACK, + Items.END_STONE, + Items.STONE, + Items.BASALT, + Items.BLACKSTONE, + Items.COBBLED_DEEPSLATE + ) val Item.block: Block get() = Block.getBlockFromItem(this) val ItemStack.blockItem get() = item as? BlockItem val Item.nutrition: Int get() = components.get(DataComponentTypes.FOOD)?.nutrition ?: 0 - context(safeContext: SafeContext) val ItemStack.armorSlot get() = ArmorContainer.slots.firstOrNull { it.canInsert(this) } From b7dfcded22ab61267ac88dc63559800557802c63 Mon Sep 17 00:00:00 2001 From: beanbag44 <107891830+beanbag44@users.noreply.github.com> Date: Tue, 8 Sep 2026 15:27:58 +0100 Subject: [PATCH 14/43] checkpoint --- .../mixin/entity/LivingEntityMixin.java | 1 - .../command/commands/TransferCommand.kt | 62 +-- .../kotlin/com/lambda/config/ConfigEditor.kt | 2 +- .../config/automation/AutomationConfig.kt | 30 +- .../lambda/config/blocks/InventoryConfig.kt | 8 +- .../com/lambda/config/entries/Setting.kt | 61 +-- .../serializers/PlacedContainerSerializer.kt | 115 ++++ .../graphics/mc/renderer/ChunkedRenderer.kt | 8 +- .../com/lambda/graphics/text/FontHandler.kt | 2 +- src/main/kotlin/com/lambda/gui/MenuBar.kt | 2 +- .../lambda/gui/components/ClickGuiLayout.kt | 9 +- .../simulation/result/results/BreakResult.kt | 5 +- .../result/results/GenericResult.kt | 5 +- .../construction/simulation/sims/BreakSim.kt | 3 +- .../simulation/sims/InteractSim.kt | 20 +- .../construction/verify/TargetState.kt | 19 +- .../lambda/interaction/container/Container.kt | 73 ++- .../container/ContainerSerializer.kt | 115 ++++ .../container/containers/ArmorContainer.kt | 2 +- .../container/containers/CreativeContainer.kt | 39 +- .../container/containers/CursorContainer.kt | 2 +- .../external/ShulkerBoxContainer.kt | 21 +- .../containers/external/StashContainer.kt | 13 +- .../container/selection/ContainerSelection.kt | 68 ++- .../container/selection/StackSelection.kt | 38 +- .../handler/handlers/BaritoneHandler.kt | 2 +- .../handler/handlers/ContainerHandler.kt | 513 ++++++++++-------- .../handler/handlers/GlideHandler.kt | 10 +- .../InteractedBlockHandler.kt | 6 +- .../{packet => }/PacketLimitHandler.kt | 2 +- .../handlers/breaking/RebreakHandler.kt | 4 +- .../manager/managers/breaking/BreakManager.kt | 4 +- .../managers/interacting/InteractManager.kt | 8 +- .../managers/inventory/InventoryManager.kt | 6 +- .../container/containers/ChestContainer.kt | 0 .../containers/EnderChestContainer.kt | 0 .../containers/ShulkerBoxContainer.kt | 0 .../kotlin/com/lambda/module/HudModule.kt | 2 +- src/main/kotlin/com/lambda/module/Module.kt | 5 +- .../com/lambda/module/{tag => }/ModuleTag.kt | 6 +- .../com/lambda/module/hud/AccountName.kt | 2 +- .../kotlin/com/lambda/module/hud/Baritone.kt | 2 +- .../com/lambda/module/hud/Coordinates.kt | 2 +- src/main/kotlin/com/lambda/module/hud/Fps.kt | 2 +- .../com/lambda/module/hud/ModuleList.kt | 2 +- .../kotlin/com/lambda/module/hud/Rotation.kt | 2 +- .../com/lambda/module/hud/Speedometer.kt | 2 +- .../com/lambda/module/hud/TaskFlowHud.kt | 2 +- src/main/kotlin/com/lambda/module/hud/Tps.kt | 2 +- .../kotlin/com/lambda/module/hud/Watermark.kt | 2 +- .../lambda/module/modules/chat/AntiSpam.kt | 2 +- .../module/modules/chat/ChatTimestamp.kt | 4 +- .../lambda/module/modules/chat/CustomChat.kt | 2 +- .../lambda/module/modules/chat/FancyChat.kt | 2 +- .../module/modules/chat/FriendHighlight.kt | 4 +- .../module/modules/client/AutoUpdater.kt | 2 +- .../com/lambda/module/modules/client/Capes.kt | 2 +- .../lambda/module/modules/client/Client.kt | 2 +- .../lambda/module/modules/client/Discord.kt | 2 +- .../module/modules/client/ModuleNotifier.kt | 2 +- .../lambda/module/modules/combat/AutoArmor.kt | 30 +- .../lambda/module/modules/combat/AutoTotem.kt | 3 +- .../lambda/module/modules/combat/Criticals.kt | 2 +- .../module/modules/combat/CrystalAura.kt | 45 +- .../module/modules/combat/FakePlayer.kt | 2 +- .../lambda/module/modules/combat/KillAura.kt | 4 +- .../module/modules/combat/PlayerTrap.kt | 41 +- .../lambda/module/modules/combat/Surround.kt | 22 +- .../combat/autodisconnect/AutoDisconnect.kt | 2 +- .../module/modules/debug/BaritoneTest.kt | 2 +- .../lambda/module/modules/debug/BlockTest.kt | 2 +- .../module/modules/debug/ContainerTest.kt | 2 +- .../modules/debug/DebugRendererModule.kt | 2 +- .../lambda/module/modules/debug/FallTest.kt | 2 +- .../module/modules/debug/InventoryDebug.kt | 2 +- .../module/modules/debug/PropertyPrinter.kt | 2 +- .../lambda/module/modules/debug/RenderTest.kt | 2 +- .../modules/debug/RendererTestModule.kt | 2 +- .../module/modules/debug/RotationTest.kt | 2 +- .../modules/debug/SettingsTestModule.kt | 2 +- .../lambda/module/modules/debug/SilentSwap.kt | 4 +- .../lambda/module/modules/debug/StateInfo.kt | 2 +- .../lambda/module/modules/debug/TimerTest.kt | 2 +- .../module/modules/movement/AutoSpiral.kt | 2 +- .../module/modules/movement/AutoWalk.kt | 2 +- .../module/modules/movement/BackTrack.kt | 2 +- .../module/modules/movement/BetterFirework.kt | 4 +- .../lambda/module/modules/movement/Blink.kt | 2 +- .../modules/movement/ElytraAltitudeControl.kt | 4 +- .../module/modules/movement/EntityControl.kt | 2 +- .../lambda/module/modules/movement/Jesus.kt | 2 +- .../lambda/module/modules/movement/NoFall.kt | 2 +- .../module/modules/movement/NoJumpCooldown.kt | 2 +- .../module/modules/movement/SafeWalk.kt | 2 +- .../lambda/module/modules/movement/Speed.kt | 6 +- .../lambda/module/modules/movement/Sprint.kt | 2 +- .../module/modules/movement/TargetStrafe.kt | 2 +- .../module/modules/movement/TickShift.kt | 2 +- .../lambda/module/modules/movement/Timer.kt | 2 +- .../module/modules/movement/Velocity.kt | 2 +- .../modules/movement/elytrafly/ElytraFly.kt | 6 +- .../movement/elytrafly/ElytraFlyMode.kt | 30 +- .../movement/elytrafly/ObstaclePassingMode.kt | 8 +- .../elytrafly/modes/BounceElytraFly.kt | 2 +- .../module/modules/network/PacketDelay.kt | 2 +- .../module/modules/network/PacketLimiter.kt | 4 +- .../module/modules/network/PacketLogger.kt | 2 +- .../module/modules/network/Rubberband.kt | 2 +- .../module/modules/network/ServerSpoof.kt | 2 +- .../lambda/module/modules/player/AntiAFK.kt | 2 +- .../lambda/module/modules/player/AntiAim.kt | 4 +- .../lambda/module/modules/player/AutoEat.kt | 2 +- .../module/modules/player/AutoElytraSwap.kt | 2 +- .../module/modules/player/ClickFriend.kt | 2 +- .../module/modules/player/EndermanLook.kt | 2 +- .../lambda/module/modules/player/FastBreak.kt | 2 +- .../lambda/module/modules/player/Interact.kt | 2 +- .../module/modules/player/InventoryMove.kt | 2 +- .../module/modules/player/InventoryResync.kt | 2 +- .../module/modules/player/InventoryTweaks.kt | 2 +- .../lambda/module/modules/player/LineLock.kt | 2 +- .../module/modules/player/NoForceRotate.kt | 2 +- .../module/modules/player/PacketMine.kt | 2 +- .../lambda/module/modules/player/PortalGui.kt | 2 +- .../com/lambda/module/modules/player/Reach.kt | 2 +- .../lambda/module/modules/player/Replay.kt | 2 +- .../module/modules/player/RotationLock.kt | 2 +- .../module/modules/player/StackReplenish.kt | 2 +- .../lambda/module/modules/player/ToolSaver.kt | 2 +- .../module/modules/render/BlockOutline.kt | 2 +- .../lambda/module/modules/render/Bobbing.kt | 2 +- .../module/modules/render/CameraTweaks.kt | 2 +- .../module/modules/render/ContainerPreview.kt | 2 +- .../com/lambda/module/modules/render/Esp.kt | 2 +- .../lambda/module/modules/render/ExtraTab.kt | 2 +- .../lambda/module/modules/render/FreeLook.kt | 2 +- .../lambda/module/modules/render/Freecam.kt | 2 +- .../module/modules/render/Fullbright.kt | 2 +- .../module/modules/render/LightLevels.kt | 7 +- .../module/modules/render/MapPreview.kt | 2 +- .../lambda/module/modules/render/Nametags.kt | 2 +- .../lambda/module/modules/render/NoRender.kt | 2 +- .../lambda/module/modules/render/RadiusESP.kt | 2 +- .../lambda/module/modules/render/Search.kt | 2 +- .../com/lambda/module/modules/render/Time.kt | 2 +- .../lambda/module/modules/render/Tracers.kt | 2 +- .../lambda/module/modules/render/ViewModel.kt | 2 +- .../lambda/module/modules/render/Weather.kt | 2 +- .../module/modules/render/WorldColors.kt | 2 +- .../com/lambda/module/modules/render/XRay.kt | 2 +- .../com/lambda/module/modules/render/Zoom.kt | 2 +- .../lambda/module/modules/world/AirPlace.kt | 2 +- .../lambda/module/modules/world/AutoPortal.kt | 2 +- .../lambda/module/modules/world/AutoSign.kt | 2 +- .../module/modules/world/AutoVillagerCycle.kt | 2 +- .../module/modules/world/HighwayTools.kt | 2 +- .../module/modules/world/MapDownloader.kt | 2 +- .../com/lambda/module/modules/world/Nuker.kt | 2 +- .../lambda/module/modules/world/Printer.kt | 2 +- .../lambda/module/modules/world/Scaffold.kt | 2 +- .../lambda/module/modules/world/StashMover.kt | 2 +- .../task/tasks/AcquirePlacedBlockTask.kt | 2 +- .../com/lambda/task/tasks/CollectDropsTask.kt | 2 +- .../task/tasks/ContainerTransferTask.kt | 98 +++- .../kotlin/com/lambda/util/FolderRegistry.kt | 3 +- .../com/lambda/util/combat/DamageUtils.kt | 4 +- .../com/lambda/util/item/ItemStackUtils.kt | 4 +- .../kotlin/com/lambda/util/math/Vectors.kt | 4 +- .../player/prediction/PredictionEntity.kt | 10 +- 169 files changed, 1173 insertions(+), 681 deletions(-) create mode 100644 src/main/kotlin/com/lambda/config/serializers/PlacedContainerSerializer.kt create mode 100644 src/main/kotlin/com/lambda/interaction/container/ContainerSerializer.kt rename src/main/kotlin/com/lambda/interaction/handler/handlers/{interacting => }/InteractedBlockHandler.kt (94%) rename src/main/kotlin/com/lambda/interaction/handler/handlers/{packet => }/PacketLimitHandler.kt (97%) delete mode 100644 src/main/kotlin/com/lambda/interaction/material/container/containers/ChestContainer.kt delete mode 100644 src/main/kotlin/com/lambda/interaction/material/container/containers/EnderChestContainer.kt delete mode 100644 src/main/kotlin/com/lambda/interaction/material/container/containers/ShulkerBoxContainer.kt rename src/main/kotlin/com/lambda/module/{tag => }/ModuleTag.kt (96%) diff --git a/src/main/java/com/lambda/mixin/entity/LivingEntityMixin.java b/src/main/java/com/lambda/mixin/entity/LivingEntityMixin.java index 7b2ec4dc3..9af7e6302 100644 --- a/src/main/java/com/lambda/mixin/entity/LivingEntityMixin.java +++ b/src/main/java/com/lambda/mixin/entity/LivingEntityMixin.java @@ -49,7 +49,6 @@ @Mixin(LivingEntity.class) public abstract class LivingEntityMixin extends EntityMixin { - @Unique private final LivingEntity lambda$instance = (LivingEntity) (Object) this; @Definition(id = "getJumpVelocity", method = "Lnet/minecraft/entity/LivingEntity;getJumpVelocity()F") diff --git a/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt b/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt index 9077ebe4d..9bcfb93fa 100644 --- a/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt +++ b/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt @@ -29,7 +29,7 @@ import com.lambda.brigadier.required import com.lambda.command.LambdaCommand import com.lambda.config.automation.AutomationConfig import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.selectStack -import com.lambda.interaction.handler.handlers.ContainerHandler +import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.selectContainer import com.lambda.interaction.handler.handlers.findContainers import com.lambda.interaction.handler.handlers.findContainersWithSpace import com.lambda.task.Task @@ -56,26 +56,23 @@ object TransferCommand : LambdaCommand( isItem(stack(ctx).value().item) } AutomationConfig.DEFAULT.runSafeAutomated { - val containers = selection.findContainers().toList() + val containers = findContainers(selection).toList() val indexedContainers = containers.withIndex() suggestMatching( indexedContainers, builder, - { (index, container) -> - "\"${index + 1}. ${container.name}\"" - }, - { (_, container) -> - container.descriptionAndStock(selection) - } + { (index, container) -> "\"${index + 1}. ${container.name}\"" }, + { (_, container) -> container.descriptionAndStock(selection) } ) } ?: builder.buildFuture() } required(string("to")) { to -> suggests { ctx, builder -> - val selection = selectStack(amount(ctx).value()) { - isItem(stack(ctx).value().item) - } + val selection = + selectStack(amount(ctx).value()) { + isItem(stack(ctx).value().item) + } AutomationConfig.DEFAULT.runSafeAutomated { val containers = selection.findContainersWithSpace().toList() val indexedContainers = containers.withIndex() @@ -83,30 +80,31 @@ object TransferCommand : LambdaCommand( suggestMatching( indexedContainers, builder, - { (index, container) -> - "\"${index + 1}. ${container.name}\"" - }, - { (_, container) -> - container.descriptionAndStock(selection) - } + { (index, container) -> "\"${index + 1}. ${container.name}\"" }, + { (_, container) -> container.descriptionAndStock(selection) } ) } ?: builder.buildFuture() } executeWithResult { - val selection = selectStack(amount().value()) { - isItem(stack().value().item) - } AutomationConfig.DEFAULT.runSafeAutomated { - val fromContainer = ContainerHandler.filteredContainers.find { - it.name == from().value().split(".").last().trim() - } ?: return@executeWithResult failure("From container not found") + val fromSelection = + selectContainer { + custom { it.name == from().value().split(".").first().trim() } + } - val toContainer = ContainerHandler.filteredContainers.find { - it.name == to().value().split(".").last().trim() - } ?: return@executeWithResult failure("To container not found") + val toSelection = + selectContainer { + custom { it.name == to().value().split(".").first().trim() } + } - transfer(selection, fromContainer, toContainer) - .start() + lastContainerTransfer = + transfer( + selectStack(amount().value()) { + isItem(stack().value().item) + }, + fromSelection, + toSelection + ).start() } return@executeWithResult success() } @@ -117,9 +115,11 @@ object TransferCommand : LambdaCommand( required(literal("cancel")) { executeWithResult { - lastContainerTransfer?.cancel() ?: run { - return@executeWithResult failure("No transfer to cancel") - } + lastContainerTransfer + ?.cancel() + ?: run { + return@executeWithResult failure("No transfer to cancel") + } this@TransferCommand.info("$lastContainerTransfer cancelled") lastContainerTransfer = null success() diff --git a/src/main/kotlin/com/lambda/config/ConfigEditor.kt b/src/main/kotlin/com/lambda/config/ConfigEditor.kt index 8700d9889..df332bd5d 100644 --- a/src/main/kotlin/com/lambda/config/ConfigEditor.kt +++ b/src/main/kotlin/com/lambda/config/ConfigEditor.kt @@ -21,7 +21,7 @@ package com.lambda.config import com.lambda.config.entries.Property import com.lambda.config.entries.Setting -import com.lambda.config.entries.Setting.Companion.onValueChange +import com.lambda.config.entries.onValueChange import com.lambda.context.SafeContext import kotlin.reflect.KProperty0 import kotlin.reflect.jvm.isAccessible diff --git a/src/main/kotlin/com/lambda/config/automation/AutomationConfig.kt b/src/main/kotlin/com/lambda/config/automation/AutomationConfig.kt index 4272fdf96..d90420e89 100644 --- a/src/main/kotlin/com/lambda/config/automation/AutomationConfig.kt +++ b/src/main/kotlin/com/lambda/config/automation/AutomationConfig.kt @@ -31,6 +31,9 @@ import com.lambda.config.categories.AutomationCategory import com.lambda.context.Automated import com.lambda.module.Module +@DslMarker +private annotation class AutomationConfigMarker + open class AutomationConfig( name: String, configCategory: ConfigCategory = AutomationCategory @@ -55,20 +58,17 @@ open class AutomationConfig( private const val HOTBAR_TAB = "Hotbar" private const val EAT_TAB = "Eat" - @DslMarker - private annotation class AutomationConfigMarker - - @AutomationConfigMarker - context(module: Module) - fun IMutableAutomationConfig.setDefaultAutomationConfig( - name: String = module.name - ) = AutomationConfig("$name Automation Config").also { this.defaultAutomationConfig = it } - - @AutomationConfigMarker - fun IMutableAutomationConfig.setDefaultAutomationConfig( - name: String - ) = AutomationConfig("$name Automation Config").also { this.defaultAutomationConfig = it } - val DEFAULT = AutomationConfig("Default") } -} \ No newline at end of file +} + +@AutomationConfigMarker +context(module: Module) +fun IMutableAutomationConfig.setDefaultAutomationConfig( + name: String = module.name +) = AutomationConfig("$name Automation Config").also { this.defaultAutomationConfig = it } + +@AutomationConfigMarker +fun IMutableAutomationConfig.setDefaultAutomationConfig( + name: String +) = AutomationConfig("$name Automation Config").also { this.defaultAutomationConfig = it } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt b/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt index 5ac44e036..4f0e9c893 100644 --- a/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt +++ b/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt @@ -48,22 +48,22 @@ interface InventoryConfig { fun materialComparator(selection: StackSelection) = when (this) { WithMaxItems -> compareBy { it.type } - .thenByDescending { it.stackCount(selection) } + .thenByDescending { it.count(selection) } .thenBy { it.name } WithMinItems -> compareBy { it.type } - .thenBy { it.stackCount(selection) } + .thenBy { it.count(selection) } .thenBy { it.name } } fun spaceComparator(selection: StackSelection) = when (this) { WithMaxItems -> compareBy { it.type } - .thenByDescending { it.spaceAvailable(selection) } + .thenByDescending { it.spaceLeft(selection) } .thenBy { it.name } WithMinItems -> compareBy { it.type } - .thenBy { it.spaceAvailable(selection) } + .thenBy { it.spaceLeft(selection) } .thenBy { it.name } } } diff --git a/src/main/kotlin/com/lambda/config/entries/Setting.kt b/src/main/kotlin/com/lambda/config/entries/Setting.kt index e68c0def7..17777d42a 100644 --- a/src/main/kotlin/com/lambda/config/entries/Setting.kt +++ b/src/main/kotlin/com/lambda/config/entries/Setting.kt @@ -31,6 +31,7 @@ import com.lambda.config.ConfigEntry import com.lambda.config.EntryCore import com.lambda.config.EntryLayer import com.lambda.config.MultipleLayerType +import com.lambda.config.entries.Setting.ValueListener import com.lambda.context.SafeContext import com.lambda.gui.dsl.ImGuiBuilder import com.lambda.threading.runSafe @@ -229,42 +230,40 @@ abstract class Setting( override fun toString() = "Setting $name: $value" - companion object { - /** - * Will only register changes of the variable, not the content of the variable! - * E.g., if the variable is a list, it will only register if the list reference changes, not if the content of the list changes. - */ - @ConfigEntryDsl - fun , T> S.onValueChange(block: SafeContext.(from: T, to: T) -> Unit) = - apply { - listeners.add(ValueListener(true) { from, to -> - runSafe { block(from, to) } - }) - } - - @ConfigEntryDsl - fun , T> S.onValueChangeUnsafe(block: (from: T, to: T) -> Unit) = - apply { listeners.add(ValueListener(true, block)) } + class ValueListener(val requiresValueChange: Boolean, val execute: (from: T, to: T) -> Unit) +} - @ConfigEntryDsl - fun , T> S.onValueSet(block: SafeContext.(from: T, to: T) -> Unit) = - apply { - listeners.add(ValueListener(false) { from, to -> - runSafe { block(from, to) } - }) - } +/** + * Will only register changes of the variable, not the content of the variable! + * E.g., if the variable is a list, it will only register if the list reference changes, not if the content of the list changes. + */ +@ConfigEntryDsl +fun , T> S.onValueChange(block: SafeContext.(from: T, to: T) -> Unit) = + apply { + listeners.add(ValueListener(true) { from, to -> + runSafe { block(from, to) } + }) + } - @ConfigEntryDsl - fun , T> S.onValueSetUnsafe(block: (from: T, to: T) -> Unit) = - apply { listeners.add(ValueListener(false, block)) } +@ConfigEntryDsl +fun , T> S.onValueChangeUnsafe(block: (from: T, to: T) -> Unit) = + apply { listeners.add(ValueListener(true, block)) } - @ConfigEntryDsl - fun , T> S.disabled(predicate: () -> Boolean) = - apply { disabled = predicate } +@ConfigEntryDsl +fun , T> S.onValueSet(block: SafeContext.(from: T, to: T) -> Unit) = + apply { + listeners.add(ValueListener(false) { from, to -> + runSafe { block(from, to) } + }) } - class ValueListener(val requiresValueChange: Boolean, val execute: (from: T, to: T) -> Unit) -} +@ConfigEntryDsl +fun , T> S.onValueSetUnsafe(block: (from: T, to: T) -> Unit) = + apply { listeners.add(ValueListener(false, block)) } + +@ConfigEntryDsl +fun , T> S.disabled(predicate: () -> Boolean) = + apply { disabled = predicate } class SettingEntryLayer, U>( override val parent: EntryLayer.Multiple>, diff --git a/src/main/kotlin/com/lambda/config/serializers/PlacedContainerSerializer.kt b/src/main/kotlin/com/lambda/config/serializers/PlacedContainerSerializer.kt new file mode 100644 index 000000000..062915fab --- /dev/null +++ b/src/main/kotlin/com/lambda/config/serializers/PlacedContainerSerializer.kt @@ -0,0 +1,115 @@ +@file:Suppress("unused") + +package com.lambda.config.serializers + +import com.lambda.config.Deserializer +import com.lambda.config.JsonOps +import com.lambda.config.Serializer +import com.lambda.interaction.container.ContainerType +import com.lambda.interaction.container.PlacedContainer +import com.lambda.interaction.container.containers.external.ChestContainer +import com.lambda.interaction.container.containers.external.DoubleChestContainer +import com.lambda.interaction.container.containers.external.PlacedShulkerBoxContainer +import net.minecraft.block.Blocks +import net.minecraft.item.ItemStack +import net.minecraft.registry.Registries +import net.minecraft.util.Identifier +import net.minecraft.util.math.BlockPos +import tools.jackson.core.JsonGenerator +import tools.jackson.core.JsonParser +import tools.jackson.databind.DeserializationContext +import tools.jackson.databind.SerializationContext +import tools.jackson.databind.node.ArrayNode +import tools.jackson.databind.node.JsonNodeFactory +import tools.jackson.databind.node.ObjectNode + +object PlacedContainerSerializer : Serializer(PlacedContainer::class.java) { + override fun serialize(container: PlacedContainer, gen: JsonGenerator, ctxt: SerializationContext) { + val root = JsonNodeFactory.instance.objectNode() + + root.put("Type", container.type.name) + root.set("Pos", blockPosNode(container.pos)) + + when (container) { + is PlacedShulkerBoxContainer -> { + val blockId = Registries.BLOCK.getId(container.block).toString() + root.put("Block", blockId) + } + is DoubleChestContainer -> { + root.put("Double", true) + root.set("Left Pos", blockPosNode(container.leftPos)) + root.set("Right Pos", blockPosNode(container.rightPos)) + } + else -> {} + } + + val stackArray = JsonNodeFactory.instance.arrayNode() + for (stack in container.stacks) { + val encoded = ItemStack.CODEC + .encodeStart(JsonOps.UNCOMPRESSED, stack) + .result() + encoded.ifPresent { stackArray.add(it) } + } + root.set("Stacks", stackArray) + + gen.writeTree(root) + } + + private fun blockPosNode(pos: BlockPos): ObjectNode { + val node = JsonNodeFactory.instance.objectNode() + node.put("X", pos.x) + node.put("Y", pos.y) + node.put("Z", pos.z) + return node + } +} + +object PlacedContainerDeserializer : Deserializer(PlacedContainer::class.java) { + override fun deserialize(p: JsonParser, ctxt: DeserializationContext): PlacedContainer? { + val root = p.readValueAsTree() ?: return null + + val typeName = root.get("Type")?.asString() ?: return null + val type = ContainerType.entries.find { it.name == typeName } ?: return null + + val posNode = root.get("Pos") as? ObjectNode ?: return null + val pos = parseBlockPos(posNode) ?: return null + + val stackArray = root.get("Stacks") as? ArrayNode + val stacks = + stackArray?.mapNotNull { element -> + ItemStack.CODEC + .parse(JsonOps.UNCOMPRESSED, element) + .result() + .orElse(null) + } ?: emptyList() + + val container = + when (type) { + ContainerType.PlacedShulkerBox -> { + val blockId = root.get("Block")?.asString() + val block = blockId?.let { Registries.BLOCK.get(Identifier.of(it)) } ?: Blocks.SHULKER_BOX + PlacedShulkerBoxContainer(pos, block, null, stacks) + } + ContainerType.Chest -> { + if (root.get("Double")?.asBoolean() == true) { + val leftPos = (root.get("Left Pos") as? ObjectNode)?.let { parseBlockPos(it) } ?: pos + val rightPos = (root.get("Right Pos") as? ObjectNode)?.let { parseBlockPos(it) } ?: pos + DoubleChestContainer(pos, leftPos, rightPos, stacks) + } else { + ChestContainer(pos, stacks) + } + } + else -> return null + } + + container.scanStacksForNestedContainers() + return container + } + + private fun parseBlockPos(node: ObjectNode): BlockPos? { + val x = node.get("X")?.asInt() ?: return null + val y = node.get("Y")?.asInt() ?: return null + val z = node.get("Z")?.asInt() ?: return null + return BlockPos(x, y, z) + } +} diff --git a/src/main/kotlin/com/lambda/graphics/mc/renderer/ChunkedRenderer.kt b/src/main/kotlin/com/lambda/graphics/mc/renderer/ChunkedRenderer.kt index d7aebfca2..767c56ca6 100644 --- a/src/main/kotlin/com/lambda/graphics/mc/renderer/ChunkedRenderer.kt +++ b/src/main/kotlin/com/lambda/graphics/mc/renderer/ChunkedRenderer.kt @@ -63,7 +63,7 @@ class ChunkedRenderer( private val uploadQueue = ConcurrentLinkedDeque<() -> Unit>() init { - owner.listenUnsafe { event -> + owner.listenUnsafe(alwaysListen = true) { event -> val pos = event.pos val world = mc.world ?: return@listenUnsafe world.getWorldChunk(pos)?.chunkData?.markDirty() @@ -77,9 +77,9 @@ class ChunkedRenderer( if (zInChunk == 15) world.getWorldChunk(pos.south())?.chunkData?.markDirty() } - owner.listenUnsafe { event -> event.chunk.chunkData.markDirty() } - owner.listenUnsafe { chunkMap.remove(it.chunk.chunkKey)?.clearData() } - owner.listenUnsafe { clear() } + owner.listenUnsafe(alwaysListen = true) { event -> event.chunk.chunkData.markDirty() } + owner.listenUnsafe(alwaysListen = true) { chunkMap.remove(it.chunk.chunkKey)?.clearData() } + owner.listenUnsafe(alwaysListen = true) { clear() } owner.listenConcurrentlyUnsafe { if (pauseUpdates()) return@listenConcurrentlyUnsafe diff --git a/src/main/kotlin/com/lambda/graphics/text/FontHandler.kt b/src/main/kotlin/com/lambda/graphics/text/FontHandler.kt index edb196076..197e207fc 100644 --- a/src/main/kotlin/com/lambda/graphics/text/FontHandler.kt +++ b/src/main/kotlin/com/lambda/graphics/text/FontHandler.kt @@ -20,7 +20,7 @@ package com.lambda.graphics.text import com.lambda.Lambda.LOG import com.lambda.config.Config import com.lambda.config.categories.FontCategory -import com.lambda.config.entries.Setting.Companion.onValueChangeUnsafe +import com.lambda.config.entries.onValueChangeUnsafe import com.lambda.core.Loadable import com.lambda.event.events.ClientEvent import com.lambda.event.listener.SafeListener.Companion.listen diff --git a/src/main/kotlin/com/lambda/gui/MenuBar.kt b/src/main/kotlin/com/lambda/gui/MenuBar.kt index 2c5550ff2..df50b2a31 100644 --- a/src/main/kotlin/com/lambda/gui/MenuBar.kt +++ b/src/main/kotlin/com/lambda/gui/MenuBar.kt @@ -43,7 +43,7 @@ import com.lambda.imgui.type.ImBoolean import com.lambda.interaction.handler.handlers.BaritoneHandler import com.lambda.module.ModuleRegistry import com.lambda.module.ModuleRegistry.moduleNameMap -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.network.LambdaAPI import com.lambda.threading.runSafe import com.lambda.util.CommunicationUtils.info diff --git a/src/main/kotlin/com/lambda/gui/components/ClickGuiLayout.kt b/src/main/kotlin/com/lambda/gui/components/ClickGuiLayout.kt index f4e708bd8..f8965fbe4 100644 --- a/src/main/kotlin/com/lambda/gui/components/ClickGuiLayout.kt +++ b/src/main/kotlin/com/lambda/gui/components/ClickGuiLayout.kt @@ -21,7 +21,7 @@ import com.lambda.Lambda.mc import com.lambda.config.Config import com.lambda.config.Tab import com.lambda.config.categories.GuiCategory -import com.lambda.config.entries.Setting.Companion.onValueChange +import com.lambda.config.entries.onValueChange import com.lambda.config.settings.complex.KeybindSetting.Companion.onPressUnsafe import com.lambda.core.Loadable import com.lambda.event.events.GuiEvent @@ -49,8 +49,8 @@ import com.lambda.imgui.flag.ImGuiWindowFlags import com.lambda.module.ModuleRegistry import com.lambda.module.modules.client.Client import com.lambda.module.modules.combat.autodisconnect.AutoDisconnectScreen -import com.lambda.module.tag.ModuleTag -import com.lambda.module.tag.ModuleTag.Companion.shownTags +import com.lambda.module.ModuleTag +import com.lambda.module.ModuleTag.Companion.shownTags import com.lambda.sound.LambdaSound import com.lambda.sound.SoundHandler.play import com.lambda.util.Describable @@ -157,8 +157,7 @@ object ClickGuiLayout : Loadable, Config( mc.window.setIcon(mc.defaultResourcePack, icon) } } - @JvmStatic - @Tab(GENERAL_TAB) val setLambdaWindowTitle by setting("Set Lambda Window Title", true).onValueChange { _, _ -> mc.updateWindowTitle() } + @Tab(GENERAL_TAB) @JvmStatic val setLambdaWindowTitle by setting("Set Lambda Window Title", true).onValueChange { _, _ -> mc.updateWindowTitle() } @Tab(GENERAL_TAB) val lambdaTitleAppendixName by setting("Append Username", true) { setLambdaWindowTitle }.onValueChange { _, _ -> mc.updateWindowTitle() } @Tab(GENERAL_TAB) val backgroundBlur by setting("Background Blur", true) @Tab(GENERAL_TAB) val backgroundDarkening by setting("Background Darkening", true) diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt index 361763443..e5bf44af2 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt @@ -33,8 +33,9 @@ import com.lambda.interaction.construction.simulation.result.Rank import com.lambda.interaction.construction.simulation.result.Resolvable import com.lambda.interaction.container.containers.HotbarContainer import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.selectStack +import com.lambda.interaction.container.selection.select import com.lambda.interaction.handler.handlers.BaritoneHandler -import com.lambda.task.tasks.transferTo +import com.lambda.task.tasks.transfer import net.minecraft.block.BlockState import net.minecraft.item.Item import net.minecraft.util.math.BlockPos @@ -119,7 +120,7 @@ sealed class BreakResult : BuildResult() { context(_: Automated) override fun resolve() = selectStack { inverted { isItem(badItem) } } - .transferTo(HotbarContainer) + .transfer(toSelection = HotbarContainer.select()) override fun compareResult(other: ComparableResult) = when (other) { diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/GenericResult.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/GenericResult.kt index 0e06d44da..2c5e64a8d 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/GenericResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/GenericResult.kt @@ -28,8 +28,9 @@ import com.lambda.interaction.construction.simulation.result.Rank import com.lambda.interaction.construction.simulation.result.Resolvable import com.lambda.interaction.container.containers.HotbarContainer import com.lambda.interaction.container.selection.StackSelection +import com.lambda.interaction.container.selection.select import com.lambda.interaction.handler.handlers.BaritoneHandler -import com.lambda.task.tasks.transferTo +import com.lambda.task.tasks.transfer import net.minecraft.client.data.TextureMap.side import net.minecraft.item.ItemStack import net.minecraft.util.math.BlockPos @@ -100,7 +101,7 @@ sealed class GenericResult : BuildResult() { context(_: Automated) override fun resolve() = neededSelection - .transferTo(HotbarContainer) + .transfer(toSelection = HotbarContainer.select()) override fun RenderBuilder.render() { val center = pos.toCenterPos() diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/BreakSim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/BreakSim.kt index 06f5e8c1c..6f60974e2 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/BreakSim.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/BreakSim.kt @@ -158,8 +158,7 @@ class BreakSim internal constructor(simInfo: SimInfo) ofAnyType(ContainerType.Hotbar) } - val hotbarCandidates = stackSelection - .findContainers(containerSelection) + val hotbarCandidates = findContainers(stackSelection, containerSelection) .flatMap { stackSelection.filter(it.stacks) } .toList() if (hotbarCandidates.isEmpty()) { diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/InteractSim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/InteractSim.kt index 341a15e8e..8f8ec8872 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/InteractSim.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/InteractSim.kt @@ -208,14 +208,18 @@ class InteractSim internal constructor(simInfo: InteractSimInfo) val stackSelection = item?.select(if (item == Items.AIR) 0 else 1) ?: selectStack { sortedWith { compareByDescending { it.stack.inventoryIndex == player.inventory.selectedSlot } } } val containerSelection = selectContainer { ofAnyType(ContainerType.Hotbar) } - val container = stackSelection.findContainers(containerSelection).firstOrNull() ?: run { - result(GenericResult.WrongItemSelection(pos, stackSelection, player.mainHandStack)) - return null - } - return stackSelection.filter(container.slots).run { - firstOrNull { it.index == player.inventory.selectedSlot } - ?: firstOrNull() - } + val container = findContainers(stackSelection, containerSelection) + .firstOrNull() + ?: run { + result(GenericResult.WrongItemSelection(pos, stackSelection, player.mainHandStack)) + return null + } + return stackSelection + .filter(container.slots) + .run { + firstOrNull { it.index == player.inventory.selectedSlot } + ?: firstOrNull() + } } private suspend fun AutomatedSafeContext.simRotation( diff --git a/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt b/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt index 1ba987fc8..7eb8fd043 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt @@ -20,6 +20,7 @@ package com.lambda.interaction.construction.verify import com.lambda.context.AutomatedSafeContext import com.lambda.context.SafeContext import com.lambda.interaction.handler.handlers.findContainerWithDisposable +import com.lambda.interaction.handler.handlers.findContainersWithDisposable import com.lambda.util.BlockUtils.blockState import com.lambda.util.BlockUtils.emptyState import com.lambda.util.BlockUtils.isEmpty @@ -86,9 +87,12 @@ sealed class TargetState : StateMatcher { context(automatedSafeContext: AutomatedSafeContext) override fun getStack(pos: BlockPos) = with(automatedSafeContext) { - findContainerWithDisposable()?.stacks?.firstOrNull { - it.item in inventoryConfig.disposables && it.item.block !in replace - } ?: ItemStack(Items.NETHERRACK) + findContainerWithDisposable() + ?.stacks + ?.firstOrNull { + it.item in inventoryConfig.disposables && it.item.block !in replace + } + ?: ItemStack(Items.NETHERRACK) } context(_: AutomatedSafeContext) @@ -113,9 +117,12 @@ sealed class TargetState : StateMatcher { context(automatedSafeContext: AutomatedSafeContext) override fun getStack(pos: BlockPos) = with(automatedSafeContext) { - findContainerWithDisposable()?.stacks?.firstOrNull { - it.item in inventoryConfig.disposables - } ?: ItemStack(Items.NETHERRACK) + findContainerWithDisposable() + ?.stacks + ?.firstOrNull { + it.item in inventoryConfig.disposables + } + ?: ItemStack(Items.NETHERRACK) } context(_: AutomatedSafeContext) diff --git a/src/main/kotlin/com/lambda/interaction/container/Container.kt b/src/main/kotlin/com/lambda/interaction/container/Container.kt index d8dd36dd5..18c348749 100644 --- a/src/main/kotlin/com/lambda/interaction/container/Container.kt +++ b/src/main/kotlin/com/lambda/interaction/container/Container.kt @@ -22,14 +22,17 @@ import com.lambda.context.AutomatedSafeContext import com.lambda.context.SafeContext import com.lambda.event.EventFlow.post import com.lambda.event.events.ContainerEvent +import com.lambda.interaction.container.containers.external.ShulkerBoxContainer import com.lambda.interaction.container.selection.StackSelection import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.task.Task.Ta5kBuilder import com.lambda.util.Nameable import com.lambda.util.item.ItemStackUtils.count -import com.lambda.util.item.ItemStackUtils.empty +import com.lambda.util.item.ItemStackUtils.emptySpace +import com.lambda.util.item.ItemStackUtils.shulkerBoxStacks import com.lambda.util.item.ItemStackUtils.spaceLeft +import com.lambda.util.item.ItemUtils.SHULKER_BOXES import com.lambda.util.item.ItemUtils.toItemCount import com.lambda.util.text.TextBuilder import com.lambda.util.text.TextDsl @@ -71,6 +74,8 @@ abstract class Container( it.stack.isStackable } + open val isAccessed get() = true + context(_: SafeContext) fun descriptionAndStock(selection: StackSelection) = buildText { @@ -83,29 +88,72 @@ abstract class Container( fun TextBuilder.stock(selection: StackSelection) { literal("\n") literal("Contains ") - val available = stackCount(selection) + val available = count(selection) highlighted(if (available == Int.MAX_VALUE) "∞" else available.toItemCount()) literal(" of ") highlighted("${selection.optimalStack?.name?.string}") literal("\n") literal("Could store ") - val left = spaceAvailable(selection) + val left = spaceLeft(selection) highlighted(if (left == Int.MAX_VALUE) "∞" else left.toItemCount()) literal(" of ") highlighted("${selection.optimalStack?.name?.string}") } - open val isAccessed get() = true - fun update(slots: List) { this.stacks = slots } + fun scanStacksForNestedContainers() { + stacks.forEachIndexed { index, stack -> + if (stack.item in SHULKER_BOXES) { + storedContainers[index] = + ShulkerBoxContainer( + stack.name.string, + stack.item, + stack.shulkerBoxStacks, + this, + index + ).also { it.scanStacksForNestedContainers() } + } else storedContainers.remove(index) + } + } + @Ta5kBuilder context(automated: Automated) open fun access(): OpenContainerTask<*>? = PlayerOpenContainerTask(description, ::isAccessed) + open fun count(selection: StackSelection) = + slots.takeUnless { it.isEmpty() }?.let { selection.filter(it).count } + ?: selection.filter(stacks).count + + open fun spaceLeft(selection: StackSelection) = + slots.takeUnless { it.isEmpty() }?.let { selection.filter(it).spaceLeft + it.emptySpace } + ?: (selection.filter(stacks).spaceLeft + stacks.emptySpace) + + open fun findSlots(selection: StackSelection) = selection.filter(slots) + + fun findSlot(selection: StackSelection) = findSlots(selection).firstOrNull() + + open fun findStacks(selection: StackSelection) = selection.filter(stacks) + + fun findStack(selection: StackSelection) = findStacks(selection).firstOrNull() + + context(_: Automated) + fun findMoveSlots( + selection: StackSelection, + toContainer: Container, + toStackSelection: StackSelection = StackSelection.EVERYTHING + ) = Pair(findSlot(selection), toContainer.findReplaceSlot(toStackSelection)) + + context(_: Automated) + open fun findReplaceSlot( + selection: StackSelection = StackSelection.EVERYTHING + ) = findSlots(selection) + .sortedWith(replaceSorter) + .firstOrNull() + context(automatedSafeContext: AutomatedSafeContext) internal fun swap(fromSlot: Slot, toSlot: Slot, toContainer: Container): Boolean { val transferEvent = ContainerEvent.Transfer(fromSlot, toSlot, this@Container, toContainer) @@ -125,20 +173,5 @@ abstract class Container( } } - open fun stackCount(selection: StackSelection) = - selection.filter(stacks).count - - open fun spaceAvailable(selection: StackSelection) = - selection.filter(stacks).spaceLeft + stacks.empty - - context(_: Automated) - fun getTransferSlots(selection: StackSelection, destination: Container): Pair = - Pair(getSlot(selection), destination.getReplaceSlot()) - - context(_: Automated) - open fun getReplaceSlot() = slots.sortedWith(replaceSorter).firstOrNull() - - open fun getSlot(selection: StackSelection) = selection.bestMatch(slots) - override fun compareTo(other: Container) = compareBy { it.type }.compare(this, other) } diff --git a/src/main/kotlin/com/lambda/interaction/container/ContainerSerializer.kt b/src/main/kotlin/com/lambda/interaction/container/ContainerSerializer.kt new file mode 100644 index 000000000..d53ce206e --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/container/ContainerSerializer.kt @@ -0,0 +1,115 @@ +/* + * 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.container + +import com.lambda.Lambda.LOG +import com.lambda.Lambda.mapper +import com.lambda.interaction.container.containers.external.EnderChestContainer +import com.lambda.util.FolderRegistry +import net.minecraft.util.math.BlockPos +import java.nio.file.Path +import kotlin.io.path.createDirectories +import kotlin.io.path.deleteIfExists +import kotlin.io.path.exists +import kotlin.io.path.notExists + +/** + * Handles saving and loading [PlacedContainer]s to/from disk as JSON files. + * + * Serialized containers are stored under [FolderRegistry.containers]. + */ +object ContainerSerializer { + private const val ENDER_CHEST_FILE = "ender_chest.json" + + /** + * A reusable sequence that lazily loads all serialized containers from disk. + * Every iteration reads the files anew, preventing RAM exhaustion. + */ + val serializedContainers: Sequence = + sequence { + val dir = FolderRegistry.containers + if (dir.notExists()) return@sequence + + dir.toFile().walkTopDown().forEach { file -> + if (file.isFile && file.name.endsWith(".json") && file.name != ENDER_CHEST_FILE) { + yield(readContainer(file.toPath()) ?: return@forEach) + } + } + } + + /** + * Saves a [PlacedContainer] to disk as JSON. + */ + @Synchronized + fun saveContainer(container: PlacedContainer) { + val dir = ensureDirectory() + val file = dir.resolve(containerFileName(container.pos)).toFile() + mapper.writeValue(file, container) + } + + /** + * Saves the [EnderChestContainer] to disk. This is always loaded into memory + * for the entire session. + */ + fun saveEnderChest() { + val dir = ensureDirectory() + val file = dir.resolve(ENDER_CHEST_FILE).toFile() + mapper.writeValue(file, EnderChestContainer) + } + + /** + * Loads the [EnderChestContainer] contents from disk, if available. + * Called on session start to restore the ender chest state. + */ + fun loadEnderChest() { + val file = FolderRegistry.containers.resolve(ENDER_CHEST_FILE) + if (!file.exists()) return + runCatching { + mapper.readValue(file.toFile(), EnderChestContainer::class.java) + }.onFailure { + LOG.warn("Failed to load ender chest from $file", it) + } + } + + /** + * Removes a serialized container from disk. + */ + @Synchronized + fun removeContainer(pos: BlockPos) { + val dir = FolderRegistry.containers + dir.resolve(containerFileName(pos)).deleteIfExists() + } + + + + private fun readContainer(file: Path): PlacedContainer? = + runCatching { + mapper.readValue(file.toFile(), PlacedContainer::class.java) + }.onFailure { + LOG.warn("Failed to read container from ${file.fileName}", it) + }.getOrNull() + + private fun ensureDirectory() = + FolderRegistry.containers.also { if (it.notExists()) it.createDirectories() } + + private fun containerFileName(pos: BlockPos): String { + val chunkX = pos.x shr 4 + val chunkZ = pos.z shr 4 + return "${chunkX}_${chunkZ}_${pos.x}_${pos.y}_${pos.z}.json" + } +} diff --git a/src/main/kotlin/com/lambda/interaction/container/containers/ArmorContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/ArmorContainer.kt index bd54eebf8..2a76c37ab 100644 --- a/src/main/kotlin/com/lambda/interaction/container/containers/ArmorContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/container/containers/ArmorContainer.kt @@ -37,4 +37,4 @@ object ArmorContainer : Container(ContainerType.Armor) { override val description = buildText { literal("Armor") } override val isAccessed get() = mc.player?.currentScreenHandler?.syncId == 0 -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/lambda/interaction/container/containers/CreativeContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/CreativeContainer.kt index 2c53447d1..f6538f81c 100644 --- a/src/main/kotlin/com/lambda/interaction/container/containers/CreativeContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/container/containers/CreativeContainer.kt @@ -40,30 +40,35 @@ data object CreativeContainer : Container(ContainerType.Creative) { override val description = buildText { literal("Creative") } + override fun count(selection: StackSelection): Int = + if (mc.player?.isCreative == true && correctScreenHandler && selection.optimalStack != null) Int.MAX_VALUE else -1 + + override fun spaceLeft(selection: StackSelection): Int = + if (mc.player?.isCreative == true && correctScreenHandler && selection.optimalStack != null) Int.MAX_VALUE else -1 + context(safeContext: SafeContext) override fun InvRequestBuilder.swap(fromHere: Slot, toSlot: Slot) { clickCreativeStack(fromHere.stack, toSlot.id) safeContext.player.currentScreenHandler.slots.getOrNull(toSlot.id)?.stack = fromHere.stack } - override fun getSlot(selection: StackSelection) = - selection.optimalStack?.let { stack -> - Slot( - object : SingleStackInventory { - override fun getStack() = stack - override fun setStack(stack: ItemStack?) {} - override fun markDirty() {} - override fun canPlayerUse(player: PlayerEntity?) = false - }, - 0, 0, 0 - ) - } + override fun findSlots(selection: StackSelection) = + listOfNotNull( + selection.optimalStack?.let { stack -> + Slot( + object : SingleStackInventory { + override fun getStack() = stack + override fun setStack(stack: ItemStack?) {} + override fun markDirty() {} + override fun canPlayerUse(player: PlayerEntity?) = false + }, + 0, 0, 0 + ) + } + ) - override fun stackCount(selection: StackSelection): Int = - if (mc.player?.isCreative == true && correctScreenHandler && selection.optimalStack != null) Int.MAX_VALUE else -1 - - override fun spaceAvailable(selection: StackSelection): Int = - if (mc.player?.isCreative == true && correctScreenHandler && selection.optimalStack != null) Int.MAX_VALUE else -1 + override fun findStacks(selection: StackSelection) = + listOfNotNull(selection.optimalStack) private val correctScreenHandler get() = mc.player?.currentScreenHandler is PlayerScreenHandler || mc.player?.currentScreenHandler is CreativeInventoryScreen.CreativeScreenHandler diff --git a/src/main/kotlin/com/lambda/interaction/container/containers/CursorContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/CursorContainer.kt index 5211c5d90..4455c6f4a 100644 --- a/src/main/kotlin/com/lambda/interaction/container/containers/CursorContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/container/containers/CursorContainer.kt @@ -32,4 +32,4 @@ object CursorContainer : Container(ContainerType.Cursor) { set(_) {} override val description = buildText { literal("Cursor") } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt index d2e67c234..8ba7a894f 100644 --- a/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt @@ -27,13 +27,14 @@ import com.lambda.interaction.container.NestedContainer import com.lambda.interaction.container.OpenContainerTask import com.lambda.interaction.container.OpenedContainerContext import com.lambda.interaction.container.containers.HotbarContainer +import com.lambda.interaction.container.selection.ContainerSelection import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.selectStack +import com.lambda.interaction.container.selection.select import com.lambda.task.Task.Ta5kBuilder import com.lambda.task.tasks.breakAndCollect import com.lambda.task.tasks.openContainer import com.lambda.task.tasks.placeContainer import com.lambda.task.tasks.transfer -import com.lambda.task.tasks.transferTo import com.lambda.task.tasks.wrappers.actionTask import com.lambda.task.tasks.wrappers.taskOrNull import com.lambda.task.tasks.wrappers.then @@ -79,8 +80,8 @@ data class ShulkerBoxContainer( override fun SafeContext.onStart() { transfer( selectStack { inIndex(index); isItem(shulkerItem) }, - containedIn, - HotbarContainer + containedIn.select(), + HotbarContainer.select() ).then { slot -> placeContainer(slot) } .then { pos -> openContainer(pos).onSuccess { @@ -104,17 +105,11 @@ data class ShulkerBoxContainer( .then { selectStack { isItem(shulkerItem) - hasCustomName(itemName) + hasName(itemName) sortedByBestContentMatch(stacks) - }.transferTo() - transfer( - selectStack { - isItem(shulkerItem) - hasCustomName(itemName) - sortedByBestContentMatch(stacks) - }, - HotbarAndInventoryContainer, - fromContainer + }.transfer( + ContainerSelection.HOTBAR_AND_INVENTORY, + fromContainer.select() ) } } diff --git a/src/main/kotlin/com/lambda/interaction/container/containers/external/StashContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/external/StashContainer.kt index bd3420298..7d1276ac1 100644 --- a/src/main/kotlin/com/lambda/interaction/container/containers/external/StashContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/container/containers/external/StashContainer.kt @@ -38,11 +38,12 @@ data class StashContainer( get() = chests.flatMap { it.stacks } set(_) {} - override val description = buildText { - literal("Stash at ") - highlighted(pos.center.roundedBlockPos.toShortString()) - } + override val description = + buildText { + literal("Stash at ") + highlighted(pos.center.roundedBlockPos.toShortString()) + } - override fun stackCount(selection: StackSelection): Int = - chests.sumOf { it.stackCount(selection) } + override fun count(selection: StackSelection): Int = + chests.sumOf { it.count(selection) } } diff --git a/src/main/kotlin/com/lambda/interaction/container/selection/ContainerSelection.kt b/src/main/kotlin/com/lambda/interaction/container/selection/ContainerSelection.kt index 39f72b7fa..5da064731 100644 --- a/src/main/kotlin/com/lambda/interaction/container/selection/ContainerSelection.kt +++ b/src/main/kotlin/com/lambda/interaction/container/selection/ContainerSelection.kt @@ -27,13 +27,26 @@ import com.lambda.interaction.container.containers.HotbarContainer import com.lambda.interaction.container.containers.InventoryContainer import com.lambda.interaction.container.containers.OffHandContainer import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.selectContainer +import com.lambda.interaction.handler.handlers.ContainerSearchScope + +@ContainerMarker +fun Container.select( + scope: ContainerSearchScope = ContainerSearchScope.Accessed +) = selectContainer(scope) { ofAny(this@select) } + +@ContainerMarker +fun selectContainers( + vararg containers: Container, + scope: ContainerSearchScope = ContainerSearchScope.Accessed +) = selectContainer(scope) { ofAny(*containers) } /** - * ContainerSelection is a class that holds a predicate for matching MaterialContainers. + * ContainerSelection is a class that holds a predicate for matching containers. */ @Suppress("unused") -class ContainerSelection( +class ContainerSelection @ContainerMarker internal constructor( val selector: (Container) -> Boolean, + val scope: ContainerSearchScope = ContainerSearchScope.Accessed, val containersWhitelist: Collection = emptyList(), val accessedOnly: Boolean = false, val comparator: Comparator = compareBy { it.type } @@ -57,24 +70,35 @@ class ContainerSelection( companion object { val EVERYTHING = ContainerSelection({ true }) val NOTHING = ContainerSelection({ false }) - val HOTBAR_AND_INVENTORY = selectContainer { ofAny(HotbarContainer, InventoryContainer) } + val HOTBAR_AND_INVENTORY = selectContainers(HotbarContainer, InventoryContainer) val PLAYER = - selectContainer { - ofAny( - HotbarContainer, - InventoryContainer, - OffHandContainer, - CursorContainer, - ArmorContainer, - CreativeContainer - ) - } + selectContainers( + HotbarContainer, + InventoryContainer, + OffHandContainer, + CursorContainer, + ArmorContainer, + CreativeContainer + ) } } @Suppress("unused") @ContainerMarker -class ContainerSelectionBuilder private constructor() { +class ContainerSelectionBuilder @ContainerMarker private constructor( + private val scope: ContainerSearchScope +) { + @ContainerMarker + private constructor( + selection: ContainerSelection, + scope: ContainerSearchScope + ) : this(scope) { + this.selector = selection.selector + this.containersWhitelist.addAll(selection.containersWhitelist) + this.accessedOnly = selection.accessedOnly + this.comparator = selection.comparator + } + private var selector: (Container) -> Boolean = { true } private var containersWhitelist = mutableListOf() private var accessedOnly = false @@ -94,10 +118,14 @@ class ContainerSelectionBuilder private constructor() { appendSelector { container -> !types.contains(container.type) } } - fun matches(stackSelection: StackSelection) { + fun matchesSlots(stackSelection: StackSelection) { appendSelector { container -> stackSelection.filter(container.slots).isNotEmpty() } } + fun matchesStacks(stackSelection: StackSelection) { + appendSelector { container -> stackSelection.filter(container.stacks).isNotEmpty() } + } + fun matches(containerSelection: ContainerSelection) { appendSelector { container -> containerSelection.matches(container) } } @@ -131,11 +159,17 @@ class ContainerSelectionBuilder private constructor() { this.selector = { currentSelector(it) && selector(it) xor invert } } - private fun build() = ContainerSelection(selector, containersWhitelist, accessedOnly, comparator) + private fun build() = ContainerSelection(selector, scope, containersWhitelist, accessedOnly, comparator) companion object { fun selectContainer( + scope: ContainerSearchScope = ContainerSearchScope.Accessed, builder: ContainerSelectionBuilder.() -> Unit - ) = ContainerSelectionBuilder().apply(builder).build() + ) = ContainerSelectionBuilder(scope).apply(builder).build() + + fun ContainerSelection.mutate( + scope: ContainerSearchScope = this.scope, + builder: ContainerSelectionBuilder.() -> Unit = {} + ) = ContainerSelectionBuilder(this, scope).apply(builder).build() } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/container/selection/StackSelection.kt b/src/main/kotlin/com/lambda/interaction/container/selection/StackSelection.kt index b1c403288..a58f95d22 100644 --- a/src/main/kotlin/com/lambda/interaction/container/selection/StackSelection.kt +++ b/src/main/kotlin/com/lambda/interaction/container/selection/StackSelection.kt @@ -17,6 +17,7 @@ package com.lambda.interaction.container.selection +import com.lambda.interaction.container.Container import com.lambda.interaction.container.ContainerMarker import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.selectStack import com.lambda.util.EnchantmentUtils.getEnchantment @@ -43,7 +44,7 @@ fun Item.select(count: Int = 0) = selectStack(count) { isItem(this@select) } fun ItemStack.select(count: Int = 0) = selectStack(count) { isItemStack(this@select) } @Suppress("unused") -class StackSelection( +class StackSelection @ContainerMarker internal constructor( val whitelistedSlots: Collection = emptyList(), val count: Int = 0, val item: Item? = null, @@ -85,13 +86,21 @@ class StackSelection( fun filter(slots: Iterable) = slots .let { slots -> - if (whitelistedSlots.isNotEmpty()) whitelistedSlots.filter { it in slots } else slots + if (whitelistedSlots.isNotEmpty()) { + whitelistedSlots.filter { it in slots } + } else slots } .filter(::matches) .map { StackAndSlot(it.stack, it) } .sortedWith(comparator) .map { it.slot } + infix fun isIn(container: Container) = + container.count(this) >= count + + infix fun spaceIn(container: Container) = + container.spaceLeft(this) >= count + companion object { val EVERYTHING = StackSelection { _, _ -> true } val NOTHING = StackSelection { _, _ -> false } @@ -100,16 +109,30 @@ class StackSelection( @Suppress("unused") @ContainerMarker -class StackSelectionBuilder private constructor(private val count: Int = 0) { +class StackSelectionBuilder @ContainerMarker private constructor( + private var count: Int = 0 +) { private val whitelistedSlots = mutableListOf() private var selector: (ItemStack, Slot?) -> Boolean = { _, _ -> true } private var item: Item? = null - private var stackByRef = false private var itemStack: ItemStack? = null private var comparator: Comparator>? = null private var inShulkerBoxesOnly: Boolean = false private var invertNewSelectors = false + @ContainerMarker + private constructor( + selection: StackSelection, + count: Int = selection.count + ) : this(count) { + this.whitelistedSlots.addAll(selection.whitelistedSlots) + this.selector = selection.selector + this.item = selection.item + this.itemStack = selection.itemStack + this.comparator = selection.comparator + this.inShulkerBoxesOnly = selection.inShulkerBox + } + fun isItem(item: Item) { this.item = item appendSelector { stack, _ -> stack.item == item } @@ -146,7 +169,7 @@ class StackSelectionBuilder private constructor(private val count: Int = 0) { appendSelector { stack, _ -> stack.item in ItemUtils.SHULKER_BOXES } } - fun hasCustomName(name: String) { + fun hasName(name: String) { appendSelector { stack, _ -> stack.name.string == name } @@ -293,6 +316,11 @@ class StackSelectionBuilder private constructor(private val count: Int = 0) { count: Int = 0, builder: StackSelectionBuilder.() -> Unit ) = StackSelectionBuilder(count).apply(builder).build() + + fun StackSelection.mutate( + count: Int = this.count, + builder: StackSelectionBuilder.() -> Unit = {} + ) = StackSelectionBuilder(this, count).apply(builder).build() } } diff --git a/src/main/kotlin/com/lambda/interaction/handler/handlers/BaritoneHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/BaritoneHandler.kt index b0959604f..85fe0978f 100644 --- a/src/main/kotlin/com/lambda/interaction/handler/handlers/BaritoneHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/BaritoneHandler.kt @@ -28,7 +28,7 @@ import com.lambda.config.Tab import com.lambda.config.automation.AutomationConfig import com.lambda.config.blocks.RotationSettings import com.lambda.config.categories.LambdaCategory -import com.lambda.config.entries.Setting.Companion.onValueChange +import com.lambda.config.entries.onValueChange import com.lambda.context.Automated import com.lambda.util.BlockUtils.blockPos import net.minecraft.util.BlockMirror diff --git a/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt index 1740a2eea..be438a6ad 100644 --- a/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt @@ -26,6 +26,7 @@ import com.lambda.event.events.WorldEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.container.Container import com.lambda.interaction.container.ContainerMarker +import com.lambda.interaction.container.ContainerSerializer import com.lambda.interaction.container.PlacedContainer import com.lambda.interaction.container.containers.external.ChestContainer import com.lambda.interaction.container.containers.external.DoubleChestContainer @@ -35,16 +36,17 @@ import com.lambda.interaction.container.containers.external.ShulkerBoxContainer import com.lambda.interaction.container.selection.ContainerSelection import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.selectContainer import com.lambda.interaction.container.selection.StackSelection -import com.lambda.interaction.container.selection.select -import com.lambda.interaction.handler.handlers.ContainerHandler.filteredContainers +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.mutate +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.selectStack import com.lambda.util.BlockUtils.blockEntity -import com.lambda.util.BlockUtils.blockState +import com.lambda.util.FolderRegistry import com.lambda.util.ReflectionUtils.getInstances import com.lambda.util.extension.containerStacks import com.lambda.util.item.ItemStackUtils.shulkerBoxStacks import com.lambda.util.item.ItemUtils.SHULKER_BOXES import com.lambda.util.player.SlotUtils.typeSafe import net.minecraft.block.ChestBlock +import net.minecraft.block.ShulkerBoxBlock import net.minecraft.block.entity.BlockEntity import net.minecraft.block.entity.ChestBlockEntity import net.minecraft.block.entity.EnderChestBlockEntity @@ -56,260 +58,307 @@ import net.minecraft.screen.ScreenHandler import net.minecraft.screen.ScreenHandlerType import net.minecraft.state.property.Properties import net.minecraft.util.math.BlockPos -import net.minecraft.util.math.ChunkPos +import com.lambda.Lambda +import com.lambda.context.AutomatedSafeContext +import java.nio.file.Files @Suppress("unused") object ContainerHandler : Loadable { - val containers: Sequence - get() { - fun Container.allNested(): Sequence = - sequence { - val nested = storedContainers.values - yieldAll(nested) - nested.forEach { yieldAll(it.allNested()) } - } - val baseContainers = compileContainers.asSequence() + - placedContainers.values.asSequence().flatMap { it.values } - return baseContainers + baseContainers.flatMap { it.allNested() } - } - - val compileContainers = getInstances() - val placedContainers = mutableMapOf>() - - context(automated: Automated) - val filteredContainers - get() = containers - .filter { automated.inventoryConfig.containerSelection.matches(it) } - .sorted() - - var lastInteractedBlockEntity: BlockEntity? = null - var pendingInteractedBlockEntity: BlockEntity? = null - - override fun load() = "Loaded ${compileContainers.size} containers" - - init { - listen { event -> - val packet = event.packet as? PlayerInteractBlockC2SPacket ?: return@listen - val entity = blockEntity(packet.blockHitResult.blockPos) - pendingInteractedBlockEntity = entity - lastInteractedBlockEntity = entity - } - - listen { event -> - val sh = event.screenHandler - onContainerUpdate(sh) - if (sh.syncId != 0 && pendingInteractedBlockEntity == null) { - lastInteractedBlockEntity = null - } - } - - listen { - if (it.screenHandler.syncId != 0) { - pendingInteractedBlockEntity = null - } - } - - listen { onContainerUpdate() } - listen { onContainerUpdate() } - - listen { event -> - val pos = event.pos - val containersInChunk = placedContainers[ChunkPos(pos)] ?: return@listen - val cachedContainer = containersInChunk[pos] ?: return@listen - val currentState = blockState(cachedContainer.pos) - if (currentState.block != event.newState.block) { - containersInChunk.remove(pos) - } - } - - listen { event -> - val chunk = event.chunk - val cachedContainers = placedContainers[chunk.pos] ?: return@listen - cachedContainers.values.retainAll { container -> - val matchingEntity = chunk.blockEntities[container.pos] ?: return@retainAll false - blockState(container.pos).block == matchingEntity.cachedState.block - } - } - } - - private fun SafeContext.onContainerUpdate(sh: ScreenHandler = player.currentScreenHandler) { - val blockEntity = lastInteractedBlockEntity - if (blockEntity != null) updatePlacedContainer(sh, blockEntity) - } - - private fun updatePlacedContainer( - sh: ScreenHandler, - blockEntity: BlockEntity - ) { - val pos = blockEntity.pos - when (blockEntity) { - is EnderChestBlockEntity -> { - if (sh.typeSafe != ScreenHandlerType.GENERIC_9X3) return - EnderChestContainer.apply { - update(sh.containerStacks) - scanContainerContents() - } - } - - is ChestBlockEntity -> { - val state = blockEntity.cachedState - if (state.block !is ChestBlock) return - - val stacks = blockEntity.stacks() - val chestType = state.get(Properties.CHEST_TYPE) ?: return - val chunkPos = ChunkPos(pos) - - if (chestType == ChestType.SINGLE) { - if (sh.typeSafe != ScreenHandlerType.GENERIC_9X3) return - (chunkPos.getPlacedContainer(pos) as? ChestContainer) - ?.update(stacks) - ?: run { chunkPos.setPlacedContainer(pos, ChestContainer(pos, stacks)) } - } else { - if (sh.typeSafe != ScreenHandlerType.GENERIC_9X6) return - val facing = state.get(Properties.HORIZONTAL_FACING) ?: return - val otherPos = - when (chestType) { - ChestType.LEFT -> pos.offset(facing.rotateYClockwise()) - ChestType.RIGHT -> pos.offset(facing.rotateYCounterclockwise()) - } - val leftPos = if (chestType == ChestType.LEFT) pos else otherPos - val leftChunkPos = ChunkPos(leftPos) - val rightPos = if (chestType == ChestType.RIGHT) pos else otherPos - val rightChunkPos = ChunkPos(rightPos) - - val existing = (leftChunkPos.getPlacedContainer(leftPos) as? DoubleChestContainer) - ?: (rightChunkPos.getPlacedContainer(rightPos) as? DoubleChestContainer) - - if (existing != null) { - existing.update(stacks) - leftChunkPos.setPlacedContainer(leftPos, existing) - rightChunkPos.setPlacedContainer(rightPos, existing) - } else { - val doubleChest = DoubleChestContainer(pos, leftPos, rightPos, stacks) - leftChunkPos.setPlacedContainer(leftPos, doubleChest) - rightChunkPos.setPlacedContainer(rightPos, doubleChest) - } - } - - chunkPos.getPlacedContainer(pos)?.scanContainerContents() - } - - is ShulkerBoxBlockEntity -> { - if (sh.typeSafe != ScreenHandlerType.SHULKER_BOX) return - val stacks = blockEntity.stacks() - val chunkPos = ChunkPos(pos) - (chunkPos.getPlacedContainer(pos) as? PlacedShulkerBoxContainer) - ?.update(stacks) - ?: run { - chunkPos.setPlacedContainer( - pos, - PlacedShulkerBoxContainer(pos, blockEntity.cachedState.block, null, stacks) - ) - } - - chunkPos.getPlacedContainer(pos)?.scanContainerContents() - } - } - } - - private fun Container.scanContainerContents() { - slots.forEach { slot -> - val stack = slot.stack - val index = slot.index - if (stack.item in SHULKER_BOXES) { - storedContainers[index] = - ShulkerBoxContainer( - stack.name.string, - stack.item, - stack.shulkerBoxStacks, - this, - index - ).also { it.scanContainerContents() } - } else storedContainers.remove(index) - } - } - - private fun getPlacedContainer(pos: BlockPos) = placedContainers[ChunkPos(pos)]?.get(pos) - - private fun ChunkPos.getPlacedContainer(pos: BlockPos) = placedContainers[this]?.get(pos) - - private fun setPlacedContainer(pos: BlockPos, container: PlacedContainer) = - placedContainers.getOrPut(ChunkPos(pos)) { mutableMapOf() }.put(pos, container) - - private fun ChunkPos.setPlacedContainer(pos: BlockPos, container: PlacedContainer) = - placedContainers.getOrPut(this) { mutableMapOf() }.put(pos, container) - - private fun Inventory.stacks() = iterator().asSequence().toList() + val compileContainers = getInstances() + var accessedPlacedContainer: PlacedContainer? = null + private set + + var lastInteractedBlockEntity: BlockEntity? = null + var pendingInteractedBlockEntity: BlockEntity? = null + + override fun load(): String { + ContainerSerializer.loadEnderChest() + return "Loaded ${compileContainers.size} containers" + } + + init { + listen { event -> + val packet = event.packet as? PlayerInteractBlockC2SPacket ?: return@listen + val entity = blockEntity(packet.blockHitResult.blockPos) + pendingInteractedBlockEntity = entity + lastInteractedBlockEntity = entity + } + + listen { event -> + val sh = event.screenHandler + onContainerUpdate(sh) + if (sh.syncId != 0 && pendingInteractedBlockEntity == null) { + lastInteractedBlockEntity = null + } + accessedPlacedContainer = null + } + + listen { + if (it.screenHandler.syncId != 0) { + pendingInteractedBlockEntity = null + } + } + + listen { onContainerUpdate() } + listen { onContainerUpdate() } + + listen { event -> + val oldBlock = event.oldState.block + if (oldBlock is ChestBlock || oldBlock is ShulkerBoxBlock) { + if (event.newState.block != oldBlock) { + ContainerSerializer.removeContainer(event.pos) + } + } + } + + listen { event -> + val chunk = event.chunk + val prefix = "${chunk.pos.x}_${chunk.pos.z}_" + + try { + val dir = FolderRegistry.containers + if (Files.exists(dir)) { + Files.newDirectoryStream(dir, "$prefix*.json").use { stream -> + stream.forEach { path -> + val parts = path.fileName.toString().removeSuffix(".json").split('_') + if (parts.size == 5) { + val x = parts[2].toIntOrNull() ?: return@forEach + val y = parts[3].toIntOrNull() ?: return@forEach + val z = parts[4].toIntOrNull() ?: return@forEach + val pos = BlockPos(x, y, z) + val entity = chunk.blockEntities[pos] + if (entity !is ChestBlockEntity && entity !is ShulkerBoxBlockEntity) { + Files.deleteIfExists(path) + } + } + } + } + } + } catch (e: Exception) { + Lambda.LOG.warn("Failed to clean up ghost containers for chunk ${chunk.pos}", e) + } + } + } + + private fun SafeContext.onContainerUpdate(sh: ScreenHandler = player.currentScreenHandler) { + val blockEntity = lastInteractedBlockEntity + if (blockEntity != null) updatePlacedContainer(sh, blockEntity) + } + + private fun updatePlacedContainer( + sh: ScreenHandler, + blockEntity: BlockEntity + ) { + val pos = blockEntity.pos + when (blockEntity) { + is EnderChestBlockEntity -> { + if (sh.typeSafe != ScreenHandlerType.GENERIC_9X3) return + EnderChestContainer.apply { + update(sh.containerStacks) + scanContainerContents() + } + ContainerSerializer.saveEnderChest() + } + + is ChestBlockEntity -> { + val state = blockEntity.cachedState + if (state.block !is ChestBlock) return + + val stacks = blockEntity.stacks() + val chestType = state.get(Properties.CHEST_TYPE) ?: return + + if (chestType == ChestType.SINGLE) { + if (sh.typeSafe != ScreenHandlerType.GENERIC_9X3) return + (accessedPlacedContainer as? ChestContainer) + ?.update(stacks) + ?: run { accessedPlacedContainer = ChestContainer(pos, stacks) } + } else { + if (sh.typeSafe != ScreenHandlerType.GENERIC_9X6) return + val facing = state.get(Properties.HORIZONTAL_FACING) ?: return + val otherPos = + when (chestType) { + ChestType.LEFT -> pos.offset(facing.rotateYClockwise()) + ChestType.RIGHT -> pos.offset(facing.rotateYCounterclockwise()) + } + val leftPos = if (chestType == ChestType.LEFT) pos else otherPos + val rightPos = if (chestType == ChestType.RIGHT) pos else otherPos + + val existing = accessedPlacedContainer as? DoubleChestContainer + if (existing != null && existing.pos == pos) { + existing.update(stacks) + } else { + accessedPlacedContainer = DoubleChestContainer(pos, leftPos, rightPos, stacks) + } + } + + accessedPlacedContainer?.let { + it.scanContainerContents() + ContainerSerializer.saveContainer(it) + } + } + + is ShulkerBoxBlockEntity -> { + if (sh.typeSafe != ScreenHandlerType.SHULKER_BOX) return + val stacks = blockEntity.stacks() + (accessedPlacedContainer as? PlacedShulkerBoxContainer) + ?.update(stacks) + ?: run { + accessedPlacedContainer = PlacedShulkerBoxContainer(pos, blockEntity.cachedState.block, null, stacks) + } + + accessedPlacedContainer?.let { + it.scanContainerContents() + ContainerSerializer.saveContainer(it) + } + } + } + } + + private fun Container.scanContainerContents() { + slots.forEach { slot -> + val stack = slot.stack + val index = slot.index + if (stack.item in SHULKER_BOXES) { + storedContainers[index] = + ShulkerBoxContainer( + stack.name.string, + stack.item, + stack.shulkerBoxStacks, + this, + index + ).also { it.scanContainerContents() } + } else storedContainers.remove(index) + } + } + + private fun Inventory.stacks() = iterator().asSequence().toList() } @ContainerMarker -context(_: Automated) -fun findContainer( - selection: ContainerSelection -) = filteredContainers.find(selection) +context(_: AutomatedSafeContext) +fun StackSelection.move( + fromSelection: ContainerSelection = ContainerSelection.EVERYTHING, + toSelection: ContainerSelection = ContainerSelection.EVERYTHING, + toStackSelection: StackSelection = StackSelection.EVERYTHING +): Boolean { + val singleSelection = mutate(count.coerceAtMost(64)) + val fromContainer = + findContainer( + singleSelection, + selectContainer { + matches(fromSelection) + isAccessed() + } + ) + ?: return false + + val toContainer = + findContainer( + toStackSelection, + selectContainer { + matches(toSelection) + isAccessed() + } + ) + ?: return false + + val (fromSlot, toSlot) = fromContainer.findMoveSlots(this, toContainer, toStackSelection) + if (fromSlot == null || toSlot == null) return false + + return fromContainer.swap(fromSlot, toSlot, toContainer) +} @ContainerMarker -context(automated: Automated) -fun StackSelection.findContainer( - selection: ContainerSelection = ContainerSelection.EVERYTHING -) = findContainers(selection).firstOrNull() +context(_: Automated) +fun findContainer( + stackSelection: StackSelection = StackSelection.EVERYTHING, + containerSelection: ContainerSelection = ContainerSelection.EVERYTHING +) = findContainers(stackSelection, containerSelection) + .firstOrNull() @ContainerMarker context(automated: Automated) -fun StackSelection.findContainers( - containerSelection: ContainerSelection = ContainerSelection.EVERYTHING -) = filteredContainers - .filter { containerSelection.matches(it) } - .filter { it.stackCount(this) >= count } - .sortedWith(automated.inventoryConfig.accessPriority.materialComparator(this)) +fun findContainers( + stackSelection: StackSelection = StackSelection.EVERYTHING, + containerSelection: ContainerSelection = ContainerSelection.EVERYTHING +) = searchContainers(containerSelection.scope) + .filter { containerSelection.matches(it) } + .filter { stackSelection isIn it } + .sortedWith(automated.inventoryConfig.accessPriority.materialComparator(stackSelection)) @ContainerMarker -context(automated: Automated) +context(_: Automated) fun StackSelection.findContainerWithSpace( - containerSelection: ContainerSelection = ContainerSelection.EVERYTHING -) = findContainersWithSpace(containerSelection).firstOrNull() + containerSelection: ContainerSelection = ContainerSelection.EVERYTHING +) = findContainersWithSpace(containerSelection) + .firstOrNull() @ContainerMarker context(automated: Automated) fun StackSelection.findContainersWithSpace( - containerSelection: ContainerSelection = ContainerSelection.EVERYTHING -) = filteredContainers - .filter { containerSelection.matches(it) } - .filter { it.spaceAvailable(this) >= count } - .sortedWith(automated.inventoryConfig.accessPriority.spaceComparator(this)) + containerSelection: ContainerSelection = ContainerSelection.EVERYTHING +) = searchContainers(containerSelection.scope) + .filter { containerSelection.matches(it) } + .filter { this spaceIn it } + .sortedWith(automated.inventoryConfig.accessPriority.spaceComparator(this)) @ContainerMarker -context(automated: Automated) -fun StackSelection.findSlots( - containerSelection: ContainerSelection = ContainerSelection.EVERYTHING -) = - findContainers( - selectContainer { - matches(containerSelection) - isAccessed() - } - ).flatMap { filter(it.slots) } +context(_: Automated) +fun findContainerWithDisposable( + containerSelection: ContainerSelection = ContainerSelection.EVERYTHING +) = findContainersWithDisposable(containerSelection) + .firstOrNull() @ContainerMarker context(automated: Automated) -fun findContainerWithDisposable( - containerSelection: ContainerSelection = ContainerSelection.EVERYTHING +fun findContainersWithDisposable( + containerSelection: ContainerSelection = ContainerSelection.EVERYTHING ) = - with(automated) { - findContainer( - selectContainer { - matches(containerSelection) - custom { container -> - inventoryConfig.disposables.any { container.stackCount(it.asItem().select(1)) > 0 } - } - } - ) - } - -private fun getSearchContainers(scope: ContainerSearchScope): Collection { + with(automated) { + findContainers( + containerSelection = selectContainer(containerSelection.scope) { + matches(containerSelection) + matchesStacks( + selectStack(1) { + isOneOfItems(inventoryConfig.disposables) + } + ) + } + ) + } +context(automated: Automated) +private fun searchContainers(scope: ContainerSearchScope): Sequence { + fun Container.allNested(): Sequence = + sequence { + yield(this@allNested) + storedContainers.values.forEach { nested -> + yieldAll(nested.allNested()) + } + } + + val compiled = ContainerHandler.compileContainers.asSequence() + val placedSeq = sequenceOf(ContainerHandler.accessedPlacedContainer).filterNotNull() + + val baseContainers = + when (scope) { + ContainerSearchScope.Player -> compiled - EnderChestContainer + ContainerSearchScope.Compiled -> compiled + ContainerSearchScope.Accessed -> placedSeq + compiled.filter { it.isAccessed } + ContainerSearchScope.All -> compiled + placedSeq + + ContainerSerializer.serializedContainers.filter { diskContainer -> + diskContainer.pos != ContainerHandler.accessedPlacedContainer?.pos + } + } + + return baseContainers + .flatMap { it.allNested() } + .filter { automated.inventoryConfig.containerSelection.matches(it) } } -private enum class ContainerSearchScope { - +enum class ContainerSearchScope { + Player, + Compiled, + Accessed, + All } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/handler/handlers/GlideHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/GlideHandler.kt index 87225c54f..d7de19c14 100644 --- a/src/main/kotlin/com/lambda/interaction/handler/handlers/GlideHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/GlideHandler.kt @@ -17,9 +17,11 @@ package com.lambda.interaction.handler.handlers +import com.lambda.context.Automated import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.container.selection.ContainerSelection import com.lambda.interaction.container.selection.StackAndSlot import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.selectStack import com.lambda.module.modules.combat.AutoArmor @@ -164,14 +166,18 @@ object GlideHandler { } @JvmStatic - context(safeContext: SafeContext) + context(safeContext: SafeContext, _: Automated) fun canGlide(): Boolean = with(safeContext) { val fakeFly = ElytraFly.isEnabled && ElytraFly.fakeFly val canGlideAlready = player.canGlideWithChestPiece() != fakeFly if (!autoSwapChecking || canGlideAlready) return canGlideAlready val selection = if (fakeFly) CHESTPLATE_SELECTION else ELYTRA_SELECTION - return selection.findContainers(HOTBAR_AND_INVENTORY_CONTAINERS) + return findContainers( + selection, + ContainerSelection.HOTBAR_AND_INVENTORY + ).toList() + .isNotEmpty() } private class PendingGlideAction( diff --git a/src/main/kotlin/com/lambda/interaction/handler/handlers/interacting/InteractedBlockHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/InteractedBlockHandler.kt similarity index 94% rename from src/main/kotlin/com/lambda/interaction/handler/handlers/interacting/InteractedBlockHandler.kt rename to src/main/kotlin/com/lambda/interaction/handler/handlers/InteractedBlockHandler.kt index b5dc73358..829ce02ed 100644 --- a/src/main/kotlin/com/lambda/interaction/handler/handlers/interacting/InteractedBlockHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/InteractedBlockHandler.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.handler.handlers.interacting +package com.lambda.interaction.handler.handlers import com.lambda.config.automation.AutomationConfig import com.lambda.config.blocks.InteractConfig @@ -32,8 +32,8 @@ import com.lambda.util.collections.LimitedDecayQueue internal object InteractedBlockHandler : PostActionHandler() { override val pendingActions = LimitedDecayQueue( - AutomationConfig.DEFAULT.buildConfig.maxPendingActions, - AutomationConfig.DEFAULT.buildConfig.actionTimeout * 50L + AutomationConfig.Companion.DEFAULT.buildConfig.maxPendingActions, + AutomationConfig.Companion.DEFAULT.buildConfig.actionTimeout * 50L ) { if (Client.verboseDebug) warn("${it::class.simpleName} at ${it.context.blockPos.toShortString()} timed out") if (it.interactConfig.interactConfirmationMode != InteractConfig.InteractConfirmationMode.AwaitThenPlace) { diff --git a/src/main/kotlin/com/lambda/interaction/handler/handlers/packet/PacketLimitHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/PacketLimitHandler.kt similarity index 97% rename from src/main/kotlin/com/lambda/interaction/handler/handlers/packet/PacketLimitHandler.kt rename to src/main/kotlin/com/lambda/interaction/handler/handlers/PacketLimitHandler.kt index 6229e1e29..8820cd659 100644 --- a/src/main/kotlin/com/lambda/interaction/handler/handlers/packet/PacketLimitHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/PacketLimitHandler.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.handler.handlers.packet +package com.lambda.interaction.handler.handlers import com.lambda.config.blocks.BuildConfig import com.lambda.context.Automated diff --git a/src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/RebreakHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/RebreakHandler.kt index 5817eb84c..f800db9ff 100644 --- a/src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/RebreakHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/RebreakHandler.kt @@ -26,8 +26,8 @@ import com.lambda.event.listener.UnsafeListener.Companion.listenUnsafe import com.lambda.interaction.construction.simulation.context.BreakContext import com.lambda.interaction.handler.handlers.breaking.BrokenBlockHandler.destroyBlock import com.lambda.interaction.handler.handlers.breaking.RebreakHandler.reBreak -import com.lambda.interaction.handler.handlers.packet.PacketLimitHandler -import com.lambda.interaction.handler.handlers.packet.PacketType +import com.lambda.interaction.handler.handlers.PacketLimitHandler +import com.lambda.interaction.handler.handlers.PacketType import com.lambda.interaction.manager.managers.breaking.BreakInfo import com.lambda.interaction.manager.managers.breaking.BreakManager import com.lambda.interaction.manager.managers.breaking.BreakManager.calcBreakDelta diff --git a/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakManager.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakManager.kt index 64ab9cd82..00a14ab4e 100644 --- a/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakManager.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakManager.kt @@ -42,8 +42,8 @@ import com.lambda.interaction.handler.handlers.breaking.BrokenBlockHandler.start import com.lambda.interaction.handler.handlers.breaking.RebreakHandler import com.lambda.interaction.handler.handlers.breaking.RebreakHandler.getRebreakPotential import com.lambda.interaction.handler.handlers.breaking.RebreakResult -import com.lambda.interaction.handler.handlers.packet.PacketLimitHandler -import com.lambda.interaction.handler.handlers.packet.PacketType +import com.lambda.interaction.handler.handlers.PacketLimitHandler +import com.lambda.interaction.handler.handlers.PacketType import com.lambda.interaction.manager.Manager import com.lambda.interaction.manager.ManagerUtils.isPosBlocked import com.lambda.interaction.manager.PositionBlocking diff --git a/src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractManager.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractManager.kt index 4c57ca26a..315281898 100644 --- a/src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractManager.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractManager.kt @@ -28,10 +28,10 @@ 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.interaction.construction.simulation.context.InteractContext -import com.lambda.interaction.handler.handlers.interacting.InteractedBlockHandler -import com.lambda.interaction.handler.handlers.interacting.InteractedBlockHandler.startPending -import com.lambda.interaction.handler.handlers.packet.PacketLimitHandler -import com.lambda.interaction.handler.handlers.packet.PacketType +import com.lambda.interaction.handler.handlers.InteractedBlockHandler +import com.lambda.interaction.handler.handlers.InteractedBlockHandler.startPending +import com.lambda.interaction.handler.handlers.PacketLimitHandler +import com.lambda.interaction.handler.handlers.PacketType import com.lambda.interaction.manager.Manager import com.lambda.interaction.manager.ManagerUtils.isPosBlocked import com.lambda.interaction.manager.PositionBlocking diff --git a/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryManager.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryManager.kt index ee90518eb..62fda5e39 100644 --- a/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryManager.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryManager.kt @@ -22,9 +22,9 @@ import com.lambda.context.SafeContext import com.lambda.event.events.PacketEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.handler.handlers.packet.PacketLimitHandler.canSendPackets -import com.lambda.interaction.handler.handlers.packet.PacketLimitHandler.sentPackets -import com.lambda.interaction.handler.handlers.packet.PacketType +import com.lambda.interaction.handler.handlers.PacketLimitHandler.canSendPackets +import com.lambda.interaction.handler.handlers.PacketLimitHandler.sentPackets +import com.lambda.interaction.handler.handlers.PacketType import com.lambda.interaction.manager.Manager import com.lambda.interaction.manager.managers.inventory.InventoryManager.actions import com.lambda.interaction.manager.managers.inventory.InventoryManager.activeRequest 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 deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/main/kotlin/com/lambda/interaction/material/container/containers/EnderChestContainer.kt b/src/main/kotlin/com/lambda/interaction/material/container/containers/EnderChestContainer.kt deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/main/kotlin/com/lambda/interaction/material/container/containers/ShulkerBoxContainer.kt b/src/main/kotlin/com/lambda/interaction/material/container/containers/ShulkerBoxContainer.kt deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/main/kotlin/com/lambda/module/HudModule.kt b/src/main/kotlin/com/lambda/module/HudModule.kt index 9b3d571e4..23be5636b 100644 --- a/src/main/kotlin/com/lambda/module/HudModule.kt +++ b/src/main/kotlin/com/lambda/module/HudModule.kt @@ -19,7 +19,7 @@ package com.lambda.module import com.lambda.config.settings.complex.Bind import com.lambda.gui.Layout -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import java.awt.Color abstract class HudModule( diff --git a/src/main/kotlin/com/lambda/module/Module.kt b/src/main/kotlin/com/lambda/module/Module.kt index 0f106d0db..593fdce38 100644 --- a/src/main/kotlin/com/lambda/module/Module.kt +++ b/src/main/kotlin/com/lambda/module/Module.kt @@ -23,8 +23,8 @@ import com.lambda.config.ConfigCategory import com.lambda.config.automation.IMutableAutomationConfig import com.lambda.config.automation.MutableAutomationConfig import com.lambda.config.categories.ModuleCategory -import com.lambda.config.entries.Setting.Companion.onValueChange -import com.lambda.config.entries.Setting.Companion.onValueChangeUnsafe +import com.lambda.config.entries.onValueChange +import com.lambda.config.entries.onValueChangeUnsafe import com.lambda.config.settings.complex.Bind import com.lambda.config.settings.complex.KeybindSetting.Companion.onPress import com.lambda.config.settings.complex.KeybindSetting.Companion.onRelease @@ -41,7 +41,6 @@ import com.lambda.event.listener.SafeListener import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.event.listener.UnsafeListener import com.lambda.module.modules.client.Client -import com.lambda.module.tag.ModuleTag import com.lambda.sound.LambdaSound import com.lambda.sound.SoundHandler.play import com.lambda.util.KeyCode diff --git a/src/main/kotlin/com/lambda/module/tag/ModuleTag.kt b/src/main/kotlin/com/lambda/module/ModuleTag.kt similarity index 96% rename from src/main/kotlin/com/lambda/module/tag/ModuleTag.kt rename to src/main/kotlin/com/lambda/module/ModuleTag.kt index caab346cc..e01c0d70b 100644 --- a/src/main/kotlin/com/lambda/module/tag/ModuleTag.kt +++ b/src/main/kotlin/com/lambda/module/ModuleTag.kt @@ -15,12 +15,12 @@ * along with this program. If not, see . */ -package com.lambda.module.tag +package com.lambda.module import com.lambda.util.Nameable /** - * The [ModuleTag] class represents a tag, that can be associated in any cardinality with a [Module]. + * The [ModuleTag] class represents a tag, that can be associated in any cardinality with a [java.lang.Module]. * * Tags are used to categorize and organize modules, making them easier to find. * They can be custom created as per the user's needs. @@ -61,4 +61,4 @@ data class ModuleTag(override val name: String) : Nameable { fun isTagShown(tag: ModuleTag) = shownTags.contains(tag) } -} +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/module/hud/AccountName.kt b/src/main/kotlin/com/lambda/module/hud/AccountName.kt index 7b81bdaa3..24289c330 100644 --- a/src/main/kotlin/com/lambda/module/hud/AccountName.kt +++ b/src/main/kotlin/com/lambda/module/hud/AccountName.kt @@ -19,7 +19,7 @@ package com.lambda.module.hud import com.lambda.gui.dsl.ImGuiBuilder import com.lambda.module.HudModule -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafe @Suppress("unused") diff --git a/src/main/kotlin/com/lambda/module/hud/Baritone.kt b/src/main/kotlin/com/lambda/module/hud/Baritone.kt index 438671ece..fa0e9c10b 100644 --- a/src/main/kotlin/com/lambda/module/hud/Baritone.kt +++ b/src/main/kotlin/com/lambda/module/hud/Baritone.kt @@ -21,7 +21,7 @@ import com.lambda.gui.dsl.ImGuiBuilder import com.lambda.interaction.construction.simulation.BuildGoal import com.lambda.interaction.handler.handlers.BaritoneHandler import com.lambda.module.HudModule -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag @Suppress("unused") object Baritone : HudModule( diff --git a/src/main/kotlin/com/lambda/module/hud/Coordinates.kt b/src/main/kotlin/com/lambda/module/hud/Coordinates.kt index a09dae66f..5498a2d2a 100644 --- a/src/main/kotlin/com/lambda/module/hud/Coordinates.kt +++ b/src/main/kotlin/com/lambda/module/hud/Coordinates.kt @@ -23,7 +23,7 @@ import com.lambda.config.editSetting import com.lambda.config.withEdits import com.lambda.gui.dsl.ImGuiBuilder import com.lambda.module.HudModule -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafe import com.lambda.util.FormattingUtils.format import com.lambda.util.extension.dimensionName diff --git a/src/main/kotlin/com/lambda/module/hud/Fps.kt b/src/main/kotlin/com/lambda/module/hud/Fps.kt index 917238a02..2dcf3a3a9 100644 --- a/src/main/kotlin/com/lambda/module/hud/Fps.kt +++ b/src/main/kotlin/com/lambda/module/hud/Fps.kt @@ -21,7 +21,7 @@ import com.lambda.event.events.RenderEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.gui.dsl.ImGuiBuilder import com.lambda.module.HudModule -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.collections.LimitedDecayQueue import kotlin.time.Duration.Companion.seconds diff --git a/src/main/kotlin/com/lambda/module/hud/ModuleList.kt b/src/main/kotlin/com/lambda/module/hud/ModuleList.kt index cd438fbc2..6180de437 100644 --- a/src/main/kotlin/com/lambda/module/hud/ModuleList.kt +++ b/src/main/kotlin/com/lambda/module/hud/ModuleList.kt @@ -21,7 +21,7 @@ import com.lambda.gui.dsl.ImGuiBuilder import com.lambda.imgui.flag.ImGuiCol import com.lambda.module.HudModule import com.lambda.module.ModuleRegistry -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import java.awt.Color @Suppress("unused") diff --git a/src/main/kotlin/com/lambda/module/hud/Rotation.kt b/src/main/kotlin/com/lambda/module/hud/Rotation.kt index b9246cd0b..6530c1992 100644 --- a/src/main/kotlin/com/lambda/module/hud/Rotation.kt +++ b/src/main/kotlin/com/lambda/module/hud/Rotation.kt @@ -22,7 +22,7 @@ import com.lambda.config.editSetting import com.lambda.config.withEdits import com.lambda.gui.dsl.ImGuiBuilder import com.lambda.module.HudModule -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafe import com.lambda.util.FormattingUtils.format diff --git a/src/main/kotlin/com/lambda/module/hud/Speedometer.kt b/src/main/kotlin/com/lambda/module/hud/Speedometer.kt index 5ef49be74..354e8b719 100644 --- a/src/main/kotlin/com/lambda/module/hud/Speedometer.kt +++ b/src/main/kotlin/com/lambda/module/hud/Speedometer.kt @@ -22,7 +22,7 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.gui.dsl.ImGuiBuilder import com.lambda.module.HudModule -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.SpeedUnit import net.minecraft.util.math.Vec3d diff --git a/src/main/kotlin/com/lambda/module/hud/TaskFlowHud.kt b/src/main/kotlin/com/lambda/module/hud/TaskFlowHud.kt index eaa6aadf4..326b2b363 100644 --- a/src/main/kotlin/com/lambda/module/hud/TaskFlowHud.kt +++ b/src/main/kotlin/com/lambda/module/hud/TaskFlowHud.kt @@ -19,7 +19,7 @@ package com.lambda.module.hud import com.lambda.gui.dsl.ImGuiBuilder import com.lambda.module.HudModule -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.task.RootTask @Suppress("unused") diff --git a/src/main/kotlin/com/lambda/module/hud/Tps.kt b/src/main/kotlin/com/lambda/module/hud/Tps.kt index 3c94e178e..2d8c4fb97 100644 --- a/src/main/kotlin/com/lambda/module/hud/Tps.kt +++ b/src/main/kotlin/com/lambda/module/hud/Tps.kt @@ -20,7 +20,7 @@ package com.lambda.module.hud import com.lambda.gui.dsl.ImGuiBuilder import com.lambda.imgui.ImVec2 import com.lambda.module.HudModule -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.FormattingUtils.format import com.lambda.util.ServerTPSUtils import com.lambda.util.ServerTPSUtils.recentData diff --git a/src/main/kotlin/com/lambda/module/hud/Watermark.kt b/src/main/kotlin/com/lambda/module/hud/Watermark.kt index fa46da254..4436e3f2d 100644 --- a/src/main/kotlin/com/lambda/module/hud/Watermark.kt +++ b/src/main/kotlin/com/lambda/module/hud/Watermark.kt @@ -21,7 +21,7 @@ import com.lambda.graphics.texture.TextureOwner.upload import com.lambda.gui.dsl.ImGuiBuilder import com.lambda.imgui.ImGui import com.lambda.module.HudModule -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag @Suppress("unused") object Watermark : HudModule( diff --git a/src/main/kotlin/com/lambda/module/modules/chat/AntiSpam.kt b/src/main/kotlin/com/lambda/module/modules/chat/AntiSpam.kt index 25c6dab7f..cb55a5b9b 100644 --- a/src/main/kotlin/com/lambda/module/modules/chat/AntiSpam.kt +++ b/src/main/kotlin/com/lambda/module/modules/chat/AntiSpam.kt @@ -24,7 +24,7 @@ import com.lambda.event.events.ChatEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.handler.handlers.FriendHandler import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.ChatUtils.addresses import com.lambda.util.ChatUtils.colors import com.lambda.util.ChatUtils.discord diff --git a/src/main/kotlin/com/lambda/module/modules/chat/ChatTimestamp.kt b/src/main/kotlin/com/lambda/module/modules/chat/ChatTimestamp.kt index 9fafeecbb..f3a7fded9 100644 --- a/src/main/kotlin/com/lambda/module/modules/chat/ChatTimestamp.kt +++ b/src/main/kotlin/com/lambda/module/modules/chat/ChatTimestamp.kt @@ -20,13 +20,13 @@ package com.lambda.module.modules.chat import com.lambda.config.blocks.FormatterConfig import com.lambda.config.blocks.FormatterSettings import com.lambda.config.editTypedSettings -import com.lambda.config.entries.Setting.Companion.onValueChange +import com.lambda.config.entries.onValueChange import com.lambda.config.hide import com.lambda.config.withEdits import com.lambda.event.events.ChatEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.FormattingUtils.format import com.lambda.util.text.buildText import com.lambda.util.text.literal diff --git a/src/main/kotlin/com/lambda/module/modules/chat/CustomChat.kt b/src/main/kotlin/com/lambda/module/modules/chat/CustomChat.kt index 3357ecf63..4cff328bd 100644 --- a/src/main/kotlin/com/lambda/module/modules/chat/CustomChat.kt +++ b/src/main/kotlin/com/lambda/module/modules/chat/CustomChat.kt @@ -23,7 +23,7 @@ import com.lambda.event.events.ChatEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.handler.handlers.BaritoneHandler import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.NamedEnum @Suppress("unused") diff --git a/src/main/kotlin/com/lambda/module/modules/chat/FancyChat.kt b/src/main/kotlin/com/lambda/module/modules/chat/FancyChat.kt index c9220aeb6..074b96cd8 100644 --- a/src/main/kotlin/com/lambda/module/modules/chat/FancyChat.kt +++ b/src/main/kotlin/com/lambda/module/modules/chat/FancyChat.kt @@ -22,7 +22,7 @@ import com.lambda.event.events.ChatEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.handler.handlers.BaritoneHandler import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.ChatUtils.toBlue import com.lambda.util.ChatUtils.toGreen import com.lambda.util.ChatUtils.toLeet diff --git a/src/main/kotlin/com/lambda/module/modules/chat/FriendHighlight.kt b/src/main/kotlin/com/lambda/module/modules/chat/FriendHighlight.kt index 604d03a0f..239dcec05 100644 --- a/src/main/kotlin/com/lambda/module/modules/chat/FriendHighlight.kt +++ b/src/main/kotlin/com/lambda/module/modules/chat/FriendHighlight.kt @@ -17,12 +17,12 @@ package com.lambda.module.modules.chat -import com.lambda.config.entries.Setting.Companion.onValueChange +import com.lambda.config.entries.onValueChange import com.lambda.event.events.ChatEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.handler.handlers.FriendHandler import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.sound.SoundHandler.playSound import com.lambda.util.CommunicationUtils.logError import com.lambda.util.text.MessageType 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 9d62142d3..4d3dd975e 100644 --- a/src/main/kotlin/com/lambda/module/modules/client/AutoUpdater.kt +++ b/src/main/kotlin/com/lambda/module/modules/client/AutoUpdater.kt @@ -26,7 +26,7 @@ import com.lambda.gui.dsl.ImGuiBuilder.popupModal import com.lambda.imgui.ImGui import com.lambda.imgui.flag.ImGuiWindowFlags import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runIO import com.lambda.util.CommunicationUtils.debug import com.lambda.util.CommunicationUtils.logError diff --git a/src/main/kotlin/com/lambda/module/modules/client/Capes.kt b/src/main/kotlin/com/lambda/module/modules/client/Capes.kt index e1e42039f..bb42498e2 100644 --- a/src/main/kotlin/com/lambda/module/modules/client/Capes.kt +++ b/src/main/kotlin/com/lambda/module/modules/client/Capes.kt @@ -18,7 +18,7 @@ package com.lambda.module.modules.client import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag object Capes : Module( name = "Capes", diff --git a/src/main/kotlin/com/lambda/module/modules/client/Client.kt b/src/main/kotlin/com/lambda/module/modules/client/Client.kt index 180314afd..a56bd2565 100644 --- a/src/main/kotlin/com/lambda/module/modules/client/Client.kt +++ b/src/main/kotlin/com/lambda/module/modules/client/Client.kt @@ -22,7 +22,7 @@ import com.lambda.config.Tab import com.lambda.graphics.mc.renderer.TickedRenderer.Companion.tickedRenderer import com.lambda.interaction.construction.simulation.result.Drawable import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import java.awt.Color object Client : Module( diff --git a/src/main/kotlin/com/lambda/module/modules/client/Discord.kt b/src/main/kotlin/com/lambda/module/modules/client/Discord.kt index 7858e254f..2679a1052 100644 --- a/src/main/kotlin/com/lambda/module/modules/client/Discord.kt +++ b/src/main/kotlin/com/lambda/module/modules/client/Discord.kt @@ -24,7 +24,7 @@ import com.lambda.event.EventFlow import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listenOnce import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.network.NetworkHandler.updateToken import com.lambda.network.api.v1.endpoints.linkDiscord import com.lambda.threading.runConcurrent diff --git a/src/main/kotlin/com/lambda/module/modules/client/ModuleNotifier.kt b/src/main/kotlin/com/lambda/module/modules/client/ModuleNotifier.kt index 95425ca6b..40a791379 100644 --- a/src/main/kotlin/com/lambda/module/modules/client/ModuleNotifier.kt +++ b/src/main/kotlin/com/lambda/module/modules/client/ModuleNotifier.kt @@ -20,7 +20,7 @@ package com.lambda.module.modules.client import com.lambda.event.events.ModuleEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.CommunicationUtils.log import com.lambda.util.Describable import com.lambda.util.NamedEnum diff --git a/src/main/kotlin/com/lambda/module/modules/combat/AutoArmor.kt b/src/main/kotlin/com/lambda/module/modules/combat/AutoArmor.kt index bbb40e5ce..b5a5518d5 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/AutoArmor.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/AutoArmor.kt @@ -17,7 +17,7 @@ package com.lambda.module.modules.combat -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.hideAllExcept import com.lambda.config.settings.complex.Bind import com.lambda.config.settings.complex.KeybindSetting.Companion.onPress @@ -26,9 +26,13 @@ import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.container.containers.ArmorContainer +import com.lambda.interaction.container.selection.ContainerSelection +import com.lambda.interaction.container.selection.select +import com.lambda.interaction.handler.handlers.findContainer +import com.lambda.interaction.handler.handlers.findContainers import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.EnchantmentUtils.getEnchantment import com.lambda.util.item.ItemUtils.armorSlot import net.minecraft.component.DataComponentTypes @@ -118,18 +122,23 @@ object AutoArmor : Module( fun SafeContext.tick() { tickedThisTick = true - val armorSlots = ArmorContainer.slots - - val swappable = HotbarAndInventoryContainer.slots - .filter { it.stack.isEquipable && (!ignoreBinding || it.stack.getEnchantment(Enchantments.BINDING_CURSE) <= 0) } + val armorSlots = findContainer(containerSelection = ArmorContainer.select())?.slots ?: return + + val swappable = findContainers(containerSelection = ContainerSelection.HOTBAR_AND_INVENTORY) + .flatMap { it.slots } + .filter { + it.stack.isEquipable(armorSlots) && + (!ignoreBinding || it.stack.getEnchantment(Enchantments.BINDING_CURSE) <= 0) + } .sortedWith(SORTER) .distinctBy { it.stack.armorSlot } val swaps = mutableListOf>() armorSlots.forEach { equipped -> - val new = swappable.find { new -> - equipped.canInsert(new.stack) && SORTER.compare(equipped, new) > 0 - } ?: return@forEach + val new = + swappable.find { new -> + equipped.canInsert(new.stack) && SORTER.compare(equipped, new) > 0 + } ?: return@forEach swaps.add(Pair(new, equipped)) } @@ -144,8 +153,7 @@ object AutoArmor : Module( } context(_: SafeContext) - private val ItemStack.isEquipable - get() = ArmorContainer.slots.any { it.canInsert(this) } + private fun ItemStack.isEquipable(armorSlots: List) = armorSlots.any { it.canInsert(this) } private enum class Protection(val enchant: RegistryKey) { Protection(Enchantments.PROTECTION), diff --git a/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt b/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt index 40ff8980a..32c21c1db 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt @@ -25,10 +25,9 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.container.selection.select import com.lambda.interaction.handler.handlers.FriendHandler -import com.lambda.interaction.inventory.container.select import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.combat.CombatUtils.hasDeadlyCrystal import com.lambda.util.combat.DamageUtils.isFallDeadly import com.lambda.util.extension.fullHealth diff --git a/src/main/kotlin/com/lambda/module/modules/combat/Criticals.kt b/src/main/kotlin/com/lambda/module/modules/combat/Criticals.kt index bc7822c21..1755d1335 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/Criticals.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/Criticals.kt @@ -23,7 +23,7 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.manager.managers.rotating.Rotation import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotationTo import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.PacketUtils.sendPacket import com.lambda.util.extension.rotation import com.lambda.util.math.component1 diff --git a/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt b/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt index 09a6e4a44..39ccad79d 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt @@ -18,7 +18,7 @@ package com.lambda.module.modules.combat import com.lambda.config.Tab -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.blocks.TargetingSettings import com.lambda.config.hide import com.lambda.config.hideAllExcept @@ -28,14 +28,17 @@ import com.lambda.event.events.EntityEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.container.containers.HotbarContainer +import com.lambda.interaction.container.containers.InventoryContainer import com.lambda.interaction.container.containers.OffHandContainer -import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.selectStack +import com.lambda.interaction.container.selection.select +import com.lambda.interaction.handler.handlers.findContainer +import com.lambda.interaction.handler.handlers.move import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotationTo import com.lambda.interaction.manager.managers.rotating.RotationManager import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafe import com.lambda.threading.runSafeAutomated import com.lambda.threading.runSafeGameScheduled @@ -49,7 +52,7 @@ import com.lambda.util.extension.fullHealth import com.lambda.util.math.MathUtils.ceilToInt import com.lambda.util.math.MathUtils.roundToStep import com.lambda.util.math.distSq -import com.lambda.util.math.flooredBlockPos +import com.lambda.util.math.blockPos import com.lambda.util.math.getHitVec import com.lambda.util.math.minus import com.lambda.util.math.plus @@ -480,21 +483,33 @@ object CrystalAura : Module( if (rotate && !rotationRequest { rotation(placeRotation) }.submit().done) return@runSafe - val selection = selectStack { isItem(Items.END_CRYSTAL) } + val selection = Items.END_CRYSTAL.select() + if ((swapHand == Hand.MAIN_HAND && player.mainHandStack.item != selection.item) || (swapHand == Hand.OFF_HAND && player.offHandStack.item != selection.item) ) runSafeAutomated { if (!swap) return@runSafe - var crystalSlot = HotbarContainer.stacks.indexOfFirst { selection.matches(it) } - if (crystalSlot < 0) { - val swapTo = when (swapHand) { - Hand.MAIN_HAND -> HotbarContainer - Hand.OFF_HAND -> OffHandContainer - } - if (!selection.move(swapTo)) return@runSafe - crystalSlot = HotbarContainer.stacks.indexOfFirst { selection.matches(it) } + + val toContainer = + when (swapHand) { + Hand.MAIN_HAND -> HotbarContainer + Hand.OFF_HAND -> OffHandContainer + }.select() + + val crystalSlot = findContainer(selection, toContainer) + ?.findSlot(selection) + + if (crystalSlot == null) { + if (!selection.move(InventoryContainer.select(), toContainer)) return@runSafe + } + + if (swapHand == Hand.MAIN_HAND) { + val crystalSlot = selection + .bestMatch(HotbarContainer.slots) + ?.index + ?: return@runSafe + if (crystalSlot < 0 || !hotbarRequest(crystalSlot).submit().done) return@runSafe } - if (!hotbarRequest(crystalSlot).submit().done) return@runSafe } placeTimer.runSafeIfPassed(placeDelay.milliseconds) { @@ -531,7 +546,7 @@ object CrystalAura : Module( } private val EndCrystalEntity.baseBlockPos get() = - (pos - Vec3d(0.0, 0.5, 0.0)).flooredBlockPos + (pos - Vec3d(0.0, 0.5, 0.0)).blockPos private val BlockPos.crystalPosition get() = this.getHitVec(Direction.UP) diff --git a/src/main/kotlin/com/lambda/module/modules/combat/FakePlayer.kt b/src/main/kotlin/com/lambda/module/modules/combat/FakePlayer.kt index 266fad905..318fad2fb 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/FakePlayer.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/FakePlayer.kt @@ -24,7 +24,7 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.event.listener.SafeListener.Companion.listenConcurrently import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.network.mojang.getProfile import com.lambda.threading.onShutdown import com.lambda.util.Timer diff --git a/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt b/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt index 8bee87050..2ae087f05 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt @@ -18,7 +18,7 @@ package com.lambda.module.modules.combat import com.lambda.config.Tab -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.blocks.TargetingSettings import com.lambda.config.editSetting import com.lambda.config.hide @@ -33,7 +33,7 @@ import com.lambda.interaction.container.selection.StackSelectionBuilder.Companio import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafeAutomated import com.lambda.util.NamedEnum import com.lambda.util.item.ItemStackUtils.attackDamage diff --git a/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt b/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt index 761c0bf67..3b68b1217 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt @@ -17,23 +17,26 @@ package com.lambda.module.modules.combat -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.editTypedSettings import com.lambda.config.hideBlock import com.lambda.config.withEdits import com.lambda.context.SafeContext import com.lambda.interaction.construction.blueprint.TickingBlueprint.Companion.tickingBlueprint import com.lambda.interaction.construction.verify.TargetState +import com.lambda.interaction.container.selection.ContainerSelection +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.selectStack import com.lambda.interaction.handler.handlers.FriendHandler.isFriend +import com.lambda.interaction.handler.handlers.findContainer import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.task.Task import com.lambda.task.start import com.lambda.task.tasks.build import com.lambda.util.BlockUtils.blockState import com.lambda.util.extension.shrinkByEpsilon import com.lambda.util.item.ItemUtils.block -import com.lambda.util.math.flooredBlockPos +import com.lambda.util.math.blockPos import com.lambda.util.world.entitySearch import net.minecraft.block.Blocks import net.minecraft.client.network.OtherClientPlayerEntity @@ -68,14 +71,26 @@ object PlayerTrap : Module( onEnable { task = tickingBlueprint { - val block = HotbarAndInventoryContainer.stacks.firstOrNull { - it.item is BlockItem && blocks.contains(it.item.block) - }?.item?.block ?: return@tickingBlueprint emptyMap() - val targetPlayer = if (self) player - else entitySearch( - buildConfig.blockReach, - player.eyePos.flooredBlockPos - ).firstOrNull { friends || !isFriend(it.gameProfile) } ?: return@tickingBlueprint emptyMap() + val selection = + selectStack { + custom { stack, _ -> + stack.item is BlockItem && blocks.contains(stack.item.block) + } + } + + val block = findContainer(selection, ContainerSelection.HOTBAR_AND_INVENTORY) + ?.findStack(selection) + ?.item?.block + ?: return@tickingBlueprint emptyMap() + + val targetPlayer = + if (self) player + else entitySearch( + buildConfig.blockReach, + player.eyePos.blockPos + ).firstOrNull { friends || !isFriend(it.gameProfile) } + ?: return@tickingBlueprint emptyMap() + getTrapPositions(targetPlayer).associateWith { TargetState.Block(block) } }.build(finishOnDone = false) .start() @@ -84,8 +99,8 @@ object PlayerTrap : Module( } fun SafeContext.getTrapPositions(player: PlayerEntity): Set { - val min = player.boundingBox.shrinkByEpsilon().minPos.flooredBlockPos.add(-1, -1, -1) - val max = player.boundingBox.shrinkByEpsilon().maxPos.flooredBlockPos.add(1, 1, 1) + val min = player.boundingBox.shrinkByEpsilon().minPos.blockPos.add(-1, -1, -1) + val max = player.boundingBox.shrinkByEpsilon().maxPos.blockPos.add(1, 1, 1) return buildSet { (min.x + 1.. diff --git a/src/main/kotlin/com/lambda/module/modules/combat/Surround.kt b/src/main/kotlin/com/lambda/module/modules/combat/Surround.kt index fe24c73f8..552cad24c 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/Surround.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/Surround.kt @@ -17,15 +17,18 @@ package com.lambda.module.modules.combat -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.editTypedSettings import com.lambda.config.hideBlock import com.lambda.config.withEdits import com.lambda.interaction.construction.blueprint.TickingBlueprint.Companion.tickingBlueprint import com.lambda.interaction.construction.verify.TargetState +import com.lambda.interaction.container.selection.ContainerSelection +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.selectStack +import com.lambda.interaction.handler.handlers.findContainer import com.lambda.module.Module import com.lambda.module.modules.combat.PlayerTrap.getTrapPositions -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.task.Task import com.lambda.task.start import com.lambda.task.tasks.build @@ -58,9 +61,18 @@ object Surround : Module( onEnable { task = tickingBlueprint { - val block = HotbarAndInventoryContainer.stacks.firstOrNull { - it.item is BlockItem && blocks.contains(it.item.block) - }?.item?.block ?: return@tickingBlueprint emptyMap() + val selection = + selectStack { + custom { stack, _ -> + stack.item is BlockItem && blocks.contains(stack.item.block) + } + } + + val block = findContainer(selection, ContainerSelection.HOTBAR_AND_INVENTORY) + ?.findStack(selection) + ?.item?.block + ?: return@tickingBlueprint emptyMap() + getTrapPositions(player) .filter { it.y <= player.blockPos.y } .associateWith { TargetState.Block(block) } diff --git a/src/main/kotlin/com/lambda/module/modules/combat/autodisconnect/AutoDisconnect.kt b/src/main/kotlin/com/lambda/module/modules/combat/autodisconnect/AutoDisconnect.kt index a72292411..0d968759d 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/autodisconnect/AutoDisconnect.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/autodisconnect/AutoDisconnect.kt @@ -28,7 +28,7 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.gui.components.ClickGuiLayout import com.lambda.interaction.handler.handlers.FriendHandler import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.sound.SoundHandler.playSound import com.lambda.util.CommunicationUtils import com.lambda.util.CommunicationUtils.info diff --git a/src/main/kotlin/com/lambda/module/modules/debug/BaritoneTest.kt b/src/main/kotlin/com/lambda/module/modules/debug/BaritoneTest.kt index 759fc8253..82e684486 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/BaritoneTest.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/BaritoneTest.kt @@ -22,7 +22,7 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.handler.handlers.BaritoneHandler import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag @Suppress("unused") object BaritoneTest : Module( diff --git a/src/main/kotlin/com/lambda/module/modules/debug/BlockTest.kt b/src/main/kotlin/com/lambda/module/modules/debug/BlockTest.kt index 8dd42d214..71db39448 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/BlockTest.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/BlockTest.kt @@ -19,7 +19,7 @@ package com.lambda.module.modules.debug import com.lambda.graphics.mc.renderer.TickedRenderer.Companion.tickedRenderer import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafe import com.lambda.util.world.blockSearch import net.minecraft.block.Blocks diff --git a/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt b/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt index 5bcea7fb9..364e2bc90 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt @@ -21,7 +21,7 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.container.selection.select import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.task.start import com.lambda.task.tasks.acquireStack import net.minecraft.item.Items diff --git a/src/main/kotlin/com/lambda/module/modules/debug/DebugRendererModule.kt b/src/main/kotlin/com/lambda/module/modules/debug/DebugRendererModule.kt index 3e417f071..e4abf5b44 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/DebugRendererModule.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/DebugRendererModule.kt @@ -18,7 +18,7 @@ package com.lambda.module.modules.debug import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag @Suppress("unused") object DebugRendererModule: Module( diff --git a/src/main/kotlin/com/lambda/module/modules/debug/FallTest.kt b/src/main/kotlin/com/lambda/module/modules/debug/FallTest.kt index d397b76a5..9ec65d9e8 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/FallTest.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/FallTest.kt @@ -20,7 +20,7 @@ package com.lambda.module.modules.debug import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.CommunicationUtils.info import com.lambda.util.combat.DamageUtils.fallDamage import com.lambda.util.combat.DamageUtils.isFallDeadly diff --git a/src/main/kotlin/com/lambda/module/modules/debug/InventoryDebug.kt b/src/main/kotlin/com/lambda/module/modules/debug/InventoryDebug.kt index 47ec92134..85af88c45 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/InventoryDebug.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/InventoryDebug.kt @@ -22,7 +22,7 @@ import com.lambda.event.events.InventoryEvent import com.lambda.event.events.PacketEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.CommunicationUtils.info import com.lambda.util.DynamicReflectionSerializer.dynamicString import net.minecraft.network.packet.c2s.play.ClickSlotC2SPacket diff --git a/src/main/kotlin/com/lambda/module/modules/debug/PropertyPrinter.kt b/src/main/kotlin/com/lambda/module/modules/debug/PropertyPrinter.kt index 6b5a18da1..77a3dabd2 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/PropertyPrinter.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/PropertyPrinter.kt @@ -18,7 +18,7 @@ package com.lambda.module.modules.debug import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.FolderRegistry import com.lambda.util.extension.resolveFile import net.minecraft.block.Block diff --git a/src/main/kotlin/com/lambda/module/modules/debug/RenderTest.kt b/src/main/kotlin/com/lambda/module/modules/debug/RenderTest.kt index 1a0dc8dc7..5d5b177f5 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/RenderTest.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/RenderTest.kt @@ -21,7 +21,7 @@ import com.lambda.graphics.mc.renderer.ImmediateRenderer.Companion.immediateRend import com.lambda.graphics.mc.renderer.TickedRenderer.Companion.tickedRenderer import com.lambda.graphics.util.DynamicAABB.Companion.dynamicBox import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafe import com.lambda.util.extension.tickDelta import com.lambda.util.math.setAlpha diff --git a/src/main/kotlin/com/lambda/module/modules/debug/RendererTestModule.kt b/src/main/kotlin/com/lambda/module/modules/debug/RendererTestModule.kt index 0e8692900..23b48c4a1 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/RendererTestModule.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/RendererTestModule.kt @@ -32,7 +32,7 @@ import com.lambda.graphics.mc.renderer.ImmediateRenderer.Companion.immediateRend import com.lambda.graphics.mc.renderer.TickedRenderer.Companion.tickedRenderer import com.lambda.graphics.outline.OutlineStyle import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafe import com.lambda.util.extension.prevPos import com.lambda.util.extension.tickDelta diff --git a/src/main/kotlin/com/lambda/module/modules/debug/RotationTest.kt b/src/main/kotlin/com/lambda/module/modules/debug/RotationTest.kt index 1648c632e..b5d63290b 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/RotationTest.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/RotationTest.kt @@ -21,7 +21,7 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.player.RotationUtils.lookAt import net.minecraft.util.hit.HitResult diff --git a/src/main/kotlin/com/lambda/module/modules/debug/SettingsTestModule.kt b/src/main/kotlin/com/lambda/module/modules/debug/SettingsTestModule.kt index 2facef200..f3430fc74 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/SettingsTestModule.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/SettingsTestModule.kt @@ -26,7 +26,7 @@ import com.lambda.config.blocks.ScreenTextSettings import com.lambda.config.blocks.WorldLineSettings import com.lambda.config.blocks.WorldTextSettings import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag @Suppress("unused") object SettingsTestModule : Module( diff --git a/src/main/kotlin/com/lambda/module/modules/debug/SilentSwap.kt b/src/main/kotlin/com/lambda/module/modules/debug/SilentSwap.kt index faa464526..9c3e8df60 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/SilentSwap.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/SilentSwap.kt @@ -17,14 +17,14 @@ package com.lambda.module.modules.debug -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.hideAllExcept import com.lambda.config.withEdits import com.lambda.event.events.PlayerEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.CommunicationUtils.info object SilentSwap : Module( diff --git a/src/main/kotlin/com/lambda/module/modules/debug/StateInfo.kt b/src/main/kotlin/com/lambda/module/modules/debug/StateInfo.kt index 1cf5a0a47..1789ca0ca 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/StateInfo.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/StateInfo.kt @@ -19,7 +19,7 @@ package com.lambda.module.modules.debug import com.lambda.config.settings.complex.KeybindSetting.Companion.onPress import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.BlockUtils.blockState import com.lambda.util.CommunicationUtils.info import com.lambda.util.KeyCode diff --git a/src/main/kotlin/com/lambda/module/modules/debug/TimerTest.kt b/src/main/kotlin/com/lambda/module/modules/debug/TimerTest.kt index 25073151a..10b6abe2c 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/TimerTest.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/TimerTest.kt @@ -21,7 +21,7 @@ import com.lambda.event.events.ClientEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.event.listener.SafeListener.Companion.listenConcurrently import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.CommunicationUtils.info @Suppress("unused") diff --git a/src/main/kotlin/com/lambda/module/modules/movement/AutoSpiral.kt b/src/main/kotlin/com/lambda/module/modules/movement/AutoSpiral.kt index a026b7fa7..15e4f463c 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/AutoSpiral.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/AutoSpiral.kt @@ -24,7 +24,7 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.handler.handlers.BaritoneHandler import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.BlockPosIterators import com.lambda.util.extension.isNether import com.lambda.util.player.RotationUtils.lookAt diff --git a/src/main/kotlin/com/lambda/module/modules/movement/AutoWalk.kt b/src/main/kotlin/com/lambda/module/modules/movement/AutoWalk.kt index d0dc18219..9218fe002 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/AutoWalk.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/AutoWalk.kt @@ -25,7 +25,7 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.manager.managers.breaking.BreakManager import com.lambda.interaction.manager.managers.interacting.InteractManager import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.math.MathUtils.toDouble import com.lambda.util.player.MovementUtils.roundedForward import com.lambda.util.player.MovementUtils.roundedStrafing diff --git a/src/main/kotlin/com/lambda/module/modules/movement/BackTrack.kt b/src/main/kotlin/com/lambda/module/modules/movement/BackTrack.kt index 281766a82..95abb3962 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/BackTrack.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/BackTrack.kt @@ -28,7 +28,7 @@ import com.lambda.graphics.util.DynamicAABB import com.lambda.gui.components.ClickGuiLayout import com.lambda.module.Module import com.lambda.module.modules.combat.KillAura -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.ClientPacket import com.lambda.util.PacketUtils.handlePacketSilently import com.lambda.util.PacketUtils.sendPacketSilently diff --git a/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt b/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt index 2298bf1f2..2d20319c9 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt @@ -19,7 +19,7 @@ package com.lambda.module.modules.movement import com.lambda.Lambda import com.lambda.Lambda.mc -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.editSetting import com.lambda.config.hideAllExcept import com.lambda.config.settings.complex.Bind @@ -36,7 +36,7 @@ import com.lambda.interaction.handler.handlers.GlideHandler import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafe import com.lambda.util.CommunicationUtils.warn import com.lambda.util.KeyCode diff --git a/src/main/kotlin/com/lambda/module/modules/movement/Blink.kt b/src/main/kotlin/com/lambda/module/modules/movement/Blink.kt index 444006b51..fe555adea 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/Blink.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/Blink.kt @@ -27,7 +27,7 @@ import com.lambda.graphics.util.DynamicAABB import com.lambda.gui.components.ClickGuiLayout import com.lambda.module.Module import com.lambda.module.modules.combat.KillAura -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafe import com.lambda.util.PacketUtils.handlePacketSilently import com.lambda.util.PacketUtils.sendPacketSilently diff --git a/src/main/kotlin/com/lambda/module/modules/movement/ElytraAltitudeControl.kt b/src/main/kotlin/com/lambda/module/modules/movement/ElytraAltitudeControl.kt index 1dab22558..d083c4730 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/ElytraAltitudeControl.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/ElytraAltitudeControl.kt @@ -18,7 +18,7 @@ package com.lambda.module.modules.movement import com.lambda.config.Tab -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.hideAllExcept import com.lambda.config.withEdits import com.lambda.event.events.TickEvent @@ -26,7 +26,7 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module import com.lambda.module.modules.movement.BetterFirework.startFirework -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafe import com.lambda.util.CommunicationUtils.info import com.lambda.util.SpeedUnit diff --git a/src/main/kotlin/com/lambda/module/modules/movement/EntityControl.kt b/src/main/kotlin/com/lambda/module/modules/movement/EntityControl.kt index 03bff2f1d..240b35840 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/EntityControl.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/EntityControl.kt @@ -21,7 +21,7 @@ import com.lambda.event.events.PacketEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.world.fastEntitySearch import net.minecraft.entity.passive.AbstractHorseEntity import net.minecraft.network.packet.c2s.play.PlayerInteractEntityC2SPacket diff --git a/src/main/kotlin/com/lambda/module/modules/movement/Jesus.kt b/src/main/kotlin/com/lambda/module/modules/movement/Jesus.kt index 7f024e4bb..059ce26e3 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/Jesus.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/Jesus.kt @@ -24,7 +24,7 @@ import com.lambda.event.events.TickEvent import com.lambda.event.events.WorldEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.NamedEnum import com.lambda.util.extension.isElytraFlying import com.lambda.util.math.MathUtils.toInt diff --git a/src/main/kotlin/com/lambda/module/modules/movement/NoFall.kt b/src/main/kotlin/com/lambda/module/modules/movement/NoFall.kt index 5275eb08a..82b2f4bea 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/NoFall.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/NoFall.kt @@ -20,7 +20,7 @@ package com.lambda.module.modules.movement import com.lambda.event.events.MovementEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.PacketUtils.sendPacket import com.lambda.util.math.component1 import com.lambda.util.math.component2 diff --git a/src/main/kotlin/com/lambda/module/modules/movement/NoJumpCooldown.kt b/src/main/kotlin/com/lambda/module/modules/movement/NoJumpCooldown.kt index 1746e1e95..4e0ddef0d 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/NoJumpCooldown.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/NoJumpCooldown.kt @@ -18,7 +18,7 @@ package com.lambda.module.modules.movement import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag object NoJumpCooldown : Module( name = "NoJumpCooldown", diff --git a/src/main/kotlin/com/lambda/module/modules/movement/SafeWalk.kt b/src/main/kotlin/com/lambda/module/modules/movement/SafeWalk.kt index e34f6db0f..87dad538c 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/SafeWalk.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/SafeWalk.kt @@ -20,7 +20,7 @@ package com.lambda.module.modules.movement import com.lambda.event.events.MovementEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.player.MovementUtils.motionX import com.lambda.util.player.MovementUtils.motionZ import com.lambda.util.player.MovementUtils.sneaking diff --git a/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt b/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt index 821757d4e..cd173de2b 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt @@ -17,8 +17,8 @@ package com.lambda.module.modules.movement -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig -import com.lambda.config.entries.Setting.Companion.onValueChange +import com.lambda.config.automation.setDefaultAutomationConfig +import com.lambda.config.entries.onValueChange import com.lambda.config.hideAllExcept import com.lambda.config.withEdits import com.lambda.context.SafeContext @@ -28,7 +28,7 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.NamedEnum import com.lambda.util.extension.contains import com.lambda.util.extension.isElytraFlying diff --git a/src/main/kotlin/com/lambda/module/modules/movement/Sprint.kt b/src/main/kotlin/com/lambda/module/modules/movement/Sprint.kt index d31ac27cc..7dfe55128 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/Sprint.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/Sprint.kt @@ -18,7 +18,7 @@ package com.lambda.module.modules.movement import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag object Sprint : Module( name = "Sprint", diff --git a/src/main/kotlin/com/lambda/module/modules/movement/TargetStrafe.kt b/src/main/kotlin/com/lambda/module/modules/movement/TargetStrafe.kt index 71b8159dd..5c92c6a66 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/TargetStrafe.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/TargetStrafe.kt @@ -29,7 +29,7 @@ import com.lambda.graphics.mc.renderer.ImmediateRenderer.Companion.immediateRend import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotationTo import com.lambda.module.Module import com.lambda.module.modules.combat.KillAura -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import net.minecraft.entity.effect.StatusEffects import net.minecraft.util.math.Vec3d import java.awt.Color diff --git a/src/main/kotlin/com/lambda/module/modules/movement/TickShift.kt b/src/main/kotlin/com/lambda/module/modules/movement/TickShift.kt index 98cf8c9fa..8cf57f44b 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/TickShift.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/TickShift.kt @@ -24,7 +24,7 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module import com.lambda.module.modules.combat.KillAura -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runConcurrent import com.lambda.util.CommunicationUtils.info import com.lambda.util.PacketUtils.handlePacketSilently diff --git a/src/main/kotlin/com/lambda/module/modules/movement/Timer.kt b/src/main/kotlin/com/lambda/module/modules/movement/Timer.kt index 359f78bde..2765c5fbe 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/Timer.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/Timer.kt @@ -20,7 +20,7 @@ package com.lambda.module.modules.movement import com.lambda.event.events.ClientEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag @Suppress("unused") object Timer : Module( diff --git a/src/main/kotlin/com/lambda/module/modules/movement/Velocity.kt b/src/main/kotlin/com/lambda/module/modules/movement/Velocity.kt index cd2470c54..76e27baa0 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/Velocity.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/Velocity.kt @@ -20,7 +20,7 @@ package com.lambda.module.modules.movement import com.lambda.event.events.PacketEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import net.minecraft.network.packet.s2c.play.EntityVelocityUpdateS2CPacket object Velocity : Module( diff --git a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFly.kt b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFly.kt index a55f96e3e..ea2740859 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFly.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFly.kt @@ -18,9 +18,9 @@ package com.lambda.module.modules.movement.elytrafly import com.lambda.config.Tab -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.editSetting -import com.lambda.config.entries.Setting.Companion.onValueChange +import com.lambda.config.entries.onValueChange import com.lambda.config.forEachSetting import com.lambda.config.hideAllExcept import com.lambda.config.withEdits @@ -34,7 +34,7 @@ import com.lambda.module.Module import com.lambda.module.modules.movement.elytrafly.modes.BounceElytraFly import com.lambda.module.modules.movement.elytrafly.modes.GeneralElytraFly import com.lambda.module.modules.movement.elytrafly.modes.GrimControlElytraFly -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafe import com.lambda.util.NamedEnum import com.lambda.util.Timer diff --git a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt index c2f816622..f21fef99c 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt @@ -25,7 +25,9 @@ import com.lambda.event.events.PacketEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.container.containers.ArmorContainer +import com.lambda.interaction.container.selection.ContainerSelection import com.lambda.interaction.handler.handlers.GlideHandler.ELYTRA_SELECTION +import com.lambda.interaction.handler.handlers.findContainer import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest @@ -85,11 +87,12 @@ abstract class ElytraFlyMode( } } - val elytraSlot = findElytra() ?: run { - logError("Fake Fly requires an elytra in your inventory, preferably in your hotbar.") - ElytraFly.disable() - return false - } + val elytraSlot = findElytra() + ?: run { + logError("Fake Fly requires an elytra in your inventory, preferably in your hotbar") + ElytraFly.disable() + return false + } val elytraInHotbar = elytraSlot.index in 0..8 val chestSlot = ArmorContainer.slots.getOrNull(1) ?: return false @@ -111,16 +114,21 @@ abstract class ElytraFlyMode( } } - val inventoryRequest = inventoryRequest { - swapChest() - action { startFly() } - swapChest() - }.submit(false) + val inventoryRequest = + inventoryRequest { + swapChest() + action { startFly() } + swapChest() + }.submit(false) return inventoryRequest.done } - fun findElytra(): Slot? = ELYTRA_SELECTION.filter(HotbarAndInventoryContainer.slots).minByOrNull { it.index } + fun findElytra(): Slot? = + findContainer( + ELYTRA_SELECTION, + ContainerSelection.HOTBAR_AND_INVENTORY + )?.findSlot(ELYTRA_SELECTION) protected fun SafeContext.startFly() { player.setFlag(Entity.GLIDING_FLAG_INDEX, true) diff --git a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ObstaclePassingMode.kt b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ObstaclePassingMode.kt index ab6867227..152671136 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ObstaclePassingMode.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ObstaclePassingMode.kt @@ -30,7 +30,7 @@ import com.lambda.util.CommunicationUtils.logError import com.lambda.util.SpeedUnit import com.lambda.util.math.dist import com.lambda.util.math.distSq -import com.lambda.util.math.flooredBlockPos +import com.lambda.util.math.blockPos import com.lambda.util.math.isLoaded import com.lambda.util.world.raycast.InteractionMask import com.lambda.util.world.raycast.RayCastUtils.blockResult @@ -90,7 +90,7 @@ abstract class ObstaclePassingMode( passingToPos?.let { passingTo -> if (passingTo distSq startPos < playerPos distSq startPos) { - val atClosestPointBlockPos = closestLinePoint.flooredBlockPos == player.blockPos + val atClosestPointBlockPos = closestLinePoint.blockPos == player.blockPos if (!atClosestPointBlockPos) pathToValidPoint(closestLinePoint, snappedDir, false) else { BaritoneHandler.cancel() @@ -172,7 +172,7 @@ abstract class ObstaclePassingMode( private fun passTo(pos: Vec3d) { passingToPos = pos - BaritoneHandler.setGoalAndPath(GoalGetToBlock(pos.flooredBlockPos)) + BaritoneHandler.setGoalAndPath(GoalGetToBlock(pos.blockPos)) } protected fun Vec3d.findClosestPointOnLine(snappedDirection: Vec3d): Vec3d { @@ -185,7 +185,7 @@ abstract class ObstaclePassingMode( private fun Vec3d.isObstructed(direction: Vec3d) = if (!isLoaded) false else { - flooredBlockPos.down().let { downPos -> + blockPos.down().let { downPos -> !safeContext.blockState(downPos).isSolidBlock(safeContext.world, downPos) } || if (add(0.0, passerConfig.minObstacleHeight, 0.0).rayCastObstructed(direction)) true diff --git a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/BounceElytraFly.kt b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/BounceElytraFly.kt index f47f1327f..942f1442c 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/BounceElytraFly.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/BounceElytraFly.kt @@ -19,7 +19,7 @@ package com.lambda.module.modules.movement.elytrafly.modes import com.lambda.config.Config import com.lambda.config.Group -import com.lambda.config.entries.Setting.Companion.onValueChange +import com.lambda.config.entries.onValueChange import com.lambda.context.SafeContext import com.lambda.event.events.MovementEvent import com.lambda.event.events.PacketEvent diff --git a/src/main/kotlin/com/lambda/module/modules/network/PacketDelay.kt b/src/main/kotlin/com/lambda/module/modules/network/PacketDelay.kt index c6774f7a2..c150e5484 100644 --- a/src/main/kotlin/com/lambda/module/modules/network/PacketDelay.kt +++ b/src/main/kotlin/com/lambda/module/modules/network/PacketDelay.kt @@ -22,7 +22,7 @@ import com.lambda.event.events.PacketEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.graphics.mc.renderer.TickedRenderer.Companion.tickedRenderer import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runConcurrent import com.lambda.threading.runGameScheduled import com.lambda.threading.runSafe diff --git a/src/main/kotlin/com/lambda/module/modules/network/PacketLimiter.kt b/src/main/kotlin/com/lambda/module/modules/network/PacketLimiter.kt index f1b0aa7c2..a62fd1f71 100644 --- a/src/main/kotlin/com/lambda/module/modules/network/PacketLimiter.kt +++ b/src/main/kotlin/com/lambda/module/modules/network/PacketLimiter.kt @@ -17,11 +17,11 @@ package com.lambda.module.modules.network -import com.lambda.config.entries.Setting.Companion.onValueChange +import com.lambda.config.entries.onValueChange import com.lambda.event.events.PacketEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.CommunicationUtils.info import com.lambda.util.ReflectionUtils.className import com.lambda.util.collections.LimitedDecayQueue diff --git a/src/main/kotlin/com/lambda/module/modules/network/PacketLogger.kt b/src/main/kotlin/com/lambda/module/modules/network/PacketLogger.kt index efcf05299..3660e73d5 100644 --- a/src/main/kotlin/com/lambda/module/modules/network/PacketLogger.kt +++ b/src/main/kotlin/com/lambda/module/modules/network/PacketLogger.kt @@ -24,7 +24,7 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.UnsafeListener.Companion.listenConcurrentlyUnsafe import com.lambda.event.listener.UnsafeListener.Companion.listenUnsafe import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runIO import com.lambda.util.CommunicationUtils import com.lambda.util.CommunicationUtils.info diff --git a/src/main/kotlin/com/lambda/module/modules/network/Rubberband.kt b/src/main/kotlin/com/lambda/module/modules/network/Rubberband.kt index 45fb7f1b7..148fb311c 100644 --- a/src/main/kotlin/com/lambda/module/modules/network/Rubberband.kt +++ b/src/main/kotlin/com/lambda/module/modules/network/Rubberband.kt @@ -21,7 +21,7 @@ import com.lambda.event.events.PacketEvent import com.lambda.event.events.PlayerPacketEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.CommunicationUtils.warn import com.lambda.util.collections.LimitedOrderedSet import com.lambda.util.math.dist diff --git a/src/main/kotlin/com/lambda/module/modules/network/ServerSpoof.kt b/src/main/kotlin/com/lambda/module/modules/network/ServerSpoof.kt index 39dbcfdd1..d034bca9c 100644 --- a/src/main/kotlin/com/lambda/module/modules/network/ServerSpoof.kt +++ b/src/main/kotlin/com/lambda/module/modules/network/ServerSpoof.kt @@ -20,7 +20,7 @@ package com.lambda.module.modules.network import com.lambda.event.events.PacketEvent import com.lambda.event.listener.UnsafeListener.Companion.listenUnsafe import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.CommunicationUtils.info import com.lambda.util.text.ClickEvents import com.lambda.util.text.buildText diff --git a/src/main/kotlin/com/lambda/module/modules/player/AntiAFK.kt b/src/main/kotlin/com/lambda/module/modules/player/AntiAFK.kt index 25f485e17..4091c92b2 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/AntiAFK.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/AntiAFK.kt @@ -20,7 +20,7 @@ package com.lambda.module.modules.player import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import net.minecraft.util.Hand @Suppress("unused") diff --git a/src/main/kotlin/com/lambda/module/modules/player/AntiAim.kt b/src/main/kotlin/com/lambda/module/modules/player/AntiAim.kt index 645239f00..5fd9b717e 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/AntiAim.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/AntiAim.kt @@ -17,14 +17,14 @@ package com.lambda.module.modules.player -import com.lambda.config.entries.Setting.Companion.onValueChange +import com.lambda.config.entries.onValueChange import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotationTo import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.math.distSq import net.minecraft.entity.Entity import net.minecraft.entity.player.PlayerEntity diff --git a/src/main/kotlin/com/lambda/module/modules/player/AutoEat.kt b/src/main/kotlin/com/lambda/module/modules/player/AutoEat.kt index 6d2997dca..1bb5e18e2 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/AutoEat.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/AutoEat.kt @@ -24,7 +24,7 @@ import com.lambda.config.withEdits import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.task.Task import com.lambda.task.start import com.lambda.task.tasks.eat diff --git a/src/main/kotlin/com/lambda/module/modules/player/AutoElytraSwap.kt b/src/main/kotlin/com/lambda/module/modules/player/AutoElytraSwap.kt index 2bde07f67..aeead9b02 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/AutoElytraSwap.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/AutoElytraSwap.kt @@ -26,7 +26,7 @@ import com.lambda.interaction.handler.handlers.GlideHandler.swapped import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module import com.lambda.module.modules.combat.AutoArmor -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.CommunicationUtils.warn import com.lambda.util.player.PlayerUtils.canGlideWithChestPiece import net.minecraft.screen.slot.Slot diff --git a/src/main/kotlin/com/lambda/module/modules/player/ClickFriend.kt b/src/main/kotlin/com/lambda/module/modules/player/ClickFriend.kt index 402260cd5..a1adaea38 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/ClickFriend.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/ClickFriend.kt @@ -25,7 +25,7 @@ import com.lambda.interaction.handler.handlers.FriendHandler.befriend import com.lambda.interaction.handler.handlers.FriendHandler.isFriend import com.lambda.interaction.handler.handlers.FriendHandler.unfriend import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.CommunicationUtils.info import com.lambda.util.InputUtils.isSatisfied import com.lambda.util.world.raycast.RayCastUtils.entityResult diff --git a/src/main/kotlin/com/lambda/module/modules/player/EndermanLook.kt b/src/main/kotlin/com/lambda/module/modules/player/EndermanLook.kt index 20024f5cb..73722103a 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/EndermanLook.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/EndermanLook.kt @@ -26,7 +26,7 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotationTo import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.math.distSq import net.minecraft.entity.EquipmentSlot import net.minecraft.entity.mob.EndermanEntity diff --git a/src/main/kotlin/com/lambda/module/modules/player/FastBreak.kt b/src/main/kotlin/com/lambda/module/modules/player/FastBreak.kt index b0270222a..20742f245 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/FastBreak.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/FastBreak.kt @@ -29,7 +29,7 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.construction.simulation.context.BuildContext import com.lambda.interaction.manager.managers.breaking.breakRequest import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafeAutomated import java.util.concurrent.ConcurrentLinkedQueue diff --git a/src/main/kotlin/com/lambda/module/modules/player/Interact.kt b/src/main/kotlin/com/lambda/module/modules/player/Interact.kt index 48fec9a24..6ae205a89 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/Interact.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/Interact.kt @@ -18,7 +18,7 @@ package com.lambda.module.modules.player import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag object Interact : Module( name = "Interact", diff --git a/src/main/kotlin/com/lambda/module/modules/player/InventoryMove.kt b/src/main/kotlin/com/lambda/module/modules/player/InventoryMove.kt index b65300f3b..59f5d317c 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/InventoryMove.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/InventoryMove.kt @@ -25,7 +25,7 @@ import com.lambda.gui.LambdaScreen import com.lambda.interaction.manager.managers.rotating.RotationMode import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.InputUtils import com.lambda.util.InputUtils.isKeyPressed import com.lambda.util.math.MathUtils.toFloatSign diff --git a/src/main/kotlin/com/lambda/module/modules/player/InventoryResync.kt b/src/main/kotlin/com/lambda/module/modules/player/InventoryResync.kt index 59d0909c7..3ce04fa89 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/InventoryResync.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/InventoryResync.kt @@ -21,7 +21,7 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.TickTimer @Suppress("unused") diff --git a/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt b/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt index cb82dbc01..ae88a5bfd 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt @@ -28,7 +28,7 @@ import com.lambda.interaction.container.OpenedContainerContext import com.lambda.interaction.container.containers.external.EnderChestContainer import com.lambda.interaction.handler.handlers.ContainerHandler import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.task.Task import com.lambda.task.start import com.lambda.task.tasks.wrappers.then diff --git a/src/main/kotlin/com/lambda/module/modules/player/LineLock.kt b/src/main/kotlin/com/lambda/module/modules/player/LineLock.kt index 5f254bce7..db71f04c9 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/LineLock.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/LineLock.kt @@ -29,7 +29,7 @@ import com.lambda.graphics.mc.renderer.ImmediateRenderer.Companion.immediateRend import com.lambda.interaction.manager.managers.rotating.RotationMode import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafe import net.minecraft.util.math.Vec3d import java.awt.Color diff --git a/src/main/kotlin/com/lambda/module/modules/player/NoForceRotate.kt b/src/main/kotlin/com/lambda/module/modules/player/NoForceRotate.kt index 7ca7798fb..9ef7a1953 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/NoForceRotate.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/NoForceRotate.kt @@ -20,7 +20,7 @@ package com.lambda.module.modules.player import com.lambda.event.events.PlayerEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag @Suppress("unused") object NoForceRotate : Module( diff --git a/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt b/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt index 779ecf750..c56cdcd2b 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt @@ -39,7 +39,7 @@ import com.lambda.interaction.construction.simulation.sim import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.manager.managers.breaking.BreakRequestBuilder.Companion.breakRequest import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafe import com.lambda.threading.runSafeAutomated import com.lambda.util.BlockUtils.blockState diff --git a/src/main/kotlin/com/lambda/module/modules/player/PortalGui.kt b/src/main/kotlin/com/lambda/module/modules/player/PortalGui.kt index dea4ec2e1..3b7022704 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/PortalGui.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/PortalGui.kt @@ -18,7 +18,7 @@ package com.lambda.module.modules.player import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag object PortalGui : Module( name = "PortalGui", diff --git a/src/main/kotlin/com/lambda/module/modules/player/Reach.kt b/src/main/kotlin/com/lambda/module/modules/player/Reach.kt index 13855a2e6..5a6e38b84 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/Reach.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/Reach.kt @@ -18,7 +18,7 @@ package com.lambda.module.modules.player import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag object Reach : Module( name = "Reach", diff --git a/src/main/kotlin/com/lambda/module/modules/player/Replay.kt b/src/main/kotlin/com/lambda/module/modules/player/Replay.kt index 9ebf59a3b..50bc048f0 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/Replay.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/Replay.kt @@ -40,7 +40,7 @@ import com.lambda.interaction.manager.managers.rotating.RotationMode import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module import com.lambda.module.modules.player.Replay.InputAction.Companion.toAction -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.sound.SoundHandler.playSound import com.lambda.util.CommunicationUtils.info import com.lambda.util.CommunicationUtils.logError diff --git a/src/main/kotlin/com/lambda/module/modules/player/RotationLock.kt b/src/main/kotlin/com/lambda/module/modules/player/RotationLock.kt index fb6148691..b7a5ca5af 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/RotationLock.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/RotationLock.kt @@ -26,7 +26,7 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.manager.managers.rotating.RotationMode import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import kotlin.math.roundToInt @Suppress("unused") diff --git a/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt b/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt index fb411189d..b1e435d49 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt @@ -28,7 +28,7 @@ import com.lambda.interaction.container.containers.HotbarContainer import com.lambda.interaction.container.containers.InventoryContainer import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.item.ItemStackUtils.slotId import net.minecraft.item.ItemStack import net.minecraft.item.Items diff --git a/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt b/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt index 77de50df6..7f3c5ccc7 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt @@ -27,7 +27,7 @@ import com.lambda.interaction.container.containers.HotbarContainer import com.lambda.interaction.container.containers.InventoryContainer import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.EnchantmentUtils.forEachEnchantment import com.lambda.util.EnchantmentUtils.getEnchantment import net.minecraft.item.ItemStack diff --git a/src/main/kotlin/com/lambda/module/modules/render/BlockOutline.kt b/src/main/kotlin/com/lambda/module/modules/render/BlockOutline.kt index 81eae6976..17d7a2dc3 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/BlockOutline.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/BlockOutline.kt @@ -27,7 +27,7 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.graphics.mc.renderer.ImmediateRenderer.Companion.immediateRenderer import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafe import com.lambda.util.BlockUtils.blockState import com.lambda.util.extension.tickDelta diff --git a/src/main/kotlin/com/lambda/module/modules/render/Bobbing.kt b/src/main/kotlin/com/lambda/module/modules/render/Bobbing.kt index d3d9303d5..f255e0c81 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/Bobbing.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/Bobbing.kt @@ -18,7 +18,7 @@ package com.lambda.module.modules.render import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag object Bobbing : Module( name = "Bobbing", diff --git a/src/main/kotlin/com/lambda/module/modules/render/CameraTweaks.kt b/src/main/kotlin/com/lambda/module/modules/render/CameraTweaks.kt index 21c383419..0647ea3b1 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/CameraTweaks.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/CameraTweaks.kt @@ -18,7 +18,7 @@ package com.lambda.module.modules.render import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag object CameraTweaks : Module( name = "CameraTweaks", diff --git a/src/main/kotlin/com/lambda/module/modules/render/ContainerPreview.kt b/src/main/kotlin/com/lambda/module/modules/render/ContainerPreview.kt index 66634e981..bd3e6360e 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/ContainerPreview.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/ContainerPreview.kt @@ -23,7 +23,7 @@ import com.lambda.config.entries.Setting.Companion.onValueChange import com.lambda.config.settings.complex.Bind import com.lambda.interaction.container.containers.external.EnderChestContainer import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafe import com.lambda.util.InputUtils.isSatisfied import com.lambda.util.KeyCode diff --git a/src/main/kotlin/com/lambda/module/modules/render/Esp.kt b/src/main/kotlin/com/lambda/module/modules/render/Esp.kt index 1639b59ee..a2cc59eb9 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/Esp.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/Esp.kt @@ -31,7 +31,7 @@ import com.lambda.graphics.mc.RenderBuilder import com.lambda.graphics.mc.renderer.ImmediateRenderer.Companion.immediateRenderer import com.lambda.graphics.util.DynamicAABB.Companion.interpolatedBox import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafe import com.lambda.util.NamedEnum import com.lambda.util.math.setAlpha diff --git a/src/main/kotlin/com/lambda/module/modules/render/ExtraTab.kt b/src/main/kotlin/com/lambda/module/modules/render/ExtraTab.kt index d70c37a5a..fd561bcd2 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/ExtraTab.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/ExtraTab.kt @@ -18,7 +18,7 @@ package com.lambda.module.modules.render import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import java.awt.Color object ExtraTab : Module( diff --git a/src/main/kotlin/com/lambda/module/modules/render/FreeLook.kt b/src/main/kotlin/com/lambda/module/modules/render/FreeLook.kt index 5541edce9..486efbd56 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/FreeLook.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/FreeLook.kt @@ -23,7 +23,7 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.manager.managers.rotating.Rotation import com.lambda.interaction.manager.managers.rotating.RotationManager import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.extension.rotation import net.minecraft.client.option.Perspective diff --git a/src/main/kotlin/com/lambda/module/modules/render/Freecam.kt b/src/main/kotlin/com/lambda/module/modules/render/Freecam.kt index e4881965c..3ec2aae6c 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/Freecam.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/Freecam.kt @@ -35,7 +35,7 @@ import com.lambda.interaction.manager.managers.rotating.Rotation import com.lambda.interaction.manager.managers.rotating.RotationMode import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafe import com.lambda.util.Describable import com.lambda.util.NamedEnum diff --git a/src/main/kotlin/com/lambda/module/modules/render/Fullbright.kt b/src/main/kotlin/com/lambda/module/modules/render/Fullbright.kt index 09e15863c..9d59f795a 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/Fullbright.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/Fullbright.kt @@ -22,7 +22,7 @@ import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listenOnce import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import net.minecraft.entity.effect.StatusEffectInstance import net.minecraft.entity.effect.StatusEffects diff --git a/src/main/kotlin/com/lambda/module/modules/render/LightLevels.kt b/src/main/kotlin/com/lambda/module/modules/render/LightLevels.kt index eb590e40b..011ecaebf 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/LightLevels.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/LightLevels.kt @@ -19,7 +19,6 @@ package com.lambda.module.modules.render import com.lambda.config.Group import com.lambda.config.blocks.WorldLineSettings -import com.lambda.config.entries.Setting.Companion.onValueChange import com.lambda.config.forEachSetting import com.lambda.config.hide import com.lambda.config.withEdits @@ -29,10 +28,10 @@ import com.lambda.graphics.mc.RenderBuilder import com.lambda.graphics.mc.renderer.ChunkedRenderer.Companion.chunkedRenderer import com.lambda.graphics.mc.renderer.TickedRenderer.Companion.tickedRenderer import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafe import com.lambda.util.BlockUtils.blockState -import com.lambda.util.math.flooredBlockPos +import com.lambda.util.math.blockPos import com.lambda.util.math.setAlpha import com.lambda.util.math.vec3d import com.lambda.util.world.toBlockPos @@ -92,7 +91,7 @@ object LightLevels : Module( runSafe { if (areaMode.player) buildPositions(positions, player.blockPos) - if (areaMode.camera) buildPositions(positions, mc.gameRenderer.camera.pos.flooredBlockPos) + if (areaMode.camera) buildPositions(positions, mc.gameRenderer.camera.pos.blockPos) positions.forEach { pos -> buildRender(pos, dashStyle) } diff --git a/src/main/kotlin/com/lambda/module/modules/render/MapPreview.kt b/src/main/kotlin/com/lambda/module/modules/render/MapPreview.kt index fedeea4cd..270f73c83 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/MapPreview.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/MapPreview.kt @@ -19,7 +19,7 @@ package com.lambda.module.modules.render import com.lambda.Lambda.mc import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import net.minecraft.client.font.TextRenderer import net.minecraft.client.gl.RenderPipelines import net.minecraft.client.gui.DrawContext diff --git a/src/main/kotlin/com/lambda/module/modules/render/Nametags.kt b/src/main/kotlin/com/lambda/module/modules/render/Nametags.kt index 7cafda47f..176106c69 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/Nametags.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/Nametags.kt @@ -32,7 +32,7 @@ import com.lambda.graphics.text.FontHandler import com.lambda.graphics.util.DynamicAABB.Companion.interpolatedBox import com.lambda.interaction.handler.handlers.FriendHandler.isFriend import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafe import com.lambda.util.extension.fullHealth import com.lambda.util.extension.maxFullHealth diff --git a/src/main/kotlin/com/lambda/module/modules/render/NoRender.kt b/src/main/kotlin/com/lambda/module/modules/render/NoRender.kt index 1287a2fcb..2ba504765 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/NoRender.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/NoRender.kt @@ -22,7 +22,7 @@ import com.lambda.config.blocks.EntitySelectionSettings import com.lambda.config.editTypedSettings import com.lambda.config.withEdits import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.EntityUtils.createNameMap import com.lambda.util.ReflectionUtils.scanResult import net.minecraft.block.entity.BlockEntity diff --git a/src/main/kotlin/com/lambda/module/modules/render/RadiusESP.kt b/src/main/kotlin/com/lambda/module/modules/render/RadiusESP.kt index 478cb318b..6e844c124 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/RadiusESP.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/RadiusESP.kt @@ -28,7 +28,7 @@ import com.lambda.graphics.mc.RenderBuilder import com.lambda.graphics.mc.renderer.ChunkedRenderer.Companion.chunkedRenderer import com.lambda.graphics.mc.renderer.ImmediateRenderer.Companion.immediateRenderer import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafe import com.lambda.util.BlockUtils.blockState import com.lambda.util.math.setAlpha diff --git a/src/main/kotlin/com/lambda/module/modules/render/Search.kt b/src/main/kotlin/com/lambda/module/modules/render/Search.kt index 15752cf49..7d941f1f6 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/Search.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/Search.kt @@ -36,7 +36,7 @@ import com.lambda.graphics.util.DirectionMask import com.lambda.graphics.util.DirectionMask.buildSideMesh import com.lambda.graphics.util.DynamicAABB.Companion.interpolatedBox import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafe import com.lambda.util.BlockUtils.blockState import com.lambda.util.EntityUtils.decorationEntityMap diff --git a/src/main/kotlin/com/lambda/module/modules/render/Time.kt b/src/main/kotlin/com/lambda/module/modules/render/Time.kt index d5e384c68..986b7876d 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/Time.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/Time.kt @@ -21,7 +21,7 @@ import com.lambda.event.events.PacketEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import net.minecraft.network.packet.s2c.play.WorldTimeUpdateS2CPacket object Time : Module( diff --git a/src/main/kotlin/com/lambda/module/modules/render/Tracers.kt b/src/main/kotlin/com/lambda/module/modules/render/Tracers.kt index 792102e79..f2033ad5c 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/Tracers.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/Tracers.kt @@ -28,7 +28,7 @@ import com.lambda.graphics.mc.renderer.ImmediateRenderer.Companion.immediateRend import com.lambda.graphics.mc.renderer.RendererUtils.worldToScreenNormalized import com.lambda.interaction.handler.handlers.FriendHandler.isFriend import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafe import com.lambda.util.extension.prevPos import com.lambda.util.extension.tickDelta diff --git a/src/main/kotlin/com/lambda/module/modules/render/ViewModel.kt b/src/main/kotlin/com/lambda/module/modules/render/ViewModel.kt index ff2acce3d..82239bbfa 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/ViewModel.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/ViewModel.kt @@ -24,7 +24,7 @@ import com.lambda.event.events.ButtonEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import net.minecraft.client.network.AbstractClientPlayerEntity import net.minecraft.client.util.math.MatrixStack import net.minecraft.item.ItemStack diff --git a/src/main/kotlin/com/lambda/module/modules/render/Weather.kt b/src/main/kotlin/com/lambda/module/modules/render/Weather.kt index 13a6b5239..5c218aaf1 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/Weather.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/Weather.kt @@ -19,7 +19,7 @@ package com.lambda.module.modules.render import com.lambda.config.Tab import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafe import net.minecraft.world.World diff --git a/src/main/kotlin/com/lambda/module/modules/render/WorldColors.kt b/src/main/kotlin/com/lambda/module/modules/render/WorldColors.kt index 227d0e209..b8307141e 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/WorldColors.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/WorldColors.kt @@ -18,7 +18,7 @@ package com.lambda.module.modules.render import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.math.vec3d import net.minecraft.util.math.Vec3d import java.awt.Color diff --git a/src/main/kotlin/com/lambda/module/modules/render/XRay.kt b/src/main/kotlin/com/lambda/module/modules/render/XRay.kt index 6e9d1e2a6..4b58bae51 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/XRay.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/XRay.kt @@ -23,7 +23,7 @@ import com.lambda.config.settings.collections.onDeselect import com.lambda.config.settings.collections.onSelect import com.lambda.context.SafeContext import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.BlockUtils import net.minecraft.block.Blocks import net.minecraft.fluid.Fluids diff --git a/src/main/kotlin/com/lambda/module/modules/render/Zoom.kt b/src/main/kotlin/com/lambda/module/modules/render/Zoom.kt index d991feb93..d654a69c5 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/Zoom.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/Zoom.kt @@ -20,7 +20,7 @@ package com.lambda.module.modules.render import com.lambda.event.events.ButtonEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.NamedEnum import java.lang.Math.clamp diff --git a/src/main/kotlin/com/lambda/module/modules/world/AirPlace.kt b/src/main/kotlin/com/lambda/module/modules/world/AirPlace.kt index b29ce5000..952a1bda5 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/AirPlace.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/AirPlace.kt @@ -36,7 +36,7 @@ import com.lambda.interaction.manager.managers.interacting.InteractRequest import com.lambda.interaction.manager.managers.interacting.interactRequest import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotation import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafeAutomated import com.lambda.util.InputUtils.isSatisfied import com.lambda.util.KeyCode diff --git a/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt b/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt index e9154a9f8..a6665179c 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt @@ -45,7 +45,7 @@ import com.lambda.module.modules.world.AutoPortal.PosHandler.currAnchorPos import com.lambda.module.modules.world.AutoPortal.PosHandler.obiPositions import com.lambda.module.modules.world.AutoPortal.PosHandler.portalPositions import com.lambda.module.modules.world.AutoPortal.PosHandler.prevAnchorPos -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.task.Task import com.lambda.task.start import com.lambda.task.tasks.build diff --git a/src/main/kotlin/com/lambda/module/modules/world/AutoSign.kt b/src/main/kotlin/com/lambda/module/modules/world/AutoSign.kt index d2f1e502b..068c898ad 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/AutoSign.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/AutoSign.kt @@ -21,7 +21,7 @@ import com.ibm.icu.util.Calendar import com.lambda.event.events.GuiEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runConcurrent import com.lambda.threading.runSafeGameScheduled import com.lambda.util.PacketUtils.sendPacket diff --git a/src/main/kotlin/com/lambda/module/modules/world/AutoVillagerCycle.kt b/src/main/kotlin/com/lambda/module/modules/world/AutoVillagerCycle.kt index 82751cce5..78352b15c 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/AutoVillagerCycle.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/AutoVillagerCycle.kt @@ -32,7 +32,7 @@ import com.lambda.interaction.construction.blueprint.toBlueprint import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.sound.SoundHandler.playSound import com.lambda.task.Task import com.lambda.task.start diff --git a/src/main/kotlin/com/lambda/module/modules/world/HighwayTools.kt b/src/main/kotlin/com/lambda/module/modules/world/HighwayTools.kt index b3c89f8f1..7ca52d72b 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/HighwayTools.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/HighwayTools.kt @@ -25,7 +25,7 @@ import com.lambda.interaction.construction.blueprint.PropagatingBlueprint.Compan import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.handler.handlers.BaritoneHandler import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.task.Task import com.lambda.task.start import com.lambda.task.tasks.build diff --git a/src/main/kotlin/com/lambda/module/modules/world/MapDownloader.kt b/src/main/kotlin/com/lambda/module/modules/world/MapDownloader.kt index 7ced3f6a3..5f9b6e324 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/MapDownloader.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/MapDownloader.kt @@ -20,7 +20,7 @@ package com.lambda.module.modules.world import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.util.FileUtils.locationBoundDirectory import com.lambda.util.FolderRegistry import com.lambda.util.StringUtils.hashString diff --git a/src/main/kotlin/com/lambda/module/modules/world/Nuker.kt b/src/main/kotlin/com/lambda/module/modules/world/Nuker.kt index 6f79fafb3..de229a0ff 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/Nuker.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/Nuker.kt @@ -25,7 +25,7 @@ import com.lambda.context.SafeContext import com.lambda.interaction.construction.blueprint.TickingBlueprint.Companion.tickingBlueprint import com.lambda.interaction.construction.verify.TargetState import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.task.Task import com.lambda.task.start import com.lambda.task.tasks.build diff --git a/src/main/kotlin/com/lambda/module/modules/world/Printer.kt b/src/main/kotlin/com/lambda/module/modules/world/Printer.kt index 6b8639976..016ed4a3a 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/Printer.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/Printer.kt @@ -33,7 +33,7 @@ import com.lambda.interaction.construction.simulation.result.results.BreakResult import com.lambda.interaction.construction.simulation.result.results.InteractResult import com.lambda.interaction.construction.verify.TargetState import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.task.Task import com.lambda.task.start import com.lambda.task.tasks.build 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 b0e8f5c6d..85f99e94f 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/Scaffold.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/Scaffold.kt @@ -34,7 +34,7 @@ import com.lambda.interaction.container.containers.HotbarContainer import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.selectStack import com.lambda.interaction.manager.managers.interacting.interactRequest import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.threading.runSafeAutomated import com.lambda.util.BlockUtils.blockPos import com.lambda.util.BlockUtils.blockState 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 804f3648d..06f603307 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,7 @@ import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.dist import com.lambda.interaction.manager.managers.rotating.RotationManager import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag +import com.lambda.module.ModuleTag import com.lambda.task.Task import com.lambda.task.start import com.lambda.task.tasks.build diff --git a/src/main/kotlin/com/lambda/task/tasks/AcquirePlacedBlockTask.kt b/src/main/kotlin/com/lambda/task/tasks/AcquirePlacedBlockTask.kt index 71c0fb03b..6602dc3f6 100644 --- a/src/main/kotlin/com/lambda/task/tasks/AcquirePlacedBlockTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/AcquirePlacedBlockTask.kt @@ -67,6 +67,6 @@ class AcquirePlacedBlockTask @Ta5kBuilder internal constructor( override fun SafeContext.onStart() { findBlock(block, searchRadius) .softFail() - .execute(this@AcquirePlacedBlockTask) + .start() } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt b/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt index c165f9000..5ae1d61ba 100644 --- a/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt @@ -48,7 +48,7 @@ class CollectDropsTask @Ta5kBuilder internal constructor( ) : Task(), Automated by automated { override val name get() = "Collecting ${drops.size} item drop(s)" - override fun SafeContext.onStart() { + init { listen { runSafeAutomated { val target = diff --git a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt index 275a02318..fc674267b 100644 --- a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt @@ -26,7 +26,8 @@ import com.lambda.interaction.container.ExternalContainer import com.lambda.interaction.container.OpenedContainerContext import com.lambda.interaction.container.selection.ContainerSelection import com.lambda.interaction.container.selection.StackSelection -import com.lambda.interaction.handler.handlers.findContainer +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.mutate +import com.lambda.interaction.handler.handlers.findContainers import com.lambda.task.Task import com.lambda.task.Task.Ta5kBuilder import com.lambda.task.tasks.wrappers.taskOrNull @@ -40,51 +41,75 @@ context(automated: Automated) fun transfer( selection: StackSelection, fromSelection: ContainerSelection = ContainerSelection.EVERYTHING, - toSelection: ContainerSelection -) = ContainerTransferTask(selection, fromSelection, toSelection, automated) + toSelection: ContainerSelection, + toStack: StackSelection = StackSelection.EVERYTHING +) = ContainerTransferTask(selection, fromSelection, toSelection, toStack, automated) @Ta5kBuilder @JvmName("transferExt") context(automated: Automated) -fun StackSelection.transferTo( +fun StackSelection.transfer( fromSelection: ContainerSelection = ContainerSelection.EVERYTHING, - toSelection: ContainerSelection -) = transfer(this, fromSelection, toSelection) + toSelection: ContainerSelection, + toStack: StackSelection = StackSelection.EVERYTHING +) = transfer(this, fromSelection, toSelection, toStack) class ContainerTransferTask @Ta5kBuilder internal constructor( private val selection: StackSelection, private val fromSelection: ContainerSelection, private val toSelection: ContainerSelection, + private val toStack: StackSelection, automated: Automated ) : Task(), Automated by automated { override val name = "Transferring $selection from $fromSelection to $toSelection" override fun SafeContext.onStart() { - val fromContainer = findContainer(containerSelection) - ?: run { - failure(IllegalStateException("Could not find container to pull $selection from")) + val singleSelection = selection.mutate(count = if (selection.count == 0) 0 else 1) + + val fromContainers = findContainers(singleSelection, fromSelection).toList() + val toContainers = findContainers(toStack, toSelection) + .filter { toStack !== StackSelection.EVERYTHING || it.spaceLeft(singleSelection) > 0 } + .toList() + + if (fromContainers.isEmpty()) { + failure(IllegalStateException("Could not find any container to pull $selection from")) + return + } + if (toContainers.isEmpty()) { + failure(IllegalStateException("Could not find any container with space to push $selection to")) + return + } + + if (selection.count > 0) { + val totalAvailable = fromContainers.sumOf { it.count(singleSelection) } + if (totalAvailable < selection.count) { + failure(IllegalStateException("Not enough items across all containers to pull $selection")) return } + } + + val fromContainer = fromContainers.first() + val toContainer = toContainers.first() + when { - fromContainer.isAccessed && toContainer.isAccessed -> - TransferTask() + fromContainers.all { it.isAccessed } && toContainers.all { it.isAccessed } -> + TransferTask(fromContainers, toContainers) .onSuccess { success(it) } fromContainer !is ExternalContainer -> - openTransferClose(toContainer, toContainer, containerSelection) + openTransferClose(toContainer, fromSelection, toSelection) toContainer !is ExternalContainer -> - openTransferClose(fromContainer, toContainer, containerSelection) + openTransferClose(fromContainer, fromSelection, toSelection) - else -> { + else -> taskOrNull { fromContainer.access() } .then { fromCtx -> - transfer(selection, toContainer, ContainerSelection.HOTBAR_AND_INVENTORY) + transfer(selection, fromSelection, ContainerSelection.HOTBAR_AND_INVENTORY) .thenOrNull { fromCtx?.close() } } .thenOrNull { toContainer.access() } - .then { toCtx -> transferAndClose(toCtx, toContainer, ContainerSelection.HOTBAR_AND_INVENTORY) } - } + .then { toCtx -> transferAndClose(toCtx, ContainerSelection.HOTBAR_AND_INVENTORY, toSelection) } }?.start() } @@ -96,11 +121,11 @@ class ContainerTransferTask @Ta5kBuilder internal constructor( @Ta5kBuilder private fun openTransferClose( containerToOpen: Container, - to: Container, - containerSelection: ContainerSelection + fromSelection: ContainerSelection, + toSelection: ContainerSelection ) = containerToOpen .access() - ?.then { ctx -> transferAndClose(ctx, to, containerSelection) } + ?.then { ctx -> transferAndClose(ctx, fromSelection, toSelection) } /** * Transfers [selection] from [from] to [to], then closes the container via [ctx]. @@ -117,7 +142,10 @@ class ContainerTransferTask @Ta5kBuilder internal constructor( ?.onSuccess { success(slot) } } - private inner class TransferTask @Ta5kBuilder constructor() : Task() { + private inner class TransferTask @Ta5kBuilder constructor( + private val fromContainers: List, + private val toContainers: List + ) : Task() { override val name = "Transferring" private var transferred = 0 @@ -127,21 +155,39 @@ class ContainerTransferTask @Ta5kBuilder internal constructor( listen { runSafeAutomated { while (selection.count == 0 || transferred < selection.count) { - val (fromSlot, toSlot) = fromContainer.getTransferSlots(selection, toContainer) + val currentSelection = selection.mutate(count = if (selection.count == 0) 0 else selection.count - transferred) + + var fromSlot: Slot? = null + var toSlot: Slot? = null + var fromContainer: Container? = null + var toContainer: Container? = null + + for (fc in fromContainers) { + for (tc in toContainers) { + val slots = fc.findMoveSlots(currentSelection, tc, toStack) + if (slots.first != null && slots.second != null) { + fromSlot = slots.first + toSlot = slots.second + fromContainer = fc + toContainer = tc + break + } + } + if (fromSlot != null) break + } - if (fromSlot == null || toSlot == null) { + if (fromSlot == null || toSlot == null || fromContainer == null || toContainer == null) { lastSlot?.let { slot -> if (selection.count == 0 || transferred >= selection.count) { success(slot) return@listen } } - if (fromSlot == null) checkFail() - else failure("Unable to find a slot to transfer to.") + checkFail() return@listen } - val moveCount = fromSlot.stack.count + val moveCount = minOf(fromSlot.stack.count, if (selection.count == 0) Int.MAX_VALUE else selection.count - transferred) if (!fromContainer.swap(fromSlot, toSlot, toContainer)) { checkFail() return@listen diff --git a/src/main/kotlin/com/lambda/util/FolderRegistry.kt b/src/main/kotlin/com/lambda/util/FolderRegistry.kt index d02d89b6f..7c284a79f 100644 --- a/src/main/kotlin/com/lambda/util/FolderRegistry.kt +++ b/src/main/kotlin/com/lambda/util/FolderRegistry.kt @@ -35,6 +35,7 @@ object FolderRegistry : Loadable { val replay: Path = lambda.resolve("replay") val cache: Path = lambda.resolve("cache") val capes: Path = cache.resolve("capes") + val containers: Path = cache.resolve("containers") val structure: Path = lambda.resolve("structure") val maps: Path = lambda.resolve("maps") val fonts: Path = lambda.resolve("fonts") @@ -42,7 +43,7 @@ object FolderRegistry : Loadable { val File.relativeMCPath: Path get() = minecraft.relativize(toPath()) override fun load(): String { - val folders = listOf(lambda, config, packetLogs, replay, cache, capes, structure, maps, fonts) + val folders = listOf(lambda, config, packetLogs, replay, cache, capes, containers, structure, maps, fonts) val createdFolders = folders.mapNotNull { if (it.notExists()) { it.createDirectories() diff --git a/src/main/kotlin/com/lambda/util/combat/DamageUtils.kt b/src/main/kotlin/com/lambda/util/combat/DamageUtils.kt index d6d890a09..f1b3abba8 100644 --- a/src/main/kotlin/com/lambda/util/combat/DamageUtils.kt +++ b/src/main/kotlin/com/lambda/util/combat/DamageUtils.kt @@ -20,7 +20,7 @@ package com.lambda.util.combat import com.lambda.context.SafeContext import com.lambda.util.BlockUtils.blockState import com.lambda.util.extension.fullHealth -import com.lambda.util.math.flooredBlockPos +import com.lambda.util.math.blockPos import com.lambda.util.math.minus import com.lambda.util.player.prediction.buildPlayerPrediction import net.minecraft.block.BedBlock @@ -76,7 +76,7 @@ object DamageUtils { val predictedPos = prediction.position val fallDistance = player.y - predictedPos.y + player.fallDistance - val state = blockState(predictedPos.flooredBlockPos) + val state = blockState(predictedPos.blockPos) val block = state.block val distance = fallDistance + diff --git a/src/main/kotlin/com/lambda/util/item/ItemStackUtils.kt b/src/main/kotlin/com/lambda/util/item/ItemStackUtils.kt index 867acc01d..3a06ff7a4 100644 --- a/src/main/kotlin/com/lambda/util/item/ItemStackUtils.kt +++ b/src/main/kotlin/com/lambda/util/item/ItemStackUtils.kt @@ -76,8 +76,8 @@ object ItemStackUtils { val ItemStack.hasSpace get() = spaceLeft > 0 val List.spaceLeft get() = sumOf { it.spaceLeft } val List.spaceLeft @JvmName("slotSpaceLeft") get() = sumOf { it.stack.spaceLeft } - val List.empty: Int get() = count { it.isEmpty } - val List.empty: Int @JvmName("slotEmpty") get() = count { it.stack.isEmpty } + val List.emptySpace: Int get() = count { it.isEmpty } + val List.emptySpace: Int @JvmName("emptySlotSpace") get() = count { it.stack.isEmpty } val List.count: Int get() = if (isEmpty()) -1 else sumOf { it.count } val List.count: Int @JvmName("slotCount") get() = if (isEmpty()) -1 else sumOf { it.stack.count } val List.copy: List get() = map { it.copy() } diff --git a/src/main/kotlin/com/lambda/util/math/Vectors.kt b/src/main/kotlin/com/lambda/util/math/Vectors.kt index b43767cae..baaa02434 100644 --- a/src/main/kotlin/com/lambda/util/math/Vectors.kt +++ b/src/main/kotlin/com/lambda/util/math/Vectors.kt @@ -107,7 +107,7 @@ fun Vec3d.approximate(other: Vec3d, precision: Double = 2.0E-4): Boolean = val Vec3d.roundedBlockPos: BlockPos get() = BlockPos(x.roundToInt(), y.roundToInt(), z.roundToInt()) -val Vec3d.flooredBlockPos: BlockPos +val Vec3d.blockPos: BlockPos get() = BlockPos(x.floorToInt(), y.floorToInt(), z.floorToInt()) val Entity.netherCoord: Vec3d get() = pos.multiply(0.125, 1.0, 0.125) @@ -205,7 +205,7 @@ infix fun Entity.distSq(other: Vec3i): Int = blockPos distSq other infix fun Entity.distSq(other: Entity): Double = squaredDistanceTo(other) context(safeContext: SafeContext) -val Vec3d.isLoaded get() = flooredBlockPos.isLoaded +val Vec3d.isLoaded get() = blockPos.isLoaded val UP = Vec3d(0.0, 1.0, 0.0) val DOWN = Vec3d(0.0, -1.0, 0.0) diff --git a/src/main/kotlin/com/lambda/util/player/prediction/PredictionEntity.kt b/src/main/kotlin/com/lambda/util/player/prediction/PredictionEntity.kt index a4a6e3bc6..25a93a848 100644 --- a/src/main/kotlin/com/lambda/util/player/prediction/PredictionEntity.kt +++ b/src/main/kotlin/com/lambda/util/player/prediction/PredictionEntity.kt @@ -26,7 +26,7 @@ import com.lambda.util.BlockUtils.blockState import com.lambda.util.math.DOWN import com.lambda.util.math.MathUtils.toIntSign import com.lambda.util.math.MathUtils.toRadian -import com.lambda.util.math.flooredBlockPos +import com.lambda.util.math.blockPos import com.lambda.util.math.plus import com.lambda.util.math.times import com.lambda.util.player.MovementUtils.forward @@ -85,7 +85,7 @@ class PredictionEntity(val player: ClientPlayerEntity) { // Other shit private var jumpingCooldown = player.jumpingCooldown - private var velocityAffectingPos = player.supportingBlockPos.orElse((position + DOWN * 0.001).flooredBlockPos) + private var velocityAffectingPos = player.supportingBlockPos.orElse((position + DOWN * 0.001).blockPos) private var horizontalCollision = player.horizontalCollision private var verticalCollision = player.verticalCollision @@ -208,7 +208,7 @@ class PredictionEntity(val player: ClientPlayerEntity) { } val velocityMultiplier = run { - val f = blockState(position.flooredBlockPos).block.velocityMultiplier.toDouble() + val f = blockState(position.blockPos).block.velocityMultiplier.toDouble() val g = blockState(velocityAffectingPos).block.velocityMultiplier.toDouble() if (f == 1.0) g else f } @@ -220,7 +220,7 @@ class PredictionEntity(val player: ClientPlayerEntity) { boundingBox = normalized.offset(position) } - velocityAffectingPos = (position + DOWN * 0.001).flooredBlockPos + velocityAffectingPos = (position + DOWN * 0.001).blockPos } /** @see net.minecraft.entity.LivingEntity.jump */ @@ -232,7 +232,7 @@ class PredictionEntity(val player: ClientPlayerEntity) { /** @see net.minecraft.entity.Entity.getJumpVelocityMultiplier */ val jumpHeight = run { - val f = blockState(position.flooredBlockPos).block.jumpVelocityMultiplier.toDouble() + val f = blockState(position.blockPos).block.jumpVelocityMultiplier.toDouble() val g = blockState(velocityAffectingPos).block.jumpVelocityMultiplier.toDouble() if (f == 1.0) g else f } * 0.42 + player.jumpBoostVelocityModifier From 8d0c65ff98c82f2993d8ea77c22e77111e26034c Mon Sep 17 00:00:00 2001 From: beanbag44 <107891830+beanbag44@users.noreply.github.com> Date: Thu, 10 Sep 2026 12:50:52 +0100 Subject: [PATCH 15/43] fix all syntax errors but most things are broken/could be written better --- .../command/commands/TransferCommand.kt | 33 ++- .../com/lambda/config/blocks/EatConfig.kt | 12 +- .../lambda/config/blocks/InventoryConfig.kt | 35 +-- .../lambda/config/blocks/InventorySettings.kt | 2 - .../EnderChestContainerSerializer.kt | 69 ++++++ .../lambda/gui/components/ClickGuiLayout.kt | 4 +- .../simulation/result/results/BreakResult.kt | 4 +- .../construction/simulation/sims/BreakSim.kt | 18 +- .../simulation/sims/InteractSim.kt | 17 +- .../construction/verify/TargetState.kt | 1 - .../lambda/interaction/container/Container.kt | 4 +- .../external/ShulkerBoxContainer.kt | 8 +- .../container/selection/ContainerSelection.kt | 94 +++++--- .../container/selection/StackSelection.kt | 206 +++++++++++++----- .../handler/handlers/ContainerHandler.kt | 121 ++++++---- .../handler/handlers/GlideHandler.kt | 30 +-- .../handlers/breaking/RebreakHandler.kt | 4 +- .../manager/managers/breaking/BreakManager.kt | 6 +- .../kotlin/com/lambda/module/HudModule.kt | 1 - .../lambda/module/modules/combat/AutoTotem.kt | 2 +- .../module/modules/combat/CrystalAura.kt | 11 +- .../lambda/module/modules/combat/KillAura.kt | 4 +- .../module/modules/combat/PlayerTrap.kt | 11 +- .../lambda/module/modules/combat/Surround.kt | 13 +- .../module/modules/movement/BackTrack.kt | 2 +- .../module/modules/movement/BetterFirework.kt | 14 +- .../lambda/module/modules/movement/Blink.kt | 2 +- .../modules/movement/ElytraAltitudeControl.kt | 2 +- .../module/modules/movement/TargetStrafe.kt | 2 +- .../module/modules/movement/TickShift.kt | 2 +- .../modules/movement/elytrafly/ElytraFly.kt | 2 +- .../movement/elytrafly/ElytraFlyMode.kt | 8 +- .../movement/elytrafly/ObstaclePassingMode.kt | 2 +- .../lambda/module/modules/player/AutoEat.kt | 2 +- .../module/modules/player/AutoElytraSwap.kt | 18 +- .../module/modules/player/EndermanLook.kt | 2 +- .../lambda/module/modules/player/FastBreak.kt | 2 +- .../module/modules/player/InventoryTweaks.kt | 20 +- .../lambda/module/modules/player/LineLock.kt | 2 +- .../module/modules/player/PacketMine.kt | 6 +- .../lambda/module/modules/player/Replay.kt | 2 +- .../module/modules/player/RotationLock.kt | 2 +- .../module/modules/player/StackReplenish.kt | 4 +- .../lambda/module/modules/player/ToolSaver.kt | 2 +- .../module/modules/render/ContainerPreview.kt | 2 +- .../com/lambda/module/modules/render/Esp.kt | 2 +- .../lambda/module/modules/render/Freecam.kt | 4 +- .../module/modules/render/Fullbright.kt | 2 +- .../module/modules/render/LightLevels.kt | 1 + .../lambda/module/modules/render/RadiusESP.kt | 2 +- .../lambda/module/modules/render/Search.kt | 2 +- .../lambda/module/modules/render/ViewModel.kt | 2 +- .../com/lambda/module/modules/render/XRay.kt | 2 +- .../lambda/module/modules/world/AirPlace.kt | 2 +- .../lambda/module/modules/world/AutoPortal.kt | 19 +- .../module/modules/world/AutoVillagerCycle.kt | 2 +- .../module/modules/world/HighwayTools.kt | 2 +- .../module/modules/world/MapDownloader.kt | 5 +- .../com/lambda/module/modules/world/Nuker.kt | 4 +- .../lambda/module/modules/world/Printer.kt | 4 +- .../lambda/module/modules/world/Scaffold.kt | 8 +- .../lambda/module/modules/world/StashMover.kt | 52 ++++- .../kotlin/com/lambda/network/CapeHandler.kt | 2 +- .../task/tasks/AcquirePlacedBlockTask.kt | 17 +- .../com/lambda/task/tasks/AcquireStackTask.kt | 10 +- .../com/lambda/task/tasks/CollectDropsTask.kt | 5 +- .../task/tasks/ContainerTransferTask.kt | 61 +++--- .../kotlin/com/lambda/task/tasks/EatTask.kt | 8 +- 68 files changed, 651 insertions(+), 377 deletions(-) create mode 100644 src/main/kotlin/com/lambda/config/serializers/EnderChestContainerSerializer.kt diff --git a/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt b/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt index 9bcfb93fa..def4906f1 100644 --- a/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt +++ b/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt @@ -28,10 +28,9 @@ import com.lambda.brigadier.executeWithResult import com.lambda.brigadier.required import com.lambda.command.LambdaCommand import com.lambda.config.automation.AutomationConfig -import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.selectStack -import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.selectContainer +import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.containerSelection +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection import com.lambda.interaction.handler.handlers.findContainers -import com.lambda.interaction.handler.handlers.findContainersWithSpace import com.lambda.task.Task import com.lambda.task.start import com.lambda.task.tasks.transfer @@ -52,11 +51,16 @@ object TransferCommand : LambdaCommand( required(integer("amount", 1)) { amount -> required(string("from")) { from -> suggests { ctx, builder -> - val selection = selectStack(amount(ctx).value()) { + val selection = stackSelection(amount(ctx).value()) { isItem(stack(ctx).value().item) } AutomationConfig.DEFAULT.runSafeAutomated { - val containers = findContainers(selection).toList() + val containers = + findContainers( + containerSelection { + hasStack(selection) + } + ).toList() val indexedContainers = containers.withIndex() suggestMatching( @@ -70,11 +74,16 @@ object TransferCommand : LambdaCommand( required(string("to")) { to -> suggests { ctx, builder -> val selection = - selectStack(amount(ctx).value()) { + stackSelection(amount(ctx).value()) { isItem(stack(ctx).value().item) } AutomationConfig.DEFAULT.runSafeAutomated { - val containers = selection.findContainersWithSpace().toList() + val containers = + findContainers( + containerSelection { + hasSpace(selection) + } + ).toList() val indexedContainers = containers.withIndex() suggestMatching( @@ -88,18 +97,18 @@ object TransferCommand : LambdaCommand( executeWithResult { AutomationConfig.DEFAULT.runSafeAutomated { val fromSelection = - selectContainer { - custom { it.name == from().value().split(".").first().trim() } + containerSelection { + predicate { it.name == from().value().split(".").first().trim() } } val toSelection = - selectContainer { - custom { it.name == to().value().split(".").first().trim() } + containerSelection { + predicate { it.name == to().value().split(".").first().trim() } } lastContainerTransfer = transfer( - selectStack(amount().value()) { + stackSelection(amount().value()) { isItem(stack().value().item) }, fromSelection, diff --git a/src/main/kotlin/com/lambda/config/blocks/EatConfig.kt b/src/main/kotlin/com/lambda/config/blocks/EatConfig.kt index 721b5470e..d727e4831 100644 --- a/src/main/kotlin/com/lambda/config/blocks/EatConfig.kt +++ b/src/main/kotlin/com/lambda/config/blocks/EatConfig.kt @@ -20,7 +20,7 @@ package com.lambda.config.blocks import com.lambda.context.Automated import com.lambda.context.AutomatedSafeContext import com.lambda.interaction.container.selection.StackAndSlot -import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.selectStack +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection import com.lambda.threading.runSafe import com.lambda.util.Describable import com.lambda.util.NamedEnum @@ -96,14 +96,14 @@ interface EatConfig { context(c: Automated) fun selector() = - selectStack { + stackSelection { when(this@Reason) { - Hunger -> isOneOfItems(c.eatConfig.nutritiousFood) - Damage -> isOneOfItems(c.eatConfig.regenerationFood) - Fire -> isOneOfItems(c.eatConfig.resistanceFood) + Hunger -> ofAnyItems(c.eatConfig.nutritiousFood) + Damage -> ofAnyItems(c.eatConfig.regenerationFood) + Fire -> ofAnyItems(c.eatConfig.resistanceFood) else -> {} } - if (c.eatConfig.ignoreBadFood) isNoneOfItems(c.eatConfig.badFood) + if (c.eatConfig.ignoreBadFood) noneOfItems(c.eatConfig.badFood) sortedWith(c.eatConfig.selectionPriority.comparator) } } diff --git a/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt b/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt index 4f0e9c893..6e65ca89d 100644 --- a/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt +++ b/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt @@ -20,8 +20,7 @@ package com.lambda.config.blocks import com.lambda.event.events.TickEvent import com.lambda.interaction.container.Container import com.lambda.interaction.container.selection.ContainerSelection -import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.selectContainer -import com.lambda.interaction.container.selection.StackSelection +import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.containerSelection import com.lambda.util.Describable import com.lambda.util.NamedEnum import net.minecraft.item.Item @@ -29,42 +28,24 @@ import net.minecraft.item.Item interface InventoryConfig { val tickStageMask: Collection val disposables: Collection - val accessPriority: Priority - val storePriority: Priority val allowedContainers: Collection val containerSelection: ContainerSelection - get() = selectContainer { ofAnyType(*allowedContainers.toTypedArray()) } + get() = containerSelection { ofAnyType(*allowedContainers.toTypedArray()) } val enderChestSearchRadius: Int - enum class Priority( + @Suppress("unused") + enum class ContainerPriority( override val displayName: String, override val description: String ) : NamedEnum, Describable { WithMinItems("With Min Items", "Pick containers with the fewest matching items (or least space) first; useful for topping off or clearing leftovers."), WithMaxItems("With Max Items", "Pick containers with the most matching items (or most space) first; ideal for bulk moves with fewer transfers."); - fun materialComparator(selection: StackSelection) = - when (this) { - WithMaxItems -> compareBy { it.type } - .thenByDescending { it.count(selection) } - .thenBy { it.name } - - WithMinItems -> compareBy { it.type } - .thenBy { it.count(selection) } - .thenBy { it.name } - } - - fun spaceComparator(selection: StackSelection) = - when (this) { - WithMaxItems -> compareBy { it.type } - .thenByDescending { it.spaceLeft(selection) } - .thenBy { it.name } - - WithMinItems -> compareBy { it.type } - .thenBy { it.spaceLeft(selection) } - .thenBy { it.name } - } + fun comparator(countSelector: (Container, T) -> Int) = + compareBy> { it.first.type } + .thenByDescending { countSelector(it.first, it.second) } + .thenBy { it.first.name } } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/config/blocks/InventorySettings.kt b/src/main/kotlin/com/lambda/config/blocks/InventorySettings.kt index 2659c92f1..84f059189 100644 --- a/src/main/kotlin/com/lambda/config/blocks/InventorySettings.kt +++ b/src/main/kotlin/com/lambda/config/blocks/InventorySettings.kt @@ -31,8 +31,6 @@ class InventorySettings(override val c: Config) : InventoryConfig, ConfigBlock { override val tickStageMask by c.setting("Inventory Stage Mask", ALL_STAGES.toSet(), description = "The sub-tick timing at which inventory actions are performed", displayClassName = true) override val disposables by c.setting("Disposables", ItemUtils.DEFAULT_DISPOSABLES, description = "Items that will be ignored when checking for a free slot") - override val accessPriority by c.setting("Access Priority", InventoryConfig.Priority.WithMinItems, "What container to prefer when retrieving the item from") - override val storePriority by c.setting("Store Priority", InventoryConfig.Priority.WithMinItems, "What container to prefer when storing the item to") override val allowedContainers by c.setting("Allowed Containers", ContainerType.entries, description = "What containers are accessible") @Group(ENDER_CHEST_GROUP) override val enderChestSearchRadius by c.setting("Initial Search Radius", 10, 0..20, unit = " blocks", description = "Initial search radius to scan for an already placed ender chest. If one isn't found, one will be placed. If an ender chest isn't found in the inventory, it scans the rest of the loaded area to find an already placed one") diff --git a/src/main/kotlin/com/lambda/config/serializers/EnderChestContainerSerializer.kt b/src/main/kotlin/com/lambda/config/serializers/EnderChestContainerSerializer.kt new file mode 100644 index 000000000..a62b83044 --- /dev/null +++ b/src/main/kotlin/com/lambda/config/serializers/EnderChestContainerSerializer.kt @@ -0,0 +1,69 @@ +/* + * 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 . + */ + +@file:Suppress("unused") + +package com.lambda.config.serializers + +import com.lambda.config.Deserializer +import com.lambda.config.JsonOps +import com.lambda.config.Serializer +import com.lambda.interaction.container.ContainerType +import com.lambda.interaction.container.containers.external.EnderChestContainer +import net.minecraft.item.ItemStack +import tools.jackson.core.JsonGenerator +import tools.jackson.core.JsonParser +import tools.jackson.databind.DeserializationContext +import tools.jackson.databind.SerializationContext +import tools.jackson.databind.node.ArrayNode +import tools.jackson.databind.node.JsonNodeFactory +import tools.jackson.databind.node.ObjectNode + +object EnderChestContainerSerializer : Serializer(EnderChestContainer::class.java) { + override fun serialize(container: EnderChestContainer, gen: JsonGenerator, ctxt: SerializationContext) { + val root = JsonNodeFactory.instance.objectNode() + root.put("Type", ContainerType.EnderChest.name) + + val stackArray = JsonNodeFactory.instance.arrayNode() + for (stack in container.stacks) { + val encoded = ItemStack.CODEC + .encodeStart(JsonOps.UNCOMPRESSED, stack) + .result() + encoded.ifPresent { stackArray.add(it) } + } + root.set("Stacks", stackArray) + + gen.writeTree(root) + } +} + +object EnderChestContainerDeserializer : Deserializer(EnderChestContainer::class.java) { + override fun deserialize(p: JsonParser, ctxt: DeserializationContext): EnderChestContainer { + val root = p.readValueAsTree() + val stackArray = root.get("Stacks") as? ArrayNode + val stacks = stackArray?.mapNotNull { element -> + ItemStack.CODEC + .parse(JsonOps.UNCOMPRESSED, element) + .result() + .orElse(null) + } ?: emptyList() + + EnderChestContainer.update(stacks) + EnderChestContainer.scanStacksForNestedContainers() + return EnderChestContainer + } +} diff --git a/src/main/kotlin/com/lambda/gui/components/ClickGuiLayout.kt b/src/main/kotlin/com/lambda/gui/components/ClickGuiLayout.kt index f8965fbe4..9bc94da3f 100644 --- a/src/main/kotlin/com/lambda/gui/components/ClickGuiLayout.kt +++ b/src/main/kotlin/com/lambda/gui/components/ClickGuiLayout.kt @@ -47,10 +47,10 @@ import com.lambda.imgui.flag.ImGuiCond import com.lambda.imgui.flag.ImGuiHoveredFlags import com.lambda.imgui.flag.ImGuiWindowFlags import com.lambda.module.ModuleRegistry -import com.lambda.module.modules.client.Client -import com.lambda.module.modules.combat.autodisconnect.AutoDisconnectScreen import com.lambda.module.ModuleTag import com.lambda.module.ModuleTag.Companion.shownTags +import com.lambda.module.modules.client.Client +import com.lambda.module.modules.combat.autodisconnect.AutoDisconnectScreen import com.lambda.sound.LambdaSound import com.lambda.sound.SoundHandler.play import com.lambda.util.Describable diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt index e5bf44af2..87bc4f532 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt @@ -32,7 +32,7 @@ import com.lambda.interaction.construction.simulation.result.Navigable import com.lambda.interaction.construction.simulation.result.Rank import com.lambda.interaction.construction.simulation.result.Resolvable import com.lambda.interaction.container.containers.HotbarContainer -import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.selectStack +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection import com.lambda.interaction.container.selection.select import com.lambda.interaction.handler.handlers.BaritoneHandler import com.lambda.task.tasks.transfer @@ -119,7 +119,7 @@ sealed class BreakResult : BuildResult() { context(_: Automated) override fun resolve() = - selectStack { inverted { isItem(badItem) } } + stackSelection { notItem(badItem) } .transfer(toSelection = HotbarContainer.select()) override fun compareResult(other: ComparableResult) = diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/BreakSim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/BreakSim.kt index 6f60974e2..f3876b581 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/BreakSim.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/BreakSim.kt @@ -29,11 +29,11 @@ import com.lambda.interaction.construction.simulation.result.results.GenericResu import com.lambda.interaction.construction.simulation.sim import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.container.ContainerType -import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.selectContainer +import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.containerSelection import com.lambda.interaction.container.selection.StackAndSlot import com.lambda.interaction.container.selection.StackSelection -import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.selectStack -import com.lambda.interaction.handler.handlers.findContainers +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection +import com.lambda.interaction.handler.handlers.findStacks import com.lambda.interaction.manager.managers.hotbar.HotbarManager import com.lambda.interaction.manager.managers.rotating.RotationManager import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest @@ -138,11 +138,11 @@ class BreakSim internal constructor(simInfo: SimInfo) } private fun AutomatedSafeContext.getSwapStack(): Pair? { - val stackSelection = selectStack { + val stackSelection = stackSelection { if (breakConfig.efficientOnly) isEfficientForBreaking(state) if (breakConfig.suitableToolsOnly) isSuitableForBreaking(state) - if (breakConfig.forceSilkTouch) hasEnchantment(Enchantments.SILK_TOUCH) - if (breakConfig.forceFortunePickaxe) hasEnchantment(Enchantments.FORTUNE) + if (breakConfig.forceSilkTouch) withEnchantment(Enchantments.SILK_TOUCH) + if (breakConfig.forceFortunePickaxe) withEnchantment(Enchantments.FORTUNE) sortedWith { compareByDescending> { it.stack.canBreak(CachedBlockPosition(world, pos, false)) @@ -154,13 +154,11 @@ class BreakSim internal constructor(simInfo: SimInfo) } } - val containerSelection = selectContainer { + val containerSelection = containerSelection { ofAnyType(ContainerType.Hotbar) } - val hotbarCandidates = findContainers(stackSelection, containerSelection) - .flatMap { stackSelection.filter(it.stacks) } - .toList() + val hotbarCandidates = findStacks(stackSelection, containerSelection).toList() if (hotbarCandidates.isEmpty()) { result(GenericResult.WrongItemSelection(pos, stackSelection, player.mainHandStack)) return null diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/InteractSim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/InteractSim.kt index 8f8ec8872..e20ac4ecd 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/InteractSim.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/InteractSim.kt @@ -28,10 +28,10 @@ import com.lambda.interaction.construction.simulation.result.results.InteractRes import com.lambda.interaction.construction.simulation.sim import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.container.ContainerType -import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.selectContainer -import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.selectStack +import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.containerSelection +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection import com.lambda.interaction.container.selection.select -import com.lambda.interaction.handler.handlers.findContainers +import com.lambda.interaction.handler.handlers.findContainer import com.lambda.interaction.manager.managers.rotating.Rotation import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotation import com.lambda.interaction.manager.managers.rotating.RotationManager @@ -206,10 +206,13 @@ class InteractSim internal constructor(simInfo: InteractSimInfo) return null } val stackSelection = item?.select(if (item == Items.AIR) 0 else 1) - ?: selectStack { sortedWith { compareByDescending { it.stack.inventoryIndex == player.inventory.selectedSlot } } } - val containerSelection = selectContainer { ofAnyType(ContainerType.Hotbar) } - val container = findContainers(stackSelection, containerSelection) - .firstOrNull() + ?: stackSelection { sortedWith { compareByDescending { it.stack.inventoryIndex == player.inventory.selectedSlot } } } + val containerSelection = + containerSelection { + hasStack(stackSelection) + ofAnyType(ContainerType.Hotbar) + } + val container = findContainer(containerSelection) ?: run { result(GenericResult.WrongItemSelection(pos, stackSelection, player.mainHandStack)) return null diff --git a/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt b/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt index 7eb8fd043..d79b3748b 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt @@ -20,7 +20,6 @@ package com.lambda.interaction.construction.verify import com.lambda.context.AutomatedSafeContext import com.lambda.context.SafeContext import com.lambda.interaction.handler.handlers.findContainerWithDisposable -import com.lambda.interaction.handler.handlers.findContainersWithDisposable import com.lambda.util.BlockUtils.blockState import com.lambda.util.BlockUtils.emptyState import com.lambda.util.BlockUtils.isEmpty diff --git a/src/main/kotlin/com/lambda/interaction/container/Container.kt b/src/main/kotlin/com/lambda/interaction/container/Container.kt index 18c348749..4195e1134 100644 --- a/src/main/kotlin/com/lambda/interaction/container/Container.kt +++ b/src/main/kotlin/com/lambda/interaction/container/Container.kt @@ -144,12 +144,12 @@ abstract class Container( fun findMoveSlots( selection: StackSelection, toContainer: Container, - toStackSelection: StackSelection = StackSelection.EVERYTHING + toStackSelection: StackSelection = StackSelection.ANYTHING ) = Pair(findSlot(selection), toContainer.findReplaceSlot(toStackSelection)) context(_: Automated) open fun findReplaceSlot( - selection: StackSelection = StackSelection.EVERYTHING + selection: StackSelection = StackSelection.ANYTHING ) = findSlots(selection) .sortedWith(replaceSorter) .firstOrNull() diff --git a/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt index 8ba7a894f..99ec23127 100644 --- a/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt @@ -28,7 +28,7 @@ import com.lambda.interaction.container.OpenContainerTask import com.lambda.interaction.container.OpenedContainerContext import com.lambda.interaction.container.containers.HotbarContainer import com.lambda.interaction.container.selection.ContainerSelection -import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.selectStack +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection import com.lambda.interaction.container.selection.select import com.lambda.task.Task.Ta5kBuilder import com.lambda.task.tasks.breakAndCollect @@ -79,7 +79,7 @@ data class ShulkerBoxContainer( ) : OpenContainerTask(description), Automated by automated { override fun SafeContext.onStart() { transfer( - selectStack { inIndex(index); isItem(shulkerItem) }, + stackSelection { inIndex(index); isItem(shulkerItem) }, containedIn.select(), HotbarContainer.select() ).then { slot -> placeContainer(slot) } @@ -103,9 +103,9 @@ data class ShulkerBoxContainer( else actionTask { player.closeScreen() } }.then { breakAndCollect(blockPos) } .then { - selectStack { + stackSelection { isItem(shulkerItem) - hasName(itemName) + withName(itemName) sortedByBestContentMatch(stacks) }.transfer( ContainerSelection.HOTBAR_AND_INVENTORY, diff --git a/src/main/kotlin/com/lambda/interaction/container/selection/ContainerSelection.kt b/src/main/kotlin/com/lambda/interaction/container/selection/ContainerSelection.kt index 5da064731..9fbb794ea 100644 --- a/src/main/kotlin/com/lambda/interaction/container/selection/ContainerSelection.kt +++ b/src/main/kotlin/com/lambda/interaction/container/selection/ContainerSelection.kt @@ -20,25 +20,26 @@ package com.lambda.interaction.container.selection import com.lambda.interaction.container.Container import com.lambda.interaction.container.ContainerMarker import com.lambda.interaction.container.ContainerType +import com.lambda.interaction.container.NestedContainer import com.lambda.interaction.container.containers.ArmorContainer import com.lambda.interaction.container.containers.CreativeContainer import com.lambda.interaction.container.containers.CursorContainer import com.lambda.interaction.container.containers.HotbarContainer import com.lambda.interaction.container.containers.InventoryContainer import com.lambda.interaction.container.containers.OffHandContainer -import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.selectContainer +import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.containerSelection import com.lambda.interaction.handler.handlers.ContainerSearchScope @ContainerMarker fun Container.select( scope: ContainerSearchScope = ContainerSearchScope.Accessed -) = selectContainer(scope) { ofAny(this@select) } +) = containerSelection(scope) { ofAny(this@select) } @ContainerMarker fun selectContainers( vararg containers: Container, scope: ContainerSearchScope = ContainerSearchScope.Accessed -) = selectContainer(scope) { ofAny(*containers) } +) = containerSelection(scope) { ofAny(*containers) } /** * ContainerSelection is a class that holds a predicate for matching containers. @@ -48,7 +49,8 @@ class ContainerSelection @ContainerMarker internal constructor( val selector: (Container) -> Boolean, val scope: ContainerSearchScope = ContainerSearchScope.Accessed, val containersWhitelist: Collection = emptyList(), - val accessedOnly: Boolean = false, + val containersBlacklist: Collection = emptyList(), + val accessScope: AccessScope = AccessScope.Both, val comparator: Comparator = compareBy { it.type } ) { @ContainerMarker @@ -62,13 +64,16 @@ class ContainerSelection @ContainerMarker internal constructor( run { containersWhitelist .takeIf { it.isNotEmpty() } - ?.filter { it in containers && (!accessedOnly || it.isAccessed) } - ?: containers - }.filter(selector) + ?.asSequence() + ?.filter { it in containers && (accessScope.accessed?.equals(it.isAccessed) != false) } + ?: containers.asSequence() + }.filter { it !in containersBlacklist } + .filter(selector) .sortedWith(comparator) + .toList() companion object { - val EVERYTHING = ContainerSelection({ true }) + val ACCESSED = ContainerSelection({ true }) val NOTHING = ContainerSelection({ false }) val HOTBAR_AND_INVENTORY = selectContainers(HotbarContainer, InventoryContainer) val PLAYER = @@ -95,21 +100,27 @@ class ContainerSelectionBuilder @ContainerMarker private constructor( ) : this(scope) { this.selector = selection.selector this.containersWhitelist.addAll(selection.containersWhitelist) - this.accessedOnly = selection.accessedOnly + this.containersBlacklist.addAll(selection.containersBlacklist) + this.accessScope = selection.accessScope this.comparator = selection.comparator } private var selector: (Container) -> Boolean = { true } - private var containersWhitelist = mutableListOf() - private var accessedOnly = false + private val containersWhitelist = mutableListOf() + private val containersBlacklist = mutableListOf() + private var accessScope: AccessScope = AccessScope.Both private var comparator: Comparator = compareBy { it.type } - private var invertNewSelectors = false - fun ofAny(vararg container: Container) { - containersWhitelist.addAll(container) + fun ofAny(vararg containers: Container) { + containersWhitelist.addAll(containers) appendSelector { container -> container in containersWhitelist } } + fun noneOf(vararg containers: Container) { + containersBlacklist.addAll(containers) + appendSelector { container -> container !in containersBlacklist } + } + fun ofAnyType(vararg types: ContainerType) { appendSelector { container -> types.contains(container.type) } } @@ -118,31 +129,50 @@ class ContainerSelectionBuilder @ContainerMarker private constructor( appendSelector { container -> !types.contains(container.type) } } - fun matchesSlots(stackSelection: StackSelection) { - appendSelector { container -> stackSelection.filter(container.slots).isNotEmpty() } + fun isNested() { + appendSelector { container -> container is NestedContainer } } - fun matchesStacks(stackSelection: StackSelection) { - appendSelector { container -> stackSelection.filter(container.stacks).isNotEmpty() } + fun notNested() { + appendSelector { container -> container !is NestedContainer } } fun matches(containerSelection: ContainerSelection) { appendSelector { container -> containerSelection.matches(container) } } + fun noMatch(containerSelection: ContainerSelection) { + appendSelector { container -> !containerSelection.matches(container) } + } + + fun hasStack(stackSelection: StackSelection) { + appendSelector { container -> stackSelection isIn container } + } + + fun noStack(stackSelection: StackSelection) { + appendSelector { container -> !stackSelection.isIn(container) } + } + + fun hasSpace(stackSelection: StackSelection) { + appendSelector { container -> stackSelection spaceIn container } + } + + fun noSpace(stackSelection: StackSelection) { + appendSelector { container -> !stackSelection.spaceIn(container) } + } + fun isAccessed() { - accessedOnly = true + accessScope = AccessScope.Accessed appendSelector { it.isAccessed } } - fun custom(predicate: (Container) -> Boolean) { - appendSelector { predicate(it) } + fun notAccessed() { + accessScope = AccessScope.NotAccessed + appendSelector { !it.isAccessed } } - fun inverted(block: () -> Boolean) { - invertNewSelectors = true - block() - invertNewSelectors = false + fun predicate(predicate: (Container) -> Boolean) { + appendSelector { predicate(it) } } fun sortedWith(comparator: Comparator) { @@ -154,15 +184,13 @@ class ContainerSelectionBuilder @ContainerMarker private constructor( } private fun appendSelector(selector: (Container) -> Boolean) { - val invert = invertNewSelectors - val currentSelector = this.selector - this.selector = { currentSelector(it) && selector(it) xor invert } + this.selector = { this.selector(it) && selector(it) } } - private fun build() = ContainerSelection(selector, scope, containersWhitelist, accessedOnly, comparator) + private fun build() = ContainerSelection(selector, scope, containersWhitelist, containersBlacklist, accessScope, comparator) companion object { - fun selectContainer( + fun containerSelection( scope: ContainerSearchScope = ContainerSearchScope.Accessed, builder: ContainerSelectionBuilder.() -> Unit ) = ContainerSelectionBuilder(scope).apply(builder).build() @@ -172,4 +200,10 @@ class ContainerSelectionBuilder @ContainerMarker private constructor( builder: ContainerSelectionBuilder.() -> Unit = {} ) = ContainerSelectionBuilder(this, scope).apply(builder).build() } +} + +enum class AccessScope(val accessed: Boolean?) { + Both(null), + Accessed(true), + NotAccessed(false) } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/container/selection/StackSelection.kt b/src/main/kotlin/com/lambda/interaction/container/selection/StackSelection.kt index a58f95d22..262ff755b 100644 --- a/src/main/kotlin/com/lambda/interaction/container/selection/StackSelection.kt +++ b/src/main/kotlin/com/lambda/interaction/container/selection/StackSelection.kt @@ -19,7 +19,7 @@ package com.lambda.interaction.container.selection import com.lambda.interaction.container.Container import com.lambda.interaction.container.ContainerMarker -import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.selectStack +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection import com.lambda.util.EnchantmentUtils.getEnchantment import com.lambda.util.item.ItemStackUtils.shulkerBoxStacks import com.lambda.util.item.ItemUtils @@ -38,18 +38,18 @@ import net.minecraft.screen.slot.Slot import java.util.* @ContainerMarker -fun Item.select(count: Int = 0) = selectStack(count) { isItem(this@select) } +fun Item.select(count: Int = 0) = stackSelection(count) { isItem(this@select) } @ContainerMarker -fun ItemStack.select(count: Int = 0) = selectStack(count) { isItemStack(this@select) } +fun ItemStack.select(count: Int = 0) = stackSelection(count) { isItemStack(this@select) } -@Suppress("unused") class StackSelection @ContainerMarker internal constructor( val whitelistedSlots: Collection = emptyList(), + val blacklistedSlots: Collection = emptyList(), val count: Int = 0, val item: Item? = null, val itemStack: ItemStack? = null, - val inShulkerBox: Boolean = false, + val shulkerBoxScope: ShulkerBoxScope = ShulkerBoxScope.Both, val comparator: Comparator> = compareBy { it.stack.count }, val selector: (ItemStack, Slot?) -> Boolean, ) { @@ -63,23 +63,25 @@ class StackSelection @ContainerMarker internal constructor( @ContainerMarker fun matches(stack: ItemStack) = - if (inShulkerBox) stack.shulkerBoxStacks.any { selector(it, null) } - else selector(stack, null) + shulkerBoxScope.inShulkerBox?.equals(stack.shulkerBoxStacks.any { selector(it, null) }) != false || + selector(stack, null) @ContainerMarker fun matches(slot: Slot): Boolean = - if (inShulkerBox) slot.stack.shulkerBoxStacks.any { selector(it, null) } - else selector(slot.stack, slot) + shulkerBoxScope.inShulkerBox?.equals(slot.stack.shulkerBoxStacks.any { selector(it, null) }) != false || + selector(slot.stack, slot) @ContainerMarker @JvmName("filter1") fun filter(stacks: Iterable) = stacks .let { if (whitelistedSlots.isNotEmpty()) emptyList() else it } + .asSequence() .filter(::matches) .map { StackAndSlot(it, null) } .sortedWith(comparator) .map { it.stack } + .toList() @ContainerMarker @JvmName("filter2") @@ -87,22 +89,29 @@ class StackSelection @ContainerMarker internal constructor( slots .let { slots -> if (whitelistedSlots.isNotEmpty()) { - whitelistedSlots.filter { it in slots } - } else slots + whitelistedSlots + .asSequence() + .filter { it in slots } + } else slots.asSequence() } + .filter { it !in blacklistedSlots } .filter(::matches) .map { StackAndSlot(it.stack, it) } .sortedWith(comparator) .map { it.slot } + .toList() + @ContainerMarker infix fun isIn(container: Container) = container.count(this) >= count + @ContainerMarker infix fun spaceIn(container: Container) = container.spaceLeft(this) >= count + @Suppress("unused") companion object { - val EVERYTHING = StackSelection { _, _ -> true } + val ANYTHING = StackSelection { _, _ -> true } val NOTHING = StackSelection { _, _ -> false } } } @@ -113,12 +122,12 @@ class StackSelectionBuilder @ContainerMarker private constructor( private var count: Int = 0 ) { private val whitelistedSlots = mutableListOf() + private val blacklistedSlots = mutableListOf() private var selector: (ItemStack, Slot?) -> Boolean = { _, _ -> true } private var item: Item? = null private var itemStack: ItemStack? = null private var comparator: Comparator>? = null - private var inShulkerBoxesOnly: Boolean = false - private var invertNewSelectors = false + private var shulkerBoxScope: ShulkerBoxScope = ShulkerBoxScope.NotInShulkerBox @ContainerMarker private constructor( @@ -126,11 +135,12 @@ class StackSelectionBuilder @ContainerMarker private constructor( count: Int = selection.count ) : this(count) { this.whitelistedSlots.addAll(selection.whitelistedSlots) + this.blacklistedSlots.addAll(selection.blacklistedSlots) this.selector = selection.selector this.item = selection.item this.itemStack = selection.itemStack this.comparator = selection.comparator - this.inShulkerBoxesOnly = selection.inShulkerBox + this.shulkerBoxScope = selection.shulkerBoxScope } fun isItem(item: Item) { @@ -138,30 +148,51 @@ class StackSelectionBuilder @ContainerMarker private constructor( appendSelector { stack, _ -> stack.item == item } } + fun notItem(item: Item) { + appendSelector { stack, _ -> stack.item != item } + } + inline fun isItem() { val kClass = T::class appendSelector { stack, _ -> stack::class == kClass } } + inline fun notItem() { + val kClass = T::class + appendSelector { stack, _ -> stack::class != kClass } + } + fun isItemStack(stack: ItemStack) { this.itemStack = stack appendSelector { s, _ -> ItemStack.areEqual(s, stack) } } + fun notItemStack(stack: ItemStack) { + appendSelector { s, _ -> !ItemStack.areEqual(s, stack) } + } + fun isItemStackByRef(stack: ItemStack) { this.itemStack = stack appendSelector { s, _ -> s === stack } } - fun inShulkerBoxesOnly() { - inShulkerBoxesOnly = true + fun notItemStackByRef(stack: ItemStack) { + appendSelector { s, _ -> s !== stack } + } + + fun alsoInShulkerBoxes() { + shulkerBoxScope = ShulkerBoxScope.Both } - fun isOneOfItems(items: Collection) { + fun onlyInShulkerBoxes() { + shulkerBoxScope = ShulkerBoxScope.InShulkerBox + } + + fun ofAnyItems(items: Collection) { appendSelector { stack, _ -> items.contains(stack.item) } } - fun isNoneOfItems(items: Collection) { + fun noneOfItems(items: Collection) { appendSelector { stack, _ -> !items.contains(stack.item) } } @@ -169,52 +200,87 @@ class StackSelectionBuilder @ContainerMarker private constructor( appendSelector { stack, _ -> stack.item in ItemUtils.SHULKER_BOXES } } - fun hasName(name: String) { - appendSelector { stack, _ -> - stack.name.string == name - } + fun notShulkerBox() { + appendSelector { stack, _ -> stack.item !in ItemUtils.SHULKER_BOXES } + } + + fun withName(name: String) { + appendSelector { stack, _ -> stack.name.string == name } + } + + fun withoutName(name: String) { + appendSelector { stack, _ -> stack.name.string != name } } - fun isOneOfStacks(stacks: Collection) { + fun ofAnyStacks(stacks: Collection) { appendSelector { stack, _ -> stacks.contains(stack) } } - fun isNoneOfStacks(stacks: Collection) { + fun noneOfStacks(stacks: Collection) { appendSelector { stack, _ -> !stacks.contains(stack) } } fun isEfficientForBreaking(blockState: BlockState) { appendSelector { itemStack, _ -> - val hasEfficientTool = efficientToolCache.getOrPut(blockState) { - ItemUtils.TOOLS.any { it.getMiningSpeed(it.defaultStack, blockState) > 1f } - } - if (hasEfficientTool) itemStack.item.getMiningSpeed(itemStack, blockState) > 1f + if (hasEfficientTool(blockState)) itemStack.item.getMiningSpeed(itemStack, blockState) > 1f else true } } + fun notEfficientForBreaking(blockState: BlockState) { + appendSelector { itemStack, _ -> + if (hasEfficientTool(blockState)) itemStack.item.getMiningSpeed(itemStack, blockState) <= 1f + else true + } + } + + private fun hasEfficientTool(blockState: BlockState) = + efficientToolCache.getOrPut(blockState) { + ItemUtils.TOOLS.any { it.getMiningSpeed(it.defaultStack, blockState) > 1f } + } + fun isSuitableForBreaking(blockState: BlockState) { appendSelector { itemStack, _ -> !blockState.isToolRequired || itemStack.isSuitableFor(blockState) } } - fun hasTag(tag: TagKey) { + fun notSuitableForBreaking(blockState: BlockState) { + appendSelector { itemStack, _ -> + blockState.isToolRequired && !itemStack.isSuitableFor(blockState) + } + } + + fun withTag(tag: TagKey) { appendSelector { stack, _ -> stack.isIn(tag) } } - fun hasUseAction(action: UseAction) { + fun withoutTag(tag: TagKey) { + appendSelector { stack, _ -> !stack.isIn(tag) } + } + + fun withUseAction(action: UseAction) { appendSelector { stack, _ -> stack.useAction == action } } - fun isTool() = hasComponent(DataComponentTypes.TOOL) + fun withoutUseAction(action: UseAction) { + appendSelector { stack, _ -> stack.useAction != action } + } - fun isFood() = hasComponent(DataComponentTypes.FOOD) + fun isTool() = withComponent(DataComponentTypes.TOOL) - fun hasComponent(type: ComponentType<*>) { - appendSelector { stack, _ -> - stack.components.contains(type) - } + fun notTool() = withoutComponent(DataComponentTypes.TOOL) + + fun isFood() = withComponent(DataComponentTypes.FOOD) + + fun notFood() = withoutComponent(DataComponentTypes.FOOD) + + fun withComponent(type: ComponentType<*>) { + appendSelector { stack, _ -> stack.components.contains(type) } + } + + fun withoutComponent(type: ComponentType<*>) { + appendSelector { stack, _ -> !stack.components.contains(type) } } fun isBlock(block: Block) { @@ -222,17 +288,29 @@ class StackSelectionBuilder @ContainerMarker private constructor( appendSelector { stack, _ -> stack.item == block.asItem() } } - fun hasDamage(damage: Int) { + fun notBlock(block: Block) { + appendSelector { stack, _ -> stack.item != block.asItem() } + } + + fun withDamage(damage: Int) { appendSelector { stack, _ -> stack.damage == damage } } - fun hasEnchantment(enchantment: RegistryKey, level: Int = -1) { + fun withoutDamage(damage: Int) { + appendSelector { stack, _ -> stack.damage != damage } + } + + fun withEnchantment(enchantment: RegistryKey, level: Int = -1) { appendSelector { stack, _ -> - if (level < 0) { - stack.getEnchantment(enchantment) > 0 - } else { - stack.getEnchantment(enchantment) == level - } + if (level < 0) stack.getEnchantment(enchantment) > 0 + else stack.getEnchantment(enchantment) == level + } + } + + fun withoutEnchantment(enchantment: RegistryKey, level: Int = -1) { + appendSelector { stack, _ -> + if (level < 0) stack.getEnchantment(enchantment) <= 0 + else stack.getEnchantment(enchantment) != level } } @@ -240,27 +318,38 @@ class StackSelectionBuilder @ContainerMarker private constructor( appendSelector { stack, _ -> stack.isEmpty} } + fun notEmpty() { + appendSelector { stack, _ -> !stack.isEmpty} + } + fun isSlot(slot: Slot) { appendSelector { _, s -> s != null && s matches slot } } + fun notSlot(slot: Slot) { + appendSelector { _, s -> s == null || !s.matches(slot) } + } + fun isSlotByReference(slot: Slot) { whitelistedSlots.add(slot) appendSelector { _, s -> s === slot } } + fun notSlotByReference(slot: Slot) { + blacklistedSlots.add(slot) + appendSelector { _, s -> s !== slot } + } + fun inIndex(index: Int) { appendSelector { _, s -> s?.index == index } } - fun custom(predicate: (ItemStack, Slot?) -> Boolean) { - appendSelector { stack, slot -> predicate(stack, slot) } + fun notInIndex(index: Int) { + appendSelector { _, s -> s?.index != index } } - fun inverted(block: () -> Unit) { - invertNewSelectors = true - block() - invertNewSelectors = false + fun predicate(predicate: (ItemStack, Slot?) -> Boolean) { + appendSelector { stack, slot -> predicate(stack, slot) } } fun sortedWith(newComparator: Comparator>) { @@ -291,20 +380,19 @@ class StackSelectionBuilder @ContainerMarker private constructor( @PublishedApi internal fun appendSelector(selector: (ItemStack, Slot?) -> Boolean) { - val invert = invertNewSelectors - val currentSelector = this.selector this.selector = { stack, slot -> - currentSelector(stack, slot) && selector(stack, slot) xor invert + this.selector(stack, slot) && selector(stack, slot) } } private fun build() = StackSelection( whitelistedSlots, + blacklistedSlots, count, item, itemStack, - inShulkerBoxesOnly, + shulkerBoxScope, comparator ?: compareBy { it.stack.count }, selector ) @@ -312,7 +400,7 @@ class StackSelectionBuilder @ContainerMarker private constructor( companion object { private val efficientToolCache = Collections.synchronizedMap(mutableMapOf()) - fun selectStack( + fun stackSelection( count: Int = 0, builder: StackSelectionBuilder.() -> Unit ) = StackSelectionBuilder(count).apply(builder).build() @@ -324,4 +412,10 @@ class StackSelectionBuilder @ContainerMarker private constructor( } } -data class StackAndSlot(val stack: ItemStack, val slot: T) \ No newline at end of file +data class StackAndSlot(val stack: ItemStack, val slot: T) + +enum class ShulkerBoxScope(val inShulkerBox: Boolean?) { + Both(null), + InShulkerBox(true), + NotInShulkerBox(false) +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt index be438a6ad..ca19ef387 100644 --- a/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt @@ -17,7 +17,9 @@ package com.lambda.interaction.handler.handlers +import com.lambda.Lambda import com.lambda.context.Automated +import com.lambda.context.AutomatedSafeContext import com.lambda.context.SafeContext import com.lambda.core.Loadable import com.lambda.event.events.InventoryEvent @@ -34,14 +36,15 @@ import com.lambda.interaction.container.containers.external.EnderChestContainer import com.lambda.interaction.container.containers.external.PlacedShulkerBoxContainer import com.lambda.interaction.container.containers.external.ShulkerBoxContainer import com.lambda.interaction.container.selection.ContainerSelection -import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.selectContainer +import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.containerSelection import com.lambda.interaction.container.selection.StackSelection import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.mutate -import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.selectStack +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection import com.lambda.util.BlockUtils.blockEntity import com.lambda.util.FolderRegistry import com.lambda.util.ReflectionUtils.getInstances import com.lambda.util.extension.containerStacks +import com.lambda.util.item.ItemStackUtils.count import com.lambda.util.item.ItemStackUtils.shulkerBoxStacks import com.lambda.util.item.ItemUtils.SHULKER_BOXES import com.lambda.util.player.SlotUtils.typeSafe @@ -58,8 +61,6 @@ import net.minecraft.screen.ScreenHandler import net.minecraft.screen.ScreenHandlerType import net.minecraft.state.property.Properties import net.minecraft.util.math.BlockPos -import com.lambda.Lambda -import com.lambda.context.AutomatedSafeContext import java.nio.file.Files @Suppress("unused") @@ -237,16 +238,15 @@ object ContainerHandler : Loadable { @ContainerMarker context(_: AutomatedSafeContext) fun StackSelection.move( - fromSelection: ContainerSelection = ContainerSelection.EVERYTHING, - toSelection: ContainerSelection = ContainerSelection.EVERYTHING, - toStackSelection: StackSelection = StackSelection.EVERYTHING + fromSelection: ContainerSelection = ContainerSelection.ACCESSED, + toSelection: ContainerSelection = ContainerSelection.ACCESSED, + replaceSelection: StackSelection = StackSelection.ANYTHING ): Boolean { - val singleSelection = mutate(count.coerceAtMost(64)) val fromContainer = findContainer( - singleSelection, - selectContainer { + containerSelection { matches(fromSelection) + hasStack(mutate(count.coerceAtMost(64))) isAccessed() } ) @@ -254,76 +254,119 @@ fun StackSelection.move( val toContainer = findContainer( - toStackSelection, - selectContainer { + containerSelection { matches(toSelection) + hasStack(replaceSelection) isAccessed() } ) ?: return false - val (fromSlot, toSlot) = fromContainer.findMoveSlots(this, toContainer, toStackSelection) + val (fromSlot, toSlot) = fromContainer.findMoveSlots(this, toContainer, replaceSelection) if (fromSlot == null || toSlot == null) return false return fromContainer.swap(fromSlot, toSlot, toContainer) } @ContainerMarker -context(_: Automated) -fun findContainer( - stackSelection: StackSelection = StackSelection.EVERYTHING, - containerSelection: ContainerSelection = ContainerSelection.EVERYTHING -) = findContainers(stackSelection, containerSelection) +context(automated: Automated) +fun findSlot( + stackSelection: StackSelection = StackSelection.ANYTHING, + containerSelection: ContainerSelection = ContainerSelection.ACCESSED, + sorted: Boolean = true +) = findSlots(stackSelection, containerSelection, sorted) .firstOrNull() @ContainerMarker context(automated: Automated) -fun findContainers( - stackSelection: StackSelection = StackSelection.EVERYTHING, - containerSelection: ContainerSelection = ContainerSelection.EVERYTHING +fun findSlots( + stackSelection: StackSelection = StackSelection.ANYTHING, + containerSelection: ContainerSelection = ContainerSelection.ACCESSED, + sorted: Boolean = true +) = searchContainers(containerSelection.scope) + .filter { containerSelection.matches(it) } + .let { + if (sorted) it.sorted() + else it + } + .mapNotNull { container -> + val slots = stackSelection.filter(container.slots) + if (slots.count >= stackSelection.count) slots + else null + } + .flatten() + +@ContainerMarker +context(automated: Automated) +fun findStack( + stackSelection: StackSelection = StackSelection.ANYTHING, + containerSelection: ContainerSelection = ContainerSelection.ACCESSED, + sorted: Boolean = true +) = findStacks(stackSelection, containerSelection, sorted) + .firstOrNull() + +@ContainerMarker +context(automated: Automated) +fun findStacks( + stackSelection: StackSelection = StackSelection.ANYTHING, + containerSelection: ContainerSelection = ContainerSelection.ACCESSED, + sorted: Boolean = true ) = searchContainers(containerSelection.scope) .filter { containerSelection.matches(it) } - .filter { stackSelection isIn it } - .sortedWith(automated.inventoryConfig.accessPriority.materialComparator(stackSelection)) + .let { + if (sorted) it.sorted() + else it + } + .mapNotNull { container -> + val stacks = stackSelection.filter(container.stacks) + if (stacks.count >= stackSelection.count) stacks + else null + } + .flatten() @ContainerMarker context(_: Automated) -fun StackSelection.findContainerWithSpace( - containerSelection: ContainerSelection = ContainerSelection.EVERYTHING -) = findContainersWithSpace(containerSelection) +fun findContainer( + containerSelection: ContainerSelection = ContainerSelection.ACCESSED, + sorted: Boolean = true +) = findContainers(containerSelection, sorted) .firstOrNull() @ContainerMarker context(automated: Automated) -fun StackSelection.findContainersWithSpace( - containerSelection: ContainerSelection = ContainerSelection.EVERYTHING +fun findContainers( + containerSelection: ContainerSelection = ContainerSelection.ACCESSED, + sorted: Boolean = true ) = searchContainers(containerSelection.scope) .filter { containerSelection.matches(it) } - .filter { this spaceIn it } - .sortedWith(automated.inventoryConfig.accessPriority.spaceComparator(this)) + .let { + if (sorted) it.sorted() + else it + } @ContainerMarker context(_: Automated) fun findContainerWithDisposable( - containerSelection: ContainerSelection = ContainerSelection.EVERYTHING -) = findContainersWithDisposable(containerSelection) + containerSelection: ContainerSelection = ContainerSelection.ACCESSED, + sorted: Boolean = true +) = findContainersWithDisposable(containerSelection, sorted) .firstOrNull() @ContainerMarker context(automated: Automated) fun findContainersWithDisposable( - containerSelection: ContainerSelection = ContainerSelection.EVERYTHING + containerSelection: ContainerSelection = ContainerSelection.ACCESSED, + sorted: Boolean = true ) = with(automated) { findContainers( - containerSelection = selectContainer(containerSelection.scope) { + containerSelection(containerSelection.scope) { matches(containerSelection) - matchesStacks( - selectStack(1) { - isOneOfItems(inventoryConfig.disposables) - } + hasStack( + stackSelection(1) { ofAnyItems(inventoryConfig.disposables) } ) - } + }, + sorted ) } diff --git a/src/main/kotlin/com/lambda/interaction/handler/handlers/GlideHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/GlideHandler.kt index d7de19c14..2a8d1b498 100644 --- a/src/main/kotlin/com/lambda/interaction/handler/handlers/GlideHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/GlideHandler.kt @@ -17,13 +17,12 @@ package com.lambda.interaction.handler.handlers -import com.lambda.context.Automated import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.container.selection.ContainerSelection +import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.containerSelection import com.lambda.interaction.container.selection.StackAndSlot -import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.selectStack +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection import com.lambda.module.modules.combat.AutoArmor import com.lambda.module.modules.movement.elytrafly.ElytraFly import com.lambda.module.modules.movement.elytrafly.ElytraFly.mode @@ -42,9 +41,9 @@ import net.minecraft.registry.tag.ItemTags object GlideHandler { val ELYTRA_SELECTION = - selectStack { + stackSelection { isItem(Items.ELYTRA) - custom { stack, _ -> stack.damage < stack.maxDamage } + predicate { stack, _ -> stack.damage < stack.maxDamage } sortedWith { compareByDescending> { it.stack.getEnchantment(Enchantments.UNBREAKING) @@ -54,10 +53,10 @@ object GlideHandler { } } val CHESTPLATE_SELECTION = - selectStack { - hasTag(ItemTags.CHEST_ARMOR) - inverted { isItem(Items.ELYTRA) } - custom { stack, _ -> stack.damage < stack.maxDamage } + stackSelection { + withTag(ItemTags.CHEST_ARMOR) + notItem(Items.ELYTRA) + predicate { stack, _ -> stack.damage < stack.maxDamage } sortedWith { compareByDescending> { it.stack.getOrDefault(DataComponentTypes.ATTRIBUTE_MODIFIERS, null) @@ -166,18 +165,19 @@ object GlideHandler { } @JvmStatic - context(safeContext: SafeContext, _: Automated) + context(safeContext: SafeContext) fun canGlide(): Boolean = with(safeContext) { val fakeFly = ElytraFly.isEnabled && ElytraFly.fakeFly val canGlideAlready = player.canGlideWithChestPiece() != fakeFly if (!autoSwapChecking || canGlideAlready) return canGlideAlready val selection = if (fakeFly) CHESTPLATE_SELECTION else ELYTRA_SELECTION - return findContainers( - selection, - ContainerSelection.HOTBAR_AND_INVENTORY - ).toList() - .isNotEmpty() + return with(ElytraFly) { + findContainer( + containerSelection { hasStack(selection) }, + false + ) != null + } } private class PendingGlideAction( diff --git a/src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/RebreakHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/RebreakHandler.kt index f800db9ff..40f04a2bf 100644 --- a/src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/RebreakHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/RebreakHandler.kt @@ -24,10 +24,10 @@ 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.interaction.construction.simulation.context.BreakContext -import com.lambda.interaction.handler.handlers.breaking.BrokenBlockHandler.destroyBlock -import com.lambda.interaction.handler.handlers.breaking.RebreakHandler.reBreak import com.lambda.interaction.handler.handlers.PacketLimitHandler import com.lambda.interaction.handler.handlers.PacketType +import com.lambda.interaction.handler.handlers.breaking.BrokenBlockHandler.destroyBlock +import com.lambda.interaction.handler.handlers.breaking.RebreakHandler.reBreak import com.lambda.interaction.manager.managers.breaking.BreakInfo import com.lambda.interaction.manager.managers.breaking.BreakManager import com.lambda.interaction.manager.managers.breaking.BreakManager.calcBreakDelta diff --git a/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakManager.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakManager.kt index 00a14ab4e..eabc193ee 100644 --- a/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakManager.kt +++ b/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakManager.kt @@ -35,6 +35,8 @@ import com.lambda.interaction.construction.simulation.result.results.BreakResult import com.lambda.interaction.construction.simulation.sim import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.container.selection.StackSelection +import com.lambda.interaction.handler.handlers.PacketLimitHandler +import com.lambda.interaction.handler.handlers.PacketType import com.lambda.interaction.handler.handlers.breaking.BrokenBlockHandler import com.lambda.interaction.handler.handlers.breaking.BrokenBlockHandler.destroyBlock import com.lambda.interaction.handler.handlers.breaking.BrokenBlockHandler.pendingActions @@ -42,8 +44,6 @@ import com.lambda.interaction.handler.handlers.breaking.BrokenBlockHandler.start import com.lambda.interaction.handler.handlers.breaking.RebreakHandler import com.lambda.interaction.handler.handlers.breaking.RebreakHandler.getRebreakPotential import com.lambda.interaction.handler.handlers.breaking.RebreakResult -import com.lambda.interaction.handler.handlers.PacketLimitHandler -import com.lambda.interaction.handler.handlers.PacketType import com.lambda.interaction.manager.Manager import com.lambda.interaction.manager.ManagerUtils.isPosBlocked import com.lambda.interaction.manager.PositionBlocking @@ -139,7 +139,7 @@ object BreakManager : Manager( .lastOrNull { it.breakConfig.doubleBreak || it.type == Secondary }?.context?.itemSelection - ?: StackSelection.EVERYTHING + ?: StackSelection.ANYTHING override val blockedPositions get() = activeInfos.map { it.context.blockPos } + pendingActions.map { it.context.blockPos } diff --git a/src/main/kotlin/com/lambda/module/HudModule.kt b/src/main/kotlin/com/lambda/module/HudModule.kt index 23be5636b..5a4372b02 100644 --- a/src/main/kotlin/com/lambda/module/HudModule.kt +++ b/src/main/kotlin/com/lambda/module/HudModule.kt @@ -19,7 +19,6 @@ package com.lambda.module import com.lambda.config.settings.complex.Bind import com.lambda.gui.Layout -import com.lambda.module.ModuleTag import java.awt.Color abstract class HudModule( diff --git a/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt b/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt index 32c21c1db..517625872 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt @@ -17,7 +17,7 @@ package com.lambda.module.modules.combat -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.hideAllExcept import com.lambda.config.withEdits import com.lambda.context.SafeContext diff --git a/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt b/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt index 39ccad79d..75902dad3 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt @@ -31,7 +31,7 @@ import com.lambda.interaction.container.containers.HotbarContainer import com.lambda.interaction.container.containers.InventoryContainer import com.lambda.interaction.container.containers.OffHandContainer import com.lambda.interaction.container.selection.select -import com.lambda.interaction.handler.handlers.findContainer +import com.lambda.interaction.handler.handlers.findSlot import com.lambda.interaction.handler.handlers.move import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotationTo @@ -51,8 +51,8 @@ import com.lambda.util.combat.CombatUtils.crystalDamage import com.lambda.util.extension.fullHealth import com.lambda.util.math.MathUtils.ceilToInt import com.lambda.util.math.MathUtils.roundToStep -import com.lambda.util.math.distSq import com.lambda.util.math.blockPos +import com.lambda.util.math.distSq import com.lambda.util.math.getHitVec import com.lambda.util.math.minus import com.lambda.util.math.plus @@ -490,17 +490,16 @@ object CrystalAura : Module( ) runSafeAutomated { if (!swap) return@runSafe - val toContainer = + val toContainerSelection = when (swapHand) { Hand.MAIN_HAND -> HotbarContainer Hand.OFF_HAND -> OffHandContainer }.select() - val crystalSlot = findContainer(selection, toContainer) - ?.findSlot(selection) + val crystalSlot = findSlot(selection, toContainerSelection) if (crystalSlot == null) { - if (!selection.move(InventoryContainer.select(), toContainer)) return@runSafe + if (!selection.move(InventoryContainer.select(), toContainerSelection)) return@runSafe } if (swapHand == Hand.MAIN_HAND) { diff --git a/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt b/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt index 2ae087f05..d9962e06e 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt @@ -29,7 +29,7 @@ import com.lambda.event.events.InventoryEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.container.containers.HotbarContainer -import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.selectStack +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module @@ -120,7 +120,7 @@ object KillAura : Module( if (swap) { val selection = - selectStack { + stackSelection { sortedWith { compareByDescending { damageMode.block(this@listen, it.stack) diff --git a/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt b/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt index 3b68b1217..0e140935c 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt @@ -25,9 +25,9 @@ import com.lambda.context.SafeContext import com.lambda.interaction.construction.blueprint.TickingBlueprint.Companion.tickingBlueprint import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.container.selection.ContainerSelection -import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.selectStack +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection import com.lambda.interaction.handler.handlers.FriendHandler.isFriend -import com.lambda.interaction.handler.handlers.findContainer +import com.lambda.interaction.handler.handlers.findStack import com.lambda.module.Module import com.lambda.module.ModuleTag import com.lambda.task.Task @@ -72,14 +72,13 @@ object PlayerTrap : Module( onEnable { task = tickingBlueprint { val selection = - selectStack { - custom { stack, _ -> + stackSelection { + predicate { stack, _ -> stack.item is BlockItem && blocks.contains(stack.item.block) } } - val block = findContainer(selection, ContainerSelection.HOTBAR_AND_INVENTORY) - ?.findStack(selection) + val block = findStack(selection, ContainerSelection.HOTBAR_AND_INVENTORY) ?.item?.block ?: return@tickingBlueprint emptyMap() diff --git a/src/main/kotlin/com/lambda/module/modules/combat/Surround.kt b/src/main/kotlin/com/lambda/module/modules/combat/Surround.kt index 552cad24c..48d5eb31f 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/Surround.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/Surround.kt @@ -24,11 +24,11 @@ import com.lambda.config.withEdits import com.lambda.interaction.construction.blueprint.TickingBlueprint.Companion.tickingBlueprint import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.container.selection.ContainerSelection -import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.selectStack -import com.lambda.interaction.handler.handlers.findContainer +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection +import com.lambda.interaction.handler.handlers.findStack import com.lambda.module.Module -import com.lambda.module.modules.combat.PlayerTrap.getTrapPositions import com.lambda.module.ModuleTag +import com.lambda.module.modules.combat.PlayerTrap.getTrapPositions import com.lambda.task.Task import com.lambda.task.start import com.lambda.task.tasks.build @@ -62,14 +62,13 @@ object Surround : Module( onEnable { task = tickingBlueprint { val selection = - selectStack { - custom { stack, _ -> + stackSelection { + predicate { stack, _ -> stack.item is BlockItem && blocks.contains(stack.item.block) } } - val block = findContainer(selection, ContainerSelection.HOTBAR_AND_INVENTORY) - ?.findStack(selection) + val block = findStack(selection, ContainerSelection.HOTBAR_AND_INVENTORY) ?.item?.block ?: return@tickingBlueprint emptyMap() diff --git a/src/main/kotlin/com/lambda/module/modules/movement/BackTrack.kt b/src/main/kotlin/com/lambda/module/modules/movement/BackTrack.kt index 95abb3962..abcfe1088 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/BackTrack.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/BackTrack.kt @@ -27,8 +27,8 @@ import com.lambda.graphics.mc.renderer.ImmediateRenderer.Companion.immediateRend import com.lambda.graphics.util.DynamicAABB import com.lambda.gui.components.ClickGuiLayout import com.lambda.module.Module -import com.lambda.module.modules.combat.KillAura import com.lambda.module.ModuleTag +import com.lambda.module.modules.combat.KillAura import com.lambda.util.ClientPacket import com.lambda.util.PacketUtils.handlePacketSilently import com.lambda.util.PacketUtils.sendPacketSilently diff --git a/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt b/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt index 2d20319c9..b2ae33205 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt @@ -30,9 +30,11 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.container.containers.HotbarContainer import com.lambda.interaction.container.containers.InventoryContainer -import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.selectStack +import com.lambda.interaction.container.selection.ContainerSelection +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection import com.lambda.interaction.container.selection.select import com.lambda.interaction.handler.handlers.GlideHandler +import com.lambda.interaction.handler.handlers.findStack import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module @@ -84,9 +86,11 @@ object BetterFirework : Module( private var takeoffState = TakeoffState.Idle val ClientPlayerEntity.hasFireworks: Boolean - get() = Items.FIREWORK_ROCKET.select() - .filter(HotbarAndInventoryContainer.stacks) - .isNotEmpty() || offHandStack.item == Items.FIREWORK_ROCKET + get() = + findStack( + Items.FIREWORK_ROCKET.select(), + ContainerSelection.HOTBAR_AND_INVENTORY + ) != null || offHandStack.item == Items.FIREWORK_ROCKET context(_: SafeContext) private val ClientPlayerEntity.canTakeoff: Boolean @@ -167,7 +171,7 @@ object BetterFirework : Module( if (!inventory) return val inventoryMatch = selection.bestMatch(InventoryContainer.slots) ?: return - val hotbarSlot = selectStack { isEmpty() }.bestMatch(HotbarContainer.slots)?.index ?: 8 + val hotbarSlot = stackSelection { isEmpty() }.bestMatch(HotbarContainer.slots)?.index ?: 8 inventoryRequest { swapWithHotbar(inventoryMatch.id, hotbarSlot) diff --git a/src/main/kotlin/com/lambda/module/modules/movement/Blink.kt b/src/main/kotlin/com/lambda/module/modules/movement/Blink.kt index fe555adea..eca5c391e 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/Blink.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/Blink.kt @@ -26,8 +26,8 @@ import com.lambda.graphics.mc.renderer.TickedRenderer.Companion.tickedRenderer import com.lambda.graphics.util.DynamicAABB import com.lambda.gui.components.ClickGuiLayout import com.lambda.module.Module -import com.lambda.module.modules.combat.KillAura import com.lambda.module.ModuleTag +import com.lambda.module.modules.combat.KillAura import com.lambda.threading.runSafe import com.lambda.util.PacketUtils.handlePacketSilently import com.lambda.util.PacketUtils.sendPacketSilently diff --git a/src/main/kotlin/com/lambda/module/modules/movement/ElytraAltitudeControl.kt b/src/main/kotlin/com/lambda/module/modules/movement/ElytraAltitudeControl.kt index d083c4730..9f076b769 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/ElytraAltitudeControl.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/ElytraAltitudeControl.kt @@ -25,8 +25,8 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module -import com.lambda.module.modules.movement.BetterFirework.startFirework import com.lambda.module.ModuleTag +import com.lambda.module.modules.movement.BetterFirework.startFirework import com.lambda.threading.runSafe import com.lambda.util.CommunicationUtils.info import com.lambda.util.SpeedUnit diff --git a/src/main/kotlin/com/lambda/module/modules/movement/TargetStrafe.kt b/src/main/kotlin/com/lambda/module/modules/movement/TargetStrafe.kt index 5c92c6a66..78865afd7 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/TargetStrafe.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/TargetStrafe.kt @@ -28,8 +28,8 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.graphics.mc.renderer.ImmediateRenderer.Companion.immediateRenderer import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotationTo import com.lambda.module.Module -import com.lambda.module.modules.combat.KillAura import com.lambda.module.ModuleTag +import com.lambda.module.modules.combat.KillAura import net.minecraft.entity.effect.StatusEffects import net.minecraft.util.math.Vec3d import java.awt.Color diff --git a/src/main/kotlin/com/lambda/module/modules/movement/TickShift.kt b/src/main/kotlin/com/lambda/module/modules/movement/TickShift.kt index 8cf57f44b..c89e52c07 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/TickShift.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/TickShift.kt @@ -23,8 +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.module.Module -import com.lambda.module.modules.combat.KillAura import com.lambda.module.ModuleTag +import com.lambda.module.modules.combat.KillAura import com.lambda.threading.runConcurrent import com.lambda.util.CommunicationUtils.info import com.lambda.util.PacketUtils.handlePacketSilently diff --git a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFly.kt b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFly.kt index ea2740859..4f1389c3e 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFly.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFly.kt @@ -31,10 +31,10 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.manager.managers.rotating.RotationManager import com.lambda.module.Module +import com.lambda.module.ModuleTag import com.lambda.module.modules.movement.elytrafly.modes.BounceElytraFly import com.lambda.module.modules.movement.elytrafly.modes.GeneralElytraFly import com.lambda.module.modules.movement.elytrafly.modes.GrimControlElytraFly -import com.lambda.module.ModuleTag import com.lambda.threading.runSafe import com.lambda.util.NamedEnum import com.lambda.util.Timer diff --git a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt index f21fef99c..cd1eb6e30 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt @@ -27,7 +27,7 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.container.containers.ArmorContainer import com.lambda.interaction.container.selection.ContainerSelection import com.lambda.interaction.handler.handlers.GlideHandler.ELYTRA_SELECTION -import com.lambda.interaction.handler.handlers.findContainer +import com.lambda.interaction.handler.handlers.findSlot import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest @@ -124,11 +124,7 @@ abstract class ElytraFlyMode( return inventoryRequest.done } - fun findElytra(): Slot? = - findContainer( - ELYTRA_SELECTION, - ContainerSelection.HOTBAR_AND_INVENTORY - )?.findSlot(ELYTRA_SELECTION) + fun findElytra(): Slot? = findSlot(ELYTRA_SELECTION, ContainerSelection.HOTBAR_AND_INVENTORY) protected fun SafeContext.startFly() { player.setFlag(Entity.GLIDING_FLAG_INDEX, true) diff --git a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ObstaclePassingMode.kt b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ObstaclePassingMode.kt index 152671136..f92853026 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ObstaclePassingMode.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ObstaclePassingMode.kt @@ -28,9 +28,9 @@ import com.lambda.threading.runGameScheduled import com.lambda.util.BlockUtils.blockState import com.lambda.util.CommunicationUtils.logError import com.lambda.util.SpeedUnit +import com.lambda.util.math.blockPos import com.lambda.util.math.dist import com.lambda.util.math.distSq -import com.lambda.util.math.blockPos import com.lambda.util.math.isLoaded import com.lambda.util.world.raycast.InteractionMask import com.lambda.util.world.raycast.RayCastUtils.blockResult diff --git a/src/main/kotlin/com/lambda/module/modules/player/AutoEat.kt b/src/main/kotlin/com/lambda/module/modules/player/AutoEat.kt index 1bb5e18e2..a6da8b71d 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/AutoEat.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/AutoEat.kt @@ -17,7 +17,7 @@ package com.lambda.module.modules.player -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.blocks.EatConfig.Companion.reasonEating import com.lambda.config.hideAllExcept import com.lambda.config.withEdits diff --git a/src/main/kotlin/com/lambda/module/modules/player/AutoElytraSwap.kt b/src/main/kotlin/com/lambda/module/modules/player/AutoElytraSwap.kt index aeead9b02..a2fc3f1ed 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/AutoElytraSwap.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/AutoElytraSwap.kt @@ -23,10 +23,11 @@ import com.lambda.interaction.handler.handlers.GlideHandler.CHESTPLATE_SELECTION import com.lambda.interaction.handler.handlers.GlideHandler.ELYTRA_SELECTION import com.lambda.interaction.handler.handlers.GlideHandler.manuallySwapped import com.lambda.interaction.handler.handlers.GlideHandler.swapped +import com.lambda.interaction.handler.handlers.findSlots import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module -import com.lambda.module.modules.combat.AutoArmor import com.lambda.module.ModuleTag +import com.lambda.module.modules.combat.AutoArmor import com.lambda.util.CommunicationUtils.warn import com.lambda.util.player.PlayerUtils.canGlideWithChestPiece import net.minecraft.screen.slot.Slot @@ -45,13 +46,14 @@ object AutoElytraSwap : Module( fun manualSwap(elytra: Boolean): Boolean { val swapSlot = - HotbarAndInventoryContainer.slots.let { slots -> - if (elytra) ELYTRA_SELECTION.filter(slots) - else CHESTPLATE_SELECTION.filter(slots) - }.minByOrNull { it.index } ?: run { - AutoElytraSwap.warn("The required armor piece was not found for AutoElytraSwap to work.") - return false - } + findSlots( + if (elytra) ELYTRA_SELECTION + else CHESTPLATE_SELECTION + ).minByOrNull { it.index } + ?: run { + AutoElytraSwap.warn("The required armor piece was not found for AutoElytraSwap to work.") + return false + } return swapWithChestplate(swapSlot) } diff --git a/src/main/kotlin/com/lambda/module/modules/player/EndermanLook.kt b/src/main/kotlin/com/lambda/module/modules/player/EndermanLook.kt index 73722103a..7aa51038d 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/EndermanLook.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/EndermanLook.kt @@ -17,7 +17,7 @@ package com.lambda.module.modules.player -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.hideAllExcept import com.lambda.config.withEdits import com.lambda.context.SafeContext diff --git a/src/main/kotlin/com/lambda/module/modules/player/FastBreak.kt b/src/main/kotlin/com/lambda/module/modules/player/FastBreak.kt index 20742f245..76a5204fc 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/FastBreak.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/FastBreak.kt @@ -17,7 +17,7 @@ package com.lambda.module.modules.player -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.editSetting import com.lambda.config.editTypedSettings import com.lambda.config.hide diff --git a/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt b/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt index ae88a5bfd..5c4cbd212 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt @@ -17,16 +17,17 @@ package com.lambda.module.modules.player -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.hideAllExcept import com.lambda.config.withEdits import com.lambda.event.events.InventoryEvent import com.lambda.event.events.PlayerEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.container.NestedContainer +import com.lambda.interaction.container.ContainerType import com.lambda.interaction.container.OpenedContainerContext import com.lambda.interaction.container.containers.external.EnderChestContainer -import com.lambda.interaction.handler.handlers.ContainerHandler +import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.containerSelection +import com.lambda.interaction.handler.handlers.findContainer import com.lambda.module.Module import com.lambda.module.ModuleTag import com.lambda.task.Task @@ -37,7 +38,6 @@ import net.minecraft.item.Items import net.minecraft.screen.ScreenHandler import net.minecraft.screen.slot.SlotActionType import java.util.* -import kotlin.collections.fold @Suppress("unused") object InventoryTweaks : Module( @@ -70,12 +70,12 @@ object InventoryTweaks : Module( val targetContainer = if (stack.item == Items.ENDER_CHEST) EnderChestContainer - else { - ContainerHandler.allContainers - .filterIsInstance() - .firstOrNull { container -> container.index == slot.index } - ?: return@listen - } + else findContainer( + containerSelection { + ofAnyType(ContainerType.ShulkerBox) + isNested() + } + ) ?: return@listen event.cancel() diff --git a/src/main/kotlin/com/lambda/module/modules/player/LineLock.kt b/src/main/kotlin/com/lambda/module/modules/player/LineLock.kt index db71f04c9..40d78570d 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/LineLock.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/LineLock.kt @@ -18,7 +18,7 @@ package com.lambda.module.modules.player import com.lambda.config.Group -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.editSetting import com.lambda.config.hideAllExcept import com.lambda.config.withEdits diff --git a/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt b/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt index c56cdcd2b..c5b7b9a13 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt @@ -18,12 +18,12 @@ package com.lambda.module.modules.player import com.lambda.config.Group -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.blocks.BreakConfig.SwingMode import com.lambda.config.editSetting import com.lambda.config.editTypedSettings -import com.lambda.config.entries.Setting.Companion.disabled -import com.lambda.config.entries.Setting.Companion.onValueChange +import com.lambda.config.entries.disabled +import com.lambda.config.entries.onValueChange import com.lambda.config.hide import com.lambda.config.hideAllExcept import com.lambda.config.withEdits diff --git a/src/main/kotlin/com/lambda/module/modules/player/Replay.kt b/src/main/kotlin/com/lambda/module/modules/player/Replay.kt index 50bc048f0..c5135903e 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/Replay.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/Replay.kt @@ -39,8 +39,8 @@ import com.lambda.interaction.manager.managers.rotating.Rotation import com.lambda.interaction.manager.managers.rotating.RotationMode import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module -import com.lambda.module.modules.player.Replay.InputAction.Companion.toAction import com.lambda.module.ModuleTag +import com.lambda.module.modules.player.Replay.InputAction.Companion.toAction import com.lambda.sound.SoundHandler.playSound import com.lambda.util.CommunicationUtils.info import com.lambda.util.CommunicationUtils.logError diff --git a/src/main/kotlin/com/lambda/module/modules/player/RotationLock.kt b/src/main/kotlin/com/lambda/module/modules/player/RotationLock.kt index b7a5ca5af..3fa739d4b 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/RotationLock.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/RotationLock.kt @@ -17,7 +17,7 @@ package com.lambda.module.modules.player -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.editSetting import com.lambda.config.hideAllExcept import com.lambda.config.withEdits diff --git a/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt b/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt index b1e435d49..4e4d97de9 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt @@ -17,7 +17,7 @@ package com.lambda.module.modules.player -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.hide import com.lambda.config.hideAllExcept import com.lambda.config.withEdits @@ -47,7 +47,7 @@ object StackReplenish : Module( .withEdits { hideAllExcept(::inventoryConfig) inventoryConfig.apply { - hide(::disposables, ::accessPriority, ::storePriority) + hide(::disposables) } } diff --git a/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt b/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt index 7f3c5ccc7..ffaecdeb2 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt @@ -17,7 +17,7 @@ package com.lambda.module.modules.player -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.hideAllExcept import com.lambda.config.withEdits import com.lambda.event.events.ContainerEvent diff --git a/src/main/kotlin/com/lambda/module/modules/render/ContainerPreview.kt b/src/main/kotlin/com/lambda/module/modules/render/ContainerPreview.kt index bd3e6360e..0ee377384 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/ContainerPreview.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/ContainerPreview.kt @@ -19,7 +19,7 @@ package com.lambda.module.modules.render import com.lambda.Lambda.mc import com.lambda.config.Tab -import com.lambda.config.entries.Setting.Companion.onValueChange +import com.lambda.config.entries.onValueChange import com.lambda.config.settings.complex.Bind import com.lambda.interaction.container.containers.external.EnderChestContainer import com.lambda.module.Module diff --git a/src/main/kotlin/com/lambda/module/modules/render/Esp.kt b/src/main/kotlin/com/lambda/module/modules/render/Esp.kt index a2cc59eb9..be0d21c83 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/Esp.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/Esp.kt @@ -23,7 +23,7 @@ import com.lambda.config.blocks.EntityColorSettings import com.lambda.config.blocks.EntitySelectionSettings import com.lambda.config.blocks.OutlineSettings import com.lambda.config.blocks.WorldLineSettings -import com.lambda.config.entries.Setting.Companion.onValueChange +import com.lambda.config.entries.onValueChange import com.lambda.config.forEachSetting import com.lambda.config.hide import com.lambda.config.withEdits diff --git a/src/main/kotlin/com/lambda/module/modules/render/Freecam.kt b/src/main/kotlin/com/lambda/module/modules/render/Freecam.kt index 3ec2aae6c..94f24cc09 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/Freecam.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/Freecam.kt @@ -18,9 +18,9 @@ package com.lambda.module.modules.render import com.lambda.Lambda.mc -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.editSetting -import com.lambda.config.entries.Setting.Companion.onValueChange +import com.lambda.config.entries.onValueChange import com.lambda.config.hideAllExcept import com.lambda.config.withEdits import com.lambda.context.SafeContext diff --git a/src/main/kotlin/com/lambda/module/modules/render/Fullbright.kt b/src/main/kotlin/com/lambda/module/modules/render/Fullbright.kt index 9d59f795a..18dfd1a33 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/Fullbright.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/Fullbright.kt @@ -17,7 +17,7 @@ package com.lambda.module.modules.render -import com.lambda.config.entries.Setting.Companion.onValueChange +import com.lambda.config.entries.onValueChange import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listenOnce diff --git a/src/main/kotlin/com/lambda/module/modules/render/LightLevels.kt b/src/main/kotlin/com/lambda/module/modules/render/LightLevels.kt index 011ecaebf..7d3de14c9 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/LightLevels.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/LightLevels.kt @@ -19,6 +19,7 @@ package com.lambda.module.modules.render import com.lambda.config.Group import com.lambda.config.blocks.WorldLineSettings +import com.lambda.config.entries.onValueChange import com.lambda.config.forEachSetting import com.lambda.config.hide import com.lambda.config.withEdits diff --git a/src/main/kotlin/com/lambda/module/modules/render/RadiusESP.kt b/src/main/kotlin/com/lambda/module/modules/render/RadiusESP.kt index 6e844c124..455ea2ebb 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/RadiusESP.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/RadiusESP.kt @@ -19,7 +19,7 @@ package com.lambda.module.modules.render import com.lambda.config.Group import com.lambda.config.blocks.WorldLineSettings -import com.lambda.config.entries.Setting.Companion.onValueChange +import com.lambda.config.entries.onValueChange import com.lambda.config.forEachSetting import com.lambda.config.hide import com.lambda.config.withEdits diff --git a/src/main/kotlin/com/lambda/module/modules/render/Search.kt b/src/main/kotlin/com/lambda/module/modules/render/Search.kt index 7d941f1f6..a0b0f134d 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/Search.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/Search.kt @@ -21,7 +21,7 @@ import com.lambda.config.Group import com.lambda.config.blocks.ScreenLineSettings import com.lambda.config.blocks.WorldLineSettings import com.lambda.config.editTypedSettings -import com.lambda.config.entries.Setting.Companion.onValueChange +import com.lambda.config.entries.onValueChange import com.lambda.config.forEachSetting import com.lambda.config.hide import com.lambda.config.settings.collections.onDeselect diff --git a/src/main/kotlin/com/lambda/module/modules/render/ViewModel.kt b/src/main/kotlin/com/lambda/module/modules/render/ViewModel.kt index 82239bbfa..b109d3b5a 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/ViewModel.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/ViewModel.kt @@ -19,7 +19,7 @@ package com.lambda.module.modules.render import com.lambda.Lambda.mc import com.lambda.config.Tab -import com.lambda.config.entries.Setting.Companion.onValueChange +import com.lambda.config.entries.onValueChange import com.lambda.event.events.ButtonEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen diff --git a/src/main/kotlin/com/lambda/module/modules/render/XRay.kt b/src/main/kotlin/com/lambda/module/modules/render/XRay.kt index 4b58bae51..426526ee8 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/XRay.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/XRay.kt @@ -18,7 +18,7 @@ package com.lambda.module.modules.render import com.lambda.Lambda.mc -import com.lambda.config.entries.Setting.Companion.onValueChange +import com.lambda.config.entries.onValueChange import com.lambda.config.settings.collections.onDeselect import com.lambda.config.settings.collections.onSelect import com.lambda.context.SafeContext diff --git a/src/main/kotlin/com/lambda/module/modules/world/AirPlace.kt b/src/main/kotlin/com/lambda/module/modules/world/AirPlace.kt index 952a1bda5..7ac8b39a9 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/AirPlace.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/AirPlace.kt @@ -19,7 +19,7 @@ package com.lambda.module.modules.world import com.lambda.Lambda.mc import com.lambda.config.Group -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.hideAllExcept import com.lambda.config.settings.complex.Bind import com.lambda.config.withEdits diff --git a/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt b/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt index a6665179c..59eb6e0ca 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt @@ -19,7 +19,7 @@ package com.lambda.module.modules.world import baritone.api.pathing.goals.GoalBlock import com.lambda.config.Group -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.blocks.WorldLineSettings import com.lambda.config.editSetting import com.lambda.config.forEachSetting @@ -36,16 +36,19 @@ import com.lambda.graphics.mc.renderer.ImmediateRenderer.Companion.immediateRend import com.lambda.graphics.util.DirectionMask import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.container.containers.HotbarContainer -import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.selectStack +import com.lambda.interaction.container.selection.ContainerSelection +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection +import com.lambda.interaction.container.selection.select import com.lambda.interaction.handler.handlers.BaritoneHandler +import com.lambda.interaction.handler.handlers.findSlot import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module +import com.lambda.module.ModuleTag import com.lambda.module.modules.world.AutoPortal.PosHandler.currAnchorPos import com.lambda.module.modules.world.AutoPortal.PosHandler.obiPositions import com.lambda.module.modules.world.AutoPortal.PosHandler.portalPositions import com.lambda.module.modules.world.AutoPortal.PosHandler.prevAnchorPos -import com.lambda.module.ModuleTag import com.lambda.task.Task import com.lambda.task.start import com.lambda.task.tasks.build @@ -322,12 +325,12 @@ object AutoPortal : Module( return } - val sel = selectStack(1) { isItem() } + val selection = stackSelection(1) { isItem() } - val hotbarStack = sel.filter(HotbarContainer.slots).firstOrNull() - if (hotbarStack != null) { + val hotbarSlot = findSlot(selection, HotbarContainer.select()) + if (hotbarSlot != null) { val request = - hotbarRequest(hotbarStack.index) { + hotbarRequest(hotbarSlot.index) { keepTicks(0) }.submit(false) if (request.done) block() @@ -335,7 +338,7 @@ object AutoPortal : Module( } val invSlot = - if (inventory) sel.filter(HotbarAndInventoryContainer.slots).firstOrNull() + if (inventory) findSlot(selection, ContainerSelection.HOTBAR_AND_INVENTORY) else null if (invSlot == null) { failure("No Flint and Steel!") diff --git a/src/main/kotlin/com/lambda/module/modules/world/AutoVillagerCycle.kt b/src/main/kotlin/com/lambda/module/modules/world/AutoVillagerCycle.kt index 78352b15c..6ee1bc20c 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/AutoVillagerCycle.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/AutoVillagerCycle.kt @@ -18,7 +18,7 @@ package com.lambda.module.modules.world import com.lambda.config.Group -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.hideAllExcept import com.lambda.config.settings.complex.Bind import com.lambda.config.settings.complex.KeybindSetting.Companion.onPress diff --git a/src/main/kotlin/com/lambda/module/modules/world/HighwayTools.kt b/src/main/kotlin/com/lambda/module/modules/world/HighwayTools.kt index 7ca52d72b..82f0aee02 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/HighwayTools.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/HighwayTools.kt @@ -17,7 +17,7 @@ package com.lambda.module.modules.world -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.editTypedSettings import com.lambda.config.withEdits import com.lambda.interaction.construction.blueprint.Blueprint.Companion.emptyStructure diff --git a/src/main/kotlin/com/lambda/module/modules/world/MapDownloader.kt b/src/main/kotlin/com/lambda/module/modules/world/MapDownloader.kt index 5f9b6e324..608deef16 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/MapDownloader.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/MapDownloader.kt @@ -19,6 +19,8 @@ package com.lambda.module.modules.world import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.container.selection.ContainerSelection +import com.lambda.interaction.handler.handlers.findStacks import com.lambda.module.Module import com.lambda.module.ModuleTag import com.lambda.util.FileUtils.locationBoundDirectory @@ -42,7 +44,8 @@ object MapDownloader : Module( listen { val mapStates = entitySearch(128.0) .mapNotNull { FilledMapItem.getMapState(it.heldItemStack, world) } + - PlayerContainer.stacks.mapNotNull { FilledMapItem.getMapState(it, world) } + findStacks(containerSelection = ContainerSelection.PLAYER) + .mapNotNull { FilledMapItem.getMapState(it, world) } mapStates.forEach { map -> val name = map.hash diff --git a/src/main/kotlin/com/lambda/module/modules/world/Nuker.kt b/src/main/kotlin/com/lambda/module/modules/world/Nuker.kt index de229a0ff..d19eec4f0 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/Nuker.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/Nuker.kt @@ -17,9 +17,9 @@ package com.lambda.module.modules.world -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.editTypedSettings -import com.lambda.config.entries.Setting.Companion.onValueChange +import com.lambda.config.entries.onValueChange import com.lambda.config.withEdits import com.lambda.context.SafeContext import com.lambda.interaction.construction.blueprint.TickingBlueprint.Companion.tickingBlueprint diff --git a/src/main/kotlin/com/lambda/module/modules/world/Printer.kt b/src/main/kotlin/com/lambda/module/modules/world/Printer.kt index 016ed4a3a..a8284547c 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/Printer.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/Printer.kt @@ -19,9 +19,9 @@ package com.lambda.module.modules.world import com.lambda.Lambda.mc import com.lambda.config.Group -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.blocks.WorldLineSettings -import com.lambda.config.entries.Setting.Companion.onValueChange +import com.lambda.config.entries.onValueChange import com.lambda.config.forEachSetting import com.lambda.config.hideAllExcept import com.lambda.config.withEdits 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 85f99e94f..7354bf66e 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/Scaffold.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/Scaffold.kt @@ -17,7 +17,7 @@ package com.lambda.module.modules.world -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.editSetting import com.lambda.config.editTypedSettings import com.lambda.config.hide @@ -31,7 +31,7 @@ import com.lambda.interaction.construction.simulation.context.BuildContext import com.lambda.interaction.construction.simulation.sim import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.container.containers.HotbarContainer -import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.selectStack +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection import com.lambda.interaction.manager.managers.interacting.interactRequest import com.lambda.module.Module import com.lambda.module.ModuleTag @@ -74,8 +74,8 @@ object Scaffold : Module( } listen { - val selection = selectStack { - custom { stack, _ -> + val selection = stackSelection { + predicate { stack, _ -> stack.blockItem.let { blockItem -> blockItem != null && blockItem.block !in blacklistedBlocks } } } 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 06f603307..4c29df3f8 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt @@ -20,9 +20,9 @@ package com.lambda.module.modules.world import baritone.api.pathing.goals.GoalBlock import com.lambda.Lambda.mc import com.lambda.config.Tab -import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.editTypedSettings -import com.lambda.config.entries.Setting.Companion.onValueChange +import com.lambda.config.entries.onValueChange import com.lambda.config.hide import com.lambda.config.hideBlock import com.lambda.config.settings.complex.Bind @@ -42,7 +42,13 @@ import com.lambda.interaction.container.containers.HotbarContainer import com.lambda.interaction.container.containers.InventoryContainer import com.lambda.interaction.container.containers.OffHandContainer import com.lambda.interaction.container.containers.external.EnderChestContainer +import com.lambda.interaction.container.selection.ContainerSelection +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection import com.lambda.interaction.handler.handlers.BaritoneHandler +import com.lambda.interaction.handler.handlers.findContainers +import com.lambda.interaction.handler.handlers.findSlots +import com.lambda.interaction.handler.handlers.findStack +import com.lambda.interaction.handler.handlers.findStacks import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.interaction.manager.managers.rotating.Rotation @@ -454,7 +460,9 @@ object StashMover : Module( MoverState.DispensingPearl -> handleDispensingPearl() MoverState.AwaitingPearl -> checkTimerProgress(MoverState.DispensingPearl, pearlButtonTimeout) { - if (HotbarAndInventoryContainer.stacks.any { it.item === Items.ENDER_PEARL }) { + val hasPearl = findContainers(containerSelection = ContainerSelection.HOTBAR_AND_INVENTORY) + .any { it.stacks.any { it.item == Items.ENDER_PEARL } } + if (hasPearl) { pearlThrown = false moverState = MoverState.ThrowingPearl true @@ -501,7 +509,7 @@ object StashMover : Module( if (moverState != MoverState.AwaitingTeleport) return@listen val packet = event.packet if (packet !is PlayerPositionLookS2CPacket) return@listen - if (finished && HotbarAndInventoryContainer.stacks.all { it.isEmpty }) { + if (finished && findStacks(containerSelection = ContainerSelection.HOTBAR_AND_INVENTORY).all { it.isEmpty }) { success(finishedMessage) return@listen } @@ -520,12 +528,17 @@ object StashMover : Module( moverState = MoverState.OpeningPullContainer return } - if (!moveFromContainerToContainer(screenHandler.containerSlots, HotbarAndInventoryContainer.stacks)) return + val move = + moveFromContainerToContainer( + screenHandler.containerSlots, + findStacks(containerSelection = ContainerSelection.HOTBAR_AND_INVENTORY).toList() + ) + if (!move) return pullContainer?.let { container -> if (screenHandler.containerStacks.all { it.isEmpty }) { pullContainers.remove(container) pulledContainers.add(container) - if (HotbarAndInventoryContainer.stacks.any { it.isEmpty }) { + if (findStack(stackSelection { isEmpty() }, ContainerSelection.HOTBAR_AND_INVENTORY) != null) { moverState = MoverState.OpeningPullContainer return } @@ -543,7 +556,12 @@ object StashMover : Module( moverState = MoverState.OpeningPutEnderChest return } - if (moveFromContainerToContainer(HotbarAndInventoryContainer.slots, screenHandler.containerStacks)) { + val move = + moveFromContainerToContainer( + findSlots(containerSelection = ContainerSelection.HOTBAR_AND_INVENTORY).toList(), + screenHandler.containerStacks + ) + if (move) { moverState = MoverState.OpeningPullContainer } } @@ -580,7 +598,7 @@ object StashMover : Module( rotation(rotation) }.submit() if (!rotationRequest.done || rotation dist RotationManager.serverRotation > 0.001) return - val throwSlots = HotbarAndInventoryContainer.slots.filter { !it.stack.isEmpty } + val throwSlots = findSlots(stackSelection { notEmpty() }, ContainerSelection.HOTBAR_AND_INVENTORY).toList() if (throwSlots.isNotEmpty()) { val inventoryRequest = inventoryRequest(settleForLess = true) { throwSlots.forEach { slot -> @@ -597,12 +615,17 @@ object StashMover : Module( moverState = MoverState.OpeningPutContainer return } - if (!moveFromContainerToContainer(HotbarAndInventoryContainer.slots, screenHandler.containerStacks)) return + val move = + moveFromContainerToContainer( + findSlots(containerSelection = ContainerSelection.HOTBAR_AND_INVENTORY).toList(), + screenHandler.containerStacks + ) + if (!move) return putContainer?.let { container -> if (screenHandler.containerStacks.all { !it.isEmpty }) { putContainers.remove(container) filledContainers.add(container) - if (HotbarAndInventoryContainer.stacks.any { !it.isEmpty }) { + if (findStack(stackSelection { notEmpty() }, ContainerSelection.HOTBAR_AND_INVENTORY) != null) { moverState = MoverState.OpeningPutContainer return } @@ -616,7 +639,12 @@ object StashMover : Module( moverState = MoverState.OpeningPullEnderChest return } - if (moveFromContainerToContainer(screenHandler.containerSlots, HotbarAndInventoryContainer.stacks)) { + val move = + moveFromContainerToContainer( + screenHandler.containerSlots, + findStacks(containerSelection = ContainerSelection.HOTBAR_AND_INVENTORY).toList() + ) + if (move) { putOrThrowItems() } } @@ -659,7 +687,7 @@ object StashMover : Module( if (pearlThrown) { if (!player.offHandStack.isEmpty) { - if (HotbarAndInventoryContainer.stacks.none { it.isEmpty }) { + if (findStack(stackSelection { isEmpty() }, ContainerSelection.HOTBAR_AND_INVENTORY) == null) { failWithLog("No free slots to return the offhand stack to!", ::failure) return } diff --git a/src/main/kotlin/com/lambda/network/CapeHandler.kt b/src/main/kotlin/com/lambda/network/CapeHandler.kt index e92ba1540..241c1f653 100644 --- a/src/main/kotlin/com/lambda/network/CapeHandler.kt +++ b/src/main/kotlin/com/lambda/network/CapeHandler.kt @@ -21,7 +21,7 @@ import com.lambda.Lambda.LOG import com.lambda.Lambda.mc import com.lambda.config.Config import com.lambda.config.categories.SecretsCategory -import com.lambda.config.entries.Setting.Companion.onValueChangeUnsafe +import com.lambda.config.entries.onValueChangeUnsafe import com.lambda.core.Loadable import com.lambda.event.events.WorldEvent import com.lambda.event.listener.SafeListener.Companion.listen diff --git a/src/main/kotlin/com/lambda/task/tasks/AcquirePlacedBlockTask.kt b/src/main/kotlin/com/lambda/task/tasks/AcquirePlacedBlockTask.kt index 6602dc3f6..3547c047d 100644 --- a/src/main/kotlin/com/lambda/task/tasks/AcquirePlacedBlockTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/AcquirePlacedBlockTask.kt @@ -21,8 +21,8 @@ import com.lambda.context.Automated import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.container.containers.ArmorContainer.findSlot import com.lambda.interaction.container.selection.select -import com.lambda.interaction.handler.handlers.findSlots import com.lambda.task.Task import com.lambda.task.Task.Ta5kBuilder import com.lambda.task.tasks.wrappers.softFail @@ -48,15 +48,12 @@ class AcquirePlacedBlockTask @Ta5kBuilder internal constructor( init { listen { runSafeAutomated { - block.item - .select(1) - .findSlots() - .firstOrNull()?.let { slot -> - PlaceContainerTask(slot, this) - .onSuccess { success(it) } - .execute(this@AcquirePlacedBlockTask) - return@listen - } + findSlot(block.item.select()).let { slot -> + PlaceContainerTask(slot, this) + .onSuccess { success(it) } + .execute(this@AcquirePlacedBlockTask) + return@listen + } } failure("No $block found in range or hotbar/inventory!") diff --git a/src/main/kotlin/com/lambda/task/tasks/AcquireStackTask.kt b/src/main/kotlin/com/lambda/task/tasks/AcquireStackTask.kt index e8db24cd2..0f95dc667 100644 --- a/src/main/kotlin/com/lambda/task/tasks/AcquireStackTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/AcquireStackTask.kt @@ -21,7 +21,7 @@ import com.lambda.context.Automated import com.lambda.context.SafeContext import com.lambda.interaction.container.containers.HotbarContainer import com.lambda.interaction.container.selection.StackSelection -import com.lambda.interaction.handler.handlers.findContainer +import com.lambda.interaction.container.selection.select import com.lambda.task.Task import com.lambda.task.Task.Ta5kBuilder import com.lambda.threading.runSafeAutomated @@ -46,11 +46,9 @@ class AcquireStackTask @Ta5kBuilder internal constructor( override fun SafeContext.onStart() { runSafeAutomated { - selection.findContainer() - ?.let { transfer(selection, it, HotbarContainer) } - ?.onSuccess { slot -> success(slot) } - ?.start() - ?: failure(NoContainerFound(selection)) // ToDo: Create crafting path + selection.transfer(toSelection = HotbarContainer.select()) + .onSuccess { slot -> success(slot) } + .start() } } } diff --git a/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt b/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt index 5ae1d61ba..cae228e6a 100644 --- a/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt @@ -22,7 +22,10 @@ import com.lambda.context.Automated import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.container.selection.ContainerSelection +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection import com.lambda.interaction.handler.handlers.BaritoneHandler +import com.lambda.interaction.handler.handlers.findStack import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.task.Task import com.lambda.task.Task.Ta5kBuilder @@ -64,7 +67,7 @@ class CollectDropsTask @Ta5kBuilder internal constructor( return@listen } - if (HotbarAndInventoryContainer.stacks.none { it.isEmpty }) { + if (findStack(stackSelection { isEmpty() }, ContainerSelection.HOTBAR_AND_INVENTORY) == null) { val stackToThrow = player.currentScreenHandler.playerSlots.firstOrNull { it.stack.item in inventoryConfig.disposables diff --git a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt index fc674267b..a8036ebe4 100644 --- a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt @@ -25,6 +25,7 @@ import com.lambda.interaction.container.Container import com.lambda.interaction.container.ExternalContainer import com.lambda.interaction.container.OpenedContainerContext import com.lambda.interaction.container.selection.ContainerSelection +import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.mutate import com.lambda.interaction.container.selection.StackSelection import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.mutate import com.lambda.interaction.handler.handlers.findContainers @@ -40,50 +41,58 @@ import net.minecraft.screen.slot.Slot context(automated: Automated) fun transfer( selection: StackSelection, - fromSelection: ContainerSelection = ContainerSelection.EVERYTHING, + fromSelection: ContainerSelection = ContainerSelection.ACCESSED, toSelection: ContainerSelection, - toStack: StackSelection = StackSelection.EVERYTHING + toStack: StackSelection = StackSelection.ANYTHING ) = ContainerTransferTask(selection, fromSelection, toSelection, toStack, automated) @Ta5kBuilder @JvmName("transferExt") context(automated: Automated) fun StackSelection.transfer( - fromSelection: ContainerSelection = ContainerSelection.EVERYTHING, + fromSelection: ContainerSelection = ContainerSelection.ACCESSED, toSelection: ContainerSelection, - toStack: StackSelection = StackSelection.EVERYTHING + toStack: StackSelection = StackSelection.ANYTHING ) = transfer(this, fromSelection, toSelection, toStack) class ContainerTransferTask @Ta5kBuilder internal constructor( - private val selection: StackSelection, + private val fromStack: StackSelection, private val fromSelection: ContainerSelection, private val toSelection: ContainerSelection, private val toStack: StackSelection, automated: Automated ) : Task(), Automated by automated { - override val name = "Transferring $selection from $fromSelection to $toSelection" + override val name = "Transferring $fromStack from $fromSelection to $toSelection" override fun SafeContext.onStart() { - val singleSelection = selection.mutate(count = if (selection.count == 0) 0 else 1) + val singleSelection = fromStack.mutate(count = if (fromStack.count == 0) 0 else 1) - val fromContainers = findContainers(singleSelection, fromSelection).toList() - val toContainers = findContainers(toStack, toSelection) - .filter { toStack !== StackSelection.EVERYTHING || it.spaceLeft(singleSelection) > 0 } - .toList() + val fromContainers = + findContainers( + fromSelection.mutate { + hasStack(fromStack) + } + ).toList() + val toContainers = + findContainers( + toSelection.mutate { + hasStack(toStack) + } + ).toList() if (fromContainers.isEmpty()) { - failure(IllegalStateException("Could not find any container to pull $selection from")) + failure(IllegalStateException("Could not find any container to pull $fromStack from")) return } if (toContainers.isEmpty()) { - failure(IllegalStateException("Could not find any container with space to push $selection to")) + failure(IllegalStateException("Could not find any container with space to push $fromStack to")) return } - if (selection.count > 0) { + if (fromStack.count > 0) { val totalAvailable = fromContainers.sumOf { it.count(singleSelection) } - if (totalAvailable < selection.count) { - failure(IllegalStateException("Not enough items across all containers to pull $selection")) + if (totalAvailable < fromStack.count) { + failure(IllegalStateException("Not enough items across all containers to pull $fromStack")) return } } @@ -105,7 +114,7 @@ class ContainerTransferTask @Ta5kBuilder internal constructor( else -> taskOrNull { fromContainer.access() } .then { fromCtx -> - transfer(selection, fromSelection, ContainerSelection.HOTBAR_AND_INVENTORY) + transfer(fromStack, fromSelection, ContainerSelection.HOTBAR_AND_INVENTORY) .thenOrNull { fromCtx?.close() } } .thenOrNull { toContainer.access() } @@ -114,7 +123,7 @@ class ContainerTransferTask @Ta5kBuilder internal constructor( } /** - * Opens [containerToOpen], transfers [selection] from [from] to [to], then closes the opened container. + * Opens [containerToOpen], transfers [fromStack] from [from] to [to], then closes the opened container. * * Used when exactly one of the two containers is external and needs to be accessed. */ @@ -128,14 +137,14 @@ class ContainerTransferTask @Ta5kBuilder internal constructor( ?.then { ctx -> transferAndClose(ctx, fromSelection, toSelection) } /** - * Transfers [selection] from [from] to [to], then closes the container via [ctx]. + * Transfers [fromStack] from [from] to [to], then closes the container via [ctx]. */ @Ta5kBuilder private fun transferAndClose( ctx: OpenedContainerContext?, fromSelection: ContainerSelection, toSelection: ContainerSelection - ) = transfer(selection, fromSelection, toSelection) + ) = transfer(fromStack, fromSelection, toSelection) .thenOrNull { slot -> ctx ?.close() @@ -154,8 +163,8 @@ class ContainerTransferTask @Ta5kBuilder internal constructor( init { listen { runSafeAutomated { - while (selection.count == 0 || transferred < selection.count) { - val currentSelection = selection.mutate(count = if (selection.count == 0) 0 else selection.count - transferred) + while (fromStack.count == 0 || transferred < fromStack.count) { + val currentSelection = fromStack.mutate(count = if (fromStack.count == 0) 0 else fromStack.count - transferred) var fromSlot: Slot? = null var toSlot: Slot? = null @@ -178,7 +187,7 @@ class ContainerTransferTask @Ta5kBuilder internal constructor( if (fromSlot == null || toSlot == null || fromContainer == null || toContainer == null) { lastSlot?.let { slot -> - if (selection.count == 0 || transferred >= selection.count) { + if (fromStack.count == 0 || transferred >= fromStack.count) { success(slot) return@listen } @@ -187,7 +196,7 @@ class ContainerTransferTask @Ta5kBuilder internal constructor( return@listen } - val moveCount = minOf(fromSlot.stack.count, if (selection.count == 0) Int.MAX_VALUE else selection.count - transferred) + val moveCount = minOf(fromSlot.stack.count, if (fromStack.count == 0) Int.MAX_VALUE else fromStack.count - transferred) if (!fromContainer.swap(fromSlot, toSlot, toContainer)) { checkFail() return@listen @@ -197,7 +206,7 @@ class ContainerTransferTask @Ta5kBuilder internal constructor( lastSlot = toSlot // count == 0 means single stack transfer - if (selection.count == 0) { + if (fromStack.count == 0) { success(toSlot) return@listen } @@ -209,7 +218,7 @@ class ContainerTransferTask @Ta5kBuilder internal constructor( } private fun checkFail() { - failure(IllegalStateException("Unable to access $selection")) + failure(IllegalStateException("Unable to access $fromStack")) } } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/task/tasks/EatTask.kt b/src/main/kotlin/com/lambda/task/tasks/EatTask.kt index 088a8e79c..1e987d79e 100644 --- a/src/main/kotlin/com/lambda/task/tasks/EatTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/EatTask.kt @@ -25,6 +25,7 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.container.containers.HotbarContainer import com.lambda.interaction.container.containers.InventoryContainer +import com.lambda.interaction.container.selection.select import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest import com.lambda.task.Task import com.lambda.task.Task.Ta5kBuilder @@ -72,7 +73,12 @@ class EatTask @Ta5kBuilder internal constructor( } else { if (InventoryContainer.slots.any { selection.matches(it) }) { runSafeAutomated { - transfer(selection, InventoryContainer, HotbarContainer) + transfer( + selection, + InventoryContainer.select(), + HotbarContainer.select() + ).start() + return@listen } } if (holdingUse) { From d9efa6d6d11e6ac3be8f014d06aaeb02d4c9cbcc Mon Sep 17 00:00:00 2001 From: beanbag44 <107891830+beanbag44@users.noreply.github.com> Date: Sat, 12 Sep 2026 20:20:27 +0100 Subject: [PATCH 16/43] improvements --- .../EnderChestContainerSerializer.kt | 21 +- .../lambda/interaction/container/Container.kt | 7 +- ...ntainerMarker.kt => ContainerDslMarker.kt} | 2 +- .../container/OpenContainerTask.kt | 10 - .../external/EnderChestContainer.kt | 4 +- .../external/ShulkerBoxContainer.kt | 8 +- .../container/selection/ContainerSelection.kt | 73 +++- .../container/selection/StackSelection.kt | 128 +++---- .../handler/handlers/ContainerHandler.kt | 38 +- .../module/modules/player/InventoryTweaks.kt | 5 +- src/main/kotlin/com/lambda/task/Task.kt | 84 ++--- .../com/lambda/task/tasks/AcquireStackTask.kt | 7 +- .../kotlin/com/lambda/task/tasks/BuildTask.kt | 4 +- .../com/lambda/task/tasks/CollectDropsTask.kt | 58 +-- .../task/tasks/ContainerTransferTask.kt | 332 +++++++++++------- .../lambda/task/tasks/wrappers/ActionTasks.kt | 2 +- 16 files changed, 463 insertions(+), 320 deletions(-) rename src/main/kotlin/com/lambda/interaction/container/{ContainerMarker.kt => ContainerDslMarker.kt} (95%) diff --git a/src/main/kotlin/com/lambda/config/serializers/EnderChestContainerSerializer.kt b/src/main/kotlin/com/lambda/config/serializers/EnderChestContainerSerializer.kt index a62b83044..8601077c5 100644 --- a/src/main/kotlin/com/lambda/config/serializers/EnderChestContainerSerializer.kt +++ b/src/main/kotlin/com/lambda/config/serializers/EnderChestContainerSerializer.kt @@ -52,16 +52,21 @@ object EnderChestContainerSerializer : Serializer(EnderChes } object EnderChestContainerDeserializer : Deserializer(EnderChestContainer::class.java) { - override fun deserialize(p: JsonParser, ctxt: DeserializationContext): EnderChestContainer { + override fun deserialize(p: JsonParser?, ctxt: DeserializationContext?): EnderChestContainer? { + throw initFromJsonException("EnderChestContainer") + } + + override fun deserialize(p: JsonParser, ctxt: DeserializationContext, intoValue: EnderChestContainer): EnderChestContainer { val root = p.readValueAsTree() val stackArray = root.get("Stacks") as? ArrayNode - val stacks = stackArray?.mapNotNull { element -> - ItemStack.CODEC - .parse(JsonOps.UNCOMPRESSED, element) - .result() - .orElse(null) - } ?: emptyList() - + val stacks = + stackArray?.mapNotNull { element -> + ItemStack.CODEC + .parse(JsonOps.UNCOMPRESSED, element) + .result() + .orElse(null) + } ?: emptyList() + EnderChestContainer.update(stacks) EnderChestContainer.scanStacksForNestedContainers() return EnderChestContainer diff --git a/src/main/kotlin/com/lambda/interaction/container/Container.kt b/src/main/kotlin/com/lambda/interaction/container/Container.kt index 4195e1134..6ffecb4a6 100644 --- a/src/main/kotlin/com/lambda/interaction/container/Container.kt +++ b/src/main/kotlin/com/lambda/interaction/container/Container.kt @@ -121,8 +121,7 @@ abstract class Container( @Ta5kBuilder context(automated: Automated) - open fun access(): OpenContainerTask<*>? = - PlayerOpenContainerTask(description, ::isAccessed) + open fun access(): OpenContainerTask<*>? = null open fun count(selection: StackSelection) = slots.takeUnless { it.isEmpty() }?.let { selection.filter(it).count } @@ -145,12 +144,12 @@ abstract class Container( selection: StackSelection, toContainer: Container, toStackSelection: StackSelection = StackSelection.ANYTHING - ) = Pair(findSlot(selection), toContainer.findReplaceSlot(toStackSelection)) + ) = Pair(selection.filter(slots).firstOrNull(), toContainer.findReplaceSlot(toStackSelection)) context(_: Automated) open fun findReplaceSlot( selection: StackSelection = StackSelection.ANYTHING - ) = findSlots(selection) + ) = selection.filter(slots) .sortedWith(replaceSorter) .firstOrNull() diff --git a/src/main/kotlin/com/lambda/interaction/container/ContainerMarker.kt b/src/main/kotlin/com/lambda/interaction/container/ContainerDslMarker.kt similarity index 95% rename from src/main/kotlin/com/lambda/interaction/container/ContainerMarker.kt rename to src/main/kotlin/com/lambda/interaction/container/ContainerDslMarker.kt index 9b56dc2c7..9269e464e 100644 --- a/src/main/kotlin/com/lambda/interaction/container/ContainerMarker.kt +++ b/src/main/kotlin/com/lambda/interaction/container/ContainerDslMarker.kt @@ -18,4 +18,4 @@ package com.lambda.interaction.container @DslMarker -annotation class ContainerMarker \ No newline at end of file +annotation class ContainerDslMarker \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/container/OpenContainerTask.kt b/src/main/kotlin/com/lambda/interaction/container/OpenContainerTask.kt index 61a07e5d7..27cc94c61 100644 --- a/src/main/kotlin/com/lambda/interaction/container/OpenContainerTask.kt +++ b/src/main/kotlin/com/lambda/interaction/container/OpenContainerTask.kt @@ -17,7 +17,6 @@ package com.lambda.interaction.container -import com.lambda.context.SafeContext import com.lambda.task.Task import net.minecraft.text.Text @@ -25,13 +24,4 @@ abstract class OpenContainerTask @Ta5kBuilder intern description: Text ) : Task() { override val name = "accessing container: ${description.string}" -} - -class PlayerOpenContainerTask @Ta5kBuilder internal constructor( - description: Text, - private val isAccessed: () -> Boolean -) : OpenContainerTask(description) { - override fun SafeContext.onStart() { - success(BasicOpenedContainerContext(isAccessed)) - } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/container/containers/external/EnderChestContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/external/EnderChestContainer.kt index 925cf1aaa..da40583bb 100644 --- a/src/main/kotlin/com/lambda/interaction/container/containers/external/EnderChestContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/container/containers/external/EnderChestContainer.kt @@ -25,8 +25,10 @@ import com.lambda.interaction.container.ContainerType import com.lambda.interaction.container.ExternalContainer import com.lambda.interaction.container.OpenContainerTask import com.lambda.interaction.container.OpenedContainerContext +import com.lambda.interaction.container.containers.HotbarContainer import com.lambda.interaction.container.selection.select import com.lambda.interaction.handler.handlers.ContainerHandler.lastInteractedBlockEntity +import com.lambda.interaction.handler.handlers.findSlot import com.lambda.task.Task.Ta5kBuilder import com.lambda.task.tasks.acquireStack import com.lambda.task.tasks.breakAndCollect @@ -83,7 +85,7 @@ object EnderChestContainer : Container(ContainerType.EnderChest), ExternalContai }, onFailure = { acquireStack(Items.ENDER_CHEST.select(1)) - .then { placeContainer(it) } + .then { placeContainer(findSlot(it, HotbarContainer.select())) } .then { pos -> openContainer(pos).onSuccess { success(OpenedEnderChestContext(pos)) diff --git a/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt index 99ec23127..9a61e1a27 100644 --- a/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt @@ -30,6 +30,8 @@ import com.lambda.interaction.container.containers.HotbarContainer import com.lambda.interaction.container.selection.ContainerSelection import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection import com.lambda.interaction.container.selection.select +import com.lambda.interaction.handler.handlers.ContainerSearchScope +import com.lambda.interaction.handler.handlers.findSlot import com.lambda.task.Task.Ta5kBuilder import com.lambda.task.tasks.breakAndCollect import com.lambda.task.tasks.openContainer @@ -79,10 +81,10 @@ data class ShulkerBoxContainer( ) : OpenContainerTask(description), Automated by automated { override fun SafeContext.onStart() { transfer( - stackSelection { inIndex(index); isItem(shulkerItem) }, + stackSelection { predicate { _, slot -> slot?.index == index }; isItem(shulkerItem) }, containedIn.select(), HotbarContainer.select() - ).then { slot -> placeContainer(slot) } + ).then { result -> placeContainer(findSlot(result.stackSelection, result.containerSelection)) } .then { pos -> openContainer(pos).onSuccess { success(OpenedShulkerBoxContext(pos, containedIn)) @@ -109,7 +111,7 @@ data class ShulkerBoxContainer( sortedByBestContentMatch(stacks) }.transfer( ContainerSelection.HOTBAR_AND_INVENTORY, - fromContainer.select() + fromContainer.select(ContainerSearchScope.Loaded) ) } } diff --git a/src/main/kotlin/com/lambda/interaction/container/selection/ContainerSelection.kt b/src/main/kotlin/com/lambda/interaction/container/selection/ContainerSelection.kt index 9fbb794ea..3316a6afb 100644 --- a/src/main/kotlin/com/lambda/interaction/container/selection/ContainerSelection.kt +++ b/src/main/kotlin/com/lambda/interaction/container/selection/ContainerSelection.kt @@ -18,7 +18,7 @@ package com.lambda.interaction.container.selection import com.lambda.interaction.container.Container -import com.lambda.interaction.container.ContainerMarker +import com.lambda.interaction.container.ContainerDslMarker import com.lambda.interaction.container.ContainerType import com.lambda.interaction.container.NestedContainer import com.lambda.interaction.container.containers.ArmorContainer @@ -30,12 +30,17 @@ import com.lambda.interaction.container.containers.OffHandContainer import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.containerSelection import com.lambda.interaction.handler.handlers.ContainerSearchScope -@ContainerMarker +@ContainerDslMarker fun Container.select( scope: ContainerSearchScope = ContainerSearchScope.Accessed ) = containerSelection(scope) { ofAny(this@select) } -@ContainerMarker +@ContainerDslMarker +fun Iterable.select( + scope: ContainerSearchScope = ContainerSearchScope.Accessed +) = containerSelection(scope) { ofAny(this@select) } + +@ContainerDslMarker fun selectContainers( vararg containers: Container, scope: ContainerSearchScope = ContainerSearchScope.Accessed @@ -45,21 +50,22 @@ fun selectContainers( * ContainerSelection is a class that holds a predicate for matching containers. */ @Suppress("unused") -class ContainerSelection @ContainerMarker internal constructor( +class ContainerSelection @ContainerDslMarker internal constructor( val selector: (Container) -> Boolean, val scope: ContainerSearchScope = ContainerSearchScope.Accessed, val containersWhitelist: Collection = emptyList(), + val loadedContainers: Collection = emptyList(), val containersBlacklist: Collection = emptyList(), val accessScope: AccessScope = AccessScope.Both, val comparator: Comparator = compareBy { it.type } ) { - @ContainerMarker + @ContainerDslMarker fun bestMatch(containers: Iterable) = filter(containers).firstOrNull() - @ContainerMarker + @ContainerDslMarker fun matches(container: Container): Boolean = selector(container) - @ContainerMarker + @ContainerDslMarker fun filter(containers: Iterable) = run { containersWhitelist @@ -89,11 +95,11 @@ class ContainerSelection @ContainerMarker internal constructor( } @Suppress("unused") -@ContainerMarker -class ContainerSelectionBuilder @ContainerMarker private constructor( +@ContainerDslMarker +class ContainerSelectionBuilder @ContainerDslMarker private constructor( private val scope: ContainerSearchScope ) { - @ContainerMarker + @ContainerDslMarker private constructor( selection: ContainerSelection, scope: ContainerSearchScope @@ -107,12 +113,28 @@ class ContainerSelectionBuilder @ContainerMarker private constructor( private var selector: (Container) -> Boolean = { true } private val containersWhitelist = mutableListOf() + private val loadedContainers = mutableListOf() private val containersBlacklist = mutableListOf() private var accessScope: AccessScope = AccessScope.Both private var comparator: Comparator = compareBy { it.type } + fun withContainers(vararg containers: Container) { + loadedContainers.addAll(containers) + } + + fun withContainers(containers: Iterable) { + loadedContainers.addAll(containers) + } + fun ofAny(vararg containers: Container) { containersWhitelist.addAll(containers) + withContainers(*containers) + appendSelector { container -> container in containersWhitelist } + } + + fun ofAny(containers: Iterable) { + containersWhitelist.addAll(containers) + withContainers(containers) appendSelector { container -> container in containersWhitelist } } @@ -121,14 +143,27 @@ class ContainerSelectionBuilder @ContainerMarker private constructor( appendSelector { container -> container !in containersBlacklist } } + fun noneOf(containers: Iterable) { + containersBlacklist.addAll(containers) + appendSelector { container -> container !in containersBlacklist } + } + fun ofAnyType(vararg types: ContainerType) { appendSelector { container -> types.contains(container.type) } } + fun ofAnyType(types: Iterable) { + appendSelector { container -> types.contains(container.type) } + } + fun noneOfType(vararg types: ContainerType) { appendSelector { container -> !types.contains(container.type) } } + fun noneOfType(types: Iterable) { + appendSelector { container -> !types.contains(container.type) } + } + fun isNested() { appendSelector { container -> container is NestedContainer } } @@ -184,10 +219,20 @@ class ContainerSelectionBuilder @ContainerMarker private constructor( } private fun appendSelector(selector: (Container) -> Boolean) { - this.selector = { this.selector(it) && selector(it) } - } - - private fun build() = ContainerSelection(selector, scope, containersWhitelist, containersBlacklist, accessScope, comparator) + val currentSelector = this.selector + this.selector = { currentSelector(it) && selector(it) } + } + + private fun build() = + ContainerSelection( + selector, + scope, + containersWhitelist, + loadedContainers, + containersBlacklist, + accessScope, + comparator + ) companion object { fun containerSelection( diff --git a/src/main/kotlin/com/lambda/interaction/container/selection/StackSelection.kt b/src/main/kotlin/com/lambda/interaction/container/selection/StackSelection.kt index 262ff755b..19716278e 100644 --- a/src/main/kotlin/com/lambda/interaction/container/selection/StackSelection.kt +++ b/src/main/kotlin/com/lambda/interaction/container/selection/StackSelection.kt @@ -15,15 +15,16 @@ * along with this program. If not, see . */ +@file:Suppress("unused") + package com.lambda.interaction.container.selection import com.lambda.interaction.container.Container -import com.lambda.interaction.container.ContainerMarker +import com.lambda.interaction.container.ContainerDslMarker import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection import com.lambda.util.EnchantmentUtils.getEnchantment import com.lambda.util.item.ItemStackUtils.shulkerBoxStacks import com.lambda.util.item.ItemUtils -import com.lambda.util.player.SlotUtils.matches import net.minecraft.block.Block import net.minecraft.block.BlockState import net.minecraft.component.ComponentType @@ -37,16 +38,16 @@ import net.minecraft.registry.tag.TagKey import net.minecraft.screen.slot.Slot import java.util.* -@ContainerMarker -fun Item.select(count: Int = 0) = stackSelection(count) { isItem(this@select) } +@ContainerDslMarker +fun Item.select(count: Int = 1) = stackSelection(count) { isItem(this@select) } -@ContainerMarker -fun ItemStack.select(count: Int = 0) = stackSelection(count) { isItemStack(this@select) } +@ContainerDslMarker +fun ItemStack.select(count: Int = 1) = stackSelection(count) { isItemStack(this@select) } -class StackSelection @ContainerMarker internal constructor( +class StackSelection @ContainerDslMarker internal constructor( + val count: Int = 1, val whitelistedSlots: Collection = emptyList(), val blacklistedSlots: Collection = emptyList(), - val count: Int = 0, val item: Item? = null, val itemStack: ItemStack? = null, val shulkerBoxScope: ShulkerBoxScope = ShulkerBoxScope.Both, @@ -55,23 +56,33 @@ class StackSelection @ContainerMarker internal constructor( ) { val optimalStack = itemStack ?: item?.let { ItemStack(it, count) } - @ContainerMarker + @ContainerDslMarker fun bestMatch(stacks: Iterable) = filter(stacks).firstOrNull() - @ContainerMarker + @ContainerDslMarker fun bestMatch(slots: Iterable) = filter(slots).firstOrNull() - @ContainerMarker - fun matches(stack: ItemStack) = - shulkerBoxScope.inShulkerBox?.equals(stack.shulkerBoxStacks.any { selector(it, null) }) != false || - selector(stack, null) + @ContainerDslMarker + fun matches(stack: ItemStack): Boolean { + val matchesSelf = selector(stack, null) + return when (shulkerBoxScope.inShulkerBox) { + true -> stack.shulkerBoxStacks.any { selector(it, null) } + false -> matchesSelf + null -> matchesSelf || stack.shulkerBoxStacks.any { selector(it, null) } + } + } - @ContainerMarker - fun matches(slot: Slot): Boolean = - shulkerBoxScope.inShulkerBox?.equals(slot.stack.shulkerBoxStacks.any { selector(it, null) }) != false || - selector(slot.stack, slot) + @ContainerDslMarker + fun matches(slot: Slot): Boolean { + val matchesSelf = selector(slot.stack, slot) + return when (shulkerBoxScope.inShulkerBox) { + true -> slot.stack.shulkerBoxStacks.any { selector(it, null) } + false -> matchesSelf + null -> matchesSelf || slot.stack.shulkerBoxStacks.any { selector(it, null) } + } + } - @ContainerMarker + @ContainerDslMarker @JvmName("filter1") fun filter(stacks: Iterable) = stacks @@ -83,7 +94,7 @@ class StackSelection @ContainerMarker internal constructor( .map { it.stack } .toList() - @ContainerMarker + @ContainerDslMarker @JvmName("filter2") fun filter(slots: Iterable) = slots @@ -101,25 +112,39 @@ class StackSelection @ContainerMarker internal constructor( .map { it.slot } .toList() - @ContainerMarker + @ContainerDslMarker infix fun isIn(container: Container) = container.count(this) >= count - @ContainerMarker + @ContainerDslMarker + fun isIn(vararg containers: Container) = + containers.sumOf { it.count(this) } >= count + + @ContainerDslMarker + infix fun isIn(containers: Iterable) = + containers.sumOf { it.count(this) } >= count + + @ContainerDslMarker infix fun spaceIn(container: Container) = container.spaceLeft(this) >= count - @Suppress("unused") + @ContainerDslMarker + fun spaceIn(vararg containers: Container) = + containers.sumOf { it.spaceLeft(this) } >= count + + @ContainerDslMarker + infix fun spaceIn(containers: Iterable) = + containers.sumOf { it.spaceLeft(this) } >= count + companion object { - val ANYTHING = StackSelection { _, _ -> true } + val ANYTHING = StackSelection(0) { _, _ -> true } val NOTHING = StackSelection { _, _ -> false } } } -@Suppress("unused") -@ContainerMarker -class StackSelectionBuilder @ContainerMarker private constructor( - private var count: Int = 0 +@ContainerDslMarker +class StackSelectionBuilder @ContainerDslMarker private constructor( + private var count: Int = 1 ) { private val whitelistedSlots = mutableListOf() private val blacklistedSlots = mutableListOf() @@ -129,7 +154,7 @@ class StackSelectionBuilder @ContainerMarker private constructor( private var comparator: Comparator>? = null private var shulkerBoxScope: ShulkerBoxScope = ShulkerBoxScope.NotInShulkerBox - @ContainerMarker + @ContainerDslMarker private constructor( selection: StackSelection, count: Int = selection.count @@ -188,11 +213,11 @@ class StackSelectionBuilder @ContainerMarker private constructor( shulkerBoxScope = ShulkerBoxScope.InShulkerBox } - fun ofAnyItems(items: Collection) { + fun ofAnyItems(items: Iterable) { appendSelector { stack, _ -> items.contains(stack.item) } } - fun noneOfItems(items: Collection) { + fun noneOfItems(items: Iterable) { appendSelector { stack, _ -> !items.contains(stack.item) } } @@ -212,11 +237,11 @@ class StackSelectionBuilder @ContainerMarker private constructor( appendSelector { stack, _ -> stack.name.string != name } } - fun ofAnyStacks(stacks: Collection) { + fun ofAnyStacks(stacks: Iterable) { appendSelector { stack, _ -> stacks.contains(stack) } } - fun noneOfStacks(stacks: Collection) { + fun noneOfStacks(stacks: Iterable) { appendSelector { stack, _ -> !stacks.contains(stack) } } @@ -315,37 +340,11 @@ class StackSelectionBuilder @ContainerMarker private constructor( } fun isEmpty() { - appendSelector { stack, _ -> stack.isEmpty} + appendSelector { stack, _ -> stack.isEmpty } } fun notEmpty() { - appendSelector { stack, _ -> !stack.isEmpty} - } - - fun isSlot(slot: Slot) { - appendSelector { _, s -> s != null && s matches slot } - } - - fun notSlot(slot: Slot) { - appendSelector { _, s -> s == null || !s.matches(slot) } - } - - fun isSlotByReference(slot: Slot) { - whitelistedSlots.add(slot) - appendSelector { _, s -> s === slot } - } - - fun notSlotByReference(slot: Slot) { - blacklistedSlots.add(slot) - appendSelector { _, s -> s !== slot } - } - - fun inIndex(index: Int) { - appendSelector { _, s -> s?.index == index } - } - - fun notInIndex(index: Int) { - appendSelector { _, s -> s?.index != index } + appendSelector { stack, _ -> !stack.isEmpty } } fun predicate(predicate: (ItemStack, Slot?) -> Boolean) { @@ -380,16 +379,17 @@ class StackSelectionBuilder @ContainerMarker private constructor( @PublishedApi internal fun appendSelector(selector: (ItemStack, Slot?) -> Boolean) { + val currentSelector = this.selector this.selector = { stack, slot -> - this.selector(stack, slot) && selector(stack, slot) + currentSelector(stack, slot) && selector(stack, slot) } } private fun build() = StackSelection( + count, whitelistedSlots, blacklistedSlots, - count, item, itemStack, shulkerBoxScope, @@ -401,7 +401,7 @@ class StackSelectionBuilder @ContainerMarker private constructor( private val efficientToolCache = Collections.synchronizedMap(mutableMapOf()) fun stackSelection( - count: Int = 0, + count: Int = 1, builder: StackSelectionBuilder.() -> Unit ) = StackSelectionBuilder(count).apply(builder).build() diff --git a/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt index ca19ef387..08c4ee113 100644 --- a/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt @@ -27,7 +27,7 @@ import com.lambda.event.events.PacketEvent import com.lambda.event.events.WorldEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.container.Container -import com.lambda.interaction.container.ContainerMarker +import com.lambda.interaction.container.ContainerDslMarker import com.lambda.interaction.container.ContainerSerializer import com.lambda.interaction.container.PlacedContainer import com.lambda.interaction.container.containers.external.ChestContainer @@ -235,7 +235,7 @@ object ContainerHandler : Loadable { private fun Inventory.stacks() = iterator().asSequence().toList() } -@ContainerMarker +@ContainerDslMarker context(_: AutomatedSafeContext) fun StackSelection.move( fromSelection: ContainerSelection = ContainerSelection.ACCESSED, @@ -268,7 +268,7 @@ fun StackSelection.move( return fromContainer.swap(fromSlot, toSlot, toContainer) } -@ContainerMarker +@ContainerDslMarker context(automated: Automated) fun findSlot( stackSelection: StackSelection = StackSelection.ANYTHING, @@ -277,13 +277,13 @@ fun findSlot( ) = findSlots(stackSelection, containerSelection, sorted) .firstOrNull() -@ContainerMarker +@ContainerDslMarker context(automated: Automated) fun findSlots( stackSelection: StackSelection = StackSelection.ANYTHING, containerSelection: ContainerSelection = ContainerSelection.ACCESSED, sorted: Boolean = true -) = searchContainers(containerSelection.scope) +) = searchContainers(containerSelection) .filter { containerSelection.matches(it) } .let { if (sorted) it.sorted() @@ -296,7 +296,7 @@ fun findSlots( } .flatten() -@ContainerMarker +@ContainerDslMarker context(automated: Automated) fun findStack( stackSelection: StackSelection = StackSelection.ANYTHING, @@ -305,13 +305,13 @@ fun findStack( ) = findStacks(stackSelection, containerSelection, sorted) .firstOrNull() -@ContainerMarker +@ContainerDslMarker context(automated: Automated) fun findStacks( stackSelection: StackSelection = StackSelection.ANYTHING, containerSelection: ContainerSelection = ContainerSelection.ACCESSED, sorted: Boolean = true -) = searchContainers(containerSelection.scope) +) = searchContainers(containerSelection) .filter { containerSelection.matches(it) } .let { if (sorted) it.sorted() @@ -324,7 +324,7 @@ fun findStacks( } .flatten() -@ContainerMarker +@ContainerDslMarker context(_: Automated) fun findContainer( containerSelection: ContainerSelection = ContainerSelection.ACCESSED, @@ -332,19 +332,19 @@ fun findContainer( ) = findContainers(containerSelection, sorted) .firstOrNull() -@ContainerMarker +@ContainerDslMarker context(automated: Automated) fun findContainers( containerSelection: ContainerSelection = ContainerSelection.ACCESSED, sorted: Boolean = true -) = searchContainers(containerSelection.scope) +) = searchContainers(containerSelection) .filter { containerSelection.matches(it) } .let { if (sorted) it.sorted() else it } -@ContainerMarker +@ContainerDslMarker context(_: Automated) fun findContainerWithDisposable( containerSelection: ContainerSelection = ContainerSelection.ACCESSED, @@ -352,7 +352,7 @@ fun findContainerWithDisposable( ) = findContainersWithDisposable(containerSelection, sorted) .firstOrNull() -@ContainerMarker +@ContainerDslMarker context(automated: Automated) fun findContainersWithDisposable( containerSelection: ContainerSelection = ContainerSelection.ACCESSED, @@ -371,7 +371,9 @@ fun findContainersWithDisposable( } context(automated: Automated) -private fun searchContainers(scope: ContainerSearchScope): Sequence { +private fun searchContainers( + selection: ContainerSelection +): Sequence { fun Container.allNested(): Sequence = sequence { yield(this@allNested) @@ -384,9 +386,10 @@ private fun searchContainers(scope: ContainerSearchScope): Sequence { val placedSeq = sequenceOf(ContainerHandler.accessedPlacedContainer).filterNotNull() val baseContainers = - when (scope) { + when (selection.scope) { ContainerSearchScope.Player -> compiled - EnderChestContainer ContainerSearchScope.Compiled -> compiled + ContainerSearchScope.Loaded -> compiled + placedSeq + selection.loadedContainers ContainerSearchScope.Accessed -> placedSeq + compiled.filter { it.isAccessed } ContainerSearchScope.All -> compiled + placedSeq + ContainerSerializer.serializedContainers.filter { diskContainer -> @@ -400,8 +403,9 @@ private fun searchContainers(scope: ContainerSearchScope): Sequence { } enum class ContainerSearchScope { - Player, - Compiled, Accessed, + Compiled, + Loaded, + Player, All } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt b/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt index 5c4cbd212..b43257a5f 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt @@ -24,6 +24,7 @@ import com.lambda.event.events.InventoryEvent import com.lambda.event.events.PlayerEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.container.ContainerType +import com.lambda.interaction.container.NestedContainer import com.lambda.interaction.container.OpenedContainerContext import com.lambda.interaction.container.containers.external.EnderChestContainer import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.containerSelection @@ -73,7 +74,9 @@ object InventoryTweaks : Module( else findContainer( containerSelection { ofAnyType(ContainerType.ShulkerBox) - isNested() + predicate { container -> + container is NestedContainer && container.index == slot.index + } } ) ?: return@listen diff --git a/src/main/kotlin/com/lambda/task/Task.kt b/src/main/kotlin/com/lambda/task/Task.kt index 599c84373..65679fd1b 100644 --- a/src/main/kotlin/com/lambda/task/Task.kt +++ b/src/main/kotlin/com/lambda/task/Task.kt @@ -33,7 +33,7 @@ import kotlin.time.DurationUnit import kotlin.time.toDuration @Suppress("unused") -abstract class Task : Nameable, Muteable { +abstract class Task : Nameable, Muteable { var state = State.Init var age = 0 @@ -44,7 +44,7 @@ abstract class Task : Nameable, Muteable { override val isMuted: Boolean get() = state == State.Paused || state == State.Init - private val successCallbacks = mutableListOf Unit>() + private val successCallbacks = mutableListOf Unit>() private val completionCallbacks = mutableListOf Unit>() private val failureCallbacks = mutableListOf Unit>() @@ -63,6 +63,7 @@ abstract class Task : Nameable, Muteable { } init { + if (this is RootTask) state = State.Running listen({ Int.MAX_VALUE }) { age++ } } @@ -73,30 +74,6 @@ abstract class Task : Nameable, Muteable { @DslMarker annotation class Ta5kBuilder - /** - * Invoked when the task starts execution. - * - * This method serves as a lifecycle hook and can be overridden to define - * custom behavior or initialization steps necessary before the task begins. - * It provides a `SafeContext` to access relevant context-sensitive properties - * or actions safely. - * - * By default, this method does not contain any logic. Subclasses may override - * it to implement specific functionality, such as logging, resource allocation, - * or preparing preconditions for task execution. - */ - @Ta5kBuilder - protected open fun SafeContext.onStart() {} - - /** - * This function is called when the task is canceled. - * It can be overridden for tasks that need to perform cleanup operations, - * such as cancelling a block breaking progress, releasing resources, - * or stopping any ongoing operations that were started by the task. - */ - @Ta5kBuilder - protected open fun SafeContext.onCancel() {} - @Ta5kBuilder context(parentTask: Task<*>) fun start(pauseParent: Boolean = true) = @@ -115,7 +92,7 @@ abstract class Task : Nameable, Muteable { * @throws IllegalArgumentException if the owner task is the same as the task being executed. */ @Ta5kBuilder - fun execute(parent: Task<*>, pauseParent: Boolean = true): Task { + fun execute(parent: Task<*>, pauseParent: Boolean = true): Task { require(parent != this) { "Cannot execute a task as a sub task of itself" } parent.subTasks.add(this) this@Task.parent = parent @@ -129,8 +106,32 @@ abstract class Task : Nameable, Muteable { return this } + /** + * Invoked when the task starts execution. + * + * This method serves as a lifecycle hook and can be overridden to define + * custom behavior or initialization steps necessary before the task begins. + * It provides a `SafeContext` to access relevant context-sensitive properties + * or actions safely. + * + * By default, this method does not contain any logic. Subclasses may override + * it to implement specific functionality, such as logging, resource allocation, + * or preparing preconditions for task execution. + */ + @Ta5kBuilder + protected open fun SafeContext.onStart() {} + + /** + * This function is called when the task is canceled. + * It can be overridden for tasks that need to perform cleanup operations, + * such as cancelling a block breaking progress, releasing resources, + * or stopping any ongoing operations that were started by the task. + */ + @Ta5kBuilder + protected open fun SafeContext.onCancel() {} + @Ta5kBuilder - protected fun success(result: Result) { + protected fun success(result: R) { if (state != State.Running && state != State.Paused) return unsubscribe() state = State.Completed @@ -157,7 +158,7 @@ abstract class Task : Nameable, Muteable { e: Throwable, stacktrace: MutableList> = mutableListOf(), ) { - if (state != State.Running && state != State.Failed) return + if (state != State.Running && state != State.Paused) return unsubscribe() state = State.Failed cancelSubTasks() @@ -195,11 +196,14 @@ abstract class Task : Nameable, Muteable { @Ta5kBuilder private fun internalCancel(removeFromParent: Boolean = true) { if (state != State.Running && state != State.Paused) return + cancelSubTasks() if (this is RootTask) return unsubscribe() state = State.Cancelled - cancelSubTasks() - runSafe { onCancel() } + runSafe { + onCancel() + completionCallbacks.forEach { it.invoke(this) } + } if (removeFromParent) parent?.subTasks?.remove(this) parent?.activate() @@ -211,18 +215,18 @@ abstract class Task : Nameable, Muteable { subTasks.clear() } - @Ta5kBuilder - fun activate() { - if (state != State.Paused) return - state = State.Running - } - @Ta5kBuilder fun pause() { if (state != State.Running) return state = State.Paused } + @Ta5kBuilder + fun activate() { + if (state != State.Paused) return + state = State.Running + } + @Ta5kBuilder protected open fun onSubTaskSuccess(subTask: Task<*>) { activate() @@ -242,7 +246,7 @@ abstract class Task : Nameable, Muteable { * Registers a callback for if the task succeeds. */ @Ta5kBuilder - fun onSuccess(callback: SafeContext.(Result) -> Unit): Task { + fun onSuccess(callback: SafeContext.(R) -> Unit): Task { successCallbacks.add(callback) return this } @@ -253,7 +257,7 @@ abstract class Task : Nameable, Muteable { * This could be mistaken for [withRecovery] which is used to wrap the given task with another task that runs a recovery task if this one fails. */ @Ta5kBuilder - fun onFailure(callback: SafeContext.(Throwable) -> Unit): Task { + fun onFailure(callback: SafeContext.(Throwable) -> Unit): Task { failureCallbacks.add(callback) return this } @@ -264,7 +268,7 @@ abstract class Task : Nameable, Muteable { * This is called regardless of whether the task succeeds or fails. */ @Ta5kBuilder - fun onCompletion(callback: SafeContext.() -> Unit): Task { + fun onCompletion(callback: SafeContext.() -> Unit): Task { completionCallbacks.add(callback) return this } diff --git a/src/main/kotlin/com/lambda/task/tasks/AcquireStackTask.kt b/src/main/kotlin/com/lambda/task/tasks/AcquireStackTask.kt index 0f95dc667..bfd335b50 100644 --- a/src/main/kotlin/com/lambda/task/tasks/AcquireStackTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/AcquireStackTask.kt @@ -25,7 +25,6 @@ import com.lambda.interaction.container.selection.select import com.lambda.task.Task import com.lambda.task.Task.Ta5kBuilder import com.lambda.threading.runSafeAutomated -import net.minecraft.screen.slot.Slot @Ta5kBuilder context(automated: Automated) @@ -40,17 +39,15 @@ fun acquireStack(selection: () -> StackSelection) = class AcquireStackTask @Ta5kBuilder internal constructor( val selection: StackSelection, automated: Automated -) : Task(), Automated by automated { +) : Task(), Automated by automated { override val name: String get() = "Acquiring $selection" override fun SafeContext.onStart() { runSafeAutomated { selection.transfer(toSelection = HotbarContainer.select()) - .onSuccess { slot -> success(slot) } + .onSuccess { result -> success(result.stackSelection) } .start() } } } - -class NoContainerFound(selection: StackSelection) : Exception("No container found matching $selection") diff --git a/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt b/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt index 8f64d39b1..e4ca93e65 100644 --- a/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt @@ -246,7 +246,7 @@ class BuildTask @Ta5kBuilder internal constructor( lifeMaintenance && eatTask == null && runSafeAutomated { reasonEating() }.shouldEat() -> { eatTask = eat() .thenAction { eatTask = null } - .execute(this@BuildTask) + .start() return true } @@ -360,7 +360,7 @@ class BuildTask @Ta5kBuilder internal constructor( if (collectDropsTask != null) return true collectDropsTask = collectDrops(dropsToCollect) .thenAction { collectDropsTask = null } - .execute(this@BuildTask) + .start() return true } diff --git a/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt b/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt index cae228e6a..19bc25c21 100644 --- a/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt @@ -19,6 +19,7 @@ package com.lambda.task.tasks import baritone.api.pathing.goals.GoalBlock import com.lambda.context.Automated +import com.lambda.context.AutomatedSafeContext import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen @@ -54,36 +55,45 @@ class CollectDropsTask @Ta5kBuilder internal constructor( init { listen { runSafeAutomated { - val target = - drops.firstOrNull() ?: run { - BaritoneHandler.cancel() - success() - return@listen - } + do { + val done = pollNextItem() + } while (!done) + } + } + } - if (!world.entities.contains(target)) { - drops.remove(target) + private fun AutomatedSafeContext.pollNextItem(): Boolean { + val target = + drops.firstOrNull() + ?: run { BaritoneHandler.cancel() - return@listen + success() + return true } - if (findStack(stackSelection { isEmpty() }, ContainerSelection.HOTBAR_AND_INVENTORY) == null) { - val stackToThrow = - player.currentScreenHandler.playerSlots.firstOrNull { - it.stack.item in inventoryConfig.disposables - } ?: run { - failure("Inventory is full and no disposable items to throw") - return@listen - } - inventoryRequest { - throwStack(stackToThrow.id) - }.submit() - return@listen - } + if (!world.entities.contains(target)) { + drops.remove(target) + BaritoneHandler.cancel() + return false + } - BaritoneHandler.setGoalAndPath(GoalBlock(target.blockPos)) - } + if (findStack(stackSelection(0) { isEmpty() }, ContainerSelection.HOTBAR_AND_INVENTORY, false) == null) { + val stackToThrow = + player.currentScreenHandler.playerSlots.firstOrNull { + it.stack.item in inventoryConfig.disposables + } ?: run { + failure("Inventory is full and no disposable items to throw") + return true + } + inventoryRequest { + throwStack(stackToThrow.id) + }.submit() + return true } + + BaritoneHandler.setGoalAndPath(GoalBlock(target.blockPos)) + + return true } override fun SafeContext.onCancel() { diff --git a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt index a8036ebe4..8ab79138a 100644 --- a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt @@ -18,16 +18,19 @@ package com.lambda.task.tasks import com.lambda.context.Automated +import com.lambda.context.AutomatedSafeContext import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.container.Container import com.lambda.interaction.container.ExternalContainer -import com.lambda.interaction.container.OpenedContainerContext import com.lambda.interaction.container.selection.ContainerSelection -import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.mutate +import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.containerSelection import com.lambda.interaction.container.selection.StackSelection import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.mutate +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection +import com.lambda.interaction.container.selection.select +import com.lambda.interaction.container.selection.selectContainers import com.lambda.interaction.handler.handlers.findContainers import com.lambda.task.Task import com.lambda.task.Task.Ta5kBuilder @@ -40,11 +43,11 @@ import net.minecraft.screen.slot.Slot @Ta5kBuilder context(automated: Automated) fun transfer( - selection: StackSelection, + fromStack: StackSelection, fromSelection: ContainerSelection = ContainerSelection.ACCESSED, toSelection: ContainerSelection, toStack: StackSelection = StackSelection.ANYTHING -) = ContainerTransferTask(selection, fromSelection, toSelection, toStack, automated) +) = ContainerTransferTask(fromStack, fromSelection, toSelection, toStack, automated) @Ta5kBuilder @JvmName("transferExt") @@ -55,170 +58,249 @@ fun StackSelection.transfer( toStack: StackSelection = StackSelection.ANYTHING ) = transfer(this, fromSelection, toSelection, toStack) +class TransferResult internal constructor( + val stackSelection: StackSelection, + val containerSelection: ContainerSelection +) + class ContainerTransferTask @Ta5kBuilder internal constructor( private val fromStack: StackSelection, private val fromSelection: ContainerSelection, private val toSelection: ContainerSelection, private val toStack: StackSelection, automated: Automated -) : Task(), Automated by automated { - override val name = "Transferring $fromStack from $fromSelection to $toSelection" +) : Task(), Automated by automated { + override val name = "Transferring $fromStack" - override fun SafeContext.onStart() { - val singleSelection = fromStack.mutate(count = if (fromStack.count == 0) 0 else 1) + private var transferred = 0 + private val resultSlots = mutableListOf() + private val resultContainers = mutableSetOf() + override fun SafeContext.onStart() { val fromContainers = findContainers( - fromSelection.mutate { - hasStack(fromStack) + containerSelection(fromSelection.scope) { + matches(fromSelection) + hasStack(fromStack.mutate(1)) } ).toList() + .takeIf { fromStack isIn it } + ?: run { + failure("Could not find source containers for $fromStack") + return + } + val toContainers = findContainers( - toSelection.mutate { - hasStack(toStack) + containerSelection(toSelection.scope) { + matches(toSelection) + hasStack(toStack.mutate(1)) } ).toList() + .takeIf { toStack isIn it } + ?: run { + failure("Could not find destination containers for $toStack") + return + } - if (fromContainers.isEmpty()) { - failure(IllegalStateException("Could not find any container to pull $fromStack from")) - return - } - if (toContainers.isEmpty()) { - failure(IllegalStateException("Could not find any container with space to push $fromStack to")) + transferNextPair(fromContainers.toMutableList(), toContainers.toMutableList()) + } + + private fun remainingSelection() = + fromStack.mutate(count = if (fromStack.count <= 0) 0 else fromStack.count - transferred) + + private fun isComplete() = + (fromStack.count <= 0 && transferred > 0) || (fromStack.count != 0 && transferred >= fromStack.count) + + private fun buildResult() = + TransferResult( + stackSelection { + predicate { _, slot -> + slot != null && + resultSlots.any { + slot.inventory::class == it.inventory::class && slot.index == it.index + } + } + }, + selectContainers(*resultContainers.toTypedArray()) + ) + + /** + * Takes the first container from each queue as a pair, builds a transfer + * chain for that pair, then advances to the next pair on completion. + * + * Calls [success] or [failure] directly on this task when done. + */ + private fun transferNextPair( + fromQueue: MutableList, + toQueue: MutableList + ) { + if (isComplete()) { + success(buildResult()) return } - if (fromStack.count > 0) { - val totalAvailable = fromContainers.sumOf { it.count(singleSelection) } - if (totalAvailable < fromStack.count) { - failure(IllegalStateException("Not enough items across all containers to pull $fromStack")) - return - } + if (fromQueue.isEmpty() || toQueue.isEmpty()) { + failure("Not enough containers to transfer $fromStack (transferred $transferred/${fromStack.count})") + return } - val fromContainer = fromContainers.first() - val toContainer = toContainers.first() + val fromContainer = fromQueue.first() + val toContainer = toQueue.first() - when { - fromContainers.all { it.isAccessed } && toContainers.all { it.isAccessed } -> - TransferTask(fromContainers, toContainers) - .onSuccess { success(it) } + val bothExternal = fromContainer is ExternalContainer && !fromContainer.isAccessed && + toContainer is ExternalContainer && !toContainer.isAccessed - fromContainer !is ExternalContainer -> - openTransferClose(toContainer, fromSelection, toSelection) + val pairChain = + if (bothExternal) pullPushChain(fromContainer, toContainer, toQueue) + else directPairChain(fromContainer, toContainer) - toContainer !is ExternalContainer -> - openTransferClose(fromContainer, fromSelection, toSelection) - - else -> - taskOrNull { fromContainer.access() } - .then { fromCtx -> - transfer(fromStack, fromSelection, ContainerSelection.HOTBAR_AND_INVENTORY) - .thenOrNull { fromCtx?.close() } - } - .thenOrNull { toContainer.access() } - .then { toCtx -> transferAndClose(toCtx, ContainerSelection.HOTBAR_AND_INVENTORY, toSelection) } - }?.start() + pairChain.onSuccess { + if (fromContainer.count(fromStack) <= 0) fromQueue.remove(fromContainer) + if (!bothExternal && toContainer.spaceLeft(fromStack) <= 0) toQueue.remove(toContainer) + transferNextPair(fromQueue, toQueue) + }.start() } /** - * Opens [containerToOpen], transfers [fromStack] from [from] to [to], then closes the opened container. - * - * Used when exactly one of the two containers is external and needs to be accessed. + * Builds a chain for a pair where at most one container is external. + * Uses [taskOrNull] to uniformly handle already-accessed containers. */ - @Ta5kBuilder - private fun openTransferClose( - containerToOpen: Container, - fromSelection: ContainerSelection, - toSelection: ContainerSelection - ) = containerToOpen - .access() - ?.then { ctx -> transferAndClose(ctx, fromSelection, toSelection) } + private fun directPairChain( + fromContainer: Container, + toContainer: Container + ) = + taskOrNull { fromContainer.access() }.then { fromContext -> + taskOrNull { toContainer.access() }.then { toContext -> + SwapTask(fromContainer.select(), toContainer.select()) + .thenOrNull { toContext?.close() } + .thenOrNull { fromContext?.close() } + } + } /** - * Transfers [fromStack] from [from] to [to], then closes the container via [ctx]. + * Builds a chain for when both containers are external. + * Pulls items from [fromContainer] into the player inventory, then delegates + * to [pushChain] to push them into [toContainer]. */ - @Ta5kBuilder - private fun transferAndClose( - ctx: OpenedContainerContext?, - fromSelection: ContainerSelection, - toSelection: ContainerSelection - ) = transfer(fromStack, fromSelection, toSelection) - .thenOrNull { slot -> - ctx - ?.close() - ?.onSuccess { success(slot) } - } + private fun pullPushChain( + fromContainer: Container, + toContainer: Container, + toQueue: MutableList + ) = + taskOrNull { fromContainer.access() }.then { fromContext -> + SwapTask( + fromContainer.select(), + ContainerSelection.HOTBAR_AND_INVENTORY, + replaceSelection = StackSelection.ANYTHING, + trackResult = false + ).thenOrNull { fromContext?.close() } + }.then { pushChain(toContainer, toQueue) } - private inner class TransferTask @Ta5kBuilder constructor( - private val fromContainers: List, - private val toContainers: List - ) : Task() { - override val name = "Transferring" + /** + * Pushes items from the player inventory into [destination]. + * If [destination] fills up but the player still has items, advances [toQueue] + * and pushes to the next destination. + */ + private fun pushChain( + destination: Container, + toQueue: MutableList + ): Task<*> = + taskOrNull { destination.access() }.then { destinationContext -> + SwapTask(ContainerSelection.HOTBAR_AND_INVENTORY, destination.select()) + .thenOrNull { destinationContext?.close() } + }.onSuccess { + if (isComplete()) return@onSuccess - private var transferred = 0 - private var lastSlot: Slot? = null + if (destination.spaceLeft(fromStack) == 0) toQueue.remove(destination) + + val playerHasItems = findContainers(ContainerSelection.HOTBAR_AND_INVENTORY) + .any { container -> fromStack.mutate(1) isIn container } + if (!playerHasItems) return@onSuccess + + val nextDestination = toQueue.firstOrNull() ?: return@onSuccess + pushChain(nextDestination, toQueue).start() + } + + /** + * Swaps items between containers matching [fromSelection] and [toSelection] one slot per tick. + * Completes when no more valid move slots can be found or the total count is reached. + * Updates [transferred], [resultSlots], and [resultContainers] on the outer task. + * + * @param trackResult whether to record destination slots in [resultSlots]/[resultContainers]. + * Set to `false` for intermediary transfers (e.g. pulling to player inventory). + */ + private inner class SwapTask @Ta5kBuilder constructor( + private val fromSelection: ContainerSelection, + private val toSelection: ContainerSelection, + private val replaceSelection: StackSelection = toStack, + private val trackResult: Boolean = true + ) : Task() { + override val name = "Swapping slots" init { listen { runSafeAutomated { - while (fromStack.count == 0 || transferred < fromStack.count) { - val currentSelection = fromStack.mutate(count = if (fromStack.count == 0) 0 else fromStack.count - transferred) - - var fromSlot: Slot? = null - var toSlot: Slot? = null - var fromContainer: Container? = null - var toContainer: Container? = null - - for (fc in fromContainers) { - for (tc in toContainers) { - val slots = fc.findMoveSlots(currentSelection, tc, toStack) - if (slots.first != null && slots.second != null) { - fromSlot = slots.first - toSlot = slots.second - fromContainer = fc - toContainer = tc - break - } - } - if (fromSlot != null) break - } - - if (fromSlot == null || toSlot == null || fromContainer == null || toContainer == null) { - lastSlot?.let { slot -> - if (fromStack.count == 0 || transferred >= fromStack.count) { - success(slot) - return@listen - } - } - checkFail() - return@listen - } - - val moveCount = minOf(fromSlot.stack.count, if (fromStack.count == 0) Int.MAX_VALUE else fromStack.count - transferred) - if (!fromContainer.swap(fromSlot, toSlot, toContainer)) { - checkFail() - return@listen - } - - transferred += moveCount - lastSlot = toSlot - - // count == 0 means single stack transfer - if (fromStack.count == 0) { - success(toSlot) - return@listen - } + while (true) { + if (swapStack()) break } - - lastSlot?.let { success(it) } ?: checkFail() } } } - private fun checkFail() { - failure(IllegalStateException("Unable to access $fromStack")) + private fun AutomatedSafeContext.swapStack(): Boolean { + val currentSelection = remainingSelection() + + val moveSlots = + findContainers(fromSelection).firstNotNullOfOrNull { fromContainer -> + findContainers(toSelection).firstNotNullOfOrNull { toContainer -> + val (from, to) = + fromContainer.findMoveSlots( + currentSelection, + toContainer, + replaceSelection + ) + if (from != null && to != null) { + Triple( + fromContainer, + toContainer, + Pair(from, to) + ) + } else null + } + } + + if (moveSlots == null) { + success() + return true + } + + val (fromContainer, toContainer, slots) = moveSlots + val (fromSlot, toSlot) = slots + val moveCount = + minOf( + fromSlot.stack.count, + if (fromStack.count == 0) Int.MAX_VALUE + else fromStack.count - transferred + ) + + if (!fromContainer.swap(fromSlot, toSlot, toContainer)) { + failure("Swap failed for $fromStack") + return true + } + + transferred += moveCount + if (trackResult) { + resultSlots.add(toSlot) + resultContainers.add(toContainer) + } + + if (fromStack.count == 0 || isComplete()) { + success() + return true + } + + return false } } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/task/tasks/wrappers/ActionTasks.kt b/src/main/kotlin/com/lambda/task/tasks/wrappers/ActionTasks.kt index d1bb7290e..ea5d58a2e 100644 --- a/src/main/kotlin/com/lambda/task/tasks/wrappers/ActionTasks.kt +++ b/src/main/kotlin/com/lambda/task/tasks/wrappers/ActionTasks.kt @@ -57,6 +57,6 @@ class SequencedActionTask @Ta5kBuilder internal constructor( .onSuccess { result -> success(action(this, result)) } - .execute(this@SequencedActionTask) + .start() } } \ No newline at end of file From e710f2f37e4fa217d8898ca130736fd410810f8f Mon Sep 17 00:00:00 2001 From: beanbag44 <107891830+beanbag44@users.noreply.github.com> Date: Mon, 14 Sep 2026 16:47:26 +0100 Subject: [PATCH 17/43] cleanup --- .../command/commands/TransferCommand.kt | 10 +- .../lambda/config/settings/StringSetting.kt | 2 + .../settings/collections/CollectionSetting.kt | 2 + .../config/settings/complex/KeybindSetting.kt | 29 +++-- .../lambda/gui/components/ClickGuiLayout.kt | 1 - .../construction/simulation/sims/BreakSim.kt | 34 ++---- .../simulation/sims/InteractSim.kt | 27 ++-- .../construction/verify/TargetState.kt | 29 +---- .../lambda/interaction/container/Container.kt | 51 ++++---- ...ffHandContainer.kt => OffhandContainer.kt} | 2 +- .../external/ShulkerBoxContainer.kt | 11 +- .../container/selection/ContainerSelection.kt | 25 ++-- .../container/selection/StackSelection.kt | 66 ++++++---- .../handler/handlers/ContainerHandler.kt | 6 +- .../handler/handlers/GlideHandler.kt | 11 +- src/main/kotlin/com/lambda/module/Module.kt | 2 - .../lambda/module/modules/combat/AutoArmor.kt | 115 ++++++++++-------- .../module/modules/combat/CrystalAura.kt | 18 +-- .../module/modules/combat/PlayerTrap.kt | 9 +- .../lambda/module/modules/debug/StateInfo.kt | 1 - .../module/modules/movement/BetterFirework.kt | 23 ++-- .../movement/elytrafly/ElytraFlyMode.kt | 1 + .../module/modules/player/AutoElytraSwap.kt | 17 +-- .../module/modules/player/ClickFriend.kt | 1 - .../lambda/module/modules/player/Replay.kt | 1 - .../lambda/module/modules/world/AutoPortal.kt | 26 ++-- .../module/modules/world/AutoVillagerCycle.kt | 1 - .../lambda/module/modules/world/Scaffold.kt | 2 +- .../lambda/module/modules/world/StashMover.kt | 35 +++--- .../task/tasks/AcquirePlacedBlockTask.kt | 21 ++-- .../com/lambda/task/tasks/CollectDropsTask.kt | 15 ++- .../task/tasks/ContainerTransferTask.kt | 42 ++++--- .../kotlin/com/lambda/task/tasks/EatTask.kt | 2 +- .../lambda/task/tasks/wrappers/BranchTasks.kt | 12 +- .../lambda/task/tasks/wrappers/OnFailTasks.kt | 16 +-- .../task/tasks/wrappers/SequenceTasks.kt | 10 +- .../task/tasks/wrappers/SoftFailTask.kt | 2 +- 37 files changed, 344 insertions(+), 334 deletions(-) rename src/main/kotlin/com/lambda/interaction/container/containers/{OffHandContainer.kt => OffhandContainer.kt} (96%) diff --git a/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt b/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt index def4906f1..6eb985cf3 100644 --- a/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt +++ b/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt @@ -30,6 +30,7 @@ import com.lambda.command.LambdaCommand import com.lambda.config.automation.AutomationConfig import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.containerSelection import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection +import com.lambda.interaction.container.selection.select import com.lambda.interaction.handler.handlers.findContainers import com.lambda.task.Task import com.lambda.task.start @@ -51,9 +52,7 @@ object TransferCommand : LambdaCommand( required(integer("amount", 1)) { amount -> required(string("from")) { from -> suggests { ctx, builder -> - val selection = stackSelection(amount(ctx).value()) { - isItem(stack(ctx).value().item) - } + val selection = stack(ctx).value().item.select(amount(ctx).value()) AutomationConfig.DEFAULT.runSafeAutomated { val containers = findContainers( @@ -73,10 +72,7 @@ object TransferCommand : LambdaCommand( } required(string("to")) { to -> suggests { ctx, builder -> - val selection = - stackSelection(amount(ctx).value()) { - isItem(stack(ctx).value().item) - } + val selection = stack(ctx).value().item.select(amount(ctx).value()) AutomationConfig.DEFAULT.runSafeAutomated { val containers = findContainers( diff --git a/src/main/kotlin/com/lambda/config/settings/StringSetting.kt b/src/main/kotlin/com/lambda/config/settings/StringSetting.kt index cb379f4ec..6ae8f4ac5 100644 --- a/src/main/kotlin/com/lambda/config/settings/StringSetting.kt +++ b/src/main/kotlin/com/lambda/config/settings/StringSetting.kt @@ -15,6 +15,8 @@ * along with this program. If not, see . */ +@file:Suppress("unused") + package com.lambda.config.settings import com.lambda.brigadier.argument.greedyString diff --git a/src/main/kotlin/com/lambda/config/settings/collections/CollectionSetting.kt b/src/main/kotlin/com/lambda/config/settings/collections/CollectionSetting.kt index 6dc59d4cf..bc01d1ef2 100644 --- a/src/main/kotlin/com/lambda/config/settings/collections/CollectionSetting.kt +++ b/src/main/kotlin/com/lambda/config/settings/collections/CollectionSetting.kt @@ -15,6 +15,8 @@ * along with this program. If not, see . */ +@file:Suppress("unused") + package com.lambda.config.settings.collections import com.lambda.config.Config diff --git a/src/main/kotlin/com/lambda/config/settings/complex/KeybindSetting.kt b/src/main/kotlin/com/lambda/config/settings/complex/KeybindSetting.kt index 00629b99f..5b069fd72 100644 --- a/src/main/kotlin/com/lambda/config/settings/complex/KeybindSetting.kt +++ b/src/main/kotlin/com/lambda/config/settings/complex/KeybindSetting.kt @@ -15,6 +15,8 @@ * along with this program. If not, see . */ +@file:Suppress("unused") + package com.lambda.config.settings.complex import com.fasterxml.jackson.annotation.JsonIncludeProperties @@ -218,26 +220,23 @@ class KeybindSetting( } } - @Suppress("unused") - companion object { - @ConfigEntryDsl - fun KeybindSetting.onPress(block: SafeContext.(ButtonEvent) -> Unit) = apply { pressListeners.add(block) } + @ConfigEntryDsl + fun onPress(block: SafeContext.(ButtonEvent) -> Unit) = apply { pressListeners.add(block) } - @ConfigEntryDsl - fun KeybindSetting.onPressUnsafe(block: (ButtonEvent) -> Unit) = apply { unsafePressListeners.add(block) } + @ConfigEntryDsl + fun onPressUnsafe(block: (ButtonEvent) -> Unit) = apply { unsafePressListeners.add(block) } - @ConfigEntryDsl - fun KeybindSetting.onRepeat(block: SafeContext.(ButtonEvent) -> Unit) = apply { repeatListeners.add(block) } + @ConfigEntryDsl + fun onRepeat(block: SafeContext.(ButtonEvent) -> Unit) = apply { repeatListeners.add(block) } - @ConfigEntryDsl - fun KeybindSetting.onRepeatUnsafe(block: (ButtonEvent) -> Unit) = apply { unsafeRepeatListeners.add(block) } + @ConfigEntryDsl + fun onRepeatUnsafe(block: (ButtonEvent) -> Unit) = apply { unsafeRepeatListeners.add(block) } - @ConfigEntryDsl - fun KeybindSetting.onRelease(block: SafeContext.(ButtonEvent) -> Unit) = apply { releaseListeners.add(block) } + @ConfigEntryDsl + fun onRelease(block: SafeContext.(ButtonEvent) -> Unit) = apply { releaseListeners.add(block) } - @ConfigEntryDsl - fun KeybindSetting.onReleaseUnsafe(block: (ButtonEvent) -> Unit) = apply { unsafeReleaseListeners.add(block) } - } + @ConfigEntryDsl + fun onReleaseUnsafe(block: (ButtonEvent) -> Unit) = apply { unsafeReleaseListeners.add(block) } } @Suppress("unused") diff --git a/src/main/kotlin/com/lambda/gui/components/ClickGuiLayout.kt b/src/main/kotlin/com/lambda/gui/components/ClickGuiLayout.kt index 9bc94da3f..f294df0e1 100644 --- a/src/main/kotlin/com/lambda/gui/components/ClickGuiLayout.kt +++ b/src/main/kotlin/com/lambda/gui/components/ClickGuiLayout.kt @@ -22,7 +22,6 @@ import com.lambda.config.Config import com.lambda.config.Tab import com.lambda.config.categories.GuiCategory import com.lambda.config.entries.onValueChange -import com.lambda.config.settings.complex.KeybindSetting.Companion.onPressUnsafe import com.lambda.core.Loadable import com.lambda.event.events.GuiEvent import com.lambda.event.listener.UnsafeListener.Companion.listenUnsafe diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/BreakSim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/BreakSim.kt index f3876b581..eca23d9c7 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/BreakSim.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/BreakSim.kt @@ -28,12 +28,12 @@ import com.lambda.interaction.construction.simulation.result.results.BreakResult import com.lambda.interaction.construction.simulation.result.results.GenericResult import com.lambda.interaction.construction.simulation.sim import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.container.ContainerType -import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.containerSelection +import com.lambda.interaction.container.containers.HotbarContainer import com.lambda.interaction.container.selection.StackAndSlot import com.lambda.interaction.container.selection.StackSelection import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection -import com.lambda.interaction.handler.handlers.findStacks +import com.lambda.interaction.container.selection.select +import com.lambda.interaction.handler.handlers.findStack import com.lambda.interaction.manager.managers.hotbar.HotbarManager import com.lambda.interaction.manager.managers.rotating.RotationManager import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest @@ -154,29 +154,13 @@ class BreakSim internal constructor(simInfo: SimInfo) } } - val containerSelection = containerSelection { - ofAnyType(ContainerType.Hotbar) - } - - val hotbarCandidates = findStacks(stackSelection, containerSelection).toList() - if (hotbarCandidates.isEmpty()) { - result(GenericResult.WrongItemSelection(pos, stackSelection, player.mainHandStack)) - return null - } - - var bestStack = ItemStack.EMPTY - var bestBreakDelta = -1f - hotbarCandidates.forEach { stack -> - val breakDelta = state.calcItemBlockBreakingDelta(pos, stack) - if (breakDelta > bestBreakDelta || - (stack == player.mainHandStack && breakDelta >= bestBreakDelta) - ) { - bestBreakDelta = breakDelta - bestStack = stack + val bestStack = findStack(stackSelection, HotbarContainer.select()) + ?: run { + result(GenericResult.WrongItemSelection(pos, stackSelection, player.mainHandStack)) + return null } - } - return if (bestBreakDelta == -1f) null - else Pair(bestStack, stackSelection) + + return Pair(bestStack, stackSelection) } private suspend fun AutomatedSafeContext.affectsFluids(): Boolean { diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/InteractSim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/InteractSim.kt index e20ac4ecd..57419852e 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/InteractSim.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/InteractSim.kt @@ -27,11 +27,10 @@ import com.lambda.interaction.construction.simulation.result.results.GenericResu import com.lambda.interaction.construction.simulation.result.results.InteractResult import com.lambda.interaction.construction.simulation.sim import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.container.ContainerType -import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.containerSelection +import com.lambda.interaction.container.containers.HotbarContainer import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection import com.lambda.interaction.container.selection.select -import com.lambda.interaction.handler.handlers.findContainer +import com.lambda.interaction.handler.handlers.findSlot import com.lambda.interaction.manager.managers.rotating.Rotation import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotation import com.lambda.interaction.manager.managers.rotating.RotationManager @@ -205,23 +204,17 @@ class InteractSim internal constructor(simInfo: InteractSimInfo) supervisorScope.cancel() return null } - val stackSelection = item?.select(if (item == Items.AIR) 0 else 1) - ?: stackSelection { sortedWith { compareByDescending { it.stack.inventoryIndex == player.inventory.selectedSlot } } } - val containerSelection = - containerSelection { - hasStack(stackSelection) - ofAnyType(ContainerType.Hotbar) + + val stackSelection = + stackSelection(if (item == Items.AIR) 0 else 1) { + item?.let { isItem(it) } + sortedWith { compareByDescending { it.stack.inventoryIndex == player.inventory.selectedSlot } } } - val container = findContainer(containerSelection) + + return findSlot(stackSelection, HotbarContainer.select()) ?: run { result(GenericResult.WrongItemSelection(pos, stackSelection, player.mainHandStack)) - return null - } - return stackSelection - .filter(container.slots) - .run { - firstOrNull { it.index == player.inventory.selectedSlot } - ?: firstOrNull() + null } } diff --git a/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt b/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt index d79b3748b..be94ce2f8 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt @@ -88,9 +88,7 @@ sealed class TargetState : StateMatcher { with(automatedSafeContext) { findContainerWithDisposable() ?.stacks - ?.firstOrNull { - it.item in inventoryConfig.disposables && it.item.block !in replace - } + ?.firstOrNull { it.item in inventoryConfig.disposables } ?: ItemStack(Items.NETHERRACK) } @@ -118,9 +116,7 @@ sealed class TargetState : StateMatcher { with(automatedSafeContext) { findContainerWithDisposable() ?.stacks - ?.firstOrNull { - it.item in inventoryConfig.disposables - } + ?.firstOrNull { it.item in inventoryConfig.disposables } ?: ItemStack(Items.NETHERRACK) } @@ -190,25 +186,4 @@ sealed class TargetState : StateMatcher { override fun isEmpty() = block.defaultState.isEmpty } - - data class SpecificStack(val itemStack: ItemStack) : TargetState() { - override fun toString() = "Specific stack of ${itemStack.item.name.string.capitalize()}" - - private val block = itemStack.item.block - - context(safeContext: SafeContext) - override fun matches( - state: BlockState, - pos: BlockPos, - ignoredProperties: Collection> - ) = state.block == block - - context(_: AutomatedSafeContext) - override fun getStack(pos: BlockPos) = itemStack - - context(_: AutomatedSafeContext) - override fun getState(pos: BlockPos): BlockState = block.defaultState - - override fun isEmpty() = block.defaultState.isEmpty - } } diff --git a/src/main/kotlin/com/lambda/interaction/container/Container.kt b/src/main/kotlin/com/lambda/interaction/container/Container.kt index 6ffecb4a6..e6feafe87 100644 --- a/src/main/kotlin/com/lambda/interaction/container/Container.kt +++ b/src/main/kotlin/com/lambda/interaction/container/Container.kt @@ -23,7 +23,9 @@ import com.lambda.context.SafeContext import com.lambda.event.EventFlow.post import com.lambda.event.events.ContainerEvent import com.lambda.interaction.container.containers.external.ShulkerBoxContainer +import com.lambda.interaction.container.selection.StackAndSlot import com.lambda.interaction.container.selection.StackSelection +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.mutate import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.task.Task.Ta5kBuilder @@ -60,19 +62,20 @@ abstract class Container( abstract val description: Text context(automated: Automated) - open val replaceSorter get() = compareByDescending { - it.stack.isEmpty - }.thenByDescending { - it.stack.item in automated.inventoryConfig.disposables - }.thenByDescending { - !it.stack.item.components.contains(DataComponentTypes.TOOL) - }.thenByDescending { - !it.stack.item.components.contains(DataComponentTypes.FOOD) - }.thenByDescending { - !it.stack.item.components.contains(DataComponentTypes.CONSUMABLE) - }.thenByDescending { - it.stack.isStackable - } + open val replaceSorter + get() = compareByDescending> { + it.stack.isEmpty + }.thenByDescending { + it.stack.item in automated.inventoryConfig.disposables + }.thenByDescending { + !it.stack.item.components.contains(DataComponentTypes.TOOL) + }.thenByDescending { + !it.stack.item.components.contains(DataComponentTypes.FOOD) + }.thenByDescending { + !it.stack.item.components.contains(DataComponentTypes.CONSUMABLE) + }.thenByDescending { + it.stack.isStackable + } open val isAccessed get() = true @@ -124,12 +127,14 @@ abstract class Container( open fun access(): OpenContainerTask<*>? = null open fun count(selection: StackSelection) = - slots.takeUnless { it.isEmpty() }?.let { selection.filter(it).count } - ?: selection.filter(stacks).count + slots.takeUnless { it.isEmpty() }?.let { + selection.filter(it, false).count + } ?: selection.filter(stacks, false).count open fun spaceLeft(selection: StackSelection) = - slots.takeUnless { it.isEmpty() }?.let { selection.filter(it).spaceLeft + it.emptySpace } - ?: (selection.filter(stacks).spaceLeft + stacks.emptySpace) + slots.takeUnless { it.isEmpty() }?.let { + selection.filter(it, false).spaceLeft + it.emptySpace + } ?: (selection.filter(stacks, false).spaceLeft + stacks.emptySpace) open fun findSlots(selection: StackSelection) = selection.filter(slots) @@ -144,14 +149,18 @@ abstract class Container( selection: StackSelection, toContainer: Container, toStackSelection: StackSelection = StackSelection.ANYTHING - ) = Pair(selection.filter(slots).firstOrNull(), toContainer.findReplaceSlot(toStackSelection)) + ) = + Pair( + selection.bestMatch(slots), + toContainer.findReplaceSlot(toStackSelection) + ) context(_: Automated) open fun findReplaceSlot( selection: StackSelection = StackSelection.ANYTHING - ) = selection.filter(slots) - .sortedWith(replaceSorter) - .firstOrNull() + ) = selection + .mutate { sortedWith(replaceSorter) } + .bestMatch(slots) context(automatedSafeContext: AutomatedSafeContext) internal fun swap(fromSlot: Slot, toSlot: Slot, toContainer: Container): Boolean { diff --git a/src/main/kotlin/com/lambda/interaction/container/containers/OffHandContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/OffhandContainer.kt similarity index 96% rename from src/main/kotlin/com/lambda/interaction/container/containers/OffHandContainer.kt rename to src/main/kotlin/com/lambda/interaction/container/containers/OffhandContainer.kt index 400af4860..b2b5fefb6 100644 --- a/src/main/kotlin/com/lambda/interaction/container/containers/OffHandContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/container/containers/OffhandContainer.kt @@ -28,7 +28,7 @@ import com.lambda.util.text.literal import net.minecraft.item.ItemStack import net.minecraft.screen.slot.Slot -object OffHandContainer : Container(ContainerType.OffHand) { +object OffhandContainer : Container(ContainerType.OffHand) { override val slots: List get() = mc.player?.offHandSlots ?: emptyList() override var stacks: List diff --git a/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt index 9a61e1a27..b0aeb2021 100644 --- a/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt @@ -81,8 +81,15 @@ data class ShulkerBoxContainer( ) : OpenContainerTask(description), Automated by automated { override fun SafeContext.onStart() { transfer( - stackSelection { predicate { _, slot -> slot?.index == index }; isItem(shulkerItem) }, - containedIn.select(), + stackSelection { + sortedWith { + compareByDescending { stackAndSlot -> stackAndSlot.slot?.index == index } + } + isItem(shulkerItem) + withName(itemName) + sortedByBestContentMatch(stacks) + }, + containedIn.select(ContainerSearchScope.Loaded), HotbarContainer.select() ).then { result -> placeContainer(findSlot(result.stackSelection, result.containerSelection)) } .then { pos -> diff --git a/src/main/kotlin/com/lambda/interaction/container/selection/ContainerSelection.kt b/src/main/kotlin/com/lambda/interaction/container/selection/ContainerSelection.kt index 3316a6afb..eae927955 100644 --- a/src/main/kotlin/com/lambda/interaction/container/selection/ContainerSelection.kt +++ b/src/main/kotlin/com/lambda/interaction/container/selection/ContainerSelection.kt @@ -21,29 +21,25 @@ import com.lambda.interaction.container.Container import com.lambda.interaction.container.ContainerDslMarker import com.lambda.interaction.container.ContainerType import com.lambda.interaction.container.NestedContainer -import com.lambda.interaction.container.containers.ArmorContainer -import com.lambda.interaction.container.containers.CreativeContainer -import com.lambda.interaction.container.containers.CursorContainer import com.lambda.interaction.container.containers.HotbarContainer import com.lambda.interaction.container.containers.InventoryContainer -import com.lambda.interaction.container.containers.OffHandContainer import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.containerSelection import com.lambda.interaction.handler.handlers.ContainerSearchScope @ContainerDslMarker fun Container.select( - scope: ContainerSearchScope = ContainerSearchScope.Accessed + scope: ContainerSearchScope = ContainerSearchScope.Loaded ) = containerSelection(scope) { ofAny(this@select) } @ContainerDslMarker fun Iterable.select( - scope: ContainerSearchScope = ContainerSearchScope.Accessed + scope: ContainerSearchScope = ContainerSearchScope.Loaded ) = containerSelection(scope) { ofAny(this@select) } @ContainerDslMarker fun selectContainers( vararg containers: Container, - scope: ContainerSearchScope = ContainerSearchScope.Accessed + scope: ContainerSearchScope = ContainerSearchScope.Loaded ) = containerSelection(scope) { ofAny(*containers) } /** @@ -52,7 +48,7 @@ fun selectContainers( @Suppress("unused") class ContainerSelection @ContainerDslMarker internal constructor( val selector: (Container) -> Boolean, - val scope: ContainerSearchScope = ContainerSearchScope.Accessed, + val scope: ContainerSearchScope = ContainerSearchScope.Loaded, val containersWhitelist: Collection = emptyList(), val loadedContainers: Collection = emptyList(), val containersBlacklist: Collection = emptyList(), @@ -81,16 +77,13 @@ class ContainerSelection @ContainerDslMarker internal constructor( companion object { val ACCESSED = ContainerSelection({ true }) val NOTHING = ContainerSelection({ false }) - val HOTBAR_AND_INVENTORY = selectContainers(HotbarContainer, InventoryContainer) - val PLAYER = + val HOTBAR_AND_INVENTORY = selectContainers( HotbarContainer, InventoryContainer, - OffHandContainer, - CursorContainer, - ArmorContainer, - CreativeContainer + scope = ContainerSearchScope.Player ) + val PLAYER = containerSelection(scope = ContainerSearchScope.Player) } } @@ -236,8 +229,8 @@ class ContainerSelectionBuilder @ContainerDslMarker private constructor( companion object { fun containerSelection( - scope: ContainerSearchScope = ContainerSearchScope.Accessed, - builder: ContainerSelectionBuilder.() -> Unit + scope: ContainerSearchScope = ContainerSearchScope.Loaded, + builder: ContainerSelectionBuilder.() -> Unit = {} ) = ContainerSelectionBuilder(scope).apply(builder).build() fun ContainerSelection.mutate( diff --git a/src/main/kotlin/com/lambda/interaction/container/selection/StackSelection.kt b/src/main/kotlin/com/lambda/interaction/container/selection/StackSelection.kt index 19716278e..94765357f 100644 --- a/src/main/kotlin/com/lambda/interaction/container/selection/StackSelection.kt +++ b/src/main/kotlin/com/lambda/interaction/container/selection/StackSelection.kt @@ -62,6 +62,12 @@ class StackSelection @ContainerDslMarker internal constructor( @ContainerDslMarker fun bestMatch(slots: Iterable) = filter(slots).firstOrNull() + @ContainerDslMarker + fun match(stacks: Iterable) = stacks.find(::matches) + + @ContainerDslMarker + fun match(slots: Iterable) = slots.find(::matches) + @ContainerDslMarker fun matches(stack: ItemStack): Boolean { val matchesSelf = selector(stack, null) @@ -84,33 +90,45 @@ class StackSelection @ContainerDslMarker internal constructor( @ContainerDslMarker @JvmName("filter1") - fun filter(stacks: Iterable) = - stacks - .let { if (whitelistedSlots.isNotEmpty()) emptyList() else it } - .asSequence() - .filter(::matches) - .map { StackAndSlot(it, null) } - .sortedWith(comparator) - .map { it.stack } - .toList() + fun filter( + stacks: Iterable, + sorted: Boolean = true + ) = stacks + .let { if (whitelistedSlots.isNotEmpty()) emptyList() else it } + .asSequence() + .filter(::matches) + .let { + if (sorted) + it.map { StackAndSlot(it, null) } + .sortedWith(comparator) + .map { it.stack } + else it + } + .toList() @ContainerDslMarker @JvmName("filter2") - fun filter(slots: Iterable) = - slots - .let { slots -> - if (whitelistedSlots.isNotEmpty()) { - whitelistedSlots - .asSequence() - .filter { it in slots } - } else slots.asSequence() - } - .filter { it !in blacklistedSlots } - .filter(::matches) - .map { StackAndSlot(it.stack, it) } - .sortedWith(comparator) - .map { it.slot } - .toList() + fun filter( + slots: Iterable, + sorted: Boolean = true + ) = slots + .let { slots -> + if (whitelistedSlots.isNotEmpty()) { + whitelistedSlots + .asSequence() + .filter { it in slots } + } else slots.asSequence() + } + .filter { it !in blacklistedSlots } + .filter(::matches) + .let { + if (sorted) + it.map { StackAndSlot(it.stack, it) } + .sortedWith(comparator) + .map { it.slot } + else it + } + .toList() @ContainerDslMarker infix fun isIn(container: Container) = diff --git a/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt index 08c4ee113..e35a15050 100644 --- a/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt @@ -387,10 +387,10 @@ private fun searchContainers( val baseContainers = when (selection.scope) { - ContainerSearchScope.Player -> compiled - EnderChestContainer - ContainerSearchScope.Compiled -> compiled ContainerSearchScope.Loaded -> compiled + placedSeq + selection.loadedContainers ContainerSearchScope.Accessed -> placedSeq + compiled.filter { it.isAccessed } + ContainerSearchScope.Compiled -> compiled + ContainerSearchScope.Player -> compiled - EnderChestContainer ContainerSearchScope.All -> compiled + placedSeq + ContainerSerializer.serializedContainers.filter { diskContainer -> diskContainer.pos != ContainerHandler.accessedPlacedContainer?.pos @@ -403,9 +403,9 @@ private fun searchContainers( } enum class ContainerSearchScope { + Loaded, Accessed, Compiled, - Loaded, Player, All } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/handler/handlers/GlideHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/GlideHandler.kt index 2a8d1b498..2220f1cd1 100644 --- a/src/main/kotlin/com/lambda/interaction/handler/handlers/GlideHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/GlideHandler.kt @@ -20,7 +20,7 @@ package com.lambda.interaction.handler.handlers import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.containerSelection +import com.lambda.interaction.container.selection.ContainerSelection import com.lambda.interaction.container.selection.StackAndSlot import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection import com.lambda.module.modules.combat.AutoArmor @@ -49,7 +49,7 @@ object GlideHandler { it.stack.getEnchantment(Enchantments.UNBREAKING) }.thenByDescending { it.stack.getEnchantment(Enchantments.MENDING) - } + }.thenBy { it.slot?.index } } } val CHESTPLATE_SELECTION = @@ -74,7 +74,7 @@ object GlideHandler { it.stack.getEnchantment(Enchantments.UNBREAKING) }.thenByDescending { it.stack.getEnchantment(Enchantments.MENDING) - } + }.thenBy { it.slot?.index } } } @@ -173,8 +173,9 @@ object GlideHandler { if (!autoSwapChecking || canGlideAlready) return canGlideAlready val selection = if (fakeFly) CHESTPLATE_SELECTION else ELYTRA_SELECTION return with(ElytraFly) { - findContainer( - containerSelection { hasStack(selection) }, + findStack( + selection, + ContainerSelection.HOTBAR_AND_INVENTORY, false ) != null } diff --git a/src/main/kotlin/com/lambda/module/Module.kt b/src/main/kotlin/com/lambda/module/Module.kt index 593fdce38..a5ab332b1 100644 --- a/src/main/kotlin/com/lambda/module/Module.kt +++ b/src/main/kotlin/com/lambda/module/Module.kt @@ -26,8 +26,6 @@ import com.lambda.config.categories.ModuleCategory import com.lambda.config.entries.onValueChange import com.lambda.config.entries.onValueChangeUnsafe import com.lambda.config.settings.complex.Bind -import com.lambda.config.settings.complex.KeybindSetting.Companion.onPress -import com.lambda.config.settings.complex.KeybindSetting.Companion.onRelease import com.lambda.context.SafeContext import com.lambda.event.EventFlow.post import com.lambda.event.EventFlow.updateListenerSorting diff --git a/src/main/kotlin/com/lambda/module/modules/combat/AutoArmor.kt b/src/main/kotlin/com/lambda/module/modules/combat/AutoArmor.kt index b5a5518d5..9152e2d82 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/AutoArmor.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/AutoArmor.kt @@ -20,16 +20,16 @@ package com.lambda.module.modules.combat import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.hideAllExcept import com.lambda.config.settings.complex.Bind -import com.lambda.config.settings.complex.KeybindSetting.Companion.onPress import com.lambda.config.withEdits import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.container.containers.ArmorContainer import com.lambda.interaction.container.selection.ContainerSelection +import com.lambda.interaction.container.selection.StackAndSlot +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection import com.lambda.interaction.container.selection.select -import com.lambda.interaction.handler.handlers.findContainer -import com.lambda.interaction.handler.handlers.findContainers +import com.lambda.interaction.handler.handlers.findSlots import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module import com.lambda.module.ModuleTag @@ -65,51 +65,50 @@ object AutoArmor : Module( var overriddenElytraPriority: Boolean? = null - val SORTER = compareByDescending { - if (it.stack.isDamageable && 1 - (it.stack.damage.toFloat() / it.stack.maxDamage) < minDurabilityPercentage.toFloat() / 100) - -Double.MAX_VALUE - else 0.0 - }.thenByDescending { - if (overriddenElytraPriority ?: elytraPriority) { - if (it.stack.item == Items.ELYTRA) 1.0 + val SORTER = + compareByDescending> { + if (it.stack.isDamageable && 1 - (it.stack.damage.toFloat() / it.stack.maxDamage) < minDurabilityPercentage.toFloat() / 100) + -Double.MAX_VALUE else 0.0 - } else 0.0 - }.thenByDescending { - it.stack.getOrDefault(DataComponentTypes.ATTRIBUTE_MODIFIERS, null) - ?.modifiers - ?.find { modifier -> modifier.attribute == EntityAttributes.ARMOR } - ?.modifier?.value - ?: 0.0 - }.thenByDescending { - it.stack.getOrDefault(DataComponentTypes.ATTRIBUTE_MODIFIERS, null) - ?.modifiers - ?.find { modifier -> modifier.attribute == EntityAttributes.ARMOR_TOUGHNESS } - ?.modifier?.value - ?: 0.0 - }.thenByDescending { - val stack = it.stack - when { - stack.isIn(ItemTags.FOOT_ARMOR) -> stack.getEnchantment(feetProtection.enchant) - stack.isIn(ItemTags.LEG_ARMOR) -> stack.getEnchantment(legProtection.enchant) - stack.isIn(ItemTags.CHEST_ARMOR) -> stack.getEnchantment(chestProtection.enchant) - else -> stack.getEnchantment(headProtection.enchant) - } - }.thenByDescending { slot -> - Protection.entries.fold(0) { acc, protection -> - acc + slot.stack.getEnchantment(protection.enchant) + }.thenByDescending { + if (overriddenElytraPriority ?: elytraPriority) { + if (it.stack.item == Items.ELYTRA) 1.0 + else 0.0 + } else 0.0 + }.thenByDescending { + it.stack.getOrDefault(DataComponentTypes.ATTRIBUTE_MODIFIERS, null) + ?.modifiers + ?.find { modifier -> modifier.attribute == EntityAttributes.ARMOR } + ?.modifier?.value + ?: 0.0 + }.thenByDescending { + it.stack.getOrDefault(DataComponentTypes.ATTRIBUTE_MODIFIERS, null) + ?.modifiers + ?.find { modifier -> modifier.attribute == EntityAttributes.ARMOR_TOUGHNESS } + ?.modifier?.value + ?: 0.0 + }.thenByDescending { + val stack = it.stack + when { + stack.isIn(ItemTags.FOOT_ARMOR) -> stack.getEnchantment(feetProtection.enchant) + stack.isIn(ItemTags.LEG_ARMOR) -> stack.getEnchantment(legProtection.enchant) + stack.isIn(ItemTags.CHEST_ARMOR) -> stack.getEnchantment(chestProtection.enchant) + else -> stack.getEnchantment(headProtection.enchant) + } + }.thenByDescending { stackAndSlot -> + Protection.entries.fold(0) { acc, protection -> + acc + stackAndSlot.stack.getEnchantment(protection.enchant) + } + }.thenByDescending { stackAndSlot -> + stackAndSlot.stack.getEnchantment(Enchantments.UNBREAKING) + + stackAndSlot.stack.getEnchantment(Enchantments.MENDING) + }.thenByDescending { stackAndSlot -> + stackAndSlot.slot?.index in 0..8 } - }.thenByDescending { slot -> - slot.stack.getEnchantment(Enchantments.UNBREAKING) + - slot.stack.getEnchantment(Enchantments.MENDING) - }.thenByDescending { slot -> - slot.index in 0..8 - } init { setDefaultAutomationConfig() - .withEdits { - hideAllExcept(::inventoryConfig) - } + .withEdits { hideAllExcept(::inventoryConfig) } listen { if (!tickedThisTick) tick() @@ -122,25 +121,33 @@ object AutoArmor : Module( fun SafeContext.tick() { tickedThisTick = true - val armorSlots = findContainer(containerSelection = ArmorContainer.select())?.slots ?: return + val armorSlots = findSlots(containerSelection = ArmorContainer.select()) + .toList() + .takeUnless { it.isEmpty() } + ?: return - val swappable = findContainers(containerSelection = ContainerSelection.HOTBAR_AND_INVENTORY) - .flatMap { it.slots } - .filter { - it.stack.isEquipable(armorSlots) && - (!ignoreBinding || it.stack.getEnchantment(Enchantments.BINDING_CURSE) <= 0) - } - .sortedWith(SORTER) + val swappable = + findSlots( + stackSelection { + predicate { stack, _ -> stack.isEquipable(armorSlots) } + if (ignoreBinding) withoutEnchantment(Enchantments.BINDING_CURSE) + sortedWith(SORTER) + }, + ContainerSelection.HOTBAR_AND_INVENTORY + ) .distinctBy { it.stack.armorSlot } val swaps = mutableListOf>() - armorSlots.forEach { equipped -> + armorSlots + .map { StackAndSlot(it.stack, it) } + .forEach { equipped -> val new = swappable.find { new -> - equipped.canInsert(new.stack) && SORTER.compare(equipped, new) > 0 + equipped.slot.canInsert(new.stack) && + SORTER.compare(equipped, StackAndSlot(new.stack, new)) > 0 } ?: return@forEach - swaps.add(Pair(new, equipped)) + swaps.add(Pair(new, equipped.slot)) } if (swaps.isEmpty()) return diff --git a/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt b/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt index 75902dad3..0de55253a 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt @@ -29,7 +29,7 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.container.containers.HotbarContainer import com.lambda.interaction.container.containers.InventoryContainer -import com.lambda.interaction.container.containers.OffHandContainer +import com.lambda.interaction.container.containers.OffhandContainer import com.lambda.interaction.container.selection.select import com.lambda.interaction.handler.handlers.findSlot import com.lambda.interaction.handler.handlers.move @@ -485,29 +485,31 @@ object CrystalAura : Module( val selection = Items.END_CRYSTAL.select() - if ((swapHand == Hand.MAIN_HAND && player.mainHandStack.item != selection.item) || - (swapHand == Hand.OFF_HAND && player.offHandStack.item != selection.item) + if ((swapHand == Hand.MAIN_HAND && !selection.matches(player.mainHandStack)) || + (swapHand == Hand.OFF_HAND && !selection.matches(player.offHandStack)) ) runSafeAutomated { if (!swap) return@runSafe val toContainerSelection = when (swapHand) { Hand.MAIN_HAND -> HotbarContainer - Hand.OFF_HAND -> OffHandContainer + Hand.OFF_HAND -> OffhandContainer }.select() val crystalSlot = findSlot(selection, toContainerSelection) - if (crystalSlot == null) { - if (!selection.move(InventoryContainer.select(), toContainerSelection)) return@runSafe - } + if (crystalSlot == null && + !selection.move(InventoryContainer.select(), toContainerSelection) + ) { + return@runSafe + } if (swapHand == Hand.MAIN_HAND) { val crystalSlot = selection .bestMatch(HotbarContainer.slots) ?.index ?: return@runSafe - if (crystalSlot < 0 || !hotbarRequest(crystalSlot).submit().done) return@runSafe + if (!hotbarRequest(crystalSlot).submit().done) return@runSafe } } diff --git a/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt b/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt index 0e140935c..4a8b41d9a 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt @@ -73,8 +73,9 @@ object PlayerTrap : Module( task = tickingBlueprint { val selection = stackSelection { + isItem() predicate { stack, _ -> - stack.item is BlockItem && blocks.contains(stack.item.block) + blocks.contains(stack.item.block) } } @@ -94,7 +95,11 @@ object PlayerTrap : Module( }.build(finishOnDone = false) .start() } - onDisable { task?.cancel(); task = null } + + onDisable { + task?.cancel() + task = null + } } fun SafeContext.getTrapPositions(player: PlayerEntity): Set { diff --git a/src/main/kotlin/com/lambda/module/modules/debug/StateInfo.kt b/src/main/kotlin/com/lambda/module/modules/debug/StateInfo.kt index 1789ca0ca..97d40e693 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/StateInfo.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/StateInfo.kt @@ -17,7 +17,6 @@ package com.lambda.module.modules.debug -import com.lambda.config.settings.complex.KeybindSetting.Companion.onPress import com.lambda.module.Module import com.lambda.module.ModuleTag import com.lambda.util.BlockUtils.blockState diff --git a/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt b/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt index b2ae33205..d929dd916 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt @@ -23,16 +23,16 @@ import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.editSetting import com.lambda.config.hideAllExcept import com.lambda.config.settings.complex.Bind -import com.lambda.config.settings.complex.KeybindSetting.Companion.onPress import com.lambda.config.withEdits import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.container.containers.HotbarContainer import com.lambda.interaction.container.containers.InventoryContainer -import com.lambda.interaction.container.selection.ContainerSelection +import com.lambda.interaction.container.containers.OffhandContainer import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection import com.lambda.interaction.container.selection.select +import com.lambda.interaction.container.selection.selectContainers import com.lambda.interaction.handler.handlers.GlideHandler import com.lambda.interaction.handler.handlers.findStack import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest @@ -61,15 +61,16 @@ object BetterFirework : Module( .onPress { if (mc.crosshairTarget?.type == HitResult.Type.BLOCK && !middleClickCancel && - activateButton.mouse == Lambda.mc.options.pickItemKey.boundKey.code) return@onPress + activateButton.mouse == Lambda.mc.options.pickItemKey.boundKey.code + ) return@onPress - if (!player.hasFireworks) { + if (!hasFireworks) { warn("You need to have fireworks in your inventory to use this module!") return@onPress } // Prevent using multiple times if (takeoffState != TakeoffState.Idle) return@onPress - // If already gliding use another firework + // If already gliding, use another firework if (player.canStartGliding || player.isGliding) takeoffState = TakeoffState.StartFlying else if (player.canTakeoff) takeoffState = TakeoffState.Jumping } @@ -85,12 +86,12 @@ object BetterFirework : Module( private var takeoffState = TakeoffState.Idle - val ClientPlayerEntity.hasFireworks: Boolean - get() = - findStack( - Items.FIREWORK_ROCKET.select(), - ContainerSelection.HOTBAR_AND_INVENTORY - ) != null || offHandStack.item == Items.FIREWORK_ROCKET + val hasFireworks: Boolean + get() = findStack( + Items.FIREWORK_ROCKET.select(), + selectContainers(HotbarContainer, InventoryContainer, OffhandContainer), + sorted = false + ) != null context(_: SafeContext) private val ClientPlayerEntity.canTakeoff: Boolean diff --git a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt index cd1eb6e30..3724161df 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt @@ -104,6 +104,7 @@ abstract class ElytraFlyMode( }.submit() if (!hotbarRequest.done) return false } + fun InvRequestBuilder.swapChest() { if (elytraInHotbar) { interaction.interactItem(player, Hand.MAIN_HAND) diff --git a/src/main/kotlin/com/lambda/module/modules/player/AutoElytraSwap.kt b/src/main/kotlin/com/lambda/module/modules/player/AutoElytraSwap.kt index a2fc3f1ed..dd9f3fe47 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/AutoElytraSwap.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/AutoElytraSwap.kt @@ -19,11 +19,12 @@ package com.lambda.module.modules.player import com.lambda.context.SafeContext import com.lambda.interaction.container.containers.ArmorContainer +import com.lambda.interaction.container.selection.ContainerSelection import com.lambda.interaction.handler.handlers.GlideHandler.CHESTPLATE_SELECTION import com.lambda.interaction.handler.handlers.GlideHandler.ELYTRA_SELECTION import com.lambda.interaction.handler.handlers.GlideHandler.manuallySwapped import com.lambda.interaction.handler.handlers.GlideHandler.swapped -import com.lambda.interaction.handler.handlers.findSlots +import com.lambda.interaction.handler.handlers.findSlot import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module import com.lambda.module.ModuleTag @@ -46,14 +47,14 @@ object AutoElytraSwap : Module( fun manualSwap(elytra: Boolean): Boolean { val swapSlot = - findSlots( + findSlot( if (elytra) ELYTRA_SELECTION - else CHESTPLATE_SELECTION - ).minByOrNull { it.index } - ?: run { - AutoElytraSwap.warn("The required armor piece was not found for AutoElytraSwap to work.") - return false - } + else CHESTPLATE_SELECTION, + ContainerSelection.HOTBAR_AND_INVENTORY + ) ?: run { + AutoElytraSwap.warn("The required armor piece was not found for AutoElytraSwap to work.") + return false + } return swapWithChestplate(swapSlot) } diff --git a/src/main/kotlin/com/lambda/module/modules/player/ClickFriend.kt b/src/main/kotlin/com/lambda/module/modules/player/ClickFriend.kt index a1adaea38..e264d9983 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/ClickFriend.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/ClickFriend.kt @@ -18,7 +18,6 @@ package com.lambda.module.modules.player import com.lambda.config.settings.complex.Bind -import com.lambda.config.settings.complex.KeybindSetting.Companion.onPress import com.lambda.context.SafeContext import com.lambda.interaction.handler.handlers.FriendHandler import com.lambda.interaction.handler.handlers.FriendHandler.befriend diff --git a/src/main/kotlin/com/lambda/module/modules/player/Replay.kt b/src/main/kotlin/com/lambda/module/modules/player/Replay.kt index c5135903e..ba5b93e3e 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/Replay.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/Replay.kt @@ -27,7 +27,6 @@ import com.google.gson.JsonSerializationContext import com.google.gson.JsonSerializer import com.lambda.brigadier.CommandResult import com.lambda.config.blocks.RotationConfig -import com.lambda.config.settings.complex.KeybindSetting.Companion.onPress import com.lambda.context.SafeContext import com.lambda.event.EventFlow.lambdaScope import com.lambda.event.events.MovementEvent diff --git a/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt b/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt index 59eb6e0ca..8f1def9f0 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt @@ -26,8 +26,6 @@ import com.lambda.config.forEachSetting import com.lambda.config.hide import com.lambda.config.hideBlock import com.lambda.config.settings.complex.Bind -import com.lambda.config.settings.complex.KeybindSetting.Companion.onPress -import com.lambda.config.settings.complex.KeybindSetting.Companion.onRelease import com.lambda.config.withEdits import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent @@ -36,7 +34,7 @@ import com.lambda.graphics.mc.renderer.ImmediateRenderer.Companion.immediateRend import com.lambda.graphics.util.DirectionMask import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.container.containers.HotbarContainer -import com.lambda.interaction.container.selection.ContainerSelection +import com.lambda.interaction.container.containers.InventoryContainer import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection import com.lambda.interaction.container.selection.select import com.lambda.interaction.handler.handlers.BaritoneHandler @@ -64,7 +62,6 @@ import com.lambda.util.math.lerp import com.lambda.util.math.setAlpha import com.lambda.util.math.vec3d import net.minecraft.block.Blocks -import net.minecraft.item.FlintAndSteelItem import net.minecraft.item.Items import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket import net.minecraft.network.packet.c2s.play.PlayerInteractBlockC2SPacket @@ -78,7 +75,7 @@ import net.minecraft.util.math.Vec3d @Suppress("unused") object AutoPortal : Module( name = "AutoPortal", - description = "Automatically places and lights a nether portal", + description = "Automatically builds a nether portal", tag = ModuleTag.WORLD ) { private const val RENDER_GROUP = "Renders" @@ -278,7 +275,7 @@ object AutoPortal : Module( } } - private class LightTask( + private class LightTask @Ta5kBuilder constructor( private val pos: BlockPos, private val walkIn: Boolean ) : Task() { @@ -325,12 +322,12 @@ object AutoPortal : Module( return } - val selection = stackSelection(1) { isItem() } + val selection = Items.FLINT_AND_STEEL.select() - val hotbarSlot = findSlot(selection, HotbarContainer.select()) - if (hotbarSlot != null) { + val hotbarIndex = selection.bestMatch(HotbarContainer.slots)?.index + if (hotbarIndex != null) { val request = - hotbarRequest(hotbarSlot.index) { + hotbarRequest(hotbarIndex) { keepTicks(0) }.submit(false) if (request.done) block() @@ -338,16 +335,17 @@ object AutoPortal : Module( } val invSlot = - if (inventory) findSlot(selection, ContainerSelection.HOTBAR_AND_INVENTORY) + if (inventory) findSlot(selection, InventoryContainer.select()) else null if (invSlot == null) { failure("No Flint and Steel!") return } val hotbarSlotToSwapWith = - HotbarContainer.slots.find { slot -> - slot.stack.isEmpty - }?.index ?: 8 + findSlot( + stackSelection(0) { isEmpty() }, + HotbarContainer.select() + )?.index ?: 8 inventoryRequest { swapWithHotbar(invSlot.id, hotbarSlotToSwapWith) diff --git a/src/main/kotlin/com/lambda/module/modules/world/AutoVillagerCycle.kt b/src/main/kotlin/com/lambda/module/modules/world/AutoVillagerCycle.kt index 6ee1bc20c..29bb84b6c 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/AutoVillagerCycle.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/AutoVillagerCycle.kt @@ -21,7 +21,6 @@ import com.lambda.config.Group import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.hideAllExcept import com.lambda.config.settings.complex.Bind -import com.lambda.config.settings.complex.KeybindSetting.Companion.onPress import com.lambda.config.withEdits import com.lambda.context.SafeContext import com.lambda.event.events.PacketEvent 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 7354bf66e..333061543 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/Scaffold.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/Scaffold.kt @@ -80,7 +80,7 @@ object Scaffold : Module( } } val stack = player.mainHandStack.takeIf { selection.matches(it) } - ?: selection.filter(HotbarContainer.stacks).firstOrNull() ?: return@listen + ?: selection.bestMatch(HotbarContainer.stacks) ?: return@listen val playerSupport = player.blockPos.down() val alreadySupported = blockState(playerSupport).hasSolidTopSurface(world, playerSupport, player) if (alreadySupported) return@listen 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 4c29df3f8..b1490bcd7 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt @@ -26,7 +26,6 @@ import com.lambda.config.entries.onValueChange import com.lambda.config.hide import com.lambda.config.hideBlock import com.lambda.config.settings.complex.Bind -import com.lambda.config.settings.complex.KeybindSetting.Companion.onPress import com.lambda.config.withEdits import com.lambda.context.SafeContext import com.lambda.event.events.ButtonEvent @@ -40,12 +39,12 @@ import com.lambda.graphics.mc.renderer.TickedRenderer.Companion.tickedRenderer import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.container.containers.HotbarContainer import com.lambda.interaction.container.containers.InventoryContainer -import com.lambda.interaction.container.containers.OffHandContainer +import com.lambda.interaction.container.containers.OffhandContainer import com.lambda.interaction.container.containers.external.EnderChestContainer import com.lambda.interaction.container.selection.ContainerSelection import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection +import com.lambda.interaction.container.selection.select import com.lambda.interaction.handler.handlers.BaritoneHandler -import com.lambda.interaction.handler.handlers.findContainers import com.lambda.interaction.handler.handlers.findSlots import com.lambda.interaction.handler.handlers.findStack import com.lambda.interaction.handler.handlers.findStacks @@ -460,8 +459,12 @@ object StashMover : Module( MoverState.DispensingPearl -> handleDispensingPearl() MoverState.AwaitingPearl -> checkTimerProgress(MoverState.DispensingPearl, pearlButtonTimeout) { - val hasPearl = findContainers(containerSelection = ContainerSelection.HOTBAR_AND_INVENTORY) - .any { it.stacks.any { it.item == Items.ENDER_PEARL } } + val hasPearl = + findStack( + Items.ENDER_PEARL.select(), + ContainerSelection.HOTBAR_AND_INVENTORY, + sorted = false + ) != null if (hasPearl) { pearlThrown = false moverState = MoverState.ThrowingPearl @@ -509,7 +512,13 @@ object StashMover : Module( if (moverState != MoverState.AwaitingTeleport) return@listen val packet = event.packet if (packet !is PlayerPositionLookS2CPacket) return@listen - if (finished && findStacks(containerSelection = ContainerSelection.HOTBAR_AND_INVENTORY).all { it.isEmpty }) { + val allMoved = + findStack( + stackSelection { notEmpty() }, + ContainerSelection.HOTBAR_AND_INVENTORY, + sorted = false + ) == null + if (finished && allMoved) { success(finishedMessage) return@listen } @@ -577,7 +586,7 @@ object StashMover : Module( pulledContainers.clear() moverState = MoverState.MessagingForPearl } - .execute(this) + .start() } private fun SafeContext.handleMessagingForPearl() { @@ -644,9 +653,7 @@ object StashMover : Module( screenHandler.containerSlots, findStacks(containerSelection = ContainerSelection.HOTBAR_AND_INVENTORY).toList() ) - if (move) { - putOrThrowItems() - } + if (move) putOrThrowItems() } private fun SafeContext.handleDispensingPearl() { @@ -673,7 +680,7 @@ object StashMover : Module( tickTimer.reset() moverState = MoverState.AwaitingPearl } - ?.execute(this@MoverBot) + ?.start() } private fun SafeContext.handleThrowingPearl() { @@ -691,7 +698,7 @@ object StashMover : Module( failWithLog("No free slots to return the offhand stack to!", ::failure) return } - val offhandSlot = OffHandContainer.slots.firstOrNull() ?: run { failWithLog("No offhand slot? This shouldn't occur.", ::failure); return } + val offhandSlot = OffhandContainer.slots.firstOrNull() ?: run { failWithLog("No offhand slot? This shouldn't occur.", ::failure); return } inventoryRequest { quickMove(offhandSlot.id) }.submit() } putOrThrowItems() @@ -775,7 +782,7 @@ object StashMover : Module( openContainer(pos).thenAction { onOpened(pos) - }.execute(this@MoverBot) + }.start() } private fun SafeContext.moveFromContainerToContainer( @@ -848,7 +855,7 @@ object StashMover : Module( ?.thenAction { pearlState = PearlState.Waiting } - ?.execute(this@PearlBot) + ?.start() } else -> {} } diff --git a/src/main/kotlin/com/lambda/task/tasks/AcquirePlacedBlockTask.kt b/src/main/kotlin/com/lambda/task/tasks/AcquirePlacedBlockTask.kt index 3547c047d..dc36b22be 100644 --- a/src/main/kotlin/com/lambda/task/tasks/AcquirePlacedBlockTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/AcquirePlacedBlockTask.kt @@ -21,8 +21,9 @@ import com.lambda.context.Automated import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.container.containers.ArmorContainer.findSlot +import com.lambda.interaction.container.selection.ContainerSelection import com.lambda.interaction.container.selection.select +import com.lambda.interaction.handler.handlers.findSlot import com.lambda.task.Task import com.lambda.task.Task.Ta5kBuilder import com.lambda.task.tasks.wrappers.softFail @@ -48,16 +49,16 @@ class AcquirePlacedBlockTask @Ta5kBuilder internal constructor( init { listen { runSafeAutomated { - findSlot(block.item.select()).let { slot -> - PlaceContainerTask(slot, this) - .onSuccess { success(it) } - .execute(this@AcquirePlacedBlockTask) - return@listen - } - } + val slot = findSlot(block.item.select(), ContainerSelection.HOTBAR_AND_INVENTORY) + ?: run { + failure("No $block found in range or hotbar/inventory!") + return@listen + } - failure("No $block found in range or hotbar/inventory!") - return@listen + placeContainer(slot) + .onSuccess { success(it) } + .start() + } } } diff --git a/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt b/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt index 19bc25c21..cdac5988e 100644 --- a/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt @@ -26,12 +26,12 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.container.selection.ContainerSelection import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection import com.lambda.interaction.handler.handlers.BaritoneHandler +import com.lambda.interaction.handler.handlers.findSlot import com.lambda.interaction.handler.handlers.findStack import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.task.Task import com.lambda.task.Task.Ta5kBuilder import com.lambda.threading.runSafeAutomated -import com.lambda.util.extension.playerSlots import net.minecraft.entity.ItemEntity @Ta5kBuilder @@ -78,15 +78,18 @@ class CollectDropsTask @Ta5kBuilder internal constructor( } if (findStack(stackSelection(0) { isEmpty() }, ContainerSelection.HOTBAR_AND_INVENTORY, false) == null) { - val stackToThrow = - player.currentScreenHandler.playerSlots.firstOrNull { - it.stack.item in inventoryConfig.disposables - } ?: run { + val slotToThrow = + findSlot( + stackSelection { ofAnyItems(inventoryConfig.disposables) }, + ContainerSelection.HOTBAR_AND_INVENTORY, + sorted = false + ) ?: run { failure("Inventory is full and no disposable items to throw") return true } + inventoryRequest { - throwStack(stackToThrow.id) + throwStack(slotToThrow.id) }.submit() return true } diff --git a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt index 8ab79138a..b1fe2a21f 100644 --- a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt @@ -28,7 +28,6 @@ import com.lambda.interaction.container.selection.ContainerSelection import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.containerSelection import com.lambda.interaction.container.selection.StackSelection import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.mutate -import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection import com.lambda.interaction.container.selection.select import com.lambda.interaction.container.selection.selectContainers import com.lambda.interaction.handler.handlers.findContainers @@ -38,6 +37,8 @@ import com.lambda.task.tasks.wrappers.taskOrNull import com.lambda.task.tasks.wrappers.then import com.lambda.task.tasks.wrappers.thenOrNull import com.lambda.threading.runSafeAutomated +import com.lambda.util.item.ItemStackUtils.equal +import net.minecraft.item.ItemStack import net.minecraft.screen.slot.Slot @Ta5kBuilder @@ -73,7 +74,7 @@ class ContainerTransferTask @Ta5kBuilder internal constructor( override val name = "Transferring $fromStack" private var transferred = 0 - private val resultSlots = mutableListOf() + private val resultSlots = mutableMapOf() private val resultContainers = mutableSetOf() override fun SafeContext.onStart() { @@ -107,22 +108,33 @@ class ContainerTransferTask @Ta5kBuilder internal constructor( } private fun remainingSelection() = - fromStack.mutate(count = if (fromStack.count <= 0) 0 else fromStack.count - transferred) + fromStack.mutate( + if (fromStack.count <= 0) 0 + else fromStack.count - transferred + ) - private fun isComplete() = - (fromStack.count <= 0 && transferred > 0) || (fromStack.count != 0 && transferred >= fromStack.count) + private fun isComplete() = transferred >= fromStack.count private fun buildResult() = TransferResult( - stackSelection { - predicate { _, slot -> - slot != null && - resultSlots.any { - slot.inventory::class == it.inventory::class && slot.index == it.index - } + fromStack.mutate { + sortedWith { + compareByDescending { stackAndSlot -> + val slot = stackAndSlot.slot + slot != null && + resultSlots.entries.find { + slot.index == it.key.index + }?.let { moveEntry -> + slot.inventory::class == moveEntry.key.inventory::class && + moveEntry.key.stack.equal(moveEntry.value) + } == true + } } }, - selectContainers(*resultContainers.toTypedArray()) + selectContainers( + containers = resultContainers.toTypedArray(), + scope = toSelection.scope + ) ) /** @@ -214,8 +226,8 @@ class ContainerTransferTask @Ta5kBuilder internal constructor( if (destination.spaceLeft(fromStack) == 0) toQueue.remove(destination) - val playerHasItems = findContainers(ContainerSelection.HOTBAR_AND_INVENTORY) - .any { container -> fromStack.mutate(1) isIn container } + val playerHasItems = + fromStack.mutate(1) isIn findContainers(ContainerSelection.HOTBAR_AND_INVENTORY).toList() if (!playerHasItems) return@onSuccess val nextDestination = toQueue.firstOrNull() ?: return@onSuccess @@ -291,7 +303,7 @@ class ContainerTransferTask @Ta5kBuilder internal constructor( transferred += moveCount if (trackResult) { - resultSlots.add(toSlot) + resultSlots[toSlot] = toSlot.stack resultContainers.add(toContainer) } diff --git a/src/main/kotlin/com/lambda/task/tasks/EatTask.kt b/src/main/kotlin/com/lambda/task/tasks/EatTask.kt index 1e987d79e..d2b1548e1 100644 --- a/src/main/kotlin/com/lambda/task/tasks/EatTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/EatTask.kt @@ -71,7 +71,7 @@ class EatTask @Ta5kBuilder internal constructor( }.submit() if (!request.done) return@listen } else { - if (InventoryContainer.slots.any { selection.matches(it) }) { + if (selection isIn InventoryContainer) { runSafeAutomated { transfer( selection, diff --git a/src/main/kotlin/com/lambda/task/tasks/wrappers/BranchTasks.kt b/src/main/kotlin/com/lambda/task/tasks/wrappers/BranchTasks.kt index 1228310e9..617c52e16 100644 --- a/src/main/kotlin/com/lambda/task/tasks/wrappers/BranchTasks.kt +++ b/src/main/kotlin/com/lambda/task/tasks/wrappers/BranchTasks.kt @@ -52,14 +52,14 @@ class BranchTask @Ta5kBuilder internal constructor( .onSuccess { result -> onSuccessGenerator(this, result) .onSuccess { success(it) } - .execute(this@BranchTask) + .start() } .onFailure { cause -> onFailureGenerator(this, cause) .onSuccess { success(it) } - .execute(this@BranchTask) + .start() } - .execute(this@BranchTask) + .start() } override fun onSubTaskFailure(subTask: Task<*>, cause: Throwable) { @@ -84,16 +84,16 @@ class OptionalBranchTask @Ta5kBuilder internal constructor( .onSuccess { result -> onSuccessGenerator(this, result) ?.onSuccess { success(it) } - ?.execute(this@OptionalBranchTask) + ?.start() ?: success(null) } .onFailure { cause -> onFailureGenerator(this, cause) ?.onSuccess { success(it) } - ?.execute(this@OptionalBranchTask) + ?.start() ?: failure("Optional onFailure task not present for task: ${innerTask.name}") } - .execute(this@OptionalBranchTask) + .start() } override fun onSubTaskFailure(subTask: Task<*>, cause: Throwable) { diff --git a/src/main/kotlin/com/lambda/task/tasks/wrappers/OnFailTasks.kt b/src/main/kotlin/com/lambda/task/tasks/wrappers/OnFailTasks.kt index a2b242777..4a6f7cc10 100644 --- a/src/main/kotlin/com/lambda/task/tasks/wrappers/OnFailTasks.kt +++ b/src/main/kotlin/com/lambda/task/tasks/wrappers/OnFailTasks.kt @@ -60,9 +60,9 @@ class OnFailTask @Ta5kBuilder internal constructor( .onFailure { cause -> onFailTaskGenerator(cause) .onSuccess { success(null) } - .execute(this@OnFailTask) + .start() } - .execute(this@OnFailTask) + .start() } override fun onSubTaskFailure(subTask: Task<*>, cause: Throwable) { @@ -87,7 +87,7 @@ class OnFailOrNullTask @Ta5kBuilder internal constructor( override fun SafeContext.onStart() { innerTask .onSuccess { success(it) } - .execute(this@OnFailOrNullTask) + .start() } override fun onSubTaskFailure(subTask: Task<*>, cause: Throwable) { @@ -95,7 +95,7 @@ class OnFailOrNullTask @Ta5kBuilder internal constructor( runSafe { onFailTaskGenerator(cause) ?.onSuccess { success(null) } - ?.execute(this@OnFailOrNullTask) + ?.start() ?: super.onSubTaskFailure(subTask, cause) } } else super.onSubTaskFailure(subTask, cause) @@ -123,9 +123,9 @@ class RecoveryTask @Ta5kBuilder internal constructor( .onFailure { cause -> recoveryTaskGenerator(this, cause) .onSuccess { success(it) } - .execute(this@RecoveryTask) + .start() } - .execute(this@RecoveryTask) + .start() } override fun onSubTaskFailure(subTask: Task<*>, cause: Throwable) { @@ -151,7 +151,7 @@ class OptionalRecoveryTask @Ta5kBuilder internal constructor( override fun SafeContext.onStart() { innerTask .onSuccess { success(it) } - .execute(this@OptionalRecoveryTask) + .start() } override fun onSubTaskFailure(subTask: Task<*>, cause: Throwable) { @@ -159,7 +159,7 @@ class OptionalRecoveryTask @Ta5kBuilder internal constructor( runSafe { recoveryTaskGenerator(this, cause) ?.onSuccess { success(it) } - ?.execute(this@OptionalRecoveryTask) + ?.start() ?: super.onSubTaskFailure(subTask, cause) } } else super.onSubTaskFailure(subTask, cause) diff --git a/src/main/kotlin/com/lambda/task/tasks/wrappers/SequenceTasks.kt b/src/main/kotlin/com/lambda/task/tasks/wrappers/SequenceTasks.kt index ea00d449d..00f27af01 100644 --- a/src/main/kotlin/com/lambda/task/tasks/wrappers/SequenceTasks.kt +++ b/src/main/kotlin/com/lambda/task/tasks/wrappers/SequenceTasks.kt @@ -64,9 +64,9 @@ class SequencedTask @Ta5kBuilder internal constructor( .onSuccess { result -> second = nextTaskGenerator(this, result) .onSuccess { success(it) } - .execute(this@SequencedTask) + .start() } - .execute(this@SequencedTask) + .start() } } @@ -88,10 +88,10 @@ class OptionalSequencedTask @Ta5kBuilder internal constructor( .onSuccess { result -> nextTaskGenerator(this, result) ?.onSuccess { success(it) } - ?.execute(this@OptionalSequencedTask) + ?.start() ?: success(null) } - .execute(this@OptionalSequencedTask) + .start() } } @@ -110,7 +110,7 @@ class TaskOrNullTask @Ta5kBuilder internal constructor( override fun SafeContext.onStart() { taskOrNullSupplier() ?.onSuccess { success(it) } - ?.execute(this@TaskOrNullTask) + ?.start() ?: success(null) } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/task/tasks/wrappers/SoftFailTask.kt b/src/main/kotlin/com/lambda/task/tasks/wrappers/SoftFailTask.kt index 5a0cb6004..863840d48 100644 --- a/src/main/kotlin/com/lambda/task/tasks/wrappers/SoftFailTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/wrappers/SoftFailTask.kt @@ -40,7 +40,7 @@ class SoftFailTask @Ta5kBuilder internal constructor( override fun SafeContext.onStart() { innerTask .onSuccess { success(it) } - .execute(this@SoftFailTask) + .start() } override fun onSubTaskFailure(subTask: Task<*>, cause: Throwable) { From 9b92f5fba319faf63f14bf406faea7748ec95d63 Mon Sep 17 00:00:00 2001 From: beanbag44 <107891830+beanbag44@users.noreply.github.com> Date: Tue, 15 Sep 2026 09:51:43 +0100 Subject: [PATCH 18/43] more improvements --- .../construction/verify/TargetState.kt | 17 +++++---- .../lambda/module/modules/combat/AutoTotem.kt | 32 +++++++--------- .../module/modules/player/AutoElytraSwap.kt | 22 ++++++----- .../module/modules/player/StackReplenish.kt | 37 ++++++++++++------- .../lambda/module/modules/player/ToolSaver.kt | 13 +++---- .../lambda/module/modules/world/StashMover.kt | 6 +-- .../task/tasks/ContainerTransferTask.kt | 18 +++------ .../kotlin/com/lambda/task/tasks/EatTask.kt | 3 +- 8 files changed, 75 insertions(+), 73 deletions(-) diff --git a/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt b/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt index be94ce2f8..e3e71183a 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt @@ -19,7 +19,8 @@ package com.lambda.interaction.construction.verify import com.lambda.context.AutomatedSafeContext import com.lambda.context.SafeContext -import com.lambda.interaction.handler.handlers.findContainerWithDisposable +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection +import com.lambda.interaction.handler.handlers.findStack import com.lambda.util.BlockUtils.blockState import com.lambda.util.BlockUtils.emptyState import com.lambda.util.BlockUtils.isEmpty @@ -86,10 +87,12 @@ sealed class TargetState : StateMatcher { context(automatedSafeContext: AutomatedSafeContext) override fun getStack(pos: BlockPos) = with(automatedSafeContext) { - findContainerWithDisposable() - ?.stacks - ?.firstOrNull { it.item in inventoryConfig.disposables } - ?: ItemStack(Items.NETHERRACK) + findStack( + stackSelection { + ofAnyItems(inventoryConfig.disposables) + predicate { stack, _ -> stack.item.block !in replace } + } + ) ?: ItemStack(Items.NETHERRACK) } context(_: AutomatedSafeContext) @@ -114,9 +117,7 @@ sealed class TargetState : StateMatcher { context(automatedSafeContext: AutomatedSafeContext) override fun getStack(pos: BlockPos) = with(automatedSafeContext) { - findContainerWithDisposable() - ?.stacks - ?.firstOrNull { it.item in inventoryConfig.disposables } + findStack(stackSelection { ofAnyItems(inventoryConfig.disposables) }) ?: ItemStack(Items.NETHERRACK) } diff --git a/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt b/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt index 517625872..bb9a5e893 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt @@ -23,11 +23,14 @@ import com.lambda.config.withEdits import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.container.containers.OffhandContainer +import com.lambda.interaction.container.selection.ContainerSelection import com.lambda.interaction.container.selection.select import com.lambda.interaction.handler.handlers.FriendHandler -import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest +import com.lambda.interaction.handler.handlers.move import com.lambda.module.Module import com.lambda.module.ModuleTag +import com.lambda.threading.runSafeAutomated import com.lambda.util.combat.CombatUtils.hasDeadlyCrystal import com.lambda.util.combat.DamageUtils.isFallDeadly import com.lambda.util.extension.fullHealth @@ -57,27 +60,20 @@ object AutoTotem : Module( init { setDefaultAutomationConfig() - .withEdits { - hideAllExcept(::inventoryConfig) - } + .withEdits { hideAllExcept(::inventoryConfig) } listen { if (!always && Reason.entries.none { it.check(this) }) return@listen - if ((!ignoreWhenHolding || !player.isHolding(Items.TOTEM_OF_UNDYING)) && player.offHandStack.item != Items.TOTEM_OF_UNDYING) { - Items.TOTEM_OF_UNDYING.select() - .filter(player.currentScreenHandler.slots) - .takeIf { it.isNotEmpty() } - ?.let { totems -> - val cursor = player.currentScreenHandler.cursorStack - val targetSlot = player.currentScreenHandler.slots - .findLast { !cursor.isEmpty && it.canInsert(cursor) } - - inventoryRequest { - targetSlot?.let { pickup(it.id, 0) } - swapWithHotbar(totems.first().id, 40) - }.submit() - } + val swapWhileHolding = !ignoreWhenHolding || player.mainHandStack.item != Items.TOTEM_OF_UNDYING + if (swapWhileHolding && player.offHandStack.item != Items.TOTEM_OF_UNDYING) { + runSafeAutomated { + Items.TOTEM_OF_UNDYING.select() + .move( + ContainerSelection.HOTBAR_AND_INVENTORY, + OffhandContainer.select() + ) + } } } } diff --git a/src/main/kotlin/com/lambda/module/modules/player/AutoElytraSwap.kt b/src/main/kotlin/com/lambda/module/modules/player/AutoElytraSwap.kt index dd9f3fe47..6ffa13cd2 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/AutoElytraSwap.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/AutoElytraSwap.kt @@ -19,16 +19,20 @@ package com.lambda.module.modules.player import com.lambda.context.SafeContext import com.lambda.interaction.container.containers.ArmorContainer +import com.lambda.interaction.container.containers.HotbarContainer +import com.lambda.interaction.container.containers.InventoryContainer import com.lambda.interaction.container.selection.ContainerSelection +import com.lambda.interaction.container.selection.select import com.lambda.interaction.handler.handlers.GlideHandler.CHESTPLATE_SELECTION import com.lambda.interaction.handler.handlers.GlideHandler.ELYTRA_SELECTION import com.lambda.interaction.handler.handlers.GlideHandler.manuallySwapped import com.lambda.interaction.handler.handlers.GlideHandler.swapped import com.lambda.interaction.handler.handlers.findSlot -import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest +import com.lambda.interaction.handler.handlers.findSlots import com.lambda.module.Module import com.lambda.module.ModuleTag import com.lambda.module.modules.combat.AutoArmor +import com.lambda.threading.runSafeAutomated import com.lambda.util.CommunicationUtils.warn import com.lambda.util.player.PlayerUtils.canGlideWithChestPiece import net.minecraft.screen.slot.Slot @@ -60,14 +64,14 @@ object AutoElytraSwap : Module( } private fun swapWithChestplate(swapSlot: Slot): Boolean { - val chestplateSlot = ArmorContainer.slots.getOrNull(1) ?: return false - return AutoElytraSwap.inventoryRequest { - if (swapSlot.index in 0..8) swapWithHotbar(chestplateSlot.id, swapSlot.index) - else { - moveSlot(swapSlot.id, chestplateSlot.id) - if (!chestplateSlot.stack.isEmpty) pickup(swapSlot.id) - } - }.submit(false).done + val chestplateSlot = findSlots(containerSelection = ArmorContainer.select()) + .toList() + .getOrNull(1) + ?: return false + val sourceContainer = if (swapSlot.index in 0..8) HotbarContainer else InventoryContainer + return runSafeAutomated { + ArmorContainer.swap(chestplateSlot, swapSlot, sourceContainer) + } ?: false } context(safeContext: SafeContext) diff --git a/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt b/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt index 4e4d97de9..65534711d 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt @@ -21,17 +21,20 @@ import com.lambda.config.automation.setDefaultAutomationConfig import com.lambda.config.hide import com.lambda.config.hideAllExcept import com.lambda.config.withEdits -import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.container.containers.HotbarContainer import com.lambda.interaction.container.containers.InventoryContainer +import com.lambda.interaction.container.containers.OffhandContainer +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection +import com.lambda.interaction.container.selection.select +import com.lambda.interaction.handler.handlers.findSlots import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module import com.lambda.module.ModuleTag -import com.lambda.util.item.ItemStackUtils.slotId import net.minecraft.item.ItemStack import net.minecraft.item.Items +import net.minecraft.screen.slot.Slot @Suppress("unused") object StackReplenish : Module( @@ -53,23 +56,31 @@ object StackReplenish : Module( listen { if (player.currentScreenHandler.cursorStack.item !== Items.AIR) return@listen - HotbarContainer.stacks.forEach { stack -> checkReplenish(stack) } - if (offhand) checkReplenish(player.offHandStack) + findSlots(containerSelection = HotbarContainer.select()).forEach { slot -> checkReplenish(slot) } + if (offhand) findSlots(containerSelection = OffhandContainer.select()).forEach { slot -> checkReplenish(slot) } } } - private fun SafeContext.checkReplenish(stack: ItemStack) { + private fun checkReplenish(targetSlot: Slot) { + val stack = targetSlot.stack + if (stack.isEmpty || !stack.isStackable) return if (stack.count.toFloat() / stack.maxCount >= (minStackPercent.toFloat() / 100)) return - if (!stack.isStackable) return - InventoryContainer.stacks.forEach { invStack -> - if (!ItemStack.areItemsAndComponentsEqual(invStack, stack)) return@forEach - val invId = invStack.slotId - val completing = stack.count + invStack.count >= stack.maxCount - val tooMany = invStack.count + stack.count > stack.maxCount + val replenishSlots = + findSlots( + stackSelection { + predicate { s, _ -> ItemStack.areItemsAndComponentsEqual(s, stack) } + }, + InventoryContainer.select() + ) + + replenishSlots.forEach { replenishSlot -> + val invStack = replenishSlot.stack + val completing = targetSlot.stack.count + invStack.count >= stack.maxCount + val tooMany = targetSlot.stack.count + invStack.count > stack.maxCount inventoryRequest { - moveSlot(invId, stack.slotId) - if (tooMany) pickup(invId) + moveSlot(replenishSlot.id, targetSlot.id) + if (tooMany) pickup(replenishSlot.id) }.submit() if (completing) return } diff --git a/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt b/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt index ffaecdeb2..eb2d43d52 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt @@ -25,9 +25,9 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.container.containers.HotbarContainer import com.lambda.interaction.container.containers.InventoryContainer -import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest import com.lambda.module.Module import com.lambda.module.ModuleTag +import com.lambda.threading.runSafeAutomated import com.lambda.util.EnchantmentUtils.forEachEnchantment import com.lambda.util.EnchantmentUtils.getEnchantment import net.minecraft.item.ItemStack @@ -82,14 +82,11 @@ object ToolSaver : Module( if (swaps.isEmpty()) return@listen - inventoryRequest { - swaps.forEach { - pickup(it.first.id) - pickup(it.second.id) - if (!it.second.stack.isEmpty) - pickup(it.first.id) + runSafeAutomated { + swaps.forEach { (endangered, swapWith) -> + HotbarContainer.swap(endangered, swapWith, InventoryContainer) } - }.submit() + } } listen { event -> 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 b1490bcd7..83f23010a 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt @@ -45,6 +45,7 @@ import com.lambda.interaction.container.selection.ContainerSelection import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection import com.lambda.interaction.container.selection.select import com.lambda.interaction.handler.handlers.BaritoneHandler +import com.lambda.interaction.handler.handlers.findSlot import com.lambda.interaction.handler.handlers.findSlots import com.lambda.interaction.handler.handlers.findStack import com.lambda.interaction.handler.handlers.findStacks @@ -74,7 +75,6 @@ import com.lambda.util.extension.containerStacks 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.text.bold import com.lambda.util.text.buildText import com.lambda.util.text.color @@ -711,12 +711,12 @@ object StashMover : Module( }.submit() if (!rotationRequest.done) return if (player.mainHandStack.item != Items.ENDER_PEARL) { - val hotbarSlot = HotbarContainer.slots.firstOrNull { it.stack.item === Items.ENDER_PEARL } + val hotbarSlot = findSlot(Items.ENDER_PEARL.select(), HotbarContainer.select()) if (hotbarSlot != null) { val hotbarRequest = hotbarRequest(hotbarSlot.index).submit() if (!hotbarRequest.done) return } else { - val inventorySlot = player.allSlots.firstOrNull { it.stack.item === Items.ENDER_PEARL } + val inventorySlot = findSlot(Items.ENDER_PEARL.select(), InventoryContainer.select()) if (inventorySlot == null) { failWithLog("No pearl in inventory!", ::failure) return diff --git a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt index b1fe2a21f..093abf4e9 100644 --- a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt @@ -25,7 +25,7 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.container.Container import com.lambda.interaction.container.ExternalContainer import com.lambda.interaction.container.selection.ContainerSelection -import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.containerSelection +import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.mutate import com.lambda.interaction.container.selection.StackSelection import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.mutate import com.lambda.interaction.container.selection.select @@ -79,12 +79,8 @@ class ContainerTransferTask @Ta5kBuilder internal constructor( override fun SafeContext.onStart() { val fromContainers = - findContainers( - containerSelection(fromSelection.scope) { - matches(fromSelection) - hasStack(fromStack.mutate(1)) - } - ).toList() + findContainers(fromSelection.mutate { hasStack(fromStack.mutate(1)) }) + .toList() .takeIf { fromStack isIn it } ?: run { failure("Could not find source containers for $fromStack") @@ -92,12 +88,8 @@ class ContainerTransferTask @Ta5kBuilder internal constructor( } val toContainers = - findContainers( - containerSelection(toSelection.scope) { - matches(toSelection) - hasStack(toStack.mutate(1)) - } - ).toList() + findContainers(toSelection.mutate { hasStack(toStack.mutate(1)) }) + .toList() .takeIf { toStack isIn it } ?: run { failure("Could not find destination containers for $toStack") diff --git a/src/main/kotlin/com/lambda/task/tasks/EatTask.kt b/src/main/kotlin/com/lambda/task/tasks/EatTask.kt index d2b1548e1..f2938289a 100644 --- a/src/main/kotlin/com/lambda/task/tasks/EatTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/EatTask.kt @@ -26,6 +26,7 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.container.containers.HotbarContainer import com.lambda.interaction.container.containers.InventoryContainer import com.lambda.interaction.container.selection.select +import com.lambda.interaction.handler.handlers.findSlot import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest import com.lambda.task.Task import com.lambda.task.Task.Ta5kBuilder @@ -63,7 +64,7 @@ class EatTask @Ta5kBuilder internal constructor( } val selection = reason.selector() - val hotbarSlot = selection.bestMatch(HotbarContainer.slots) + val hotbarSlot = findSlot(selection, HotbarContainer.select()) if (hotbarSlot != null) { val request = hotbarRequest(hotbarSlot.index) { From 4b0c4d06b8544dc1cb9abff56c53d75ab8c6ee4f Mon Sep 17 00:00:00 2001 From: beanbag44 <107891830+beanbag44@users.noreply.github.com> Date: Tue, 15 Sep 2026 12:13:05 +0100 Subject: [PATCH 19/43] try to move away from direct slot retrieval so containers can be filtered out if disabled in the config. Also handles sorting and other misc tweaks --- .../serializers/EnderChestContainerSerializer.kt | 12 ++++++------ .../serializers/PlacedContainerSerializer.kt | 12 ++++++------ .../com/lambda/module/modules/combat/KillAura.kt | 12 ++++++++---- .../module/modules/movement/BetterFirework.kt | 10 ++++++++-- .../modules/movement/elytrafly/ElytraFlyMode.kt | 7 ++++++- .../elytrafly/modes/GrimControlElytraFly.kt | 10 +++++++--- .../com/lambda/module/modules/player/ToolSaver.kt | 15 ++++++++++++--- .../com/lambda/module/modules/world/Scaffold.kt | 4 +++- .../com/lambda/module/modules/world/StashMover.kt | 6 +++++- 9 files changed, 61 insertions(+), 27 deletions(-) diff --git a/src/main/kotlin/com/lambda/config/serializers/EnderChestContainerSerializer.kt b/src/main/kotlin/com/lambda/config/serializers/EnderChestContainerSerializer.kt index 8601077c5..28c650439 100644 --- a/src/main/kotlin/com/lambda/config/serializers/EnderChestContainerSerializer.kt +++ b/src/main/kotlin/com/lambda/config/serializers/EnderChestContainerSerializer.kt @@ -39,12 +39,12 @@ object EnderChestContainerSerializer : Serializer(EnderChes root.put("Type", ContainerType.EnderChest.name) val stackArray = JsonNodeFactory.instance.arrayNode() - for (stack in container.stacks) { - val encoded = ItemStack.CODEC - .encodeStart(JsonOps.UNCOMPRESSED, stack) - .result() - encoded.ifPresent { stackArray.add(it) } - } + container.stacks.forEach { stack -> + val encoded = ItemStack.CODEC + .encodeStart(JsonOps.UNCOMPRESSED, stack) + .result() + encoded.ifPresent { stackArray.add(it) } + } root.set("Stacks", stackArray) gen.writeTree(root) diff --git a/src/main/kotlin/com/lambda/config/serializers/PlacedContainerSerializer.kt b/src/main/kotlin/com/lambda/config/serializers/PlacedContainerSerializer.kt index 062915fab..1a6c9af20 100644 --- a/src/main/kotlin/com/lambda/config/serializers/PlacedContainerSerializer.kt +++ b/src/main/kotlin/com/lambda/config/serializers/PlacedContainerSerializer.kt @@ -44,12 +44,12 @@ object PlacedContainerSerializer : Serializer(PlacedContainer:: } val stackArray = JsonNodeFactory.instance.arrayNode() - for (stack in container.stacks) { - val encoded = ItemStack.CODEC - .encodeStart(JsonOps.UNCOMPRESSED, stack) - .result() - encoded.ifPresent { stackArray.add(it) } - } + container.stacks.forEach { stack -> + val encoded = ItemStack.CODEC + .encodeStart(JsonOps.UNCOMPRESSED, stack) + .result() + encoded.ifPresent { stackArray.add(it) } + } root.set("Stacks", stackArray) gen.writeTree(root) diff --git a/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt b/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt index d9962e06e..a72df33f1 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt @@ -30,6 +30,8 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.container.containers.HotbarContainer import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection +import com.lambda.interaction.container.selection.select +import com.lambda.interaction.handler.handlers.findSlot import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.Module @@ -128,10 +130,12 @@ object KillAura : Module( } } - selection.bestMatch(HotbarContainer.stacks)?.let { bestStack -> - val slotId = HotbarContainer.stacks.indexOf(bestStack) - if (!hotbarRequest(slotId).submit().done) return@listen - } + val slot = + findSlot( + selection, + HotbarContainer.select() + ) ?: return@listen + if (!hotbarRequest(slot.index).submit().done) return@listen } if (!rotated) return@listen diff --git a/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt b/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt index d929dd916..b16111c5a 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt @@ -34,6 +34,7 @@ import com.lambda.interaction.container.selection.StackSelectionBuilder.Companio import com.lambda.interaction.container.selection.select import com.lambda.interaction.container.selection.selectContainers import com.lambda.interaction.handler.handlers.GlideHandler +import com.lambda.interaction.handler.handlers.findSlot import com.lambda.interaction.handler.handlers.findStack import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest @@ -171,8 +172,13 @@ object BetterFirework : Module( if (!inventory) return - val inventoryMatch = selection.bestMatch(InventoryContainer.slots) ?: return - val hotbarSlot = stackSelection { isEmpty() }.bestMatch(HotbarContainer.slots)?.index ?: 8 + val inventoryMatch = findSlot(containerSelection = InventoryContainer.select()) ?: return + val hotbarSlot = + findSlot( + stackSelection { isEmpty() }, + HotbarContainer.select() + )?.index + ?: 8 inventoryRequest { swapWithHotbar(inventoryMatch.id, hotbarSlot) diff --git a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt index 3724161df..1ab2929e8 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt @@ -26,8 +26,10 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.container.containers.ArmorContainer import com.lambda.interaction.container.selection.ContainerSelection +import com.lambda.interaction.container.selection.select import com.lambda.interaction.handler.handlers.GlideHandler.ELYTRA_SELECTION import com.lambda.interaction.handler.handlers.findSlot +import com.lambda.interaction.handler.handlers.findSlots import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest @@ -95,7 +97,10 @@ abstract class ElytraFlyMode( } val elytraInHotbar = elytraSlot.index in 0..8 - val chestSlot = ArmorContainer.slots.getOrNull(1) ?: return false + val chestSlot = findSlots(containerSelection = ArmorContainer.select()) + .toList() + .getOrNull(1) + ?: return false if (elytraInHotbar) { val hotbarRequest = diff --git a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/GrimControlElytraFly.kt b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/GrimControlElytraFly.kt index bf3bc8b28..974e5d6e9 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/GrimControlElytraFly.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/GrimControlElytraFly.kt @@ -25,6 +25,7 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.container.containers.HotbarContainer import com.lambda.interaction.container.containers.InventoryContainer import com.lambda.interaction.container.selection.select +import com.lambda.interaction.handler.handlers.findStack import com.lambda.interaction.manager.managers.rotating.RotationManager import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest import com.lambda.module.modules.movement.BetterFirework.startFirework @@ -155,7 +156,7 @@ class GrimControlElytraFly( !player.isGliding || flipFlopMode.isFlipFlopping(hasFirework) || moving - ) return@listen + ) return@listen if (stillTickTimer.hasSurpassed(packetGap)) { stillTickTimer.reset() return@listen @@ -165,8 +166,11 @@ class GrimControlElytraFly( } private fun findFirework(): ItemStack? { - val stack = Items.FIREWORK_ROCKET.select() - return stack.bestMatch(HotbarContainer.stacks) ?: if (inventory) stack.bestMatch(InventoryContainer.stacks) else null + val selection = Items.FIREWORK_ROCKET.select() + return findStack(selection, HotbarContainer.select(), false) + ?: if (inventory) { + findStack(selection, InventoryContainer.select(), false) + } else null } override fun pausingMovement() = still diff --git a/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt b/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt index eb2d43d52..1562a92ed 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt @@ -25,6 +25,9 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.container.containers.HotbarContainer import com.lambda.interaction.container.containers.InventoryContainer +import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection +import com.lambda.interaction.container.selection.select +import com.lambda.interaction.handler.handlers.findSlots import com.lambda.module.Module import com.lambda.module.ModuleTag import com.lambda.threading.runSafeAutomated @@ -49,10 +52,15 @@ object ToolSaver : Module( } listen { - val endangeredStacks = HotbarContainer.slots.filter { it.stack.isEndangered } + val endangeredSlots = + findSlots( + stackSelection { predicate { stack, _ -> stack.isEndangered } }, + HotbarContainer.select() + ) - val inventorySlots = InventoryContainer.slots - val swaps = endangeredStacks + val inventorySlots = findSlots(containerSelection = InventoryContainer.select()) + + val swaps = endangeredSlots .mapNotNull { endangered -> val sorter = compareByDescending { swapSlot -> if (!replace) 0 @@ -79,6 +87,7 @@ object ToolSaver : Module( ?: return@mapNotNull null endangered to swapWith } + .toList() if (swaps.isEmpty()) return@listen 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 333061543..15911be0f 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/Scaffold.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/Scaffold.kt @@ -32,6 +32,8 @@ import com.lambda.interaction.construction.simulation.sim import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.container.containers.HotbarContainer import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection +import com.lambda.interaction.container.selection.select +import com.lambda.interaction.handler.handlers.findStack import com.lambda.interaction.manager.managers.interacting.interactRequest import com.lambda.module.Module import com.lambda.module.ModuleTag @@ -80,7 +82,7 @@ object Scaffold : Module( } } val stack = player.mainHandStack.takeIf { selection.matches(it) } - ?: selection.bestMatch(HotbarContainer.stacks) ?: return@listen + ?: findStack(containerSelection = HotbarContainer.select()) ?: return@listen val playerSupport = player.blockPos.down() val alreadySupported = blockState(playerSupport).hasSolidTopSurface(world, playerSupport, player) if (alreadySupported) return@listen 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 83f23010a..f5d3fa8db 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt @@ -664,7 +664,11 @@ object StashMover : Module( } if (BaritoneHandler.isActive) return if (HotbarContainer.stacks.none { it.isEmpty }) { - val firstSlot = HotbarContainer.slots.getOrNull(0) ?: run { failWithLog("No first slot? This shouldn't occur.", ::failure); return } + val firstSlot = findSlot(containerSelection = HotbarContainer.select()) + ?: run { + failWithLog("No first slot? This shouldn't occur.", ::failure) + return + } if (InventoryContainer.stacks.any { it.isEmpty }) { inventoryRequest { quickMove(firstSlot.id) }.submit() return From 7b70bc40a7a932aa39cf91745f0a09f90b748bc7 Mon Sep 17 00:00:00 2001 From: beanbag44 <107891830+beanbag44@users.noreply.github.com> Date: Tue, 15 Sep 2026 20:18:29 +0100 Subject: [PATCH 20/43] cleanup/fixes --- .../lambda/mixin/MinecraftClientMixin.java | 3 +- .../lambda/mixin/input/KeyBindingMixin.java | 1 - .../EnderChestContainerSerializer.kt | 13 ++-- .../serializers/PlacedContainerSerializer.kt | 67 +++++++++---------- .../handler/handlers/ContainerHandler.kt | 25 +++++-- .../lambda/module/modules/world/StashMover.kt | 1 + .../kotlin/com/lambda/util/FolderRegistry.kt | 2 +- 7 files changed, 58 insertions(+), 54 deletions(-) diff --git a/src/main/java/com/lambda/mixin/MinecraftClientMixin.java b/src/main/java/com/lambda/mixin/MinecraftClientMixin.java index fe7c740ae..d20a73c7a 100644 --- a/src/main/java/com/lambda/mixin/MinecraftClientMixin.java +++ b/src/main/java/com/lambda/mixin/MinecraftClientMixin.java @@ -17,11 +17,11 @@ package com.lambda.mixin; -import com.lambda.interaction.handler.handlers.TimerHandler; import com.lambda.event.EventFlow; import com.lambda.event.events.*; import com.lambda.gui.DearImGui; import com.lambda.gui.components.ClickGuiLayout; +import com.lambda.interaction.handler.handlers.TimerHandler; import com.lambda.module.modules.movement.BetterFirework; import com.lambda.module.modules.player.Interact; import com.lambda.module.modules.player.InventoryMove; @@ -141,6 +141,7 @@ private void onScreenOpen(@Nullable Screen screen, CallbackInfo ci) { } } + @SuppressWarnings("DiscouragedShift") @Inject(method = "setScreen", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/Screen;removed()V", shift = At.Shift.BEFORE)) private void onScreenRemove(@Nullable Screen screen, CallbackInfo ci) { if (currentScreen == null) return; diff --git a/src/main/java/com/lambda/mixin/input/KeyBindingMixin.java b/src/main/java/com/lambda/mixin/input/KeyBindingMixin.java index 24954cb22..39cd5d969 100644 --- a/src/main/java/com/lambda/mixin/input/KeyBindingMixin.java +++ b/src/main/java/com/lambda/mixin/input/KeyBindingMixin.java @@ -19,7 +19,6 @@ import com.lambda.module.modules.movement.Speed; import com.lambda.module.modules.movement.Sprint; -import com.lambda.module.modules.movement.TargetStrafe; import com.llamalad7.mixinextras.injector.ModifyReturnValue; import net.minecraft.client.option.KeyBinding; import org.spongepowered.asm.mixin.Mixin; diff --git a/src/main/kotlin/com/lambda/config/serializers/EnderChestContainerSerializer.kt b/src/main/kotlin/com/lambda/config/serializers/EnderChestContainerSerializer.kt index 28c650439..fda34376e 100644 --- a/src/main/kotlin/com/lambda/config/serializers/EnderChestContainerSerializer.kt +++ b/src/main/kotlin/com/lambda/config/serializers/EnderChestContainerSerializer.kt @@ -30,24 +30,23 @@ import tools.jackson.core.JsonParser import tools.jackson.databind.DeserializationContext import tools.jackson.databind.SerializationContext import tools.jackson.databind.node.ArrayNode -import tools.jackson.databind.node.JsonNodeFactory import tools.jackson.databind.node.ObjectNode object EnderChestContainerSerializer : Serializer(EnderChestContainer::class.java) { override fun serialize(container: EnderChestContainer, gen: JsonGenerator, ctxt: SerializationContext) { - val root = JsonNodeFactory.instance.objectNode() - root.put("Type", ContainerType.EnderChest.name) + gen.writeStartObject() + gen.writeStringProperty("Type", ContainerType.EnderChest.name) - val stackArray = JsonNodeFactory.instance.arrayNode() + gen.writeArrayPropertyStart("Stacks") container.stacks.forEach { stack -> val encoded = ItemStack.CODEC .encodeStart(JsonOps.UNCOMPRESSED, stack) .result() - encoded.ifPresent { stackArray.add(it) } + encoded.ifPresent { gen.writePOJO(it) } } - root.set("Stacks", stackArray) + gen.writeEndArray() - gen.writeTree(root) + gen.writeEndObject() } } diff --git a/src/main/kotlin/com/lambda/config/serializers/PlacedContainerSerializer.kt b/src/main/kotlin/com/lambda/config/serializers/PlacedContainerSerializer.kt index 1a6c9af20..9af3d318d 100644 --- a/src/main/kotlin/com/lambda/config/serializers/PlacedContainerSerializer.kt +++ b/src/main/kotlin/com/lambda/config/serializers/PlacedContainerSerializer.kt @@ -2,9 +2,9 @@ package com.lambda.config.serializers -import com.lambda.config.Deserializer +import com.lambda.config.FallbackDeserializer +import com.lambda.config.FallbackSerializer import com.lambda.config.JsonOps -import com.lambda.config.Serializer import com.lambda.interaction.container.ContainerType import com.lambda.interaction.container.PlacedContainer import com.lambda.interaction.container.containers.external.ChestContainer @@ -20,51 +20,40 @@ import tools.jackson.core.JsonParser import tools.jackson.databind.DeserializationContext import tools.jackson.databind.SerializationContext import tools.jackson.databind.node.ArrayNode -import tools.jackson.databind.node.JsonNodeFactory import tools.jackson.databind.node.ObjectNode -object PlacedContainerSerializer : Serializer(PlacedContainer::class.java) { +object PlacedContainerSerializer : FallbackSerializer(PlacedContainer::class.java) { override fun serialize(container: PlacedContainer, gen: JsonGenerator, ctxt: SerializationContext) { - val root = JsonNodeFactory.instance.objectNode() - - root.put("Type", container.type.name) - root.set("Pos", blockPosNode(container.pos)) + gen.writeStartObject() + gen.writeStringProperty("Type", container.type.name) + gen.writePOJOProperty("Pos", BlockPos.CODEC.encodeStart(JsonOps.UNCOMPRESSED, container.pos).orThrow) when (container) { is PlacedShulkerBoxContainer -> { val blockId = Registries.BLOCK.getId(container.block).toString() - root.put("Block", blockId) + gen.writeStringProperty("Block", blockId) } is DoubleChestContainer -> { - root.put("Double", true) - root.set("Left Pos", blockPosNode(container.leftPos)) - root.set("Right Pos", blockPosNode(container.rightPos)) + gen.writeBooleanProperty("Double", true) + gen.writePOJOProperty("Left Pos", BlockPos.CODEC.encodeStart(JsonOps.UNCOMPRESSED, container.leftPos).orThrow) + gen.writePOJOProperty("Right Pos", BlockPos.CODEC.encodeStart(JsonOps.UNCOMPRESSED, container.rightPos).orThrow) } else -> {} } - - val stackArray = JsonNodeFactory.instance.arrayNode() + + gen.writeArrayPropertyStart("Stacks") container.stacks.forEach { stack -> val encoded = ItemStack.CODEC .encodeStart(JsonOps.UNCOMPRESSED, stack) .result() - encoded.ifPresent { stackArray.add(it) } + encoded.ifPresent { gen.writePOJO(it) } } - root.set("Stacks", stackArray) - - gen.writeTree(root) - } - - private fun blockPosNode(pos: BlockPos): ObjectNode { - val node = JsonNodeFactory.instance.objectNode() - node.put("X", pos.x) - node.put("Y", pos.y) - node.put("Z", pos.z) - return node + gen.writeEndArray() + gen.writeEndObject() } } -object PlacedContainerDeserializer : Deserializer(PlacedContainer::class.java) { +object PlacedContainerDeserializer : FallbackDeserializer(PlacedContainer::class.java) { override fun deserialize(p: JsonParser, ctxt: DeserializationContext): PlacedContainer? { val root = p.readValueAsTree() ?: return null @@ -72,7 +61,10 @@ object PlacedContainerDeserializer : Deserializer(PlacedContain val type = ContainerType.entries.find { it.name == typeName } ?: return null val posNode = root.get("Pos") as? ObjectNode ?: return null - val pos = parseBlockPos(posNode) ?: return null + val pos = BlockPos.CODEC.parse(JsonOps.UNCOMPRESSED, posNode) + .result() + .orElse(null) + ?: return null val stackArray = root.get("Stacks") as? ArrayNode val stacks = @@ -92,8 +84,16 @@ object PlacedContainerDeserializer : Deserializer(PlacedContain } ContainerType.Chest -> { if (root.get("Double")?.asBoolean() == true) { - val leftPos = (root.get("Left Pos") as? ObjectNode)?.let { parseBlockPos(it) } ?: pos - val rightPos = (root.get("Right Pos") as? ObjectNode)?.let { parseBlockPos(it) } ?: pos + val leftPosNode = root.get("Left Pos") as? ObjectNode + val leftPos = BlockPos.CODEC.parse(JsonOps.UNCOMPRESSED, leftPosNode) + .result() + .orElse(null) + ?: return null + val rightPosNode = root.get("Right Pos") as? ObjectNode + val rightPos = BlockPos.CODEC.parse(JsonOps.UNCOMPRESSED, rightPosNode) + .result() + .orElse(null) + ?: return null DoubleChestContainer(pos, leftPos, rightPos, stacks) } else { ChestContainer(pos, stacks) @@ -105,11 +105,4 @@ object PlacedContainerDeserializer : Deserializer(PlacedContain container.scanStacksForNestedContainers() return container } - - private fun parseBlockPos(node: ObjectNode): BlockPos? { - val x = node.get("X")?.asInt() ?: return null - val y = node.get("Y")?.asInt() ?: return null - val z = node.get("Z")?.asInt() ?: return null - return BlockPos(x, y, z) - } } diff --git a/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt index e35a15050..702adb85b 100644 --- a/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt @@ -30,6 +30,11 @@ import com.lambda.interaction.container.Container import com.lambda.interaction.container.ContainerDslMarker import com.lambda.interaction.container.ContainerSerializer import com.lambda.interaction.container.PlacedContainer +import com.lambda.interaction.container.containers.ArmorContainer +import com.lambda.interaction.container.containers.CursorContainer +import com.lambda.interaction.container.containers.HotbarContainer +import com.lambda.interaction.container.containers.InventoryContainer +import com.lambda.interaction.container.containers.OffhandContainer import com.lambda.interaction.container.containers.external.ChestContainer import com.lambda.interaction.container.containers.external.DoubleChestContainer import com.lambda.interaction.container.containers.external.EnderChestContainer @@ -55,7 +60,6 @@ import net.minecraft.block.entity.ChestBlockEntity import net.minecraft.block.entity.EnderChestBlockEntity import net.minecraft.block.entity.ShulkerBoxBlockEntity import net.minecraft.block.enums.ChestType -import net.minecraft.inventory.Inventory import net.minecraft.network.packet.c2s.play.PlayerInteractBlockC2SPacket import net.minecraft.screen.ScreenHandler import net.minecraft.screen.ScreenHandlerType @@ -142,11 +146,20 @@ object ContainerHandler : Loadable { } private fun SafeContext.onContainerUpdate(sh: ScreenHandler = player.currentScreenHandler) { + InventoryContainer.scanContainerContents() + HotbarContainer.scanContainerContents() + OffhandContainer.scanContainerContents() + CursorContainer.scanContainerContents() val blockEntity = lastInteractedBlockEntity - if (blockEntity != null) updatePlacedContainer(sh, blockEntity) + if (blockEntity != null) { + updatePlacedContainer(sh, blockEntity) + return + } + + ArmorContainer.scanContainerContents() } - private fun updatePlacedContainer( + private fun SafeContext.updatePlacedContainer( sh: ScreenHandler, blockEntity: BlockEntity ) { @@ -165,7 +178,7 @@ object ContainerHandler : Loadable { val state = blockEntity.cachedState if (state.block !is ChestBlock) return - val stacks = blockEntity.stacks() + val stacks = sh.containerStacks val chestType = state.get(Properties.CHEST_TYPE) ?: return if (chestType == ChestType.SINGLE) { @@ -200,7 +213,7 @@ object ContainerHandler : Loadable { is ShulkerBoxBlockEntity -> { if (sh.typeSafe != ScreenHandlerType.SHULKER_BOX) return - val stacks = blockEntity.stacks() + val stacks = sh.containerStacks (accessedPlacedContainer as? PlacedShulkerBoxContainer) ?.update(stacks) ?: run { @@ -231,8 +244,6 @@ object ContainerHandler : Loadable { } else storedContainers.remove(index) } } - - private fun Inventory.stacks() = iterator().asSequence().toList() } @ContainerDslMarker 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 f5d3fa8db..ef557c568 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt @@ -99,6 +99,7 @@ import org.lwjgl.glfw.GLFW import java.awt.Color import kotlin.math.min +//ToDo: Transform to use the updated container api alongside properly implementing StashContainer and using tasks for chaining instead of a state machine. @Suppress("unused") object StashMover : Module( name = "StashMover", diff --git a/src/main/kotlin/com/lambda/util/FolderRegistry.kt b/src/main/kotlin/com/lambda/util/FolderRegistry.kt index 7c284a79f..2f52fe878 100644 --- a/src/main/kotlin/com/lambda/util/FolderRegistry.kt +++ b/src/main/kotlin/com/lambda/util/FolderRegistry.kt @@ -35,7 +35,7 @@ object FolderRegistry : Loadable { val replay: Path = lambda.resolve("replay") val cache: Path = lambda.resolve("cache") val capes: Path = cache.resolve("capes") - val containers: Path = cache.resolve("containers") + val containers: Path = lambda.resolve("containers") val structure: Path = lambda.resolve("structure") val maps: Path = lambda.resolve("maps") val fonts: Path = lambda.resolve("fonts") From b1f7de76d267562cd923721793b1a695b202f7e9 Mon Sep 17 00:00:00 2001 From: beanbag44 <107891830+beanbag44@users.noreply.github.com> Date: Fri, 2 Oct 2026 01:18:38 +0100 Subject: [PATCH 21/43] big checkpoint --- .../ClientPlayInteractionManagerMixin.java | 6 + .../EnderChestContainerSerializer.kt | 41 +- .../serializers/PlacedContainerSerializer.kt | 53 +- .../com/lambda/event/events/PlayerEvent.kt | 10 +- .../simulation/result/Contextual.kt | 4 +- .../lambda/interaction/container/Container.kt | 78 ++- .../interaction/container/NestedContainer.kt | 6 + .../container/containers/CreativeContainer.kt | 1 + .../container/containers/CursorContainer.kt | 39 +- .../external/DoubleChestContainer.kt | 4 + .../container/selection/StackSelection.kt | 37 +- .../handler/handlers/ContainerHandler.kt | 18 +- .../handler/handlers/PacketLimitHandler.kt | 10 +- .../managers/inventory/InventoryManager.kt | 14 +- .../managers/inventory/InventoryRequest.kt | 9 +- .../modules/debug/ContainerTransferTest.kt | 152 +++++ .../com/lambda/module/modules/render/XRay.kt | 2 +- .../task/tasks/ContainerTransferTask.kt | 599 +++++++++++++----- src/main/kotlin/com/lambda/util/BlockUtils.kt | 325 +++++----- .../com/lambda/util/item/StackMovePlanner.kt | 144 +++++ src/test/kotlin/StackMovePlannerTest.kt | 115 ++++ 21 files changed, 1283 insertions(+), 384 deletions(-) create mode 100644 src/main/kotlin/com/lambda/module/modules/debug/ContainerTransferTest.kt create mode 100644 src/main/kotlin/com/lambda/util/item/StackMovePlanner.kt create mode 100644 src/test/kotlin/StackMovePlannerTest.kt diff --git a/src/main/java/com/lambda/mixin/entity/ClientPlayInteractionManagerMixin.java b/src/main/java/com/lambda/mixin/entity/ClientPlayInteractionManagerMixin.java index e13b3f459..717a10743 100644 --- a/src/main/java/com/lambda/mixin/entity/ClientPlayInteractionManagerMixin.java +++ b/src/main/java/com/lambda/mixin/entity/ClientPlayInteractionManagerMixin.java @@ -111,6 +111,12 @@ public void clickSlotHead(int syncId, int slotId, int button, SlotActionType act if (EventFlow.post(click).isCanceled()) ci.cancel(); } + @Inject(method = "clickSlot", at = @At("TAIL")) + public void clickSlotTail(int syncId, int slotId, int button, SlotActionType actionType, PlayerEntity player, CallbackInfo ci) { + if (syncId != player.currentScreenHandler.syncId) return; + EventFlow.post(new PlayerEvent.SlotClick.Post(syncId, slotId, button, actionType, player.currentScreenHandler)); + } + /** * Posts {@link InventoryEvent.HotbarSlot.Update} and returns the event value as the selected slot *
{@code
diff --git a/src/main/kotlin/com/lambda/config/serializers/EnderChestContainerSerializer.kt b/src/main/kotlin/com/lambda/config/serializers/EnderChestContainerSerializer.kt
index fda34376e..08c442617 100644
--- a/src/main/kotlin/com/lambda/config/serializers/EnderChestContainerSerializer.kt
+++ b/src/main/kotlin/com/lambda/config/serializers/EnderChestContainerSerializer.kt
@@ -37,13 +37,21 @@ object EnderChestContainerSerializer : Serializer(EnderChes
         gen.writeStartObject()
         gen.writeStringProperty("Type", ContainerType.EnderChest.name)
 
-	    gen.writeArrayPropertyStart("Stacks")
-	    container.stacks.forEach { stack ->
-		    val encoded = ItemStack.CODEC
-			    .encodeStart(JsonOps.UNCOMPRESSED, stack)
-			    .result()
-		    encoded.ifPresent { gen.writePOJO(it) }
-	    }
+        gen.writeArrayPropertyStart("Stacks")
+        container.stacks.forEach { stack ->
+            if (stack.isEmpty) {
+                gen.writeNull()
+            } else {
+                val encoded = ItemStack.CODEC
+                    .encodeStart(JsonOps.UNCOMPRESSED, stack)
+                    .result()
+                if (encoded.isPresent) {
+                    gen.writePOJO(encoded.get())
+                } else {
+                    gen.writeNull()
+                }
+            }
+        }
         gen.writeEndArray()
 
         gen.writeEndObject()
@@ -58,14 +66,23 @@ object EnderChestContainerDeserializer : Deserializer(Ender
     override fun deserialize(p: JsonParser, ctxt: DeserializationContext, intoValue: EnderChestContainer): EnderChestContainer {
         val root = p.readValueAsTree()
         val stackArray = root.get("Stacks") as? ArrayNode
-        val stacks =
+        val decodedStacks =
             stackArray?.mapNotNull { element ->
-                ItemStack.CODEC
-                    .parse(JsonOps.UNCOMPRESSED, element)
-                    .result()
-                    .orElse(null)
+                if (element.isNull) {
+                    ItemStack.EMPTY
+                } else {
+                    ItemStack.CODEC
+                        .parse(JsonOps.UNCOMPRESSED, element)
+                        .result()
+                        .orElse(ItemStack.EMPTY)
+                }
             } ?: emptyList()
 
+        val stacks =
+            if (decodedStacks.size < 27) {
+                decodedStacks + List(27 - decodedStacks.size) { ItemStack.EMPTY }
+            } else decodedStacks
+
         EnderChestContainer.update(stacks)
         EnderChestContainer.scanStacksForNestedContainers()
         return EnderChestContainer
diff --git a/src/main/kotlin/com/lambda/config/serializers/PlacedContainerSerializer.kt b/src/main/kotlin/com/lambda/config/serializers/PlacedContainerSerializer.kt
index 9af3d318d..abc91d173 100644
--- a/src/main/kotlin/com/lambda/config/serializers/PlacedContainerSerializer.kt
+++ b/src/main/kotlin/com/lambda/config/serializers/PlacedContainerSerializer.kt
@@ -42,12 +42,20 @@ object PlacedContainerSerializer : FallbackSerializer(PlacedCon
         }
 
         gen.writeArrayPropertyStart("Stacks")
-	    container.stacks.forEach { stack ->
-		    val encoded = ItemStack.CODEC
-			    .encodeStart(JsonOps.UNCOMPRESSED, stack)
-			    .result()
-		    encoded.ifPresent { gen.writePOJO(it) }
-	    }
+        container.stacks.forEach { stack ->
+            if (stack.isEmpty) {
+                gen.writeNull()
+            } else {
+                val encoded = ItemStack.CODEC
+                    .encodeStart(JsonOps.UNCOMPRESSED, stack)
+                    .result()
+                if (encoded.isPresent) {
+                    gen.writePOJO(encoded.get())
+                } else {
+                    gen.writeNull()
+                }
+            }
+        }
         gen.writeEndArray()
         gen.writeEndObject()
     }
@@ -60,43 +68,52 @@ object PlacedContainerDeserializer : FallbackDeserializer(Place
         val typeName = root.get("Type")?.asString() ?: return null
         val type = ContainerType.entries.find { it.name == typeName } ?: return null
         
-        val posNode = root.get("Pos") as? ObjectNode ?: return null
+        val posNode = root.get("Pos") ?: return null
         val pos = BlockPos.CODEC.parse(JsonOps.UNCOMPRESSED, posNode)
             .result()
             .orElse(null)
             ?: return null
         
         val stackArray = root.get("Stacks") as? ArrayNode
-        val stacks =
+        val decodedStacks =
             stackArray?.mapNotNull { element ->
-                ItemStack.CODEC
-                    .parse(JsonOps.UNCOMPRESSED, element)
-                    .result()
-                    .orElse(null)
+                if (element.isNull) {
+                    ItemStack.EMPTY
+                } else {
+                    ItemStack.CODEC
+                        .parse(JsonOps.UNCOMPRESSED, element)
+                        .result()
+                        .orElse(ItemStack.EMPTY)
+                }
             } ?: emptyList()
-        
+
+        fun padStacks(expectedSize: Int): List =
+            if (decodedStacks.size < expectedSize) {
+                decodedStacks + List(expectedSize - decodedStacks.size) { ItemStack.EMPTY }
+            } else decodedStacks
+
         val container =
             when (type) {
                 ContainerType.PlacedShulkerBox -> {
                     val blockId = root.get("Block")?.asString()
                     val block = blockId?.let { Registries.BLOCK.get(Identifier.of(it)) } ?: Blocks.SHULKER_BOX
-                    PlacedShulkerBoxContainer(pos, block, null, stacks)
+                    PlacedShulkerBoxContainer(pos, block, null, padStacks(27))
                 }
                 ContainerType.Chest -> {
                     if (root.get("Double")?.asBoolean() == true) {
-                        val leftPosNode = root.get("Left Pos") as? ObjectNode
+                        val leftPosNode = root.get("Left Pos") ?: return null
                         val leftPos = BlockPos.CODEC.parse(JsonOps.UNCOMPRESSED, leftPosNode)
                             .result()
                             .orElse(null)
                             ?: return null
-                        val rightPosNode = root.get("Right Pos") as? ObjectNode
+                        val rightPosNode = root.get("Right Pos") ?: return null
                         val rightPos = BlockPos.CODEC.parse(JsonOps.UNCOMPRESSED, rightPosNode)
                             .result()
                             .orElse(null)
                             ?: return null
-                        DoubleChestContainer(pos, leftPos, rightPos, stacks)
+                        DoubleChestContainer(pos, leftPos, rightPos, padStacks(54))
                     } else {
-                        ChestContainer(pos, stacks)
+                        ChestContainer(pos, padStacks(27))
                     }
                 }
                 else -> return null
diff --git a/src/main/kotlin/com/lambda/event/events/PlayerEvent.kt b/src/main/kotlin/com/lambda/event/events/PlayerEvent.kt
index 1498eaa59..9a21f9fcf 100644
--- a/src/main/kotlin/com/lambda/event/events/PlayerEvent.kt
+++ b/src/main/kotlin/com/lambda/event/events/PlayerEvent.kt
@@ -142,5 +142,13 @@ sealed class PlayerEvent {
         val button: Int,
         val action: SlotActionType,
         val screenHandler: ScreenHandler,
-    ) : ICancellable by Cancellable()
+    ) : ICancellable by Cancellable() {
+        data class Post(
+            val syncId: Int,
+            val slot: Int,
+            val button: Int,
+            val action: SlotActionType,
+            val screenHandler: ScreenHandler,
+        ) : Event
+    }
 }
diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/Contextual.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/Contextual.kt
index 56a7395f3..fd67a73dd 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/Contextual.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/Contextual.kt
@@ -36,8 +36,8 @@ interface Contextual : ComparableResult {
     override fun compareResult(other: ComparableResult) = runSafe {
         when (other) {
             is Contextual -> compareBy {
-                if (it is InteractContext) BlockUtils.fluids.indexOf(it.cachedState.fluidState.fluid)
-                else BlockUtils.fluids.size - 1
+                if (it is InteractContext) BlockUtils.FLUIDS.indexOf(it.cachedState.fluidState.fluid)
+                else BlockUtils.FLUIDS.size - 1
             }.thenByDescending {
                 if (it is InteractContext && it.cachedState.fluidState.level != 0) it.blockPos.y
                 else Int.MIN_VALUE
diff --git a/src/main/kotlin/com/lambda/interaction/container/Container.kt b/src/main/kotlin/com/lambda/interaction/container/Container.kt
index e6feafe87..49f278fc9 100644
--- a/src/main/kotlin/com/lambda/interaction/container/Container.kt
+++ b/src/main/kotlin/com/lambda/interaction/container/Container.kt
@@ -22,12 +22,14 @@ import com.lambda.context.AutomatedSafeContext
 import com.lambda.context.SafeContext
 import com.lambda.event.EventFlow.post
 import com.lambda.event.events.ContainerEvent
+import com.lambda.interaction.container.containers.CursorContainer
 import com.lambda.interaction.container.containers.external.ShulkerBoxContainer
 import com.lambda.interaction.container.selection.StackAndSlot
 import com.lambda.interaction.container.selection.StackSelection
 import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.mutate
 import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder
 import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest
+import com.lambda.interaction.manager.managers.inventory.InventoryRequest
 import com.lambda.task.Task.Ta5kBuilder
 import com.lambda.util.Nameable
 import com.lambda.util.item.ItemStackUtils.count
@@ -127,14 +129,18 @@ abstract class Container(
     open fun access(): OpenContainerTask<*>? = null
 
     open fun count(selection: StackSelection) =
-        slots.takeUnless { it.isEmpty() }?.let {
-            selection.filter(it, false).count
-        } ?: selection.filter(stacks, false).count
+        slots
+            .takeUnless { it.isEmpty() }
+            ?.let {
+                selection.filter(it, false).count
+            } ?: selection.filter(stacks, false).count
 
     open fun spaceLeft(selection: StackSelection) =
-	    slots.takeUnless { it.isEmpty() }?.let {
-            selection.filter(it, false).spaceLeft + it.emptySpace
-        } ?: (selection.filter(stacks, false).spaceLeft + stacks.emptySpace)
+        slots
+            .takeUnless { it.isEmpty() }
+            ?.let {
+                selection.filter(it, false).spaceLeft + selection.emptySpace(it)
+            } ?: (selection.filter(stacks, false).spaceLeft + stacks.emptySpace)
 
     open fun findSlots(selection: StackSelection) = selection.filter(slots)
 
@@ -144,16 +150,38 @@ abstract class Container(
 
     fun findStack(selection: StackSelection) = findStacks(selection).firstOrNull()
 
+    context(automated: Automated)
+    open val isAccessible: Boolean
+        get() = type in automated.inventoryConfig.allowedContainers
+
+    open fun requiredContainers(toContainer: Container): List =
+        if (swapMethodPriority == 0 && toContainer.swapMethodPriority == 0) listOf(CursorContainer)
+        else emptyList()
+
+    context(_: Automated)
+    fun canSwapWith(toContainer: Container): Boolean =
+        requiredContainers(toContainer).all { it.isAccessible }
+
     context(_: Automated)
     fun findMoveSlots(
         selection: StackSelection,
         toContainer: Container,
         toStackSelection: StackSelection = StackSelection.ANYTHING
-    ) =
-        Pair(
-            selection.bestMatch(slots),
-            toContainer.findReplaceSlot(toStackSelection)
-        )
+    ): Pair {
+        if (!canSwapWith(toContainer)) return Pair(null, null)
+        val fromSlot = selection.bestMatch(slots)
+        val toSlot =
+            fromSlot?.let {
+                toContainer.findReplaceSlot(it.stack, toStackSelection)
+            } ?: toContainer.findReplaceSlot(toStackSelection)
+        val validToSlot =
+            toSlot?.takeIf {
+                it.stack.isEmpty ||
+                        fromSlot == null ||
+                        fromSlot.canInsert(it.stack)
+            }
+        return Pair(fromSlot, validToSlot)
+    }
 
     context(_: Automated)
     open fun findReplaceSlot(
@@ -162,16 +190,27 @@ abstract class Container(
         .mutate { sortedWith(replaceSorter) }
         .bestMatch(slots)
 
+    context(_: Automated)
+    open fun findReplaceSlot(
+        incomingStack: ItemStack,
+        selection: StackSelection = StackSelection.ANYTHING
+    ) = findReplaceSlot(selection.mutate { canInsert(incomingStack) })
+
     context(automatedSafeContext: AutomatedSafeContext)
-    internal fun swap(fromSlot: Slot, toSlot: Slot, toContainer: Container): Boolean {
+    internal fun swapRequest(fromSlot: Slot, toSlot: Slot, toContainer: Container): InventoryRequest? {
+        if (!canSwapWith(toContainer)) return null
         val transferEvent = ContainerEvent.Transfer(fromSlot, toSlot, this@Container, toContainer)
-        if (transferEvent.post().isCanceled()) return false
+        if (transferEvent.post().isCanceled()) return null
         return automatedSafeContext.inventoryRequest {
             if (swapMethodPriority > toContainer.swapMethodPriority) swap(fromSlot, toSlot)
             else with(toContainer) { swap(toSlot, fromSlot) }
-        }.submit().done
+        }
     }
 
+    context(automatedSafeContext: AutomatedSafeContext)
+    internal fun swap(fromSlot: Slot, toSlot: Slot, toContainer: Container): Boolean =
+        swapRequest(fromSlot, toSlot, toContainer)?.submit()?.done ?: false
+
     context(safeContext: SafeContext)
     protected open fun InvRequestBuilder.swap(fromHere: Slot, toSlot: Slot) {
         if (fromHere.stack.isEmpty) moveSlot(toSlot.id, fromHere.id)
@@ -181,5 +220,14 @@ abstract class Container(
         }
     }
 
-    override fun compareTo(other: Container) = compareBy { it.type }.compare(this, other)
+    override fun compareTo(other: Container) =
+        compareBy { it.type }
+            .compare(this, other)
+
+    open fun haveMatchingInventories(other: Container) =
+        this === other ||
+                (this is PlacedContainer &&
+                        other is PlacedContainer &&
+                        this::class == other::class &&
+                        pos == other.pos)
 }
diff --git a/src/main/kotlin/com/lambda/interaction/container/NestedContainer.kt b/src/main/kotlin/com/lambda/interaction/container/NestedContainer.kt
index e71c7033e..a12f77556 100644
--- a/src/main/kotlin/com/lambda/interaction/container/NestedContainer.kt
+++ b/src/main/kotlin/com/lambda/interaction/container/NestedContainer.kt
@@ -17,7 +17,13 @@
 
 package com.lambda.interaction.container
 
+import com.lambda.context.Automated
+
 abstract class NestedContainer(type: ContainerType) : Container(type), ExternalContainer {
 	abstract val containedIn: Container
 	abstract val index: Int
+
+	context(_: Automated)
+	override val isAccessible: Boolean
+		get() = super.isAccessible && containedIn.isAccessible
 }
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/interaction/container/containers/CreativeContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/CreativeContainer.kt
index f6538f81c..6f9d1181d 100644
--- a/src/main/kotlin/com/lambda/interaction/container/containers/CreativeContainer.kt
+++ b/src/main/kotlin/com/lambda/interaction/container/containers/CreativeContainer.kt
@@ -32,6 +32,7 @@ import net.minecraft.item.ItemStack
 import net.minecraft.screen.PlayerScreenHandler
 import net.minecraft.screen.slot.Slot
 
+@Suppress("unused")
 data object CreativeContainer : Container(ContainerType.Creative) {
 	override val slots = emptyList()
 	override var stacks = emptyList()
diff --git a/src/main/kotlin/com/lambda/interaction/container/containers/CursorContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/CursorContainer.kt
index 4455c6f4a..b19df139d 100644
--- a/src/main/kotlin/com/lambda/interaction/container/containers/CursorContainer.kt
+++ b/src/main/kotlin/com/lambda/interaction/container/containers/CursorContainer.kt
@@ -18,18 +18,53 @@
 package com.lambda.interaction.container.containers
 
 import com.lambda.Lambda.mc
+import com.lambda.context.SafeContext
 import com.lambda.interaction.container.Container
 import com.lambda.interaction.container.ContainerType
+import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder
 import com.lambda.util.text.buildText
 import com.lambda.util.text.literal
+import net.minecraft.entity.player.PlayerEntity
+import net.minecraft.inventory.SingleStackInventory
 import net.minecraft.item.ItemStack
+import net.minecraft.screen.ScreenHandler
 import net.minecraft.screen.slot.Slot
 
 object CursorContainer : Container(ContainerType.Cursor) {
-	override val slots: List = emptyList()
+	override val slots: List
+		get() =
+			mc.player?.currentScreenHandler?.let {
+				listOf(CursorSlot(it))
+			} ?: emptyList()
+
 	override var stacks: List
-		get() = mc.player?.currentScreenHandler?.cursorStack?.let { listOf(it) } ?: emptyList()
+		get() =
+			mc.player?.currentScreenHandler?.cursorStack?.let {
+				listOf(it)
+			} ?: emptyList()
 		set(_) {}
 
+	override val swapMethodPriority = 12
+
 	override val description = buildText { literal("Cursor") }
+
+	context(safeContext: SafeContext)
+	override fun InvRequestBuilder.swap(fromHere: Slot, toSlot: Slot) {
+		pickup(toSlot.id, 0)
+	}
+
+	class CursorSlot(val screenHandler: ScreenHandler) : Slot(
+		object : SingleStackInventory {
+			override fun getStack(): ItemStack = screenHandler.cursorStack
+			override fun setStack(stack: ItemStack?) {
+				if (stack != null) screenHandler.cursorStack = stack
+			}
+			override fun markDirty() {}
+			override fun canPlayerUse(player: PlayerEntity?) = true
+		},
+		0, 0, 0
+	) {
+		override fun canInsert(stack: ItemStack): Boolean = true
+		override fun canTakeItems(playerEntity: PlayerEntity?): Boolean = true
+	}
 }
diff --git a/src/main/kotlin/com/lambda/interaction/container/containers/external/DoubleChestContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/external/DoubleChestContainer.kt
index f9a9ff386..849ce689a 100644
--- a/src/main/kotlin/com/lambda/interaction/container/containers/external/DoubleChestContainer.kt
+++ b/src/main/kotlin/com/lambda/interaction/container/containers/external/DoubleChestContainer.kt
@@ -21,6 +21,7 @@ import com.lambda.Lambda.mc
 import com.lambda.context.Automated
 import com.lambda.context.SafeContext
 import com.lambda.interaction.container.BasicOpenedContainerContext
+import com.lambda.interaction.container.Container
 import com.lambda.interaction.container.ContainerType
 import com.lambda.interaction.container.OpenContainerTask
 import com.lambda.interaction.container.PlacedContainer
@@ -92,4 +93,7 @@ data class DoubleChestContainer(
 				.start()
 		}
 	}
+
+	override fun haveMatchingInventories(other: Container) =
+		other is DoubleChestContainer && (other.leftPos == leftPos || other.rightPos == rightPos)
 }
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/interaction/container/selection/StackSelection.kt b/src/main/kotlin/com/lambda/interaction/container/selection/StackSelection.kt
index 94765357f..8a450d485 100644
--- a/src/main/kotlin/com/lambda/interaction/container/selection/StackSelection.kt
+++ b/src/main/kotlin/com/lambda/interaction/container/selection/StackSelection.kt
@@ -154,6 +154,14 @@ class StackSelection @ContainerDslMarker internal constructor(
 	infix fun spaceIn(containers: Iterable) =
 		containers.sumOf { it.spaceLeft(this) } >= count
 
+	@ContainerDslMarker
+	fun canInsert(slot: Slot) =
+		optimalStack?.let { !it.isEmpty && slot.canInsert(it) } ?: true
+
+	@ContainerDslMarker
+	fun emptySpace(slots: Iterable) =
+		slots.count { it.stack.isEmpty && canInsert(it) }
+
 	companion object {
 		val ANYTHING = StackSelection(0) { _, _ -> true }
 		val NOTHING = StackSelection { _, _ -> false }
@@ -186,6 +194,18 @@ class StackSelectionBuilder @ContainerDslMarker private constructor(
 		this.shulkerBoxScope = selection.shulkerBoxScope
 	}
 
+	fun withSelection(stackSelection: StackSelection) {
+		appendSelector(stackSelection.selector)
+		whitelistedSlots.addAll(stackSelection.whitelistedSlots)
+		blacklistedSlots.addAll(stackSelection.blacklistedSlots)
+	}
+
+	fun withoutSelection(stackSelection: StackSelection) {
+		appendSelector { stack, slot -> !stackSelection.selector(stack, slot) }
+		whitelistedSlots.addAll(stackSelection.blacklistedSlots)
+		blacklistedSlots.addAll(stackSelection.whitelistedSlots)
+	}
+
 	fun isItem(item: Item) {
 		this.item = item
 		appendSelector { stack, _ -> stack.item == item }
@@ -369,6 +389,21 @@ class StackSelectionBuilder @ContainerDslMarker private constructor(
 		appendSelector { stack, slot -> predicate(stack, slot) }
 	}
 
+	fun canInsert(stack: ItemStack) {
+		if (!stack.isEmpty) {
+			appendSelector { _, slot -> slot == null || slot.canInsert(stack) }
+		}
+	}
+
+	fun canInsert(selection: StackSelection) {
+		selection.optimalStack?.let { canInsert(it) }
+	}
+
+	fun canInsert() {
+		val optimal = itemStack ?: item?.let { ItemStack(it, count) }
+		optimal?.let { canInsert(it) }
+	}
+
 	fun sortedWith(newComparator: Comparator>) {
 		comparator = comparator?.thenComparing(newComparator) ?: newComparator
 	}
@@ -420,7 +455,7 @@ class StackSelectionBuilder @ContainerDslMarker private constructor(
 
 		fun stackSelection(
 			count: Int = 1,
-			builder: StackSelectionBuilder.() -> Unit
+			builder: StackSelectionBuilder.() -> Unit = {}
 		) = StackSelectionBuilder(count).apply(builder).build()
 
 		fun StackSelection.mutate(
diff --git a/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt
index 702adb85b..a65c75498 100644
--- a/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt
+++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt
@@ -24,6 +24,7 @@ import com.lambda.context.SafeContext
 import com.lambda.core.Loadable
 import com.lambda.event.events.InventoryEvent
 import com.lambda.event.events.PacketEvent
+import com.lambda.event.events.PlayerEvent
 import com.lambda.event.events.WorldEvent
 import com.lambda.event.listener.SafeListener.Companion.listen
 import com.lambda.interaction.container.Container
@@ -71,7 +72,7 @@ import java.nio.file.Files
 object ContainerHandler : Loadable {
 	val compileContainers = getInstances()
 	var accessedPlacedContainer: PlacedContainer? = null
-		private set
+		internal set
 
 	var lastInteractedBlockEntity: BlockEntity? = null
 	var pendingInteractedBlockEntity: BlockEntity? = null
@@ -105,7 +106,7 @@ object ContainerHandler : Loadable {
 		}
 
 		listen { onContainerUpdate() }
-		listen { onContainerUpdate() }
+		listen { onContainerUpdate() }
 
 		listen { event ->
 			val oldBlock = event.oldState.block
@@ -198,7 +199,7 @@ object ContainerHandler : Loadable {
 					val rightPos = if (chestType == ChestType.RIGHT) pos else otherPos
 
 					val existing = accessedPlacedContainer as? DoubleChestContainer
-					if (existing != null && existing.pos == pos) {
+					if (existing != null && (existing.leftPos == leftPos || existing.rightPos == rightPos)) {
 						existing.update(stacks)
 					} else {
 						accessedPlacedContainer = DoubleChestContainer(pos, leftPos, rightPos, stacks)
@@ -208,6 +209,12 @@ object ContainerHandler : Loadable {
 				accessedPlacedContainer?.let {
 					it.scanContainerContents()
 					ContainerSerializer.saveContainer(it)
+					if (it is DoubleChestContainer) {
+						val counterpartPos = if (it.pos == it.leftPos) it.rightPos else it.leftPos
+						val counterpart = DoubleChestContainer(counterpartPos, it.leftPos, it.rightPos, it.stacks, it.stash)
+						counterpart.storedContainers.putAll(it.storedContainers)
+						ContainerSerializer.saveContainer(counterpart)
+					}
 				}
 			}
 
@@ -404,13 +411,14 @@ private fun searchContainers(
 			ContainerSearchScope.Player -> compiled - EnderChestContainer
 			ContainerSearchScope.All -> compiled + placedSeq +
 					ContainerSerializer.serializedContainers.filter { diskContainer ->
-						diskContainer.pos != ContainerHandler.accessedPlacedContainer?.pos
+						val accessed = ContainerHandler.accessedPlacedContainer
+						accessed == null || !diskContainer.haveMatchingInventories(accessed)
 					}
 		}
 
 	return baseContainers
 		.flatMap { it.allNested() }
-		.filter { automated.inventoryConfig.containerSelection.matches(it) }
+		.filter { it.isAccessible }
 }
 
 enum class ContainerSearchScope {
diff --git a/src/main/kotlin/com/lambda/interaction/handler/handlers/PacketLimitHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/PacketLimitHandler.kt
index 8820cd659..8aae8ade3 100644
--- a/src/main/kotlin/com/lambda/interaction/handler/handlers/PacketLimitHandler.kt
+++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/PacketLimitHandler.kt
@@ -37,14 +37,18 @@ internal object PacketLimitHandler {
 	}
 
 	context(automated: Automated)
-	fun canSendPackets(packetCount: Int, packetType: PacketType): Boolean {
-		val handler = packetLimitMap[packetType] ?: return false
+	fun availablePackets(packetType: PacketType): Int {
+		val handler = packetLimitMap[packetType] ?: return 0
 		handler.maxPacketsThisTimeframe = packetType.maxPacketsPerTimeframe(automated.buildConfig)
 		handler.timeframe = automated.buildConfig.limitTimeframe.milliseconds
 		handler.removeStale(TimeSource.Monotonic.markNow())
-		return handler.packetTimestamps.size + packetCount <= handler.maxPacketsThisTimeframe
+		return (handler.maxPacketsThisTimeframe - handler.packetTimestamps.size).coerceAtLeast(0)
 	}
 
+	context(automated: Automated)
+	fun canSendPackets(packetCount: Int, packetType: PacketType): Boolean =
+		availablePackets(packetType) >= packetCount
+
 	fun sentPackets(packetCount: Int, packetType: PacketType) {
 		packetLimitMap[packetType]?.let { handler ->
 			val currentTime = TimeSource.Monotonic.markNow()
diff --git a/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryManager.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryManager.kt
index 62fda5e39..a3dca3e98 100644
--- a/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryManager.kt
+++ b/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryManager.kt
@@ -98,7 +98,10 @@ object InventoryManager : Manager(
 	 * needs to equip a totem of undying.
 	 */
 	override fun AutomatedSafeContext.handleRequest(request: InventoryRequest) {
-		if (activeRequest != null) return
+		if (activeRequest != null) {
+			request.failureReason = InvRequestFailureReason.Preoccupied
+			return
+		}
 
 		val playerActionCount = request.actions.count { it is InventoryAction.Player }
 		val inventoryActionCount = request.actions.count { it is InventoryAction.Inventory }
@@ -106,11 +109,14 @@ object InventoryManager : Manager(
 		val canPerformAllInventoryActions = canSendPackets(inventoryActionCount, PacketType.Inventory)
 		if ((!canPerformAllPlayerActions || !canPerformAllInventoryActions) &&
 			!request.settleForLess &&
-			!request.mustPerform) return
+			!request.mustPerform
+		) {
+			request.failureReason = InvRequestFailureReason.PacketLimit
+			return
+		}
 
 		if (request.fresh) populateFrom(request)
 
-
 		processActiveRequest()
 		if (request.nowOrNothing) {
 			activeRequest = null
@@ -151,6 +157,8 @@ object InventoryManager : Manager(
 				active.done = true
 				active.onComplete?.invoke(this)
 				activeRequest = null
+			} else {
+				active.failureReason = InvRequestFailureReason.PacketLimit
 			}
 
 			if (actionsThisTick > 0) activeThisTick = true
diff --git a/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryRequest.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryRequest.kt
index 3a7521c21..2ad88ed55 100644
--- a/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryRequest.kt
+++ b/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryRequest.kt
@@ -49,10 +49,11 @@ class InventoryRequest(
 ) : Request(), Automated by automated {
 	override val requestId = ++requestCount
 	override val tickStageMask get() = inventoryConfig.tickStageMask
+	var failureReason: InvRequestFailureReason = InvRequestFailureReason.None
 	override var done = false
 
 	@InvRequestDsl
-	override fun submit(queueIfMismatchedStage: Boolean) =
+	override fun submit(queueIfMismatchedStage: Boolean): InventoryRequest =
 		InventoryManager.request(this, queueIfMismatchedStage)
 
 	companion object {
@@ -176,3 +177,9 @@ class InvRequestBuilder private constructor(
 			InvRequestBuilder(settleForLess, mustPerform).apply(builder).build()
 	}
 }
+
+enum class InvRequestFailureReason {
+	None,
+	Preoccupied,
+	PacketLimit
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/module/modules/debug/ContainerTransferTest.kt b/src/main/kotlin/com/lambda/module/modules/debug/ContainerTransferTest.kt
new file mode 100644
index 000000000..80bb1ffec
--- /dev/null
+++ b/src/main/kotlin/com/lambda/module/modules/debug/ContainerTransferTest.kt
@@ -0,0 +1,152 @@
+/*
+ * 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.debug
+
+import com.lambda.context.AutomatedSafeContext
+import com.lambda.event.events.ButtonEvent
+import com.lambda.event.listener.SafeListener.Companion.listen
+import com.lambda.interaction.container.ContainerType
+import com.lambda.interaction.container.PlacedContainer
+import com.lambda.interaction.container.containers.external.DoubleChestContainer
+import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.containerSelection
+import com.lambda.interaction.container.selection.select
+import com.lambda.interaction.handler.handlers.ContainerSearchScope
+import com.lambda.interaction.handler.handlers.findContainers
+import com.lambda.module.Module
+import com.lambda.module.ModuleTag
+import com.lambda.task.start
+import com.lambda.task.tasks.transfer
+import com.lambda.threading.runSafeAutomated
+import com.lambda.util.BlockUtils.blockState
+import com.lambda.util.CommunicationUtils.info
+import com.lambda.util.math.distSq
+import net.minecraft.block.ChestBlock
+import net.minecraft.item.Items
+import net.minecraft.util.hit.BlockHitResult
+import net.minecraft.util.math.BlockPos
+import org.lwjgl.glfw.GLFW
+
+@Suppress("unused")
+object ContainerTransferTest : Module(
+    name = "ContainerTransferTest",
+    description = "Test moving stack selections over 1 stack from one external container to another.",
+    tag = ModuleTag.DEBUG,
+) {
+    private val amountToMove by setting("Amount", 128, 1..1000)
+
+    private var sourcePos: BlockPos? = null
+    private var destPos: BlockPos? = null
+
+    init {
+        onEnable {
+            sourcePos = null
+            destPos = null
+            info("Middle-click source chest, then middle-click destination chest.")
+        }
+
+        listen { event ->
+            if (event.button != GLFW.GLFW_MOUSE_BUTTON_MIDDLE || event.action != 1) return@listen
+            
+            val hitResult = mc.crosshairTarget as? BlockHitResult ?: return@listen
+            val pos = hitResult.blockPos
+            
+            val block = blockState(pos).block
+            if (block !is ChestBlock) return@listen
+            
+            if (sourcePos == null) {
+                sourcePos = pos
+                info("Source container set to $pos. Now middle-click destination.")
+                event.cancel()
+            } else if (destPos == null && pos != sourcePos) {
+                destPos = pos
+                info("Destination container set to $pos. Starting transfer task...")
+                event.cancel()
+                
+                runSafeAutomated {
+                    startTransferTask(sourcePos!!, destPos!!)
+                }
+                
+                disable()
+            }
+        }
+    }
+
+    private fun AutomatedSafeContext.startTransferTask(fromPos: BlockPos, toPos: BlockPos) {
+        info("--- DEBUG INFO ---")
+        val allChests = findContainers(containerSelection(ContainerSearchScope.All) { ofAnyType(ContainerType.Chest) }).toList()
+        info("Total known Chest containers in scope All: ${allChests.size}")
+        
+        val matchedFrom = allChests.filter { 
+            val placed = it as? PlacedContainer ?: return@filter false
+            placed.pos distSq fromPos <= 4.0 ||
+                (placed is DoubleChestContainer && (placed.leftPos distSq fromPos <= 4.0 || placed.rightPos distSq fromPos <= 4.0))
+        }
+        info("Chests matching fromPos distance: ${matchedFrom.size}")
+        
+        if (matchedFrom.isNotEmpty()) {
+            val fromContainer = matchedFrom.first()
+            val totalItems = fromContainer.stacks.sumOf { it.count }
+            info("First matched source container has $totalItems total items.")
+            if (totalItems < amountToMove) {
+                info("WARNING: Source container has fewer items ($totalItems) than required ($amountToMove). The task will fail!")
+            }
+        } else {
+            info("WARNING: No chest found near fromPos. Did you open it at least once to save it?")
+        }
+
+        val matchedTo = allChests.filter { 
+            val placed = it as? PlacedContainer ?: return@filter false
+            placed.pos distSq toPos <= 4.0 ||
+                (placed is DoubleChestContainer && (placed.leftPos distSq toPos <= 4.0 || placed.rightPos distSq toPos <= 4.0))
+        }
+        info("Chests matching toPos distance: ${matchedTo.size}")
+        if (matchedTo.isNotEmpty()) {
+            val toContainer = matchedTo.first()
+            info("First matched destination container has ${toContainer.stacks.size} slots (${toContainer.stacks.count { it.isEmpty }} empty).")
+        } else {
+            info("WARNING: No chest found near toPos. Did you open it at least once to save it?")
+        }
+        info("------------------")
+
+        val fromSelection =
+            containerSelection(ContainerSearchScope.All) {
+                ofAnyType(ContainerType.Chest)
+                predicate { container ->
+                    val placed = container as? PlacedContainer ?: return@predicate false
+                    placed.pos distSq fromPos <= 4.0 ||
+                        (placed is DoubleChestContainer && (placed.leftPos distSq fromPos <= 4.0 || placed.rightPos distSq fromPos <= 4.0))
+                }
+            }
+        val toSelection =
+            containerSelection(ContainerSearchScope.All) {
+                ofAnyType(ContainerType.Chest)
+                predicate { container ->
+                    val placed = container as? PlacedContainer ?: return@predicate false
+                    placed.pos distSq toPos <= 4.0 ||
+                        (placed is DoubleChestContainer && (placed.leftPos distSq toPos <= 4.0 || placed.rightPos distSq toPos <= 4.0))
+                }
+            }
+
+        val stackSelection = Items.OBSIDIAN.select(amountToMove)
+
+        stackSelection.transfer(fromSelection, toSelection)
+            .onSuccess { info("Transfer task completed!") }
+            .onFailure { info("Transfer task failed: $it") }
+            .start()
+    }
+}
diff --git a/src/main/kotlin/com/lambda/module/modules/render/XRay.kt b/src/main/kotlin/com/lambda/module/modules/render/XRay.kt
index 426526ee8..679913e81 100644
--- a/src/main/kotlin/com/lambda/module/modules/render/XRay.kt
+++ b/src/main/kotlin/com/lambda/module/modules/render/XRay.kt
@@ -47,7 +47,7 @@ object XRay : Module(
 		Blocks.ANCIENT_DEBRIS
 	)
 
-	val fluids = BlockUtils.fluids - Fluids.EMPTY
+	val fluids = BlockUtils.FLUIDS - Fluids.EMPTY
 
 	@JvmStatic val opacity by setting("Opacity", 40, 1..100, 1, "Opacity of the non x-rayed blocks, (automatically overridden as 0 when running Sodium)").onValueChange(::reload)
 	@JvmStatic val blockSelection by setting("Block Selection", defaultBlocks, Registries.BLOCK - setOf(Blocks.WATER, Blocks.LAVA), description = "Block selection that will be shown (whitelist) or hidden (blacklist)")
diff --git a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt
index 093abf4e9..9d5af96c5 100644
--- a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt
+++ b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt
@@ -20,17 +20,25 @@ package com.lambda.task.tasks
 import com.lambda.context.Automated
 import com.lambda.context.AutomatedSafeContext
 import com.lambda.context.SafeContext
+import com.lambda.event.EventFlow.post
+import com.lambda.event.events.ContainerEvent
 import com.lambda.event.events.TickEvent
 import com.lambda.event.listener.SafeListener.Companion.listen
 import com.lambda.interaction.container.Container
 import com.lambda.interaction.container.ExternalContainer
+import com.lambda.interaction.container.containers.CursorContainer
+import com.lambda.interaction.container.containers.HotbarContainer
+import com.lambda.interaction.container.containers.InventoryContainer
 import com.lambda.interaction.container.selection.ContainerSelection
 import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.mutate
 import com.lambda.interaction.container.selection.StackSelection
 import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.mutate
-import com.lambda.interaction.container.selection.select
 import com.lambda.interaction.container.selection.selectContainers
+import com.lambda.interaction.handler.handlers.PacketLimitHandler.availablePackets
+import com.lambda.interaction.handler.handlers.PacketType
 import com.lambda.interaction.handler.handlers.findContainers
+import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest
+import com.lambda.interaction.manager.managers.inventory.InvRequestFailureReason
 import com.lambda.task.Task
 import com.lambda.task.Task.Ta5kBuilder
 import com.lambda.task.tasks.wrappers.taskOrNull
@@ -38,6 +46,8 @@ import com.lambda.task.tasks.wrappers.then
 import com.lambda.task.tasks.wrappers.thenOrNull
 import com.lambda.threading.runSafeAutomated
 import com.lambda.util.item.ItemStackUtils.equal
+import com.lambda.util.item.ItemStackUtils.hasSpace
+import com.lambda.util.item.StackMovePlanner
 import net.minecraft.item.ItemStack
 import net.minecraft.screen.slot.Slot
 
@@ -64,6 +74,23 @@ class TransferResult internal constructor(
 	val containerSelection: ContainerSelection
 )
 
+/**
+ * Moves items matching [fromStack] out of containers matching [fromSelection] into containers matching
+ * [toSelection].
+ *
+ * Counting semantics: [StackSelection.count] of [fromStack] is the exact number of items to move. A count
+ * of `0` or less moves everything that matches. Individual moves are planned to the exact item count using
+ * left and right clicks (see [StackMovePlanner]); a whole stack is only moved past the requested count when
+ * the sole way into the destination is swapping out a foreign stack.
+ *
+ * Destination slots are chosen in this order: a partial stack of the same item, an empty slot, then (only
+ * when nothing else is possible) a foreign stack matching [toStack] that gets swapped into the source slot.
+ *
+ * When both containers are external (chests, shulker boxes, ender chest) items travel through the player's
+ * hotbar and inventory: a trip pulls as much as fits (or as much as still needs moving) into free player
+ * slots, closes the source, opens the destination and pushes exactly the staged items back out. Trips repeat
+ * until the request is satisfied, so sources larger than the player inventory work too.
+ */
 class ContainerTransferTask @Ta5kBuilder internal constructor(
 	private val fromStack: StackSelection,
 	private val fromSelection: ContainerSelection,
@@ -73,39 +100,97 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 ) : Task(), Automated by automated {
 	override val name = "Transferring $fromStack"
 
+	/** `true` when [fromStack] carries no count: everything matching gets moved. */
+	private val unlimited = fromStack.count <= 0
+
 	private var transferred = 0
+	private val remaining
+		get() =
+			if (unlimited) Int.MAX_VALUE
+			else (fromStack.count - transferred).coerceAtLeast(0)
+	private val isComplete
+		get() = !unlimited && remaining <= 0
+
+	private val progressText
+		get() = "$transferred moved"
+
 	private val resultSlots = mutableMapOf()
 	private val resultContainers = mutableSetOf()
 
+	private val fromQueue = mutableListOf()
+	private val toQueue = mutableListOf()
+
 	override fun SafeContext.onStart() {
-		val fromContainers =
-			findContainers(fromSelection.mutate { hasStack(fromStack.mutate(1)) })
-				.toList()
-				.takeIf { fromStack isIn it }
-				?: run {
-					failure("Could not find source containers for $fromStack")
-					return
+		fromQueue +=
+			findContainers(
+				fromSelection.mutate {
+					hasStack(fromStack.mutate(1))
 				}
+			)
+		if (fromQueue.isEmpty() || (!unlimited && !(fromStack isIn fromQueue))) {
+			failure("Could not find source containers for $fromStack")
+			return
+		}
 
-		val toContainers =
-			findContainers(toSelection.mutate { hasStack(toStack.mutate(1)) })
-				.toList()
-				.takeIf { toStack isIn it }
-				?: run {
-					failure("Could not find destination containers for $toStack")
-					return
-				}
+		toQueue += findContainers(toSelection).filter { canReceive(it) }
+		if (toQueue.isEmpty()) {
+			failure("Could not find destination containers with space for $fromStack")
+			return
+		}
 
-		transferNextPair(fromContainers.toMutableList(), toContainers.toMutableList())
+		nextStep()
 	}
 
-	private fun remainingSelection() =
-		fromStack.mutate(
-			if (fromStack.count <= 0) 0
-			else fromStack.count - transferred
-		)
+	/**
+	 * Picks the next source and destination pair and runs one transfer step between them. A step either moves
+	 * items directly (at most one side external) or performs a pull/push trip through the player inventory
+	 * (both sides external). Repeats until the requested count is reached or a queue runs dry.
+	 */
+	private fun SafeContext.nextStep() {
+		if (isComplete) {
+			success(buildResult())
+			return
+		}
 
-	private fun isComplete() = transferred >= fromStack.count
+		val from = fromQueue.firstOrNull()
+			?: run {
+				if (unlimited && transferred > 0) success(buildResult())
+				else failure("Ran out of source containers for $fromStack ($progressText)")
+				return
+			}
+		val to = toQueue.firstOrNull { !it.haveMatchingInventories(from) }
+			?: run {
+				failure("No destination container has space left for $fromStack ($progressText)")
+				return
+			}
+
+		val prevTransferred = transferred
+		val step =
+			if (from is ExternalContainer && to is ExternalContainer) tripChain(from)
+			else directChain(from, to)
+
+		step
+			.onSuccess {
+				if (isComplete) {
+					success(buildResult())
+					return@onSuccess
+				}
+
+				if (transferred <= prevTransferred) {
+					toQueue.removeAll { it.haveMatchingInventories(to) }
+				}
+				nextStep()
+			}
+			.start()
+	}
+
+	/**
+	 * A destination can receive when it has room for the item (a partial stack or an empty slot) or holds a
+	 * stack matching [toStack] that may be swapped out.
+	 */
+	private fun canReceive(container: Container) =
+		container.spaceLeft(fromStack.mutate(1)) > 0 ||
+			container.count(toStack.mutate { notEmpty(); withoutSelection(fromStack) }) > 0
 
 	private fun buildResult() =
 		TransferResult(
@@ -130,181 +215,373 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 		)
 
 	/**
-	 * Takes the first container from each queue as a pair, builds a transfer
-	 * chain for that pair, then advances to the next pair on completion.
-	 *
-	 * Calls [success] or [failure] directly on this task when done.
+	 * Moves directly between [from] and [to]. At most one of them is external, so at most one screen has to be
+	 * opened; screens this task opened are closed again afterwards.
 	 */
-	private fun transferNextPair(
-		fromQueue: MutableList,
-		toQueue: MutableList
-	) {
-		if (isComplete()) {
-			success(buildResult())
-			return
-		}
-
-		if (fromQueue.isEmpty() || toQueue.isEmpty()) {
-			failure("Not enough containers to transfer $fromStack (transferred $transferred/${fromStack.count})")
-			return
+	private fun directChain(from: Container, to: Container): Task<*> =
+		taskOrNull { from.access() }.then { fromContext ->
+			taskOrNull { to.access() }.then { toContext ->
+				MoveTask(
+					fromContainers = listOf(from),
+					toContainers = listOf(to),
+					fromSelection = fromStack,
+					toSelection = toStack,
+					limit = remaining
+				).thenOrNull {
+					if (!canReceive(to)) {
+						toQueue.removeAll { it.haveMatchingInventories(to) }
+					}
+					toContext?.close()
+				}.thenOrNull {
+					if (from.count(fromStack) <= 0) {
+						fromQueue.removeAll { it.haveMatchingInventories(from) }
+					}
+					fromContext?.close()
+				}
+			}
 		}
 
-		val fromContainer = fromQueue.first()
-		val toContainer = toQueue.first()
-
-		val bothExternal = fromContainer is ExternalContainer && !fromContainer.isAccessed &&
-				toContainer is ExternalContainer && !toContainer.isAccessed
-
-		val pairChain =
-			if (bothExternal) pullPushChain(fromContainer, toContainer, toQueue)
-			else directPairChain(fromContainer, toContainer)
-
-		pairChain.onSuccess {
-			if (fromContainer.count(fromStack) <= 0) fromQueue.remove(fromContainer)
-			if (!bothExternal && toContainer.spaceLeft(fromStack) <= 0) toQueue.remove(toContainer)
-			transferNextPair(fromQueue, toQueue)
-		}.start()
-	}
-
 	/**
-	 * Builds a chain for a pair where at most one container is external.
-	 * Uses [taskOrNull] to uniformly handle already-accessed containers.
+	 * One trip for two external containers: pull from [from] into free player slots, close it, then push the
+	 * staged items into the destination queue. Loops are driven naturally by [nextStep].
 	 */
-	private fun directPairChain(
-		fromContainer: Container,
-		toContainer: Container
-	) =
-		taskOrNull { fromContainer.access() }.then { fromContext ->
-			taskOrNull { toContainer.access() }.then { toContext ->
-				SwapTask(fromContainer.select(), toContainer.select())
-					.thenOrNull { toContext?.close() }
-					.thenOrNull { fromContext?.close() }
+	private fun tripChain(from: Container): Task<*> =
+		taskOrNull { from.access() }
+			.then { fromContext ->
+				MoveTask(
+					fromContainers = listOf(from),
+					toContainers = listOf(InventoryContainer, HotbarContainer),
+					fromSelection = fromStack,
+					toSelection = StackSelection.ANYTHING,
+					limit = remaining,
+					allowReplace = false,
+					trackResult = false
+				).thenOrNull {
+					if (from.count(fromStack) <= 0) {
+						fromQueue.removeAll { it.haveMatchingInventories(from) }
+					}
+					fromContext?.close()
+				}
 			}
-		}
+			.then { pushChain(from) }
 
 	/**
-	 * Builds a chain for when both containers are external.
-	 * Pulls items from [fromContainer] into the player inventory, then delegates
-	 * to [pushChain] to push them into [toContainer].
+	 * Pushes items matching [fromStack] from the player inventory into the first available destination.
+	 * If a destination fills up but the player still has items, advances [toQueue] and pushes to the next one.
 	 */
-	private fun pullPushChain(
-		fromContainer: Container,
-		toContainer: Container,
-		toQueue: MutableList
-	) =
-		taskOrNull { fromContainer.access() }.then { fromContext ->
-			SwapTask(
-				fromContainer.select(),
-				ContainerSelection.HOTBAR_AND_INVENTORY,
-				replaceSelection = StackSelection.ANYTHING,
-				trackResult = false
-			).thenOrNull { fromContext?.close() }
-		}.then { pushChain(toContainer, toQueue) }
+	private fun pushChain(from: Container): Task<*> {
+		val to = toQueue.firstOrNull { !it.haveMatchingInventories(from) }
+			?: return taskOrNull { null }
+
+		return taskOrNull { to.access() }
+			.then { toContext ->
+				MoveTask(
+					fromContainers = listOf(InventoryContainer, HotbarContainer),
+					toContainers = listOf(to),
+					fromSelection = fromStack,
+					toSelection = toStack,
+					limit = remaining
+				).thenOrNull {
+					if (!canReceive(to)) {
+						toQueue.removeAll { it.haveMatchingInventories(to) }
+					}
+					toContext?.close()
+				}
+			}
+			.thenOrNull {
+				if (isComplete) return@thenOrNull null
 
-	/**
-	 * Pushes items from the player inventory into [destination].
-	 * If [destination] fills up but the player still has items, advances [toQueue]
-	 * and pushes to the next destination.
-	 */
-	private fun pushChain(
-		destination: Container,
-		toQueue: MutableList
-	): Task<*> =
-		taskOrNull { destination.access() }.then { destinationContext ->
-			SwapTask(ContainerSelection.HOTBAR_AND_INVENTORY, destination.select())
-				.thenOrNull { destinationContext?.close() }
-		}.onSuccess {
-			if (isComplete()) return@onSuccess
-
-			if (destination.spaceLeft(fromStack) == 0) toQueue.remove(destination)
-
-			val playerHasItems =
-				fromStack.mutate(1) isIn findContainers(ContainerSelection.HOTBAR_AND_INVENTORY).toList()
-			if (!playerHasItems) return@onSuccess
-
-			val nextDestination = toQueue.firstOrNull() ?: return@onSuccess
-			pushChain(nextDestination, toQueue).start()
-		}
+				val playerHasItems =
+					fromStack.mutate(1) isIn findContainers(ContainerSelection.HOTBAR_AND_INVENTORY).asIterable()
+				if (!playerHasItems) return@thenOrNull null
+
+				if (toQueue.any { !it.haveMatchingInventories(from) }) pushChain(from)
+				else null
+			}
+	}
 
 	/**
-	 * Swaps items between containers matching [fromSelection] and [toSelection] one slot per tick.
-	 * Completes when no more valid move slots can be found or the total count is reached.
-	 * Updates [transferred], [resultSlots], and [resultContainers] on the outer task.
+	 * Moves items between already-accessed containers. Slot pairs are selected fresh each tick using
+	 * [fromSelection] and the container's own [Container.findMoveSlots] / [Container.findReplaceSlot] APIs —
+	 * no slot references are cached across ticks.
 	 *
-	 * @param trackResult whether to record destination slots in [resultSlots]/[resultContainers].
-	 *   Set to `false` for intermediary transfers (e.g. pulling to player inventory).
+	 * For partial-stack precision, moves are planned with [StackMovePlanner] which finds the shortest
+	 * sequence of vanilla `PICKUP` clicks to reach the exact item count. Whole-stack moves use the
+	 * container's own [Container.swap] method which picks the optimal click strategy (e.g. hotbar swap).
+	 *
+	 * @param allowReplace whether a foreign stack matching [toSelection] may be swapped out as a last resort.
+	 * @param trackResult whether destination slots are recorded for the [TransferResult].
 	 */
-	private inner class SwapTask @Ta5kBuilder constructor(
-		private val fromSelection: ContainerSelection,
-		private val toSelection: ContainerSelection,
-		private val replaceSelection: StackSelection = toStack,
+	private inner class MoveTask @Ta5kBuilder constructor(
+		private val fromContainers: List,
+		private val toContainers: List,
+		private val fromSelection: StackSelection,
+		toSelection: StackSelection,
+		private val limit: Int = if (unlimited) Int.MAX_VALUE else remaining,
+		private val allowReplace: Boolean = true,
 		private val trackResult: Boolean = true
-	) : Task() {
-		override val name = "Swapping slots"
+	) : Task() {
+		override val name get() = "Moving ${this@MoveTask.fromSelection}"
+		private var moved = 0
+		private var lastFailureReason = InvRequestFailureReason.None
+
+		private val toSelection = toSelection.mutate { withoutSelection(this@MoveTask.fromSelection) }
 
 		init {
 			listen {
-				runSafeAutomated {
-					while (true) {
-						if (swapStack()) break
-					}
-				}
+				runSafeAutomated { tick() }
 			}
 		}
 
-		private fun AutomatedSafeContext.swapStack(): Boolean {
-			val currentSelection = remainingSelection()
+		private fun AutomatedSafeContext.tick() {
+			lastFailureReason = InvRequestFailureReason.None
+
+			val currentLimit =
+				if (limit == Int.MAX_VALUE) Int.MAX_VALUE
+				else (limit - moved).coerceAtLeast(0)
+			if (currentLimit <= 0) {
+				finish()
+				return
+			}
 
+			val currentSelection =
+				fromSelection.mutate(
+					if (limit == Int.MAX_VALUE) 0
+					else minOf(currentLimit, fromSelection.count.coerceAtLeast(1))
+				) { notEmpty() }
+
+			// Find the best slot pair fresh — never cached across ticks
 			val moveSlots =
-				findContainers(fromSelection).firstNotNullOfOrNull { fromContainer ->
-					findContainers(toSelection).firstNotNullOfOrNull { toContainer ->
-						val (from, to) =
-							fromContainer.findMoveSlots(
-								currentSelection,
-								toContainer,
-								replaceSelection
-							)
-						if (from != null && to != null) {
-							Triple(
-								fromContainer,
-								toContainer,
-								Pair(from, to)
+				fromContainers.firstNotNullOfOrNull { fromContainer ->
+					val fromSlot = fromContainer.findSlot(currentSelection) ?: return@firstNotNullOfOrNull null
+
+					toContainers.firstNotNullOfOrNull { toContainer ->
+						if (toContainer.haveMatchingInventories(fromContainer)) return@firstNotNullOfOrNull null
+						if (!fromContainer.canSwapWith(toContainer)) return@firstNotNullOfOrNull null
+
+						// 1. Prefer merging into an existing partial stack of the same item
+						val mergeSlot =
+							toContainer.findSlot(
+								currentSelection.mutate {
+									notEmpty()
+									predicate { s, _ -> s.hasSpace }
+									canInsert(fromSlot.stack)
+								}
 							)
+						if (mergeSlot != null) {
+							return@firstNotNullOfOrNull Triple(fromContainer, toContainer, fromSlot to mergeSlot)
+						}
+
+						// 2. Otherwise find an empty slot or (if replacement allowed) a foreign replace slot
+						val replaceSlot = toContainer.findReplaceSlot(fromSlot.stack, toSelection)
+						if (replaceSlot != null && (replaceSlot.stack.isEmpty || (allowReplace && fromSlot.canInsert(replaceSlot.stack)))) {
+							Triple(fromContainer, toContainer, fromSlot to replaceSlot)
 						} else null
 					}
 				}
 
 			if (moveSlots == null) {
-				success()
-				return true
+				finish()
+				return
+			}
+
+			val (fromContainer, toContainer, pair) = moveSlots
+			val (fromSlot, toSlot) = pair
+
+			move(fromContainer, fromSlot, toContainer, toSlot, currentLimit)
+		}
+
+		/**
+		 * Moves items from [fromSlot] to [toSlot]. Uses native quick-move (shift-click) for whole-stack
+		 * moves into matching partial stacks or empty slots. Uses atomic [Container.swap] when replacing
+		 * a foreign slot (when [allowReplace] is true). Uses [StackMovePlanner] for partial-stack moves to
+		 * reach the exact requested count.
+		 */
+		private fun AutomatedSafeContext.move(
+			fromContainer: Container,
+			fromSlot: Slot,
+			toContainer: Container,
+			toSlot: Slot,
+			currentLimit: Int
+		) {
+			val stack = fromSlot.stack
+			if (stack.isEmpty) {
+				finish()
+				return
+			}
+
+			val moveCount = minOf(stack.count, currentLimit)
+			if (moveCount <= 0) {
+				finish()
+				return
+			}
+
+			val wholeStack = moveCount == stack.count
+			val toSameItem = !toSlot.stack.isEmpty && toSlot.stack.sameItemAs(stack)
+			val toForeign = !toSlot.stack.isEmpty && !toSameItem
+
+			// 1. Foreign slot replacement (swapping the whole stack is the sole way into a foreign slot)
+			if (toForeign && allowReplace) {
+				val actualMoved = stack.count
+				val request = fromContainer
+					.swapRequest(fromSlot, toSlot, toContainer)
+					?.submit()
+				if (request == null || !request.done) {
+					if (request != null) checkFinish(request.failureReason)
+					else finish()
+					return
+				}
+
+				moved += actualMoved
+				if (trackResult) {
+					transferred += actualMoved
+					resultSlots[toSlot] = toSlot.stack.copy()
+					resultContainers.add(toContainer)
+				}
+				return
+			}
+
+			// Can't move into a foreign slot when replacement is not allowed
+			if (toForeign) {
+				finish()
+				return
+			}
+
+			// 2. Whole-stack fast-path using native quick-move (shift-click)
+			if (wholeStack) {
+				val cursorStack = CursorContainer.stacks.firstOrNull() ?: return
+				if (!cursorStack.isEmpty) return
+
+				val packetLimit = availablePackets(PacketType.Inventory)
+				if (packetLimit < 1) {
+					lastFailureReason = InvRequestFailureReason.PacketLimit
+					return
+				}
+
+				val event = ContainerEvent.Transfer(fromSlot, toSlot, fromContainer, toContainer)
+				if (event.post().isCanceled()) return
+
+				val initialStack = stack.copy()
+				val countBefore = initialStack.count
+				val toSlotsBefore =
+					if (trackResult) toContainer.slots.map { it.stack.copy() }
+					else null
+
+				val request = inventoryRequest {
+					quickMove(fromSlot.id)
+				}.submit()
+
+				lastFailureReason = request.failureReason
+				if (!request.done) {
+					checkFinish(request.failureReason)
+					return
+				}
+
+				val countAfter =
+					if (fromSlot.stack.isEmpty || !fromSlot.stack.sameItemAs(initialStack)) 0
+					else fromSlot.stack.count
+				val actualMoved = countBefore - countAfter
+				if (actualMoved <= 0) {
+					finish()
+					return
+				}
+
+				moved += actualMoved
+				if (trackResult) {
+					transferred += actualMoved
+					toContainer.slots.forEachIndexed { index, slot ->
+						val prev = toSlotsBefore?.getOrNull(index)
+						if (prev == null || prev.isEmpty || slot.stack.count > prev.count || !slot.stack.sameItemAs(prev)) {
+							if (!slot.stack.isEmpty) {
+								resultSlots[slot] = slot.stack.copy()
+								resultContainers.add(toContainer)
+							}
+						}
+					}
+				}
+				return
+			}
+
+			// Partial move — plan clicks within the currently available packet budget
+			if (!CursorContainer.isAccessible) {
+				finish()
+				return
 			}
+			val cursorStack = CursorContainer.stacks.firstOrNull() ?: return
+			if (!cursorStack.isEmpty && !cursorStack.sameItemAs(stack)) return
 
-			val (fromContainer, toContainer, slots) = moveSlots
-			val (fromSlot, toSlot) = slots
-			val moveCount =
-				minOf(
-					fromSlot.stack.count,
-					if (fromStack.count == 0) Int.MAX_VALUE
-					else fromStack.count - transferred
-				)
-
-			if (!fromContainer.swap(fromSlot, toSlot, toContainer)) {
-				failure("Swap failed for $fromStack")
-				return true
+			val packetLimit = availablePackets(PacketType.Inventory)
+			if (packetLimit < 2) {
+				lastFailureReason = InvRequestFailureReason.PacketLimit
+				return
 			}
 
-			transferred += moveCount
+			val toCount =
+				if (toSameItem) toSlot.stack.count
+				else 0
+			val maxCount = toSlot.getMaxItemCount(stack)
+			val goal = minOf(toCount + moveCount, maxCount)
+			if (goal <= toCount) return
+
+			val state = StackMovePlanner.State(cursorStack.count, stack.count, toCount)
+			val idealPlan = StackMovePlanner.plan(state, maxCount, goal)
+			val (stepGoal, plan) =
+				if (idealPlan != null && idealPlan.size <= packetLimit) {
+					goal to idealPlan
+				} else {
+					var foundGoal = goal
+					var foundPlan: List? = null
+					for (g in goal downTo toCount + 1) {
+						val p = StackMovePlanner.plan(state, maxCount, g) ?: continue
+						if (p.size <= packetLimit) {
+							foundGoal = g
+							foundPlan = p
+							break
+						}
+					}
+					foundGoal to foundPlan
+				}
+
+			if (plan == null || plan.isEmpty()) {
+				lastFailureReason = InvRequestFailureReason.PacketLimit
+				return
+			}
+
+			val actualMove = stepGoal - toCount
+			if (actualMove <= 0) return
+
+			// Post the transfer event for partial moves (swap() posts its own for whole moves)
+			val event = ContainerEvent.Transfer(fromSlot, toSlot, fromContainer, toContainer)
+			if (event.post().isCanceled()) return
+
+			val request = inventoryRequest {
+				plan.forEach { click ->
+					val slotId =
+						if (click.target == StackMovePlanner.Target.Source) fromSlot.id
+						else toSlot.id
+					pickup(slotId, click.button)
+				}
+			}.submit()
+
+			lastFailureReason = request.failureReason
+			if (!request.done) return
+
+			moved += actualMove
 			if (trackResult) {
-				resultSlots[toSlot] = toSlot.stack
+				transferred += actualMove
+				resultSlots[toSlot] = toSlot.stack.copy()
 				resultContainers.add(toContainer)
 			}
+		}
 
-			if (fromStack.count == 0 || isComplete()) {
-				success()
-				return true
-			}
+		private fun checkFinish(failureReason: InvRequestFailureReason = InvRequestFailureReason.None) {
+			if (failureReason == InvRequestFailureReason.None) finish()
+		}
 
-			return false
+		private fun finish() {
+			success(moved)
 		}
+
+		fun ItemStack.sameItemAs(other: ItemStack) = ItemStack.areItemsAndComponentsEqual(this, other)
 	}
-}
\ No newline at end of file
+}
diff --git a/src/main/kotlin/com/lambda/util/BlockUtils.kt b/src/main/kotlin/com/lambda/util/BlockUtils.kt
index 6ae3b7a78..fc42e7f3c 100644
--- a/src/main/kotlin/com/lambda/util/BlockUtils.kt
+++ b/src/main/kotlin/com/lambda/util/BlockUtils.kt
@@ -101,172 +101,179 @@ import net.minecraft.util.math.Vec3i
 
 @Suppress("unused")
 object BlockUtils {
-    val signs = setOf(
-        Blocks.OAK_SIGN,
-        Blocks.BIRCH_SIGN,
-        Blocks.ACACIA_SIGN,
-        Blocks.CHERRY_SIGN,
-        Blocks.JUNGLE_SIGN,
-        Blocks.DARK_OAK_SIGN,
-        Blocks.MANGROVE_SIGN,
-        Blocks.BAMBOO_SIGN,
-        Blocks.CRIMSON_SIGN,
-        Blocks.WARPED_SIGN,
-        Blocks.SPRUCE_SIGN
-    )
+    val SIGNS =
+        setOf(
+            Blocks.OAK_SIGN,
+            Blocks.BIRCH_SIGN,
+            Blocks.ACACIA_SIGN,
+            Blocks.CHERRY_SIGN,
+            Blocks.JUNGLE_SIGN,
+            Blocks.DARK_OAK_SIGN,
+            Blocks.MANGROVE_SIGN,
+            Blocks.BAMBOO_SIGN,
+            Blocks.CRIMSON_SIGN,
+            Blocks.WARPED_SIGN,
+            Blocks.SPRUCE_SIGN
+        )
 
-    val wallSigns = setOf(
-        Blocks.OAK_WALL_SIGN,
-        Blocks.BIRCH_WALL_SIGN,
-        Blocks.ACACIA_WALL_SIGN,
-        Blocks.CHERRY_WALL_SIGN,
-        Blocks.JUNGLE_WALL_SIGN,
-        Blocks.DARK_OAK_WALL_SIGN,
-        Blocks.MANGROVE_WALL_SIGN,
-        Blocks.BAMBOO_WALL_SIGN,
-        Blocks.CRIMSON_WALL_SIGN,
-        Blocks.WARPED_WALL_SIGN,
-        Blocks.SPRUCE_WALL_SIGN
-    )
+    val WALL_SIGNS =
+        setOf(
+            Blocks.OAK_WALL_SIGN,
+            Blocks.BIRCH_WALL_SIGN,
+            Blocks.ACACIA_WALL_SIGN,
+            Blocks.CHERRY_WALL_SIGN,
+            Blocks.JUNGLE_WALL_SIGN,
+            Blocks.DARK_OAK_WALL_SIGN,
+            Blocks.MANGROVE_WALL_SIGN,
+            Blocks.BAMBOO_WALL_SIGN,
+            Blocks.CRIMSON_WALL_SIGN,
+            Blocks.WARPED_WALL_SIGN,
+            Blocks.SPRUCE_WALL_SIGN
+        )
 
-    val hangingSigns = setOf(
-        Blocks.OAK_HANGING_SIGN,
-        Blocks.BIRCH_HANGING_SIGN,
-        Blocks.ACACIA_HANGING_SIGN,
-        Blocks.CHERRY_HANGING_SIGN,
-        Blocks.JUNGLE_HANGING_SIGN,
-        Blocks.DARK_OAK_HANGING_SIGN,
-        Blocks.MANGROVE_HANGING_SIGN,
-        Blocks.BAMBOO_HANGING_SIGN,
-        Blocks.CRIMSON_HANGING_SIGN,
-        Blocks.WARPED_HANGING_SIGN,
-        Blocks.SPRUCE_HANGING_SIGN
-    )
+    val HANGING_SIGNS =
+        setOf(
+            Blocks.OAK_HANGING_SIGN,
+            Blocks.BIRCH_HANGING_SIGN,
+            Blocks.ACACIA_HANGING_SIGN,
+            Blocks.CHERRY_HANGING_SIGN,
+            Blocks.JUNGLE_HANGING_SIGN,
+            Blocks.DARK_OAK_HANGING_SIGN,
+            Blocks.MANGROVE_HANGING_SIGN,
+            Blocks.BAMBOO_HANGING_SIGN,
+            Blocks.CRIMSON_HANGING_SIGN,
+            Blocks.WARPED_HANGING_SIGN,
+            Blocks.SPRUCE_HANGING_SIGN
+        )
 
-    val hangingWallSigns = setOf(
-        Blocks.OAK_WALL_HANGING_SIGN,
-        Blocks.BIRCH_WALL_HANGING_SIGN,
-        Blocks.ACACIA_WALL_HANGING_SIGN,
-        Blocks.CHERRY_WALL_HANGING_SIGN,
-        Blocks.JUNGLE_WALL_HANGING_SIGN,
-        Blocks.DARK_OAK_WALL_HANGING_SIGN,
-        Blocks.MANGROVE_WALL_HANGING_SIGN,
-        Blocks.BAMBOO_WALL_HANGING_SIGN,
-        Blocks.CRIMSON_WALL_HANGING_SIGN,
-        Blocks.WARPED_WALL_HANGING_SIGN,
-        Blocks.SPRUCE_WALL_HANGING_SIGN
-    )
+    val HANGING_WALL_SIGNS =
+        setOf(
+            Blocks.OAK_WALL_HANGING_SIGN,
+            Blocks.BIRCH_WALL_HANGING_SIGN,
+            Blocks.ACACIA_WALL_HANGING_SIGN,
+            Blocks.CHERRY_WALL_HANGING_SIGN,
+            Blocks.JUNGLE_WALL_HANGING_SIGN,
+            Blocks.DARK_OAK_WALL_HANGING_SIGN,
+            Blocks.MANGROVE_WALL_HANGING_SIGN,
+            Blocks.BAMBOO_WALL_HANGING_SIGN,
+            Blocks.CRIMSON_WALL_HANGING_SIGN,
+            Blocks.WARPED_WALL_HANGING_SIGN,
+            Blocks.SPRUCE_WALL_HANGING_SIGN
+        )
 
-    val allSigns = signs + wallSigns + hangingSigns + hangingWallSigns
+    val ALL_SIGNS = SIGNS + WALL_SIGNS + HANGING_SIGNS + HANGING_WALL_SIGNS
 
-    val pottedBlocks = setOf(
-        Blocks.POTTED_WARPED_FUNGUS,
-        Blocks.POTTED_AZALEA_BUSH,
-        Blocks.POTTED_CLOSED_EYEBLOSSOM,
-        Blocks.POTTED_CACTUS,
-        Blocks.POTTED_PINK_TULIP,
-        Blocks.POTTED_FLOWERING_AZALEA_BUSH,
-        Blocks.POTTED_RED_TULIP,
-        Blocks.POTTED_CORNFLOWER,
-        Blocks.POTTED_DANDELION,
-        Blocks.POTTED_SPRUCE_SAPLING,
-        Blocks.POTTED_WHITE_TULIP,
-        Blocks.POTTED_OAK_SAPLING,
-        Blocks.POTTED_WITHER_ROSE,
-        Blocks.POTTED_PALE_OAK_SAPLING,
-        Blocks.POTTED_ACACIA_SAPLING,
-        Blocks.POTTED_BIRCH_SAPLING,
-        Blocks.POTTED_ALLIUM,
-        Blocks.POTTED_CRIMSON_FUNGUS,
-        Blocks.POTTED_CRIMSON_ROOTS,
-        Blocks.POTTED_JUNGLE_SAPLING,
-        Blocks.POTTED_DEAD_BUSH,
-        Blocks.POTTED_TORCHFLOWER,
-        Blocks.POTTED_BLUE_ORCHID,
-        Blocks.POTTED_BROWN_MUSHROOM,
-        Blocks.POTTED_BAMBOO,
-        Blocks.POTTED_MANGROVE_PROPAGULE,
-        Blocks.POTTED_CHERRY_SAPLING,
-        Blocks.POTTED_AZURE_BLUET,
-        Blocks.POTTED_DARK_OAK_SAPLING,
-        Blocks.POTTED_RED_MUSHROOM,
-        Blocks.POTTED_WARPED_ROOTS,
-        Blocks.POTTED_OPEN_EYEBLOSSOM,
-        Blocks.POTTED_ORANGE_TULIP,
-        Blocks.POTTED_OXEYE_DAISY,
-        Blocks.POTTED_POPPY,
-        Blocks.POTTED_LILY_OF_THE_VALLEY,
-        Blocks.POTTED_FERN
-    )
+    val POTTED_BLOCKS =
+        setOf(
+            Blocks.POTTED_WARPED_FUNGUS,
+            Blocks.POTTED_AZALEA_BUSH,
+            Blocks.POTTED_CLOSED_EYEBLOSSOM,
+            Blocks.POTTED_CACTUS,
+            Blocks.POTTED_PINK_TULIP,
+            Blocks.POTTED_FLOWERING_AZALEA_BUSH,
+            Blocks.POTTED_RED_TULIP,
+            Blocks.POTTED_CORNFLOWER,
+            Blocks.POTTED_DANDELION,
+            Blocks.POTTED_SPRUCE_SAPLING,
+            Blocks.POTTED_WHITE_TULIP,
+            Blocks.POTTED_OAK_SAPLING,
+            Blocks.POTTED_WITHER_ROSE,
+            Blocks.POTTED_PALE_OAK_SAPLING,
+            Blocks.POTTED_ACACIA_SAPLING,
+            Blocks.POTTED_BIRCH_SAPLING,
+            Blocks.POTTED_ALLIUM,
+            Blocks.POTTED_CRIMSON_FUNGUS,
+            Blocks.POTTED_CRIMSON_ROOTS,
+            Blocks.POTTED_JUNGLE_SAPLING,
+            Blocks.POTTED_DEAD_BUSH,
+            Blocks.POTTED_TORCHFLOWER,
+            Blocks.POTTED_BLUE_ORCHID,
+            Blocks.POTTED_BROWN_MUSHROOM,
+            Blocks.POTTED_BAMBOO,
+            Blocks.POTTED_MANGROVE_PROPAGULE,
+            Blocks.POTTED_CHERRY_SAPLING,
+            Blocks.POTTED_AZURE_BLUET,
+            Blocks.POTTED_DARK_OAK_SAPLING,
+            Blocks.POTTED_RED_MUSHROOM,
+            Blocks.POTTED_WARPED_ROOTS,
+            Blocks.POTTED_OPEN_EYEBLOSSOM,
+            Blocks.POTTED_ORANGE_TULIP,
+            Blocks.POTTED_OXEYE_DAISY,
+            Blocks.POTTED_POPPY,
+            Blocks.POTTED_LILY_OF_THE_VALLEY,
+            Blocks.POTTED_FERN
+        )
 
-    val interactionBlocks = setOf(
-        AbstractCauldronBlock::class,
-        AbstractFurnaceBlock::class,
-        AbstractSignBlock::class,
-        AnvilBlock::class,
-        BarrelBlock::class,
-        BeaconBlock::class,
-        BedBlock::class,
-        BeehiveBlock::class,
-        BellBlock::class,
-        BrewingStandBlock::class,
-        ButtonBlock::class,
-        CakeBlock::class,
-        CampfireBlock::class,
-        CandleBlock::class,
-        CandleCakeBlock::class,
-        CartographyTableBlock::class,
-        CaveVinesBodyBlock::class,
-        CaveVinesHeadBlock::class,
-        ChestBlock::class,
-        ChiseledBookshelfBlock::class,
-        CommandBlock::class,
-        ComparatorBlock::class,
-        ComposterBlock::class,
-        CrafterBlock::class,
-        CraftingTableBlock::class,
-        DaylightDetectorBlock::class,
-        DecoratedPotBlock::class,
-        DispenserBlock::class,
-        DropperBlock::class,
-        DoorBlock::class,
-        DragonEggBlock::class,
-        EnchantingTableBlock::class,
-        EnderChestBlock::class,
-        FenceBlock::class,
-        FenceGateBlock::class,
-        FlowerPotBlock::class,
-        GrindstoneBlock::class,
-        HopperBlock::class,
-        JigsawBlock::class,
-        JukeboxBlock::class,
-        LecternBlock::class,
-        LeverBlock::class,
-        LightBlock::class,
-        LoomBlock::class,
-        NoteBlock::class,
-        PistonExtensionBlock::class,
-        PumpkinBlock::class,
-        RedstoneOreBlock::class,
-        RedstoneWireBlock::class,
-        RepeaterBlock::class,
-        RespawnAnchorBlock::class,
-        ShulkerBoxBlock::class,
-        SmithingTableBlock::class,
-        StonecutterBlock::class,
-        StructureBlock::class,
-        SweetBerryBushBlock::class,
-        TntBlock::class,
-        TrapdoorBlock::class
-    )
+    val INTERACTION_BLOCKS =
+        setOf(
+            AbstractCauldronBlock::class,
+            AbstractFurnaceBlock::class,
+            AbstractSignBlock::class,
+            AnvilBlock::class,
+            BarrelBlock::class,
+            BeaconBlock::class,
+            BedBlock::class,
+            BeehiveBlock::class,
+            BellBlock::class,
+            BrewingStandBlock::class,
+            ButtonBlock::class,
+            CakeBlock::class,
+            CampfireBlock::class,
+            CandleBlock::class,
+            CandleCakeBlock::class,
+            CartographyTableBlock::class,
+            CaveVinesBodyBlock::class,
+            CaveVinesHeadBlock::class,
+            ChestBlock::class,
+            ChiseledBookshelfBlock::class,
+            CommandBlock::class,
+            ComparatorBlock::class,
+            ComposterBlock::class,
+            CrafterBlock::class,
+            CraftingTableBlock::class,
+            DaylightDetectorBlock::class,
+            DecoratedPotBlock::class,
+            DispenserBlock::class,
+            DropperBlock::class,
+            DoorBlock::class,
+            DragonEggBlock::class,
+            EnchantingTableBlock::class,
+            EnderChestBlock::class,
+            FenceBlock::class,
+            FenceGateBlock::class,
+            FlowerPotBlock::class,
+            GrindstoneBlock::class,
+            HopperBlock::class,
+            JigsawBlock::class,
+            JukeboxBlock::class,
+            LecternBlock::class,
+            LeverBlock::class,
+            LightBlock::class,
+            LoomBlock::class,
+            NoteBlock::class,
+            PistonExtensionBlock::class,
+            PumpkinBlock::class,
+            RedstoneOreBlock::class,
+            RedstoneWireBlock::class,
+            RepeaterBlock::class,
+            RespawnAnchorBlock::class,
+            ShulkerBoxBlock::class,
+            SmithingTableBlock::class,
+            StonecutterBlock::class,
+            StructureBlock::class,
+            SweetBerryBushBlock::class,
+            TntBlock::class,
+            TrapdoorBlock::class
+        )
 
-    val fluids = listOf(
-        Fluids.LAVA,
-        Fluids.FLOWING_LAVA,
-        Fluids.WATER,
-        Fluids.FLOWING_WATER,
-        Fluids.EMPTY,
-    )
+    val FLUIDS =
+        listOf(
+            Fluids.LAVA,
+            Fluids.FLOWING_LAVA,
+            Fluids.WATER,
+            Fluids.FLOWING_WATER,
+            Fluids.EMPTY,
+        )
 
     fun SafeContext.blockState(pos: BlockPos): BlockState = world.getBlockState(pos)
     fun SafeContext.fluidState(pos: BlockPos): FluidState = world.getFluidState(pos)
diff --git a/src/main/kotlin/com/lambda/util/item/StackMovePlanner.kt b/src/main/kotlin/com/lambda/util/item/StackMovePlanner.kt
new file mode 100644
index 000000000..3b1756e9c
--- /dev/null
+++ b/src/main/kotlin/com/lambda/util/item/StackMovePlanner.kt
@@ -0,0 +1,144 @@
+/*
+ * 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.util.item
+
+import java.util.concurrent.ConcurrentHashMap
+
+/**
+ * Plans the shortest sequence of vanilla `PICKUP` slot clicks that moves an exact number of items
+ * of a single item type between two slots, using the cursor as the only intermediary.
+ *
+ * The model follows vanilla click semantics for a source slot, a destination slot and the cursor,
+ * all holding the same item type (or being empty):
+ *  - Left click with an empty cursor picks up the whole slot.
+ *  - Right click with an empty cursor picks up half the slot, rounded up.
+ *  - Left click with a held stack places as much as fits into the slot.
+ *  - Right click with a held stack places exactly one item.
+ *
+ * A breadth-first search over `(cursor, source)` (the destination count follows from the invariant
+ * `cursor + source + destination = total`) finds the minimal click count. Results are cached per
+ * distinct `(cursor, source, destination, maxCount, destinationGoal)` since the state space is tiny
+ * and transfers tend to repeat the same shapes.
+ */
+object StackMovePlanner {
+	enum class Target {
+		Source,
+		Destination
+	}
+
+	/** A single slot click: [button] `0` is a left click, `1` a right click. */
+	data class Click(val target: Target, val button: Int)
+
+	/** Item counts held by the cursor and both slots. */
+	data class State(val cursor: Int, val source: Int, val destination: Int) {
+		val total get() = cursor + source + destination
+	}
+
+	private data class Key(val cursor: Int, val source: Int, val destination: Int, val maxCount: Int, val goal: Int)
+
+	private val cache = ConcurrentHashMap>()
+
+	private val actions =
+		listOf(
+			Click(Target.Source, 0),
+			Click(Target.Source, 1),
+			Click(Target.Destination, 0),
+			Click(Target.Destination, 1)
+		)
+
+	/**
+	 * Applies one [click] to [state] following vanilla `PICKUP` semantics.
+	 * Returns the same instance when the click would have no effect.
+	 */
+	fun apply(state: State, click: Click, maxCount: Int): State {
+		val slot = if (click.target == Target.Source) state.source else state.destination
+		val cursor = state.cursor
+		val (newCursor, newSlot) =
+			if (cursor == 0) {
+				if (slot == 0) return state
+				val taken = if (click.button == 0) slot else (slot + 1) / 2
+				taken to slot - taken
+			} else {
+				val room = maxCount - slot
+				if (room <= 0) return state
+				val placed = if (click.button == 0) minOf(cursor, room) else 1
+				cursor - placed to slot + placed
+			}
+		return if (click.target == Target.Source) State(newCursor, newSlot, state.destination)
+		else State(newCursor, state.source, newSlot)
+	}
+
+	/**
+	 * Finds the shortest click sequence that ends with the destination holding exactly [destinationGoal]
+	 * items and an empty cursor. Returns `null` when the goal is unreachable (for example when the goal
+	 * exceeds [maxCount] or the items available).
+	 */
+	fun plan(state: State, maxCount: Int, destinationGoal: Int): List? {
+		if (maxCount <= 0 || destinationGoal < 0 || destinationGoal > maxCount) return null
+		if (destinationGoal > state.total) return null
+		if (state.cursor == 0 && state.destination == destinationGoal) return emptyList()
+		if (state.total - destinationGoal > maxCount) return null
+
+		val key = Key(state.cursor, state.source, state.destination, maxCount, destinationGoal)
+		return cache.getOrPut(key) { search(state, maxCount, destinationGoal) }
+	}
+
+	/**
+	 * Number of clicks needed for [plan], or `null` when unreachable.
+	 */
+	fun cost(state: State, maxCount: Int, destinationGoal: Int) = plan(state, maxCount, destinationGoal)?.size
+
+	private fun search(start: State, maxCount: Int, goal: Int): List? {
+		val width = maxCount + 1
+		fun index(state: State) = state.cursor * width + state.source
+
+		// parent[index] holds (parentIndex shl 2) or actionIndex; -1 marks unvisited.
+		val parent = IntArray(width * width) { -1 }
+		val queue = ArrayDeque()
+		val startIndex = index(start)
+		parent[startIndex] = startIndex shl 2
+		queue.addLast(start)
+
+		while (queue.isNotEmpty()) {
+			val state = queue.removeFirst()
+			if (state.cursor == 0 && state.destination == goal) return reconstruct(parent, width, startIndex, index(state))
+
+			actions.forEachIndexed { actionIndex, click ->
+				val next = apply(state, click, maxCount)
+				if (next === state) return@forEachIndexed
+				if (next.cursor > maxCount || next.source > maxCount || next.destination > maxCount) return@forEachIndexed
+				val nextIndex = index(next)
+				if (parent[nextIndex] != -1) return@forEachIndexed
+				parent[nextIndex] = (index(state) shl 2) or actionIndex
+				queue.addLast(next)
+			}
+		}
+		return null
+	}
+
+	private fun reconstruct(parent: IntArray, width: Int, startIndex: Int, endIndex: Int): List {
+		val clicks = ArrayDeque()
+		var current = endIndex
+		while (current != startIndex) {
+			val entry = parent[current]
+			clicks.addFirst(actions[entry and 3])
+			current = entry ushr 2
+		}
+		return clicks.toList()
+	}
+}
diff --git a/src/test/kotlin/StackMovePlannerTest.kt b/src/test/kotlin/StackMovePlannerTest.kt
new file mode 100644
index 000000000..52f7e9970
--- /dev/null
+++ b/src/test/kotlin/StackMovePlannerTest.kt
@@ -0,0 +1,115 @@
+/*
+ * 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 .
+ */
+
+import com.lambda.util.item.StackMovePlanner
+import com.lambda.util.item.StackMovePlanner.Click
+import com.lambda.util.item.StackMovePlanner.State
+import com.lambda.util.item.StackMovePlanner.Target
+import kotlin.random.Random
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+
+class StackMovePlannerTest {
+	private fun simulate(start: State, clicks: List, maxCount: Int) =
+		clicks.fold(start) { state, click -> StackMovePlanner.apply(state, click, maxCount) }
+
+	private fun assertReaches(start: State, maxCount: Int, goal: Int): List {
+		val plan = assertNotNull(StackMovePlanner.plan(start, maxCount, goal), "no plan for $start -> $goal (max $maxCount)")
+		val end = simulate(start, plan, maxCount)
+		assertEquals(goal, end.destination, "destination after $plan from $start")
+		assertEquals(0, end.cursor, "cursor after $plan from $start")
+		assertEquals(start.total, end.total, "items must be conserved")
+		return plan
+	}
+
+	@Test
+	fun wholeStackIntoEmptySlotIsTwoClicks() {
+		val plan = assertReaches(State(0, 64, 0), 64, 64)
+		assertEquals(listOf(Click(Target.Source, 0), Click(Target.Destination, 0)), plan)
+	}
+
+	@Test
+	fun singleItemIsThreeClicks() {
+		val plan = assertReaches(State(0, 64, 0), 64, 1)
+		assertEquals(3, plan.size)
+	}
+
+	@Test
+	fun halfStackUsesRightClickPickup() {
+		val plan = assertReaches(State(0, 64, 0), 64, 32)
+		assertEquals(listOf(Click(Target.Source, 1), Click(Target.Destination, 0)), plan)
+	}
+
+	@Test
+	fun partialCountBeatsPlacingOneAtATime() {
+		val plan = assertReaches(State(0, 64, 0), 64, 10)
+		assertTrue(plan.size < 12, "expected a smarter plan than 12 single placements, got ${plan.size}")
+	}
+
+	@Test
+	fun mergesIntoPartialDestination() {
+		// The whole source fits: pick up, place.
+		assertEquals(2, assertReaches(State(0, 14, 50), 64, 64).size)
+		// Only 14 of 20 fit, so the surplus has to go back: pick up, place, return.
+		assertEquals(3, assertReaches(State(0, 20, 50), 64, 64).size)
+	}
+
+	@Test
+	fun continuesFromItemsAlreadyOnCursor() {
+		assertReaches(State(5, 10, 0), 64, 15)
+		assertReaches(State(30, 0, 10), 64, 20)
+	}
+
+	@Test
+	fun alreadySatisfiedNeedsNoClicks() {
+		assertEquals(emptyList(), StackMovePlanner.plan(State(0, 10, 7), 64, 7))
+	}
+
+	@Test
+	fun rejectsUnreachableGoals() {
+		assertNull(StackMovePlanner.plan(State(0, 10, 0), 64, 11))
+		assertNull(StackMovePlanner.plan(State(0, 64, 64), 64, 65))
+		assertNull(StackMovePlanner.plan(State(0, 64, 64), 64, 10), "source cannot hold the surplus")
+		assertNull(StackMovePlanner.plan(State(0, 5, 0), 0, 1))
+	}
+
+	@Test
+	fun unstackableItemsMoveWhole() {
+		val plan = assertReaches(State(0, 1, 0), 1, 1)
+		assertEquals(2, plan.size)
+	}
+
+	@Test
+	fun everyReachableGoalHasAValidPlan() {
+		val random = Random(1337)
+		for (maxCount in listOf(1, 16, 64)) {
+			repeat(300) {
+				val source = random.nextInt(0, maxCount + 1)
+				val destination = random.nextInt(0, maxCount + 1)
+				val total = source + destination
+				val low = (total - maxCount).coerceAtLeast(0)
+				val high = minOf(maxCount, total)
+				if (low > high) return@repeat
+				val goal = random.nextInt(low, high + 1)
+				assertReaches(State(0, source, destination), maxCount, goal)
+			}
+		}
+	}
+}

From 1a51af52f158aff5ac4ced9a1674d0aa081b12be Mon Sep 17 00:00:00 2001
From: beanbag44 <107891830+beanbag44@users.noreply.github.com>
Date: Fri, 2 Oct 2026 17:25:03 +0100
Subject: [PATCH 22/43] update jackson version

---
 gradle.properties                                    |  2 +-
 .../com/lambda/task/tasks/ContainerTransferTask.kt   | 12 ++++--------
 2 files changed, 5 insertions(+), 9 deletions(-)

diff --git a/gradle.properties b/gradle.properties
index 71b45c1cf..b86d9f6a1 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -35,7 +35,7 @@ baritoneVersion=1.14.0
 discordIPCVersion=6f6b6cce17
 classGraphVersion=4.8.184
 ktorVersion=3.3.3
-jacksonVersion=3.1.1
+jacksonVersion=3.1.4
 mockkVersion=1.14.7
 spairVersion=1.90.0
 
diff --git a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt
index 9d5af96c5..15f049ed6 100644
--- a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt
+++ b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt
@@ -226,7 +226,6 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 					toContainers = listOf(to),
 					fromSelection = fromStack,
 					toSelection = toStack,
-					limit = remaining
 				).thenOrNull {
 					if (!canReceive(to)) {
 						toQueue.removeAll { it.haveMatchingInventories(to) }
@@ -253,7 +252,6 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 					toContainers = listOf(InventoryContainer, HotbarContainer),
 					fromSelection = fromStack,
 					toSelection = StackSelection.ANYTHING,
-					limit = remaining,
 					allowReplace = false,
 					trackResult = false
 				).thenOrNull {
@@ -279,8 +277,7 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 					fromContainers = listOf(InventoryContainer, HotbarContainer),
 					toContainers = listOf(to),
 					fromSelection = fromStack,
-					toSelection = toStack,
-					limit = remaining
+					toSelection = toStack
 				).thenOrNull {
 					if (!canReceive(to)) {
 						toQueue.removeAll { it.haveMatchingInventories(to) }
@@ -317,7 +314,6 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 		private val toContainers: List,
 		private val fromSelection: StackSelection,
 		toSelection: StackSelection,
-		private val limit: Int = if (unlimited) Int.MAX_VALUE else remaining,
 		private val allowReplace: Boolean = true,
 		private val trackResult: Boolean = true
 	) : Task() {
@@ -337,8 +333,8 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 			lastFailureReason = InvRequestFailureReason.None
 
 			val currentLimit =
-				if (limit == Int.MAX_VALUE) Int.MAX_VALUE
-				else (limit - moved).coerceAtLeast(0)
+				if (remaining == Int.MAX_VALUE) Int.MAX_VALUE
+				else (remaining - moved).coerceAtLeast(0)
 			if (currentLimit <= 0) {
 				finish()
 				return
@@ -346,7 +342,7 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 
 			val currentSelection =
 				fromSelection.mutate(
-					if (limit == Int.MAX_VALUE) 0
+					if (remaining == Int.MAX_VALUE) 0
 					else minOf(currentLimit, fromSelection.count.coerceAtLeast(1))
 				) { notEmpty() }
 

From c18a7c389f8a52144a9cfb4d47a841fbc4552f9e Mon Sep 17 00:00:00 2001
From: beanbag44 <107891830+beanbag44@users.noreply.github.com>
Date: Sun, 4 Oct 2026 13:19:22 +0100
Subject: [PATCH 23/43] better

---
 .../com/lambda/ContainerTransferGameTest.kt   | 961 ++++++++++++++++++
 src/gametest/resources/fabric.mod.json        |   4 +
 .../lambda/interaction/container/Container.kt |   2 +-
 .../container/selection/ContainerSelection.kt |  21 +-
 .../container/selection/StackSelection.kt     |   5 +-
 .../handler/handlers/ContainerHandler.kt      |  27 +-
 src/main/kotlin/com/lambda/task/Task.kt       |   3 +-
 .../task/tasks/ContainerTransferTask.kt       | 247 +++--
 8 files changed, 1165 insertions(+), 105 deletions(-)
 create mode 100644 src/gametest/kotlin/com/lambda/ContainerTransferGameTest.kt

diff --git a/src/gametest/kotlin/com/lambda/ContainerTransferGameTest.kt b/src/gametest/kotlin/com/lambda/ContainerTransferGameTest.kt
new file mode 100644
index 000000000..e51932277
--- /dev/null
+++ b/src/gametest/kotlin/com/lambda/ContainerTransferGameTest.kt
@@ -0,0 +1,961 @@
+/*
+ * 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 .
+ */
+
+@file:Suppress("unused")
+
+package com.lambda
+
+import com.lambda.Lambda.LOG
+import com.lambda.config.automation.AutomationConfig
+import com.lambda.config.blocks.BuildConfig
+import com.lambda.config.blocks.InventoryConfig
+import com.lambda.context.Automated
+import com.lambda.interaction.container.Container
+import com.lambda.interaction.container.ContainerSerializer
+import com.lambda.interaction.container.ContainerType
+import com.lambda.interaction.container.PlacedContainer
+import com.lambda.interaction.container.containers.HotbarContainer
+import com.lambda.interaction.container.containers.InventoryContainer
+import com.lambda.interaction.container.containers.external.DoubleChestContainer
+import com.lambda.interaction.container.selection.ContainerSelection
+import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.containerSelection
+import com.lambda.interaction.container.selection.StackSelection
+import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection
+import com.lambda.interaction.container.selection.select
+import com.lambda.interaction.handler.handlers.ContainerSearchScope
+import com.lambda.task.RootTask
+import com.lambda.task.Task
+import com.lambda.task.start
+import com.lambda.task.tasks.openContainer
+import com.lambda.task.tasks.transfer
+import com.lambda.task.tasks.wrappers.thenAction
+import com.lambda.threading.runSafe
+import net.fabricmc.fabric.api.client.gametest.v1.FabricClientGameTest
+import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext
+import net.fabricmc.fabric.api.client.gametest.v1.context.TestServerContext
+import net.minecraft.block.entity.ChestBlockEntity
+import net.minecraft.client.gui.screen.world.WorldCreator
+import net.minecraft.entity.ItemEntity
+import net.minecraft.item.Item
+import net.minecraft.item.ItemStack
+import net.minecraft.item.Items
+import net.minecraft.util.math.BlockPos
+
+/**
+ * Exercises [com.lambda.task.tasks.ContainerTransferTask] inside a real singleplayer world: chests are placed and
+ * filled with server commands, the real task opens them, clicks through the real inventory manager, and the
+ * server-side inventories are read back afterwards.
+ *
+ * Every scenario additionally checks that no item was created or destroyed, that the client and server agree on
+ * the player inventory (no desync), that no screen was left open and that the cursor is empty.
+ *
+ * Scenarios never throw mid-way; all expectation failures are collected and reported together at the end so one
+ * run gives the full picture.
+ */
+@Suppress("UnstableApiUsage")
+object ContainerTransferGameTest : FabricClientGameTest {
+	private const val PLAYER = "Steve"
+	private const val TRANSFER_TIMEOUT = 20 * 40
+	private const val OPEN_TIMEOUT = 20 * 10
+
+	private val STONE: Item get() = Items.STONE
+	private val DIRT: Item get() = Items.DIRT
+	private val COBBLE: Item get() = Items.COBBLESTONE
+	private val OAK_PLANKS: Item get() = Items.OAK_PLANKS
+	private val SAND: Item get() = Items.SAND
+	private val GRAVEL: Item get() = Items.GRAVEL
+	private val GLASS: Item get() = Items.GLASS
+	private val BRICKS: Item get() = Items.BRICKS
+
+	private val failures = mutableListOf()
+	private val passed = mutableListOf()
+
+	override fun runTest(context: ClientGameTestContext) {
+		failures.clear()
+		passed.clear()
+
+		val singleplayer = context.worldBuilder()
+			.adjustSettings { it.gameMode = WorldCreator.Mode.SURVIVAL }
+			.create()
+
+		try {
+			singleplayer.clientWorld.waitForChunksDownload()
+			val arena = Arena(context, singleplayer.server)
+			arena.prepareWorld()
+
+			scenarios.forEach { scenario -> arena.run(scenario) }
+		} catch (e: ClientGoneException) {
+			LOG.error("Container transfer game test aborted: ${e.message}")
+			failures += "[run] aborted: the client is no longer running"
+		} finally {
+			runCatching { context.runOnClient { RootTask.cancel() } }
+			runCatching { singleplayer.close() }
+		}
+
+		LOG.info("Container transfer game test: ${passed.size} scenarios passed, ${failures.size} expectations failed")
+		passed.forEach { LOG.info("  PASS $it") }
+		failures.forEach { LOG.error("  FAIL $it") }
+		check(failures.isEmpty()) {
+			"${failures.size} container transfer expectations failed:\n" + failures.joinToString("\n") { "  - $it" }
+		}
+	}
+
+	// =====================================================================================================
+	// Scenario DSL
+	// =====================================================================================================
+
+	/** A stack as the `/item` command spells it. */
+	data class Spec(val id: String, val count: Int, val components: String = "") {
+		override fun toString() = "minecraft:$id$components $count"
+	}
+
+	private fun stone(count: Int) = Spec("stone", count)
+	private fun dirt(count: Int) = Spec("dirt", count)
+	private fun cobble(count: Int) = Spec("cobblestone", count)
+	private fun namedStone(count: Int, name: String) = Spec("stone", count, "[custom_name='\"$name\"']")
+	private fun planks(count: Int) = Spec("oak_planks", count)
+	private fun sand(count: Int) = Spec("sand", count)
+	private fun gravel(count: Int) = Spec("gravel", count)
+	private fun glass(count: Int) = Spec("glass", count)
+	private fun bricks(count: Int) = Spec("bricks", count)
+
+	class Scenario(val name: String, val body: Arena.() -> Unit)
+
+	/** The client window was closed (or crashed) underneath the test; nothing further can run. */
+	class ClientGoneException(cause: Throwable) : RuntimeException("the client is no longer running", cause)
+
+	private fun scenario(name: String, body: Arena.() -> Unit) = Scenario(name, body)
+
+	class Outcome(val state: Task.State, val error: String?, val ticks: Int) {
+		val succeeded get() = state == Task.State.Completed
+		val failed get() = state == Task.State.Failed
+	}
+
+	/** Server-side truth after a scenario: chest contents by label, the player's 36 main slots, ender chest, items on the ground. */
+	class Snapshot(
+		val chests: Map>,
+		val player: List,
+		val enderChest: List,
+		val dropped: List
+	) {
+		fun chest(label: String, item: Item) = chests[label].orEmpty().filter { it.item == item }.sumOf { it.count }
+		fun hotbar(item: Item) = player.take(9).filter { it.item == item }.sumOf { it.count }
+		fun inventory(item: Item) = player.drop(9).filter { it.item == item }.sumOf { it.count }
+		fun player(item: Item) = hotbar(item) + inventory(item)
+		fun enderChest(item: Item) = enderChest.filter { it.item == item }.sumOf { it.count }
+
+		fun totals(): Map {
+			val totals = mutableMapOf()
+			(chests.values.flatten() + player + enderChest + dropped)
+				.filter { !it.isEmpty }
+				.forEach { totals.merge(it.item, it.count, Int::plus) }
+			return totals
+		}
+
+		fun describe() = buildString {
+			chests.forEach { (label, stacks) -> append(label).append('=').append(stacks.filter { !it.isEmpty }).append(' ') }
+			append("player=").append(player.filter { !it.isEmpty })
+			if (enderChest.any { !it.isEmpty }) append(" enderChest=").append(enderChest.filter { !it.isEmpty })
+			if (dropped.isNotEmpty()) append(" dropped=").append(dropped)
+		}
+	}
+
+	class Arena(private val context: ClientGameTestContext, val server: TestServerContext) {
+		private val center = BlockPos(0, -60, 0)
+
+		/** Single chest slots around the player, each two blocks away and facing the player. */
+		private val singles = mapOf(
+			"A" to (center.add(2, 0, 0) to "west"),
+			"B" to (center.add(-2, 0, 0) to "east"),
+			"C" to (center.add(0, 0, 2) to "north"),
+			"D" to (center.add(0, 0, -2) to "south")
+		)
+
+		/**
+		 * A double chest ("E") next to B, made of two east-facing halves. For an east-facing chest vanilla expects
+		 * the `left` half's partner to its south, so `left` sits at z+1 and `right` at z+2.
+		 */
+		private val doubleLeft = center.add(-2, 0, 1)
+		private val doubleRight = center.add(-2, 0, 2)
+
+		private val placed = linkedMapOf>()
+		private lateinit var scenarioName: String
+		private var before: Snapshot? = null
+
+		val automated: Automated get() = AutomationConfig.DEFAULT
+		val player get() = ContainerSelection.HOTBAR_AND_INVENTORY
+
+		fun prepareWorld() {
+			server.runCommand("/gamemode survival $PLAYER")
+			// Daylight, weather and mob spawning are already disabled by the world builder's consistent settings.
+			server.runCommand("/difficulty peaceful")
+			server.runCommand("/time set noon")
+			server.runCommand("/weather clear")
+			server.runCommand("/fill ${center.x - 5} ${center.y - 1} ${center.z - 5} ${center.x + 5} ${center.y - 1} ${center.z + 5} minecraft:stone")
+			server.runCommand("/fill ${center.x - 5} ${center.y} ${center.z - 5} ${center.x + 5} ${center.y + 4} ${center.z + 5} minecraft:air")
+			teleportPlayer()
+			context.waitTicks(10)
+		}
+
+		fun run(scenario: Scenario) {
+			scenarioName = scenario.name
+			LOG.info("=== Transfer scenario: ${scenario.name}")
+			val failuresBefore = failures.size
+			try {
+				reset()
+				scenario.body(this)
+			} catch (e: ClientGoneException) {
+				throw e
+			} catch (e: Throwable) {
+				fail("threw ${e::class.simpleName}: ${e.message}")
+				LOG.error("Scenario '${scenario.name}' threw", e)
+			} finally {
+				cancelTasks()
+			}
+			if (failures.size == failuresBefore) {
+				passed += scenario.name
+				LOG.info("PASS ${scenario.name}")
+			} else {
+				LOG.warn("FAIL ${scenario.name} (${failures.size - failuresBefore} expectation(s))")
+			}
+		}
+
+		// ---- world setup ----------------------------------------------------------------------------------
+
+		private fun reset() {
+			cancelTasks()
+			server.runCommand("/clear $PLAYER")
+			server.runCommand("/kill @e[type=item]")
+			placed.values.flatten().forEach { server.runCommand("/setblock ${it.x} ${it.y} ${it.z} minecraft:air") }
+			onClient { allPositions().forEach(ContainerSerializer::removeContainer) }
+			repeat(27) { server.runCommand("/item replace entity $PLAYER enderchest.$it with minecraft:air") }
+			placed.clear()
+			before = null
+			teleportPlayer()
+			context.waitTicks(5)
+		}
+
+		private fun teleportPlayer() {
+			server.runCommand("/tp $PLAYER ${center.x + 0.5} ${center.y} ${center.z + 0.5} 0 0")
+		}
+
+		private fun allPositions() = singles.values.map { it.first } + doubleLeft + doubleRight
+
+		/** Places a single chest at slot [label] holding [contents] in order. */
+		fun chest(label: String, vararg contents: Spec): String {
+			val (pos, facing) = singles.getValue(label)
+			server.runCommand("/setblock ${pos.x} ${pos.y} ${pos.z} minecraft:chest[facing=$facing]")
+			fill(pos, contents.toList())
+			placed[label] = listOf(pos)
+			return label
+		}
+
+		/** Places a full chest at [label] whose every slot holds [stack]. */
+		fun fullChest(label: String, stack: Spec) = chest(label, *Array(27) { stack })
+
+		/** Places the double chest "E" (54 slots) holding [contents] in order. */
+		fun doubleChest(vararg contents: Spec): String {
+			server.runCommand("/setblock ${doubleRight.x} ${doubleRight.y} ${doubleRight.z} minecraft:chest[facing=east,type=right]")
+			server.runCommand("/setblock ${doubleLeft.x} ${doubleLeft.y} ${doubleLeft.z} minecraft:chest[facing=east,type=left]")
+			fill(doubleRight, contents.take(27))
+			fill(doubleLeft, contents.drop(27))
+			placed["E"] = listOf(doubleLeft, doubleRight)
+			return "E"
+		}
+
+		private fun fill(pos: BlockPos, contents: List) {
+			contents.forEachIndexed { slot, spec ->
+				server.runCommand("/item replace block ${pos.x} ${pos.y} ${pos.z} container.$slot with $spec")
+			}
+		}
+
+		fun hotbar(slot: Int, spec: Spec) = server.runCommand("/item replace entity $PLAYER hotbar.$slot with $spec")
+		fun inventory(slot: Int, spec: Spec) = server.runCommand("/item replace entity $PLAYER inventory.$slot with $spec")
+		fun enderchest(slot: Int, spec: Spec) = server.runCommand("/item replace entity $PLAYER enderchest.$slot with $spec")
+		fun fillHotbar(spec: Spec) = repeat(9) { hotbar(it, spec) }
+		fun fillInventory(spec: Spec) = repeat(27) { inventory(it, spec) }
+
+		/**
+		 * Lets Lambda learn about every placed chest the way it does in normal play: the player opens each one once
+		 * and closes it again, which records the container and its contents on disk. Must be called after the
+		 * chests were filled. Also takes the "before" snapshot for the conservation check.
+		 */
+		fun ready() {
+			context.waitTicks(2)
+			val truth = snapshot()
+			placed.forEach { (label, positions) ->
+				val pos = positions.first()
+				val expectedItems = truth.chests[label].orEmpty().sumOf { it.count }
+				var recorded = false
+				repeat(2) { attempt ->
+					if (recorded) return@repeat
+					val open = runTask(OPEN_TIMEOUT) {
+						with(automated) { openContainer(pos) }.thenAction { player.closeScreen() }
+					}
+					if (!open.succeeded) fail("could not open chest $label at ${pos.toShortString()}: ${open.state} ${open.error}")
+					context.waitFor({ it.currentScreen == null }, OPEN_TIMEOUT)
+					context.waitTicks(2)
+					recorded = context.computeOnClient {
+						ContainerSerializer.serializedContainers.any { container ->
+							container.isAt(positions.toSet()) && container.stacks.sumOf { it.count } == expectedItems
+						}
+					}
+					if (!recorded && attempt == 0) LOG.warn("chest $label was recorded with the wrong contents, opening it again")
+				}
+				if (!recorded) fail("chest $label was not recorded with its $expectedItems items after opening it")
+			}
+			before = truth
+		}
+
+		// ---- running the task ----------------------------------------------------------------------------
+
+		fun chests(vararg labels: String): ContainerSelection {
+			val positions = labels.flatMap { placed.getValue(it) }.toSet()
+			return containerSelection(ContainerSearchScope.All) {
+				ofAnyType(ContainerType.Chest)
+				predicate { it.isAt(positions) }
+			}
+		}
+
+		fun transfer(
+			fromStack: StackSelection,
+			fromSelection: ContainerSelection = ContainerSelection.ACCESSED,
+			toSelection: ContainerSelection,
+			toStack: StackSelection = StackSelection.ANYTHING,
+			automated: Automated = this.automated,
+			onTick: (net.minecraft.client.MinecraftClient) -> Unit = {}
+		): Outcome {
+			if (before == null) ready()
+			val outcome = runTask(TRANSFER_TIMEOUT, onTick) {
+				createTransferTask(automated, fromStack, fromSelection, toSelection, toStack)
+			}
+			if (!outcome.succeeded) cancelTasks()
+			context.waitFor({ it.currentScreen == null }, OPEN_TIMEOUT)
+			context.waitTicks(5)
+			return outcome
+		}
+
+		private fun runTask(
+			timeout: Int,
+			onTick: (net.minecraft.client.MinecraftClient) -> Unit = {},
+			build: () -> Task<*>
+		): Outcome {
+			var task: Task<*>? = null
+			var error: String? = null
+			context.runOnClient {
+				runSafe {
+					task = build().onFailure { error = it.message }.start()
+				}
+			}
+			val started = task ?: return Outcome(Task.State.Failed, "no safe context to start the task", 0)
+			val ticks = try {
+				context.waitFor({ client ->
+					onTick(client)
+					started.state != Task.State.Running && started.state != Task.State.Paused
+				}, timeout)
+			} catch (_: Throwable) {
+				timeout
+			}
+			return Outcome(started.state, error, ticks)
+		}
+
+		private fun cancelTasks() {
+			onClient { RootTask.cancel() }
+			// A cancelled task may still have an "open container" packet in flight; the server answers it a few
+			// ticks later with a screen that would otherwise leak into the next scenario.
+			repeat(4) {
+				context.waitTicks(3)
+				onClient { it.player?.takeIf { p -> p.currentScreenHandler !== p.playerScreenHandler }?.closeScreen() }
+			}
+			context.waitTicks(2)
+		}
+
+		/** [ClientGameTestContext.runOnClient] that turns a closed client into a fatal [ClientGoneException]. */
+		private fun onClient(block: (net.minecraft.client.MinecraftClient) -> Unit) {
+			try {
+				context.runOnClient { block(it) }
+			} catch (e: IllegalStateException) {
+				if (e.message?.contains("no client is running") == true) throw ClientGoneException(e)
+				throw e
+			}
+		}
+
+		// ---- reading the world -----------------------------------------------------------------------------
+
+		fun snapshot(): Snapshot {
+			val labels = placed.toMap()
+			return server.computeOnServer { mc ->
+				val world = mc.overworld
+				val chests = labels.mapValues { (_, positions) ->
+					positions.flatMap { pos ->
+						val entity = world.getBlockEntity(pos) as? ChestBlockEntity ?: return@flatMap emptyList()
+						(0 until entity.size()).map { entity.getStack(it).copy() }
+					}
+				}
+				val serverPlayer = mc.playerManager.playerList.first { it.gameProfile.name == PLAYER }
+				val player = serverPlayer.inventory.mainStacks.map { it.copy() }
+				val enderChest = serverPlayer.enderChestInventory.let { ec ->
+					(0 until ec.size()).map { ec.getStack(it).copy() }
+				}
+				val dropped = world.iterateEntities().filterIsInstance().map { it.stack.copy() }
+				Snapshot(chests, player, enderChest, dropped)
+			}
+		}
+
+		/** The player's main inventory as the client sees it, to detect desync against the server. */
+		private fun clientPlayerStacks(): List =
+			context.computeOnClient, RuntimeException> { it.player!!.inventory.mainStacks.map { s -> s.copy() } }
+
+		// ---- expectations ------------------------------------------------------------------------------------
+
+		fun fail(message: String) {
+			failures += "[$scenarioName] $message"
+			LOG.warn("FAIL [$scenarioName] $message")
+		}
+
+		fun expect(condition: Boolean, message: () -> String) {
+			if (!condition) fail(message())
+		}
+
+		fun expectEquals(expected: Any?, actual: Any?, what: String) {
+			if (expected != actual) fail("$what: expected $expected but was $actual")
+		}
+
+		fun expectSuccess(outcome: Outcome) =
+			expect(outcome.succeeded) { "task did not succeed: ${outcome.state} ${outcome.error ?: ""} after ${outcome.ticks} ticks" }
+
+		fun expectFailure(outcome: Outcome) =
+			expect(outcome.failed) { "task should have failed but was ${outcome.state} (${outcome.error ?: ""}) after ${outcome.ticks} ticks" }
+
+		/** Item conservation, client/server agreement, closed screens and an empty cursor. Returns the snapshot. */
+		fun expectClean(): Snapshot {
+			val after = snapshot()
+			before?.let { expectEquals(it.totals(), after.totals(), "items were created or destroyed (${after.describe()})") }
+			expect(after.dropped.isEmpty()) { "items ended up on the ground: ${after.dropped}" }
+
+			val client = clientPlayerStacks()
+			val desynced = client.indices.filter { !ItemStack.areEqual(client[it], after.player[it]) }
+			expect(desynced.isEmpty()) {
+				"client and server disagree on player slots $desynced: client=${desynced.map { client[it] }} server=${desynced.map { after.player[it] }}"
+			}
+
+			val screen = context.computeOnClient { mc ->
+				mc.currentScreen?.let { it::class.simpleName } ?: ""
+			}
+			expect(screen.isEmpty()) { "a screen was left open: $screen" }
+			val cursor = context.computeOnClient { it.player!!.currentScreenHandler.cursorStack.copy() }
+			expect(cursor.isEmpty) { "cursor still holds $cursor" }
+			return after
+		}
+
+		// ---- configuration variants ----------------------------------------------------------------------
+
+		fun automatedWithout(vararg types: ContainerType): Automated {
+			val base = automated
+			val inventory = object : InventoryConfig by base.inventoryConfig {
+				override val allowedContainers: Collection = ContainerType.entries - types.toSet()
+			}
+			return object : Automated by base {
+				override val inventoryConfig = inventory
+			}
+		}
+
+		fun automatedWithInventoryLimit(limit: Int): Automated {
+			val base = automated
+			val build = object : BuildConfig by base.buildConfig {
+				override val inventoryLimit = limit
+			}
+			return object : Automated by base {
+				override val buildConfig = build
+			}
+		}
+	}
+
+	/** Lives outside [Arena] so the call is not shadowed by [Arena.transfer]. */
+	private fun createTransferTask(
+		automated: Automated,
+		fromStack: StackSelection,
+		fromSelection: ContainerSelection,
+		toSelection: ContainerSelection,
+		toStack: StackSelection
+	): Task<*> = with(automated) { transfer(fromStack, fromSelection, toSelection, toStack) }
+
+	private fun Container.isAt(positions: Set) =
+		when (this) {
+			is DoubleChestContainer -> leftPos in positions || rightPos in positions
+			is PlacedContainer -> pos in positions
+			else -> false
+		}
+
+	// =====================================================================================================
+	// Scenarios
+	// =====================================================================================================
+
+	private val scenarios = listOf(
+		scenario("pull a whole stack from a chest") {
+			chest("A", stone(64))
+			val outcome = transfer(STONE.select(64), chests("A"), player)
+			expectSuccess(outcome)
+			val after = expectClean()
+			expectEquals(64, after.player(STONE), "stone with the player")
+			expectEquals(0, after.chest("A", STONE), "stone left in A")
+		},
+
+		scenario("pull an exact partial count from a chest") {
+			chest("A", stone(64))
+			val outcome = transfer(STONE.select(20), chests("A"), player)
+			expectSuccess(outcome)
+			val after = expectClean()
+			expectEquals(20, after.player(STONE), "stone with the player")
+			expectEquals(44, after.chest("A", STONE), "stone left in A")
+		},
+
+		scenario("pull a count spanning several stacks") {
+			chest("A", stone(64), stone(64))
+			val outcome = transfer(STONE.select(100), chests("A"), player)
+			expectSuccess(outcome)
+			val after = expectClean()
+			expectEquals(100, after.player(STONE), "stone with the player")
+			expectEquals(28, after.chest("A", STONE), "stone left in A")
+		},
+
+		scenario("a count of zero moves everything matching and nothing else") {
+			chest("A", stone(64), dirt(64), stone(64), stone(5))
+			val outcome = transfer(STONE.select(0), chests("A"), player)
+			expectSuccess(outcome)
+			val after = expectClean()
+			expectEquals(133, after.player(STONE), "stone with the player")
+			expectEquals(0, after.chest("A", STONE), "stone left in A")
+			expectEquals(64, after.chest("A", DIRT), "dirt must stay in A")
+			expectEquals(0, after.player(DIRT), "dirt with the player")
+		},
+
+		scenario("push a stack into a chest, merging with its partial stack") {
+			chest("A", stone(10))
+			hotbar(0, stone(64))
+			val outcome = transfer(STONE.select(64), player, chests("A"))
+			expectSuccess(outcome)
+			val after = expectClean()
+			expectEquals(74, after.chest("A", STONE), "stone in A")
+			expectEquals(0, after.player(STONE), "stone with the player")
+		},
+
+		scenario("push an exact partial count into a chest") {
+			chest("A")
+			hotbar(0, stone(64))
+			val outcome = transfer(STONE.select(13), player, chests("A"))
+			expectSuccess(outcome)
+			val after = expectClean()
+			expectEquals(13, after.chest("A", STONE), "stone in A")
+			expectEquals(51, after.player(STONE), "stone with the player")
+		},
+
+		scenario("swap a foreign stack out of a full chest") {
+			fullChest("A", dirt(64))
+			hotbar(0, stone(64))
+			val outcome = transfer(STONE.select(64), HotbarContainer.select(), chests("A"))
+			expectSuccess(outcome)
+			val after = expectClean()
+			expectEquals(64, after.chest("A", STONE), "stone in A")
+			expectEquals(26 * 64, after.chest("A", DIRT), "dirt in A")
+			expectEquals(64, after.player(DIRT), "dirt with the player")
+		},
+
+		scenario("move a stack between two chests through the player inventory") {
+			chest("A", stone(64))
+			chest("B")
+			val outcome = transfer(STONE.select(64), chests("A"), chests("B"))
+			expectSuccess(outcome)
+			val after = expectClean()
+			expectEquals(64, after.chest("B", STONE), "stone in B")
+			expectEquals(0, after.chest("A", STONE), "stone in A")
+			expectEquals(0, after.player(STONE), "stone with the player")
+		},
+
+		scenario("move an exact partial count between chests") {
+			chest("A", stone(64), stone(64))
+			chest("B")
+			val outcome = transfer(STONE.select(70), chests("A"), chests("B"))
+			expectSuccess(outcome)
+			val after = expectClean()
+			expectEquals(70, after.chest("B", STONE), "stone in B")
+			expectEquals(58, after.chest("A", STONE), "stone in A")
+			expectEquals(0, after.player(STONE), "stone with the player")
+		},
+
+		scenario("a source larger than the player inventory needs several trips into a double chest") {
+			fullChest("A", stone(64))
+			fullChest("C", stone(64))
+			doubleChest()
+			val outcome = transfer(STONE.select(0), chests("A", "C"), chests("E"))
+			expectSuccess(outcome)
+			val after = expectClean()
+			expectEquals(54 * 64, after.chest("E", STONE), "stone in the double chest")
+			expectEquals(0, after.chest("A", STONE) + after.chest("C", STONE), "stone left in the sources")
+			expectEquals(0, after.player(STONE), "stone with the player")
+		},
+
+		scenario("draw from a double chest as the source") {
+			doubleChest(*Array(54) { stone(64) })
+			chest("A")
+			val outcome = transfer(STONE.select(27 * 64), chests("E"), chests("A"))
+			expectSuccess(outcome)
+			val after = expectClean()
+			expectEquals(27 * 64, after.chest("A", STONE), "stone in A")
+			expectEquals(27 * 64, after.chest("E", STONE), "stone left in the double chest")
+			expectEquals(0, after.player(STONE), "stone with the player")
+		},
+
+		scenario("continue to the next destination when the first fills up mid trip") {
+			chest("A", stone(64), stone(1))
+			chest("B", *Array(26) { dirt(64) })
+			chest("C")
+			val outcome = transfer(STONE.select(65), chests("A"), chests("B", "C"), stackSelection { isEmpty() })
+			expectSuccess(outcome)
+			val after = expectClean()
+			expectEquals(64, after.chest("B", STONE), "stone in B")
+			expectEquals(1, after.chest("C", STONE), "stone in C")
+			expectEquals(0, after.player(STONE), "stone stranded with the player")
+		},
+
+		scenario("trips do not push the player's own matching items") {
+			chest("A", stone(64))
+			chest("B")
+			hotbar(0, stone(5))
+			val outcome = transfer(STONE.select(0), chests("A"), chests("B"))
+			expectSuccess(outcome)
+			val after = expectClean()
+			expectEquals(64, after.chest("B", STONE), "stone in B")
+			expectEquals(5, after.player(STONE), "the player's own stone")
+		},
+
+		scenario("a stack selection over several items moves only those items") {
+			chest("A", stone(10), dirt(10), cobble(10))
+			val outcome = transfer(stackSelection(20) { ofAnyItems(listOf(STONE, DIRT)) }, chests("A"), player)
+			expectSuccess(outcome)
+			val after = expectClean()
+			expectEquals(20, after.player(STONE) + after.player(DIRT), "stone and dirt with the player")
+			expectEquals(0, after.player(COBBLE), "cobblestone with the player")
+		},
+
+		scenario("named and unnamed variants of an item are not swapped against each other") {
+			chest("A", namedStone(64, "Keep"))
+			hotbar(0, stone(10))
+			val outcome = transfer(STONE.select(64), chests("A"), player)
+			expectSuccess(outcome)
+			val after = expectClean()
+			expectEquals(0, after.chest("A", STONE), "stone left in A")
+			expectEquals(74, after.player(STONE), "stone with the player")
+		},
+
+		scenario("whole stack moves land in the requested player container") {
+			chest("A", stone(64))
+			val outcome = transfer(STONE.select(64), chests("A"), InventoryContainer.select())
+			expectSuccess(outcome)
+			val after = expectClean()
+			expectEquals(64, after.inventory(STONE), "stone in the main inventory")
+			expectEquals(0, after.hotbar(STONE), "stone in the hotbar")
+		},
+
+		scenario("the stack selection comparator picks the source stack") {
+			chest("A", stone(1), stone(64))
+			val selection = stackSelection(64) {
+				isItem(STONE)
+				sortedWith { compareByDescending { it.stack.count } }
+			}
+			val outcome = transfer(selection, chests("A"), player)
+			expectSuccess(outcome)
+			val after = expectClean()
+			expectEquals(64, after.player(STONE), "stone with the player")
+			expectEquals(1, after.chest("A", STONE), "the single stone must stay")
+		},
+
+		scenario("hasSpace in the destination selection skips full chests") {
+			fullChest("B", dirt(64))
+			chest("C")
+			hotbar(0, stone(64))
+			val outcome = transfer(
+				STONE.select(64),
+				player,
+				containerSelection(ContainerSearchScope.All) {
+					ofAnyType(ContainerType.Chest)
+					hasSpace(STONE.select(1))
+				}
+			)
+			expectSuccess(outcome)
+			val after = expectClean()
+			expectEquals(64, after.chest("C", STONE), "stone in C")
+			expectEquals(27 * 64, after.chest("B", DIRT), "B must be untouched")
+		},
+
+		scenario("destination filter NOTHING still allows empty slots") {
+			chest("A")
+			hotbar(0, stone(64))
+			val outcome = transfer(STONE.select(64), player, chests("A"), StackSelection.NOTHING)
+			expectSuccess(outcome)
+			val after = expectClean()
+			expectEquals(64, after.chest("A", STONE), "stone in A")
+		},
+
+		scenario("shulker box scope moves the box holding the item") {
+			chest("A", Spec("shulker_box", 1, "[container=[{slot:0,item:{id:\"minecraft:diamond\",count:64}}]]"), stone(64))
+			val outcome = transfer(
+				stackSelection(1) {
+					isItem(Items.DIAMOND)
+					alsoInShulkerBoxes()
+				},
+				chests("A"),
+				player
+			)
+			expectSuccess(outcome)
+			val after = expectClean()
+			expectEquals(1, after.player(Items.SHULKER_BOX), "shulker boxes with the player")
+			expectEquals(64, after.chest("A", STONE), "stone must stay in A")
+		},
+
+		scenario("fails cleanly when no source holds the item") {
+			chest("A", dirt(64))
+			val outcome = transfer(STONE.select(10), chests("A"), player)
+			expectFailure(outcome)
+			val after = expectClean()
+			expectEquals(64, after.chest("A", DIRT), "dirt in A")
+			expectEquals(0, after.player(DIRT), "dirt with the player")
+		},
+
+		scenario("fails cleanly when sources hold less than requested") {
+			chest("A", stone(10))
+			val outcome = transfer(STONE.select(11), chests("A"), player)
+			expectFailure(outcome)
+			val after = expectClean()
+			expectEquals(10, after.chest("A", STONE), "stone in A")
+		},
+
+		scenario("fails cleanly when source and destination are the same chest") {
+			chest("A", stone(64))
+			val outcome = transfer(STONE.select(64), chests("A"), chests("A"))
+			expectFailure(outcome)
+			val after = expectClean()
+			expectEquals(64, after.chest("A", STONE), "stone in A")
+		},
+
+		scenario("trips respect a disallowed hotbar") {
+			chest("A", stone(64))
+			chest("B")
+			var stoneSeenInHotbar = false
+			val outcome = transfer(
+				STONE.select(64), chests("A"), chests("B"),
+				automated = automatedWithout(ContainerType.Hotbar),
+				onTick = { client ->
+					val hotbar = client.player?.inventory?.mainStacks?.take(9).orEmpty()
+					if (hotbar.any { it.item == STONE }) stoneSeenInHotbar = true
+				}
+			)
+			expectSuccess(outcome)
+			val after = expectClean()
+			expectEquals(64, after.chest("B", STONE), "stone in B")
+			expect(!stoneSeenInHotbar) { "stone was staged in the hotbar although the hotbar is not an allowed container" }
+		},
+
+		scenario("an inventory packet budget of one still completes whole stack moves") {
+			chest("A", stone(64), stone(64), stone(64))
+			val outcome = transfer(STONE.select(192), chests("A"), player, automated = automatedWithInventoryLimit(1))
+			expectSuccess(outcome)
+			val after = expectClean()
+			expectEquals(192, after.player(STONE), "stone with the player")
+		},
+
+		// ---- complex / messy scenarios -------------------------------------------------------------------
+
+		scenario("wildcard ANYTHING pulls every item from a mixed chest") {
+			// A chest with a hodgepodge of blocks: stone, dirt, cobble, sand, planks. Player inventory has
+			// random junk too. ANYTHING with count 0 should empty the entire chest into the player.
+			chest("A", stone(64), dirt(32), cobble(48), sand(16), planks(10))
+			hotbar(0, glass(64))
+			hotbar(3, bricks(30))
+			inventory(5, gravel(20))
+			val outcome = transfer(StackSelection.ANYTHING, chests("A"), player)
+			expectSuccess(outcome)
+			val after = expectClean()
+			expectEquals(0, after.chest("A", STONE) + after.chest("A", DIRT) + after.chest("A", COBBLE) +
+				after.chest("A", SAND) + after.chest("A", OAK_PLANKS), "nothing should remain in A")
+			expectEquals(64, after.player(STONE), "stone with the player")
+			expectEquals(32, after.player(DIRT), "dirt with the player")
+			expectEquals(48, after.player(COBBLE), "cobblestone with the player")
+			expectEquals(16, after.player(SAND), "sand with the player")
+			expectEquals(10, after.player(OAK_PLANKS), "planks with the player")
+			// pre-existing junk must still be there
+			expectEquals(64, after.player(GLASS), "glass with the player")
+			expectEquals(30, after.player(BRICKS), "bricks with the player")
+			expectEquals(20, after.player(GRAVEL), "gravel with the player")
+		},
+
+		scenario("exclusion-based selection moves everything except one item type") {
+			// Move everything from A except cobblestone. Player already holds some gravel.
+			chest("A", stone(64), cobble(64), dirt(64), sand(32))
+			hotbar(0, gravel(15))
+			val selection = stackSelection(0) { notItem(COBBLE) }
+			val outcome = transfer(selection, chests("A"), player)
+			expectSuccess(outcome)
+			val after = expectClean()
+			expectEquals(64, after.chest("A", COBBLE), "cobblestone must stay in A")
+			expectEquals(0, after.chest("A", STONE), "stone must leave A")
+			expectEquals(0, after.chest("A", DIRT), "dirt must leave A")
+			expectEquals(0, after.chest("A", SAND), "sand must leave A")
+			expectEquals(64, after.player(STONE), "stone with the player")
+			expectEquals(64, after.player(DIRT), "dirt with the player")
+			expectEquals(32, after.player(SAND), "sand with the player")
+			expectEquals(15, after.player(GRAVEL), "pre-existing gravel untouched")
+		},
+
+		scenario("multi-item partial move with a cluttered player inventory") {
+			// Only move 20 total of stone+dirt from A, but the player's inventory is nearly full.
+			// Only hotbar slot 8 and inventory slots 25-26 are free.
+			chest("A", stone(64), dirt(64), cobble(64))
+			// Fill most of the player: 8 hotbar slots + 25 inventory slots = 33 slots used
+			repeat(8) { hotbar(it, glass(64)) }
+			repeat(25) { inventory(it, bricks(64)) }
+			val selection = stackSelection(20) { ofAnyItems(listOf(STONE, DIRT)) }
+			val outcome = transfer(selection, chests("A"), player)
+			expectSuccess(outcome)
+			val after = expectClean()
+			expectEquals(20, after.player(STONE) + after.player(DIRT), "exactly 20 stone+dirt with the player")
+			expectEquals(0, after.player(COBBLE), "cobblestone must not leak out")
+			// pre-existing items unchanged
+			expectEquals(8 * 64, after.player(GLASS), "glass with the player")
+			expectEquals(25 * 64, after.player(BRICKS), "bricks with the player")
+		},
+
+		scenario("chest-to-chest transfer with fully cluttered player inventory needs trips") {
+			// Move 64 stone from A to B, but the player has only 2 free slots.
+			// Task must still succeed using trips, even with very limited staging space.
+			chest("A", stone(64), stone(64))
+			chest("B", dirt(10))
+			// Fill 34 of 36 player slots
+			repeat(9) { hotbar(it, glass(64)) }
+			repeat(25) { inventory(it, bricks(64)) }
+			// Free up exactly 2 slots
+			server.runCommand("/item replace entity $PLAYER hotbar.7 with minecraft:air")
+			server.runCommand("/item replace entity $PLAYER hotbar.8 with minecraft:air")
+			val outcome = transfer(STONE.select(128), chests("A"), chests("B"))
+			expectSuccess(outcome)
+			val after = expectClean()
+			expectEquals(128, after.chest("B", STONE), "stone in B")
+			expectEquals(0, after.chest("A", STONE), "stone in A")
+			expectEquals(0, after.player(STONE), "stone stranded with the player")
+		},
+
+		scenario("shulker box scope finds the right shulker among junk-filled chests") {
+			// Two chests: A has random junk + a shulker containing diamonds among junk.
+			// B has only junk. The task should find and pull the shulker from A.
+			val junkShulker = Spec("shulker_box", 1, "[container=[{slot:0,item:{id:\"minecraft:gravel\",count:64}}]]")
+			val targetShulker = Spec("shulker_box", 1,
+				"[container=[{slot:0,item:{id:\"minecraft:diamond\",count:32}},{slot:1,item:{id:\"minecraft:sand\",count:64}},{slot:2,item:{id:\"minecraft:dirt\",count:64}}]]")
+			chest("A", stone(64), targetShulker, dirt(64), junkShulker, cobble(64))
+			// Player has random stuff too
+			hotbar(0, glass(10))
+			inventory(0, sand(32))
+			val outcome = transfer(
+				stackSelection(1) {
+					isItem(Items.DIAMOND)
+					alsoInShulkerBoxes()
+				},
+				chests("A"),
+				player
+			)
+			expectSuccess(outcome)
+			val after = expectClean()
+			// The shulker box with the diamond must be with the player now
+			expectEquals(1, after.player(Items.SHULKER_BOX), "shulker with the player")
+			// The other shulker (with gravel) must still be in A
+			expectEquals(64, after.chest("A", STONE), "stone stays in A")
+			expectEquals(64, after.chest("A", COBBLE), "cobble stays in A")
+		},
+
+		scenario("multi-source broad move collects from several chests into one destination") {
+			// Three source chests with mixed items. Move everything into a double chest.
+			chest("A", stone(64), dirt(64), sand(32))
+			chest("B", cobble(64), planks(64), glass(20))
+			chest("C", gravel(48), bricks(64))
+			doubleChest()
+			val outcome = transfer(StackSelection.ANYTHING, chests("A", "B", "C"), chests("E"))
+			expectSuccess(outcome)
+			val after = expectClean()
+			// All three sources should be empty
+			expectEquals(0, after.chest("A", STONE) + after.chest("A", DIRT) + after.chest("A", SAND), "A should be empty")
+			expectEquals(0, after.chest("B", COBBLE) + after.chest("B", OAK_PLANKS) + after.chest("B", GLASS), "B should be empty")
+			expectEquals(0, after.chest("C", GRAVEL) + after.chest("C", BRICKS), "C should be empty")
+			// Everything should be in the double chest
+			expectEquals(64, after.chest("E", STONE), "stone in E")
+			expectEquals(64, after.chest("E", DIRT), "dirt in E")
+			expectEquals(32, after.chest("E", SAND), "sand in E")
+			expectEquals(64, after.chest("E", COBBLE), "cobble in E")
+			expectEquals(64, after.chest("E", OAK_PLANKS), "planks in E")
+			expectEquals(20, after.chest("E", GLASS), "glass in E")
+			expectEquals(48, after.chest("E", GRAVEL), "gravel in E")
+			expectEquals(64, after.chest("E", BRICKS), "bricks in E")
+			expectEquals(0, after.player(STONE) + after.player(DIRT) + after.player(COBBLE), "nothing stranded with the player")
+		},
+
+		scenario("broad exclusion move between chests does not strand player items") {
+			// Move everything except sand from A to B. Player already has some sand (the excluded
+			// item) — those must survive. Also verify the transfer itself is correct.
+			chest("A", stone(64), dirt(64), sand(64), cobble(32), planks(16))
+			chest("B")
+			hotbar(0, sand(10))
+			inventory(0, sand(5))
+			val outcome = transfer(stackSelection(0) { notItem(SAND) }, chests("A"), chests("B"))
+			expectSuccess(outcome)
+			val after = expectClean()
+			expectEquals(64, after.chest("A", SAND), "sand must stay in A")
+			expectEquals(0, after.chest("A", STONE), "stone must leave A")
+			expectEquals(64, after.chest("B", STONE), "stone in B")
+			expectEquals(64, after.chest("B", DIRT), "dirt in B")
+			expectEquals(32, after.chest("B", COBBLE), "cobble in B")
+			expectEquals(16, after.chest("B", OAK_PLANKS), "planks in B")
+			expectEquals(15, after.player(SAND), "player's own sand untouched")
+		},
+
+		scenario("large wildcard move across trips with mixed item types") {
+			// Large broad transfer (everything) from two full chests of different items to a double chest.
+			// This exercises multi-trip wildcard moves where the staging area cycles through many item types.
+			fullChest("A", stone(64))
+			chest("C", *Array(27) { if (it % 2 == 0) dirt(64) else planks(64) })
+			doubleChest()
+			val outcome = transfer(StackSelection.ANYTHING, chests("A", "C"), chests("E"))
+			expectSuccess(outcome)
+			val after = expectClean()
+			expectEquals(27 * 64, after.chest("E", STONE), "stone in the double chest")
+			expectEquals(14 * 64, after.chest("E", DIRT), "dirt in the double chest")
+			expectEquals(13 * 64, after.chest("E", OAK_PLANKS), "planks in the double chest")
+			expectEquals(0, after.chest("A", STONE), "stone left in A")
+			expectEquals(0, after.chest("C", DIRT) + after.chest("C", OAK_PLANKS), "items left in C")
+			expectEquals(0, after.player(STONE) + after.player(DIRT) + after.player(OAK_PLANKS),
+				"nothing stranded with the player")
+		},
+
+		// Last on purpose: a task that never settles leaves "open container" packets in flight.
+		scenario("a destination that reports space but accepts nothing settles instead of looping") {
+			chest("A", stone(64))
+			chest("B")
+			val outcome = transfer(STONE.select(64), chests("A"), chests("B"), StackSelection.NOTHING)
+			expect(outcome.state != Task.State.Running && outcome.state != Task.State.Paused) {
+				"task was still ${outcome.state} after ${outcome.ticks} ticks"
+			}
+			expectClean()
+		}
+	)
+}
diff --git a/src/gametest/resources/fabric.mod.json b/src/gametest/resources/fabric.mod.json
index c9ff71449..06778c746 100644
--- a/src/gametest/resources/fabric.mod.json
+++ b/src/gametest/resources/fabric.mod.json
@@ -9,6 +9,10 @@
       {
         "adapter": "kotlin",
         "value": "com.lambda.LambdaTest"
+      },
+      {
+        "adapter": "kotlin",
+        "value": "com.lambda.ContainerTransferGameTest"
       }
     ]
   }
diff --git a/src/main/kotlin/com/lambda/interaction/container/Container.kt b/src/main/kotlin/com/lambda/interaction/container/Container.kt
index 49f278fc9..b58336cf3 100644
--- a/src/main/kotlin/com/lambda/interaction/container/Container.kt
+++ b/src/main/kotlin/com/lambda/interaction/container/Container.kt
@@ -225,7 +225,7 @@ abstract class Container(
             .compare(this, other)
 
     open fun haveMatchingInventories(other: Container) =
-        this === other ||
+        this == other ||
                 (this is PlacedContainer &&
                         other is PlacedContainer &&
                         this::class == other::class &&
diff --git a/src/main/kotlin/com/lambda/interaction/container/selection/ContainerSelection.kt b/src/main/kotlin/com/lambda/interaction/container/selection/ContainerSelection.kt
index eae927955..b893cc07d 100644
--- a/src/main/kotlin/com/lambda/interaction/container/selection/ContainerSelection.kt
+++ b/src/main/kotlin/com/lambda/interaction/container/selection/ContainerSelection.kt
@@ -62,7 +62,7 @@ class ContainerSelection @ContainerDslMarker internal constructor(
     fun matches(container: Container): Boolean = selector(container)
 
     @ContainerDslMarker
-    fun filter(containers: Iterable) =
+    fun filter(containers: Iterable, sorted: Boolean = true) =
         run {
             containersWhitelist
                 .takeIf { it.isNotEmpty() }
@@ -71,7 +71,10 @@ class ContainerSelection @ContainerDslMarker internal constructor(
                 ?: containers.asSequence()
         }.filter { it !in containersBlacklist }
             .filter(selector)
-            .sortedWith(comparator)
+            .let {
+                if (sorted) it.sortedWith(comparator)
+                else it
+            }
             .toList()
 
     companion object {
@@ -99,6 +102,7 @@ class ContainerSelectionBuilder @ContainerDslMarker private constructor(
     ) : this(scope) {
         this.selector = selection.selector
         this.containersWhitelist.addAll(selection.containersWhitelist)
+        this.loadedContainers.addAll(selection.loadedContainers)
         this.containersBlacklist.addAll(selection.containersBlacklist)
         this.accessScope = selection.accessScope
         this.comparator = selection.comparator
@@ -109,7 +113,7 @@ class ContainerSelectionBuilder @ContainerDslMarker private constructor(
     private val loadedContainers = mutableListOf()
     private val containersBlacklist = mutableListOf()
     private var accessScope: AccessScope = AccessScope.Both
-    private var comparator: Comparator = compareBy { it.type }
+    private var comparator: Comparator? = null
 
     fun withContainers(vararg containers: Container) {
         loadedContainers.addAll(containers)
@@ -204,11 +208,15 @@ class ContainerSelectionBuilder @ContainerDslMarker private constructor(
     }
 
     fun sortedWith(comparator: Comparator) {
-        this.comparator = comparator
+        this.comparator = this.comparator
+            ?.then(comparator)
+            ?: comparator
     }
 
     fun sortedWith(comparatorSupplier: () -> Comparator) {
-        this.comparator = comparatorSupplier()
+        this.comparator = this.comparator
+            ?.then(comparatorSupplier())
+            ?: comparatorSupplier()
     }
 
     private fun appendSelector(selector: (Container) -> Boolean) {
@@ -225,6 +233,9 @@ class ContainerSelectionBuilder @ContainerDslMarker private constructor(
             containersBlacklist,
             accessScope,
             comparator
+                .takeUnless { it == null }
+                ?.thenBy { it.type }
+                ?: compareBy { it.type }
         )
 
     companion object {
diff --git a/src/main/kotlin/com/lambda/interaction/container/selection/StackSelection.kt b/src/main/kotlin/com/lambda/interaction/container/selection/StackSelection.kt
index 8a450d485..c5f6b4ebe 100644
--- a/src/main/kotlin/com/lambda/interaction/container/selection/StackSelection.kt
+++ b/src/main/kotlin/com/lambda/interaction/container/selection/StackSelection.kt
@@ -134,13 +134,14 @@ class StackSelection @ContainerDslMarker internal constructor(
 	infix fun isIn(container: Container) =
 		container.count(this) >= count
 
+	// Container.count() reports -1 for "no matching slot", which must not eat into the other containers' totals.
 	@ContainerDslMarker
 	fun isIn(vararg containers: Container) =
-		containers.sumOf { it.count(this) } >= count
+		containers.sumOf { it.count(this).coerceAtLeast(0) } >= count
 
 	@ContainerDslMarker
 	infix fun isIn(containers: Iterable) =
-		containers.sumOf { it.count(this) } >= count
+		containers.sumOf { it.count(this).coerceAtLeast(0) } >= count
 
 	@ContainerDslMarker
 	infix fun spaceIn(container: Container) =
diff --git a/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt
index a65c75498..2b365476e 100644
--- a/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt
+++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt
@@ -15,6 +15,8 @@
  * along with this program.  If not, see .
  */
 
+@file:Suppress("unused")
+
 package com.lambda.interaction.handler.handlers
 
 import com.lambda.Lambda
@@ -301,12 +303,8 @@ fun findSlots(
 	stackSelection: StackSelection = StackSelection.ANYTHING,
 	containerSelection: ContainerSelection = ContainerSelection.ACCESSED,
 	sorted: Boolean = true
-) = searchContainers(containerSelection)
-	.filter { containerSelection.matches(it) }
-	.let {
-		if (sorted) it.sorted()
-		else it
-	}
+) = containerSelection
+	.filter(searchContainers(containerSelection).asIterable(), sorted)
 	.mapNotNull { container ->
 		val slots = stackSelection.filter(container.slots)
 		if (slots.count >= stackSelection.count) slots
@@ -329,12 +327,8 @@ fun findStacks(
 	stackSelection: StackSelection = StackSelection.ANYTHING,
 	containerSelection: ContainerSelection = ContainerSelection.ACCESSED,
 	sorted: Boolean = true
-) = searchContainers(containerSelection)
-	.filter { containerSelection.matches(it) }
-	.let {
-		if (sorted) it.sorted()
-		else it
-	}
+) = containerSelection
+	.filter(searchContainers(containerSelection).asIterable(), sorted)
 	.mapNotNull { container ->
 		val stacks = stackSelection.filter(container.stacks)
 		if (stacks.count >= stackSelection.count) stacks
@@ -355,12 +349,8 @@ context(automated: Automated)
 fun findContainers(
 	containerSelection: ContainerSelection = ContainerSelection.ACCESSED,
 	sorted: Boolean = true
-) = searchContainers(containerSelection)
-	.filter { containerSelection.matches(it) }
-	.let {
-		if (sorted) it.sorted()
-		else it
-	}
+) = containerSelection
+	.filter(searchContainers(containerSelection).asIterable(), sorted)
 
 @ContainerDslMarker
 context(_: Automated)
@@ -418,6 +408,7 @@ private fun searchContainers(
 
 	return baseContainers
 		.flatMap { it.allNested() }
+		.distinct()
 		.filter { it.isAccessible }
 }
 
diff --git a/src/main/kotlin/com/lambda/task/Task.kt b/src/main/kotlin/com/lambda/task/Task.kt
index 65679fd1b..460e0e7b3 100644
--- a/src/main/kotlin/com/lambda/task/Task.kt
+++ b/src/main/kotlin/com/lambda/task/Task.kt
@@ -15,6 +15,8 @@
  * along with this program.  If not, see .
  */
 
+@file:Suppress("unused")
+
 package com.lambda.task
 
 import com.lambda.Lambda.LOG
@@ -32,7 +34,6 @@ import kotlin.time.Duration.Companion.milliseconds
 import kotlin.time.DurationUnit
 import kotlin.time.toDuration
 
-@Suppress("unused")
 abstract class Task : Nameable, Muteable {
     var state = State.Init
     var age = 0
diff --git a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt
index 15f049ed6..6e1922d36 100644
--- a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt
+++ b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt
@@ -33,6 +33,7 @@ import com.lambda.interaction.container.selection.ContainerSelection
 import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.mutate
 import com.lambda.interaction.container.selection.StackSelection
 import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.mutate
+import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection
 import com.lambda.interaction.container.selection.selectContainers
 import com.lambda.interaction.handler.handlers.PacketLimitHandler.availablePackets
 import com.lambda.interaction.handler.handlers.PacketType
@@ -48,6 +49,7 @@ import com.lambda.threading.runSafeAutomated
 import com.lambda.util.item.ItemStackUtils.equal
 import com.lambda.util.item.ItemStackUtils.hasSpace
 import com.lambda.util.item.StackMovePlanner
+import net.minecraft.component.DataComponentTypes
 import net.minecraft.item.ItemStack
 import net.minecraft.screen.slot.Slot
 
@@ -83,13 +85,16 @@ class TransferResult internal constructor(
  * left and right clicks (see [StackMovePlanner]); a whole stack is only moved past the requested count when
  * the sole way into the destination is swapping out a foreign stack.
  *
- * Destination slots are chosen in this order: a partial stack of the same item, an empty slot, then (only
+ * Destination slots are chosen in this order: a partial stack of the very same item, an empty slot, then (only
  * when nothing else is possible) a foreign stack matching [toStack] that gets swapped into the source slot.
+ * [toStack] only ever selects the foreign stacks that may be swapped out; empty slots are always allowed.
  *
  * When both containers are external (chests, shulker boxes, ender chest) items travel through the player's
  * hotbar and inventory: a trip pulls as much as fits (or as much as still needs moving) into free player
  * slots, closes the source, opens the destination and pushes exactly the staged items back out. Trips repeat
- * until the request is satisfied, so sources larger than the player inventory work too.
+ * until the request is satisfied, so sources larger than the player inventory work too. Items the player
+ * already carried are never pushed, and anything staged that no destination can take is returned to the source
+ * before the task settles.
  */
 class ContainerTransferTask @Ta5kBuilder internal constructor(
 	private val fromStack: StackSelection,
@@ -111,6 +116,12 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 	private val isComplete
 		get() = !unlimited && remaining <= 0
 
+	/** Items pulled out of an external source that are still waiting in the player inventory to be pushed. */
+	private var staged = 0
+
+	/** The source the most recent step used; staged leftovers go back there. */
+	private var lastSource: Container? = null
+
 	private val progressText
 		get() = "$transferred moved"
 
@@ -120,6 +131,23 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 	private val fromQueue = mutableListOf()
 	private val toQueue = mutableListOf()
 
+	/** The player containers trips may stage items in, honouring the allowed container config. */
+	private val stagingContainers: List
+		get() = listOf(InventoryContainer, HotbarContainer)
+			.filter { it.isAccessible }
+
+	/** How much a trip may still pull: what is left to move minus what is already waiting in the player inventory. */
+	private val pullLimit
+		get() =
+			if (unlimited) Int.MAX_VALUE
+			else (remaining - staged).coerceAtLeast(0)
+
+	/** How much a push may move: only staged items, and never more than the request still needs. */
+	private val pushLimit
+		get() =
+			if (unlimited) staged
+			else minOf(remaining, staged)
+
 	override fun SafeContext.onStart() {
 		fromQueue +=
 			findContainers(
@@ -128,13 +156,13 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 				}
 			)
 		if (fromQueue.isEmpty() || (!unlimited && !(fromStack isIn fromQueue))) {
-			failure("Could not find source containers for $fromStack")
+			fail("Could not find source containers for $fromStack")
 			return
 		}
 
 		toQueue += findContainers(toSelection).filter { canReceive(it) }
 		if (toQueue.isEmpty()) {
-			failure("Could not find destination containers with space for $fromStack")
+			fail("Could not find destination containers with space for $fromStack")
 			return
 		}
 
@@ -154,15 +182,19 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 
 		val from = fromQueue.firstOrNull()
 			?: run {
-				if (unlimited && transferred > 0) success(buildResult())
-				else failure("Ran out of source containers for $fromStack ($progressText)")
+				when {
+					unlimited && transferred > 0 -> success(buildResult())
+					toQueue.isEmpty() -> fail("No destination container has space left for $fromStack ($progressText)")
+					else -> fail("Ran out of source containers for $fromStack ($progressText)")
+				}
 				return
 			}
 		val to = toQueue.firstOrNull { !it.haveMatchingInventories(from) }
 			?: run {
-				failure("No destination container has space left for $fromStack ($progressText)")
+				fail("No destination container has space left for $fromStack ($progressText)")
 				return
 			}
+		lastSource = from
 
 		val prevTransferred = transferred
 		val step =
@@ -218,18 +250,31 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 	 * Moves directly between [from] and [to]. At most one of them is external, so at most one screen has to be
 	 * opened; screens this task opened are closed again afterwards.
 	 */
-	private fun directChain(from: Container, to: Container): Task<*> =
-		taskOrNull { from.access() }.then { fromContext ->
+	private fun directChain(from: Container, to: Container): Task<*> {
+		// Every queued player-side destination travels along with [to]: a request for "hotbar and inventory" is
+		// one destination as far as vanilla's quick-move is concerned, and the move may use whichever has room.
+		val toContainers =
+			if (to is ExternalContainer) listOf(to)
+			else listOf(to) +
+					toQueue.filter {
+						it !== to && it !is ExternalContainer &&
+								!it.haveMatchingInventories(from)
+					}
+
+		return taskOrNull { from.access() }.then { fromContext ->
 			taskOrNull { to.access() }.then { toContext ->
 				MoveTask(
 					fromContainers = listOf(from),
-					toContainers = listOf(to),
+					toContainers = toContainers,
 					fromSelection = fromStack,
 					toSelection = toStack,
+					limit = remaining
 				).thenOrNull {
-					if (!canReceive(to)) {
-						toQueue.removeAll { it.haveMatchingInventories(to) }
-					}
+					toContainers
+						.filter { !canReceive(it) }
+						.forEach { full ->
+							toQueue.removeAll { it.haveMatchingInventories(full) }
+						}
 					toContext?.close()
 				}.thenOrNull {
 					if (from.count(fromStack) <= 0) {
@@ -239,6 +284,7 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 				}
 			}
 		}
+	}
 
 	/**
 	 * One trip for two external containers: pull from [from] into free player slots, close it, then push the
@@ -249,12 +295,14 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 			.then { fromContext ->
 				MoveTask(
 					fromContainers = listOf(from),
-					toContainers = listOf(InventoryContainer, HotbarContainer),
+					toContainers = stagingContainers,
 					fromSelection = fromStack,
 					toSelection = StackSelection.ANYTHING,
+					limit = pullLimit,
 					allowReplace = false,
 					trackResult = false
-				).thenOrNull {
+				).thenOrNull { pulled ->
+					staged += pulled
 					if (from.count(fromStack) <= 0) {
 						fromQueue.removeAll { it.haveMatchingInventories(from) }
 					}
@@ -264,33 +312,33 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 			.then { pushChain(from) }
 
 	/**
-	 * Pushes items matching [fromStack] from the player inventory into the first available destination.
-	 * If a destination fills up but the player still has items, advances [toQueue] and pushes to the next one.
+	 * Pushes staged items from the player inventory into the first available destination. If a destination
+	 * fills up (or takes nothing) while items are still staged, advances [toQueue] and pushes to the next one.
 	 */
 	private fun pushChain(from: Container): Task<*> {
 		val to = toQueue.firstOrNull { !it.haveMatchingInventories(from) }
 			?: return taskOrNull { null }
+		if (staged <= 0) return taskOrNull { null }
 
 		return taskOrNull { to.access() }
 			.then { toContext ->
 				MoveTask(
-					fromContainers = listOf(InventoryContainer, HotbarContainer),
+					fromContainers = stagingContainers,
 					toContainers = listOf(to),
 					fromSelection = fromStack,
-					toSelection = toStack
-				).thenOrNull {
-					if (!canReceive(to)) {
+					toSelection = toStack,
+					limit = pushLimit
+				).thenOrNull { pushed ->
+					staged = (staged - pushed).coerceAtLeast(0)
+					// A destination that took nothing is done for, whatever canReceive() thinks of it.
+					if (pushed <= 0 || !canReceive(to)) {
 						toQueue.removeAll { it.haveMatchingInventories(to) }
 					}
 					toContext?.close()
 				}
 			}
 			.thenOrNull {
-				if (isComplete) return@thenOrNull null
-
-				val playerHasItems =
-					fromStack.mutate(1) isIn findContainers(ContainerSelection.HOTBAR_AND_INVENTORY).asIterable()
-				if (!playerHasItems) return@thenOrNull null
+				if (isComplete || staged <= 0) return@thenOrNull null
 
 				if (toQueue.any { !it.haveMatchingInventories(from) }) pushChain(from)
 				else null
@@ -299,12 +347,13 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 
 	/**
 	 * Moves items between already-accessed containers. Slot pairs are selected fresh each tick using
-	 * [fromSelection] and the container's own [Container.findMoveSlots] / [Container.findReplaceSlot] APIs —
+	 * [fromSelection] and the container's own [Container.findSlot] / [Container.findReplaceSlot] APIs —
 	 * no slot references are cached across ticks.
 	 *
 	 * For partial-stack precision, moves are planned with [StackMovePlanner] which finds the shortest
-	 * sequence of vanilla `PICKUP` clicks to reach the exact item count. Whole-stack moves use the
-	 * container's own [Container.swap] method which picks the optimal click strategy (e.g. hotbar swap).
+	 * sequence of vanilla `PICKUP` clicks to reach the exact item count. Whole-stack moves use the native
+	 * quick-move when vanilla is guaranteed to put the stack into one of [toContainers], and the planned clicks
+	 * otherwise. Foreign stacks are swapped out with the container's own [Container.swap] method.
 	 *
 	 * @param allowReplace whether a foreign stack matching [toSelection] may be swapped out as a last resort.
 	 * @param trackResult whether destination slots are recorded for the [TransferResult].
@@ -314,14 +363,19 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 		private val toContainers: List,
 		private val fromSelection: StackSelection,
 		toSelection: StackSelection,
+		private val limit: Int = if (unlimited) Int.MAX_VALUE else remaining,
 		private val allowReplace: Boolean = true,
 		private val trackResult: Boolean = true
 	) : Task() {
 		override val name get() = "Moving ${this@MoveTask.fromSelection}"
 		private var moved = 0
-		private var lastFailureReason = InvRequestFailureReason.None
 
-		private val toSelection = toSelection.mutate { withoutSelection(this@MoveTask.fromSelection) }
+		/** Foreign stacks that may be swapped out: the destination filter minus anything the source filter matches. */
+		private val toSelection =
+			toSelection.mutate {
+				notEmpty()
+				withoutSelection(this@MoveTask.fromSelection)
+			}
 
 		init {
 			listen {
@@ -330,11 +384,9 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 		}
 
 		private fun AutomatedSafeContext.tick() {
-			lastFailureReason = InvRequestFailureReason.None
-
 			val currentLimit =
-				if (remaining == Int.MAX_VALUE) Int.MAX_VALUE
-				else (remaining - moved).coerceAtLeast(0)
+				if (limit == Int.MAX_VALUE) Int.MAX_VALUE
+				else (limit - moved).coerceAtLeast(0)
 			if (currentLimit <= 0) {
 				finish()
 				return
@@ -342,7 +394,7 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 
 			val currentSelection =
 				fromSelection.mutate(
-					if (remaining == Int.MAX_VALUE) 0
+					if (limit == Int.MAX_VALUE) 0
 					else minOf(currentLimit, fromSelection.count.coerceAtLeast(1))
 				) { notEmpty() }
 
@@ -355,12 +407,11 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 						if (toContainer.haveMatchingInventories(fromContainer)) return@firstNotNullOfOrNull null
 						if (!fromContainer.canSwapWith(toContainer)) return@firstNotNullOfOrNull null
 
-						// 1. Prefer merging into an existing partial stack of the same item
+						// 1. Prefer topping up a partial stack of the very same item (components included)
 						val mergeSlot =
 							toContainer.findSlot(
-								currentSelection.mutate {
-									notEmpty()
-									predicate { s, _ -> s.hasSpace }
+								stackSelection {
+									predicate { stack, _ -> !stack.isEmpty && stack.hasSpace && stack.sameItemAs(fromSlot.stack) }
 									canInsert(fromSlot.stack)
 								}
 							)
@@ -368,9 +419,16 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 							return@firstNotNullOfOrNull Triple(fromContainer, toContainer, fromSlot to mergeSlot)
 						}
 
-						// 2. Otherwise find an empty slot or (if replacement allowed) a foreign replace slot
+						// 2. Otherwise an empty slot; the destination filter never restricts those
+						val emptySlot = toContainer.findReplaceSlot(fromSlot.stack, stackSelection { isEmpty() })
+						if (emptySlot != null) {
+							return@firstNotNullOfOrNull Triple(fromContainer, toContainer, fromSlot to emptySlot)
+						}
+
+						// 3. As a last resort a foreign stack matching the destination filter that gets swapped out
+						if (!allowReplace) return@firstNotNullOfOrNull null
 						val replaceSlot = toContainer.findReplaceSlot(fromSlot.stack, toSelection)
-						if (replaceSlot != null && (replaceSlot.stack.isEmpty || (allowReplace && fromSlot.canInsert(replaceSlot.stack)))) {
+						if (replaceSlot != null && fromSlot.canInsert(replaceSlot.stack)) {
 							Triple(fromContainer, toContainer, fromSlot to replaceSlot)
 						} else null
 					}
@@ -388,10 +446,29 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 		}
 
 		/**
-		 * Moves items from [fromSlot] to [toSlot]. Uses native quick-move (shift-click) for whole-stack
-		 * moves into matching partial stacks or empty slots. Uses atomic [Container.swap] when replacing
-		 * a foreign slot (when [allowReplace] is true). Uses [StackMovePlanner] for partial-stack moves to
-		 * reach the exact requested count.
+		 * Vanilla's quick-move (shift-click) picks the target slot itself: from a container screen it fills the
+		 * whole player inventory (hotbar first), from the player inventory it fills the open container, and
+		 * inside the player's own screen it moves between hotbar and main inventory, equipping armour on the
+		 * way. It may only be used when every slot it could pick belongs to one of [toContainers].
+		 */
+		private fun AutomatedSafeContext.quickMoveStaysInside(fromContainer: Container, stack: ItemStack): Boolean {
+			if (stack.components.contains(DataComponentTypes.EQUIPPABLE)) return false
+			val playerScreen = player.currentScreenHandler === player.playerScreenHandler
+			fun covers(container: Container) = toContainers.any { it.haveMatchingInventories(container) }
+			fun coversOpenContainer() = toContainers.any { it is ExternalContainer && it.isAccessed }
+			return when {
+				fromContainer is ExternalContainer -> covers(InventoryContainer) && covers(HotbarContainer)
+				fromContainer === InventoryContainer -> if (playerScreen) covers(HotbarContainer) else coversOpenContainer()
+				fromContainer === HotbarContainer -> if (playerScreen) covers(InventoryContainer) else coversOpenContainer()
+				else -> false
+			}
+		}
+
+		/**
+		 * Moves items from [fromSlot] to [toSlot]. Uses atomic [Container.swap] when replacing a foreign slot
+		 * (when [allowReplace] is true). Uses native quick-move (shift-click) for whole-stack moves when vanilla
+		 * is certain to land inside [toContainers]. Uses [StackMovePlanner] for everything else to reach the
+		 * exact requested count in the exact requested slot.
 		 */
 		private fun AutomatedSafeContext.move(
 			fromContainer: Container,
@@ -444,13 +521,13 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 			}
 
 			// 2. Whole-stack fast-path using native quick-move (shift-click)
-			if (wholeStack) {
+			if (wholeStack && quickMoveStaysInside(fromContainer, stack)) {
 				val cursorStack = CursorContainer.stacks.firstOrNull() ?: return
 				if (!cursorStack.isEmpty) return
 
 				val packetLimit = availablePackets(PacketType.Inventory)
 				if (packetLimit < 1) {
-					lastFailureReason = InvRequestFailureReason.PacketLimit
+					checkFinish(InvRequestFailureReason.PacketLimit)
 					return
 				}
 
@@ -459,15 +536,15 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 
 				val initialStack = stack.copy()
 				val countBefore = initialStack.count
-				val toSlotsBefore =
-					if (trackResult) toContainer.slots.map { it.stack.copy() }
+				val slotsBefore =
+					if (trackResult) toContainers.map { container -> container.slots.map { it.stack.copy() } }
 					else null
 
-				val request = inventoryRequest {
-					quickMove(fromSlot.id)
-				}.submit()
+				val request =
+					inventoryRequest {
+						quickMove(fromSlot.id)
+					}.submit()
 
-				lastFailureReason = request.failureReason
 				if (!request.done) {
 					checkFinish(request.failureReason)
 					return
@@ -485,12 +562,14 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 				moved += actualMoved
 				if (trackResult) {
 					transferred += actualMoved
-					toContainer.slots.forEachIndexed { index, slot ->
-						val prev = toSlotsBefore?.getOrNull(index)
-						if (prev == null || prev.isEmpty || slot.stack.count > prev.count || !slot.stack.sameItemAs(prev)) {
-							if (!slot.stack.isEmpty) {
-								resultSlots[slot] = slot.stack.copy()
-								resultContainers.add(toContainer)
+					toContainers.forEachIndexed { containerIndex, container ->
+						container.slots.forEachIndexed { index, slot ->
+							val prev = slotsBefore?.getOrNull(containerIndex)?.getOrNull(index)
+							if (prev == null || prev.isEmpty || slot.stack.count > prev.count || !slot.stack.sameItemAs(prev)) {
+								if (!slot.stack.isEmpty) {
+									resultSlots[slot] = slot.stack.copy()
+									resultContainers.add(container)
+								}
 							}
 						}
 					}
@@ -498,7 +577,7 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 				return
 			}
 
-			// Partial move — plan clicks within the currently available packet budget
+			// 3. Planned clicks — exact count into the exact slot, within the currently available packet budget
 			if (!CursorContainer.isAccessible) {
 				finish()
 				return
@@ -506,9 +585,9 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 			val cursorStack = CursorContainer.stacks.firstOrNull() ?: return
 			if (!cursorStack.isEmpty && !cursorStack.sameItemAs(stack)) return
 
-			val packetLimit = availablePackets(PacketType.Inventory)
-			if (packetLimit < 2) {
-				lastFailureReason = InvRequestFailureReason.PacketLimit
+			val packetBudget = availablePackets(PacketType.Inventory)
+			if (packetBudget < 2) {
+				if (packetBudget >= buildConfig.inventoryLimit) finish()
 				return
 			}
 
@@ -522,14 +601,14 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 			val state = StackMovePlanner.State(cursorStack.count, stack.count, toCount)
 			val idealPlan = StackMovePlanner.plan(state, maxCount, goal)
 			val (stepGoal, plan) =
-				if (idealPlan != null && idealPlan.size <= packetLimit) {
+				if (idealPlan != null && idealPlan.size <= packetBudget) {
 					goal to idealPlan
 				} else {
 					var foundGoal = goal
 					var foundPlan: List? = null
 					for (g in goal downTo toCount + 1) {
 						val p = StackMovePlanner.plan(state, maxCount, g) ?: continue
-						if (p.size <= packetLimit) {
+						if (p.size <= packetBudget) {
 							foundGoal = g
 							foundPlan = p
 							break
@@ -538,8 +617,9 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 					foundGoal to foundPlan
 				}
 
-			if (plan == null || plan.isEmpty()) {
-				lastFailureReason = InvRequestFailureReason.PacketLimit
+			if (plan.isNullOrEmpty()) {
+				// A completely unused budget that still cannot fit the shortest plan never will: give this pair up.
+				if (packetBudget >= buildConfig.inventoryLimit) finish()
 				return
 			}
 
@@ -550,16 +630,16 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 			val event = ContainerEvent.Transfer(fromSlot, toSlot, fromContainer, toContainer)
 			if (event.post().isCanceled()) return
 
-			val request = inventoryRequest {
-				plan.forEach { click ->
-					val slotId =
-						if (click.target == StackMovePlanner.Target.Source) fromSlot.id
-						else toSlot.id
-					pickup(slotId, click.button)
-				}
-			}.submit()
+			val request =
+				inventoryRequest {
+					plan.forEach { click ->
+						val slotId =
+							if (click.target == StackMovePlanner.Target.Source) fromSlot.id
+							else toSlot.id
+						pickup(slotId, click.button)
+					}
+				}.submit()
 
-			lastFailureReason = request.failureReason
 			if (!request.done) return
 
 			moved += actualMove
@@ -571,7 +651,8 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 		}
 
 		private fun checkFinish(failureReason: InvRequestFailureReason = InvRequestFailureReason.None) {
-			if (failureReason == InvRequestFailureReason.None) finish()
+			if (failureReason != InvRequestFailureReason.None) return
+			finish()
 		}
 
 		private fun finish() {
@@ -580,4 +661,14 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 
 		fun ItemStack.sameItemAs(other: ItemStack) = ItemStack.areItemsAndComponentsEqual(this, other)
 	}
+
+	private fun fail(message: String) {
+		failure(FailedContainerTransferException(message, staged))
+	}
 }
+
+@Suppress("unused")
+class FailedContainerTransferException(
+	message: String,
+	val staged: Int
+) : IllegalStateException(message)

From 565ab83d195cd24e1f535c0d964a3b8f05d469bd Mon Sep 17 00:00:00 2001
From: beanbag44 <107891830+beanbag44@users.noreply.github.com>
Date: Mon, 5 Oct 2026 00:30:04 +0100
Subject: [PATCH 24/43] refactor

---
 .../task/tasks/ContainerTransferTask.kt       | 349 ++++++++----------
 .../com/lambda/util/item/StackMovePlanner.kt  | 194 +++++-----
 src/test/kotlin/StackMovePlannerTest.kt       | 102 ++++-
 3 files changed, 348 insertions(+), 297 deletions(-)

diff --git a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt
index 6e1922d36..252a6278c 100644
--- a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt
+++ b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt
@@ -345,15 +345,29 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 			}
 	}
 
+	/** A source slot and the destination slot chosen for it, with the containers they belong to. */
+	private data class SlotPair(
+		val fromContainer: Container,
+		val fromSlot: Slot,
+		val toContainer: Container,
+		val toSlot: Slot
+	) {
+		operator fun get(target: StackMovePlanner.Target) =
+			when (target) {
+				StackMovePlanner.Target.Source -> fromSlot
+				StackMovePlanner.Target.Destination -> toSlot
+			}
+	}
+
 	/**
 	 * Moves items between already-accessed containers. Slot pairs are selected fresh each tick using
 	 * [fromSelection] and the container's own [Container.findSlot] / [Container.findReplaceSlot] APIs —
 	 * no slot references are cached across ticks.
 	 *
-	 * For partial-stack precision, moves are planned with [StackMovePlanner] which finds the shortest
-	 * sequence of vanilla `PICKUP` clicks to reach the exact item count. Whole-stack moves use the native
-	 * quick-move when vanilla is guaranteed to put the stack into one of [toContainers], and the planned clicks
-	 * otherwise. Foreign stacks are swapped out with the container's own [Container.swap] method.
+	 * A foreign stack in the chosen destination slot is swapped out with the container's own [Container.swap].
+	 * A whole stack is shift-clicked with vanilla's quick-move when that is certain to land inside [toContainers]
+	 * and the cursor is empty. Everything else is clicked through along a [StackMovePlanner] plan, which reaches
+	 * the exact item count in the exact slot and carries on from whatever the cursor still holds.
 	 *
 	 * @param allowReplace whether a foreign stack matching [toSelection] may be swapped out as a last resort.
 	 * @param trackResult whether destination slots are recorded for the [TransferResult].
@@ -399,50 +413,49 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 				) { notEmpty() }
 
 			// Find the best slot pair fresh — never cached across ticks
-			val moveSlots =
+			val pair =
 				fromContainers.firstNotNullOfOrNull { fromContainer ->
 					val fromSlot = fromContainer.findSlot(currentSelection) ?: return@firstNotNullOfOrNull null
 
 					toContainers.firstNotNullOfOrNull { toContainer ->
 						if (toContainer.haveMatchingInventories(fromContainer)) return@firstNotNullOfOrNull null
 						if (!fromContainer.canSwapWith(toContainer)) return@firstNotNullOfOrNull null
-
-						// 1. Prefer topping up a partial stack of the very same item (components included)
-						val mergeSlot =
-							toContainer.findSlot(
-								stackSelection {
-									predicate { stack, _ -> !stack.isEmpty && stack.hasSpace && stack.sameItemAs(fromSlot.stack) }
-									canInsert(fromSlot.stack)
-								}
-							)
-						if (mergeSlot != null) {
-							return@firstNotNullOfOrNull Triple(fromContainer, toContainer, fromSlot to mergeSlot)
+						findDestinationSlot(fromSlot, toContainer)?.let { toSlot ->
+							SlotPair(fromContainer, fromSlot, toContainer, toSlot)
 						}
-
-						// 2. Otherwise an empty slot; the destination filter never restricts those
-						val emptySlot = toContainer.findReplaceSlot(fromSlot.stack, stackSelection { isEmpty() })
-						if (emptySlot != null) {
-							return@firstNotNullOfOrNull Triple(fromContainer, toContainer, fromSlot to emptySlot)
-						}
-
-						// 3. As a last resort a foreign stack matching the destination filter that gets swapped out
-						if (!allowReplace) return@firstNotNullOfOrNull null
-						val replaceSlot = toContainer.findReplaceSlot(fromSlot.stack, toSelection)
-						if (replaceSlot != null && fromSlot.canInsert(replaceSlot.stack)) {
-							Triple(fromContainer, toContainer, fromSlot to replaceSlot)
-						} else null
 					}
 				}
 
-			if (moveSlots == null) {
+			if (pair == null) {
 				finish()
 				return
 			}
 
-			val (fromContainer, toContainer, pair) = moveSlots
-			val (fromSlot, toSlot) = pair
+			move(pair, atMost = currentLimit)
+		}
+
+		/**
+		 * The slot of [toContainer] that should receive the stack in [fromSlot]: a partial stack of the very same
+		 * item (components included) is topped up first, then an empty slot is used; the destination filter never
+		 * restricts those. Only as a last resort, and only when [allowReplace] permits it, a foreign stack matching
+		 * [toSelection] is chosen to be swapped out.
+		 */
+		private fun AutomatedSafeContext.findDestinationSlot(fromSlot: Slot, toContainer: Container): Slot? {
+			val stack = fromSlot.stack
+			val partialStack =
+				stackSelection {
+					predicate { candidate, _ -> !candidate.isEmpty && candidate.hasSpace && candidate.sameItemAs(stack) }
+					canInsert(stack)
+				}
+			return toContainer.findSlot(partialStack)
+				?: toContainer.findReplaceSlot(stack, stackSelection { isEmpty() })
+				?: findSwappableSlot(fromSlot, toContainer)
+		}
 
-			move(fromContainer, fromSlot, toContainer, toSlot, currentLimit)
+		/** A foreign stack of [toContainer] matching [toSelection] that [fromSlot] accepts in return. */
+		private fun AutomatedSafeContext.findSwappableSlot(fromSlot: Slot, toContainer: Container): Slot? {
+			if (!allowReplace) return null
+			return toContainer.findReplaceSlot(fromSlot.stack, toSelection)?.takeIf { fromSlot.canInsert(it.stack) }
 		}
 
 		/**
@@ -465,201 +478,147 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 		}
 
 		/**
-		 * Moves items from [fromSlot] to [toSlot]. Uses atomic [Container.swap] when replacing a foreign slot
-		 * (when [allowReplace] is true). Uses native quick-move (shift-click) for whole-stack moves when vanilla
-		 * is certain to land inside [toContainers]. Uses [StackMovePlanner] for everything else to reach the
-		 * exact requested count in the exact requested slot.
+		 * Moves at most [atMost] items along [pair]. A foreign stack in the destination slot can only be swapped
+		 * out whole. A whole stack takes vanilla's quick-move when that is certain to land inside [toContainers]
+		 * and nothing is on the cursor. Everything else is clicked through to the exact count.
 		 */
-		private fun AutomatedSafeContext.move(
-			fromContainer: Container,
-			fromSlot: Slot,
-			toContainer: Container,
-			toSlot: Slot,
-			currentLimit: Int
-		) {
-			val stack = fromSlot.stack
-			if (stack.isEmpty) {
-				finish()
-				return
+		private fun AutomatedSafeContext.move(pair: SlotPair, atMost: Int) {
+			val stack = pair.fromSlot.stack
+			val count = minOf(stack.count, atMost)
+			val cursor = player.currentScreenHandler.cursorStack
+			when {
+				pair.toSlot.stack.isForeignTo(stack) -> swapOut(pair)
+				count == stack.count && cursor.isEmpty && quickMoveStaysInside(pair.fromContainer, stack) -> quickMoveWhole(pair)
+				else -> clickThrough(pair, count)
 			}
+		}
 
-			val moveCount = minOf(stack.count, currentLimit)
-			if (moveCount <= 0) {
-				finish()
-				return
-			}
-
-			val wholeStack = moveCount == stack.count
-			val toSameItem = !toSlot.stack.isEmpty && toSlot.stack.sameItemAs(stack)
-			val toForeign = !toSlot.stack.isEmpty && !toSameItem
-
-			// 1. Foreign slot replacement (swapping the whole stack is the sole way into a foreign slot)
-			if (toForeign && allowReplace) {
-				val actualMoved = stack.count
-				val request = fromContainer
-					.swapRequest(fromSlot, toSlot, toContainer)
-					?.submit()
-				if (request == null || !request.done) {
-					if (request != null) checkFinish(request.failureReason)
-					else finish()
-					return
-				}
-
-				moved += actualMoved
-				if (trackResult) {
-					transferred += actualMoved
-					resultSlots[toSlot] = toSlot.stack.copy()
-					resultContainers.add(toContainer)
+		/** Swaps the whole source stack with the foreign stack in the destination slot; vanilla knows no other way into an occupied slot. */
+		private fun AutomatedSafeContext.swapOut(pair: SlotPair) {
+			val count = pair.fromSlot.stack.count
+			val request = pair.fromContainer.swapRequest(pair.fromSlot, pair.toSlot, pair.toContainer)?.submit()
+			when {
+				request == null -> finish()
+				!request.done -> finishUnlessTransient(request.failureReason)
+				else -> {
+					recordMoved(count)
+					recordDestination(pair.toSlot, pair.toContainer)
 				}
-				return
 			}
+		}
 
-			// Can't move into a foreign slot when replacement is not allowed
-			if (toForeign) {
-				finish()
+		/**
+		 * Shift-clicks the whole stack and lets vanilla pick the slots, so the destination containers are diffed
+		 * afterwards to learn where the items went.
+		 */
+		private fun AutomatedSafeContext.quickMoveWhole(pair: SlotPair) {
+			if (availablePackets(PacketType.Inventory) < 1) return
+			if (ContainerEvent.Transfer(pair.fromSlot, pair.toSlot, pair.fromContainer, pair.toContainer).post().isCanceled()) return
+
+			val before = pair.fromSlot.stack.copy()
+			val destinationsBefore =
+				if (trackResult) toContainers.map { container -> container.slots.map { it.stack.copy() } }
+				else emptyList()
+			val request = inventoryRequest { quickMove(pair.fromSlot.id) }.submit()
+			if (!request.done) {
+				finishUnlessTransient(request.failureReason)
 				return
 			}
 
-			// 2. Whole-stack fast-path using native quick-move (shift-click)
-			if (wholeStack && quickMoveStaysInside(fromContainer, stack)) {
-				val cursorStack = CursorContainer.stacks.firstOrNull() ?: return
-				if (!cursorStack.isEmpty) return
-
-				val packetLimit = availablePackets(PacketType.Inventory)
-				if (packetLimit < 1) {
-					checkFinish(InvRequestFailureReason.PacketLimit)
-					return
-				}
-
-				val event = ContainerEvent.Transfer(fromSlot, toSlot, fromContainer, toContainer)
-				if (event.post().isCanceled()) return
-
-				val initialStack = stack.copy()
-				val countBefore = initialStack.count
-				val slotsBefore =
-					if (trackResult) toContainers.map { container -> container.slots.map { it.stack.copy() } }
-					else null
-
-				val request =
-					inventoryRequest {
-						quickMove(fromSlot.id)
-					}.submit()
-
-				if (!request.done) {
-					checkFinish(request.failureReason)
-					return
-				}
-
-				val countAfter =
-					if (fromSlot.stack.isEmpty || !fromSlot.stack.sameItemAs(initialStack)) 0
-					else fromSlot.stack.count
-				val actualMoved = countBefore - countAfter
-				if (actualMoved <= 0) {
-					finish()
-					return
-				}
-
-				moved += actualMoved
-				if (trackResult) {
-					transferred += actualMoved
-					toContainers.forEachIndexed { containerIndex, container ->
-						container.slots.forEachIndexed { index, slot ->
-							val prev = slotsBefore?.getOrNull(containerIndex)?.getOrNull(index)
-							if (prev == null || prev.isEmpty || slot.stack.count > prev.count || !slot.stack.sameItemAs(prev)) {
-								if (!slot.stack.isEmpty) {
-									resultSlots[slot] = slot.stack.copy()
-									resultContainers.add(container)
-								}
-							}
-						}
-					}
-				}
+			val left = pair.fromSlot.stack.takeIf { it.sameItemAs(before) }?.count ?: 0
+			val count = before.count - left
+			if (count <= 0) {
+				finish()
 				return
 			}
+			recordMoved(count)
+			if (trackResult) recordReceivingSlots(destinationsBefore)
+		}
 
-			// 3. Planned clicks — exact count into the exact slot, within the currently available packet budget
+		/**
+		 * Clicks exactly [count] items across along a [StackMovePlanner] plan that fits this tick's inventory
+		 * packet budget. When the full count does not fit, as much as the budget allows is moved now and the rest
+		 * follows on later ticks.
+		 */
+		private fun AutomatedSafeContext.clickThrough(pair: SlotPair, count: Int) {
 			if (!CursorContainer.isAccessible) {
 				finish()
 				return
 			}
-			val cursorStack = CursorContainer.stacks.firstOrNull() ?: return
-			if (!cursorStack.isEmpty && !cursorStack.sameItemAs(stack)) return
-
-			val packetBudget = availablePackets(PacketType.Inventory)
-			if (packetBudget < 2) {
-				if (packetBudget >= buildConfig.inventoryLimit) finish()
-				return
-			}
-
-			val toCount =
-				if (toSameItem) toSlot.stack.count
-				else 0
-			val maxCount = toSlot.getMaxItemCount(stack)
-			val goal = minOf(toCount + moveCount, maxCount)
-			if (goal <= toCount) return
-
-			val state = StackMovePlanner.State(cursorStack.count, stack.count, toCount)
-			val idealPlan = StackMovePlanner.plan(state, maxCount, goal)
-			val (stepGoal, plan) =
-				if (idealPlan != null && idealPlan.size <= packetBudget) {
-					goal to idealPlan
-				} else {
-					var foundGoal = goal
-					var foundPlan: List? = null
-					for (g in goal downTo toCount + 1) {
-						val p = StackMovePlanner.plan(state, maxCount, g) ?: continue
-						if (p.size <= packetBudget) {
-							foundGoal = g
-							foundPlan = p
-							break
-						}
-					}
-					foundGoal to foundPlan
-				}
-
-			if (plan.isNullOrEmpty()) {
-				// A completely unused budget that still cannot fit the shortest plan never will: give this pair up.
-				if (packetBudget >= buildConfig.inventoryLimit) finish()
+			val stack = pair.fromSlot.stack
+			val cursor = player.currentScreenHandler.cursorStack
+			// A foreign stack on the cursor is somebody else's business; wait until it has been put away.
+			if (cursor.isForeignTo(stack)) return
+
+			val budget = availablePackets(PacketType.Inventory)
+			val destinationBefore = pair.toSlot.stack.count
+			val plan =
+				StackMovePlanner.planWithin(
+					state = StackMovePlanner.State(cursor.count, stack.count, destinationBefore),
+					maxCount = pair.toSlot.getMaxItemCount(stack),
+					goal = destinationBefore + count,
+					clickBudget = budget
+				)
+			if (plan == null) {
+				// Even an untouched budget cannot fit the shortest plan: this pair can never be served, give it up.
+				if (budget >= buildConfig.inventoryLimit) finish()
 				return
 			}
 
-			val actualMove = stepGoal - toCount
-			if (actualMove <= 0) return
-
-			// Post the transfer event for partial moves (swap() posts its own for whole moves)
-			val event = ContainerEvent.Transfer(fromSlot, toSlot, fromContainer, toContainer)
-			if (event.post().isCanceled()) return
-
+			if (ContainerEvent.Transfer(pair.fromSlot, pair.toSlot, pair.fromContainer, pair.toContainer).post().isCanceled()) return
 			val request =
 				inventoryRequest {
-					plan.forEach { click ->
-						val slotId =
-							if (click.target == StackMovePlanner.Target.Source) fromSlot.id
-							else toSlot.id
-						pickup(slotId, click.button)
-					}
+					plan.clicks.forEach { click -> pickup(pair[click.target].id, click.button.id) }
 				}.submit()
+			if (!request.done) {
+				finishUnlessTransient(request.failureReason)
+				return
+			}
 
-			if (!request.done) return
+			recordMoved(plan.destination - destinationBefore)
+			recordDestination(pair.toSlot, pair.toContainer)
+		}
 
-			moved += actualMove
-			if (trackResult) {
-				transferred += actualMove
-				resultSlots[toSlot] = toSlot.stack.copy()
-				resultContainers.add(toContainer)
+		/** Books every destination slot that holds more than it did [before] vanilla's quick-move. */
+		private fun recordReceivingSlots(before: List>) {
+			toContainers.zip(before).forEach { (container, previous) ->
+				container.slots.forEachIndexed { index, slot ->
+					if (slot.stack.grewFrom(previous.getOrNull(index))) recordDestination(slot, container)
+				}
 			}
 		}
 
-		private fun checkFinish(failureReason: InvRequestFailureReason = InvRequestFailureReason.None) {
-			if (failureReason != InvRequestFailureReason.None) return
-			finish()
+		private fun recordMoved(count: Int) {
+			moved += count
+			if (trackResult) transferred += count
+		}
+
+		private fun recordDestination(slot: Slot, container: Container) {
+			if (!trackResult) return
+			resultSlots[slot] = slot.stack.copy()
+			resultContainers += container
+		}
+
+		/**
+		 * A request the manager could not take this tick is retried next tick when the cause clears up by itself
+		 * (packet limit, another request already running); any other cause will not, so the move ends here.
+		 */
+		private fun finishUnlessTransient(failureReason: InvRequestFailureReason) {
+			if (failureReason == InvRequestFailureReason.None) finish()
 		}
 
 		private fun finish() {
 			success(moved)
 		}
 
-		fun ItemStack.sameItemAs(other: ItemStack) = ItemStack.areItemsAndComponentsEqual(this, other)
+		private fun ItemStack.sameItemAs(other: ItemStack) = ItemStack.areItemsAndComponentsEqual(this, other)
+
+		/** A non-empty stack of another item, or of the same item with other components. */
+		private fun ItemStack.isForeignTo(other: ItemStack) = !isEmpty && !sameItemAs(other)
+
+		/** Whether this stack holds something [previous] did not, or more of it. */
+		private fun ItemStack.grewFrom(previous: ItemStack?) =
+			!isEmpty && (previous == null || previous.isEmpty || !sameItemAs(previous) || count > previous.count)
 	}
 
 	private fun fail(message: String) {
diff --git a/src/main/kotlin/com/lambda/util/item/StackMovePlanner.kt b/src/main/kotlin/com/lambda/util/item/StackMovePlanner.kt
index 3b1756e9c..388339879 100644
--- a/src/main/kotlin/com/lambda/util/item/StackMovePlanner.kt
+++ b/src/main/kotlin/com/lambda/util/item/StackMovePlanner.kt
@@ -17,23 +17,19 @@
 
 package com.lambda.util.item
 
-import java.util.concurrent.ConcurrentHashMap
-
 /**
- * Plans the shortest sequence of vanilla `PICKUP` slot clicks that moves an exact number of items
- * of a single item type between two slots, using the cursor as the only intermediary.
+ * Plans the shortest sequence of vanilla `PICKUP` slot clicks that moves items of one item type from a source
+ * slot into a destination slot, with the cursor as the only intermediary.
  *
- * The model follows vanilla click semantics for a source slot, a destination slot and the cursor,
- * all holding the same item type (or being empty):
+ * The model follows vanilla click semantics for two slots and a cursor that hold the same item type or nothing:
  *  - Left click with an empty cursor picks up the whole slot.
  *  - Right click with an empty cursor picks up half the slot, rounded up.
  *  - Left click with a held stack places as much as fits into the slot.
- *  - Right click with a held stack places exactly one item.
+ *  - Right click with a held stack places a single item.
  *
- * A breadth-first search over `(cursor, source)` (the destination count follows from the invariant
- * `cursor + source + destination = total`) finds the minimal click count. Results are cached per
- * distinct `(cursor, source, destination, maxCount, destinationGoal)` since the state space is tiny
- * and transfers tend to repeat the same shapes.
+ * Every count is bounded by the stack size the destination slot allows (`maxCount`), which keeps the state space
+ * at a few thousand states at most. A breadth-first search over it yields the minimal click count cheaply enough
+ * to plan every move afresh.
  */
 object StackMovePlanner {
 	enum class Target {
@@ -41,104 +37,126 @@ object StackMovePlanner {
 		Destination
 	}
 
-	/** A single slot click: [button] `0` is a left click, `1` a right click. */
-	data class Click(val target: Target, val button: Int)
+	/** A mouse button as vanilla numbers it in `PICKUP` clicks. */
+	enum class Button(val id: Int) {
+		Left(0),
+		Right(1)
+	}
+
+	data class Click(val target: Target, val button: Button)
 
 	/** Item counts held by the cursor and both slots. */
 	data class State(val cursor: Int, val source: Int, val destination: Int) {
 		val total get() = cursor + source + destination
+
+		operator fun get(target: Target) =
+			when (target) {
+				Target.Source -> source
+				Target.Destination -> destination
+			}
+
+		fun withSlot(target: Target, count: Int) =
+			when (target) {
+				Target.Source -> copy(source = count)
+				Target.Destination -> copy(destination = count)
+			}
+
+		/** Whether every count lies within what slots holding at most [maxCount] items can represent. */
+		fun fits(maxCount: Int) =
+			cursor in 0..maxCount && source in 0..maxCount && destination in 0..maxCount
+
+		/** The only state a finished move can end in: an empty cursor and [count] items in the destination. */
+		fun settled(count: Int) = State(cursor = 0, source = total - count, destination = count)
 	}
 
-	private data class Key(val cursor: Int, val source: Int, val destination: Int, val maxCount: Int, val goal: Int)
+	/** The [clicks] that leave [destination] items in the destination slot and nothing on the cursor. */
+	data class Plan(val clicks: List, val destination: Int)
+
+	/** How a state was first reached during the search. */
+	private data class Step(val previous: State, val click: Click)
 
-	private val cache = ConcurrentHashMap>()
+	private val CLICKS = Target.entries.flatMap { target -> Button.entries.map { Click(target, it) } }
 
-	private val actions =
-		listOf(
-			Click(Target.Source, 0),
-			Click(Target.Source, 1),
-			Click(Target.Destination, 0),
-			Click(Target.Destination, 1)
-		)
+	/** Applies one [click] to [state] following vanilla `PICKUP` semantics. */
+	fun apply(state: State, click: Click, maxCount: Int): State =
+		if (state.cursor == 0) state.pickUp(click) else state.place(click, maxCount)
 
 	/**
-	 * Applies one [click] to [state] following vanilla `PICKUP` semantics.
-	 * Returns the same instance when the click would have no effect.
+	 * The shortest click sequence that ends with exactly [goal] items in the destination and an empty cursor,
+	 * or `null` when there is none: the goal exceeds [maxCount] or the items available, the source could not
+	 * hold the surplus, or [state] does not [fit][State.fits] the model.
 	 */
-	fun apply(state: State, click: Click, maxCount: Int): State {
-		val slot = if (click.target == Target.Source) state.source else state.destination
-		val cursor = state.cursor
-		val (newCursor, newSlot) =
-			if (cursor == 0) {
-				if (slot == 0) return state
-				val taken = if (click.button == 0) slot else (slot + 1) / 2
-				taken to slot - taken
-			} else {
-				val room = maxCount - slot
-				if (room <= 0) return state
-				val placed = if (click.button == 0) minOf(cursor, room) else 1
-				cursor - placed to slot + placed
-			}
-		return if (click.target == Target.Source) State(newCursor, newSlot, state.destination)
-		else State(newCursor, state.source, newSlot)
+	fun plan(state: State, maxCount: Int, goal: Int): List? {
+		val end = state.settled(goal)
+		if (!state.fits(maxCount) || !end.fits(maxCount)) return null
+		return explore(state, maxCount, maxClicks = Int.MAX_VALUE).pathTo(end)
 	}
 
 	/**
-	 * Finds the shortest click sequence that ends with the destination holding exactly [destinationGoal]
-	 * items and an empty cursor. Returns `null` when the goal is unreachable (for example when the goal
-	 * exceeds [maxCount] or the items available).
+	 * The plan that brings the destination as close to [goal] as [clickBudget] clicks allow without exceeding
+	 * it: the largest count in `(state.destination, goal]` whose shortest plan fits the budget. `null` when not
+	 * even a single item can be moved within the budget.
 	 */
-	fun plan(state: State, maxCount: Int, destinationGoal: Int): List? {
-		if (maxCount <= 0 || destinationGoal < 0 || destinationGoal > maxCount) return null
-		if (destinationGoal > state.total) return null
-		if (state.cursor == 0 && state.destination == destinationGoal) return emptyList()
-		if (state.total - destinationGoal > maxCount) return null
-
-		val key = Key(state.cursor, state.source, state.destination, maxCount, destinationGoal)
-		return cache.getOrPut(key) { search(state, maxCount, destinationGoal) }
+	fun planWithin(state: State, maxCount: Int, goal: Int, clickBudget: Int): Plan? {
+		if (!state.fits(maxCount)) return null
+		val steps = explore(state, maxCount, maxClicks = clickBudget)
+		val highest = minOf(goal, maxCount, state.total)
+		return (highest downTo state.destination + 1).firstNotNullOfOrNull { count ->
+			steps.pathTo(state.settled(count))?.let { Plan(it, count) }
+		}
+	}
+
+	/** An empty cursor takes the whole slot with a left click and the larger half with a right click. */
+	private fun State.pickUp(click: Click): State {
+		val slot = this[click.target]
+		val taken =
+			when (click.button) {
+				Button.Left -> slot
+				Button.Right -> (slot + 1) / 2
+			}
+		return copy(cursor = taken).withSlot(click.target, slot - taken)
+	}
+
+	/** A held stack drops as much as fits with a left click and a single item with a right click. */
+	private fun State.place(click: Click, maxCount: Int): State {
+		val slot = this[click.target]
+		val offered =
+			when (click.button) {
+				Button.Left -> cursor
+				Button.Right -> 1
+			}
+		val placed = minOf(offered, maxCount - slot).coerceAtLeast(0)
+		return copy(cursor = cursor - placed).withSlot(click.target, slot + placed)
 	}
 
 	/**
-	 * Number of clicks needed for [plan], or `null` when unreachable.
+	 * Breadth-first search from [start]: every state reachable within [maxClicks] clicks, mapped to the step
+	 * that first reached it ([start] itself maps to `null`). Following the steps back from a state yields its
+	 * shortest plan.
 	 */
-	fun cost(state: State, maxCount: Int, destinationGoal: Int) = plan(state, maxCount, destinationGoal)?.size
-
-	private fun search(start: State, maxCount: Int, goal: Int): List? {
-		val width = maxCount + 1
-		fun index(state: State) = state.cursor * width + state.source
-
-		// parent[index] holds (parentIndex shl 2) or actionIndex; -1 marks unvisited.
-		val parent = IntArray(width * width) { -1 }
-		val queue = ArrayDeque()
-		val startIndex = index(start)
-		parent[startIndex] = startIndex shl 2
-		queue.addLast(start)
-
-		while (queue.isNotEmpty()) {
-			val state = queue.removeFirst()
-			if (state.cursor == 0 && state.destination == goal) return reconstruct(parent, width, startIndex, index(state))
-
-			actions.forEachIndexed { actionIndex, click ->
-				val next = apply(state, click, maxCount)
-				if (next === state) return@forEachIndexed
-				if (next.cursor > maxCount || next.source > maxCount || next.destination > maxCount) return@forEachIndexed
-				val nextIndex = index(next)
-				if (parent[nextIndex] != -1) return@forEachIndexed
-				parent[nextIndex] = (index(state) shl 2) or actionIndex
-				queue.addLast(next)
-			}
+	private fun explore(start: State, maxCount: Int, maxClicks: Int): Map {
+		val steps = hashMapOf(start to null)
+		var frontier = listOf(start)
+		var depth = 0
+		while (frontier.isNotEmpty() && depth < maxClicks) {
+			frontier =
+				frontier.flatMap { state ->
+					CLICKS.mapNotNull { click ->
+						apply(state, click, maxCount)
+							.takeUnless { it in steps }
+							?.also { steps[it] = Step(state, click) }
+					}
+				}
+			depth++
 		}
-		return null
+		return steps
 	}
 
-	private fun reconstruct(parent: IntArray, width: Int, startIndex: Int, endIndex: Int): List {
-		val clicks = ArrayDeque()
-		var current = endIndex
-		while (current != startIndex) {
-			val entry = parent[current]
-			clicks.addFirst(actions[entry and 3])
-			current = entry ushr 2
-		}
-		return clicks.toList()
+	private fun Map.pathTo(end: State): List? {
+		if (end !in this) return null
+		return generateSequence(this[end]) { this[it.previous] }
+			.map { it.click }
+			.toList()
+			.asReversed()
 	}
 }
diff --git a/src/test/kotlin/StackMovePlannerTest.kt b/src/test/kotlin/StackMovePlannerTest.kt
index 52f7e9970..86bd50cc5 100644
--- a/src/test/kotlin/StackMovePlannerTest.kt
+++ b/src/test/kotlin/StackMovePlannerTest.kt
@@ -16,6 +16,7 @@
  */
 
 import com.lambda.util.item.StackMovePlanner
+import com.lambda.util.item.StackMovePlanner.Button
 import com.lambda.util.item.StackMovePlanner.Click
 import com.lambda.util.item.StackMovePlanner.State
 import com.lambda.util.item.StackMovePlanner.Target
@@ -30,19 +31,34 @@ class StackMovePlannerTest {
 	private fun simulate(start: State, clicks: List, maxCount: Int) =
 		clicks.fold(start) { state, click -> StackMovePlanner.apply(state, click, maxCount) }
 
+	private fun assertSettles(start: State, clicks: List, maxCount: Int, destination: Int) {
+		val end = simulate(start, clicks, maxCount)
+		assertEquals(destination, end.destination, "destination after $clicks from $start")
+		assertEquals(0, end.cursor, "cursor after $clicks from $start")
+		assertEquals(start.total, end.total, "items must be conserved")
+	}
+
 	private fun assertReaches(start: State, maxCount: Int, goal: Int): List {
 		val plan = assertNotNull(StackMovePlanner.plan(start, maxCount, goal), "no plan for $start -> $goal (max $maxCount)")
-		val end = simulate(start, plan, maxCount)
-		assertEquals(goal, end.destination, "destination after $plan from $start")
-		assertEquals(0, end.cursor, "cursor after $plan from $start")
-		assertEquals(start.total, end.total, "items must be conserved")
+		assertSettles(start, plan, maxCount, goal)
 		return plan
 	}
 
+	/** A random state within the model and a goal that is reachable from it. */
+	private fun Random.reachableCase(maxCount: Int): Pair? {
+		val source = nextInt(0, maxCount + 1)
+		val destination = nextInt(0, maxCount + 1)
+		val total = source + destination
+		val low = (total - maxCount).coerceAtLeast(0)
+		val high = minOf(maxCount, total)
+		if (low > high) return null
+		return State(0, source, destination) to nextInt(low, high + 1)
+	}
+
 	@Test
 	fun wholeStackIntoEmptySlotIsTwoClicks() {
 		val plan = assertReaches(State(0, 64, 0), 64, 64)
-		assertEquals(listOf(Click(Target.Source, 0), Click(Target.Destination, 0)), plan)
+		assertEquals(listOf(Click(Target.Source, Button.Left), Click(Target.Destination, Button.Left)), plan)
 	}
 
 	@Test
@@ -54,7 +70,7 @@ class StackMovePlannerTest {
 	@Test
 	fun halfStackUsesRightClickPickup() {
 		val plan = assertReaches(State(0, 64, 0), 64, 32)
-		assertEquals(listOf(Click(Target.Source, 1), Click(Target.Destination, 0)), plan)
+		assertEquals(listOf(Click(Target.Source, Button.Right), Click(Target.Destination, Button.Left)), plan)
 	}
 
 	@Test
@@ -90,6 +106,14 @@ class StackMovePlannerTest {
 		assertNull(StackMovePlanner.plan(State(0, 5, 0), 0, 1))
 	}
 
+	@Test
+	fun rejectsStatesOutsideTheModel() {
+		// Counts above the stack size cannot be represented, so planning declines instead of guessing.
+		assertNull(StackMovePlanner.plan(State(70, 0, 0), 64, 64))
+		assertNull(StackMovePlanner.plan(State(0, 20, 0), 16, 16))
+		assertNull(StackMovePlanner.planWithin(State(70, 0, 0), 64, 64, clickBudget = 100))
+	}
+
 	@Test
 	fun unstackableItemsMoveWhole() {
 		val plan = assertReaches(State(0, 1, 0), 1, 1)
@@ -101,14 +125,64 @@ class StackMovePlannerTest {
 		val random = Random(1337)
 		for (maxCount in listOf(1, 16, 64)) {
 			repeat(300) {
-				val source = random.nextInt(0, maxCount + 1)
-				val destination = random.nextInt(0, maxCount + 1)
-				val total = source + destination
-				val low = (total - maxCount).coerceAtLeast(0)
-				val high = minOf(maxCount, total)
-				if (low > high) return@repeat
-				val goal = random.nextInt(low, high + 1)
-				assertReaches(State(0, source, destination), maxCount, goal)
+				val (start, goal) = random.reachableCase(maxCount) ?: return@repeat
+				assertReaches(start, maxCount, goal)
+			}
+		}
+	}
+
+	@Test
+	fun planWithinTakesTheGoalWhenItsPlanFitsTheBudget() {
+		val plan = assertNotNull(StackMovePlanner.planWithin(State(0, 64, 0), 64, goal = 32, clickBudget = 2))
+		assertEquals(32, plan.destination)
+		assertEquals(listOf(Click(Target.Source, Button.Right), Click(Target.Destination, Button.Left)), plan.clicks)
+	}
+
+	@Test
+	fun planWithinSettlesForFewerItemsWhenTheBudgetIsTight() {
+		// 20 items need more than three clicks; a single item needs exactly three (pick up, place one, put back).
+		val plan = assertNotNull(StackMovePlanner.planWithin(State(0, 64, 0), 64, goal = 20, clickBudget = 3))
+		assertEquals(1, plan.destination)
+		assertEquals(3, plan.clicks.size)
+		assertSettles(State(0, 64, 0), plan.clicks, 64, 1)
+	}
+
+	@Test
+	fun planWithinNeverLeavesItemsOnTheCursorForABiggerMove() {
+		// Picking up and placing one item is two clicks but would strand 63 items on the cursor.
+		assertNull(StackMovePlanner.planWithin(State(0, 64, 0), 64, goal = 20, clickBudget = 2))
+		assertNull(StackMovePlanner.planWithin(State(0, 64, 0), 64, goal = 20, clickBudget = 0))
+	}
+
+	@Test
+	fun planWithinClampsTheGoalToWhatCanBeHeld() {
+		val plan = assertNotNull(StackMovePlanner.planWithin(State(0, 14, 50), 64, goal = 70, clickBudget = 10))
+		assertEquals(64, plan.destination)
+		assertNull(StackMovePlanner.planWithin(State(0, 10, 10), 64, goal = 10, clickBudget = 10), "nothing to add")
+	}
+
+	@Test
+	fun planWithinIsAsGoodAsTheExactPlanAndNeverOvershoots() {
+		val random = Random(42)
+		for (maxCount in listOf(1, 16, 64)) {
+			repeat(300) {
+				val (start, goal) = random.reachableCase(maxCount) ?: return@repeat
+				if (goal <= start.destination) return@repeat
+				val budget = random.nextInt(0, 9)
+				val exact = assertNotNull(StackMovePlanner.plan(start, maxCount, goal))
+				val plan = StackMovePlanner.planWithin(start, maxCount, goal, budget)
+				if (exact.size <= budget) {
+					assertEquals(goal, assertNotNull(plan, "exact plan fits $budget clicks").destination)
+				}
+				if (plan == null) return@repeat
+				assertTrue(plan.clicks.size <= budget, "plan ${plan.clicks} exceeds the budget of $budget")
+				assertTrue(plan.destination in (start.destination + 1)..goal, "destination ${plan.destination} outside (${start.destination}, $goal]")
+				assertSettles(start, plan.clicks, maxCount, plan.destination)
+				assertEquals(
+					plan.clicks.size,
+					assertNotNull(StackMovePlanner.plan(start, maxCount, plan.destination)).size,
+					"a budgeted plan must still be a shortest plan"
+				)
 			}
 		}
 	}

From 5d96dd932afcb3966db2bf09d6f505efffb7fd51 Mon Sep 17 00:00:00 2001
From: beanbag44 <107891830+beanbag44@users.noreply.github.com>
Date: Tue, 6 Oct 2026 17:12:09 +0100
Subject: [PATCH 25/43] cleanup

---
 .../task/tasks/ContainerTransferTask.kt       | 150 ++++--------------
 .../com/lambda/util/item/StackMovePlanner.kt  |  94 ++++-------
 2 files changed, 55 insertions(+), 189 deletions(-)

diff --git a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt
index 252a6278c..72deb6eea 100644
--- a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt
+++ b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt
@@ -76,26 +76,6 @@ class TransferResult internal constructor(
 	val containerSelection: ContainerSelection
 )
 
-/**
- * Moves items matching [fromStack] out of containers matching [fromSelection] into containers matching
- * [toSelection].
- *
- * Counting semantics: [StackSelection.count] of [fromStack] is the exact number of items to move. A count
- * of `0` or less moves everything that matches. Individual moves are planned to the exact item count using
- * left and right clicks (see [StackMovePlanner]); a whole stack is only moved past the requested count when
- * the sole way into the destination is swapping out a foreign stack.
- *
- * Destination slots are chosen in this order: a partial stack of the very same item, an empty slot, then (only
- * when nothing else is possible) a foreign stack matching [toStack] that gets swapped into the source slot.
- * [toStack] only ever selects the foreign stacks that may be swapped out; empty slots are always allowed.
- *
- * When both containers are external (chests, shulker boxes, ender chest) items travel through the player's
- * hotbar and inventory: a trip pulls as much as fits (or as much as still needs moving) into free player
- * slots, closes the source, opens the destination and pushes exactly the staged items back out. Trips repeat
- * until the request is satisfied, so sources larger than the player inventory work too. Items the player
- * already carried are never pushed, and anything staged that no destination can take is returned to the source
- * before the task settles.
- */
 class ContainerTransferTask @Ta5kBuilder internal constructor(
 	private val fromStack: StackSelection,
 	private val fromSelection: ContainerSelection,
@@ -105,7 +85,6 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 ) : Task(), Automated by automated {
 	override val name = "Transferring $fromStack"
 
-	/** `true` when [fromStack] carries no count: everything matching gets moved. */
 	private val unlimited = fromStack.count <= 0
 
 	private var transferred = 0
@@ -116,10 +95,8 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 	private val isComplete
 		get() = !unlimited && remaining <= 0
 
-	/** Items pulled out of an external source that are still waiting in the player inventory to be pushed. */
 	private var staged = 0
 
-	/** The source the most recent step used; staged leftovers go back there. */
 	private var lastSource: Container? = null
 
 	private val progressText
@@ -131,18 +108,15 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 	private val fromQueue = mutableListOf()
 	private val toQueue = mutableListOf()
 
-	/** The player containers trips may stage items in, honouring the allowed container config. */
 	private val stagingContainers: List
 		get() = listOf(InventoryContainer, HotbarContainer)
 			.filter { it.isAccessible }
 
-	/** How much a trip may still pull: what is left to move minus what is already waiting in the player inventory. */
 	private val pullLimit
 		get() =
 			if (unlimited) Int.MAX_VALUE
 			else (remaining - staged).coerceAtLeast(0)
 
-	/** How much a push may move: only staged items, and never more than the request still needs. */
 	private val pushLimit
 		get() =
 			if (unlimited) staged
@@ -169,11 +143,6 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 		nextStep()
 	}
 
-	/**
-	 * Picks the next source and destination pair and runs one transfer step between them. A step either moves
-	 * items directly (at most one side external) or performs a pull/push trip through the player inventory
-	 * (both sides external). Repeats until the requested count is reached or a queue runs dry.
-	 */
 	private fun SafeContext.nextStep() {
 		if (isComplete) {
 			success(buildResult())
@@ -216,10 +185,6 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 			.start()
 	}
 
-	/**
-	 * A destination can receive when it has room for the item (a partial stack or an empty slot) or holds a
-	 * stack matching [toStack] that may be swapped out.
-	 */
 	private fun canReceive(container: Container) =
 		container.spaceLeft(fromStack.mutate(1)) > 0 ||
 			container.count(toStack.mutate { notEmpty(); withoutSelection(fromStack) }) > 0
@@ -246,13 +211,7 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 			)
 		)
 
-	/**
-	 * Moves directly between [from] and [to]. At most one of them is external, so at most one screen has to be
-	 * opened; screens this task opened are closed again afterwards.
-	 */
 	private fun directChain(from: Container, to: Container): Task<*> {
-		// Every queued player-side destination travels along with [to]: a request for "hotbar and inventory" is
-		// one destination as far as vanilla's quick-move is concerned, and the move may use whichever has room.
 		val toContainers =
 			if (to is ExternalContainer) listOf(to)
 			else listOf(to) +
@@ -286,10 +245,6 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 		}
 	}
 
-	/**
-	 * One trip for two external containers: pull from [from] into free player slots, close it, then push the
-	 * staged items into the destination queue. Loops are driven naturally by [nextStep].
-	 */
 	private fun tripChain(from: Container): Task<*> =
 		taskOrNull { from.access() }
 			.then { fromContext ->
@@ -311,10 +266,6 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 			}
 			.then { pushChain(from) }
 
-	/**
-	 * Pushes staged items from the player inventory into the first available destination. If a destination
-	 * fills up (or takes nothing) while items are still staged, advances [toQueue] and pushes to the next one.
-	 */
 	private fun pushChain(from: Container): Task<*> {
 		val to = toQueue.firstOrNull { !it.haveMatchingInventories(from) }
 			?: return taskOrNull { null }
@@ -330,7 +281,6 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 					limit = pushLimit
 				).thenOrNull { pushed ->
 					staged = (staged - pushed).coerceAtLeast(0)
-					// A destination that took nothing is done for, whatever canReceive() thinks of it.
 					if (pushed <= 0 || !canReceive(to)) {
 						toQueue.removeAll { it.haveMatchingInventories(to) }
 					}
@@ -345,7 +295,6 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 			}
 	}
 
-	/** A source slot and the destination slot chosen for it, with the containers they belong to. */
 	private data class SlotPair(
 		val fromContainer: Container,
 		val fromSlot: Slot,
@@ -359,19 +308,6 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 			}
 	}
 
-	/**
-	 * Moves items between already-accessed containers. Slot pairs are selected fresh each tick using
-	 * [fromSelection] and the container's own [Container.findSlot] / [Container.findReplaceSlot] APIs —
-	 * no slot references are cached across ticks.
-	 *
-	 * A foreign stack in the chosen destination slot is swapped out with the container's own [Container.swap].
-	 * A whole stack is shift-clicked with vanilla's quick-move when that is certain to land inside [toContainers]
-	 * and the cursor is empty. Everything else is clicked through along a [StackMovePlanner] plan, which reaches
-	 * the exact item count in the exact slot and carries on from whatever the cursor still holds.
-	 *
-	 * @param allowReplace whether a foreign stack matching [toSelection] may be swapped out as a last resort.
-	 * @param trackResult whether destination slots are recorded for the [TransferResult].
-	 */
 	private inner class MoveTask @Ta5kBuilder constructor(
 		private val fromContainers: List,
 		private val toContainers: List,
@@ -384,7 +320,6 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 		override val name get() = "Moving ${this@MoveTask.fromSelection}"
 		private var moved = 0
 
-		/** Foreign stacks that may be swapped out: the destination filter minus anything the source filter matches. */
 		private val toSelection =
 			toSelection.mutate {
 				notEmpty()
@@ -420,7 +355,7 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 					toContainers.firstNotNullOfOrNull { toContainer ->
 						if (toContainer.haveMatchingInventories(fromContainer)) return@firstNotNullOfOrNull null
 						if (!fromContainer.canSwapWith(toContainer)) return@firstNotNullOfOrNull null
-						findDestinationSlot(fromSlot, toContainer)?.let { toSlot ->
+						findToSlot(fromSlot, toContainer)?.let { toSlot ->
 							SlotPair(fromContainer, fromSlot, toContainer, toSlot)
 						}
 					}
@@ -434,13 +369,7 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 			move(pair, atMost = currentLimit)
 		}
 
-		/**
-		 * The slot of [toContainer] that should receive the stack in [fromSlot]: a partial stack of the very same
-		 * item (components included) is topped up first, then an empty slot is used; the destination filter never
-		 * restricts those. Only as a last resort, and only when [allowReplace] permits it, a foreign stack matching
-		 * [toSelection] is chosen to be swapped out.
-		 */
-		private fun AutomatedSafeContext.findDestinationSlot(fromSlot: Slot, toContainer: Container): Slot? {
+		private fun AutomatedSafeContext.findToSlot(fromSlot: Slot, toContainer: Container): Slot? {
 			val stack = fromSlot.stack
 			val partialStack =
 				stackSelection {
@@ -452,40 +381,15 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 				?: findSwappableSlot(fromSlot, toContainer)
 		}
 
-		/** A foreign stack of [toContainer] matching [toSelection] that [fromSlot] accepts in return. */
 		private fun AutomatedSafeContext.findSwappableSlot(fromSlot: Slot, toContainer: Container): Slot? {
 			if (!allowReplace) return null
 			return toContainer.findReplaceSlot(fromSlot.stack, toSelection)?.takeIf { fromSlot.canInsert(it.stack) }
 		}
 
-		/**
-		 * Vanilla's quick-move (shift-click) picks the target slot itself: from a container screen it fills the
-		 * whole player inventory (hotbar first), from the player inventory it fills the open container, and
-		 * inside the player's own screen it moves between hotbar and main inventory, equipping armour on the
-		 * way. It may only be used when every slot it could pick belongs to one of [toContainers].
-		 */
-		private fun AutomatedSafeContext.quickMoveStaysInside(fromContainer: Container, stack: ItemStack): Boolean {
-			if (stack.components.contains(DataComponentTypes.EQUIPPABLE)) return false
-			val playerScreen = player.currentScreenHandler === player.playerScreenHandler
-			fun covers(container: Container) = toContainers.any { it.haveMatchingInventories(container) }
-			fun coversOpenContainer() = toContainers.any { it is ExternalContainer && it.isAccessed }
-			return when {
-				fromContainer is ExternalContainer -> covers(InventoryContainer) && covers(HotbarContainer)
-				fromContainer === InventoryContainer -> if (playerScreen) covers(HotbarContainer) else coversOpenContainer()
-				fromContainer === HotbarContainer -> if (playerScreen) covers(InventoryContainer) else coversOpenContainer()
-				else -> false
-			}
-		}
-
-		/**
-		 * Moves at most [atMost] items along [pair]. A foreign stack in the destination slot can only be swapped
-		 * out whole. A whole stack takes vanilla's quick-move when that is certain to land inside [toContainers]
-		 * and nothing is on the cursor. Everything else is clicked through to the exact count.
-		 */
 		private fun AutomatedSafeContext.move(pair: SlotPair, atMost: Int) {
 			val stack = pair.fromSlot.stack
 			val count = minOf(stack.count, atMost)
-			val cursor = player.currentScreenHandler.cursorStack
+			val cursor = CursorContainer.stacks.firstOrNull() ?: ItemStack.EMPTY
 			when {
 				pair.toSlot.stack.isForeignTo(stack) -> swapOut(pair)
 				count == stack.count && cursor.isEmpty && quickMoveStaysInside(pair.fromContainer, stack) -> quickMoveWhole(pair)
@@ -493,7 +397,6 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 			}
 		}
 
-		/** Swaps the whole source stack with the foreign stack in the destination slot; vanilla knows no other way into an occupied slot. */
 		private fun AutomatedSafeContext.swapOut(pair: SlotPair) {
 			val count = pair.fromSlot.stack.count
 			val request = pair.fromContainer.swapRequest(pair.fromSlot, pair.toSlot, pair.toContainer)?.submit()
@@ -507,10 +410,6 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 			}
 		}
 
-		/**
-		 * Shift-clicks the whole stack and lets vanilla pick the slots, so the destination containers are diffed
-		 * afterwards to learn where the items went.
-		 */
 		private fun AutomatedSafeContext.quickMoveWhole(pair: SlotPair) {
 			if (availablePackets(PacketType.Inventory) < 1) return
 			if (ContainerEvent.Transfer(pair.fromSlot, pair.toSlot, pair.fromContainer, pair.toContainer).post().isCanceled()) return
@@ -535,28 +434,26 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 			if (trackResult) recordReceivingSlots(destinationsBefore)
 		}
 
-		/**
-		 * Clicks exactly [count] items across along a [StackMovePlanner] plan that fits this tick's inventory
-		 * packet budget. When the full count does not fit, as much as the budget allows is moved now and the rest
-		 * follows on later ticks.
-		 */
 		private fun AutomatedSafeContext.clickThrough(pair: SlotPair, count: Int) {
 			if (!CursorContainer.isAccessible) {
 				finish()
 				return
 			}
 			val stack = pair.fromSlot.stack
-			val cursor = player.currentScreenHandler.cursorStack
+			val cursor = CursorContainer.stacks.firstOrNull() ?: return
 			// A foreign stack on the cursor is somebody else's business; wait until it has been put away.
-			if (cursor.isForeignTo(stack)) return
+			if (cursor.isForeignTo(stack)) {
+				finish()
+				return
+			}
 
 			val budget = availablePackets(PacketType.Inventory)
-			val destinationBefore = pair.toSlot.stack.count
+			val beforeTo = pair.toSlot.stack.count
 			val plan =
 				StackMovePlanner.planWithin(
-					state = StackMovePlanner.State(cursor.count, stack.count, destinationBefore),
+					state = StackMovePlanner.State(cursor.count, stack.count, beforeTo),
 					maxCount = pair.toSlot.getMaxItemCount(stack),
-					goal = destinationBefore + count,
+					goal = beforeTo + count,
 					clickBudget = budget
 				)
 			if (plan == null) {
@@ -565,7 +462,8 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 				return
 			}
 
-			if (ContainerEvent.Transfer(pair.fromSlot, pair.toSlot, pair.fromContainer, pair.toContainer).post().isCanceled()) return
+			val transferEvent = ContainerEvent.Transfer(pair.fromSlot, pair.toSlot, pair.fromContainer, pair.toContainer)
+			if (transferEvent.post().isCanceled()) return
 			val request =
 				inventoryRequest {
 					plan.clicks.forEach { click -> pickup(pair[click.target].id, click.button.id) }
@@ -575,11 +473,23 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 				return
 			}
 
-			recordMoved(plan.destination - destinationBefore)
+			recordMoved(plan.destination - beforeTo)
 			recordDestination(pair.toSlot, pair.toContainer)
 		}
 
-		/** Books every destination slot that holds more than it did [before] vanilla's quick-move. */
+		private fun AutomatedSafeContext.quickMoveStaysInside(fromContainer: Container, stack: ItemStack): Boolean {
+			if (stack.components.contains(DataComponentTypes.EQUIPPABLE)) return false
+			val playerScreen = player.currentScreenHandler.syncId == 0
+			fun covers(container: Container) = toContainers.any { it.haveMatchingInventories(container) }
+			fun coversOpenContainer() = toContainers.any { it is ExternalContainer && it.isAccessed }
+			return when {
+				fromContainer is ExternalContainer -> covers(InventoryContainer) && covers(HotbarContainer)
+				fromContainer === InventoryContainer -> if (playerScreen) covers(HotbarContainer) else coversOpenContainer()
+				fromContainer === HotbarContainer -> if (playerScreen) covers(InventoryContainer) else coversOpenContainer()
+				else -> false
+			}
+		}
+
 		private fun recordReceivingSlots(before: List>) {
 			toContainers.zip(before).forEach { (container, previous) ->
 				container.slots.forEachIndexed { index, slot ->
@@ -599,10 +509,6 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 			resultContainers += container
 		}
 
-		/**
-		 * A request the manager could not take this tick is retried next tick when the cause clears up by itself
-		 * (packet limit, another request already running); any other cause will not, so the move ends here.
-		 */
 		private fun finishUnlessTransient(failureReason: InvRequestFailureReason) {
 			if (failureReason == InvRequestFailureReason.None) finish()
 		}
@@ -613,10 +519,8 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 
 		private fun ItemStack.sameItemAs(other: ItemStack) = ItemStack.areItemsAndComponentsEqual(this, other)
 
-		/** A non-empty stack of another item, or of the same item with other components. */
 		private fun ItemStack.isForeignTo(other: ItemStack) = !isEmpty && !sameItemAs(other)
 
-		/** Whether this stack holds something [previous] did not, or more of it. */
 		private fun ItemStack.grewFrom(previous: ItemStack?) =
 			!isEmpty && (previous == null || previous.isEmpty || !sameItemAs(previous) || count > previous.count)
 	}
diff --git a/src/main/kotlin/com/lambda/util/item/StackMovePlanner.kt b/src/main/kotlin/com/lambda/util/item/StackMovePlanner.kt
index 388339879..a237d626e 100644
--- a/src/main/kotlin/com/lambda/util/item/StackMovePlanner.kt
+++ b/src/main/kotlin/com/lambda/util/item/StackMovePlanner.kt
@@ -17,27 +17,12 @@
 
 package com.lambda.util.item
 
-/**
- * Plans the shortest sequence of vanilla `PICKUP` slot clicks that moves items of one item type from a source
- * slot into a destination slot, with the cursor as the only intermediary.
- *
- * The model follows vanilla click semantics for two slots and a cursor that hold the same item type or nothing:
- *  - Left click with an empty cursor picks up the whole slot.
- *  - Right click with an empty cursor picks up half the slot, rounded up.
- *  - Left click with a held stack places as much as fits into the slot.
- *  - Right click with a held stack places a single item.
- *
- * Every count is bounded by the stack size the destination slot allows (`maxCount`), which keeps the state space
- * at a few thousand states at most. A breadth-first search over it yields the minimal click count cheaply enough
- * to plan every move afresh.
- */
 object StackMovePlanner {
 	enum class Target {
 		Source,
 		Destination
 	}
 
-	/** A mouse button as vanilla numbers it in `PICKUP` clicks. */
 	enum class Button(val id: Int) {
 		Left(0),
 		Right(1)
@@ -45,7 +30,6 @@ object StackMovePlanner {
 
 	data class Click(val target: Target, val button: Button)
 
-	/** Item counts held by the cursor and both slots. */
 	data class State(val cursor: Int, val source: Int, val destination: Int) {
 		val total get() = cursor + source + destination
 
@@ -61,42 +45,18 @@ object StackMovePlanner {
 				Target.Destination -> copy(destination = count)
 			}
 
-		/** Whether every count lies within what slots holding at most [maxCount] items can represent. */
 		fun fits(maxCount: Int) =
 			cursor in 0..maxCount && source in 0..maxCount && destination in 0..maxCount
 
-		/** The only state a finished move can end in: an empty cursor and [count] items in the destination. */
 		fun settled(count: Int) = State(cursor = 0, source = total - count, destination = count)
 	}
 
-	/** The [clicks] that leave [destination] items in the destination slot and nothing on the cursor. */
 	data class Plan(val clicks: List, val destination: Int)
 
-	/** How a state was first reached during the search. */
 	private data class Step(val previous: State, val click: Click)
 
 	private val CLICKS = Target.entries.flatMap { target -> Button.entries.map { Click(target, it) } }
 
-	/** Applies one [click] to [state] following vanilla `PICKUP` semantics. */
-	fun apply(state: State, click: Click, maxCount: Int): State =
-		if (state.cursor == 0) state.pickUp(click) else state.place(click, maxCount)
-
-	/**
-	 * The shortest click sequence that ends with exactly [goal] items in the destination and an empty cursor,
-	 * or `null` when there is none: the goal exceeds [maxCount] or the items available, the source could not
-	 * hold the surplus, or [state] does not [fit][State.fits] the model.
-	 */
-	fun plan(state: State, maxCount: Int, goal: Int): List? {
-		val end = state.settled(goal)
-		if (!state.fits(maxCount) || !end.fits(maxCount)) return null
-		return explore(state, maxCount, maxClicks = Int.MAX_VALUE).pathTo(end)
-	}
-
-	/**
-	 * The plan that brings the destination as close to [goal] as [clickBudget] clicks allow without exceeding
-	 * it: the largest count in `(state.destination, goal]` whose shortest plan fits the budget. `null` when not
-	 * even a single item can be moved within the budget.
-	 */
 	fun planWithin(state: State, maxCount: Int, goal: Int, clickBudget: Int): Plan? {
 		if (!state.fits(maxCount)) return null
 		val steps = explore(state, maxCount, maxClicks = clickBudget)
@@ -106,34 +66,12 @@ object StackMovePlanner {
 		}
 	}
 
-	/** An empty cursor takes the whole slot with a left click and the larger half with a right click. */
-	private fun State.pickUp(click: Click): State {
-		val slot = this[click.target]
-		val taken =
-			when (click.button) {
-				Button.Left -> slot
-				Button.Right -> (slot + 1) / 2
-			}
-		return copy(cursor = taken).withSlot(click.target, slot - taken)
-	}
-
-	/** A held stack drops as much as fits with a left click and a single item with a right click. */
-	private fun State.place(click: Click, maxCount: Int): State {
-		val slot = this[click.target]
-		val offered =
-			when (click.button) {
-				Button.Left -> cursor
-				Button.Right -> 1
-			}
-		val placed = minOf(offered, maxCount - slot).coerceAtLeast(0)
-		return copy(cursor = cursor - placed).withSlot(click.target, slot + placed)
+	fun plan(state: State, maxCount: Int, goal: Int): List? {
+		val end = state.settled(goal)
+		if (!state.fits(maxCount) || !end.fits(maxCount)) return null
+		return explore(state, maxCount, maxClicks = Int.MAX_VALUE).pathTo(end)
 	}
 
-	/**
-	 * Breadth-first search from [start]: every state reachable within [maxClicks] clicks, mapped to the step
-	 * that first reached it ([start] itself maps to `null`). Following the steps back from a state yields its
-	 * shortest plan.
-	 */
 	private fun explore(start: State, maxCount: Int, maxClicks: Int): Map {
 		val steps = hashMapOf(start to null)
 		var frontier = listOf(start)
@@ -159,4 +97,28 @@ object StackMovePlanner {
 			.toList()
 			.asReversed()
 	}
+
+	fun apply(state: State, click: Click, maxCount: Int): State =
+		if (state.cursor == 0) state.pickUp(click) else state.place(click, maxCount)
+
+	private fun State.pickUp(click: Click): State {
+		val slot = this[click.target]
+		val taken =
+			when (click.button) {
+				Button.Left -> slot
+				Button.Right -> (slot + 1) / 2
+			}
+		return copy(cursor = taken).withSlot(click.target, slot - taken)
+	}
+
+	private fun State.place(click: Click, maxCount: Int): State {
+		val slot = this[click.target]
+		val offered =
+			when (click.button) {
+				Button.Left -> cursor
+				Button.Right -> 1
+			}
+		val placed = minOf(offered, maxCount - slot).coerceAtLeast(0)
+		return copy(cursor = cursor - placed).withSlot(click.target, slot + placed)
+	}
 }

From a59c6bf2e10e6da7fca1a95933f1e1d641797f72 Mon Sep 17 00:00:00 2001
From: beanbag44 <107891830+beanbag44@users.noreply.github.com>
Date: Tue, 6 Oct 2026 21:39:03 +0100
Subject: [PATCH 26/43] fixes

---
 .../lambda/command/commands/FriendCommand.kt  |  2 +-
 .../config/categories/FriendCategory.kt       |  2 +-
 .../lambda/interaction/container/Container.kt |  1 -
 .../handler/handlers/FriendImporter.kt        |  4 +--
 .../modules/combat/crystalaura/CrystalAura.kt |  6 ++--
 .../modules/combat/crystalaura/Opportunity.kt | 34 +++++++++++--------
 .../module/modules/network/AutoReconnect.kt   |  2 +-
 .../com/lambda/module/modules/world/Nuker.kt  | 10 +++---
 8 files changed, 32 insertions(+), 29 deletions(-)

diff --git a/src/main/kotlin/com/lambda/command/commands/FriendCommand.kt b/src/main/kotlin/com/lambda/command/commands/FriendCommand.kt
index 87f4b8454..9b9441535 100644
--- a/src/main/kotlin/com/lambda/command/commands/FriendCommand.kt
+++ b/src/main/kotlin/com/lambda/command/commands/FriendCommand.kt
@@ -32,7 +32,7 @@ import com.lambda.brigadier.required
 import com.lambda.command.CommandRegistry.prefix
 import com.lambda.command.LambdaCommand
 import com.lambda.config.categories.FriendCategory
-import com.lambda.interaction.handlers.FriendHandler
+import com.lambda.interaction.handler.handlers.FriendHandler
 import com.lambda.interaction.handlers.FriendImporter
 import com.lambda.network.mojang.getProfile
 import com.lambda.threading.runIO
diff --git a/src/main/kotlin/com/lambda/config/categories/FriendCategory.kt b/src/main/kotlin/com/lambda/config/categories/FriendCategory.kt
index 3484222a8..130c1e786 100644
--- a/src/main/kotlin/com/lambda/config/categories/FriendCategory.kt
+++ b/src/main/kotlin/com/lambda/config/categories/FriendCategory.kt
@@ -18,7 +18,7 @@
 package com.lambda.config.categories
 
 import com.lambda.config.ConfigCategory
-import com.lambda.interaction.handlers.FriendHandler
+import com.lambda.interaction.handler.handlers.FriendHandler
 import com.lambda.threading.runIO
 import com.lambda.util.FolderRegistry
 import java.io.File
diff --git a/src/main/kotlin/com/lambda/interaction/container/Container.kt b/src/main/kotlin/com/lambda/interaction/container/Container.kt
index b58336cf3..773aaa0e4 100644
--- a/src/main/kotlin/com/lambda/interaction/container/Container.kt
+++ b/src/main/kotlin/com/lambda/interaction/container/Container.kt
@@ -49,7 +49,6 @@ import net.minecraft.item.ItemStack
 import net.minecraft.screen.slot.Slot
 import net.minecraft.text.Text
 
-// ToDo: Make jsonable to persistently store them
 abstract class Container(
     val type: ContainerType
 ) : Nameable, Comparable {
diff --git a/src/main/kotlin/com/lambda/interaction/handler/handlers/FriendImporter.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/FriendImporter.kt
index abf23e216..dfc282ff1 100644
--- a/src/main/kotlin/com/lambda/interaction/handler/handlers/FriendImporter.kt
+++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/FriendImporter.kt
@@ -20,8 +20,8 @@ package com.lambda.interaction.handlers
 import com.lambda.Lambda
 import com.lambda.config.categories.FriendCategory
 import com.lambda.config.serializers.FriendList
-import com.lambda.interaction.handlers.FriendHandler.befriend
-import com.lambda.interaction.handlers.FriendHandler.isFriend
+import com.lambda.interaction.handler.handlers.FriendHandler.befriend
+import com.lambda.interaction.handler.handlers.FriendHandler.isFriend
 import com.lambda.network.mojang.getProfiles
 import com.lambda.threading.runGameScheduled
 import com.lambda.util.CommunicationUtils.info
diff --git a/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/CrystalAura.kt b/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/CrystalAura.kt
index 11c5a7fc5..c0089b8dd 100644
--- a/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/CrystalAura.kt
+++ b/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/CrystalAura.kt
@@ -18,7 +18,7 @@
 package com.lambda.module.modules.combat.crystalaura
 
 import com.lambda.config.Tab
-import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig
+import com.lambda.config.automation.setDefaultAutomationConfig
 import com.lambda.config.blocks.TargetingSettings
 import com.lambda.config.blocks.WorldLineSettings
 import com.lambda.config.forEachSetting
@@ -31,9 +31,9 @@ import com.lambda.event.events.TickEvent
 import com.lambda.event.listener.SafeListener.Companion.listen
 import com.lambda.graphics.mc.renderer.ImmediateRenderer.Companion.immediateRenderer
 import com.lambda.module.Module
+import com.lambda.module.ModuleTag
 import com.lambda.module.modules.combat.crystalaura.CrystalAuraExploding.explodeInternal
 import com.lambda.module.modules.combat.crystalaura.CrystalAuraPlacing.placeInternal
-import com.lambda.module.tag.ModuleTag
 import com.lambda.threading.runSafe
 import com.lambda.threading.runSafeGameScheduled
 import com.lambda.util.BlockUtils.blockState
@@ -44,8 +44,8 @@ import com.lambda.util.combat.CombatUtils.crystalDamage
 import com.lambda.util.extension.fullHealth
 import com.lambda.util.math.MathUtils.ceilToInt
 import com.lambda.util.math.MathUtils.roundToStep
+import com.lambda.util.math.blockPos
 import com.lambda.util.math.distSq
-import com.lambda.util.math.flooredBlockPos
 import com.lambda.util.math.getHitVec
 import com.lambda.util.math.minus
 import com.lambda.util.math.plus
diff --git a/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/Opportunity.kt b/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/Opportunity.kt
index e51b411da..ae68dc165 100644
--- a/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/Opportunity.kt
+++ b/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/Opportunity.kt
@@ -19,14 +19,17 @@ package com.lambda.module.modules.combat.crystalaura
 
 import com.lambda.Lambda
 import com.lambda.context.Automated
-import com.lambda.interaction.handlers.ContainerHandler.transfer
-import com.lambda.interaction.managers.hotbar.HotbarRequest
-import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest
-import com.lambda.interaction.managers.rotating.Rotation.Companion.rotationTo
-import com.lambda.interaction.managers.rotating.RotationManager
-import com.lambda.interaction.material.StackSelection
-import com.lambda.interaction.material.container.containers.HotbarContainer
-import com.lambda.interaction.material.container.containers.OffHandContainer
+import com.lambda.interaction.container.containers.HotbarContainer
+import com.lambda.interaction.container.containers.OffhandContainer
+import com.lambda.interaction.container.selection.StackSelection
+import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection
+import com.lambda.interaction.container.selection.select
+import com.lambda.interaction.handler.handlers.move
+import com.lambda.interaction.manager.managers.hotbar.HotbarRequest
+import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest
+import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotationTo
+import com.lambda.interaction.manager.managers.rotating.RotationManager
+import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest
 import com.lambda.module.modules.combat.crystalaura.CrystalAura.ActionType
 import com.lambda.module.modules.combat.crystalaura.CrystalAura.crystalPosition
 import com.lambda.module.modules.combat.crystalaura.CrystalAura.explodeDelay
@@ -48,6 +51,7 @@ import com.lambda.module.modules.combat.crystalaura.CrystalAura.swap
 import com.lambda.module.modules.combat.crystalaura.CrystalAura.swapHand
 import com.lambda.module.modules.combat.crystalaura.CrystalAuraExploding.explodeInternal
 import com.lambda.module.modules.combat.crystalaura.CrystalAuraPlacing.placeInternal
+import com.lambda.task.tasks.transfer
 import com.lambda.threading.runSafe
 import com.lambda.threading.runSafeAutomated
 import com.lambda.util.math.distSq
@@ -122,7 +126,7 @@ class Opportunity(
 			return@runSafe
 		}
 		var crystalHand: Hand? = null
-		val selection = StackSelection.selectStack { isItem(Items.END_CRYSTAL) }
+		val selection = stackSelection { isItem(Items.END_CRYSTAL) }
 		if ((swapHand == Hand.MAIN_HAND && player.mainHandStack.item != Items.END_CRYSTAL) ||
 			(swapHand == Hand.OFF_HAND && player.offHandStack.item != Items.END_CRYSTAL)
 		) automated.runSafeAutomated {
@@ -137,19 +141,19 @@ class Opportunity(
 
 			val swapTo = when (swapHand) {
 				Hand.MAIN_HAND -> HotbarContainer
-				Hand.OFF_HAND -> OffHandContainer
-			}
+				Hand.OFF_HAND -> OffhandContainer
+			}.select()
 
 			if (itemToUse == null || swapHand == Hand.MAIN_HAND) {
 				if (swapHand == Hand.MAIN_HAND) {
-					val itemStack = selection.bestItemMatch(player.hotbarStacks)
+					val itemStack = selection.bestMatch(player.hotbarStacks)
 					if (itemStack == null) { // retrieve to hotbar
-						if (!selection.transfer(swapTo)) return@runSafe
+						if (!selection.move(toSelection = swapTo)) return@runSafe
 					}
 					val s = player.hotbarStacks.indexOf(itemStack)
-					if ((!HotbarRequest(s, automated, nowOrNothing = false).submit().done) && itemToUse == null) return@runSafe
+					if ((!hotbarRequest(s, nowOrNothing = false).submit().done) && itemToUse == null) return@runSafe
 				} else { // retrieve to offhand
-					if (!selection.transfer(swapTo)) return@runSafe
+					if (!selection.move(toSelection = swapTo)) return@runSafe
 				}
 			}
 		}
diff --git a/src/main/kotlin/com/lambda/module/modules/network/AutoReconnect.kt b/src/main/kotlin/com/lambda/module/modules/network/AutoReconnect.kt
index c4e208f43..3b901d1d7 100644
--- a/src/main/kotlin/com/lambda/module/modules/network/AutoReconnect.kt
+++ b/src/main/kotlin/com/lambda/module/modules/network/AutoReconnect.kt
@@ -19,7 +19,7 @@ package com.lambda.module.modules.network
 
 import com.lambda.Lambda.mc
 import com.lambda.module.Module
-import com.lambda.module.tag.ModuleTag
+import com.lambda.module.ModuleTag
 import net.minecraft.client.gui.screen.Screen
 import net.minecraft.client.gui.screen.multiplayer.ConnectScreen
 import net.minecraft.client.gui.screen.world.SelectWorldScreen
diff --git a/src/main/kotlin/com/lambda/module/modules/world/Nuker.kt b/src/main/kotlin/com/lambda/module/modules/world/Nuker.kt
index 874f4107e..ed7aa9790 100644
--- a/src/main/kotlin/com/lambda/module/modules/world/Nuker.kt
+++ b/src/main/kotlin/com/lambda/module/modules/world/Nuker.kt
@@ -17,10 +17,10 @@
 
 package com.lambda.module.modules.world
 
-import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig
+import com.lambda.config.automation.setDefaultAutomationConfig
 import com.lambda.config.blocks.BreakConfig
 import com.lambda.config.editTypedSettings
-import com.lambda.config.entries.Setting.Companion.onValueChange
+import com.lambda.config.entries.onValueChange
 import com.lambda.config.settings.complex.Bind
 import com.lambda.config.withEdits
 import com.lambda.context.SafeContext
@@ -29,10 +29,10 @@ import com.lambda.event.listener.SafeListener.Companion.listen
 import com.lambda.interaction.construction.blueprint.TickingBlueprint.Companion.tickingBlueprint
 import com.lambda.interaction.construction.verify.TargetState
 import com.lambda.module.Module
-import com.lambda.module.tag.ModuleTag
-import com.lambda.task.RootTask.run
+import com.lambda.module.ModuleTag
 import com.lambda.task.Task
-import com.lambda.task.tasks.BuildTask.Companion.build
+import com.lambda.task.start
+import com.lambda.task.tasks.build
 import com.lambda.util.BlockUtils.blockPos
 import com.lambda.util.BlockUtils.blockState
 import com.lambda.util.CommunicationUtils.info

From f81a93d0f51da727a23f7f8d2ccf23e183023eb8 Mon Sep 17 00:00:00 2001
From: beanbag44 <107891830+beanbag44@users.noreply.github.com>
Date: Tue, 6 Oct 2026 23:14:29 +0100
Subject: [PATCH 27/43] move builder entry functions out of companion objects

---
 .../construction/simulation/BuildSimulator.kt | 35 ++++++++--------
 .../construction/simulation/Sim.kt            |  2 +-
 .../construction/simulation/sims/BreakSim.kt  |  2 +-
 .../simulation/sims/InteractSim.kt            |  2 +-
 .../lambda/interaction/container/Container.kt |  2 +-
 .../manager/managers/breaking/BreakManager.kt |  2 +-
 .../manager/managers/breaking/BreakRequest.kt | 37 ++++++++--------
 .../manager/managers/hotbar/HotbarRequest.kt  | 19 ++++-----
 .../managers/interacting/InteractManager.kt   |  4 +-
 .../managers/interacting/InteractRequest.kt   | 42 +++++++++----------
 .../managers/inventory/InventoryRequest.kt    | 22 +++++-----
 .../managers/rotating/RotationRequest.kt      | 19 ++++-----
 .../container/containers/ChestContainer.kt    |  0
 .../containers/EnderChestContainer.kt         |  0
 .../containers/ShulkerBoxContainer.kt         |  0
 .../lambda/module/modules/combat/AutoArmor.kt |  2 +-
 .../lambda/module/modules/combat/KillAura.kt  |  4 +-
 .../modules/combat/crystalaura/CrystalAura.kt |  2 +-
 .../modules/combat/crystalaura/Opportunity.kt |  8 +---
 .../module/modules/debug/RotationTest.kt      |  2 +-
 .../lambda/module/modules/debug/SilentSwap.kt |  3 +-
 .../module/modules/movement/AutoSpiral.kt     |  2 +-
 .../module/modules/movement/BetterFirework.kt |  4 +-
 .../modules/movement/ElytraAltitudeControl.kt |  2 +-
 .../lambda/module/modules/movement/Speed.kt   |  2 +-
 .../movement/elytrafly/ElytraFlyMode.kt       |  4 +-
 .../elytrafly/modes/BounceElytraFly.kt        |  2 +-
 .../elytrafly/modes/GrimControlElytraFly.kt   |  2 +-
 .../lambda/module/modules/player/AntiAim.kt   |  2 +-
 .../module/modules/player/EndermanLook.kt     |  2 +-
 .../module/modules/player/InventoryMove.kt    |  2 +-
 .../module/modules/player/InventoryResync.kt  |  2 +-
 .../lambda/module/modules/player/LineLock.kt  |  2 +-
 .../module/modules/player/PacketMine.kt       |  2 +-
 .../lambda/module/modules/player/Replay.kt    |  2 +-
 .../module/modules/player/RotationLock.kt     |  2 +-
 .../module/modules/player/StackReplenish.kt   |  2 +-
 .../lambda/module/modules/render/Freecam.kt   |  2 +-
 .../lambda/module/modules/world/AutoPortal.kt |  4 +-
 .../module/modules/world/AutoVillagerCycle.kt |  2 +-
 .../lambda/module/modules/world/StashMover.kt |  6 +--
 .../com/lambda/task/tasks/CollectDropsTask.kt |  2 +-
 .../task/tasks/ContainerTransferTask.kt       |  2 +-
 .../kotlin/com/lambda/task/tasks/EatTask.kt   |  2 +-
 .../lambda/task/tasks/OpenContainerTask.kt    |  2 +-
 45 files changed, 132 insertions(+), 135 deletions(-)
 delete mode 100644 src/main/kotlin/com/lambda/interaction/material/container/containers/ChestContainer.kt
 delete mode 100644 src/main/kotlin/com/lambda/interaction/material/container/containers/EnderChestContainer.kt
 delete mode 100644 src/main/kotlin/com/lambda/interaction/material/container/containers/ShulkerBoxContainer.kt

diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt
index 046855d72..ee00b43f4 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt
@@ -44,9 +44,24 @@ import net.minecraft.util.math.Vec3d
 context(automatedSafeContext: AutomatedSafeContext)
 fun Structure.sim(
 	pov: Vec3d = automatedSafeContext.player.eyePos
-) = BuildSimulator.sim(this, pov)
+) = sim(this, pov)
 
-class BuildSimulator private constructor(
+@JvmName("simExt")
+@SimDsl
+context(automatedSafeContext: AutomatedSafeContext)
+fun sim(
+	structure: Structure,
+	pov: Vec3d = automatedSafeContext.player.eyePos
+): Set =
+	with(automatedSafeContext) {
+		runBlocking(Dispatchers.Default) {
+			val results = ConcurrentSet()
+			with(BuildSimulator(structure, pov, results)) { sim() }
+			results
+		}
+	}
+
+class BuildSimulator internal constructor(
 	private val structure: Structure,
 	private val pov: Vec3d,
 	private val resultsCollection: MutableSet
@@ -66,20 +81,4 @@ class BuildSimulator private constructor(
 			}
 		}
 	}
-
-	companion object {
-		@SimDsl
-		context(automatedSafeContext: AutomatedSafeContext)
-		internal fun sim(
-			structure: Structure,
-			pov: Vec3d = automatedSafeContext.player.eyePos
-		): Set =
-			with(automatedSafeContext) {
-				runBlocking(Dispatchers.Default) {
-					val results = ConcurrentSet()
-					with(BuildSimulator(structure, pov, results)) { sim() }
-					results
-				}
-			}
-	}
 }
diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/Sim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/Sim.kt
index 1a68cd105..4b6ee893d 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/simulation/Sim.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/Sim.kt
@@ -84,7 +84,7 @@ abstract class Sim : Results {
         dependencyStack.pop()
     }
 
-    protected abstract suspend fun AutomatedSafeContext.sim()
+    abstract suspend fun AutomatedSafeContext.sim()
 
     /**
      * Scans a [voxelShape] on the given [sides] at the [pos] from the [pov].
diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/BreakSim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/BreakSim.kt
index eca23d9c7..dc2ad232b 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/BreakSim.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/BreakSim.kt
@@ -36,7 +36,7 @@ import com.lambda.interaction.container.selection.select
 import com.lambda.interaction.handler.handlers.findStack
 import com.lambda.interaction.manager.managers.hotbar.HotbarManager
 import com.lambda.interaction.manager.managers.rotating.RotationManager
-import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest
+import com.lambda.interaction.manager.managers.rotating.rotationRequest
 import com.lambda.util.BlockUtils.blockState
 import com.lambda.util.BlockUtils.calcItemBlockBreakingDelta
 import com.lambda.util.BlockUtils.instantBreakable
diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/InteractSim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/InteractSim.kt
index 57419852e..05f77e62a 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/InteractSim.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/InteractSim.kt
@@ -34,7 +34,7 @@ import com.lambda.interaction.handler.handlers.findSlot
 import com.lambda.interaction.manager.managers.rotating.Rotation
 import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotation
 import com.lambda.interaction.manager.managers.rotating.RotationManager
-import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest
+import com.lambda.interaction.manager.managers.rotating.rotationRequest
 import com.lambda.util.BlockUtils.blockState
 import com.lambda.util.EntityUtils.getPositionsWithinHitboxXZ
 import com.lambda.util.PlaceDirection
diff --git a/src/main/kotlin/com/lambda/interaction/container/Container.kt b/src/main/kotlin/com/lambda/interaction/container/Container.kt
index 773aaa0e4..e0d6ad4db 100644
--- a/src/main/kotlin/com/lambda/interaction/container/Container.kt
+++ b/src/main/kotlin/com/lambda/interaction/container/Container.kt
@@ -28,8 +28,8 @@ import com.lambda.interaction.container.selection.StackAndSlot
 import com.lambda.interaction.container.selection.StackSelection
 import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.mutate
 import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder
-import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest
 import com.lambda.interaction.manager.managers.inventory.InventoryRequest
+import com.lambda.interaction.manager.managers.inventory.inventoryRequest
 import com.lambda.task.Task.Ta5kBuilder
 import com.lambda.util.Nameable
 import com.lambda.util.item.ItemStackUtils.count
diff --git a/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakManager.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakManager.kt
index eabc193ee..80da7a4bc 100644
--- a/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakManager.kt
+++ b/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakManager.kt
@@ -72,7 +72,7 @@ import com.lambda.interaction.manager.managers.breaking.BreakManager.updateBreak
 import com.lambda.interaction.manager.managers.breaking.BreakManager.updatePreProcessing
 import com.lambda.interaction.manager.managers.breaking.SwapInfo.Companion.getSwapInfo
 import com.lambda.interaction.manager.managers.hotbar.HotbarRequest
-import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest
+import com.lambda.interaction.manager.managers.hotbar.hotbarRequest
 import com.lambda.interaction.manager.managers.interacting.InteractManager
 import com.lambda.interaction.manager.managers.rotating.RotationRequest
 import com.lambda.threading.runGameScheduled
diff --git a/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakRequest.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakRequest.kt
index f52910054..9cf16c128 100644
--- a/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakRequest.kt
+++ b/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakRequest.kt
@@ -28,13 +28,16 @@ import com.lambda.interaction.construction.simulation.result.results.BreakResult
 import com.lambda.interaction.construction.simulation.sim
 import com.lambda.interaction.construction.verify.TargetState
 import com.lambda.interaction.manager.Request
-import com.lambda.interaction.manager.managers.breaking.BreakRequestBuilder.Companion.breakRequest
 import com.lambda.threading.runSafe
 import com.lambda.util.BlockUtils.blockState
 import com.lambda.util.BlockUtils.isEmpty
 import net.minecraft.entity.ItemEntity
 import net.minecraft.util.math.BlockPos
 
+@DslMarker
+annotation class BreakRequestMarker
+
+@BreakRequestMarker
 @JvmName("breakRequest1")
 fun AutomatedSafeContext.breakRequest(
 	positions: Collection,
@@ -46,6 +49,7 @@ fun AutomatedSafeContext.breakRequest(
 	.sim()
 	.breakRequest(pendingInteractions, nowOrNothing, builder)
 
+@BreakRequestMarker
 @JvmName("breakRequest2")
 context(automated: Automated)
 fun Collection.breakRequest(
@@ -55,6 +59,7 @@ fun Collection.breakRequest(
 ) = asSequence()
 	.breakRequest(pendingInteractions, nowOrNothing, builder)
 
+@BreakRequestMarker
 @JvmName("breakRequest3")
 context(automated: Automated)
 fun Sequence.breakRequest(
@@ -69,6 +74,17 @@ fun Sequence.breakRequest(
 	.takeIf { it.isNotEmpty() }
 	?.let { automated.breakRequest(it, pendingInteractions, nowOrNothing, builder) }
 
+@BreakRequestMarker
+@JvmName("breakRequest4")
+fun Automated.breakRequest(
+	contexts: Collection,
+	pendingInteractions: MutableCollection,
+	nowOrNothing: Boolean = false,
+	builder: (BreakRequestBuilder.() -> Unit)? = null
+) = BreakRequestBuilder(
+	contexts, pendingInteractions, nowOrNothing, this
+).apply { builder?.invoke(this) }.build()
+
 /**
  * Contains the information necessary for initializing and continuing breaks within the [BreakManager].
  *
@@ -111,11 +127,8 @@ data class BreakRequest(
 	}
 }
 
-@DslMarker
-annotation class BreakRequestMarker
-
 @BreakRequestMarker
-class BreakRequestBuilder private constructor(
+class BreakRequestBuilder internal constructor(
 	private val contexts: Collection,
 	private val pendingInteractions: MutableCollection,
 	private val nowOrNothing: Boolean,
@@ -157,7 +170,7 @@ class BreakRequestBuilder private constructor(
 		onReBreak = callback
 	}
 
-	private fun build() =
+	internal fun build() =
 		BreakRequest(
 			contexts,
 			pendingInteractions,
@@ -171,16 +184,4 @@ class BreakRequestBuilder private constructor(
 			onReBreakStart,
 			onReBreak
 		)
-
-	companion object {
-		@JvmName("breakRequest4")
-		fun Automated.breakRequest(
-			contexts: Collection,
-			pendingInteractions: MutableCollection,
-			nowOrNothing: Boolean = false,
-			builder: (BreakRequestBuilder.() -> Unit)? = null
-		) = BreakRequestBuilder(
-			contexts, pendingInteractions, nowOrNothing, this
-		).apply { builder?.invoke(this) }.build()
-	}
 }
diff --git a/src/main/kotlin/com/lambda/interaction/manager/managers/hotbar/HotbarRequest.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/hotbar/HotbarRequest.kt
index 4827ca8be..95845291c 100644
--- a/src/main/kotlin/com/lambda/interaction/manager/managers/hotbar/HotbarRequest.kt
+++ b/src/main/kotlin/com/lambda/interaction/manager/managers/hotbar/HotbarRequest.kt
@@ -20,6 +20,13 @@ package com.lambda.interaction.manager.managers.hotbar
 import com.lambda.context.Automated
 import com.lambda.interaction.manager.Request
 
+@DslMarker
+private annotation class HotbarRequestMarker
+
+@HotbarRequestMarker
+fun Automated.hotbarRequest(slot: Int, nowOrNothing: Boolean = false, builder: (HotbarRequestBuilder.() -> Unit)? = null) =
+	HotbarRequestBuilder(slot, nowOrNothing, this).apply { builder?.invoke(this) }.build()
+
 /**
  * A request to change the hotbar slot.
  * If you are having an issue with the slot not changing, try setting [nowOrNothing] to false.
@@ -58,12 +65,9 @@ class HotbarRequest(
 	}
 }
 
-@DslMarker
-private annotation class HotbarRequestMarker
-
 @Suppress("unused")
 @HotbarRequestMarker
-class HotbarRequestBuilder private constructor(
+class HotbarRequestBuilder internal constructor(
 	private val slot: Int,
 	private val nowOrNothing: Boolean,
 	private val automated: Automated,
@@ -79,10 +83,5 @@ class HotbarRequestBuilder private constructor(
 		this.swapPause = swapPause
 	}
 
-	private fun build() = HotbarRequest(slot, keepTicks, swapPause, nowOrNothing, automated)
-
-	companion object {
-		fun Automated.hotbarRequest(slot: Int, nowOrNothing: Boolean = false, builder: (HotbarRequestBuilder.() -> Unit)? = null) =
-			HotbarRequestBuilder(slot, nowOrNothing, this).apply { builder?.invoke(this) }.build()
-	}
+	internal fun build() = HotbarRequest(slot, keepTicks, swapPause, nowOrNothing, automated)
 }
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractManager.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractManager.kt
index 315281898..83a97b9f9 100644
--- a/src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractManager.kt
+++ b/src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractManager.kt
@@ -36,13 +36,13 @@ import com.lambda.interaction.manager.Manager
 import com.lambda.interaction.manager.ManagerUtils.isPosBlocked
 import com.lambda.interaction.manager.PositionBlocking
 import com.lambda.interaction.manager.managers.breaking.BreakManager
-import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest
+import com.lambda.interaction.manager.managers.hotbar.hotbarRequest
 import com.lambda.interaction.manager.managers.interacting.InteractManager.activeRequest
 import com.lambda.interaction.manager.managers.interacting.InteractManager.maxInteractionsThisTick
 import com.lambda.interaction.manager.managers.interacting.InteractManager.populateFrom
 import com.lambda.interaction.manager.managers.interacting.InteractManager.potentialInteractions
 import com.lambda.interaction.manager.managers.interacting.InteractManager.processRequest
-import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest
+import com.lambda.interaction.manager.managers.inventory.inventoryRequest
 import com.lambda.module.modules.world.AutoSign
 import com.lambda.threading.runConcurrent
 import com.lambda.threading.runSafeAutomated
diff --git a/src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractRequest.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractRequest.kt
index a0d9d7849..8f7f1289b 100644
--- a/src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractRequest.kt
+++ b/src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractRequest.kt
@@ -25,12 +25,15 @@ import com.lambda.interaction.construction.simulation.result.BuildResult
 import com.lambda.interaction.construction.simulation.result.Dependent
 import com.lambda.interaction.construction.simulation.result.results.InteractResult
 import com.lambda.interaction.manager.Request
-import com.lambda.interaction.manager.managers.interacting.PlaceRequestBuilder.Companion.interactRequest
 import com.lambda.threading.runSafe
 import com.lambda.util.BlockUtils.blockState
 import com.lambda.util.BlockUtils.matches
 import net.minecraft.util.math.BlockPos
 
+@DslMarker
+annotation class InteractRequestMarker
+
+@InteractRequestMarker
 @JvmName("interactRequest1")
 context(automated: Automated)
 fun Collection.interactRequest(
@@ -40,6 +43,7 @@ fun Collection.interactRequest(
 ) = asSequence()
 	.interactRequest(pendingInteractions, nowOrNothing, builder)
 
+@InteractRequestMarker
 @JvmName("interactRequest2")
 context(automated: Automated)
 fun Sequence.interactRequest(
@@ -54,6 +58,20 @@ fun Sequence.interactRequest(
 	.takeIf { it.isNotEmpty() }
 	?.let { automated.interactRequest(it, pendingInteractions, nowOrNothing, builder) }
 
+@InteractRequestMarker
+@JvmName("interactRequest3")
+fun Automated.interactRequest(
+	contexts: Collection,
+	pendingInteractions: MutableCollection,
+	nowOrNothing: Boolean = false,
+	builder: (PlaceRequestBuilder.() -> Unit)? = null
+) = PlaceRequestBuilder(
+	contexts,
+	pendingInteractions,
+	nowOrNothing,
+	this
+).apply { builder?.invoke(this) }.build()
+
 data class InteractRequest(
 	val contexts: Collection,
 	val pendingInteractions: MutableCollection,
@@ -79,11 +97,8 @@ data class InteractRequest(
 	}
 }
 
-@DslMarker
-annotation class InteractRequestMarker
-
 @InteractRequestMarker
-class PlaceRequestBuilder private constructor(
+class PlaceRequestBuilder internal constructor(
 	private val contexts: Collection,
 	private val pendingInteractions: MutableCollection,
 	private val nowOrNothing: Boolean,
@@ -95,7 +110,7 @@ class PlaceRequestBuilder private constructor(
 		onPlace = callback
 	}
 
-	private fun build() =
+	internal fun build() =
 		InteractRequest(
 			contexts,
 			pendingInteractions,
@@ -103,19 +118,4 @@ class PlaceRequestBuilder private constructor(
 			automated,
 			onPlace
 		)
-
-	companion object {
-		@JvmName("interactRequest3")
-		fun Automated.interactRequest(
-			contexts: Collection,
-			pendingInteractions: MutableCollection,
-			nowOrNothing: Boolean = false,
-			builder: (PlaceRequestBuilder.() -> Unit)? = null
-		) = PlaceRequestBuilder(
-			contexts,
-			pendingInteractions,
-			nowOrNothing,
-			this
-		).apply { builder?.invoke(this) }.build()
-	}
 }
diff --git a/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryRequest.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryRequest.kt
index 2ad88ed55..8efd447d9 100644
--- a/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryRequest.kt
+++ b/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryRequest.kt
@@ -32,6 +32,16 @@ import net.minecraft.util.Hand
 import net.minecraft.util.math.BlockPos
 import net.minecraft.util.math.Direction
 
+@DslMarker
+annotation class InvRequestDsl
+
+@InvRequestDsl
+fun Automated.inventoryRequest(
+	settleForLess: Boolean = false,
+	mustPerform: Boolean = false,
+	builder: InvRequestBuilder.() -> Unit
+) = InvRequestBuilder(settleForLess, mustPerform).apply(builder).build()
+
 /**
  * A private constructor is used to enforce use of the [InvRequestDsl] builder.
  *
@@ -62,12 +72,9 @@ class InventoryRequest(
 	}
 }
 
-@DslMarker
-annotation class InvRequestDsl
-
 @Suppress("unused")
 @InvRequestDsl
-class InvRequestBuilder private constructor(
+class InvRequestBuilder internal constructor(
 	private val settleForLess: Boolean,
 	private val mustPerform: Boolean
 ) {
@@ -170,12 +177,7 @@ class InvRequestBuilder private constructor(
 	}
 
 	context(automated: Automated)
-	private fun build() = InventoryRequest(actions, settleForLess, mustPerform, automated, onComplete = onComplete)
-
-	companion object {
-		fun Automated.inventoryRequest(settleForLess: Boolean = false, mustPerform: Boolean = false, builder: InvRequestBuilder.() -> Unit) =
-			InvRequestBuilder(settleForLess, mustPerform).apply(builder).build()
-	}
+	internal fun build() = InventoryRequest(actions, settleForLess, mustPerform, automated, onComplete = onComplete)
 }
 
 enum class InvRequestFailureReason {
diff --git a/src/main/kotlin/com/lambda/interaction/manager/managers/rotating/RotationRequest.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/rotating/RotationRequest.kt
index f7d234f6f..8b5f62b08 100644
--- a/src/main/kotlin/com/lambda/interaction/manager/managers/rotating/RotationRequest.kt
+++ b/src/main/kotlin/com/lambda/interaction/manager/managers/rotating/RotationRequest.kt
@@ -33,6 +33,13 @@ import com.lambda.util.collections.updatableLazy
 import kotlin.math.abs
 import kotlin.math.hypot
 
+@DslMarker
+annotation class RotationRequestMarker
+
+@RotationRequestMarker
+fun Automated.rotationRequest(builder: RotationRequestBuilder.() -> Unit) =
+	RotationRequestBuilder().apply(builder).build()
+
 abstract class RotationRequest(automated: Automated) : Request(), Automated by automated {
 	override val requestId = requestCount++
 	override val tickStageMask = automated.rotationConfig.tickStageMask
@@ -50,9 +57,6 @@ abstract class RotationRequest(automated: Automated) : Request(), Automated by a
 	}
 }
 
-@DslMarker
-annotation class RotationRequestMarker
-
 interface IRotationRequest : Automated {
 	var keepTicks: Int
 	var decayTicks: Int
@@ -151,7 +155,7 @@ interface IRotationRequest : Automated {
 }
 
 @RotationRequestMarker
-class RotationRequestBuilder private constructor() {
+class RotationRequestBuilder internal constructor() {
 	private var pitchBuilder: (SafeContext.() -> Double)? = null
 	private var yawBuilder: (SafeContext.() -> Double)? = null
 	private var rotationBuilder: (SafeContext.() -> Rotation)? = null
@@ -185,7 +189,7 @@ class RotationRequestBuilder private constructor() {
 	fun rotation(rotation: Rotation) { rotationBuilder = { rotation } }
 
 	context(automated: Automated)
-	private fun build(): RotationRequest {
+	internal fun build(): RotationRequest {
 		val yawBuilder = yawBuilder
 		val pitchBuilder = pitchBuilder
 		val rotationBuilder = rotationBuilder
@@ -197,9 +201,4 @@ class RotationRequestBuilder private constructor() {
 			else -> throw IllegalArgumentException("Must specify at least one rotation value to build a rotation request")
 		}
 	}
-
-	companion object {
-		fun Automated.rotationRequest(builder: RotationRequestBuilder.() -> Unit) =
-			RotationRequestBuilder().apply(builder).build()
-	}
 }
\ No newline at end of file
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
deleted file mode 100644
index e69de29bb..000000000
diff --git a/src/main/kotlin/com/lambda/interaction/material/container/containers/EnderChestContainer.kt b/src/main/kotlin/com/lambda/interaction/material/container/containers/EnderChestContainer.kt
deleted file mode 100644
index e69de29bb..000000000
diff --git a/src/main/kotlin/com/lambda/interaction/material/container/containers/ShulkerBoxContainer.kt b/src/main/kotlin/com/lambda/interaction/material/container/containers/ShulkerBoxContainer.kt
deleted file mode 100644
index e69de29bb..000000000
diff --git a/src/main/kotlin/com/lambda/module/modules/combat/AutoArmor.kt b/src/main/kotlin/com/lambda/module/modules/combat/AutoArmor.kt
index 9152e2d82..c5c9131ff 100644
--- a/src/main/kotlin/com/lambda/module/modules/combat/AutoArmor.kt
+++ b/src/main/kotlin/com/lambda/module/modules/combat/AutoArmor.kt
@@ -30,7 +30,7 @@ import com.lambda.interaction.container.selection.StackAndSlot
 import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection
 import com.lambda.interaction.container.selection.select
 import com.lambda.interaction.handler.handlers.findSlots
-import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest
+import com.lambda.interaction.manager.managers.inventory.inventoryRequest
 import com.lambda.module.Module
 import com.lambda.module.ModuleTag
 import com.lambda.util.EnchantmentUtils.getEnchantment
diff --git a/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt b/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt
index a72df33f1..187d75584 100644
--- a/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt
+++ b/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt
@@ -32,8 +32,8 @@ import com.lambda.interaction.container.containers.HotbarContainer
 import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection
 import com.lambda.interaction.container.selection.select
 import com.lambda.interaction.handler.handlers.findSlot
-import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest
-import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest
+import com.lambda.interaction.manager.managers.hotbar.hotbarRequest
+import com.lambda.interaction.manager.managers.rotating.rotationRequest
 import com.lambda.module.Module
 import com.lambda.module.ModuleTag
 import com.lambda.threading.runSafeAutomated
diff --git a/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/CrystalAura.kt b/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/CrystalAura.kt
index c0089b8dd..817d57925 100644
--- a/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/CrystalAura.kt
+++ b/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/CrystalAura.kt
@@ -87,7 +87,7 @@ object CrystalAura : Module(
     @Tab(PLACEMENT_TAB) private val placeRange by setting("Place Range", 4.6, 1.0..7.0, 0.1, "Range to place crystals", " blocks")
     @Tab(PLACEMENT_TAB) val placeDelay by setting("Place Delay", 50L, 0L..1000L, 1L, "Delay between placement attempts", " ms")
     @Tab(PLACEMENT_TAB) val swap by setting("Swap", true, "Swaps to crystals")
-    @Tab(PLACEMENT_TAB) val swapHand by setting("Swap Hand", Hand.MAIN_HAND, "Which hand to swap the crystal to") { swap }
+    @Tab(PLACEMENT_TAB) val swapHand: Hand by setting("Swap Hand", Hand.MAIN_HAND, "Which hand to swap the crystal to") { swap }
     @Tab(PLACEMENT_TAB) val priorityMode by setting("Crystal Priority", Priority.Damage)
     @Tab(PLACEMENT_TAB) private val minDamageAdvantage by setting("Min Damage Advantage", 4.0, 1.0..10.0, 0.5) { priorityMode == Priority.Advantage }
     @Tab(PLACEMENT_TAB) private val minTargetDamage by setting("Min Target Damage", 8.0, 0.0..20.0, 0.5, "Minimum target damage to use crystals")
diff --git a/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/Opportunity.kt b/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/Opportunity.kt
index ae68dc165..3743511c1 100644
--- a/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/Opportunity.kt
+++ b/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/Opportunity.kt
@@ -21,15 +21,13 @@ import com.lambda.Lambda
 import com.lambda.context.Automated
 import com.lambda.interaction.container.containers.HotbarContainer
 import com.lambda.interaction.container.containers.OffhandContainer
-import com.lambda.interaction.container.selection.StackSelection
 import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection
 import com.lambda.interaction.container.selection.select
 import com.lambda.interaction.handler.handlers.move
-import com.lambda.interaction.manager.managers.hotbar.HotbarRequest
-import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest
+import com.lambda.interaction.manager.managers.hotbar.hotbarRequest
 import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotationTo
 import com.lambda.interaction.manager.managers.rotating.RotationManager
-import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest
+import com.lambda.interaction.manager.managers.rotating.rotationRequest
 import com.lambda.module.modules.combat.crystalaura.CrystalAura.ActionType
 import com.lambda.module.modules.combat.crystalaura.CrystalAura.crystalPosition
 import com.lambda.module.modules.combat.crystalaura.CrystalAura.explodeDelay
@@ -51,7 +49,6 @@ import com.lambda.module.modules.combat.crystalaura.CrystalAura.swap
 import com.lambda.module.modules.combat.crystalaura.CrystalAura.swapHand
 import com.lambda.module.modules.combat.crystalaura.CrystalAuraExploding.explodeInternal
 import com.lambda.module.modules.combat.crystalaura.CrystalAuraPlacing.placeInternal
-import com.lambda.task.tasks.transfer
 import com.lambda.threading.runSafe
 import com.lambda.threading.runSafeAutomated
 import com.lambda.util.math.distSq
@@ -69,7 +66,6 @@ import net.minecraft.util.math.Direction
 import net.minecraft.util.math.Vec3d
 import kotlin.time.Duration.Companion.milliseconds
 
-
 /**
  * Represents the damage information resulting from placing an end crystal on a given [blockPos]
  * and causing an explosion that targets current target entity.
diff --git a/src/main/kotlin/com/lambda/module/modules/debug/RotationTest.kt b/src/main/kotlin/com/lambda/module/modules/debug/RotationTest.kt
index b5d63290b..7b32db1e1 100644
--- a/src/main/kotlin/com/lambda/module/modules/debug/RotationTest.kt
+++ b/src/main/kotlin/com/lambda/module/modules/debug/RotationTest.kt
@@ -19,7 +19,7 @@ package com.lambda.module.modules.debug
 
 import com.lambda.event.events.TickEvent
 import com.lambda.event.listener.SafeListener.Companion.listen
-import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest
+import com.lambda.interaction.manager.managers.rotating.rotationRequest
 import com.lambda.module.Module
 import com.lambda.module.ModuleTag
 import com.lambda.util.player.RotationUtils.lookAt
diff --git a/src/main/kotlin/com/lambda/module/modules/debug/SilentSwap.kt b/src/main/kotlin/com/lambda/module/modules/debug/SilentSwap.kt
index 9c3e8df60..0635f3380 100644
--- a/src/main/kotlin/com/lambda/module/modules/debug/SilentSwap.kt
+++ b/src/main/kotlin/com/lambda/module/modules/debug/SilentSwap.kt
@@ -22,11 +22,12 @@ import com.lambda.config.hideAllExcept
 import com.lambda.config.withEdits
 import com.lambda.event.events.PlayerEvent
 import com.lambda.event.listener.SafeListener.Companion.listen
-import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest
+import com.lambda.interaction.manager.managers.hotbar.hotbarRequest
 import com.lambda.module.Module
 import com.lambda.module.ModuleTag
 import com.lambda.util.CommunicationUtils.info
 
+@Suppress("unused")
 object SilentSwap : Module(
     name = "SilentSwap",
     description = "SilentSwap",
diff --git a/src/main/kotlin/com/lambda/module/modules/movement/AutoSpiral.kt b/src/main/kotlin/com/lambda/module/modules/movement/AutoSpiral.kt
index 15e4f463c..e417c56ca 100644
--- a/src/main/kotlin/com/lambda/module/modules/movement/AutoSpiral.kt
+++ b/src/main/kotlin/com/lambda/module/modules/movement/AutoSpiral.kt
@@ -22,7 +22,7 @@ import com.lambda.context.SafeContext
 import com.lambda.event.events.TickEvent
 import com.lambda.event.listener.SafeListener.Companion.listen
 import com.lambda.interaction.handler.handlers.BaritoneHandler
-import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest
+import com.lambda.interaction.manager.managers.rotating.rotationRequest
 import com.lambda.module.Module
 import com.lambda.module.ModuleTag
 import com.lambda.util.BlockPosIterators
diff --git a/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt b/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt
index b16111c5a..e2ae24ca0 100644
--- a/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt
+++ b/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt
@@ -36,8 +36,8 @@ import com.lambda.interaction.container.selection.selectContainers
 import com.lambda.interaction.handler.handlers.GlideHandler
 import com.lambda.interaction.handler.handlers.findSlot
 import com.lambda.interaction.handler.handlers.findStack
-import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest
-import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest
+import com.lambda.interaction.manager.managers.hotbar.hotbarRequest
+import com.lambda.interaction.manager.managers.inventory.inventoryRequest
 import com.lambda.module.Module
 import com.lambda.module.ModuleTag
 import com.lambda.threading.runSafe
diff --git a/src/main/kotlin/com/lambda/module/modules/movement/ElytraAltitudeControl.kt b/src/main/kotlin/com/lambda/module/modules/movement/ElytraAltitudeControl.kt
index 9f076b769..78bfb41f8 100644
--- a/src/main/kotlin/com/lambda/module/modules/movement/ElytraAltitudeControl.kt
+++ b/src/main/kotlin/com/lambda/module/modules/movement/ElytraAltitudeControl.kt
@@ -23,7 +23,7 @@ import com.lambda.config.hideAllExcept
 import com.lambda.config.withEdits
 import com.lambda.event.events.TickEvent
 import com.lambda.event.listener.SafeListener.Companion.listen
-import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest
+import com.lambda.interaction.manager.managers.rotating.rotationRequest
 import com.lambda.module.Module
 import com.lambda.module.ModuleTag
 import com.lambda.module.modules.movement.BetterFirework.startFirework
diff --git a/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt b/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt
index cd173de2b..da02e15a4 100644
--- a/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt
+++ b/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt
@@ -26,7 +26,7 @@ import com.lambda.event.events.ClientEvent
 import com.lambda.event.events.MovementEvent
 import com.lambda.event.events.TickEvent
 import com.lambda.event.listener.SafeListener.Companion.listen
-import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest
+import com.lambda.interaction.manager.managers.rotating.rotationRequest
 import com.lambda.module.Module
 import com.lambda.module.ModuleTag
 import com.lambda.util.NamedEnum
diff --git a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt
index 1ab2929e8..dffe5591a 100644
--- a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt
+++ b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt
@@ -30,10 +30,10 @@ import com.lambda.interaction.container.selection.select
 import com.lambda.interaction.handler.handlers.GlideHandler.ELYTRA_SELECTION
 import com.lambda.interaction.handler.handlers.findSlot
 import com.lambda.interaction.handler.handlers.findSlots
-import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest
+import com.lambda.interaction.manager.managers.hotbar.hotbarRequest
 import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder
-import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest
 import com.lambda.interaction.manager.managers.inventory.InventoryManager
+import com.lambda.interaction.manager.managers.inventory.inventoryRequest
 import com.lambda.module.modules.movement.elytrafly.ElytraFly.FlyMode
 import com.lambda.module.modules.movement.elytrafly.ElytraFly.fakeFly
 import com.lambda.threading.runSafe
diff --git a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/BounceElytraFly.kt b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/BounceElytraFly.kt
index b2a0a7143..93ad904cf 100644
--- a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/BounceElytraFly.kt
+++ b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/BounceElytraFly.kt
@@ -28,7 +28,7 @@ import com.lambda.event.listener.SafeListener.Companion.listen
 import com.lambda.interaction.handler.handlers.BaritoneHandler
 import com.lambda.interaction.handler.handlers.GlideHandler
 import com.lambda.interaction.manager.managers.rotating.RotationManager
-import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest
+import com.lambda.interaction.manager.managers.rotating.rotationRequest
 import com.lambda.module.hud.Speedometer
 import com.lambda.module.modules.movement.elytrafly.ElytraFly.FlyMode
 import com.lambda.module.modules.movement.elytrafly.ElytraFly.fakeFly
diff --git a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/GrimControlElytraFly.kt b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/GrimControlElytraFly.kt
index 974e5d6e9..8893b9e7d 100644
--- a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/GrimControlElytraFly.kt
+++ b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/GrimControlElytraFly.kt
@@ -27,7 +27,7 @@ import com.lambda.interaction.container.containers.InventoryContainer
 import com.lambda.interaction.container.selection.select
 import com.lambda.interaction.handler.handlers.findStack
 import com.lambda.interaction.manager.managers.rotating.RotationManager
-import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest
+import com.lambda.interaction.manager.managers.rotating.rotationRequest
 import com.lambda.module.modules.movement.BetterFirework.startFirework
 import com.lambda.module.modules.movement.elytrafly.ElytraFly
 import com.lambda.module.modules.movement.elytrafly.ElytraFly.FlyMode
diff --git a/src/main/kotlin/com/lambda/module/modules/player/AntiAim.kt b/src/main/kotlin/com/lambda/module/modules/player/AntiAim.kt
index 5fd9b717e..5b6cfca06 100644
--- a/src/main/kotlin/com/lambda/module/modules/player/AntiAim.kt
+++ b/src/main/kotlin/com/lambda/module/modules/player/AntiAim.kt
@@ -22,7 +22,7 @@ import com.lambda.context.SafeContext
 import com.lambda.event.events.TickEvent
 import com.lambda.event.listener.SafeListener.Companion.listen
 import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotationTo
-import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest
+import com.lambda.interaction.manager.managers.rotating.rotationRequest
 import com.lambda.module.Module
 import com.lambda.module.ModuleTag
 import com.lambda.util.math.distSq
diff --git a/src/main/kotlin/com/lambda/module/modules/player/EndermanLook.kt b/src/main/kotlin/com/lambda/module/modules/player/EndermanLook.kt
index 7aa51038d..7507a3f31 100644
--- a/src/main/kotlin/com/lambda/module/modules/player/EndermanLook.kt
+++ b/src/main/kotlin/com/lambda/module/modules/player/EndermanLook.kt
@@ -24,7 +24,7 @@ import com.lambda.context.SafeContext
 import com.lambda.event.events.TickEvent
 import com.lambda.event.listener.SafeListener.Companion.listen
 import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotationTo
-import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest
+import com.lambda.interaction.manager.managers.rotating.rotationRequest
 import com.lambda.module.Module
 import com.lambda.module.ModuleTag
 import com.lambda.util.math.distSq
diff --git a/src/main/kotlin/com/lambda/module/modules/player/InventoryMove.kt b/src/main/kotlin/com/lambda/module/modules/player/InventoryMove.kt
index 59f5d317c..d6aed9e9d 100644
--- a/src/main/kotlin/com/lambda/module/modules/player/InventoryMove.kt
+++ b/src/main/kotlin/com/lambda/module/modules/player/InventoryMove.kt
@@ -23,7 +23,7 @@ import com.lambda.event.events.TickEvent
 import com.lambda.event.listener.SafeListener.Companion.listen
 import com.lambda.gui.LambdaScreen
 import com.lambda.interaction.manager.managers.rotating.RotationMode
-import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest
+import com.lambda.interaction.manager.managers.rotating.rotationRequest
 import com.lambda.module.Module
 import com.lambda.module.ModuleTag
 import com.lambda.util.InputUtils
diff --git a/src/main/kotlin/com/lambda/module/modules/player/InventoryResync.kt b/src/main/kotlin/com/lambda/module/modules/player/InventoryResync.kt
index 3ce04fa89..8c2491110 100644
--- a/src/main/kotlin/com/lambda/module/modules/player/InventoryResync.kt
+++ b/src/main/kotlin/com/lambda/module/modules/player/InventoryResync.kt
@@ -19,7 +19,7 @@ package com.lambda.module.modules.player
 
 import com.lambda.event.events.TickEvent
 import com.lambda.event.listener.SafeListener.Companion.listen
-import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest
+import com.lambda.interaction.manager.managers.inventory.inventoryRequest
 import com.lambda.module.Module
 import com.lambda.module.ModuleTag
 import com.lambda.util.TickTimer
diff --git a/src/main/kotlin/com/lambda/module/modules/player/LineLock.kt b/src/main/kotlin/com/lambda/module/modules/player/LineLock.kt
index 40d78570d..2255713db 100644
--- a/src/main/kotlin/com/lambda/module/modules/player/LineLock.kt
+++ b/src/main/kotlin/com/lambda/module/modules/player/LineLock.kt
@@ -27,7 +27,7 @@ import com.lambda.event.events.TickEvent
 import com.lambda.event.listener.SafeListener.Companion.listen
 import com.lambda.graphics.mc.renderer.ImmediateRenderer.Companion.immediateRenderer
 import com.lambda.interaction.manager.managers.rotating.RotationMode
-import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest
+import com.lambda.interaction.manager.managers.rotating.rotationRequest
 import com.lambda.module.Module
 import com.lambda.module.ModuleTag
 import com.lambda.threading.runSafe
diff --git a/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt b/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt
index c5b7b9a13..bab9ddda0 100644
--- a/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt
+++ b/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt
@@ -37,7 +37,7 @@ import com.lambda.interaction.construction.simulation.context.BuildContext
 import com.lambda.interaction.construction.simulation.result.results.BreakResult
 import com.lambda.interaction.construction.simulation.sim
 import com.lambda.interaction.construction.verify.TargetState
-import com.lambda.interaction.manager.managers.breaking.BreakRequestBuilder.Companion.breakRequest
+import com.lambda.interaction.manager.managers.breaking.breakRequest
 import com.lambda.module.Module
 import com.lambda.module.ModuleTag
 import com.lambda.threading.runSafe
diff --git a/src/main/kotlin/com/lambda/module/modules/player/Replay.kt b/src/main/kotlin/com/lambda/module/modules/player/Replay.kt
index ba5b93e3e..5213b46d5 100644
--- a/src/main/kotlin/com/lambda/module/modules/player/Replay.kt
+++ b/src/main/kotlin/com/lambda/module/modules/player/Replay.kt
@@ -36,7 +36,7 @@ import com.lambda.gui.components.ClickGuiLayout
 import com.lambda.interaction.handler.handlers.TimerHandler
 import com.lambda.interaction.manager.managers.rotating.Rotation
 import com.lambda.interaction.manager.managers.rotating.RotationMode
-import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest
+import com.lambda.interaction.manager.managers.rotating.rotationRequest
 import com.lambda.module.Module
 import com.lambda.module.ModuleTag
 import com.lambda.module.modules.player.Replay.InputAction.Companion.toAction
diff --git a/src/main/kotlin/com/lambda/module/modules/player/RotationLock.kt b/src/main/kotlin/com/lambda/module/modules/player/RotationLock.kt
index 3fa739d4b..36e52a146 100644
--- a/src/main/kotlin/com/lambda/module/modules/player/RotationLock.kt
+++ b/src/main/kotlin/com/lambda/module/modules/player/RotationLock.kt
@@ -24,7 +24,7 @@ import com.lambda.config.withEdits
 import com.lambda.event.events.TickEvent
 import com.lambda.event.listener.SafeListener.Companion.listen
 import com.lambda.interaction.manager.managers.rotating.RotationMode
-import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest
+import com.lambda.interaction.manager.managers.rotating.rotationRequest
 import com.lambda.module.Module
 import com.lambda.module.ModuleTag
 import kotlin.math.roundToInt
diff --git a/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt b/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt
index 65534711d..94eb035aa 100644
--- a/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt
+++ b/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt
@@ -29,7 +29,7 @@ import com.lambda.interaction.container.containers.OffhandContainer
 import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection
 import com.lambda.interaction.container.selection.select
 import com.lambda.interaction.handler.handlers.findSlots
-import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest
+import com.lambda.interaction.manager.managers.inventory.inventoryRequest
 import com.lambda.module.Module
 import com.lambda.module.ModuleTag
 import net.minecraft.item.ItemStack
diff --git a/src/main/kotlin/com/lambda/module/modules/render/Freecam.kt b/src/main/kotlin/com/lambda/module/modules/render/Freecam.kt
index e26ad5c3e..d7076d2c8 100644
--- a/src/main/kotlin/com/lambda/module/modules/render/Freecam.kt
+++ b/src/main/kotlin/com/lambda/module/modules/render/Freecam.kt
@@ -33,7 +33,7 @@ import com.lambda.event.listener.SafeListener.Companion.listen
 import com.lambda.event.listener.UnsafeListener.Companion.listenUnsafe
 import com.lambda.interaction.manager.managers.rotating.Rotation
 import com.lambda.interaction.manager.managers.rotating.RotationMode
-import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest
+import com.lambda.interaction.manager.managers.rotating.rotationRequest
 import com.lambda.module.Module
 import com.lambda.module.ModuleTag
 import com.lambda.threading.runSafe
diff --git a/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt b/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt
index 8f1def9f0..f98aae24c 100644
--- a/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt
+++ b/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt
@@ -39,8 +39,8 @@ import com.lambda.interaction.container.selection.StackSelectionBuilder.Companio
 import com.lambda.interaction.container.selection.select
 import com.lambda.interaction.handler.handlers.BaritoneHandler
 import com.lambda.interaction.handler.handlers.findSlot
-import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest
-import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest
+import com.lambda.interaction.manager.managers.hotbar.hotbarRequest
+import com.lambda.interaction.manager.managers.inventory.inventoryRequest
 import com.lambda.module.Module
 import com.lambda.module.ModuleTag
 import com.lambda.module.modules.world.AutoPortal.PosHandler.currAnchorPos
diff --git a/src/main/kotlin/com/lambda/module/modules/world/AutoVillagerCycle.kt b/src/main/kotlin/com/lambda/module/modules/world/AutoVillagerCycle.kt
index 29bb84b6c..e99a9faa3 100644
--- a/src/main/kotlin/com/lambda/module/modules/world/AutoVillagerCycle.kt
+++ b/src/main/kotlin/com/lambda/module/modules/world/AutoVillagerCycle.kt
@@ -29,7 +29,7 @@ import com.lambda.event.listener.SafeListener.Companion.listen
 import com.lambda.interaction.construction.blueprint.Blueprint.Companion.toStructure
 import com.lambda.interaction.construction.blueprint.toBlueprint
 import com.lambda.interaction.construction.verify.TargetState
-import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest
+import com.lambda.interaction.manager.managers.rotating.rotationRequest
 import com.lambda.module.Module
 import com.lambda.module.ModuleTag
 import com.lambda.sound.SoundHandler.playSound
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 ef557c568..81d689a49 100644
--- a/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt
+++ b/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt
@@ -49,12 +49,12 @@ import com.lambda.interaction.handler.handlers.findSlot
 import com.lambda.interaction.handler.handlers.findSlots
 import com.lambda.interaction.handler.handlers.findStack
 import com.lambda.interaction.handler.handlers.findStacks
-import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest
-import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest
+import com.lambda.interaction.manager.managers.hotbar.hotbarRequest
+import com.lambda.interaction.manager.managers.inventory.inventoryRequest
 import com.lambda.interaction.manager.managers.rotating.Rotation
 import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.dist
 import com.lambda.interaction.manager.managers.rotating.RotationManager
-import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest
+import com.lambda.interaction.manager.managers.rotating.rotationRequest
 import com.lambda.module.Module
 import com.lambda.module.ModuleTag
 import com.lambda.task.Task
diff --git a/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt b/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt
index cdac5988e..da65473cb 100644
--- a/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt
+++ b/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt
@@ -28,7 +28,7 @@ import com.lambda.interaction.container.selection.StackSelectionBuilder.Companio
 import com.lambda.interaction.handler.handlers.BaritoneHandler
 import com.lambda.interaction.handler.handlers.findSlot
 import com.lambda.interaction.handler.handlers.findStack
-import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest
+import com.lambda.interaction.manager.managers.inventory.inventoryRequest
 import com.lambda.task.Task
 import com.lambda.task.Task.Ta5kBuilder
 import com.lambda.threading.runSafeAutomated
diff --git a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt
index 72deb6eea..aa50d5f15 100644
--- a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt
+++ b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt
@@ -38,8 +38,8 @@ import com.lambda.interaction.container.selection.selectContainers
 import com.lambda.interaction.handler.handlers.PacketLimitHandler.availablePackets
 import com.lambda.interaction.handler.handlers.PacketType
 import com.lambda.interaction.handler.handlers.findContainers
-import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder.Companion.inventoryRequest
 import com.lambda.interaction.manager.managers.inventory.InvRequestFailureReason
+import com.lambda.interaction.manager.managers.inventory.inventoryRequest
 import com.lambda.task.Task
 import com.lambda.task.Task.Ta5kBuilder
 import com.lambda.task.tasks.wrappers.taskOrNull
diff --git a/src/main/kotlin/com/lambda/task/tasks/EatTask.kt b/src/main/kotlin/com/lambda/task/tasks/EatTask.kt
index f2938289a..c9c5a00fb 100644
--- a/src/main/kotlin/com/lambda/task/tasks/EatTask.kt
+++ b/src/main/kotlin/com/lambda/task/tasks/EatTask.kt
@@ -27,7 +27,7 @@ import com.lambda.interaction.container.containers.HotbarContainer
 import com.lambda.interaction.container.containers.InventoryContainer
 import com.lambda.interaction.container.selection.select
 import com.lambda.interaction.handler.handlers.findSlot
-import com.lambda.interaction.manager.managers.hotbar.HotbarRequestBuilder.Companion.hotbarRequest
+import com.lambda.interaction.manager.managers.hotbar.hotbarRequest
 import com.lambda.task.Task
 import com.lambda.task.Task.Ta5kBuilder
 import com.lambda.threading.runSafeAutomated
diff --git a/src/main/kotlin/com/lambda/task/tasks/OpenContainerTask.kt b/src/main/kotlin/com/lambda/task/tasks/OpenContainerTask.kt
index be2dbea17..6594df70d 100644
--- a/src/main/kotlin/com/lambda/task/tasks/OpenContainerTask.kt
+++ b/src/main/kotlin/com/lambda/task/tasks/OpenContainerTask.kt
@@ -23,7 +23,7 @@ import com.lambda.event.events.InventoryEvent
 import com.lambda.event.events.TickEvent
 import com.lambda.event.listener.SafeListener.Companion.listen
 import com.lambda.interaction.handler.handlers.BaritoneHandler
-import com.lambda.interaction.manager.managers.rotating.RotationRequestBuilder.Companion.rotationRequest
+import com.lambda.interaction.manager.managers.rotating.rotationRequest
 import com.lambda.task.Task
 import com.lambda.task.Task.Ta5kBuilder
 import com.lambda.threading.runSafeAutomated

From 4b71ca6ef1fb3884d1155ea4402f0e0e6fc316ca Mon Sep 17 00:00:00 2001
From: beanbag44 <107891830+beanbag44@users.noreply.github.com>
Date: Wed, 7 Oct 2026 14:05:10 +0100
Subject: [PATCH 28/43] cleanup and perform as many moves in each tick as
 possible

---
 .../com/lambda/ContainerTransferGameTest.kt   |  4 +-
 .../command/commands/TransferCommand.kt       |  4 +-
 .../com/lambda/config/blocks/EatConfig.kt     |  2 +-
 .../lambda/config/blocks/InventoryConfig.kt   |  2 +-
 .../simulation/result/results/BreakResult.kt  |  2 +-
 .../construction/simulation/sims/BreakSim.kt  |  2 +-
 .../simulation/sims/InteractSim.kt            |  2 +-
 .../construction/verify/TargetState.kt        |  2 +-
 .../lambda/interaction/container/Container.kt |  2 +-
 .../container/ContainerSerializer.kt          | 51 +++---------
 .../external/ShulkerBoxContainer.kt           |  2 +-
 .../container/selection/ContainerSelection.kt | 29 +++----
 .../container/selection/StackSelection.kt     | 27 +++----
 .../handler/handlers/ContainerHandler.kt      |  6 +-
 .../handler/handlers/GlideHandler.kt          |  2 +-
 .../lambda/module/modules/combat/AutoArmor.kt |  2 +-
 .../lambda/module/modules/combat/KillAura.kt  |  2 +-
 .../module/modules/combat/PlayerTrap.kt       |  2 +-
 .../lambda/module/modules/combat/Surround.kt  |  2 +-
 .../modules/combat/crystalaura/Opportunity.kt |  2 +-
 .../modules/debug/ContainerTransferTest.kt    |  2 +-
 .../module/modules/movement/BetterFirework.kt |  2 +-
 .../module/modules/player/InventoryTweaks.kt  |  2 +-
 .../module/modules/player/StackReplenish.kt   |  2 +-
 .../lambda/module/modules/player/ToolSaver.kt |  2 +-
 .../lambda/module/modules/world/AutoPortal.kt |  2 +-
 .../lambda/module/modules/world/Scaffold.kt   |  2 +-
 .../lambda/module/modules/world/StashMover.kt |  2 +-
 .../com/lambda/task/tasks/CollectDropsTask.kt |  2 +-
 .../task/tasks/ContainerTransferTask.kt       | 79 ++++++++++++-------
 .../com/lambda/task/tasks/SlotByCacheTask.kt  | 40 ----------
 31 files changed, 121 insertions(+), 165 deletions(-)
 delete mode 100644 src/main/kotlin/com/lambda/task/tasks/SlotByCacheTask.kt

diff --git a/src/gametest/kotlin/com/lambda/ContainerTransferGameTest.kt b/src/gametest/kotlin/com/lambda/ContainerTransferGameTest.kt
index e51932277..67f5ca7bf 100644
--- a/src/gametest/kotlin/com/lambda/ContainerTransferGameTest.kt
+++ b/src/gametest/kotlin/com/lambda/ContainerTransferGameTest.kt
@@ -32,10 +32,10 @@ import com.lambda.interaction.container.containers.HotbarContainer
 import com.lambda.interaction.container.containers.InventoryContainer
 import com.lambda.interaction.container.containers.external.DoubleChestContainer
 import com.lambda.interaction.container.selection.ContainerSelection
-import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.containerSelection
 import com.lambda.interaction.container.selection.StackSelection
-import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection
+import com.lambda.interaction.container.selection.containerSelection
 import com.lambda.interaction.container.selection.select
+import com.lambda.interaction.container.selection.stackSelection
 import com.lambda.interaction.handler.handlers.ContainerSearchScope
 import com.lambda.task.RootTask
 import com.lambda.task.Task
diff --git a/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt b/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt
index 6eb985cf3..77c63a5a9 100644
--- a/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt
+++ b/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt
@@ -28,9 +28,9 @@ import com.lambda.brigadier.executeWithResult
 import com.lambda.brigadier.required
 import com.lambda.command.LambdaCommand
 import com.lambda.config.automation.AutomationConfig
-import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.containerSelection
-import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection
+import com.lambda.interaction.container.selection.containerSelection
 import com.lambda.interaction.container.selection.select
+import com.lambda.interaction.container.selection.stackSelection
 import com.lambda.interaction.handler.handlers.findContainers
 import com.lambda.task.Task
 import com.lambda.task.start
diff --git a/src/main/kotlin/com/lambda/config/blocks/EatConfig.kt b/src/main/kotlin/com/lambda/config/blocks/EatConfig.kt
index d727e4831..548540934 100644
--- a/src/main/kotlin/com/lambda/config/blocks/EatConfig.kt
+++ b/src/main/kotlin/com/lambda/config/blocks/EatConfig.kt
@@ -20,7 +20,7 @@ package com.lambda.config.blocks
 import com.lambda.context.Automated
 import com.lambda.context.AutomatedSafeContext
 import com.lambda.interaction.container.selection.StackAndSlot
-import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection
+import com.lambda.interaction.container.selection.stackSelection
 import com.lambda.threading.runSafe
 import com.lambda.util.Describable
 import com.lambda.util.NamedEnum
diff --git a/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt b/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt
index 6e65ca89d..fdca5ff79 100644
--- a/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt
+++ b/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt
@@ -20,7 +20,7 @@ package com.lambda.config.blocks
 import com.lambda.event.events.TickEvent
 import com.lambda.interaction.container.Container
 import com.lambda.interaction.container.selection.ContainerSelection
-import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.containerSelection
+import com.lambda.interaction.container.selection.containerSelection
 import com.lambda.util.Describable
 import com.lambda.util.NamedEnum
 import net.minecraft.item.Item
diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt
index 87bc4f532..ce2cb1f35 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt
@@ -32,8 +32,8 @@ import com.lambda.interaction.construction.simulation.result.Navigable
 import com.lambda.interaction.construction.simulation.result.Rank
 import com.lambda.interaction.construction.simulation.result.Resolvable
 import com.lambda.interaction.container.containers.HotbarContainer
-import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection
 import com.lambda.interaction.container.selection.select
+import com.lambda.interaction.container.selection.stackSelection
 import com.lambda.interaction.handler.handlers.BaritoneHandler
 import com.lambda.task.tasks.transfer
 import net.minecraft.block.BlockState
diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/BreakSim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/BreakSim.kt
index dc2ad232b..b04c5f475 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/BreakSim.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/BreakSim.kt
@@ -31,8 +31,8 @@ import com.lambda.interaction.construction.verify.TargetState
 import com.lambda.interaction.container.containers.HotbarContainer
 import com.lambda.interaction.container.selection.StackAndSlot
 import com.lambda.interaction.container.selection.StackSelection
-import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection
 import com.lambda.interaction.container.selection.select
+import com.lambda.interaction.container.selection.stackSelection
 import com.lambda.interaction.handler.handlers.findStack
 import com.lambda.interaction.manager.managers.hotbar.HotbarManager
 import com.lambda.interaction.manager.managers.rotating.RotationManager
diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/InteractSim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/InteractSim.kt
index 05f77e62a..9de638861 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/InteractSim.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/InteractSim.kt
@@ -28,8 +28,8 @@ import com.lambda.interaction.construction.simulation.result.results.InteractRes
 import com.lambda.interaction.construction.simulation.sim
 import com.lambda.interaction.construction.verify.TargetState
 import com.lambda.interaction.container.containers.HotbarContainer
-import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection
 import com.lambda.interaction.container.selection.select
+import com.lambda.interaction.container.selection.stackSelection
 import com.lambda.interaction.handler.handlers.findSlot
 import com.lambda.interaction.manager.managers.rotating.Rotation
 import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotation
diff --git a/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt b/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt
index e3e71183a..7f84e824b 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt
@@ -19,7 +19,7 @@ package com.lambda.interaction.construction.verify
 
 import com.lambda.context.AutomatedSafeContext
 import com.lambda.context.SafeContext
-import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection
+import com.lambda.interaction.container.selection.stackSelection
 import com.lambda.interaction.handler.handlers.findStack
 import com.lambda.util.BlockUtils.blockState
 import com.lambda.util.BlockUtils.emptyState
diff --git a/src/main/kotlin/com/lambda/interaction/container/Container.kt b/src/main/kotlin/com/lambda/interaction/container/Container.kt
index e0d6ad4db..5c6b8c52f 100644
--- a/src/main/kotlin/com/lambda/interaction/container/Container.kt
+++ b/src/main/kotlin/com/lambda/interaction/container/Container.kt
@@ -26,7 +26,7 @@ import com.lambda.interaction.container.containers.CursorContainer
 import com.lambda.interaction.container.containers.external.ShulkerBoxContainer
 import com.lambda.interaction.container.selection.StackAndSlot
 import com.lambda.interaction.container.selection.StackSelection
-import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.mutate
+import com.lambda.interaction.container.selection.mutate
 import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder
 import com.lambda.interaction.manager.managers.inventory.InventoryRequest
 import com.lambda.interaction.manager.managers.inventory.inventoryRequest
diff --git a/src/main/kotlin/com/lambda/interaction/container/ContainerSerializer.kt b/src/main/kotlin/com/lambda/interaction/container/ContainerSerializer.kt
index d53ce206e..723825673 100644
--- a/src/main/kotlin/com/lambda/interaction/container/ContainerSerializer.kt
+++ b/src/main/kotlin/com/lambda/interaction/container/ContainerSerializer.kt
@@ -28,18 +28,9 @@ import kotlin.io.path.deleteIfExists
 import kotlin.io.path.exists
 import kotlin.io.path.notExists
 
-/**
- * Handles saving and loading [PlacedContainer]s to/from disk as JSON files.
- *
- * Serialized containers are stored under [FolderRegistry.containers].
- */
 object ContainerSerializer {
 	private const val ENDER_CHEST_FILE = "ender_chest.json"
 
-	/**
-	 * A reusable sequence that lazily loads all serialized containers from disk.
-	 * Every iteration reads the files anew, preventing RAM exhaustion.
-	 */
 	val serializedContainers: Sequence =
 		sequence {
 			val dir = FolderRegistry.containers
@@ -52,9 +43,6 @@ object ContainerSerializer {
 			}
 		}
 
-	/**
-	 * Saves a [PlacedContainer] to disk as JSON.
-	 */
 	@Synchronized
 	fun saveContainer(container: PlacedContainer) {
 		val dir = ensureDirectory()
@@ -62,20 +50,25 @@ object ContainerSerializer {
 		mapper.writeValue(file, container)
 	}
 
-	/**
-	 * Saves the [EnderChestContainer] to disk. This is always loaded into memory
-	 * for the entire session.
-	 */
+	private fun readContainer(file: Path): PlacedContainer? =
+		runCatching {
+			mapper.readValue(file.toFile(), PlacedContainer::class.java)
+		}.onFailure {
+			LOG.warn("Failed to read container from ${file.fileName}", it)
+		}.getOrNull()
+
+	@Synchronized
+	fun removeContainer(pos: BlockPos) {
+		val dir = FolderRegistry.containers
+		dir.resolve(containerFileName(pos)).deleteIfExists()
+	}
+
 	fun saveEnderChest() {
 		val dir = ensureDirectory()
 		val file = dir.resolve(ENDER_CHEST_FILE).toFile()
 		mapper.writeValue(file, EnderChestContainer)
 	}
 
-	/**
-	 * Loads the [EnderChestContainer] contents from disk, if available.
-	 * Called on session start to restore the ender chest state.
-	 */
 	fun loadEnderChest() {
 		val file = FolderRegistry.containers.resolve(ENDER_CHEST_FILE)
 		if (!file.exists()) return
@@ -86,24 +79,6 @@ object ContainerSerializer {
 		}
 	}
 
-	/**
-	 * Removes a serialized container from disk.
-	 */
-	@Synchronized
-	fun removeContainer(pos: BlockPos) {
-		val dir = FolderRegistry.containers
-		dir.resolve(containerFileName(pos)).deleteIfExists()
-	}
-
-
-
-	private fun readContainer(file: Path): PlacedContainer? =
-		runCatching {
-			mapper.readValue(file.toFile(), PlacedContainer::class.java)
-		}.onFailure {
-			LOG.warn("Failed to read container from ${file.fileName}", it)
-		}.getOrNull()
-
 	private fun ensureDirectory() =
 		FolderRegistry.containers.also { if (it.notExists()) it.createDirectories() }
 
diff --git a/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt
index b0aeb2021..c5cff585b 100644
--- a/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt
+++ b/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt
@@ -28,8 +28,8 @@ import com.lambda.interaction.container.OpenContainerTask
 import com.lambda.interaction.container.OpenedContainerContext
 import com.lambda.interaction.container.containers.HotbarContainer
 import com.lambda.interaction.container.selection.ContainerSelection
-import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection
 import com.lambda.interaction.container.selection.select
+import com.lambda.interaction.container.selection.stackSelection
 import com.lambda.interaction.handler.handlers.ContainerSearchScope
 import com.lambda.interaction.handler.handlers.findSlot
 import com.lambda.task.Task.Ta5kBuilder
diff --git a/src/main/kotlin/com/lambda/interaction/container/selection/ContainerSelection.kt b/src/main/kotlin/com/lambda/interaction/container/selection/ContainerSelection.kt
index b893cc07d..a41b0ab68 100644
--- a/src/main/kotlin/com/lambda/interaction/container/selection/ContainerSelection.kt
+++ b/src/main/kotlin/com/lambda/interaction/container/selection/ContainerSelection.kt
@@ -23,7 +23,6 @@ import com.lambda.interaction.container.ContainerType
 import com.lambda.interaction.container.NestedContainer
 import com.lambda.interaction.container.containers.HotbarContainer
 import com.lambda.interaction.container.containers.InventoryContainer
-import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.containerSelection
 import com.lambda.interaction.handler.handlers.ContainerSearchScope
 
 @ContainerDslMarker
@@ -42,6 +41,16 @@ fun selectContainers(
     scope: ContainerSearchScope = ContainerSearchScope.Loaded
 ) = containerSelection(scope) { ofAny(*containers) }
 
+fun containerSelection(
+    scope: ContainerSearchScope = ContainerSearchScope.Loaded,
+    builder: ContainerSelectionBuilder.() -> Unit = {}
+) = ContainerSelectionBuilder(scope).apply(builder).build()
+
+fun ContainerSelection.mutate(
+    scope: ContainerSearchScope = this.scope,
+    builder: ContainerSelectionBuilder.() -> Unit = {}
+) = ContainerSelectionBuilder(this, scope).apply(builder).build()
+
 /**
  * ContainerSelection is a class that holds a predicate for matching containers.
  */
@@ -92,11 +101,11 @@ class ContainerSelection @ContainerDslMarker internal constructor(
 
 @Suppress("unused")
 @ContainerDslMarker
-class ContainerSelectionBuilder @ContainerDslMarker private constructor(
+class ContainerSelectionBuilder @ContainerDslMarker internal constructor(
     private val scope: ContainerSearchScope
 ) {
     @ContainerDslMarker
-    private constructor(
+    internal constructor(
         selection: ContainerSelection,
         scope: ContainerSearchScope
     ) : this(scope) {
@@ -224,7 +233,7 @@ class ContainerSelectionBuilder @ContainerDslMarker private constructor(
         this.selector = { currentSelector(it) && selector(it) }
     }
 
-    private fun build() =
+    internal fun build() =
         ContainerSelection(
             selector,
             scope,
@@ -237,18 +246,6 @@ class ContainerSelectionBuilder @ContainerDslMarker private constructor(
                 ?.thenBy { it.type }
                 ?: compareBy { it.type }
         )
-
-    companion object {
-        fun containerSelection(
-            scope: ContainerSearchScope = ContainerSearchScope.Loaded,
-            builder: ContainerSelectionBuilder.() -> Unit = {}
-        ) = ContainerSelectionBuilder(scope).apply(builder).build()
-
-        fun ContainerSelection.mutate(
-            scope: ContainerSearchScope = this.scope,
-            builder: ContainerSelectionBuilder.() -> Unit = {}
-        ) = ContainerSelectionBuilder(this, scope).apply(builder).build()
-    }
 }
 
 enum class AccessScope(val accessed: Boolean?) {
diff --git a/src/main/kotlin/com/lambda/interaction/container/selection/StackSelection.kt b/src/main/kotlin/com/lambda/interaction/container/selection/StackSelection.kt
index c5f6b4ebe..a95d7ce66 100644
--- a/src/main/kotlin/com/lambda/interaction/container/selection/StackSelection.kt
+++ b/src/main/kotlin/com/lambda/interaction/container/selection/StackSelection.kt
@@ -21,7 +21,6 @@ package com.lambda.interaction.container.selection
 
 import com.lambda.interaction.container.Container
 import com.lambda.interaction.container.ContainerDslMarker
-import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection
 import com.lambda.util.EnchantmentUtils.getEnchantment
 import com.lambda.util.item.ItemStackUtils.shulkerBoxStacks
 import com.lambda.util.item.ItemUtils
@@ -44,6 +43,16 @@ fun Item.select(count: Int = 1) = stackSelection(count) { isItem(this@select) }
 @ContainerDslMarker
 fun ItemStack.select(count: Int = 1) = stackSelection(count) { isItemStack(this@select) }
 
+fun stackSelection(
+	count: Int = 1,
+	builder: StackSelectionBuilder.() -> Unit = {}
+) = StackSelectionBuilder(count).apply(builder).build()
+
+fun StackSelection.mutate(
+	count: Int = this.count,
+	builder: StackSelectionBuilder.() -> Unit = {}
+) = StackSelectionBuilder(this, count).apply(builder).build()
+
 class StackSelection @ContainerDslMarker internal constructor(
 	val count: Int = 1,
 	val whitelistedSlots: Collection = emptyList(),
@@ -170,7 +179,7 @@ class StackSelection @ContainerDslMarker internal constructor(
 }
 
 @ContainerDslMarker
-class StackSelectionBuilder @ContainerDslMarker private constructor(
+class StackSelectionBuilder @ContainerDslMarker internal constructor(
 	private var count: Int = 1
 ) {
 	private val whitelistedSlots = mutableListOf()
@@ -182,7 +191,7 @@ class StackSelectionBuilder @ContainerDslMarker private constructor(
 	private var shulkerBoxScope: ShulkerBoxScope = ShulkerBoxScope.NotInShulkerBox
 
 	@ContainerDslMarker
-	private constructor(
+	internal constructor(
 		selection: StackSelection,
 		count: Int = selection.count
 	) : this(count) {
@@ -439,7 +448,7 @@ class StackSelectionBuilder @ContainerDslMarker private constructor(
 		}
 	}
 
-	private fun build() =
+	internal fun build() =
 		StackSelection(
 			count,
 			whitelistedSlots,
@@ -453,16 +462,6 @@ class StackSelectionBuilder @ContainerDslMarker private constructor(
 
 	companion object {
 		private val efficientToolCache = Collections.synchronizedMap(mutableMapOf())
-
-		fun stackSelection(
-			count: Int = 1,
-			builder: StackSelectionBuilder.() -> Unit = {}
-		) = StackSelectionBuilder(count).apply(builder).build()
-
-		fun StackSelection.mutate(
-			count: Int = this.count,
-			builder: StackSelectionBuilder.() -> Unit = {}
-		) = StackSelectionBuilder(this, count).apply(builder).build()
 	}
 }
 
diff --git a/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt
index 2b365476e..36c82b4c9 100644
--- a/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt
+++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt
@@ -44,10 +44,10 @@ import com.lambda.interaction.container.containers.external.EnderChestContainer
 import com.lambda.interaction.container.containers.external.PlacedShulkerBoxContainer
 import com.lambda.interaction.container.containers.external.ShulkerBoxContainer
 import com.lambda.interaction.container.selection.ContainerSelection
-import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.containerSelection
 import com.lambda.interaction.container.selection.StackSelection
-import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.mutate
-import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection
+import com.lambda.interaction.container.selection.containerSelection
+import com.lambda.interaction.container.selection.mutate
+import com.lambda.interaction.container.selection.stackSelection
 import com.lambda.util.BlockUtils.blockEntity
 import com.lambda.util.FolderRegistry
 import com.lambda.util.ReflectionUtils.getInstances
diff --git a/src/main/kotlin/com/lambda/interaction/handler/handlers/GlideHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/GlideHandler.kt
index 2220f1cd1..bf96fae00 100644
--- a/src/main/kotlin/com/lambda/interaction/handler/handlers/GlideHandler.kt
+++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/GlideHandler.kt
@@ -22,7 +22,7 @@ import com.lambda.event.events.TickEvent
 import com.lambda.event.listener.SafeListener.Companion.listen
 import com.lambda.interaction.container.selection.ContainerSelection
 import com.lambda.interaction.container.selection.StackAndSlot
-import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection
+import com.lambda.interaction.container.selection.stackSelection
 import com.lambda.module.modules.combat.AutoArmor
 import com.lambda.module.modules.movement.elytrafly.ElytraFly
 import com.lambda.module.modules.movement.elytrafly.ElytraFly.mode
diff --git a/src/main/kotlin/com/lambda/module/modules/combat/AutoArmor.kt b/src/main/kotlin/com/lambda/module/modules/combat/AutoArmor.kt
index c5c9131ff..94cd854d3 100644
--- a/src/main/kotlin/com/lambda/module/modules/combat/AutoArmor.kt
+++ b/src/main/kotlin/com/lambda/module/modules/combat/AutoArmor.kt
@@ -27,8 +27,8 @@ import com.lambda.event.listener.SafeListener.Companion.listen
 import com.lambda.interaction.container.containers.ArmorContainer
 import com.lambda.interaction.container.selection.ContainerSelection
 import com.lambda.interaction.container.selection.StackAndSlot
-import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection
 import com.lambda.interaction.container.selection.select
+import com.lambda.interaction.container.selection.stackSelection
 import com.lambda.interaction.handler.handlers.findSlots
 import com.lambda.interaction.manager.managers.inventory.inventoryRequest
 import com.lambda.module.Module
diff --git a/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt b/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt
index 187d75584..f205ff8fb 100644
--- a/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt
+++ b/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt
@@ -29,8 +29,8 @@ import com.lambda.event.events.InventoryEvent
 import com.lambda.event.events.TickEvent
 import com.lambda.event.listener.SafeListener.Companion.listen
 import com.lambda.interaction.container.containers.HotbarContainer
-import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection
 import com.lambda.interaction.container.selection.select
+import com.lambda.interaction.container.selection.stackSelection
 import com.lambda.interaction.handler.handlers.findSlot
 import com.lambda.interaction.manager.managers.hotbar.hotbarRequest
 import com.lambda.interaction.manager.managers.rotating.rotationRequest
diff --git a/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt b/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt
index 4a8b41d9a..aa5284317 100644
--- a/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt
+++ b/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt
@@ -25,7 +25,7 @@ import com.lambda.context.SafeContext
 import com.lambda.interaction.construction.blueprint.TickingBlueprint.Companion.tickingBlueprint
 import com.lambda.interaction.construction.verify.TargetState
 import com.lambda.interaction.container.selection.ContainerSelection
-import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection
+import com.lambda.interaction.container.selection.stackSelection
 import com.lambda.interaction.handler.handlers.FriendHandler.isFriend
 import com.lambda.interaction.handler.handlers.findStack
 import com.lambda.module.Module
diff --git a/src/main/kotlin/com/lambda/module/modules/combat/Surround.kt b/src/main/kotlin/com/lambda/module/modules/combat/Surround.kt
index 48d5eb31f..7316a1c89 100644
--- a/src/main/kotlin/com/lambda/module/modules/combat/Surround.kt
+++ b/src/main/kotlin/com/lambda/module/modules/combat/Surround.kt
@@ -24,7 +24,7 @@ import com.lambda.config.withEdits
 import com.lambda.interaction.construction.blueprint.TickingBlueprint.Companion.tickingBlueprint
 import com.lambda.interaction.construction.verify.TargetState
 import com.lambda.interaction.container.selection.ContainerSelection
-import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection
+import com.lambda.interaction.container.selection.stackSelection
 import com.lambda.interaction.handler.handlers.findStack
 import com.lambda.module.Module
 import com.lambda.module.ModuleTag
diff --git a/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/Opportunity.kt b/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/Opportunity.kt
index 3743511c1..a47a7e86a 100644
--- a/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/Opportunity.kt
+++ b/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/Opportunity.kt
@@ -21,8 +21,8 @@ import com.lambda.Lambda
 import com.lambda.context.Automated
 import com.lambda.interaction.container.containers.HotbarContainer
 import com.lambda.interaction.container.containers.OffhandContainer
-import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection
 import com.lambda.interaction.container.selection.select
+import com.lambda.interaction.container.selection.stackSelection
 import com.lambda.interaction.handler.handlers.move
 import com.lambda.interaction.manager.managers.hotbar.hotbarRequest
 import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotationTo
diff --git a/src/main/kotlin/com/lambda/module/modules/debug/ContainerTransferTest.kt b/src/main/kotlin/com/lambda/module/modules/debug/ContainerTransferTest.kt
index 80bb1ffec..44a1db0f2 100644
--- a/src/main/kotlin/com/lambda/module/modules/debug/ContainerTransferTest.kt
+++ b/src/main/kotlin/com/lambda/module/modules/debug/ContainerTransferTest.kt
@@ -23,7 +23,7 @@ import com.lambda.event.listener.SafeListener.Companion.listen
 import com.lambda.interaction.container.ContainerType
 import com.lambda.interaction.container.PlacedContainer
 import com.lambda.interaction.container.containers.external.DoubleChestContainer
-import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.containerSelection
+import com.lambda.interaction.container.selection.containerSelection
 import com.lambda.interaction.container.selection.select
 import com.lambda.interaction.handler.handlers.ContainerSearchScope
 import com.lambda.interaction.handler.handlers.findContainers
diff --git a/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt b/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt
index e2ae24ca0..4d0c86f9a 100644
--- a/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt
+++ b/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt
@@ -30,9 +30,9 @@ import com.lambda.event.listener.SafeListener.Companion.listen
 import com.lambda.interaction.container.containers.HotbarContainer
 import com.lambda.interaction.container.containers.InventoryContainer
 import com.lambda.interaction.container.containers.OffhandContainer
-import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection
 import com.lambda.interaction.container.selection.select
 import com.lambda.interaction.container.selection.selectContainers
+import com.lambda.interaction.container.selection.stackSelection
 import com.lambda.interaction.handler.handlers.GlideHandler
 import com.lambda.interaction.handler.handlers.findSlot
 import com.lambda.interaction.handler.handlers.findStack
diff --git a/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt b/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt
index b43257a5f..4daf34b4a 100644
--- a/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt
+++ b/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt
@@ -27,7 +27,7 @@ import com.lambda.interaction.container.ContainerType
 import com.lambda.interaction.container.NestedContainer
 import com.lambda.interaction.container.OpenedContainerContext
 import com.lambda.interaction.container.containers.external.EnderChestContainer
-import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.containerSelection
+import com.lambda.interaction.container.selection.containerSelection
 import com.lambda.interaction.handler.handlers.findContainer
 import com.lambda.module.Module
 import com.lambda.module.ModuleTag
diff --git a/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt b/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt
index 94eb035aa..45795c9e2 100644
--- a/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt
+++ b/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt
@@ -26,8 +26,8 @@ import com.lambda.event.listener.SafeListener.Companion.listen
 import com.lambda.interaction.container.containers.HotbarContainer
 import com.lambda.interaction.container.containers.InventoryContainer
 import com.lambda.interaction.container.containers.OffhandContainer
-import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection
 import com.lambda.interaction.container.selection.select
+import com.lambda.interaction.container.selection.stackSelection
 import com.lambda.interaction.handler.handlers.findSlots
 import com.lambda.interaction.manager.managers.inventory.inventoryRequest
 import com.lambda.module.Module
diff --git a/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt b/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt
index 1562a92ed..f9d0cb5cb 100644
--- a/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt
+++ b/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt
@@ -25,8 +25,8 @@ import com.lambda.event.events.TickEvent
 import com.lambda.event.listener.SafeListener.Companion.listen
 import com.lambda.interaction.container.containers.HotbarContainer
 import com.lambda.interaction.container.containers.InventoryContainer
-import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection
 import com.lambda.interaction.container.selection.select
+import com.lambda.interaction.container.selection.stackSelection
 import com.lambda.interaction.handler.handlers.findSlots
 import com.lambda.module.Module
 import com.lambda.module.ModuleTag
diff --git a/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt b/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt
index f98aae24c..e940f8a18 100644
--- a/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt
+++ b/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt
@@ -35,8 +35,8 @@ import com.lambda.graphics.util.DirectionMask
 import com.lambda.interaction.construction.verify.TargetState
 import com.lambda.interaction.container.containers.HotbarContainer
 import com.lambda.interaction.container.containers.InventoryContainer
-import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection
 import com.lambda.interaction.container.selection.select
+import com.lambda.interaction.container.selection.stackSelection
 import com.lambda.interaction.handler.handlers.BaritoneHandler
 import com.lambda.interaction.handler.handlers.findSlot
 import com.lambda.interaction.manager.managers.hotbar.hotbarRequest
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 15911be0f..d82106b60 100644
--- a/src/main/kotlin/com/lambda/module/modules/world/Scaffold.kt
+++ b/src/main/kotlin/com/lambda/module/modules/world/Scaffold.kt
@@ -31,8 +31,8 @@ import com.lambda.interaction.construction.simulation.context.BuildContext
 import com.lambda.interaction.construction.simulation.sim
 import com.lambda.interaction.construction.verify.TargetState
 import com.lambda.interaction.container.containers.HotbarContainer
-import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection
 import com.lambda.interaction.container.selection.select
+import com.lambda.interaction.container.selection.stackSelection
 import com.lambda.interaction.handler.handlers.findStack
 import com.lambda.interaction.manager.managers.interacting.interactRequest
 import com.lambda.module.Module
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 81d689a49..d431b53fc 100644
--- a/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt
+++ b/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt
@@ -42,8 +42,8 @@ import com.lambda.interaction.container.containers.InventoryContainer
 import com.lambda.interaction.container.containers.OffhandContainer
 import com.lambda.interaction.container.containers.external.EnderChestContainer
 import com.lambda.interaction.container.selection.ContainerSelection
-import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection
 import com.lambda.interaction.container.selection.select
+import com.lambda.interaction.container.selection.stackSelection
 import com.lambda.interaction.handler.handlers.BaritoneHandler
 import com.lambda.interaction.handler.handlers.findSlot
 import com.lambda.interaction.handler.handlers.findSlots
diff --git a/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt b/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt
index da65473cb..15dbea392 100644
--- a/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt
+++ b/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt
@@ -24,7 +24,7 @@ import com.lambda.context.SafeContext
 import com.lambda.event.events.TickEvent
 import com.lambda.event.listener.SafeListener.Companion.listen
 import com.lambda.interaction.container.selection.ContainerSelection
-import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection
+import com.lambda.interaction.container.selection.stackSelection
 import com.lambda.interaction.handler.handlers.BaritoneHandler
 import com.lambda.interaction.handler.handlers.findSlot
 import com.lambda.interaction.handler.handlers.findStack
diff --git a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt
index aa50d5f15..ec4c86b38 100644
--- a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt
+++ b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt
@@ -30,11 +30,10 @@ import com.lambda.interaction.container.containers.CursorContainer
 import com.lambda.interaction.container.containers.HotbarContainer
 import com.lambda.interaction.container.containers.InventoryContainer
 import com.lambda.interaction.container.selection.ContainerSelection
-import com.lambda.interaction.container.selection.ContainerSelectionBuilder.Companion.mutate
 import com.lambda.interaction.container.selection.StackSelection
-import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.mutate
-import com.lambda.interaction.container.selection.StackSelectionBuilder.Companion.stackSelection
+import com.lambda.interaction.container.selection.mutate
 import com.lambda.interaction.container.selection.selectContainers
+import com.lambda.interaction.container.selection.stackSelection
 import com.lambda.interaction.handler.handlers.PacketLimitHandler.availablePackets
 import com.lambda.interaction.handler.handlers.PacketType
 import com.lambda.interaction.handler.handlers.findContainers
@@ -328,17 +327,21 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 
 		init {
 			listen {
-				runSafeAutomated { tick() }
+				runSafeAutomated {
+					do {
+						val done = tick()
+					} while (!done)
+				}
 			}
 		}
 
-		private fun AutomatedSafeContext.tick() {
+		private fun AutomatedSafeContext.tick(): Boolean {
 			val currentLimit =
 				if (limit == Int.MAX_VALUE) Int.MAX_VALUE
 				else (limit - moved).coerceAtLeast(0)
 			if (currentLimit <= 0) {
 				finish()
-				return
+				return true
 			}
 
 			val currentSelection =
@@ -363,10 +366,10 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 
 			if (pair == null) {
 				finish()
-				return
+				return true
 			}
 
-			move(pair, atMost = currentLimit)
+			return move(pair, atMost = currentLimit)
 		}
 
 		private fun AutomatedSafeContext.findToSlot(fromSlot: Slot, toContainer: Container): Slot? {
@@ -386,33 +389,47 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 			return toContainer.findReplaceSlot(fromSlot.stack, toSelection)?.takeIf { fromSlot.canInsert(it.stack) }
 		}
 
-		private fun AutomatedSafeContext.move(pair: SlotPair, atMost: Int) {
+		private fun AutomatedSafeContext.move(pair: SlotPair, atMost: Int): Boolean {
 			val stack = pair.fromSlot.stack
 			val count = minOf(stack.count, atMost)
 			val cursor = CursorContainer.stacks.firstOrNull() ?: ItemStack.EMPTY
-			when {
+			return when {
 				pair.toSlot.stack.isForeignTo(stack) -> swapOut(pair)
 				count == stack.count && cursor.isEmpty && quickMoveStaysInside(pair.fromContainer, stack) -> quickMoveWhole(pair)
 				else -> clickThrough(pair, count)
 			}
 		}
 
-		private fun AutomatedSafeContext.swapOut(pair: SlotPair) {
+		private fun AutomatedSafeContext.swapOut(pair: SlotPair): Boolean {
 			val count = pair.fromSlot.stack.count
 			val request = pair.fromContainer.swapRequest(pair.fromSlot, pair.toSlot, pair.toContainer)?.submit()
-			when {
-				request == null -> finish()
-				!request.done -> finishUnlessTransient(request.failureReason)
+			return when {
+				request == null -> {
+					finish()
+					true
+				}
+				!request.done -> {
+					finishUnlessTransient(request.failureReason)
+					true
+				}
 				else -> {
 					recordMoved(count)
 					recordDestination(pair.toSlot, pair.toContainer)
+					false
 				}
 			}
 		}
 
-		private fun AutomatedSafeContext.quickMoveWhole(pair: SlotPair) {
-			if (availablePackets(PacketType.Inventory) < 1) return
-			if (ContainerEvent.Transfer(pair.fromSlot, pair.toSlot, pair.fromContainer, pair.toContainer).post().isCanceled()) return
+		private fun AutomatedSafeContext.quickMoveWhole(pair: SlotPair): Boolean {
+			if (availablePackets(PacketType.Inventory) < 1) return true
+			val transferEvent =
+				ContainerEvent.Transfer(
+					pair.fromSlot,
+					pair.toSlot,
+					pair.fromContainer,
+					pair.toContainer
+				)
+			if (transferEvent.post().isCanceled()) return true
 
 			val before = pair.fromSlot.stack.copy()
 			val destinationsBefore =
@@ -421,30 +438,31 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 			val request = inventoryRequest { quickMove(pair.fromSlot.id) }.submit()
 			if (!request.done) {
 				finishUnlessTransient(request.failureReason)
-				return
+				return true
 			}
 
 			val left = pair.fromSlot.stack.takeIf { it.sameItemAs(before) }?.count ?: 0
 			val count = before.count - left
 			if (count <= 0) {
 				finish()
-				return
+				return true
 			}
 			recordMoved(count)
 			if (trackResult) recordReceivingSlots(destinationsBefore)
+			return false
 		}
 
-		private fun AutomatedSafeContext.clickThrough(pair: SlotPair, count: Int) {
+		private fun AutomatedSafeContext.clickThrough(pair: SlotPair, count: Int): Boolean {
 			if (!CursorContainer.isAccessible) {
 				finish()
-				return
+				return true
 			}
 			val stack = pair.fromSlot.stack
-			val cursor = CursorContainer.stacks.firstOrNull() ?: return
+			val cursor = CursorContainer.stacks.firstOrNull() ?: return true
 			// A foreign stack on the cursor is somebody else's business; wait until it has been put away.
 			if (cursor.isForeignTo(stack)) {
 				finish()
-				return
+				return true
 			}
 
 			val budget = availablePackets(PacketType.Inventory)
@@ -459,22 +477,29 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 			if (plan == null) {
 				// Even an untouched budget cannot fit the shortest plan: this pair can never be served, give it up.
 				if (budget >= buildConfig.inventoryLimit) finish()
-				return
+				return true
 			}
 
-			val transferEvent = ContainerEvent.Transfer(pair.fromSlot, pair.toSlot, pair.fromContainer, pair.toContainer)
-			if (transferEvent.post().isCanceled()) return
+			val transferEvent =
+				ContainerEvent.Transfer(
+					pair.fromSlot,
+					pair.toSlot,
+					pair.fromContainer,
+					pair.toContainer
+				)
+			if (transferEvent.post().isCanceled()) return true
 			val request =
 				inventoryRequest {
 					plan.clicks.forEach { click -> pickup(pair[click.target].id, click.button.id) }
 				}.submit()
 			if (!request.done) {
 				finishUnlessTransient(request.failureReason)
-				return
+				return true
 			}
 
 			recordMoved(plan.destination - beforeTo)
 			recordDestination(pair.toSlot, pair.toContainer)
+			return false
 		}
 
 		private fun AutomatedSafeContext.quickMoveStaysInside(fromContainer: Container, stack: ItemStack): Boolean {
diff --git a/src/main/kotlin/com/lambda/task/tasks/SlotByCacheTask.kt b/src/main/kotlin/com/lambda/task/tasks/SlotByCacheTask.kt
deleted file mode 100644
index 867777ba5..000000000
--- a/src/main/kotlin/com/lambda/task/tasks/SlotByCacheTask.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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.task.tasks
-
-import com.lambda.context.SafeContext
-import com.lambda.task.Task
-import com.lambda.task.Task.Ta5kBuilder
-import com.lambda.util.player.SlotUtils.matches
-import net.minecraft.screen.slot.Slot
-
-@Ta5kBuilder
-fun slotByCache(slotCache: Slot) = SlotByCacheTask(slotCache)
-
-class SlotByCacheTask @Ta5kBuilder internal constructor(
-	val slotCache: Slot
-) : Task() {
-	override val name = "Finding slot by cache: $slotCache"
-
-	override fun SafeContext.onStart() {
-		val slots = player.currentScreenHandler.slots
-		val slot = slots.find { it matches slotCache }
-		if (slot != null) success(slot)
-		else failure("Failed to find matching slot by cache: $slotCache")
-	}
-}
\ No newline at end of file

From fdd02cb4d5ae3d27dc63ce1c1fb9158a36426a3b Mon Sep 17 00:00:00 2001
From: beanbag44 <107891830+beanbag44@users.noreply.github.com>
Date: Wed, 7 Oct 2026 21:13:13 +0100
Subject: [PATCH 29/43] hopefully fix lots of issues with inventory desync and
 also fix huge bug in fake fly implementation (?)

---
 .../ClientPlayInteractionManagerMixin.java    |  6 ++++
 .../managers/inventory/InventoryManager.kt    | 34 ++++++++++++++-----
 .../movement/elytrafly/ElytraFlyMode.kt       |  4 +--
 .../util/collections/LimitedDecayQueue.kt     | 17 ++++++++++
 4 files changed, 49 insertions(+), 12 deletions(-)

diff --git a/src/main/java/com/lambda/mixin/entity/ClientPlayInteractionManagerMixin.java b/src/main/java/com/lambda/mixin/entity/ClientPlayInteractionManagerMixin.java
index 717a10743..67b4c7ddb 100644
--- a/src/main/java/com/lambda/mixin/entity/ClientPlayInteractionManagerMixin.java
+++ b/src/main/java/com/lambda/mixin/entity/ClientPlayInteractionManagerMixin.java
@@ -23,6 +23,7 @@
 import com.lambda.interaction.handler.handlers.BaritoneHandler;
 import com.lambda.interaction.manager.managers.inventory.InventoryManager;
 import com.lambda.interaction.manager.managers.rotating.RotationManager;
+import com.lambda.module.modules.client.Client;
 import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
 import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
 import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
@@ -117,6 +118,11 @@ public void clickSlotTail(int syncId, int slotId, int button, SlotActionType act
         EventFlow.post(new PlayerEvent.SlotClick.Post(syncId, slotId, button, actionType, player.currentScreenHandler));
     }
 
+    @ModifyExpressionValue(method = "clickSlot", at = @At(value = "INVOKE", target = "Lnet/minecraft/screen/ScreenHandler;getRevision()I"))
+    private int modifyClickSlotRevision(int originalRevision) {
+        return Client.INSTANCE.getAvoidInventoryDesync() ? -1 : originalRevision;
+    }
+
     /**
      * Posts {@link InventoryEvent.HotbarSlot.Update} and returns the event value as the selected slot
      * 
{@code
diff --git a/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryManager.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryManager.kt
index a3dca3e98..26f11a7db 100644
--- a/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryManager.kt
+++ b/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryManager.kt
@@ -20,6 +20,7 @@ package com.lambda.interaction.manager.managers.inventory
 import com.lambda.context.AutomatedSafeContext
 import com.lambda.context.SafeContext
 import com.lambda.event.events.PacketEvent
+import com.lambda.event.events.PlayerEvent
 import com.lambda.event.events.TickEvent
 import com.lambda.event.listener.SafeListener.Companion.listen
 import com.lambda.interaction.handler.handlers.PacketLimitHandler.canSendPackets
@@ -81,6 +82,10 @@ object InventoryManager : Manager(
             actions = mutableListOf()
         }
 
+		listen {
+			if (Client.avoidInventoryDesync) indexInventoryChanges(isSlotClick = true)
+		}
+
 		listen { event ->
 			if (event.packet is CloseHandledScreenC2SPacket) {
 				screenHandler = player.playerScreenHandler
@@ -167,20 +172,31 @@ object InventoryManager : Manager(
 
 	/**
 	 * Detects changes in item stacks between now and the last time slots were cached and
-	 * adds them all to the [alteredSlots] collection where they can be compared to
+	 * adds them to the [alteredSlots] collection where they can be compared to
 	 * incoming [InventoryS2CPacket] and [ScreenHandlerSlotUpdateS2CPacket] packets to decide whether to
 	 * block certain updates.
+	 *
+	 * When [isSlotClick] is true, an inventory click packet ([ClickSlotC2SPacket]) was sent with a mismatched
+	 * revision (-1). The server will respond with both an [InventoryS2CPacket] and potentially a
+	 * [ScreenHandlerSlotUpdateS2CPacket], so each change is cached **twice** to allow both packets to consume
+	 * their corresponding entry.
+	 *
+	 * For non-click actions (e.g. placing blocks, picking up items, eating food, equipping via right-click),
+	 * the server only ever sends single slot updates, so each change is cached **once**.
 	 */
 	context(safeContext: SafeContext)
-	fun indexInventoryChanges() {
+	fun indexInventoryChanges(isSlotClick: Boolean = false) {
 		with(safeContext) {
 			if (player.currentScreenHandler.syncId != screenHandler?.syncId) return
 			val changes = screenHandler?.slots
-				?.filter { !it.stack.equal(slots[it.id]) }
-				?.map { InventoryChange(it.id, slots[it.id], it.stack.copy()) }
+				?.filter { !it.stack.equal(slots.getOrNull(it.id)) }
+				?.map { InventoryChange(it.id, slots.getOrNull(it.id) ?: ItemStack.EMPTY, it.stack.copy()) }
 				?: emptyList()
-			if (player.currentScreenHandler.syncId == 0) alteredPlayerSlots.addAll(changes)
-			else alteredSlots.addAll(changes)
+			val target =
+				if (player.currentScreenHandler.syncId == 0) alteredPlayerSlots
+				else alteredSlots
+			target.addAll(changes)
+			if (isSlotClick) target.addAll(changes)
 			slots = getStacks(player.currentScreenHandler.slots)
 		}
 	}
@@ -208,8 +224,8 @@ object InventoryManager : Manager(
 			val alteredContents = mutableListOf()
 			val alteredSlots = if (packet.syncId == 0) alteredPlayerSlots else alteredSlots
 			packet.contents.forEachIndexed { index, incomingStack ->
-				val matches = alteredSlots.removeIf { cached ->
-					incomingStack.equal(cached.after)
+				val matches = alteredSlots.removeFirstMatch { cached ->
+					cached.slotId == index && incomingStack.equal(cached.after)
 				}
 				if (matches) alteredContents.add(packetScreenHandler.slots[index].stack)
 				else alteredContents.add(incomingStack)
@@ -240,7 +256,7 @@ object InventoryManager : Manager(
 			} ?: false
 
 			val alteredSlots = if (packet.syncId == 0) alteredPlayerSlots else alteredSlots
-			val matches = alteredSlots.removeIf {
+			val matches = alteredSlots.removeFirstMatch {
 				it.slotId == packet.slot && it.after.equal(itemStack)
 			}
 
diff --git a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt
index dffe5591a..ee74cea47 100644
--- a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt
+++ b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt
@@ -32,7 +32,6 @@ import com.lambda.interaction.handler.handlers.findSlot
 import com.lambda.interaction.handler.handlers.findSlots
 import com.lambda.interaction.manager.managers.hotbar.hotbarRequest
 import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder
-import com.lambda.interaction.manager.managers.inventory.InventoryManager
 import com.lambda.interaction.manager.managers.inventory.inventoryRequest
 import com.lambda.module.modules.movement.elytrafly.ElytraFly.FlyMode
 import com.lambda.module.modules.movement.elytrafly.ElytraFly.fakeFly
@@ -112,8 +111,7 @@ abstract class ElytraFlyMode(
 
 		fun InvRequestBuilder.swapChest() {
 			if (elytraInHotbar) {
-				interaction.interactItem(player, Hand.MAIN_HAND)
-				InventoryManager.indexInventoryChanges()
+				action { interaction.interactItem(player, Hand.MAIN_HAND) }
 			} else {
 				moveSlot(elytraSlot.id, chestSlot.id)
 				if (!chestSlot.stack.isEmpty) pickup(elytraSlot.id)
diff --git a/src/main/kotlin/com/lambda/util/collections/LimitedDecayQueue.kt b/src/main/kotlin/com/lambda/util/collections/LimitedDecayQueue.kt
index 838b4f96d..a083c26d9 100644
--- a/src/main/kotlin/com/lambda/util/collections/LimitedDecayQueue.kt
+++ b/src/main/kotlin/com/lambda/util/collections/LimitedDecayQueue.kt
@@ -97,6 +97,23 @@ class LimitedDecayQueue(
         queue.clear()
     }
 
+    /**
+     * Removes the first element matching the [predicate] and returns `true`, or `false` if no match was found.
+     * Unlike [removeIf], this stops after the first removal so that duplicate entries are consumed one at a time.
+     */
+    @Synchronized
+    fun removeFirstMatch(predicate: (E) -> Boolean): Boolean {
+        cleanUp()
+        val iterator = queue.iterator()
+        while (iterator.hasNext()) {
+            if (predicate(iterator.next().first)) {
+                iterator.remove()
+                return true
+            }
+        }
+        return false
+    }
+
     /**
      * Updates the maximum allowed size for the queue and triggers a cleanup operation
      * to remove elements exceeding the new size or falling outside the allowed time interval.

From ff9c5a5fc4e6a8c2c8d9fbe4ad36f6ed8972860e Mon Sep 17 00:00:00 2001
From: beanbag44 <107891830+beanbag44@users.noreply.github.com>
Date: Fri, 9 Oct 2026 12:59:27 +0100
Subject: [PATCH 30/43] better anti desync

---
 .../com/lambda/ShulkerPickupGameTest.kt       | 276 ++++++++++++++++
 src/gametest/resources/fabric.mod.json        |   4 +
 .../container/OpenedContainerContext.kt       |   2 +-
 .../external/EnderChestContainer.kt           |   2 +-
 .../external/ShulkerBoxContainer.kt           |   2 +-
 .../managers/inventory/InventoryLedger.kt     | 137 ++++++++
 .../managers/inventory/InventoryManager.kt    | 162 +++++----
 .../managers/inventory/InventorySyncFilter.kt | 110 +++++++
 src/test/kotlin/InventoryLedgerTest.kt        | 307 ++++++++++++++++++
 src/test/kotlin/InventorySyncFilterTest.kt    | 134 ++++++++
 10 files changed, 1064 insertions(+), 72 deletions(-)
 create mode 100644 src/gametest/kotlin/com/lambda/ShulkerPickupGameTest.kt
 create mode 100644 src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryLedger.kt
 create mode 100644 src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventorySyncFilter.kt
 create mode 100644 src/test/kotlin/InventoryLedgerTest.kt
 create mode 100644 src/test/kotlin/InventorySyncFilterTest.kt

diff --git a/src/gametest/kotlin/com/lambda/ShulkerPickupGameTest.kt b/src/gametest/kotlin/com/lambda/ShulkerPickupGameTest.kt
new file mode 100644
index 000000000..8e5ff98d1
--- /dev/null
+++ b/src/gametest/kotlin/com/lambda/ShulkerPickupGameTest.kt
@@ -0,0 +1,276 @@
+/*
+ * 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 .
+ */
+
+@file:Suppress("unused")
+
+package com.lambda
+
+import com.lambda.Lambda.LOG
+import com.lambda.config.automation.AutomationConfig
+import com.lambda.context.Automated
+import com.lambda.interaction.container.ContainerType
+import com.lambda.interaction.container.PlacedContainer
+import com.lambda.interaction.container.selection.ContainerSelection
+import com.lambda.interaction.container.selection.containerSelection
+import com.lambda.interaction.container.selection.stackSelection
+import com.lambda.interaction.handler.handlers.ContainerSearchScope
+import com.lambda.task.RootTask
+import com.lambda.task.Task
+import com.lambda.task.start
+import com.lambda.task.tasks.openContainer
+import com.lambda.task.tasks.transfer
+import com.lambda.task.tasks.wrappers.thenAction
+import com.lambda.threading.runSafe
+import net.fabricmc.fabric.api.client.gametest.v1.FabricClientGameTest
+import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext
+import net.fabricmc.fabric.api.client.gametest.v1.context.TestServerContext
+import net.minecraft.block.entity.ChestBlockEntity
+import net.minecraft.client.gui.screen.world.WorldCreator
+import net.minecraft.item.ItemStack
+import net.minecraft.item.Items
+import net.minecraft.util.math.BlockPos
+
+/**
+ * In-game replication of the nested-shulker desync: pick up a shulker box, move it into a chest
+ * on another screen, then pick up an *identical* shulker box into the *same* slot.
+ *
+ * The second pickup is new information from the server and must show up client-side. It used to
+ * be dropped: the applied first pickup was re-recorded as a phantom prediction (the snapshot was
+ * never refreshed), and moving the first shulker away happened on the chest screen, so the
+ * phantom survived under the player screen. The identical second shulker then matched the phantom
+ * and its update was canceled, so the shulker never appeared and the task chain failed looking
+ * for it.
+ *
+ * A plain chest stands in for the ender chest here: what matters for the bug is only that the
+ * move-away happens on a different screen (different sync id) than the pickups.
+ */
+@Suppress("UnstableApiUsage")
+object ShulkerPickupGameTest : FabricClientGameTest {
+    private const val PLAYER = "Steve"
+    private const val TRANSFER_TIMEOUT = 20 * 40
+    private const val OPEN_TIMEOUT = 20 * 10
+    private const val PICKUP_TIMEOUT = 100
+
+    /** hotbar.0, i.e. mainStacks[0]: the only free slot, so both pickups land here. */
+    private const val SLOT = 0
+
+    private const val SHULKER_NBT =
+        "minecraft:white_shulker_box[container=[{slot:0,item:{id:\"minecraft:ender_chest\",count:1}}]]"
+
+    private val failures = mutableListOf()
+
+    override fun runTest(context: ClientGameTestContext) {
+        failures.clear()
+
+        val singleplayer = context.worldBuilder()
+            .adjustSettings { it.gameMode = WorldCreator.Mode.SURVIVAL }
+            .create()
+
+        try {
+            singleplayer.clientWorld.waitForChunksDownload()
+            runScenario(context, singleplayer.server)
+        } finally {
+            runCatching { context.runOnClient { RootTask.cancel() } }
+            runCatching { singleplayer.close() }
+        }
+
+        if (failures.isEmpty()) {
+            LOG.info("Shulker pickup game test passed")
+        } else {
+            LOG.error("Shulker pickup game test failed:")
+            failures.forEach { LOG.error("  FAIL $it") }
+        }
+        check(failures.isEmpty()) {
+            "${failures.size} shulker pickup expectations failed:\n" + failures.joinToString("\n") { "  - $it" }
+        }
+    }
+
+    private class Outcome(val state: Task.State, val error: String?, val ticks: Int) {
+        val succeeded get() = state == Task.State.Completed
+    }
+
+    private fun runScenario(context: ClientGameTestContext, server: TestServerContext) {
+        val auto: Automated = AutomationConfig.DEFAULT
+        val center = BlockPos(0, -60, 0)
+        val chestPos = BlockPos(2, -60, 0)
+
+        prepareWorld(context, server, center)
+        server.runCommand("/clear $PLAYER")
+        server.runCommand("/kill @e[type=item]")
+        server.runCommand("/setblock ${chestPos.x} ${chestPos.y} ${chestPos.z} minecraft:chest[facing=west]")
+
+        // Every player slot except hotbar.0 holds junk, so both shulkers land in the same slot.
+        repeat(8) { server.runCommand("/item replace entity $PLAYER hotbar.${it + 1} with minecraft:stone 64") }
+        repeat(27) { server.runCommand("/item replace entity $PLAYER inventory.$it with minecraft:stone 64") }
+
+        // Let Lambda learn the chest the way it does in normal play: open it once, close it again.
+        // closeHandledScreen (not closeScreen): the server must be told, or it keeps the chest open
+        // and every later server update carries the stale sync id and is ignored client-side.
+        val open = runTask(context, OPEN_TIMEOUT) {
+            with(auto) { openContainer(chestPos) }.thenAction { player.closeHandledScreen() }
+        }
+        if (!open.succeeded) {
+            fail("could not open the chest at $chestPos: ${open.state} ${open.error}")
+            return
+        }
+        context.waitFor({ it.currentScreen == null }, OPEN_TIMEOUT)
+        context.waitTicks(2)
+
+        // First shulker arrives server-side (same packet path as an item-entity pickup).
+        // The transfer below reads the client inventory, so wait until the client actually shows
+        // it: a wait only cures lateness, it can never mask a canceled update (those never arrive).
+        server.runCommand("/give $PLAYER $SHULKER_NBT 1")
+        waitForClientSlot(context, "first shulker")
+        context.waitTicks(3)
+        val first = serverPlayerStacks(server)[SLOT]
+        if (first.item != Items.WHITE_SHULKER_BOX) {
+            fail("setup broke: hotbar.0 holds $first instead of the first shulker")
+            return
+        }
+
+        // Move it into the chest through the real transfer task: real clicks on another screen,
+        // real full inventory answers from the server.
+        val chestSelection = containerSelection(ContainerSearchScope.All) {
+            ofAnyType(ContainerType.Chest)
+            predicate { it is PlacedContainer && it.pos == chestPos }
+        }
+        val moved = runTask(context, TRANSFER_TIMEOUT) {
+            with(auto) {
+                transfer(
+                    stackSelection { isItem(Items.WHITE_SHULKER_BOX) },
+                    ContainerSelection.HOTBAR_AND_INVENTORY,
+                    chestSelection
+                )
+            }
+        }
+        if (!moved.succeeded) {
+            val seen = context.computeOnClient, RuntimeException> {
+                it.player!!.inventory.mainStacks.take(9).map { s -> s.copy() }
+            }
+            fail("could not move the first shulker into the chest: ${moved.state} ${moved.error} (client hotbar shows $seen)")
+            return
+        }
+        context.waitFor({ it.currentScreen == null }, OPEN_TIMEOUT)
+        context.waitTicks(3)
+
+        val chestHasIt = chestStacks(server, chestPos).any { it.item == Items.WHITE_SHULKER_BOX }
+        val slotFreed = serverPlayerStacks(server)[SLOT].isEmpty
+        if (!chestHasIt || !slotFreed) {
+            fail("setup broke: after the move the chest holds a shulker=$chestHasIt and hotbar.0 is empty=$slotFreed")
+            return
+        }
+
+        // Second, identical shulker arrives into the same slot. This is the step that used to die:
+        // its update matched the phantom left behind by the first pickup and was canceled.
+        server.runCommand("/give $PLAYER $SHULKER_NBT 1")
+        waitForClientSlot(context, "second shulker")
+        context.waitTicks(5)
+
+        // The client must agree with the server everywhere, especially on that slot.
+        val serverStacks = serverPlayerStacks(server)
+        val clientStacks = clientPlayerStacks(context)
+        val desynced = clientStacks.indices.filter { !ItemStack.areEqual(clientStacks[it], serverStacks[it]) }
+        if (desynced.isNotEmpty()) {
+            fail(
+                "client and server disagree on player slots $desynced: " +
+                    "client=${desynced.map { clientStacks[it] }} server=${desynced.map { serverStacks[it] }}"
+            )
+        }
+        if (serverStacks[SLOT].item != Items.WHITE_SHULKER_BOX) {
+            fail("hotbar.0 holds ${serverStacks[SLOT]} server-side instead of the second shulker")
+        }
+        val chestShulkers = chestStacks(server, chestPos).count { it.item == Items.WHITE_SHULKER_BOX }
+        val playerShulkers = serverStacks.count { it.item == Items.WHITE_SHULKER_BOX }
+        if (chestShulkers != 1 || playerShulkers != 1) {
+            fail("shulker conservation broke: chest=$chestShulkers player=$playerShulkers, expected 1 and 1")
+        }
+    }
+
+    private fun runTask(
+        context: ClientGameTestContext,
+        timeout: Int,
+        build: () -> Task<*>
+    ): Outcome {
+        var task: Task<*>? = null
+        var error: String? = null
+        context.runOnClient {
+            runSafe {
+                task = build().onFailure { error = it.message }.start()
+            }
+        }
+        val started = task ?: return Outcome(Task.State.Failed, "no safe context to start the task", 0)
+        val ticks = try {
+            context.waitFor({
+                started.state != Task.State.Running && started.state != Task.State.Paused
+            }, timeout)
+        } catch (_: Throwable) {
+            timeout
+        }
+        return Outcome(started.state, error, ticks)
+    }
+
+    private fun waitForClientSlot(context: ClientGameTestContext, what: String) {
+        try {
+            context.waitFor({ mc ->
+                mc.player?.inventory?.mainStacks?.getOrNull(SLOT)?.let {
+                    !it.isEmpty && it.item == Items.WHITE_SHULKER_BOX
+                } == true
+            }, PICKUP_TIMEOUT)
+        } catch (_: Throwable) {
+            fail("the $what never appeared in hotbar.0 within $PICKUP_TIMEOUT ticks; ${dumpState(context)}")
+        }
+    }
+
+    private fun dumpState(context: ClientGameTestContext): String {
+        val client = runCatching { clientPlayerStacks(context).take(9) }.getOrNull() ?: listOf("n/a")
+        return "client hotbar=$client"
+    }
+
+    private fun prepareWorld(context: ClientGameTestContext, server: TestServerContext, center: BlockPos) {
+        server.runCommand("/gamemode survival $PLAYER")
+        server.runCommand("/difficulty peaceful")
+        server.runCommand("/time set noon")
+        server.runCommand("/weather clear")
+        server.runCommand("/fill ${center.x - 5} ${center.y - 1} ${center.z - 5} ${center.x + 5} ${center.y - 1} ${center.z + 5} minecraft:stone")
+        server.runCommand("/fill ${center.x - 5} ${center.y} ${center.z - 5} ${center.x + 5} ${center.y + 4} ${center.z + 5} minecraft:air")
+        server.runCommand("/tp $PLAYER ${center.x + 0.5} ${center.y} ${center.z + 0.5} 0 0")
+        context.waitTicks(10)
+    }
+
+    private fun serverPlayerStacks(server: TestServerContext): List =
+        server.computeOnServer, RuntimeException> { mc ->
+            mc.playerManager.playerList.first { it.gameProfile.name == PLAYER }
+                .inventory.mainStacks.map { it.copy() }
+        }
+
+    private fun clientPlayerStacks(context: ClientGameTestContext): List =
+        context.computeOnClient, RuntimeException> {
+            it.player!!.inventory.mainStacks.map { s -> s.copy() }
+        }
+
+    private fun chestStacks(server: TestServerContext, pos: BlockPos): List =
+        server.computeOnServer, RuntimeException> { mc ->
+            val entity = mc.overworld.getBlockEntity(pos) as? ChestBlockEntity
+            if (entity == null) emptyList()
+            else (0 until entity.size()).map { entity.getStack(it).copy() }
+        }
+
+    private fun fail(message: String) {
+        failures += message
+        LOG.warn("FAIL [shulker-pickup] $message")
+    }
+}
diff --git a/src/gametest/resources/fabric.mod.json b/src/gametest/resources/fabric.mod.json
index 06778c746..80f326a13 100644
--- a/src/gametest/resources/fabric.mod.json
+++ b/src/gametest/resources/fabric.mod.json
@@ -13,6 +13,10 @@
       {
         "adapter": "kotlin",
         "value": "com.lambda.ContainerTransferGameTest"
+      },
+      {
+        "adapter": "kotlin",
+        "value": "com.lambda.ShulkerPickupGameTest"
       }
     ]
   }
diff --git a/src/main/kotlin/com/lambda/interaction/container/OpenedContainerContext.kt b/src/main/kotlin/com/lambda/interaction/container/OpenedContainerContext.kt
index 4deafbcb0..dfb78544b 100644
--- a/src/main/kotlin/com/lambda/interaction/container/OpenedContainerContext.kt
+++ b/src/main/kotlin/com/lambda/interaction/container/OpenedContainerContext.kt
@@ -34,5 +34,5 @@ open class BasicOpenedContainerContext(
 	context(_: Automated)
 	final override fun close() =
 		if (!isAccessed()) null
-		else actionTask { player.closeScreen() }
+		else actionTask { player.closeHandledScreen() }
 }
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/interaction/container/containers/external/EnderChestContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/external/EnderChestContainer.kt
index da40583bb..1b7c3e587 100644
--- a/src/main/kotlin/com/lambda/interaction/container/containers/external/EnderChestContainer.kt
+++ b/src/main/kotlin/com/lambda/interaction/container/containers/external/EnderChestContainer.kt
@@ -102,7 +102,7 @@ object EnderChestContainer : Container(ContainerType.EnderChest), ExternalContai
 		context(_: Automated)
 		override fun close() =
 			taskOrNull {
-				if (isAccessed) actionTask { player.closeScreen() }
+				if (isAccessed) actionTask { player.closeHandledScreen() }
 				else null
 			}.thenOrNull {
 				if (blockPos != null) breakAndCollect(blockPos)
diff --git a/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt
index c5cff585b..e8df6d044 100644
--- a/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt
+++ b/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt
@@ -109,7 +109,7 @@ data class ShulkerBoxContainer(
         override fun close() =
             taskOrNull {
                 if (!isAccessed) null
-                else actionTask { player.closeScreen() }
+                else actionTask { player.closeHandledScreen() }
             }.then { breakAndCollect(blockPos) }
                 .then {
                     stackSelection {
diff --git a/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryLedger.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryLedger.kt
new file mode 100644
index 000000000..360bfdd90
--- /dev/null
+++ b/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryLedger.kt
@@ -0,0 +1,137 @@
+/*
+ * 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.manager.managers.inventory
+
+import kotlin.time.Clock
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Instant
+
+class InventoryLedger(
+    private val equal: (a: T, b: T) -> Boolean,
+    var maxAgeMs: Long = 1500L,
+    private val maxDepthPerSlot: Int = 64,
+    private val clock: () -> Instant = { Clock.System.now() }
+) {
+    data class Pending(val before: T, val after: T, val recordedAt: Instant)
+
+    enum class SingleDecision {
+        Drop,
+        Apply
+    }
+
+    companion object {
+        const val CURSOR_SLOT_ID = -1
+    }
+
+    private val queues = mutableMapOf>>>()
+
+    @Synchronized
+    fun record(syncId: Int, slotId: Int, before: T, after: T) {
+        if (equal(before, after)) return
+        cleanup()
+        val queue = queues.getOrPut(syncId) { mutableMapOf() }.getOrPut(slotId) { ArrayDeque() }
+        queue.addLast(Pending(before, after, clock()))
+        while (queue.size > maxDepthPerSlot) queue.removeFirst()
+    }
+
+    @Synchronized
+    fun decideSingle(syncId: Int, slotId: Int, incoming: T): SingleDecision {
+        cleanup()
+        val queue = queues[syncId]?.get(slotId) ?: return SingleDecision.Apply
+        val match = queue.indexOfFirst { equal(it.after, incoming) }
+        return if (match < 0) {
+            removeQueue(syncId, slotId)
+            SingleDecision.Apply
+        } else {
+            repeat(match + 1) { queue.removeFirst() }
+            if (queue.isEmpty()) removeQueue(syncId, slotId)
+            SingleDecision.Drop
+        }
+    }
+
+    @Synchronized
+    fun decideFull(syncId: Int, incoming: List, client: List): List {
+        if (incoming.size != client.size) return List(incoming.size) { true }
+        cleanup()
+        return incoming.indices.map { index ->
+            val queue = queues[syncId]?.get(index)
+            if (queue.isNullOrEmpty()) {
+                true
+            } else if (equal(incoming[index], client[index])) {
+                consumeFirstAfterMatch(queue, incoming[index])
+                if (queue.isEmpty()) removeQueue(syncId, index)
+                true
+            } else {
+                val match = queue.indexOfFirst { equal(it.after, incoming[index]) }
+                when {
+                    match >= 0 -> {
+                        consumeFirstAfterMatch(queue, incoming[index])
+                        if (queue.isEmpty()) removeQueue(syncId, index)
+                        false
+                    }
+                    queue.any { equal(it.before, incoming[index]) } -> false
+                    else -> {
+                        removeQueue(syncId, index)
+                        true
+                    }
+                }
+            }
+        }
+    }
+
+    @Synchronized
+    fun clear(syncId: Int) {
+        queues.remove(syncId)
+    }
+
+    @Synchronized
+    fun pendingCount(syncId: Int, slotId: Int): Int = queues[syncId]?.get(slotId)?.size ?: 0
+
+    @Synchronized
+    fun totalPending(): Int = queues.values.sumOf { perSync -> perSync.values.sumOf { it.size } }
+
+    private fun consumeFirstAfterMatch(queue: ArrayDeque>, value: T) {
+        val match = queue.indexOfFirst { equal(it.after, value) }
+        if (match >= 0) repeat(match + 1) { queue.removeFirst() }
+    }
+
+    private fun removeQueue(syncId: Int, slotId: Int) {
+        val perSync = queues[syncId] ?: return
+        perSync.remove(slotId)
+        if (perSync.isEmpty()) queues.remove(syncId)
+    }
+
+    private fun cleanup() {
+        if (maxAgeMs <= 0) {
+            queues.clear()
+            return
+        }
+        val cutoff = clock().minus(maxAgeMs.milliseconds)
+        val emptySyncs = mutableListOf()
+        queues.forEach { (syncId, perSync) ->
+            val emptySlots = mutableListOf()
+            perSync.forEach { (slotId, queue) ->
+                while (queue.isNotEmpty() && queue.first().recordedAt < cutoff) queue.removeFirst()
+                if (queue.isEmpty()) emptySlots += slotId
+            }
+            emptySlots.forEach { perSync.remove(it) }
+            if (perSync.isEmpty()) emptySyncs += syncId
+        }
+        emptySyncs.forEach { queues.remove(it) }
+    }
+}
diff --git a/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryManager.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryManager.kt
index 26f11a7db..b92c371cc 100644
--- a/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryManager.kt
+++ b/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryManager.kt
@@ -27,15 +27,10 @@ import com.lambda.interaction.handler.handlers.PacketLimitHandler.canSendPackets
 import com.lambda.interaction.handler.handlers.PacketLimitHandler.sentPackets
 import com.lambda.interaction.handler.handlers.PacketType
 import com.lambda.interaction.manager.Manager
-import com.lambda.interaction.manager.managers.inventory.InventoryManager.actions
-import com.lambda.interaction.manager.managers.inventory.InventoryManager.activeRequest
-import com.lambda.interaction.manager.managers.inventory.InventoryManager.alteredSlots
 import com.lambda.interaction.manager.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
 import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen
 import net.minecraft.item.ItemStack
@@ -44,11 +39,15 @@ import net.minecraft.network.packet.s2c.play.InventoryS2CPacket
 import net.minecraft.network.packet.s2c.play.ScreenHandlerSlotUpdateS2CPacket
 import net.minecraft.screen.PlayerScreenHandler
 import net.minecraft.screen.ScreenHandler
-import net.minecraft.screen.slot.Slot
 
 /**
  * Manager designed to handle inventory actions. One of the key features being the inventory change detection to
  * avoid accepting old information from the server in cases where ping is high. This helps to prevent desync.
+ *
+ * Every local prediction (clicks through this manager, plus anything else the client predicts such as block
+ * placement, which is picked up by the tick diff in [indexInventoryChanges]) is recorded in the [filter] as an
+ * ordered per-slot queue. Server answers that match a recording are echoes of our own actions and are dropped;
+ * anything else is new information and is applied. See [InventorySyncFilter] for the exact rules.
  */
 object InventoryManager : Manager(
 	1,
@@ -57,15 +56,18 @@ object InventoryManager : Manager(
 	private var activeRequest: InventoryRequest? = null
 	private var actions = mutableListOf()
 
-	private var slots = listOf()
-	private var alteredSlots = LimitedDecayQueue(Int.MAX_VALUE, Client.desyncTimeout * 50L)
-	private var alteredPlayerSlots = LimitedDecayQueue(Int.MAX_VALUE, Client.desyncTimeout * 50L)
+	private val filter =
+		InventorySyncFilter(
+			{ a, b -> ItemStack.areEqual(a, b) },
+			{ it.copy() },
+			Client.desyncTimeout * 50L
+		)
 
 	var screenHandler: ScreenHandler? = null
 		set(value) {
 			if (value != null) {
-				alteredSlots.clear()
-				slots = getStacks(value.slots)
+				if (value.syncId != 0) filter.clear(value.syncId)
+				filter.snapshot(value.syncId, value.slots.associate { it.id to it.stack }, value.cursorStack)
 			}
 			field = value
 		}
@@ -83,7 +85,7 @@ object InventoryManager : Manager(
         }
 
 		listen {
-			if (Client.avoidInventoryDesync) indexInventoryChanges(isSlotClick = true)
+			if (Client.avoidInventoryDesync) indexInventoryChanges()
 		}
 
 		listen { event ->
@@ -132,8 +134,7 @@ object InventoryManager : Manager(
 	private fun populateFrom(request: InventoryRequest) {
 		activeRequest = request
 		actions = request.actions.toMutableList()
-		alteredSlots.setDecayTime(Client.desyncTimeout * 50L)
-		alteredPlayerSlots.setDecayTime(Client.desyncTimeout * 50L)
+		filter.maxAgeMs = Client.desyncTimeout * 50L
 	}
 
 	/**
@@ -171,37 +172,37 @@ object InventoryManager : Manager(
 	}
 
 	/**
-	 * Detects changes in item stacks between now and the last time slots were cached and
-	 * adds them to the [alteredSlots] collection where they can be compared to
-	 * incoming [InventoryS2CPacket] and [ScreenHandlerSlotUpdateS2CPacket] packets to decide whether to
-	 * block certain updates.
+	 * Diffs the live screen against the last snapshot and records every changed slot in the [filter]
+	 * in the order it happened. This runs after every performed action and at the end of every tick,
+	 * so predictions made outside this manager (placing blocks, eating, picking items up) are recorded
+	 * too, as long as their server echo arrives later than the next recording point.
 	 *
-	 * When [isSlotClick] is true, an inventory click packet ([ClickSlotC2SPacket]) was sent with a mismatched
-	 * revision (-1). The server will respond with both an [InventoryS2CPacket] and potentially a
-	 * [ScreenHandlerSlotUpdateS2CPacket], so each change is cached **twice** to allow both packets to consume
-	 * their corresponding entry.
+	 * The cursor is tracked the same way: every click sends a mismatched revision (-1), so the server
+	 * answers with a full [InventoryS2CPacket] carrying the cursor, and with rapid clicks that cursor
+	 * is routinely stale by the time it arrives. Merging it like a slot keeps a resurrected cursor
+	 * from breaking the transfer tasks that read it every tick.
 	 *
-	 * For non-click actions (e.g. placing blocks, picking up items, eating food, equipping via right-click),
-	 * the server only ever sends single slot updates, so each change is cached **once**.
+	 * Note the click path sends a mismatched revision (-1), so the server answers a click with a full
+	 * [InventoryS2CPacket]; that packet is merged per slot in [onInventoryUpdate], which is why each
+	 * change is recorded exactly once here.
 	 */
-	context(safeContext: SafeContext)
-	fun indexInventoryChanges(isSlotClick: Boolean = false) {
-		with(safeContext) {
-			if (player.currentScreenHandler.syncId != screenHandler?.syncId) return
-			val changes = screenHandler?.slots
-				?.filter { !it.stack.equal(slots.getOrNull(it.id)) }
-				?.map { InventoryChange(it.id, slots.getOrNull(it.id) ?: ItemStack.EMPTY, it.stack.copy()) }
-				?: emptyList()
-			val target =
-				if (player.currentScreenHandler.syncId == 0) alteredPlayerSlots
-				else alteredSlots
-			target.addAll(changes)
-			if (isSlotClick) target.addAll(changes)
-			slots = getStacks(player.currentScreenHandler.slots)
-		}
+	fun SafeContext.indexInventoryChanges() {
+		val current = player.currentScreenHandler
+		if (current.syncId != screenHandler?.syncId) return
+		filter.observeLocal(
+			current.syncId,
+			current.slots.associate { it.id to it.stack },
+			current.cursorStack
+		)
 	}
 
-	private fun getStacks(slots: Collection) = slots.map { it.stack.copy() }
+	private fun snapshotHandler(handler: ScreenHandler) {
+		filter.snapshot(
+			handler.syncId,
+			handler.slots.associate { it.id to it.stack },
+			handler.cursorStack
+		)
+	}
 
 	/**
 	 * A modified version of the minecraft onInventory method
@@ -209,28 +210,44 @@ object InventoryManager : Manager(
 	 * @see net.minecraft.client.network.ClientPlayNetworkHandler.onInventory
 	 */
 	@JvmStatic
-	fun onInventoryUpdate(packet: InventoryS2CPacket, original: Operation){
+	fun onInventoryUpdate(packet: InventoryS2CPacket, original: Operation) {
 		runSafe {
-			if (!mc.isOnThread || !Client.avoidInventoryDesync) {
-				original.call(packet)
-				return
-			}
 			val packetScreenHandler =
 				when (packet.syncId) {
 					0 -> player.playerScreenHandler
 					player.currentScreenHandler.syncId -> player.currentScreenHandler
-					else -> return@runSafe
-				}
-			val alteredContents = mutableListOf()
-			val alteredSlots = if (packet.syncId == 0) alteredPlayerSlots else alteredSlots
-			packet.contents.forEachIndexed { index, incomingStack ->
-				val matches = alteredSlots.removeFirstMatch { cached ->
-					cached.slotId == index && incomingStack.equal(cached.after)
+					else -> {
+						original.call(packet)
+						return
+					}
 				}
-				if (matches) alteredContents.add(packetScreenHandler.slots[index].stack)
-				else alteredContents.add(incomingStack)
+			if (!mc.isOnThread || !Client.avoidInventoryDesync) {
+				original.call(packet)
+				snapshotHandler(packetScreenHandler)
+				return
 			}
-			packetScreenHandler.updateSlotStacks(packet.revision(), alteredContents, packet.cursorStack())
+			val clientStacks = packetScreenHandler.slots.map { it.stack }
+			if (clientStacks.size != packet.contents.size) {
+				original.call(packet)
+				snapshotHandler(packetScreenHandler)
+				return
+			}
+			val result =
+				filter.onFull(
+					packet.syncId,
+					packet.contents,
+					clientStacks,
+					packet.cursorStack(),
+					packetScreenHandler.cursorStack
+				)
+			val merged =
+				packet.contents.mapIndexed { index, incomingStack ->
+					if (result.apply[index]) incomingStack else clientStacks[index]
+				}
+			val mergedCursor =
+				if (result.applyCursor) packet.cursorStack()
+				else packetScreenHandler.cursorStack
+			packetScreenHandler.updateSlotStacks(packet.revision(), merged, mergedCursor)
 			return
 		}
 		original.call(packet)
@@ -244,21 +261,34 @@ object InventoryManager : Manager(
 	@JvmStatic
 	fun onSlotUpdate(packet: ScreenHandlerSlotUpdateS2CPacket, original: Operation) {
 		runSafe {
+			val itemStack = packet.stack
+			mc.tutorialManager.onSlotUpdate(itemStack)
+
+			val packetScreenHandler =
+				when (packet.syncId) {
+					0 -> player.playerScreenHandler
+					player.currentScreenHandler.syncId -> player.currentScreenHandler
+					else -> {
+						original.call(packet)
+						return
+					}
+				}
+			val current = packetScreenHandler.slots.getOrNull(packet.slot)?.stack
+			if (current == null) {
+				original.call(packet)
+				return
+			}
 			if (!mc.isOnThread || !Client.avoidInventoryDesync) {
 				original.call(packet)
+				snapshotHandler(packetScreenHandler)
 				return
 			}
-			val itemStack = packet.stack
-			mc.tutorialManager.onSlotUpdate(itemStack)
 
 			val bl = (mc.currentScreen as? CreativeInventoryScreen)?.let {
 				!it.isInventoryTabSelected
 			} ?: false
 
-			val alteredSlots = if (packet.syncId == 0) alteredPlayerSlots else alteredSlots
-			val matches = alteredSlots.removeFirstMatch {
-				it.slotId == packet.slot && it.after.equal(itemStack)
-			}
+			val apply = filter.onSingle(packet.syncId, packet.slot, itemStack, current)
 
 			if (packet.syncId == 0) {
 				if (PlayerScreenHandler.isInHotbar(packet.slot) && !itemStack.isEmpty) {
@@ -268,10 +298,10 @@ object InventoryManager : Manager(
 					}
 				}
 
-				if (matches) player.playerScreenHandler.revision = packet.revision
+				if (!apply) player.playerScreenHandler.revision = packet.revision
 				else player.playerScreenHandler.setStackInSlot(packet.slot, packet.revision, itemStack)
 			} else if (packet.syncId == player.currentScreenHandler.syncId && (packet.syncId != 0 || !bl)) {
-				if (matches) player.currentScreenHandler.revision = packet.revision
+				if (!apply) player.currentScreenHandler.revision = packet.revision
 				else player.currentScreenHandler.setStackInSlot(packet.slot, packet.revision, itemStack)
 			}
 
@@ -288,10 +318,4 @@ object InventoryManager : Manager(
 	fun onSetScreenHandler(screenHandler: ScreenHandler) {
 		this.screenHandler = screenHandler
 	}
-
-	private data class InventoryChange(
-		val slotId: Int,
-		val before: ItemStack,
-		val after: ItemStack
-	)
 }
diff --git a/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventorySyncFilter.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventorySyncFilter.kt
new file mode 100644
index 000000000..d2abd8653
--- /dev/null
+++ b/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventorySyncFilter.kt
@@ -0,0 +1,110 @@
+/*
+ * 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.manager.managers.inventory
+
+import com.lambda.interaction.manager.managers.inventory.InventoryLedger.SingleDecision.Drop
+import kotlin.time.Clock
+import kotlin.time.Instant
+
+class InventorySyncFilter(
+    private val equal: (a: T, b: T) -> Boolean,
+    private val copy: (T) -> T,
+    maxAgeMs: Long = 1500L,
+    clock: () -> Instant = { Clock.System.now() }
+) {
+    private val ledger = InventoryLedger(equal, maxAgeMs, clock = clock)
+    private val slotSnapshots = mutableMapOf>()
+    private val cursorSnapshots = mutableMapOf()
+
+    var maxAgeMs: Long
+        get() = ledger.maxAgeMs
+        set(value) {
+            ledger.maxAgeMs = value
+        }
+
+    data class FullResult(val apply: List, val applyCursor: Boolean)
+
+    @Synchronized
+    fun snapshot(syncId: Int, slots: Map, cursor: T) {
+        slotSnapshots[syncId] = slots.mapValuesTo(HashMap()) { copy(it.value) }
+        cursorSnapshots[syncId] = copy(cursor)
+    }
+
+    @Synchronized
+    fun observeLocal(syncId: Int, slots: Map, cursor: T) {
+        val snap = slotSnapshots[syncId]
+        if (snap == null) {
+            slotSnapshots[syncId] = slots.mapValuesTo(HashMap()) { copy(it.value) }
+            cursorSnapshots[syncId] = copy(cursor)
+            return
+        }
+        slots.forEach { (slotId, live) ->
+            val old = snap[slotId]
+            if (old == null) snap[slotId] = copy(live)
+            else if (!equal(old, live)) {
+                ledger.record(syncId, slotId, old, copy(live))
+                snap[slotId] = copy(live)
+            }
+        }
+        snap.keys.retainAll(slots.keys)
+        val oldCursor = cursorSnapshots[syncId]
+        if (oldCursor == null) cursorSnapshots[syncId] = copy(cursor)
+        else if (!equal(oldCursor, cursor)) {
+            ledger.record(syncId, InventoryLedger.CURSOR_SLOT_ID, oldCursor, copy(cursor))
+            cursorSnapshots[syncId] = copy(cursor)
+        }
+    }
+
+    @Synchronized
+    fun onSingle(syncId: Int, slotId: Int, incoming: T, current: T): Boolean {
+        val drop = ledger.decideSingle(syncId, slotId, incoming) == Drop
+        slotSnapshots[syncId]?.let { snap ->
+            if (snap.containsKey(slotId)) snap[slotId] = copy(if (drop) current else incoming)
+        }
+        return !drop
+    }
+
+    @Synchronized
+    fun onFull(
+        syncId: Int,
+        incoming: List,
+        client: List,
+        incomingCursor: T,
+        clientCursor: T,
+    ): FullResult {
+        val apply = ledger.decideFull(syncId, incoming, client)
+        val applyCursor = ledger.decideSingle(syncId, InventoryLedger.CURSOR_SLOT_ID, incomingCursor) != Drop
+        val merged = incoming.indices.map { copy(if (apply[it]) incoming[it] else client[it]) }
+        val snap = HashMap(merged.size)
+        merged.forEachIndexed { index, stack -> snap[index] = stack }
+        slotSnapshots[syncId] = snap
+        cursorSnapshots[syncId] = copy(if (applyCursor) incomingCursor else clientCursor)
+        return FullResult(apply, applyCursor)
+    }
+
+    @Synchronized
+    fun clear(syncId: Int) {
+        ledger.clear(syncId)
+    }
+
+    @Synchronized
+    fun pendingCount(syncId: Int, slotId: Int): Int = ledger.pendingCount(syncId, slotId)
+
+    @Synchronized
+    fun totalPending(): Int = ledger.totalPending()
+}
diff --git a/src/test/kotlin/InventoryLedgerTest.kt b/src/test/kotlin/InventoryLedgerTest.kt
new file mode 100644
index 000000000..e8b224650
--- /dev/null
+++ b/src/test/kotlin/InventoryLedgerTest.kt
@@ -0,0 +1,307 @@
+/*
+ * 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 .
+ */
+
+import com.lambda.interaction.manager.managers.inventory.InventoryLedger
+import com.lambda.interaction.manager.managers.inventory.InventoryLedger.SingleDecision.Apply
+import com.lambda.interaction.manager.managers.inventory.InventoryLedger.SingleDecision.Drop
+import kotlin.random.Random
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Instant
+
+/**
+ * Challenges [InventoryLedger] with the exact failure modes of the old value-matching cache:
+ * delayed echoes after rapid actions, stale full snapshots clobbering newer slots, repeated
+ * identical values, orphaned entries eating real corrections, and server rejections.
+ *
+ * Stacks are plain data (no Minecraft classes) so these run as fast JVM unit tests.
+ */
+class InventoryLedgerTest {
+
+    private data class TestStack(val item: String, val count: Int)
+
+    private fun stone(count: Int) = TestStack("stone", count)
+    private fun dirt(count: Int) = TestStack("dirt", count)
+
+    private class ManualClock(var now: Instant = Instant.fromEpochMilliseconds(0)) {
+        fun advance(ms: Long) {
+            now = now.plus(ms.milliseconds)
+        }
+    }
+
+    private fun ledger(clock: ManualClock, maxAgeMs: Long = 60_000L) =
+        InventoryLedger({ a, b -> a == b }, maxAgeMs = maxAgeMs, clock = { clock.now })
+
+    @Test
+    fun `twelve rapid placements with delayed echoes keep the predicted count`() {
+        val clock = ManualClock()
+        val ledger = ledger(clock)
+        val slot = 36
+
+        // Client predicts 64 -> 52 while the server answers are still in flight.
+        var count = 64
+        repeat(12) {
+            ledger.record(0, slot, stone(count), stone(count - 1))
+            count--
+        }
+        assertEquals(52, count)
+        assertEquals(12, ledger.pendingCount(0, slot))
+
+        // The 12 echoes arrive late, after all predictions. Every one must be dropped.
+        repeat(12) { i ->
+            assertEquals(Drop, ledger.decideSingle(0, slot, stone(63 - i)), "echo ${63 - i} must be dropped")
+        }
+        assertEquals(0, ledger.totalPending())
+
+        // Something genuinely new (blocks removed by something else) must still apply.
+        assertEquals(Apply, ledger.decideSingle(0, slot, stone(48)))
+    }
+
+    @Test
+    fun `stale full snapshot does not clobber slots changed after it was sent`() {
+        val clock = ManualClock()
+        val ledger = ledger(clock)
+
+        // Slot 0 was decremented, then slot 1 changed after the server snapshot was taken.
+        ledger.record(0, 0, stone(64), stone(63))
+        ledger.record(0, 1, dirt(5), dirt(4))
+
+        val client = listOf(stone(63), dirt(4), stone(10))
+        // Server snapshot predates both changes; slot 2 has an unrelated external change.
+        val incoming = listOf(stone(64), dirt(5), stone(11))
+
+        assertEquals(listOf(false, false, true), ledger.decideFull(0, incoming, client))
+
+        // Nothing was consumed for the stale slots, so the real echo still drops afterwards.
+        assertEquals(1, ledger.pendingCount(0, 0))
+        assertEquals(Drop, ledger.decideSingle(0, 0, stone(63)))
+    }
+
+    @Test
+    fun `repeated identical values consume oldest first without orphans`() {
+        val clock = ManualClock()
+        val ledger = ledger(clock)
+
+        // Place (64->63), pick up (63->64), place again (64->63).
+        ledger.record(0, 36, stone(64), stone(63))
+        ledger.record(0, 36, stone(63), stone(64))
+        ledger.record(0, 36, stone(64), stone(63))
+
+        assertEquals(Drop, ledger.decideSingle(0, 36, stone(63)))
+        assertEquals(Drop, ledger.decideSingle(0, 36, stone(64)))
+        assertEquals(Drop, ledger.decideSingle(0, 36, stone(63)))
+        assertEquals(0, ledger.totalPending())
+
+        // A later genuine correction must not be eaten by a leftover entry.
+        assertEquals(Apply, ledger.decideSingle(0, 36, stone(60)))
+    }
+
+    @Test
+    fun `click records once and a later correction still applies`() {
+        val clock = ManualClock()
+        val ledger = ledger(clock)
+
+        // A click moves the stack; the forced full update echoes it back.
+        // (Slot 0, because full-packet list positions map to slot ids.)
+        ledger.record(0, 0, TestStack("sword", 1), TestStack("shield", 1))
+
+        val apply = ledger.decideFull(0, listOf(TestStack("shield", 1)), listOf(TestStack("shield", 1)))
+        assertEquals(listOf(true), apply)
+        assertEquals(0, ledger.totalPending(), "single record must be fully consumed, no orphan may remain")
+
+        // A duplicate delivery of the same value applies harmlessly (same value either way).
+        assertEquals(Apply, ledger.decideSingle(0, 0, TestStack("shield", 1)))
+
+        // A real change afterwards is new information.
+        ledger.record(0, 0, TestStack("shield", 1), dirt(2))
+        assertEquals(Apply, ledger.decideSingle(0, 0, stone(30)))
+        assertEquals(0, ledger.pendingCount(0, 0))
+    }
+
+    @Test
+    fun `server rejection of a prediction reverts to the old value`() {
+        val clock = ManualClock()
+        val ledger = ledger(clock)
+
+        // Client predicted a placement the server refused: the old count comes back.
+        ledger.record(0, 36, stone(64), stone(63))
+        assertEquals(Apply, ledger.decideSingle(0, 36, stone(64)))
+        assertEquals(0, ledger.pendingCount(0, 36))
+    }
+
+    @Test
+    fun `external value in a full snapshot applies and clears stale predictions`() {
+        val clock = ManualClock()
+        val ledger = ledger(clock)
+
+        ledger.record(0, 0, stone(64), stone(63))
+        val apply = ledger.decideFull(0, listOf(stone(70)), listOf(stone(63)))
+        assertEquals(listOf(true), apply)
+        assertEquals(0, ledger.pendingCount(0, 0))
+    }
+
+    @Test
+    fun `expired predictions no longer filter echoes`() {
+        val clock = ManualClock()
+        val ledger = ledger(clock, maxAgeMs = 100L)
+
+        ledger.record(0, 36, stone(64), stone(63))
+        clock.advance(500L)
+        // The recording has decayed, so the late echo is treated as new and applied.
+        assertEquals(Apply, ledger.decideSingle(0, 36, stone(63)))
+        assertEquals(0, ledger.totalPending())
+    }
+
+    @Test
+    fun `screens do not share predictions`() {
+        val clock = ManualClock()
+        val ledger = ledger(clock)
+
+        ledger.record(5, 3, stone(10), stone(9))
+        assertEquals(Apply, ledger.decideSingle(0, 3, stone(9)), "other screens must not consume this")
+        assertEquals(Drop, ledger.decideSingle(5, 3, stone(9)))
+        ledger.record(5, 3, stone(9), stone(8))
+        ledger.clear(5)
+        assertEquals(Apply, ledger.decideSingle(5, 3, stone(8)))
+    }
+
+    @Test
+    fun `full snapshot with mismatched sizes applies everything`() {
+        val clock = ManualClock()
+        val ledger = ledger(clock)
+
+        ledger.record(0, 0, stone(64), stone(63))
+        val apply = ledger.decideFull(0, listOf(stone(1), stone(2)), listOf(stone(63)))
+        assertEquals(listOf(true, true), apply)
+    }
+
+    @Test
+    fun `recording an unchanged slot stores nothing`() {
+        val clock = ManualClock()
+        val ledger = ledger(clock)
+
+        ledger.record(0, 0, stone(64), stone(64))
+        assertEquals(0, ledger.totalPending())
+        assertEquals(Apply, ledger.decideSingle(0, 0, stone(64)))
+    }
+
+    @Test
+    fun `random predictions with in-order echoes are all dropped`() {
+        val clock = ManualClock()
+        val ledger = ledger(clock)
+        val random = Random(1234)
+        val slots = listOf(36, 37, 38)
+        val expected = mutableMapOf>()
+
+        repeat(200) {
+            val slot = slots.random(random)
+            val current = expected[slot]?.lastOrNull() ?: stone(64)
+            val next = current.copy(count = (current.count + random.nextInt(-3, 2)).coerceIn(0, 64))
+            if (next != current) {
+                ledger.record(0, slot, current, next)
+                expected.getOrPut(slot) { ArrayDeque() }.addLast(next)
+            }
+            clock.advance(1L)
+        }
+
+        // Server echoes arrive in order per slot; each must be recognised as our own action.
+        expected.forEach { (slot, echoes) ->
+            echoes.forEach { echo ->
+                assertEquals(Drop, ledger.decideSingle(0, slot, echo), "echo $echo on slot $slot")
+            }
+        }
+        assertEquals(0, ledger.totalPending())
+
+        // And the ledger still accepts genuinely new values afterwards.
+        assertTrue(ledger.decideFull(0, listOf(dirt(1)), listOf(dirt(1))) == listOf(true))
+    }
+
+    // ---- cursor: every click is sent with a mismatched revision, so the server answers each one
+    // with a full packet carrying the cursor. With rapid grab/place pairs that cursor is routinely
+    // stale on arrival and must be merged like a slot, or a resurrected cursor breaks the tasks
+    // that read it every tick. ------------------------------------------------------------------
+
+    private val cursor = InventoryLedger.CURSOR_SLOT_ID
+    private val empty = TestStack("air", 0)
+    private fun shulker(contents: String) = TestStack("shulker[$contents]", 1)
+
+    @Test
+    fun `stale cursor from an earlier click is dropped`() {
+        val clock = ManualClock()
+        val ledger = ledger(clock)
+
+        // Click 1 grabs the shulker, click 2 puts it into the destination slot.
+        ledger.record(0, cursor, empty, shulker("gear"))
+        ledger.record(0, 10, stone(5), shulker("gear"))
+        ledger.record(0, cursor, shulker("gear"), empty)
+
+        // The first full (cursor still holding the shulker) arrives after the second click.
+        assertEquals(Drop, ledger.decideSingle(0, cursor, shulker("gear")))
+        // The destination slot in the same stale full is merged the same way.
+        assertEquals(
+            listOf(true, true, true, true, true, true, true, true, true, true, false),
+            ledger.decideFull(0, List(10) { stone(5) } + stone(5), List(10) { stone(5) } + shulker("gear"))
+        )
+    }
+
+    @Test
+    fun `two-click grab and place consumes everything once both echoes arrive`() {
+        val clock = ManualClock()
+        val ledger = ledger(clock)
+
+        ledger.record(0, cursor, empty, shulker("gear"))
+        ledger.record(0, 3, stone(5), shulker("gear"))
+        ledger.record(0, cursor, shulker("gear"), empty)
+
+        // First full arrives late: cursor grab is stale, destination already holds the shulker.
+        assertEquals(Drop, ledger.decideSingle(0, cursor, shulker("gear")))
+        assertEquals(listOf(true, true, true, false), ledger.decideFull(0, listOf(empty, empty, empty, stone(5)), listOf(empty, empty, empty, shulker("gear"))))
+
+        // Second full (everything current) drains the rest; nothing is left to false-match later.
+        assertEquals(Drop, ledger.decideSingle(0, cursor, empty))
+        assertEquals(listOf(true, true, true, true), ledger.decideFull(0, listOf(empty, empty, empty, shulker("gear")), listOf(empty, empty, empty, shulker("gear"))))
+        assertEquals(0, ledger.totalPending())
+
+        // A later genuine cursor correction still applies.
+        ledger.record(0, cursor, empty, dirt(2))
+        assertEquals(Apply, ledger.decideSingle(0, cursor, stone(30)))
+        assertEquals(0, ledger.pendingCount(0, cursor))
+    }
+
+    @Test
+    fun `novel server cursor applies and clears stale predictions`() {
+        val clock = ManualClock()
+        val ledger = ledger(clock)
+
+        ledger.record(0, cursor, empty, shulker("gear"))
+        // The server rejected the grab: the cursor is still empty, which we never predicted.
+        assertEquals(Apply, ledger.decideSingle(0, cursor, empty))
+        assertEquals(0, ledger.pendingCount(0, cursor))
+    }
+
+    @Test
+    fun `cursor predictions are isolated per screen`() {
+        val clock = ManualClock()
+        val ledger = ledger(clock)
+
+        ledger.record(5, cursor, empty, shulker("gear"))
+        assertEquals(Apply, ledger.decideSingle(0, cursor, shulker("gear")))
+        assertEquals(Drop, ledger.decideSingle(5, cursor, shulker("gear")))
+    }
+}
diff --git a/src/test/kotlin/InventorySyncFilterTest.kt b/src/test/kotlin/InventorySyncFilterTest.kt
new file mode 100644
index 000000000..378bcf0f4
--- /dev/null
+++ b/src/test/kotlin/InventorySyncFilterTest.kt
@@ -0,0 +1,134 @@
+/*
+ * 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 .
+ */
+
+import com.lambda.interaction.manager.managers.inventory.InventorySyncFilter
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+import kotlin.time.Instant
+
+/**
+ * End-to-end tests for [InventorySyncFilter]: snapshot + ledger wiring, not just the ledger rules.
+ *
+ * The headline scenario is the nested-shulker failure: pick up a shulker box, move it into the
+ * ender chest on another screen, then pick up an *identical* shulker box into the *same* slot.
+ * The second pickup is new information from the server and must be applied. Previously the
+ * applied first pickup was re-recorded as a phantom prediction on the next diff (the snapshot was
+ * never refreshed), and that phantom matched the identical second shulker, so the update was
+ * dropped and the shulker never appeared client-side.
+ *
+ * Stacks are plain data (no Minecraft classes) so these run as fast JVM unit tests.
+ */
+class InventorySyncFilterTest {
+    private data class TestStack(val item: String, val count: Int)
+
+    private val empty = TestStack("air", 0)
+    private fun shulker(contents: String) = TestStack("shulker[$contents]", 1)
+
+    private class ManualClock(var now: Instant = Instant.fromEpochMilliseconds(0))
+
+    private fun filter(clock: ManualClock, maxAgeMs: Long = 60_000L) =
+        InventorySyncFilter({ a, b -> a == b }, { it.copy() }, maxAgeMs = maxAgeMs, clock = { clock.now })
+
+    /** Player-screen slots: everything empty except [slot]. */
+    private fun playerSlots(slot: Int, stack: TestStack, size: Int = 46): Map =
+        (0 until size).associateWith { empty } + (slot to stack)
+
+    @Test
+    fun `identical shulker picked into the same slot applies`() {
+        val clock = ManualClock()
+        val filter = filter(clock)
+        val slot = 36
+        val first = shulker("ender_chest")
+        val second = shulker("ender_chest")
+        assertEquals(first, second, "test setup: the two shulkers must be identical")
+
+        // Baseline: the player screen as the client sees it.
+        filter.observeLocal(0, playerSlots(slot, empty), empty)
+
+        // The first shulker is picked up from the ground. Nothing predicted it, so it applies.
+        assertTrue(filter.onSingle(0, slot, first, empty))
+
+        // A tick passes with no local change. The applied pickup must NOT be re-recorded as
+        // something we did: there must be no phantom prediction left behind.
+        filter.observeLocal(0, playerSlots(slot, first), empty)
+        assertEquals(0, filter.totalPending(), "applied server values must refresh the snapshot")
+
+        // The first shulker moves into the ender chest on its screen (container-local ids,
+        // so these recordings live under that screen only).
+        filter.snapshot(5, mapOf(2 to empty, 40 to first), empty)
+        filter.observeLocal(5, mapOf(2 to first, 40 to empty), empty)
+        assertEquals(0, filter.pendingCount(0, slot))
+
+        // The second, identical shulker is picked up into the same slot. This is new
+        // information and must be applied, not mistaken for an echo of the first pickup.
+        assertTrue(
+            filter.onSingle(0, slot, second, empty),
+            "identical shulker into the same slot must be applied"
+        )
+        assertEquals(0, filter.pendingCount(0, slot))
+    }
+
+    @Test
+    fun `identical shulker picked into a different slot applies`() {
+        val clock = ManualClock()
+        val filter = filter(clock)
+
+        filter.observeLocal(0, playerSlots(36, empty), empty)
+        assertTrue(filter.onSingle(0, 36, shulker("ender_chest"), empty))
+        filter.observeLocal(0, playerSlots(36, shulker("ender_chest")), empty)
+
+        assertTrue(filter.onSingle(0, 37, shulker("ender_chest"), empty))
+    }
+
+    @Test
+    fun `local predictions still filter their echoes`() {
+        val clock = ManualClock()
+        val filter = filter(clock)
+        val slot = 36
+
+        filter.observeLocal(0, playerSlots(slot, TestStack("stone", 64)), empty)
+
+        // The client predicts placing a block; the late echo must be dropped.
+        filter.observeLocal(0, playerSlots(slot, TestStack("stone", 63)), empty)
+        assertEquals(1, filter.pendingCount(0, slot))
+        assertEquals(false, filter.onSingle(0, slot, TestStack("stone", 63), TestStack("stone", 63)))
+        assertEquals(0, filter.pendingCount(0, slot))
+
+        // And a correction straight afterwards still applies.
+        assertTrue(filter.onSingle(0, slot, TestStack("stone", 64), TestStack("stone", 63)))
+    }
+
+    @Test
+    fun `snapshot overwrites and per-screen state is isolated`() {
+        val clock = ManualClock()
+        val filter = filter(clock)
+
+        filter.observeLocal(0, playerSlots(36, TestStack("stone", 64)), empty)
+        filter.observeLocal(0, playerSlots(36, TestStack("stone", 63)), empty)
+        assertEquals(1, filter.pendingCount(0, 36))
+
+        // Re-snapshotting (screen reopened) does not clear predictions, but a different screen
+        // never sees them.
+        filter.snapshot(5, mapOf(0 to TestStack("stone", 1)), empty)
+        assertEquals(1, filter.pendingCount(0, 36))
+        assertTrue(filter.onSingle(5, 0, TestStack("stone", 1), TestStack("stone", 1)))
+
+        filter.clear(0)
+        assertEquals(0, filter.totalPending())
+    }
+}

From 8a5f9a0a3762ced987355ffe37b9a42995dbd1b6 Mon Sep 17 00:00:00 2001
From: beanbag44 <107891830+beanbag44@users.noreply.github.com>
Date: Fri, 9 Oct 2026 23:11:12 +0100
Subject: [PATCH 31/43] fix cursor desync and an edge case InventoryTweaks
 showed up

---
 .../com/lambda/TaskLifecycleGameTest.kt       | 216 ++++++++
 src/gametest/resources/fabric.mod.json        |   4 +
 .../ClientPlayInteractionManagerMixin.java    |   2 +-
 .../ClientPlayNetworkHandlerMixin.java        |   5 +
 .../handler/handlers/ContainerHandler.kt      |   5 +-
 .../managers/inventory/InventoryLedger.kt     |  14 +
 .../managers/inventory/InventoryManager.kt    | 166 +++---
 .../managers/inventory/InventorySyncFilter.kt |  24 +-
 .../module/modules/player/InventoryTweaks.kt  |  10 +-
 src/main/kotlin/com/lambda/task/RootTask.kt   |   4 +
 src/main/kotlin/com/lambda/task/Task.kt       | 516 +++++++++---------
 .../kotlin/com/lambda/task/tasks/BuildTask.kt |   2 +
 .../com/lambda/task/tasks/CollectDropsTask.kt |   7 +-
 .../task/tasks/ContainerTransferTask.kt       |  72 ++-
 .../lambda/task/tasks/OpenContainerTask.kt    |  13 +-
 .../task/tasks/wrappers/SoftFailTask.kt       |   8 +-
 src/test/kotlin/InventoryLedgerTest.kt        |  31 +-
 src/test/kotlin/InventorySyncFilterTest.kt    |  32 ++
 18 files changed, 787 insertions(+), 344 deletions(-)
 create mode 100644 src/gametest/kotlin/com/lambda/TaskLifecycleGameTest.kt

diff --git a/src/gametest/kotlin/com/lambda/TaskLifecycleGameTest.kt b/src/gametest/kotlin/com/lambda/TaskLifecycleGameTest.kt
new file mode 100644
index 000000000..70d6e9fe4
--- /dev/null
+++ b/src/gametest/kotlin/com/lambda/TaskLifecycleGameTest.kt
@@ -0,0 +1,216 @@
+/*
+ * 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 .
+ */
+
+@file:Suppress("unused")
+
+package com.lambda
+
+import com.lambda.Lambda.LOG
+import com.lambda.config.automation.AutomationConfig
+import com.lambda.context.Automated
+import com.lambda.interaction.container.ContainerType
+import com.lambda.interaction.container.PlacedContainer
+import com.lambda.interaction.container.selection.containerSelection
+import com.lambda.interaction.container.selection.select
+import com.lambda.interaction.handler.handlers.ContainerSearchScope
+import com.lambda.task.RootTask
+import com.lambda.task.Task
+import com.lambda.task.start
+import com.lambda.task.tasks.openContainer
+import com.lambda.task.tasks.transfer
+import com.lambda.task.tasks.wrappers.actionTask
+import com.lambda.task.tasks.wrappers.then
+import com.lambda.threading.runSafe
+import net.fabricmc.fabric.api.client.gametest.v1.FabricClientGameTest
+import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext
+import net.fabricmc.fabric.api.client.gametest.v1.context.TestServerContext
+import net.minecraft.client.gui.screen.world.WorldCreator
+import net.minecraft.item.Items
+import net.minecraft.util.math.BlockPos
+
+/**
+ * Pins down two task-system guarantees that used to break in long sessions:
+ *
+ * 1. A failing task must never wedge the root: the child's own error handling runs, siblings
+ *    survive, and [RootTask] stays Running so `;task cancel` keeps working.
+ * 2. Cancel actually clears: a stuck task (here an [openContainer] somewhere unreachable that
+ *    keeps retrying) ends up Cancelled and disappears from the tree.
+ */
+@Suppress("UnstableApiUsage")
+object TaskLifecycleGameTest : FabricClientGameTest {
+    private const val PLAYER = "Steve"
+    private const val TRANSFER_TIMEOUT = 20 * 40
+    private const val OPEN_TIMEOUT = 20 * 10
+
+    private val failures = mutableListOf()
+
+    override fun runTest(context: ClientGameTestContext) {
+        failures.clear()
+
+        val singleplayer = context.worldBuilder()
+            .adjustSettings { it.gameMode = WorldCreator.Mode.SURVIVAL }
+            .create()
+
+        try {
+            singleplayer.clientWorld.waitForChunksDownload()
+            runScenario(context, singleplayer.server)
+        } finally {
+            runCatching { context.runOnClient { RootTask.cancel() } }
+            runCatching { singleplayer.close() }
+        }
+
+        if (failures.isEmpty()) {
+            LOG.info("Task lifecycle game test passed")
+        } else {
+            LOG.error("Task lifecycle game test failed:")
+            failures.forEach { LOG.error("  FAIL $it") }
+        }
+        check(failures.isEmpty()) {
+            "${failures.size} task lifecycle expectations failed:\n" + failures.joinToString("\n") { "  - $it" }
+        }
+    }
+
+    private class Outcome(val state: Task.State, val error: String?, val ticks: Int) {
+        val succeeded get() = state == Task.State.Completed
+        val failed get() = state == Task.State.Failed
+    }
+
+    private fun runScenario(context: ClientGameTestContext, server: TestServerContext) {
+        val auto: Automated = AutomationConfig.DEFAULT
+        val center = BlockPos(0, -60, 0)
+        val chestPos = BlockPos(2, -60, 0)
+
+        prepareWorld(context, server, center)
+        server.runCommand("/clear $PLAYER")
+        server.runCommand("/kill @e[type=item]")
+        server.runCommand("/setblock ${chestPos.x} ${chestPos.y} ${chestPos.z} minecraft:chest[facing=west]")
+        server.runCommand("/item replace block ${chestPos.x} ${chestPos.y} ${chestPos.z} container.0 with minecraft:dirt 64")
+
+        // 1. A transfer with no source must fail the transfer only: the root stays Running.
+        val chests = containerSelection(ContainerSearchScope.All) {
+            ofAnyType(ContainerType.Chest)
+            predicate { it is PlacedContainer && it.pos == chestPos }
+        }
+        val doomed = runTask(context, TRANSFER_TIMEOUT) {
+            with(auto) {
+                transfer(
+                    Items.STONE.select(10),
+                    chests,
+                    chests
+                )
+            }
+        }
+        if (!doomed.failed) {
+            fail("a sourceless transfer should have failed but was ${doomed.state}")
+            return
+        }
+        val rootAfterFailure = context.computeOnClient { RootTask.state }
+        if (rootAfterFailure != Task.State.Running) {
+            fail("RootTask must stay Running after a child failure but was $rootAfterFailure")
+            return
+        }
+
+        // 1b. A failing inner task must fail its wrapper instead of stranding it Running. This
+        // proves chained children are parented to their wrapper: with flat root parenting the
+        // wrapper would never hear about the failure and hang forever.
+        val chained = runTask(context, TRANSFER_TIMEOUT) {
+            with(auto) {
+                transfer(
+                    Items.STONE.select(10),
+                    chests,
+                    chests
+                ).then { actionTask { } }
+            }
+        }
+        if (!chained.failed) {
+            fail("a chained wrapper over a failing transfer should have failed but was ${chained.state}")
+            return
+        }
+
+        // 2. A stuck open (nothing openable out there) keeps retrying; cancel must kill it.
+        val farPos = BlockPos(center.x + 5, center.y, center.z + 5)
+        var stuck: Task<*>? = null
+        context.runOnClient {
+            runSafe {
+                stuck = with(auto) { openContainer(farPos) }.start()
+            }
+        }
+        val stuckTask = stuck
+        if (stuckTask == null) {
+            fail("could not start the stuck open task")
+            return
+        }
+        context.waitTicks(30)
+        val stillRunning = context.computeOnClient {
+            stuckTask.state == Task.State.Running
+        }
+        if (!stillRunning) {
+            fail("the far open task should still be retrying but was ${stuckTask.state}")
+            return
+        }
+        context.runOnClient { RootTask.cancel() }
+        context.waitTicks(3)
+        val cancelled = context.computeOnClient { stuckTask.state }
+        if (cancelled != Task.State.Cancelled) {
+            fail("cancel must terminate the stuck task but it was $cancelled")
+            return
+        }
+        val rootAfterCancel = context.computeOnClient { RootTask.state }
+        if (rootAfterCancel != Task.State.Running) {
+            fail("RootTask must stay Running after cancel but was $rootAfterCancel")
+        }
+    }
+
+    private fun runTask(
+        context: ClientGameTestContext,
+        timeout: Int,
+        build: () -> Task<*>
+    ): Outcome {
+        var task: Task<*>? = null
+        var error: String? = null
+        context.runOnClient {
+            runSafe {
+                task = build().onFailure { error = it.message }.start()
+            }
+        }
+        val started = task ?: return Outcome(Task.State.Failed, "no safe context to start the task", 0)
+        val ticks = try {
+            context.waitFor({
+                started.state != Task.State.Running && started.state != Task.State.Paused
+            }, timeout)
+        } catch (_: Throwable) {
+            timeout
+        }
+        return Outcome(started.state, error, ticks)
+    }
+
+    private fun prepareWorld(context: ClientGameTestContext, server: TestServerContext, center: BlockPos) {
+        server.runCommand("/gamemode survival $PLAYER")
+        server.runCommand("/difficulty peaceful")
+        server.runCommand("/time set noon")
+        server.runCommand("/weather clear")
+        server.runCommand("/fill ${center.x - 5} ${center.y - 1} ${center.z - 5} ${center.x + 5} ${center.y - 1} ${center.z + 5} minecraft:stone")
+        server.runCommand("/fill ${center.x - 5} ${center.y} ${center.z - 5} ${center.x + 5} ${center.y + 4} ${center.z + 5} minecraft:air")
+        server.runCommand("/tp $PLAYER ${center.x + 0.5} ${center.y} ${center.z + 0.5} 0 0")
+        context.waitTicks(10)
+    }
+
+    private fun fail(message: String) {
+        failures += message
+        LOG.warn("FAIL [task-lifecycle] $message")
+    }
+}
diff --git a/src/gametest/resources/fabric.mod.json b/src/gametest/resources/fabric.mod.json
index 80f326a13..dc00aa322 100644
--- a/src/gametest/resources/fabric.mod.json
+++ b/src/gametest/resources/fabric.mod.json
@@ -17,6 +17,10 @@
       {
         "adapter": "kotlin",
         "value": "com.lambda.ShulkerPickupGameTest"
+      },
+      {
+        "adapter": "kotlin",
+        "value": "com.lambda.TaskLifecycleGameTest"
       }
     ]
   }
diff --git a/src/main/java/com/lambda/mixin/entity/ClientPlayInteractionManagerMixin.java b/src/main/java/com/lambda/mixin/entity/ClientPlayInteractionManagerMixin.java
index 67b4c7ddb..5630fb0b2 100644
--- a/src/main/java/com/lambda/mixin/entity/ClientPlayInteractionManagerMixin.java
+++ b/src/main/java/com/lambda/mixin/entity/ClientPlayInteractionManagerMixin.java
@@ -156,7 +156,7 @@ private void cancelBlockBreakingPre(CallbackInfo ci) {
     @WrapMethod(method = "createPlayer(Lnet/minecraft/client/world/ClientWorld;Lnet/minecraft/stat/StatHandler;Lnet/minecraft/client/recipebook/ClientRecipeBook;)Lnet/minecraft/client/network/ClientPlayerEntity;")
     private ClientPlayerEntity wrapCreatePlayer(ClientWorld world, StatHandler statHandler, ClientRecipeBook recipeBook, Operation original) {
         var player = original.call(world, statHandler, recipeBook);
-        InventoryManager.INSTANCE.setScreenHandler(player.playerScreenHandler);
+        InventoryManager.onSetScreenHandler(player.playerScreenHandler);
         return player;
     }
 }
diff --git a/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java b/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java
index 54a5b4ef3..a85af677e 100644
--- a/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java
+++ b/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java
@@ -134,6 +134,11 @@ private void wrapOnInventory(InventoryS2CPacket packet, Operation original
         InventoryManager.onInventoryUpdate(packet, original);
     }
 
+    @WrapMethod(method = "onSetCursorItem")
+    private void wrapOnSetCursorItem(SetCursorItemS2CPacket packet, Operation original) {
+        InventoryManager.onCursorUpdate(packet, original);
+    }
+
     @WrapMethod(method = "sendChatMessage(Ljava/lang/String;)V")
     void onSendMessage(String content, Operation original) {
         var event = new ChatEvent.Send(content);
diff --git a/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt
index 36c82b4c9..9dea16012 100644
--- a/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt
+++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt
@@ -96,7 +96,10 @@ object ContainerHandler : Loadable {
 			val sh = event.screenHandler
 			onContainerUpdate(sh)
 			if (sh.syncId != 0 && pendingInteractedBlockEntity == null) {
-				lastInteractedBlockEntity = null
+				val current = player.currentScreenHandler
+				if (current == null || current.syncId == 0 || current === sh) {
+					lastInteractedBlockEntity = null
+				}
 			}
 			accessedPlacedContainer = null
 		}
diff --git a/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryLedger.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryLedger.kt
index 360bfdd90..2eaf6e4e1 100644
--- a/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryLedger.kt
+++ b/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryLedger.kt
@@ -64,6 +64,20 @@ class InventoryLedger(
         }
     }
 
+    @Synchronized
+    fun decideCursor(syncId: Int, incoming: T): SingleDecision {
+        cleanup()
+        val queue = queues[syncId]?.get(CURSOR_SLOT_ID) ?: return SingleDecision.Apply
+        if (queue.any { equal(it.after, incoming) }) {
+            consumeFirstAfterMatch(queue, incoming)
+            if (queue.isEmpty()) removeQueue(syncId, CURSOR_SLOT_ID)
+            return SingleDecision.Drop
+        }
+        if (queue.any { equal(it.before, incoming) }) return SingleDecision.Drop
+        removeQueue(syncId, CURSOR_SLOT_ID)
+        return SingleDecision.Apply
+    }
+
     @Synchronized
     fun decideFull(syncId: Int, incoming: List, client: List): List {
         if (incoming.size != client.size) return List(incoming.size) { true }
diff --git a/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryManager.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryManager.kt
index b92c371cc..e5c2d49fa 100644
--- a/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryManager.kt
+++ b/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryManager.kt
@@ -19,6 +19,8 @@ package com.lambda.interaction.manager.managers.inventory
 
 import com.lambda.context.AutomatedSafeContext
 import com.lambda.context.SafeContext
+import com.lambda.Lambda.LOG
+import com.lambda.event.events.InventoryEvent
 import com.lambda.event.events.PacketEvent
 import com.lambda.event.events.PlayerEvent
 import com.lambda.event.events.TickEvent
@@ -29,6 +31,7 @@ import com.lambda.interaction.handler.handlers.PacketType
 import com.lambda.interaction.manager.Manager
 import com.lambda.interaction.manager.managers.inventory.InventoryManager.processActiveRequest
 import com.lambda.module.modules.client.Client
+import com.lambda.module.modules.client.Client.verboseDebug
 import com.lambda.threading.runSafe
 import com.lambda.threading.runSafeAutomated
 import com.llamalad7.mixinextras.injector.wrapoperation.Operation
@@ -37,6 +40,7 @@ import net.minecraft.item.ItemStack
 import net.minecraft.network.packet.c2s.play.CloseHandledScreenC2SPacket
 import net.minecraft.network.packet.s2c.play.InventoryS2CPacket
 import net.minecraft.network.packet.s2c.play.ScreenHandlerSlotUpdateS2CPacket
+import net.minecraft.network.packet.s2c.play.SetCursorItemS2CPacket
 import net.minecraft.screen.PlayerScreenHandler
 import net.minecraft.screen.ScreenHandler
 
@@ -63,15 +67,6 @@ object InventoryManager : Manager(
 			Client.desyncTimeout * 50L
 		)
 
-	var screenHandler: ScreenHandler? = null
-		set(value) {
-			if (value != null) {
-				if (value.syncId != 0) filter.clear(value.syncId)
-				filter.snapshot(value.syncId, value.slots.associate { it.id to it.stack }, value.cursorStack)
-			}
-			field = value
-		}
-
 	private var actionsThisTick = 0
 
 	override fun load(): String {
@@ -90,10 +85,14 @@ object InventoryManager : Manager(
 
 		listen { event ->
 			if (event.packet is CloseHandledScreenC2SPacket) {
-				screenHandler = player.playerScreenHandler
+				onSetScreenHandler(player.playerScreenHandler)
 			}
 		}
 
+		listen {
+			onSetScreenHandler(player.currentScreenHandler)
+		}
+
 		return "Loaded Inventory Manager"
 	}
 
@@ -188,12 +187,12 @@ object InventoryManager : Manager(
 	 */
 	fun SafeContext.indexInventoryChanges() {
 		val current = player.currentScreenHandler
-		if (current.syncId != screenHandler?.syncId) return
-		filter.observeLocal(
+		val recorded = filter.observeLocal(
 			current.syncId,
 			current.slots.associate { it.id to it.stack },
 			current.cursorStack
 		)
+		if (verboseDebug && recorded > 0) LOG.info("[desync] recorded $recorded change(s) on sync=${current.syncId}")
 	}
 
 	private fun snapshotHandler(handler: ScreenHandler) {
@@ -204,55 +203,6 @@ object InventoryManager : Manager(
 		)
 	}
 
-	/**
-	 * A modified version of the minecraft onInventory method
-	 *
-	 * @see net.minecraft.client.network.ClientPlayNetworkHandler.onInventory
-	 */
-	@JvmStatic
-	fun onInventoryUpdate(packet: InventoryS2CPacket, original: Operation) {
-		runSafe {
-			val packetScreenHandler =
-				when (packet.syncId) {
-					0 -> player.playerScreenHandler
-					player.currentScreenHandler.syncId -> player.currentScreenHandler
-					else -> {
-						original.call(packet)
-						return
-					}
-				}
-			if (!mc.isOnThread || !Client.avoidInventoryDesync) {
-				original.call(packet)
-				snapshotHandler(packetScreenHandler)
-				return
-			}
-			val clientStacks = packetScreenHandler.slots.map { it.stack }
-			if (clientStacks.size != packet.contents.size) {
-				original.call(packet)
-				snapshotHandler(packetScreenHandler)
-				return
-			}
-			val result =
-				filter.onFull(
-					packet.syncId,
-					packet.contents,
-					clientStacks,
-					packet.cursorStack(),
-					packetScreenHandler.cursorStack
-				)
-			val merged =
-				packet.contents.mapIndexed { index, incomingStack ->
-					if (result.apply[index]) incomingStack else clientStacks[index]
-				}
-			val mergedCursor =
-				if (result.applyCursor) packet.cursorStack()
-				else packetScreenHandler.cursorStack
-			packetScreenHandler.updateSlotStacks(packet.revision(), merged, mergedCursor)
-			return
-		}
-		original.call(packet)
-	}
-
 	/**
 	 * A modified version of the minecraft onScreenHandlerSlotUpdate method
 	 *
@@ -269,6 +219,7 @@ object InventoryManager : Manager(
 					0 -> player.playerScreenHandler
 					player.currentScreenHandler.syncId -> player.currentScreenHandler
 					else -> {
+						if (verboseDebug) LOG.info("[desync] single ignored: packet sync=${packet.syncId} slot=${packet.slot} current=${player.currentScreenHandler.syncId} stack=${packet.stack}")
 						original.call(packet)
 						return
 					}
@@ -314,8 +265,99 @@ object InventoryManager : Manager(
 		original.call(packet)
 	}
 
+	/**
+	 * A modified version of the minecraft onSetCursorItem method. Cursor-only updates bypass
+	 * the full packets, so without this every one of them is applied blindly — including ones
+	 * the server generated before our latest click (see [InventorySyncFilter.onCursor]).
+	 *
+	 * @see net.minecraft.client.network.ClientPlayNetworkHandler.onSetCursorItem
+	 */
+	@JvmStatic
+	fun onCursorUpdate(packet: SetCursorItemS2CPacket, original: Operation) {
+		runSafe {
+			val stack = packet.contents()
+			mc.tutorialManager.onSlotUpdate(stack)
+			if (!mc.isOnThread || !Client.avoidInventoryDesync) {
+				original.call(packet)
+				snapshotHandler(player.currentScreenHandler)
+				return
+			}
+			val handler = player.currentScreenHandler
+			val drop = !filter.onCursor(handler.syncId, stack, handler.cursorStack)
+			if (verboseDebug) {
+				LOG.info("[desync] cursor single sync=${handler.syncId} incoming=$stack client=${handler.cursorStack} apply=${!drop} pendings=${filter.pendingCount(handler.syncId, InventoryLedger.CURSOR_SLOT_ID)}")
+			}
+			if (drop) return
+			original.call(packet)
+			return
+		}
+		original.call(packet)
+	}
+
+	/**
+	 * A modified version of the minecraft onInventory method
+	 *
+	 * @see net.minecraft.client.network.ClientPlayNetworkHandler.onInventory
+	 */
+	@JvmStatic
+	fun onInventoryUpdate(packet: InventoryS2CPacket, original: Operation) {
+		runSafe {
+			val packetScreenHandler =
+				when (packet.syncId) {
+					0 -> player.playerScreenHandler
+					player.currentScreenHandler.syncId -> player.currentScreenHandler
+					else -> {
+						if (verboseDebug) LOG.info("[desync] full ignored: packet sync=${packet.syncId} current=${player.currentScreenHandler.syncId}")
+						original.call(packet)
+						return
+					}
+				}
+			if (!mc.isOnThread || !Client.avoidInventoryDesync) {
+				original.call(packet)
+				snapshotHandler(packetScreenHandler)
+				return
+			}
+			val clientStacks = packetScreenHandler.slots.map { it.stack }
+			if (clientStacks.size != packet.contents.size) {
+				original.call(packet)
+				snapshotHandler(packetScreenHandler)
+				return
+			}
+			val result =
+				filter.onFull(
+					packet.syncId,
+					packet.contents,
+					clientStacks,
+					packet.cursorStack(),
+					packetScreenHandler.cursorStack
+				)
+			val merged =
+				packet.contents.mapIndexed { index, incomingStack ->
+					if (result.apply[index]) incomingStack else clientStacks[index]
+				}
+			val mergedCursor =
+				if (result.applyCursor) packet.cursorStack()
+				else packetScreenHandler.cursorStack
+			if (verboseDebug && !ItemStack.areEqual(packet.cursorStack(), packetScreenHandler.cursorStack)) {
+				LOG.info("[desync] full sync=${packet.syncId} cursor incoming=${packet.cursorStack()} client=${packetScreenHandler.cursorStack} apply=${result.applyCursor} pendings=${filter.pendingCount(packet.syncId, InventoryLedger.CURSOR_SLOT_ID)}")
+			}
+			packetScreenHandler.updateSlotStacks(packet.revision(), merged, mergedCursor)
+			return
+		}
+		original.call(packet)
+	}
+
+	/**
+	 * Anchors tracking to a screen's live state: drops pendings that cannot belong to a fresh
+	 * screen id, and photographs the current truth. Called on opens, closes and respawns.
+	 */
 	@JvmStatic
 	fun onSetScreenHandler(screenHandler: ScreenHandler) {
-		this.screenHandler = screenHandler
+		if (screenHandler.syncId != 0) filter.clear(screenHandler.syncId)
+		filter.snapshot(
+			screenHandler.syncId,
+			screenHandler.slots.associate { it.id to it.stack },
+			screenHandler.cursorStack
+		)
 	}
 }
diff --git a/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventorySyncFilter.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventorySyncFilter.kt
index d2abd8653..fe8d4b56b 100644
--- a/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventorySyncFilter.kt
+++ b/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventorySyncFilter.kt
@@ -46,19 +46,21 @@ class InventorySyncFilter(
     }
 
     @Synchronized
-    fun observeLocal(syncId: Int, slots: Map, cursor: T) {
+    fun observeLocal(syncId: Int, slots: Map, cursor: T): Int {
         val snap = slotSnapshots[syncId]
         if (snap == null) {
             slotSnapshots[syncId] = slots.mapValuesTo(HashMap()) { copy(it.value) }
             cursorSnapshots[syncId] = copy(cursor)
-            return
+            return 0
         }
+        var recorded = 0
         slots.forEach { (slotId, live) ->
             val old = snap[slotId]
             if (old == null) snap[slotId] = copy(live)
             else if (!equal(old, live)) {
                 ledger.record(syncId, slotId, old, copy(live))
                 snap[slotId] = copy(live)
+                recorded++
             }
         }
         snap.keys.retainAll(slots.keys)
@@ -67,7 +69,9 @@ class InventorySyncFilter(
         else if (!equal(oldCursor, cursor)) {
             ledger.record(syncId, InventoryLedger.CURSOR_SLOT_ID, oldCursor, copy(cursor))
             cursorSnapshots[syncId] = copy(cursor)
+            recorded++
         }
+        return recorded
     }
 
     @Synchronized
@@ -79,6 +83,20 @@ class InventorySyncFilter(
         return !drop
     }
 
+    /**
+     * Decides a cursor-only server update ([SetCursorItemS2CPacket]). These bypass the full
+     * packets: the server emits them from content updates on non-click triggers (pickups, tick
+     * syncs), carrying cursor truth that lags our predictions by a round trip. Without this,
+     * every such packet is applied blindly and resurrects whatever we just placed.
+     * Same contract as [onSingle], tracked under [InventoryLedger.CURSOR_SLOT_ID].
+     */
+    @Synchronized
+    fun onCursor(syncId: Int, incoming: T, current: T): Boolean {
+        val drop = ledger.decideCursor(syncId, incoming) == Drop
+        if (cursorSnapshots.containsKey(syncId)) cursorSnapshots[syncId] = copy(if (drop) current else incoming)
+        return !drop
+    }
+
     @Synchronized
     fun onFull(
         syncId: Int,
@@ -88,7 +106,7 @@ class InventorySyncFilter(
         clientCursor: T,
     ): FullResult {
         val apply = ledger.decideFull(syncId, incoming, client)
-        val applyCursor = ledger.decideSingle(syncId, InventoryLedger.CURSOR_SLOT_ID, incomingCursor) != Drop
+        val applyCursor = ledger.decideCursor(syncId, incomingCursor) != Drop
         val merged = incoming.indices.map { copy(if (apply[it]) incoming[it] else client[it]) }
         val snap = HashMap(merged.size)
         merged.forEachIndexed { index, stack -> snap[index] = stack }
diff --git a/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt b/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt
index 4daf34b4a..6733143db 100644
--- a/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt
+++ b/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt
@@ -34,6 +34,7 @@ import com.lambda.module.ModuleTag
 import com.lambda.task.Task
 import com.lambda.task.start
 import com.lambda.task.tasks.wrappers.then
+import com.lambda.util.CommunicationUtils.warn
 import com.lambda.util.item.ItemUtils.SHULKER_BOXES
 import net.minecraft.item.Items
 import net.minecraft.screen.ScreenHandler
@@ -82,14 +83,18 @@ object InventoryTweaks : Module(
 
             event.cancel()
 
+            // Latest click wins: a previous open still running would fight this one over
+            // transfers, placement and screens, so stop it first.
+            openTask?.cancel()
             openTask = targetContainer
                 .access()
                 ?.onSuccess { ctx ->
                     openContexts.push(ctx)
                     lastOpenScreen = player.currentScreenHandler
-                    openTask = null
                 }
-            openTask?.start()
+                ?.onFailure { e -> warn("Failed to open ${stack.name.string}: ${e.message}") }
+                ?.onCompletion { openTask = null }
+                ?.start()
         }
 
         listen { event ->
@@ -109,6 +114,7 @@ object InventoryTweaks : Module(
                 }
 
             closeTask
+                ?.onFailure { e -> warn("Failed to close containers: ${e.message}") }
                 ?.onCompletion { isClosing = false }
                 ?.start()
                 ?: run { isClosing = false }
diff --git a/src/main/kotlin/com/lambda/task/RootTask.kt b/src/main/kotlin/com/lambda/task/RootTask.kt
index db4abda1e..006ab80f1 100644
--- a/src/main/kotlin/com/lambda/task/RootTask.kt
+++ b/src/main/kotlin/com/lambda/task/RootTask.kt
@@ -21,6 +21,10 @@ import com.lambda.task.Task.Ta5kBuilder
 
 object RootTask : Task() {
     override val name get() = "Root Task"
+
+    init {
+        state = State.Running
+    }
 }
 
 @Ta5kBuilder
diff --git a/src/main/kotlin/com/lambda/task/Task.kt b/src/main/kotlin/com/lambda/task/Task.kt
index 460e0e7b3..83da64c88 100644
--- a/src/main/kotlin/com/lambda/task/Task.kt
+++ b/src/main/kotlin/com/lambda/task/Task.kt
@@ -35,259 +35,269 @@ import kotlin.time.DurationUnit
 import kotlin.time.toDuration
 
 abstract class Task : Nameable, Muteable {
-    var state = State.Init
-    var age = 0
-
-    var parent: Task<*>? = null
-    val subTasks = mutableListOf>()
-    private val depth: Int get() = parent?.depth?.plus(1) ?: 0
-    val size: Int get() = subTasks.sumOf { it.size } + 1
-
-    override val isMuted: Boolean get() = state == State.Paused || state == State.Init
-
-    private val successCallbacks = mutableListOf Unit>()
-    private val completionCallbacks = mutableListOf Unit>()
-    private val failureCallbacks = mutableListOf Unit>()
-
-    val duration: String get() =
-        (age * 50).toDuration(DurationUnit.MILLISECONDS).toComponents { days, hours, minutes, seconds, nanoseconds ->
-            "${"%03d".format(days)}:${"%02d".format(hours)}:${"%02d".format(minutes)}:${"%02d".format(seconds)}.${"${nanoseconds / 1_000_000}".take(2)}"
-        }
-
-    enum class State {
-        Init,
-        Running,
-        Paused,
-        Cancelled,
-        Failed,
-        Completed;
-    }
-
-    init {
-        if (this is RootTask) state = State.Running
-        listen({ Int.MAX_VALUE }) { age++ }
-    }
-
-    /**
-     * "Typo" in name is used to force the dsl style green
-     * (color is based on name hash, don't ask me who came up with this)
-     */
-    @DslMarker
-    annotation class Ta5kBuilder
-
-    @Ta5kBuilder
-    context(parentTask: Task<*>)
-    fun start(pauseParent: Boolean = true) =
-        this.execute(parentTask, pauseParent)
-
-    /**
-     * Executes the current task as a subtask of the specified owner task.
-     *
-     * This method adds the current task to the owner's subtasks, sets the parent relationship,
-     * logs the execution details, and invokes the necessary lifecycle hooks. Additionally,
-     * it manages the state of the parent task and starts any required listeners for execution.
-     *
-     * @param parent The parent task that will execute this task as a sub task. Must not be the same as this task.
-     * @param pauseParent Defines whether the parent task should be paused during the execution of this task. Defaults to `true`.
-     * @return The current task instance as a `Task` to support chaining or further configuration.
-     * @throws IllegalArgumentException if the owner task is the same as the task being executed.
-     */
-    @Ta5kBuilder
-    fun execute(parent: Task<*>, pauseParent: Boolean = true): Task {
-        require(parent != this) { "Cannot execute a task as a sub task of itself" }
-        parent.subTasks.add(this)
-        this@Task.parent = parent
-        if (verboseDebug) LOG.info("${parent.name} started $name")
-        if (pauseParent) {
-            if (verboseDebug) LOG.info("$name pausing parent ${parent.name}")
-            if (parent !is RootTask) parent.pause()
-        }
-        state = State.Running
-        runSafe { runCatching { onStart() }.onFailure { failure(it) } }
-        return this
-    }
-
-    /**
-     * Invoked when the task starts execution.
-     *
-     * This method serves as a lifecycle hook and can be overridden to define
-     * custom behavior or initialization steps necessary before the task begins.
-     * It provides a `SafeContext` to access relevant context-sensitive properties
-     * or actions safely.
-     *
-     * By default, this method does not contain any logic. Subclasses may override
-     * it to implement specific functionality, such as logging, resource allocation,
-     * or preparing preconditions for task execution.
-     */
-    @Ta5kBuilder
-    protected open fun SafeContext.onStart() {}
-
-    /**
-     * This function is called when the task is canceled.
-     * It can be overridden for tasks that need to perform cleanup operations,
-     * such as cancelling a block breaking progress, releasing resources,
-     * or stopping any ongoing operations that were started by the task.
-     */
-    @Ta5kBuilder
-    protected open fun SafeContext.onCancel() {}
-
-    @Ta5kBuilder
-    protected fun success(result: R) {
-        if (state != State.Running && state != State.Paused) return
-        unsubscribe()
-        state = State.Completed
-        cancelSubTasks()
-
-        parent?.onSubTaskSuccess(this)
-        parent?.onSubTaskCompletion(this)
-
-        runSafe {
-            successCallbacks.forEach { it.invoke(this, result) }
-            completionCallbacks.forEach { it.invoke(this) }
-        }
-
-        parent?.subTasks?.remove(this)
-    }
-
-    @Ta5kBuilder
-    protected fun Task.success() {
-        success(Unit)
-    }
-
-    @Ta5kBuilder
-    protected fun failure(
-        e: Throwable,
-        stacktrace: MutableList> = mutableListOf(),
-    ) {
-        if (state != State.Running && state != State.Paused) return
-        unsubscribe()
-        state = State.Failed
-        cancelSubTasks()
-        stacktrace.add(this)
-
-        parent?.onSubTaskFailure(this, e)
-            ?: run {
-                if (!verboseDebug) return@run
-                logError(
-                    buildString {
-                        val first = stacktrace.firstOrNull() ?: return@buildString
-                        append("${first.name} failed: ${e.message}\n")
-                        stacktrace.drop(1).forEach {
-                            append("  -> ${it.name}\n")
-                        }
-                    }
-                )
-            }
-        parent?.onSubTaskCompletion(this)
-
-        runSafe {
-            failureCallbacks.forEach { it.invoke(this, e) }
-            completionCallbacks.forEach { it.invoke(this) }
-        }
-
-        parent?.subTasks?.remove(this)
-    }
-
-    @Ta5kBuilder
-    protected fun failure(message: String) = failure(IllegalStateException(message))
-
-    @Ta5kBuilder
-    fun cancel() = internalCancel(true)
-
-    @Ta5kBuilder
-    private fun internalCancel(removeFromParent: Boolean = true) {
-        if (state != State.Running && state != State.Paused) return
-        cancelSubTasks()
-        if (this is RootTask) return
-        unsubscribe()
-        state = State.Cancelled
-        runSafe {
-            onCancel()
-            completionCallbacks.forEach { it.invoke(this) }
-        }
-
-        if (removeFromParent) parent?.subTasks?.remove(this)
-        parent?.activate()
-    }
-
-    @Ta5kBuilder
-    private fun cancelSubTasks() {
-        subTasks.forEach { it.internalCancel(removeFromParent = false) }
-        subTasks.clear()
-    }
-
-    @Ta5kBuilder
-    fun pause() {
-        if (state != State.Running) return
-        state = State.Paused
-    }
-
-    @Ta5kBuilder
-    fun activate() {
-        if (state != State.Paused) return
-        state = State.Running
-    }
-
-    @Ta5kBuilder
-    protected open fun onSubTaskSuccess(subTask: Task<*>) {
-        activate()
-    }
-
-    @Ta5kBuilder
-    protected open fun onSubTaskFailure(subTask: Task<*>, cause: Throwable) {
-        failure(cause)
-    }
-
-    @Ta5kBuilder
-    protected open fun onSubTaskCompletion(subTask: Task<*>) {
-        activate()
-    }
-
-    /**
-     * Registers a callback for if the task succeeds.
-     */
-    @Ta5kBuilder
-    fun onSuccess(callback: SafeContext.(R) -> Unit): Task {
-        successCallbacks.add(callback)
-        return this
-    }
-
-    /**
-     * Registers a callback for if the task fails.
-     *
-     * This could be mistaken for [withRecovery] which is used to wrap the given task with another task that runs a recovery task if this one fails.
-     */
-    @Ta5kBuilder
-    fun onFailure(callback: SafeContext.(Throwable) -> Unit): Task {
-        failureCallbacks.add(callback)
-        return this
-    }
-
-    /**
-     * Registers a callback for when the task completes.
-     *
-     * This is called regardless of whether the task succeeds or fails.
-     */
-    @Ta5kBuilder
-    fun onCompletion(callback: SafeContext.() -> Unit): Task {
-        completionCallbacks.add(callback)
-        return this
-    }
-
-    override fun toString() =
-        buildString { appendTaskTree(this@Task) }
-
-    private fun StringBuilder.appendTaskTree(task: Task<*>, level: Int = 0, maxEntries: Int = 10) {
-        if (task.state == State.Cancelled) return
-        appendLine("${" ".repeat(level * 4)}${task.name}" + if (task !is RootTask) " [${task.state.name}] ${(task.age * 50).milliseconds}" else "")
-        val left = task.subTasks.size - maxEntries
-        if (left > 0) {
-            appendLine("${" ".repeat((level + 1) * 4)}...and $left more tasks")
-        }
-        task.subTasks.takeLast(maxEntries).forEach {
-            appendTaskTree(it, level + 1)
-        }
-    }
+	var state = State.Init
+	var age = 0
+
+	var parent: Task<*>? = null
+	val subTasks = mutableListOf>()
+	private val depth: Int get() = parent?.depth?.plus(1) ?: 0
+	val size: Int get() = subTasks.sumOf { it.size } + 1
+
+	override val isMuted: Boolean get() = state == State.Paused || state == State.Init
+
+	private val successCallbacks = mutableListOf Unit>()
+	private val completionCallbacks = mutableListOf Unit>()
+	private val failureCallbacks = mutableListOf Unit>()
+
+	val duration: String
+		get() =
+			(age * 50).toDuration(DurationUnit.MILLISECONDS).toComponents { days, hours, minutes, seconds, nanoseconds ->
+				"${"%03d".format(days)}:${"%02d".format(hours)}:${"%02d".format(minutes)}:${"%02d".format(seconds)}.${"${nanoseconds / 1_000_000}".take(2)}"
+			}
+
+	enum class State {
+		Init,
+		Running,
+		Paused,
+		Cancelled,
+		Failed,
+		Completed;
+	}
+
+	init {
+		listen({ Int.MAX_VALUE }) { age++ }
+	}
+
+	/**
+	 * "Typo" in name is used to force the dsl style green
+	 * (color is based on name hash, don't ask me who came up with this)
+	 */
+	@DslMarker
+	annotation class Ta5kBuilder
+
+	@Ta5kBuilder
+	context(parentTask: Task<*>)
+	fun start(pauseParent: Boolean = true) =
+		this.execute(parentTask, pauseParent)
+
+	/**
+	 * Executes the current task as a subtask of the specified owner task.
+	 *
+	 * This method adds the current task to the owner's subtasks, sets the parent relationship,
+	 * logs the execution details, and invokes the necessary lifecycle hooks. Additionally,
+	 * it manages the state of the parent task and starts any required listeners for execution.
+	 *
+	 * @param parent The parent task that will execute this task as a sub task. Must not be the same as this task.
+	 * @param pauseParent Defines whether the parent task should be paused during the execution of this task. Defaults to `true`.
+	 * @return The current task instance as a `Task` to support chaining or further configuration.
+	 * @throws IllegalArgumentException if the owner task is the same as the task being executed.
+	 */
+	@Ta5kBuilder
+	fun execute(parent: Task<*>, pauseParent: Boolean = true): Task =
+		apply {
+	    	require(parent != this) { "Cannot execute a task as a sub task of itself" }
+	    	if (state != State.Init) return@apply
+			if (this@Task is RootTask) return@apply
+	    	parent.subTasks.add(this)
+	    	this@Task.parent = parent
+	    	if (pauseParent) {
+			    if (verboseDebug) LOG.info("$name pausing parent ${parent.name}")
+			    if (parent !is RootTask) parent.pause()
+		    }
+			if (verboseDebug) LOG.info("${parent.name} starting $name")
+			state = State.Running
+	    	runSafe {
+				runCatching {
+					onStart()
+				}.onFailure {
+					failure(it)
+				}
+			}
+	    }
+
+	/**
+	 * Invoked when the task starts execution.
+	 *
+	 * This method serves as a lifecycle hook and can be overridden to define
+	 * custom behavior or initialization steps necessary before the task begins.
+	 * It provides a `SafeContext` to access relevant context-sensitive properties
+	 * or actions safely.
+	 *
+	 * By default, this method does not contain any logic. Subclasses may override
+	 * it to implement specific functionality, such as logging, resource allocation,
+	 * or preparing preconditions for task execution.
+	 */
+	@Ta5kBuilder
+	protected open fun SafeContext.onStart() {}
+
+	/**
+	 * This function is called when the task is canceled.
+	 * It can be overridden for tasks that need to perform cleanup operations,
+	 * such as cancelling a block breaking progress, releasing resources,
+	 * or stopping any ongoing operations that were started by the task.
+	 */
+	@Ta5kBuilder
+	protected open fun SafeContext.onCancel() {}
+
+	@Ta5kBuilder
+	protected fun success(result: R) {
+		if (state != State.Running && state != State.Paused) return
+		if (this is RootTask) return
+		unsubscribe()
+		state = State.Completed
+		cancelSubTasks()
+
+		parent?.onSubTaskSuccess(this)
+		parent?.onSubTaskCompletion(this)
+
+		runSafe {
+			successCallbacks.forEach { it.invoke(this, result) }
+			completionCallbacks.forEach { it.invoke(this) }
+		}
+
+		parent?.subTasks?.remove(this)
+	}
+
+	@Ta5kBuilder
+	protected fun Task.success() {
+		success(Unit)
+	}
+
+	@Ta5kBuilder
+	protected fun failure(
+		e: Throwable,
+		stacktrace: MutableList> = mutableListOf(),
+	) {
+		if (state != State.Running && state != State.Paused) return
+		if (this is RootTask) return
+		unsubscribe()
+		state = State.Failed
+		cancelSubTasks()
+		stacktrace.add(this)
+
+		parent?.onSubTaskFailure(this, e)
+			?: run {
+				if (!verboseDebug) return@run
+				logError(
+					buildString {
+						val first = stacktrace.firstOrNull() ?: return@buildString
+						append("${first.name} failed: ${e.message}\n")
+						stacktrace.drop(1).forEach {
+							append("  -> ${it.name}\n")
+						}
+					}
+				)
+			}
+		parent?.onSubTaskCompletion(this)
+
+		runSafe {
+			failureCallbacks.forEach { it.invoke(this, e) }
+			completionCallbacks.forEach { it.invoke(this) }
+		}
+
+		parent?.subTasks?.remove(this)
+	}
+
+	@Ta5kBuilder
+	protected fun failure(message: String) = failure(IllegalStateException(message))
+
+	@Ta5kBuilder
+	fun cancel() = internalCancel(true)
+
+	@Ta5kBuilder
+	private fun internalCancel(removeFromParent: Boolean = true) {
+		if (state != State.Running && state != State.Paused) return
+		cancelSubTasks()
+		if (this is RootTask) return
+		unsubscribe()
+		state = State.Cancelled
+		runSafe {
+			onCancel()
+			completionCallbacks.forEach { it.invoke(this) }
+		}
+
+		if (removeFromParent) parent?.subTasks?.remove(this)
+		parent?.activate()
+	}
+
+	@Ta5kBuilder
+	private fun cancelSubTasks() {
+		subTasks.toList().forEach { it.internalCancel(removeFromParent = false) }
+		subTasks.clear()
+	}
+
+	@Ta5kBuilder
+	fun pause() {
+		if (state != State.Running) return
+		state = State.Paused
+	}
+
+	@Ta5kBuilder
+	fun activate() {
+		if (state != State.Paused) return
+		state = State.Running
+	}
+
+	@Ta5kBuilder
+	protected open fun onSubTaskSuccess(subTask: Task<*>) {
+		activate()
+	}
+
+	@Ta5kBuilder
+	protected open fun onSubTaskFailure(subTask: Task<*>, cause: Throwable) {
+		failure(cause)
+	}
+
+	@Ta5kBuilder
+	protected open fun onSubTaskCompletion(subTask: Task<*>) {
+		activate()
+	}
+
+	/**
+	 * Registers a callback for if the task succeeds.
+	 */
+	@Ta5kBuilder
+	fun onSuccess(callback: SafeContext.(R) -> Unit): Task {
+		successCallbacks.add(callback)
+		return this
+	}
+
+	/**
+	 * Registers a callback for if the task fails.
+	 *
+	 * This could be mistaken for [withRecovery] which is used to wrap the given task with another task that runs a recovery task if this one fails.
+	 */
+	@Ta5kBuilder
+	fun onFailure(callback: SafeContext.(Throwable) -> Unit): Task {
+		failureCallbacks.add(callback)
+		return this
+	}
+
+	/**
+	 * Registers a callback for when the task completes.
+	 *
+	 * This is called regardless of whether the task succeeds or fails.
+	 */
+	@Ta5kBuilder
+	fun onCompletion(callback: SafeContext.() -> Unit): Task {
+		completionCallbacks.add(callback)
+		return this
+	}
+
+	override fun toString() =
+		buildString { appendTaskTree(this@Task) }
+
+	private fun StringBuilder.appendTaskTree(task: Task<*>, level: Int = 0, maxEntries: Int = 10) {
+		if (task.state == State.Cancelled) return
+		appendLine("${" ".repeat(level * 4)}${task.name}" + if (task !is RootTask) " [${task.state.name}] ${(task.age * 50).milliseconds}" else "")
+		val left = task.subTasks.size - maxEntries
+		if (left > 0) {
+			appendLine("${" ".repeat((level + 1) * 4)}...and $left more tasks")
+		}
+		task.subTasks.takeLast(maxEntries).forEach {
+			appendTaskTree(it, level + 1)
+		}
+	}
 }
 
 typealias TaskGenerator = SafeContext.(R) -> Task
diff --git a/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt b/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt
index e4ca93e65..e61276543 100644
--- a/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt
+++ b/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt
@@ -156,6 +156,8 @@ class BuildTask @Ta5kBuilder internal constructor(
     }
 
     init {
+        onCompletion { BaritoneHandler.cancel() }
+
         listen {
             if (!firstSim || !async) {
                 if (checkEmpty() || !async) return@listen
diff --git a/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt b/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt
index 15dbea392..c6209d0db 100644
--- a/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt
+++ b/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt
@@ -20,7 +20,6 @@ package com.lambda.task.tasks
 import baritone.api.pathing.goals.GoalBlock
 import com.lambda.context.Automated
 import com.lambda.context.AutomatedSafeContext
-import com.lambda.context.SafeContext
 import com.lambda.event.events.TickEvent
 import com.lambda.event.listener.SafeListener.Companion.listen
 import com.lambda.interaction.container.selection.ContainerSelection
@@ -53,6 +52,8 @@ class CollectDropsTask @Ta5kBuilder internal constructor(
 	override val name get() = "Collecting ${drops.size} item drop(s)"
 
 	init {
+		onCompletion { BaritoneHandler.cancel() }
+
 		listen {
 			runSafeAutomated {
 				do {
@@ -98,8 +99,4 @@ class CollectDropsTask @Ta5kBuilder internal constructor(
 
 		return true
 	}
-
-	override fun SafeContext.onCancel() {
-		BaritoneHandler.cancel()
-	}
 }
diff --git a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt
index ec4c86b38..20261772b 100644
--- a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt
+++ b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt
@@ -20,6 +20,7 @@ package com.lambda.task.tasks
 import com.lambda.context.Automated
 import com.lambda.context.AutomatedSafeContext
 import com.lambda.context.SafeContext
+import com.lambda.Lambda.LOG
 import com.lambda.event.EventFlow.post
 import com.lambda.event.events.ContainerEvent
 import com.lambda.event.events.TickEvent
@@ -35,10 +36,12 @@ import com.lambda.interaction.container.selection.mutate
 import com.lambda.interaction.container.selection.selectContainers
 import com.lambda.interaction.container.selection.stackSelection
 import com.lambda.interaction.handler.handlers.PacketLimitHandler.availablePackets
+import com.lambda.interaction.handler.handlers.ContainerHandler
 import com.lambda.interaction.handler.handlers.PacketType
 import com.lambda.interaction.handler.handlers.findContainers
 import com.lambda.interaction.manager.managers.inventory.InvRequestFailureReason
 import com.lambda.interaction.manager.managers.inventory.inventoryRequest
+import com.lambda.module.modules.client.Client.verboseDebug
 import com.lambda.task.Task
 import com.lambda.task.Task.Ta5kBuilder
 import com.lambda.task.tasks.wrappers.taskOrNull
@@ -48,6 +51,7 @@ import com.lambda.threading.runSafeAutomated
 import com.lambda.util.item.ItemStackUtils.equal
 import com.lambda.util.item.ItemStackUtils.hasSpace
 import com.lambda.util.item.StackMovePlanner
+import com.lambda.util.extension.containerSlots
 import net.minecraft.component.DataComponentTypes
 import net.minecraft.item.ItemStack
 import net.minecraft.screen.slot.Slot
@@ -129,19 +133,70 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 				}
 			)
 		if (fromQueue.isEmpty() || (!unlimited && !(fromStack isIn fromQueue))) {
-			fail("Could not find source containers for $fromStack")
+			if (verboseDebug) {
+				LOG.info("[transfer] no source for $fromStack ${describeState()} fromSel=$fromSelection")
+				findContainers(fromSelection).forEach {
+					LOG.info("[transfer] candidate ${it.name} ${describeContainerState(it)} count=${it.count(fromStack.mutate(1))} slots=${describeSlots(it)}")
+				}
+			}
+			fail("Could not find source containers for $fromStack (${describeState()})")
 			return
 		}
 
 		toQueue += findContainers(toSelection).filter { canReceive(it) }
+		if (verboseDebug) {
+			LOG.info("[transfer] start fromStack=$fromStack from=${fromQueue.map { it.name to it.isAccessible }} to=${toQueue.map { it.name to it.isAccessible }} screen=${player.currentScreenHandler.syncId}")
+			fromQueue.forEach { LOG.info("[transfer] from ${it.name} ${describeContainerState(it)} slots=${describeSlots(it)} stacks=${describeStacks(it)}") }
+			toQueue.forEach { LOG.info("[transfer] to ${it.name} space=${it.spaceLeft(fromStack.mutate(1))} ${describeContainerState(it)} slots=${describeSlots(it)}") }
+		}
 		if (toQueue.isEmpty()) {
-			fail("Could not find destination containers with space for $fromStack")
+			fail("Could not find destination containers with space for $fromStack (${describeState()})")
 			return
 		}
 
 		nextStep()
 	}
 
+	private fun SafeContext.describeState() =
+		"fromQueue=${fromQueue.size} toQueue=${toQueue.size} " +
+			"cursor=${player.currentScreenHandler.cursorStack} screen=${player.currentScreenHandler.syncId}"
+
+	private fun describeSlots(container: Container): String {
+		return try {
+			container.slots.joinToString { slot ->
+				"[id=${slot.id} idx=${slot.index} ${slot.stack.item} x${slot.stack.count} '${slot.stack.name.string}']"
+			}
+		} catch (_: Exception) {
+			"unreadable"
+		}
+	}
+
+	private fun SafeContext.describeContainerState(container: Container): String {
+		return try {
+			val handler = player.currentScreenHandler
+			val handlerSlots = handler.slots.size
+			val containerSlots = try {
+				handler.containerSlots.size
+			} catch (_: Exception) {
+				-1
+			}
+			val last = ContainerHandler.lastInteractedBlockEntity
+			"isAccessed=${container.isAccessed} handlerSync=${handler.syncId} handlerType=${handler.type} slots=$handlerSlots containerSlots=$containerSlots last=${last?.javaClass?.simpleName}@${last?.pos}"
+		} catch (e: Exception) {
+			"state-unreadable ${e.message}"
+		}
+	}
+
+	private fun describeStacks(container: Container): String {
+		return try {
+			container.stacks.mapIndexed { index, stack ->
+				"[$index ${stack.item} x${stack.count} '${stack.name.string}']"
+			}.joinToString()
+		} catch (_: Exception) {
+			"unreadable"
+		}
+	}
+
 	private fun SafeContext.nextStep() {
 		if (isComplete) {
 			success(buildResult())
@@ -152,14 +207,14 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 			?: run {
 				when {
 					unlimited && transferred > 0 -> success(buildResult())
-					toQueue.isEmpty() -> fail("No destination container has space left for $fromStack ($progressText)")
-					else -> fail("Ran out of source containers for $fromStack ($progressText)")
+					toQueue.isEmpty() -> fail("No destination container has space left for $fromStack ($progressText, ${describeState()})")
+					else -> fail("Ran out of source containers for $fromStack ($progressText, ${describeState()})")
 				}
 				return
 			}
 		val to = toQueue.firstOrNull { !it.haveMatchingInventories(from) }
 			?: run {
-				fail("No destination container has space left for $fromStack ($progressText)")
+				fail("No destination container has space left for $fromStack ($progressText, ${describeState()})")
 				return
 			}
 		lastSource = from
@@ -365,6 +420,13 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 				}
 
 			if (pair == null) {
+				if (verboseDebug) {
+					val fromSeen = fromContainers.map { it.name to (it.findSlot(currentSelection) != null) }
+					val cursorStack = CursorContainer.stacks.firstOrNull() ?: ItemStack.EMPTY
+					LOG.info("[transfer] no pair for $currentSelection from=$fromSeen to=${toContainers.map { it.name }} cursor=$cursorStack")
+					fromContainers.forEach { LOG.info("[transfer] dump from ${it.name} ${describeContainerState(it)} slots=${describeSlots(it)} stacks=${describeStacks(it)}") }
+					toContainers.forEach { LOG.info("[transfer] dump to ${it.name} ${describeContainerState(it)} canSwap=${fromContainers.firstOrNull()?.canSwapWith(it)} slots=${describeSlots(it)}") }
+				}
 				finish()
 				return true
 			}
diff --git a/src/main/kotlin/com/lambda/task/tasks/OpenContainerTask.kt b/src/main/kotlin/com/lambda/task/tasks/OpenContainerTask.kt
index 6594df70d..3d4374637 100644
--- a/src/main/kotlin/com/lambda/task/tasks/OpenContainerTask.kt
+++ b/src/main/kotlin/com/lambda/task/tasks/OpenContainerTask.kt
@@ -47,7 +47,7 @@ class OpenContainerTask @Ta5kBuilder internal constructor(
     private val blockPos: BlockPos,
     private val waitForSlotLoad: Boolean = true,
     private val sides: Set = Direction.entries.toSet(),
-    private val automated: Automated
+    automated: Automated
 ) : Task(), Automated by automated {
     override val name get() = "${containerState.description()} at ${blockPos.toShortString()}"
 
@@ -55,6 +55,7 @@ class OpenContainerTask @Ta5kBuilder internal constructor(
     private var containerState = State.Scoping
 
     private val retryTimer = TickTimer()
+    private var interactions = 0
 
     enum class State {
         Pathing,
@@ -71,6 +72,8 @@ class OpenContainerTask @Ta5kBuilder internal constructor(
     }
 
     init {
+        onCompletion { BaritoneHandler.cancel() }
+
         listen {
             if (containerState != State.Opening) return@listen
 
@@ -119,6 +122,14 @@ class OpenContainerTask @Ta5kBuilder internal constructor(
             player.swingHand(Hand.MAIN_HAND)
 
             containerState = State.Opening
+            if (++interactions > MAX_OPEN_ATTEMPTS) {
+                failure("Failed to open container at ${blockPos.toShortString()} after $interactions attempts")
+                return@listen
+            }
         }
     }
+
+    companion object {
+        private const val MAX_OPEN_ATTEMPTS = 20
+    }
 }
diff --git a/src/main/kotlin/com/lambda/task/tasks/wrappers/SoftFailTask.kt b/src/main/kotlin/com/lambda/task/tasks/wrappers/SoftFailTask.kt
index 863840d48..bc15f97f3 100644
--- a/src/main/kotlin/com/lambda/task/tasks/wrappers/SoftFailTask.kt
+++ b/src/main/kotlin/com/lambda/task/tasks/wrappers/SoftFailTask.kt
@@ -37,11 +37,11 @@ class SoftFailTask @Ta5kBuilder internal constructor(
 ) : Task() {
     override val name get() = "Soft fail protection for ${innerTask.name}"
 
-    override fun SafeContext.onStart() {
-        innerTask
-            .onSuccess { success(it) }
+	override fun SafeContext.onStart() {
+		innerTask
+			.onSuccess { success(it) }
             .start()
-    }
+	}
 
     override fun onSubTaskFailure(subTask: Task<*>, cause: Throwable) {
         if (subTask == innerTask) success(null)
diff --git a/src/test/kotlin/InventoryLedgerTest.kt b/src/test/kotlin/InventoryLedgerTest.kt
index e8b224650..a1142ff95 100644
--- a/src/test/kotlin/InventoryLedgerTest.kt
+++ b/src/test/kotlin/InventoryLedgerTest.kt
@@ -252,7 +252,7 @@ class InventoryLedgerTest {
         ledger.record(0, cursor, shulker("gear"), empty)
 
         // The first full (cursor still holding the shulker) arrives after the second click.
-        assertEquals(Drop, ledger.decideSingle(0, cursor, shulker("gear")))
+        assertEquals(Drop, ledger.decideCursor(0, shulker("gear")))
         // The destination slot in the same stale full is merged the same way.
         assertEquals(
             listOf(true, true, true, true, true, true, true, true, true, true, false),
@@ -270,17 +270,17 @@ class InventoryLedgerTest {
         ledger.record(0, cursor, shulker("gear"), empty)
 
         // First full arrives late: cursor grab is stale, destination already holds the shulker.
-        assertEquals(Drop, ledger.decideSingle(0, cursor, shulker("gear")))
+        assertEquals(Drop, ledger.decideCursor(0, shulker("gear")))
         assertEquals(listOf(true, true, true, false), ledger.decideFull(0, listOf(empty, empty, empty, stone(5)), listOf(empty, empty, empty, shulker("gear"))))
 
         // Second full (everything current) drains the rest; nothing is left to false-match later.
-        assertEquals(Drop, ledger.decideSingle(0, cursor, empty))
+        assertEquals(Drop, ledger.decideCursor(0, empty))
         assertEquals(listOf(true, true, true, true), ledger.decideFull(0, listOf(empty, empty, empty, shulker("gear")), listOf(empty, empty, empty, shulker("gear"))))
         assertEquals(0, ledger.totalPending())
 
         // A later genuine cursor correction still applies.
         ledger.record(0, cursor, empty, dirt(2))
-        assertEquals(Apply, ledger.decideSingle(0, cursor, stone(30)))
+        assertEquals(Apply, ledger.decideCursor(0, stone(30)))
         assertEquals(0, ledger.pendingCount(0, cursor))
     }
 
@@ -290,8 +290,25 @@ class InventoryLedgerTest {
         val ledger = ledger(clock)
 
         ledger.record(0, cursor, empty, shulker("gear"))
-        // The server rejected the grab: the cursor is still empty, which we never predicted.
-        assertEquals(Apply, ledger.decideSingle(0, cursor, empty))
+        // Something genuinely new that matches neither end of the prediction.
+        assertEquals(Apply, ledger.decideCursor(0, dirt(2)))
+        assertEquals(0, ledger.pendingCount(0, cursor))
+    }
+
+    @Test
+    fun `redundant full keeps the newer prediction without consuming it`() {
+        val clock = ManualClock()
+        val ledger = ledger(clock)
+
+        // Grab, then place: two predictions. A no-op click in between makes the server send
+        // a full carrying the intermediate cursor, which must neither apply nor consume.
+        ledger.record(0, cursor, empty, shulker("gear"))
+        ledger.record(0, cursor, shulker("gear"), empty)
+        assertEquals(Drop, ledger.decideCursor(0, shulker("gear")))
+        assertEquals(1, ledger.pendingCount(0, cursor))
+        assertEquals(Drop, ledger.decideCursor(0, shulker("gear")))
+        assertEquals(1, ledger.pendingCount(0, cursor))
+        assertEquals(Drop, ledger.decideCursor(0, empty))
         assertEquals(0, ledger.pendingCount(0, cursor))
     }
 
@@ -301,7 +318,7 @@ class InventoryLedgerTest {
         val ledger = ledger(clock)
 
         ledger.record(5, cursor, empty, shulker("gear"))
-        assertEquals(Apply, ledger.decideSingle(0, cursor, shulker("gear")))
+        assertEquals(Apply, ledger.decideCursor(0, shulker("gear")))
         assertEquals(Drop, ledger.decideSingle(5, cursor, shulker("gear")))
     }
 }
diff --git a/src/test/kotlin/InventorySyncFilterTest.kt b/src/test/kotlin/InventorySyncFilterTest.kt
index 378bcf0f4..0177adf43 100644
--- a/src/test/kotlin/InventorySyncFilterTest.kt
+++ b/src/test/kotlin/InventorySyncFilterTest.kt
@@ -15,6 +15,7 @@
  * along with this program.  If not, see .
  */
 
+import com.lambda.interaction.manager.managers.inventory.InventoryLedger
 import com.lambda.interaction.manager.managers.inventory.InventorySyncFilter
 import kotlin.test.Test
 import kotlin.test.assertEquals
@@ -131,4 +132,35 @@ class InventorySyncFilterTest {
         filter.clear(0)
         assertEquals(0, filter.totalPending())
     }
+
+    @Test
+    fun `stale cursor-only update is dropped`() {
+        val clock = ManualClock()
+        val filter = filter(clock)
+        val cursor = InventoryLedger.CURSOR_SLOT_ID
+
+        filter.observeLocal(0, playerSlots(0, empty), empty)
+        filter.observeLocal(0, playerSlots(0, empty), shulker("gear"))
+        filter.observeLocal(0, playerSlots(0, empty), empty)
+
+        // A cursor echo from after the grab arrives after the place: stale, drop it.
+        assertEquals(false, filter.onCursor(0, shulker("gear"), empty))
+        assertEquals(false, filter.onCursor(0, empty, empty))
+        assertEquals(0, filter.pendingCount(0, cursor))
+    }
+
+    @Test
+    fun `novel cursor applies and refreshes the snapshot`() {
+        val clock = ManualClock()
+        val filter = filter(clock)
+
+        filter.observeLocal(0, playerSlots(0, empty), empty)
+        filter.observeLocal(0, playerSlots(0, empty), shulker("gear"))
+
+        // The server rejected the grab and reports something unpredicted: apply it...
+        assertEquals(true, filter.onCursor(0, TestStack("dirt", 1), shulker("gear")))
+        // ...and the snapshot followed, so the next diff records no phantom.
+        filter.observeLocal(0, playerSlots(0, empty), TestStack("dirt", 1))
+        assertEquals(0, filter.totalPending())
+    }
 }

From e9035ade8edbc1c8514e3b69ce1dc70a69959867 Mon Sep 17 00:00:00 2001
From: beanbag44 <107891830+beanbag44@users.noreply.github.com>
Date: Fri, 9 Oct 2026 23:32:22 +0100
Subject: [PATCH 32/43] smaller debug logs

---
 .../kotlin/com/lambda/task/tasks/BuildTask.kt |  3 +-
 .../task/tasks/ContainerTransferTask.kt       | 59 +------------------
 2 files changed, 5 insertions(+), 57 deletions(-)

diff --git a/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt b/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt
index e61276543..51670cc7b 100644
--- a/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt
+++ b/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt
@@ -39,6 +39,7 @@ import com.lambda.interaction.construction.simulation.result.Dependent
 import com.lambda.interaction.construction.simulation.result.Drawable
 import com.lambda.interaction.construction.simulation.result.Navigable
 import com.lambda.interaction.construction.simulation.result.Resolvable
+import com.lambda.module.modules.client.Client.verboseDebug
 import com.lambda.interaction.construction.simulation.result.results.BreakResult
 import com.lambda.interaction.construction.simulation.result.results.GenericResult
 import com.lambda.interaction.construction.simulation.result.results.InteractResult
@@ -348,7 +349,7 @@ class BuildTask @Ta5kBuilder internal constructor(
             is Dependent -> handleResult(result.lastDependency, allResults)
 
             is Resolvable -> {
-	            LOG.info("Resolving: ${result.name}")
+	            if (verboseDebug) LOG.info("Resolving: ${result.name}")
                 result.resolve()
                     .softFail()
                     .start()
diff --git a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt
index 20261772b..614ced64d 100644
--- a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt
+++ b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt
@@ -36,7 +36,6 @@ import com.lambda.interaction.container.selection.mutate
 import com.lambda.interaction.container.selection.selectContainers
 import com.lambda.interaction.container.selection.stackSelection
 import com.lambda.interaction.handler.handlers.PacketLimitHandler.availablePackets
-import com.lambda.interaction.handler.handlers.ContainerHandler
 import com.lambda.interaction.handler.handlers.PacketType
 import com.lambda.interaction.handler.handlers.findContainers
 import com.lambda.interaction.manager.managers.inventory.InvRequestFailureReason
@@ -51,7 +50,6 @@ import com.lambda.threading.runSafeAutomated
 import com.lambda.util.item.ItemStackUtils.equal
 import com.lambda.util.item.ItemStackUtils.hasSpace
 import com.lambda.util.item.StackMovePlanner
-import com.lambda.util.extension.containerSlots
 import net.minecraft.component.DataComponentTypes
 import net.minecraft.item.ItemStack
 import net.minecraft.screen.slot.Slot
@@ -133,22 +131,13 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 				}
 			)
 		if (fromQueue.isEmpty() || (!unlimited && !(fromStack isIn fromQueue))) {
-			if (verboseDebug) {
-				LOG.info("[transfer] no source for $fromStack ${describeState()} fromSel=$fromSelection")
-				findContainers(fromSelection).forEach {
-					LOG.info("[transfer] candidate ${it.name} ${describeContainerState(it)} count=${it.count(fromStack.mutate(1))} slots=${describeSlots(it)}")
-				}
-			}
+			if (verboseDebug) LOG.info("[transfer] no source for $fromStack ${describeState()}")
 			fail("Could not find source containers for $fromStack (${describeState()})")
 			return
 		}
 
 		toQueue += findContainers(toSelection).filter { canReceive(it) }
-		if (verboseDebug) {
-			LOG.info("[transfer] start fromStack=$fromStack from=${fromQueue.map { it.name to it.isAccessible }} to=${toQueue.map { it.name to it.isAccessible }} screen=${player.currentScreenHandler.syncId}")
-			fromQueue.forEach { LOG.info("[transfer] from ${it.name} ${describeContainerState(it)} slots=${describeSlots(it)} stacks=${describeStacks(it)}") }
-			toQueue.forEach { LOG.info("[transfer] to ${it.name} space=${it.spaceLeft(fromStack.mutate(1))} ${describeContainerState(it)} slots=${describeSlots(it)}") }
-		}
+		if (verboseDebug) LOG.info("[transfer] start $fromStack from=${fromQueue.map { it.name }} to=${toQueue.map { it.name }} screen=${player.currentScreenHandler.syncId}")
 		if (toQueue.isEmpty()) {
 			fail("Could not find destination containers with space for $fromStack (${describeState()})")
 			return
@@ -161,42 +150,6 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 		"fromQueue=${fromQueue.size} toQueue=${toQueue.size} " +
 			"cursor=${player.currentScreenHandler.cursorStack} screen=${player.currentScreenHandler.syncId}"
 
-	private fun describeSlots(container: Container): String {
-		return try {
-			container.slots.joinToString { slot ->
-				"[id=${slot.id} idx=${slot.index} ${slot.stack.item} x${slot.stack.count} '${slot.stack.name.string}']"
-			}
-		} catch (_: Exception) {
-			"unreadable"
-		}
-	}
-
-	private fun SafeContext.describeContainerState(container: Container): String {
-		return try {
-			val handler = player.currentScreenHandler
-			val handlerSlots = handler.slots.size
-			val containerSlots = try {
-				handler.containerSlots.size
-			} catch (_: Exception) {
-				-1
-			}
-			val last = ContainerHandler.lastInteractedBlockEntity
-			"isAccessed=${container.isAccessed} handlerSync=${handler.syncId} handlerType=${handler.type} slots=$handlerSlots containerSlots=$containerSlots last=${last?.javaClass?.simpleName}@${last?.pos}"
-		} catch (e: Exception) {
-			"state-unreadable ${e.message}"
-		}
-	}
-
-	private fun describeStacks(container: Container): String {
-		return try {
-			container.stacks.mapIndexed { index, stack ->
-				"[$index ${stack.item} x${stack.count} '${stack.name.string}']"
-			}.joinToString()
-		} catch (_: Exception) {
-			"unreadable"
-		}
-	}
-
 	private fun SafeContext.nextStep() {
 		if (isComplete) {
 			success(buildResult())
@@ -420,13 +373,7 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 				}
 
 			if (pair == null) {
-				if (verboseDebug) {
-					val fromSeen = fromContainers.map { it.name to (it.findSlot(currentSelection) != null) }
-					val cursorStack = CursorContainer.stacks.firstOrNull() ?: ItemStack.EMPTY
-					LOG.info("[transfer] no pair for $currentSelection from=$fromSeen to=${toContainers.map { it.name }} cursor=$cursorStack")
-					fromContainers.forEach { LOG.info("[transfer] dump from ${it.name} ${describeContainerState(it)} slots=${describeSlots(it)} stacks=${describeStacks(it)}") }
-					toContainers.forEach { LOG.info("[transfer] dump to ${it.name} ${describeContainerState(it)} canSwap=${fromContainers.firstOrNull()?.canSwapWith(it)} slots=${describeSlots(it)}") }
-				}
+				if (verboseDebug) LOG.info("[transfer] no pair for $currentSelection from=${fromContainers.map { it.name }} to=${toContainers.map { it.name }}")
 				finish()
 				return true
 			}

From 048bfd0651d69404c758bc438e20288137b09a72 Mon Sep 17 00:00:00 2001
From: beanbag44 <107891830+beanbag44@users.noreply.github.com>
Date: Sat, 10 Oct 2026 12:00:41 +0100
Subject: [PATCH 33/43] move open and close callbacks into open functions
 rather than params

---
 .../com/lambda/interaction/manager/Manager.kt | 12 ++++++-----
 .../manager/managers/breaking/BreakManager.kt | 20 +++++++++----------
 .../manager/managers/hotbar/HotbarManager.kt  | 15 ++++++++------
 .../managers/interacting/InteractManager.kt   | 11 ++++++----
 .../managers/inventory/InventoryManager.kt    | 17 ++++++++++------
 .../kotlin/com/lambda/task/tasks/BuildTask.kt |  2 +-
 .../task/tasks/ContainerTransferTask.kt       |  2 +-
 7 files changed, 46 insertions(+), 33 deletions(-)

diff --git a/src/main/kotlin/com/lambda/interaction/manager/Manager.kt b/src/main/kotlin/com/lambda/interaction/manager/Manager.kt
index cab1aa21f..b70bbdec1 100644
--- a/src/main/kotlin/com/lambda/interaction/manager/Manager.kt
+++ b/src/main/kotlin/com/lambda/interaction/manager/Manager.kt
@@ -35,9 +35,7 @@ import kotlin.reflect.KClass
  */
 abstract class Manager(
 	val stagePriority: Int,
-	vararg val blacklistedStages: TickEvent,
-	private val onOpen: (SafeContext.() -> Unit)? = null,
-	private val onClose: (SafeContext.() -> Unit)? = null
+	vararg val blacklistedStages: TickEvent
 ) : Loadable {
 	val openStages: List = ALL_STAGES.filter { it !in blacklistedStages }
 
@@ -86,15 +84,19 @@ abstract class Manager(
                 queuedRequest = null
             }
             acceptingRequests = true
-            onOpen?.invoke(this)
+            onOpen()
         }
 
         listen(instance, { (Int.MIN_VALUE + 1) + stagePriority }) {
-            onClose?.invoke(this)
+            onClose()
             acceptingRequests = false
         }
     }
 
+	protected open fun SafeContext.onOpen() {}
+
+	protected open fun SafeContext.onClose() {}
+
 	/**
 	 * Registers a new request
 	 *
diff --git a/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakManager.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakManager.kt
index 80da7a4bc..9a2f802e8 100644
--- a/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakManager.kt
+++ b/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakManager.kt
@@ -57,7 +57,6 @@ import com.lambda.interaction.manager.managers.breaking.BreakManager.activeReque
 import com.lambda.interaction.manager.managers.breaking.BreakManager.breakInfos
 import com.lambda.interaction.manager.managers.breaking.BreakManager.breaks
 import com.lambda.interaction.manager.managers.breaking.BreakManager.canAccept
-import com.lambda.interaction.manager.managers.breaking.BreakManager.checkForCancels
 import com.lambda.interaction.manager.managers.breaking.BreakManager.handlePreProcessing
 import com.lambda.interaction.manager.managers.breaking.BreakManager.hotbarRequest
 import com.lambda.interaction.manager.managers.breaking.BreakManager.initNewBreak
@@ -67,7 +66,6 @@ import com.lambda.interaction.manager.managers.breaking.BreakManager.populateFro
 import com.lambda.interaction.manager.managers.breaking.BreakManager.processNewBreak
 import com.lambda.interaction.manager.managers.breaking.BreakManager.processRequest
 import com.lambda.interaction.manager.managers.breaking.BreakManager.rotationRequest
-import com.lambda.interaction.manager.managers.breaking.BreakManager.simulateAbandoned
 import com.lambda.interaction.manager.managers.breaking.BreakManager.updateBreakProgress
 import com.lambda.interaction.manager.managers.breaking.BreakManager.updatePreProcessing
 import com.lambda.interaction.manager.managers.breaking.SwapInfo.Companion.getSwapInfo
@@ -105,14 +103,7 @@ import kotlin.math.max
  * different requests. Each break will be handled using its own config, and just like the other managers, priority is a first-come, first-served
  * style system.
  */
-object BreakManager : Manager(
-	0,
-	onOpen = {
-		processRequest(activeRequest)
-		simulateAbandoned()
-			 },
-	onClose = { checkForCancels() }
-), PositionBlocking {
+object BreakManager : Manager(0), PositionBlocking {
 	private val breakInfos = arrayOfNulls(2)
 
 	private val activeInfos
@@ -289,6 +280,15 @@ object BreakManager : Manager(
 		return "Loaded Break Manager"
 	}
 
+	override fun SafeContext.onOpen() {
+		processRequest(activeRequest)
+		simulateAbandoned()
+	}
+
+	override fun SafeContext.onClose() {
+		checkForCancels()
+	}
+
 	/**
 	 * Attempts to accept and process the request, if there is not already an [activeRequest] and the
 	 * [BreakRequest.contexts] collection is not empty. If nowOrNothing is true, the request is cleared
diff --git a/src/main/kotlin/com/lambda/interaction/manager/managers/hotbar/HotbarManager.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/hotbar/HotbarManager.kt
index 76c69b950..bd536ccc9 100644
--- a/src/main/kotlin/com/lambda/interaction/manager/managers/hotbar/HotbarManager.kt
+++ b/src/main/kotlin/com/lambda/interaction/manager/managers/hotbar/HotbarManager.kt
@@ -25,7 +25,6 @@ import com.lambda.event.listener.SafeListener.Companion.listen
 import com.lambda.interaction.manager.Manager
 import com.lambda.interaction.manager.managers.hotbar.HotbarManager.activeRequest
 import com.lambda.interaction.manager.managers.hotbar.HotbarManager.activeSlot
-import com.lambda.interaction.manager.managers.hotbar.HotbarManager.checkResetSwap
 import com.lambda.interaction.manager.managers.hotbar.HotbarManager.setActiveRequest
 import com.lambda.interaction.manager.managers.hotbar.HotbarManager.setActiveSlot
 import com.lambda.threading.runSafe
@@ -39,11 +38,7 @@ import net.minecraft.item.ItemStack
  * set to 0, assuming the request is accepted, the manager will only swap for the duration of the current [tickStage].
  * After which, the manager will end the request and swap back to the player's selected slot.
  */
-object HotbarManager : Manager(
-	1,
-	onOpen = { if (activeRequest != null) setActiveSlot() },
-	onClose = { checkResetSwap() }
-) {
+object HotbarManager : Manager(1) {
 	private var activeRequest: HotbarRequest? = null
 	@JvmStatic var activeSlot: Int = -1
 
@@ -80,6 +75,14 @@ object HotbarManager : Manager(
 		return "Loaded Hotbar Manager"
 	}
 
+	override fun SafeContext.onOpen() {
+		if (activeRequest != null) setActiveSlot()
+	}
+
+	override fun SafeContext.onClose() {
+		checkResetSwap()
+	}
+
 	/**
 	 * If the [activeRequest] is not null, being kept, and the [request]'s slot matches the [activeSlot], the
 	 * [request]'s swapPauseAge is set to the swapped ticks and the request is denied. Otherwise, the active
diff --git a/src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractManager.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractManager.kt
index 83a97b9f9..d427bfeed 100644
--- a/src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractManager.kt
+++ b/src/main/kotlin/com/lambda/interaction/manager/managers/interacting/InteractManager.kt
@@ -72,10 +72,7 @@ import net.minecraft.util.hit.BlockHitResult
 import net.minecraft.util.math.BlockPos
 import net.minecraft.world.GameMode
 
-object InteractManager : Manager(
-	0,
-	onOpen = { activeRequest?.let { it.runSafeAutomated { processRequest(it) } } }
-), PositionBlocking {
+object InteractManager : Manager(0), PositionBlocking {
 	private var activeRequest: InteractRequest? = null
 	private var potentialInteractions = mutableListOf()
 
@@ -124,6 +121,12 @@ object InteractManager : Manager(
 		return "Loaded Place Manager"
 	}
 
+	override fun SafeContext.onOpen() {
+		activeRequest?.let {
+			it.runSafeAutomated { processRequest(it) }
+		}
+	}
+
 	/**
 	 * Accepts, and processes the request, as long as the current [activeRequest] is null, and the [com.lambda.interaction.manager.managers.breaking.BreakManager] has not
 	 * been active this tick. If nowOrNothing is true, the request is cleared after the first process.
diff --git a/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryManager.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryManager.kt
index e5c2d49fa..5ea3ccaae 100644
--- a/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryManager.kt
+++ b/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryManager.kt
@@ -17,9 +17,9 @@
 
 package com.lambda.interaction.manager.managers.inventory
 
+import com.lambda.Lambda.LOG
 import com.lambda.context.AutomatedSafeContext
 import com.lambda.context.SafeContext
-import com.lambda.Lambda.LOG
 import com.lambda.event.events.InventoryEvent
 import com.lambda.event.events.PacketEvent
 import com.lambda.event.events.PlayerEvent
@@ -29,7 +29,11 @@ import com.lambda.interaction.handler.handlers.PacketLimitHandler.canSendPackets
 import com.lambda.interaction.handler.handlers.PacketLimitHandler.sentPackets
 import com.lambda.interaction.handler.handlers.PacketType
 import com.lambda.interaction.manager.Manager
-import com.lambda.interaction.manager.managers.inventory.InventoryManager.processActiveRequest
+import com.lambda.interaction.manager.managers.inventory.InventoryManager.actions
+import com.lambda.interaction.manager.managers.inventory.InventoryManager.activeRequest
+import com.lambda.interaction.manager.managers.inventory.InventoryManager.filter
+import com.lambda.interaction.manager.managers.inventory.InventoryManager.indexInventoryChanges
+import com.lambda.interaction.manager.managers.inventory.InventoryManager.onInventoryUpdate
 import com.lambda.module.modules.client.Client
 import com.lambda.module.modules.client.Client.verboseDebug
 import com.lambda.threading.runSafe
@@ -53,10 +57,7 @@ import net.minecraft.screen.ScreenHandler
  * ordered per-slot queue. Server answers that match a recording are echoes of our own actions and are dropped;
  * anything else is new information and is applied. See [InventorySyncFilter] for the exact rules.
  */
-object InventoryManager : Manager(
-	1,
-	onOpen = { processActiveRequest() }
-) {
+object InventoryManager : Manager(1) {
 	private var activeRequest: InventoryRequest? = null
 	private var actions = mutableListOf()
 
@@ -130,6 +131,10 @@ object InventoryManager : Manager(
 		}
 	}
 
+	override fun SafeContext.onOpen() {
+		processActiveRequest()
+	}
+
 	private fun populateFrom(request: InventoryRequest) {
 		activeRequest = request
 		actions = request.actions.toMutableList()
diff --git a/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt b/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt
index 51670cc7b..966a68200 100644
--- a/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt
+++ b/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt
@@ -39,7 +39,6 @@ import com.lambda.interaction.construction.simulation.result.Dependent
 import com.lambda.interaction.construction.simulation.result.Drawable
 import com.lambda.interaction.construction.simulation.result.Navigable
 import com.lambda.interaction.construction.simulation.result.Resolvable
-import com.lambda.module.modules.client.Client.verboseDebug
 import com.lambda.interaction.construction.simulation.result.results.BreakResult
 import com.lambda.interaction.construction.simulation.result.results.GenericResult
 import com.lambda.interaction.construction.simulation.result.results.InteractResult
@@ -50,6 +49,7 @@ import com.lambda.interaction.handler.handlers.BaritoneHandler
 import com.lambda.interaction.manager.managers.breaking.breakRequest
 import com.lambda.interaction.manager.managers.interacting.interactRequest
 import com.lambda.module.modules.client.Client
+import com.lambda.module.modules.client.Client.verboseDebug
 import com.lambda.task.Task
 import com.lambda.task.Task.Ta5kBuilder
 import com.lambda.task.tasks.wrappers.softFail
diff --git a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt
index 614ced64d..1473533ff 100644
--- a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt
+++ b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt
@@ -17,10 +17,10 @@
 
 package com.lambda.task.tasks
 
+import com.lambda.Lambda.LOG
 import com.lambda.context.Automated
 import com.lambda.context.AutomatedSafeContext
 import com.lambda.context.SafeContext
-import com.lambda.Lambda.LOG
 import com.lambda.event.EventFlow.post
 import com.lambda.event.events.ContainerEvent
 import com.lambda.event.events.TickEvent

From d12ba63ec1c288f8636672efe1dbc437e3349433 Mon Sep 17 00:00:00 2001
From: beanbag44 <107891830+beanbag44@users.noreply.github.com>
Date: Sat, 10 Oct 2026 15:23:12 +0100
Subject: [PATCH 34/43] fixes and improvements

---
 .../external/ShulkerBoxContainer.kt           |  4 +-
 .../container/selection/StackSelection.kt     |  1 +
 .../module/modules/player/InventoryTweaks.kt  | 10 +--
 .../task/tasks/ContainerTransferTask.kt       | 64 +++++++++++--------
 4 files changed, 48 insertions(+), 31 deletions(-)

diff --git a/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt
index e8df6d044..f89144ce4 100644
--- a/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt
+++ b/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt
@@ -118,7 +118,9 @@ data class ShulkerBoxContainer(
                         sortedByBestContentMatch(stacks)
                     }.transfer(
                         ContainerSelection.HOTBAR_AND_INVENTORY,
-                        fromContainer.select(ContainerSearchScope.Loaded)
+                        fromContainer.select(ContainerSearchScope.Loaded),
+                        stackSelection { sortedWith { compareByDescending { it.slot?.index == index } } },
+                        allowQuickMove = false
                     )
                 }
     }
diff --git a/src/main/kotlin/com/lambda/interaction/container/selection/StackSelection.kt b/src/main/kotlin/com/lambda/interaction/container/selection/StackSelection.kt
index a95d7ce66..2ca323773 100644
--- a/src/main/kotlin/com/lambda/interaction/container/selection/StackSelection.kt
+++ b/src/main/kotlin/com/lambda/interaction/container/selection/StackSelection.kt
@@ -175,6 +175,7 @@ class StackSelection @ContainerDslMarker internal constructor(
 	companion object {
 		val ANYTHING = StackSelection(0) { _, _ -> true }
 		val NOTHING = StackSelection { _, _ -> false }
+		val EMPTY = stackSelection { isEmpty() }
 	}
 }
 
diff --git a/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt b/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt
index 6733143db..09e30cfe3 100644
--- a/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt
+++ b/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt
@@ -28,6 +28,7 @@ import com.lambda.interaction.container.NestedContainer
 import com.lambda.interaction.container.OpenedContainerContext
 import com.lambda.interaction.container.containers.external.EnderChestContainer
 import com.lambda.interaction.container.selection.containerSelection
+import com.lambda.interaction.handler.handlers.ContainerSearchScope
 import com.lambda.interaction.handler.handlers.findContainer
 import com.lambda.module.Module
 import com.lambda.module.ModuleTag
@@ -66,14 +67,15 @@ object InventoryTweaks : Module(
             val stack = slot.stack
 
             when (stack.item) {
-                in SHULKER_BOXES if (!instantShulker) -> return@listen
-                Items.ENDER_CHEST if (!instantEChest) -> return@listen
+                in SHULKER_BOXES -> if (!instantShulker) return@listen
+                Items.ENDER_CHEST -> if (!instantEChest) return@listen
+                else -> return@listen
             }
 
             val targetContainer =
                 if (stack.item == Items.ENDER_CHEST) EnderChestContainer
                 else findContainer(
-                    containerSelection {
+                    containerSelection(ContainerSearchScope.Accessed) {
                         ofAnyType(ContainerType.ShulkerBox)
                         predicate { container ->
                             container is NestedContainer && container.index == slot.index
@@ -83,8 +85,6 @@ object InventoryTweaks : Module(
 
             event.cancel()
 
-            // Latest click wins: a previous open still running would fight this one over
-            // transfers, placement and screens, so stop it first.
             openTask?.cancel()
             openTask = targetContainer
                 .access()
diff --git a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt
index 1473533ff..09585e4d3 100644
--- a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt
+++ b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt
@@ -34,7 +34,6 @@ import com.lambda.interaction.container.selection.ContainerSelection
 import com.lambda.interaction.container.selection.StackSelection
 import com.lambda.interaction.container.selection.mutate
 import com.lambda.interaction.container.selection.selectContainers
-import com.lambda.interaction.container.selection.stackSelection
 import com.lambda.interaction.handler.handlers.PacketLimitHandler.availablePackets
 import com.lambda.interaction.handler.handlers.PacketType
 import com.lambda.interaction.handler.handlers.findContainers
@@ -60,8 +59,9 @@ fun transfer(
 	fromStack: StackSelection,
 	fromSelection: ContainerSelection = ContainerSelection.ACCESSED,
 	toSelection: ContainerSelection,
-	toStack: StackSelection = StackSelection.ANYTHING
-) = ContainerTransferTask(fromStack, fromSelection, toSelection, toStack, automated)
+	toStack: StackSelection = StackSelection.ANYTHING,
+	allowQuickMove: Boolean = true,
+) = ContainerTransferTask(fromStack, fromSelection, toSelection, toStack, allowQuickMove, automated)
 
 @Ta5kBuilder
 @JvmName("transferExt")
@@ -69,8 +69,9 @@ context(automated: Automated)
 fun StackSelection.transfer(
 	fromSelection: ContainerSelection = ContainerSelection.ACCESSED,
 	toSelection: ContainerSelection,
-	toStack: StackSelection = StackSelection.ANYTHING
-) = transfer(this, fromSelection, toSelection, toStack)
+	toStack: StackSelection = StackSelection.ANYTHING,
+	allowQuickMove: Boolean = true,
+) = transfer(this, fromSelection, toSelection, toStack, allowQuickMove)
 
 class TransferResult internal constructor(
 	val stackSelection: StackSelection,
@@ -82,6 +83,7 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 	private val fromSelection: ContainerSelection,
 	private val toSelection: ContainerSelection,
 	private val toStack: StackSelection,
+	private val allowQuickMove: Boolean,
 	automated: Automated
 ) : Task(), Automated by automated {
 	override val name = "Transferring $fromStack"
@@ -327,11 +329,9 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 		override val name get() = "Moving ${this@MoveTask.fromSelection}"
 		private var moved = 0
 
-		private val toSelection =
-			toSelection.mutate {
-				notEmpty()
-				withoutSelection(this@MoveTask.fromSelection)
-			}
+		// Raw caller ordering; the destination search only narrows it,
+		// so toStack stays the end-all for which slot gets used.
+		private val baseToSelection = toSelection
 
 		init {
 			listen {
@@ -381,30 +381,44 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 			return move(pair, atMost = currentLimit)
 		}
 
-		private fun AutomatedSafeContext.findToSlot(fromSlot: Slot, toContainer: Container): Slot? {
+		private fun findToSlot(fromSlot: Slot, toContainer: Container): Slot? {
 			val stack = fromSlot.stack
-			val partialStack =
-				stackSelection {
-					predicate { candidate, _ -> !candidate.isEmpty && candidate.hasSpace && candidate.sameItemAs(stack) }
+			return toContainer.findSlot(
+				baseToSelection.mutate {
+					predicate { candidate, slot ->
+						when {
+							candidate.isEmpty -> true
+							candidate.sameItemAs(stack) -> candidate.hasSpace
+							else -> allowReplace &&
+								slot != null &&
+								!fromSelection.matches(slot) &&
+								fromSlot.canInsert(candidate)
+						}
+					}
 					canInsert(stack)
 				}
-			return toContainer.findSlot(partialStack)
-				?: toContainer.findReplaceSlot(stack, stackSelection { isEmpty() })
-				?: findSwappableSlot(fromSlot, toContainer)
-		}
-
-		private fun AutomatedSafeContext.findSwappableSlot(fromSlot: Slot, toContainer: Container): Slot? {
-			if (!allowReplace) return null
-			return toContainer.findReplaceSlot(fromSlot.stack, toSelection)?.takeIf { fromSlot.canInsert(it.stack) }
+			)
 		}
 
 		private fun AutomatedSafeContext.move(pair: SlotPair, atMost: Int): Boolean {
 			val stack = pair.fromSlot.stack
 			val count = minOf(stack.count, atMost)
 			val cursor = CursorContainer.stacks.firstOrNull() ?: ItemStack.EMPTY
-			return when {
-				pair.toSlot.stack.isForeignTo(stack) -> swapOut(pair)
-				count == stack.count && cursor.isEmpty && quickMoveStaysInside(pair.fromContainer, stack) -> quickMoveWhole(pair)
+			if (pair.toSlot.stack.isForeignTo(stack)) {
+				if (count != stack.count) {
+					finish()
+					return true
+				}
+				return swapOut(pair)
+			}
+			return when (count) {
+				stack.count if cursor.isEmpty &&
+						allowQuickMove &&
+						quickMoveStaysInside(pair.fromContainer, stack) -> quickMoveWhole(pair)
+
+				stack.count if cursor.isEmpty &&
+						pair.toSlot.stack.isEmpty -> swapOut(pair)
+
 				else -> clickThrough(pair, count)
 			}
 		}

From 5f90cf4fe3acb1c650605d331b630b452f65861d Mon Sep 17 00:00:00 2001
From: beanbag44 <107891830+beanbag44@users.noreply.github.com>
Date: Sat, 10 Oct 2026 15:29:31 +0100
Subject: [PATCH 35/43] only place shulker in original slot if the slot is
 empty

---
 .../container/containers/external/ShulkerBoxContainer.kt     | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt
index f89144ce4..4b68cc83f 100644
--- a/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt
+++ b/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt
@@ -83,7 +83,10 @@ data class ShulkerBoxContainer(
             transfer(
                 stackSelection {
                     sortedWith {
-                        compareByDescending { stackAndSlot -> stackAndSlot.slot?.index == index }
+                        compareByDescending { stackAndSlot ->
+                            stackAndSlot.slot?.index == index &&
+                                    stackAndSlot.slot.stack?.isEmpty != false
+                        }
                     }
                     isItem(shulkerItem)
                     withName(itemName)

From afcdd71121b53ba410baa5dc18dc5d255e8dd770 Mon Sep 17 00:00:00 2001
From: beanbag44 <107891830+beanbag44@users.noreply.github.com>
Date: Sat, 10 Oct 2026 15:32:50 +0100
Subject: [PATCH 36/43] whoops, fixed it in the wrong place

---
 .../containers/external/ShulkerBoxContainer.kt   | 16 +++++++++-------
 1 file changed, 9 insertions(+), 7 deletions(-)

diff --git a/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt
index 4b68cc83f..233d78d7a 100644
--- a/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt
+++ b/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt
@@ -82,12 +82,7 @@ data class ShulkerBoxContainer(
         override fun SafeContext.onStart() {
             transfer(
                 stackSelection {
-                    sortedWith {
-                        compareByDescending { stackAndSlot ->
-                            stackAndSlot.slot?.index == index &&
-                                    stackAndSlot.slot.stack?.isEmpty != false
-                        }
-                    }
+                    sortedWith { compareByDescending { it.slot?.index == index } }
                     isItem(shulkerItem)
                     withName(itemName)
                     sortedByBestContentMatch(stacks)
@@ -122,7 +117,14 @@ data class ShulkerBoxContainer(
                     }.transfer(
                         ContainerSelection.HOTBAR_AND_INVENTORY,
                         fromContainer.select(ContainerSearchScope.Loaded),
-                        stackSelection { sortedWith { compareByDescending { it.slot?.index == index } } },
+                        stackSelection {
+                            sortedWith {
+                                compareByDescending {
+                                    it.slot?.index == index &&
+                                            it.slot.stack?.isEmpty != false
+                                }
+                            }
+                        },
                         allowQuickMove = false
                     )
                 }

From 7d78caf31f38fbcfd2d84795cf8056033f8bdee6 Mon Sep 17 00:00:00 2001
From: beanbag44 <107891830+beanbag44@users.noreply.github.com>
Date: Sat, 10 Oct 2026 20:01:18 +0100
Subject: [PATCH 37/43] cleaner AutomatedSafeContext

---
 .../lambda/context/AutomatedSafeContext.kt    | 15 ++++++++++----
 .../lambda/context/IAutomatedSafeContext.kt   | 20 -------------------
 .../kotlin/com/lambda/threading/Threading.kt  |  4 ++--
 3 files changed, 13 insertions(+), 26 deletions(-)
 delete mode 100644 src/main/kotlin/com/lambda/context/IAutomatedSafeContext.kt

diff --git a/src/main/kotlin/com/lambda/context/AutomatedSafeContext.kt b/src/main/kotlin/com/lambda/context/AutomatedSafeContext.kt
index 96bb48367..3d59c2143 100644
--- a/src/main/kotlin/com/lambda/context/AutomatedSafeContext.kt
+++ b/src/main/kotlin/com/lambda/context/AutomatedSafeContext.kt
@@ -17,7 +17,14 @@
 
 package com.lambda.context
 
-class AutomatedSafeContext(
-	safeContext: SafeContext,
-	automated: Automated
-) : IAutomatedSafeContext, SafeContext by safeContext, Automated by automated
\ No newline at end of file
+interface AutomatedSafeContext : SafeContext, Automated {
+	companion object {
+		fun create(
+			safeContext: SafeContext,
+			automated: Automated
+		): AutomatedSafeContext =
+			object : AutomatedSafeContext,
+				SafeContext by safeContext,
+				Automated by automated {}
+	}
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/context/IAutomatedSafeContext.kt b/src/main/kotlin/com/lambda/context/IAutomatedSafeContext.kt
deleted file mode 100644
index a41eee489..000000000
--- a/src/main/kotlin/com/lambda/context/IAutomatedSafeContext.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * 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.context
-
-interface IAutomatedSafeContext : SafeContext, Automated
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/threading/Threading.kt b/src/main/kotlin/com/lambda/threading/Threading.kt
index a03371fe0..d81cc0b5d 100644
--- a/src/main/kotlin/com/lambda/threading/Threading.kt
+++ b/src/main/kotlin/com/lambda/threading/Threading.kt
@@ -50,7 +50,7 @@ inline fun  runSafe(block: SafeContext.() -> T): T? =
 @JvmName("runSafeAutomated0")
 context(safeContext: SafeContext)
 inline fun  Automated.runSafeAutomated(automated: Automated = this, block: AutomatedSafeContext.() -> T): T =
-    AutomatedSafeContext(safeContext, automated).run(block)
+    AutomatedSafeContext.create(safeContext, automated).run(block)
 
 /**
  * Runs the [block] in an automated context.
@@ -59,7 +59,7 @@ inline fun  Automated.runSafeAutomated(automated: Automated = this, block: Au
  */
 @JvmName("runSafeAutomated1")
 inline fun  Automated.runSafeAutomated(block: AutomatedSafeContext.() -> T): T? {
-    return AutomatedSafeContext(SafeContext.create() ?: return null, this).run(block)
+    return AutomatedSafeContext.create(SafeContext.create() ?: return null, this).run(block)
 }
 
 /**

From c2150e938567ee7463ef331310c018d3edd2b522 Mon Sep 17 00:00:00 2001
From: beanbag44 <107891830+beanbag44@users.noreply.github.com>
Date: Sat, 10 Oct 2026 20:07:46 +0100
Subject: [PATCH 38/43] cleaner InventoryConfig interface

---
 .../lambda/config/blocks/InventoryConfig.kt   | 23 -------------------
 1 file changed, 23 deletions(-)

diff --git a/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt b/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt
index fdca5ff79..c63843619 100644
--- a/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt
+++ b/src/main/kotlin/com/lambda/config/blocks/InventoryConfig.kt
@@ -18,34 +18,11 @@
 package com.lambda.config.blocks
 
 import com.lambda.event.events.TickEvent
-import com.lambda.interaction.container.Container
-import com.lambda.interaction.container.selection.ContainerSelection
-import com.lambda.interaction.container.selection.containerSelection
-import com.lambda.util.Describable
-import com.lambda.util.NamedEnum
 import net.minecraft.item.Item
 
 interface InventoryConfig {
 	val tickStageMask: Collection
 	val disposables: Collection
-
 	val allowedContainers: Collection
-	val containerSelection: ContainerSelection
-		get() = containerSelection { ofAnyType(*allowedContainers.toTypedArray()) }
-
 	val enderChestSearchRadius: Int
-
-	@Suppress("unused")
-	enum class ContainerPriority(
-		override val displayName: String,
-		override val description: String
-	) : NamedEnum, Describable {
-		WithMinItems("With Min Items", "Pick containers with the fewest matching items (or least space) first; useful for topping off or clearing leftovers."),
-		WithMaxItems("With Max Items", "Pick containers with the most matching items (or most space) first; ideal for bulk moves with fewer transfers.");
-
-		fun  comparator(countSelector: (Container, T) -> Int) =
-			compareBy> { it.first.type }
-				.thenByDescending { countSelector(it.first, it.second) }
-				.thenBy { it.first.name }
-	}
 }
\ No newline at end of file

From 30f16e9c2bc3203b80a0c54dceca42e45b7b94ce Mon Sep 17 00:00:00 2001
From: beanbag44 <107891830+beanbag44@users.noreply.github.com>
Date: Thu, 15 Oct 2026 13:37:47 +0100
Subject: [PATCH 39/43] improvements

---
 .../kotlin/com/lambda/CollectDropsGameTest.kt | 277 +++++++++++++++++
 .../com/lambda/ContainerTransferGameTest.kt   |  10 +-
 .../com/lambda/ShulkerPickupGameTest.kt       |  10 +-
 src/gametest/resources/fabric.mod.json        |   4 +
 .../command/commands/TransferCommand.kt       |   2 +-
 .../lambda/gui/components/ClickGuiLayout.kt   |   8 +-
 .../com/lambda/gui/components/HudGuiLayout.kt |   8 +-
 .../construction/simulation/sims/BreakSim.kt  |   2 +-
 .../simulation/sims/InteractSim.kt            |   2 +-
 .../construction/verify/TargetState.kt        |   2 +-
 .../external/EnderChestContainer.kt           |  24 +-
 .../external/ShulkerBoxContainer.kt           |  37 ++-
 .../container/selection/ContainerSelection.kt |   7 +-
 .../container/selection/StackSelection.kt     | 140 ++++++++-
 .../handler/handlers/ContainerHandler.kt      | 284 ++++++++++--------
 .../handler/handlers/GlideHandler.kt          |   7 +-
 .../handlers/breaking/BrokenBlockHandler.kt   |   1 +
 .../managers/inventory/InventoryManager.kt    |   9 +-
 .../managers/inventory/InventoryRequest.kt    |   7 +-
 .../managers/inventory/InventorySyncFilter.kt |  22 +-
 .../module/modules/chat/ChatTimestamp.kt      |   6 +-
 .../module/modules/chat/FriendHighlight.kt    |   6 +-
 .../lambda/module/modules/combat/AutoArmor.kt |   8 +-
 .../lambda/module/modules/combat/AutoTotem.kt |   7 +-
 .../lambda/module/modules/combat/KillAura.kt  |   2 +-
 .../module/modules/combat/PlayerTrap.kt       |   2 +-
 .../lambda/module/modules/combat/Surround.kt  |   2 +-
 .../modules/combat/crystalaura/Opportunity.kt |   6 +-
 .../modules/debug/ContainerTransferTest.kt    |   5 +-
 .../module/modules/movement/BetterFirework.kt |   4 +-
 .../movement/elytrafly/ElytraFlyMode.kt       |   5 +-
 .../elytrafly/modes/GrimControlElytraFly.kt   |   2 +-
 .../module/modules/player/AutoElytraSwap.kt   |   5 +-
 .../module/modules/player/InventoryTweaks.kt  |   2 +-
 .../module/modules/player/StackReplenish.kt   |   8 +-
 .../lambda/module/modules/player/ToolSaver.kt |   4 +-
 .../lambda/module/modules/world/AutoPortal.kt |   2 +-
 .../module/modules/world/MapDownloader.kt     |   3 +-
 .../lambda/module/modules/world/Scaffold.kt   |   2 +-
 .../lambda/module/modules/world/StashMover.kt |  31 +-
 .../task/tasks/AcquirePlacedBlockTask.kt      |   2 +-
 .../kotlin/com/lambda/task/tasks/BuildTask.kt |  11 +-
 .../com/lambda/task/tasks/CollectDropsTask.kt | 190 ++++++++++--
 .../task/tasks/ContainerTransferTask.kt       |  51 ++--
 .../kotlin/com/lambda/task/tasks/EatTask.kt   |   2 +-
 .../util/collections/LimitedDecayQueue.kt     |  25 +-
 src/test/kotlin/InventorySyncFilterTest.kt    |  44 +++
 47 files changed, 999 insertions(+), 301 deletions(-)
 create mode 100644 src/gametest/kotlin/com/lambda/CollectDropsGameTest.kt

diff --git a/src/gametest/kotlin/com/lambda/CollectDropsGameTest.kt b/src/gametest/kotlin/com/lambda/CollectDropsGameTest.kt
new file mode 100644
index 000000000..eddfd4066
--- /dev/null
+++ b/src/gametest/kotlin/com/lambda/CollectDropsGameTest.kt
@@ -0,0 +1,277 @@
+/*
+ * 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 .
+ */
+
+@file:Suppress("unused")
+
+package com.lambda
+
+import com.lambda.Lambda.LOG
+import com.lambda.config.automation.AutomationConfig
+import com.lambda.context.Automated
+import com.lambda.task.RootTask
+import com.lambda.task.Task
+import com.lambda.task.start
+import com.lambda.task.tasks.PickedUpItem
+import com.lambda.task.tasks.collectDrops
+import com.lambda.threading.runSafe
+import net.fabricmc.fabric.api.client.gametest.v1.FabricClientGameTest
+import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext
+import net.fabricmc.fabric.api.client.gametest.v1.context.TestServerContext
+import net.minecraft.client.gui.screen.world.WorldCreator
+import net.minecraft.entity.ItemEntity
+import net.minecraft.item.ItemStack
+import net.minecraft.item.Items
+import net.minecraft.util.math.BlockPos
+
+/**
+ * In-game coverage for [com.lambda.task.tasks.CollectDropsTask]'s pickup report.
+ *
+ * Drops are summoned with a pickup delay so entity references can be captured before
+ * anything is collected, then the real task walks Baritone over and the payload is
+ * checked: which slot each drop landed in and in what amount.
+ *
+ * The merge scenario is the regression core: a drop joining an existing partial stack
+ * must report the delta (3), not the whole slot (63). The old pursuit-start baseline
+ * reported the whole slot; per-tick indexing reports the delta.
+ */
+@Suppress("UnstableApiUsage")
+object CollectDropsGameTest : FabricClientGameTest {
+    private const val PLAYER = "Steve"
+    private const val COLLECT_TIMEOUT = 20 * 90
+
+    private val failures = mutableListOf()
+
+    override fun runTest(context: ClientGameTestContext) {
+        failures.clear()
+
+        val singleplayer = context.worldBuilder()
+            .adjustSettings { it.gameMode = WorldCreator.Mode.SURVIVAL }
+            .create()
+
+        try {
+            singleplayer.clientWorld.waitForChunksDownload()
+            runScenarios(context, singleplayer.server)
+        } finally {
+            runCatching { context.runOnClient { RootTask.cancel() } }
+            runCatching { singleplayer.close() }
+        }
+
+        if (failures.isEmpty()) {
+            LOG.info("Collect drops game test passed")
+        } else {
+            LOG.error("Collect drops game test failed:")
+            failures.forEach { LOG.error("  FAIL $it") }
+        }
+        check(failures.isEmpty()) {
+            "${failures.size} collect drops expectations failed:\n" + failures.joinToString("\n") { "  - $it" }
+        }
+    }
+
+    private class Outcome(val state: Task.State, val error: String?, val result: List?)
+
+    private fun runScenarios(context: ClientGameTestContext, server: TestServerContext) {
+        val auto: Automated = AutomationConfig.DEFAULT
+        val center = BlockPos(0, -60, 0)
+        prepareWorld(context, server, center)
+
+        // 1. Single drop lands whole into an empty slot: one exact record.
+        server.runCommand("/clear $PLAYER")
+        server.runCommand("/kill @e[type=item]")
+        summonAtFeet(server, center, Items.COBBLESTONE, 5)
+        collectAll(context, auto, "single drop").result?.let { pickups ->
+            checkSingle(pickups, Items.COBBLESTONE, 5, "single drop")
+            checkTotals(context, server, mapOf(Items.COBBLESTONE to 5), "single drop")
+        }
+
+        // 2. Drop merges into a partial stack: the record must be the delta (3),
+        // not the whole slot (63). This fails on the old whole-slot reporting.
+        server.runCommand("/clear $PLAYER")
+        server.runCommand("/kill @e[type=item]")
+        server.runCommand("/item replace entity $PLAYER hotbar.0 with minecraft:cobblestone 60")
+        summonAtFeet(server, center, Items.COBBLESTONE, 3)
+        collectAll(context, auto, "merge drop").result?.let { pickups ->
+            checkSingle(pickups, Items.COBBLESTONE, 3, "merge drop")
+            checkTotals(context, server, mapOf(Items.COBBLESTONE to 63), "merge drop")
+        }
+
+        // 3. Two drops of different kinds land the same tick: each attributed to
+        // its own slot. Same spot is safe: different items never merge server-side.
+        server.runCommand("/clear $PLAYER")
+        server.runCommand("/kill @e[type=item]")
+        summonAtFeet(server, center, Items.COBBLESTONE, 3)
+        summonAtFeet(server, center, Items.DIRT, 4)
+        collectAll(context, auto, "multi drop").result?.let { pickups ->
+            checkTotals(context, server, mapOf(Items.COBBLESTONE to 3, Items.DIRT to 4), "multi drop")
+            val cobble = pickups.filter { it.stack.item == Items.COBBLESTONE }.sumOf { it.stack.count }
+            val dirt = pickups.filter { it.stack.item == Items.DIRT }.sumOf { it.stack.count }
+            if (cobble != 3 || dirt != 4) {
+                fail("multi drop payload covers cobble=$cobble dirt=$dirt, expected 3 and 4 in $pickups")
+            }
+            val unknown = pickups.filter { it.slot == null }
+            if (unknown.isNotEmpty()) {
+                fail("multi drop left clean landings unattributed: $unknown")
+            }
+        }
+
+        // 4. Drop destroyed mid-pursuit: no collector, no growth. The task must fail
+        // naming it instead of crediting a phantom pickup. /kill stands in for
+        // void, lava or cactus: despawn without any pickup animation.
+        server.runCommand("/clear $PLAYER")
+        server.runCommand("/kill @e[type=item]")
+        summonAtFeet(server, center, Items.COBBLESTONE, 7)
+        val lost = collectAll(context, auto, "destroyed drop") {
+            server.runCommand("/kill @e[type=item]")
+        }
+        if (lost.state != Task.State.Failed) {
+            fail("destroyed drop: expected task failure but was ${lost.state}")
+        } else if (lost.error?.contains("Failed to collect") != true) {
+            fail("destroyed drop: wrong failure cause: ${lost.error}")
+        }
+        if ((lost.result ?: emptyList()).isNotEmpty()) {
+            fail("destroyed drop: nothing was collected but the payload is not empty: ${lost.result}")
+        }
+        checkTotals(context, server, emptyMap(), "destroyed drop")
+    }
+
+    /** Runs the real task over every item entity currently loaded client-side. */
+    private fun collectAll(
+        context: ClientGameTestContext,
+        auto: Automated,
+        what: String,
+        afterStart: (ClientGameTestContext) -> Unit = {},
+    ): Outcome {
+        context.waitTicks(3)
+        val drops = context.computeOnClient, RuntimeException> {
+            it.world?.entities?.filterIsInstance()?.toMutableSet() ?: mutableSetOf()
+        }
+        if (drops.isEmpty()) {
+            fail("$what: no item entities client-side to collect; summon setup broke")
+            return Outcome(Task.State.Failed, "no drops", null)
+        }
+        var result: List? = null
+        var error: String? = null
+        var task: Task<*>? = null
+        context.runOnClient {
+            runSafe {
+                task = with(auto) { collectDrops(drops) }
+                    .onSuccess { result = it }
+                    .onFailure { error = it.message }
+                    .start()
+            }
+        }
+        val started = task ?: run {
+            fail("$what: no safe context to start the task")
+            return Outcome(Task.State.Failed, "no task", null)
+        }
+        afterStart(context)
+        try {
+            context.waitFor({
+                started.state != Task.State.Running && started.state != Task.State.Paused
+            }, COLLECT_TIMEOUT)
+        } catch (_: Throwable) {
+            fail("$what: task did not finish within $COLLECT_TIMEOUT ticks (state=${started.state})")
+            runCatching { context.runOnClient { started.cancel() } }
+            return Outcome(started.state, error, result)
+        }
+        if (started.state != Task.State.Completed && started.state != Task.State.Failed) {
+            fail("$what: task ended ${started.state} (${error ?: "no error"})")
+            runCatching { context.runOnClient { started.cancel() } }
+            return Outcome(started.state, error, result)
+        }
+        if (started.state == Task.State.Completed && result == null) {
+            fail("$what: task completed with no payload")
+        }
+        return Outcome(started.state, error, result)
+    }
+
+    private fun checkSingle(pickups: List, item: net.minecraft.item.Item, count: Int, what: String) {
+        if (pickups.size != 1) {
+            fail("$what: expected exactly 1 pickup record, got ${pickups.size} in $pickups")
+            return
+        }
+        val single = pickups.single()
+        if (single.stack.item != item || single.stack.count != count) {
+            fail("$what: expected $count of $item, got ${single.stack} (slot=${single.slot?.id})")
+        }
+        if (single.slot == null) {
+            fail("$what: clean landing recorded with unknown slot")
+        }
+    }
+
+    private fun checkTotals(
+        context: ClientGameTestContext,
+        server: TestServerContext,
+        expected: Map,
+        what: String
+    ) {
+        context.waitTicks(3)
+        val serverStacks = serverPlayerStacks(server)
+        val clientStacks = clientPlayerStacks(context)
+        expected.forEach { (item, count) ->
+            val serverTotal = serverStacks.filter { it.item == item }.sumOf { it.count }
+            val clientTotal = clientStacks.filter { it.item == item }.sumOf { it.count }
+            if (serverTotal != count || clientTotal != count) {
+                fail("$what: expected $count of $item, server=$serverTotal client=$clientTotal")
+            }
+        }
+    }
+
+    /**
+     * Drops spawn at the player's feet so pickup is by contact at delay expiry:
+     * the test covers attribution, not Baritone locomotion.
+     */
+    private fun summonAtFeet(
+        server: TestServerContext,
+        center: BlockPos,
+        item: net.minecraft.item.Item,
+        count: Int
+    ) {
+        // PickupDelay keeps the drop on the ground until references are captured below.
+        val id = net.minecraft.registry.Registries.ITEM.getId(item)
+        val x = center.x + 0.5
+        val z = center.z + 0.5
+        server.runCommand("/summon minecraft:item $x ${center.y} $z {PickupDelay:100s,Item:{id:\"$id\",count:$count}}")
+    }
+
+    private fun prepareWorld(context: ClientGameTestContext, server: TestServerContext, center: BlockPos) {
+        server.runCommand("/gamemode survival $PLAYER")
+        server.runCommand("/difficulty peaceful")
+        server.runCommand("/time set noon")
+        server.runCommand("/weather clear")
+        server.runCommand("/fill ${center.x - 5} ${center.y - 1} ${center.z - 5} ${center.x + 5} ${center.y - 1} ${center.z + 5} minecraft:stone")
+        server.runCommand("/fill ${center.x - 5} ${center.y} ${center.z - 5} ${center.x + 5} ${center.y + 4} ${center.z + 5} minecraft:air")
+        server.runCommand("/tp $PLAYER ${center.x + 0.5} ${center.y} ${center.z + 0.5} 0 0")
+        context.waitTicks(10)
+    }
+
+    private fun serverPlayerStacks(server: TestServerContext): List =
+        server.computeOnServer, RuntimeException> { mc ->
+            mc.playerManager.playerList.first { it.gameProfile.name == PLAYER }
+                .inventory.mainStacks.map { it.copy() }
+        }
+
+    private fun clientPlayerStacks(context: ClientGameTestContext): List =
+        context.computeOnClient, RuntimeException> { mc ->
+            checkNotNull(mc.player) { "collect-drops: no client player" }
+                .inventory.mainStacks.map { s -> s.copy() }
+        }
+
+    private fun fail(message: String) {
+        failures += message
+        LOG.warn("FAIL [collect-drops] $message")
+    }
+}
diff --git a/src/gametest/kotlin/com/lambda/ContainerTransferGameTest.kt b/src/gametest/kotlin/com/lambda/ContainerTransferGameTest.kt
index 67f5ca7bf..5a6b2689f 100644
--- a/src/gametest/kotlin/com/lambda/ContainerTransferGameTest.kt
+++ b/src/gametest/kotlin/com/lambda/ContainerTransferGameTest.kt
@@ -418,7 +418,10 @@ object ContainerTransferGameTest : FabricClientGameTest {
 
 		/** The player's main inventory as the client sees it, to detect desync against the server. */
 		private fun clientPlayerStacks(): List =
-			context.computeOnClient, RuntimeException> { it.player!!.inventory.mainStacks.map { s -> s.copy() } }
+			context.computeOnClient, RuntimeException> { mc ->
+				checkNotNull(mc.player) { "transfer test: no client player" }
+					.inventory.mainStacks.map { s -> s.copy() }
+			}
 
 		// ---- expectations ------------------------------------------------------------------------------------
 
@@ -457,7 +460,10 @@ object ContainerTransferGameTest : FabricClientGameTest {
 				mc.currentScreen?.let { it::class.simpleName } ?: ""
 			}
 			expect(screen.isEmpty()) { "a screen was left open: $screen" }
-			val cursor = context.computeOnClient { it.player!!.currentScreenHandler.cursorStack.copy() }
+			val cursor = context.computeOnClient { mc ->
+				checkNotNull(mc.player) { "transfer test: no client player" }
+					.currentScreenHandler.cursorStack.copy()
+			}
 			expect(cursor.isEmpty) { "cursor still holds $cursor" }
 			return after
 		}
diff --git a/src/gametest/kotlin/com/lambda/ShulkerPickupGameTest.kt b/src/gametest/kotlin/com/lambda/ShulkerPickupGameTest.kt
index 8e5ff98d1..c70250bbe 100644
--- a/src/gametest/kotlin/com/lambda/ShulkerPickupGameTest.kt
+++ b/src/gametest/kotlin/com/lambda/ShulkerPickupGameTest.kt
@@ -158,8 +158,9 @@ object ShulkerPickupGameTest : FabricClientGameTest {
             }
         }
         if (!moved.succeeded) {
-            val seen = context.computeOnClient, RuntimeException> {
-                it.player!!.inventory.mainStacks.take(9).map { s -> s.copy() }
+            val seen = context.computeOnClient, RuntimeException> { mc ->
+                checkNotNull(mc.player) { "shulker-pickup: no client player" }
+                    .inventory.mainStacks.take(9).map { s -> s.copy() }
             }
             fail("could not move the first shulker into the chest: ${moved.state} ${moved.error} (client hotbar shows $seen)")
             return
@@ -258,8 +259,9 @@ object ShulkerPickupGameTest : FabricClientGameTest {
         }
 
     private fun clientPlayerStacks(context: ClientGameTestContext): List =
-        context.computeOnClient, RuntimeException> {
-            it.player!!.inventory.mainStacks.map { s -> s.copy() }
+        context.computeOnClient, RuntimeException> { mc ->
+            checkNotNull(mc.player) { "shulker-pickup: no client player" }
+                .inventory.mainStacks.map { s -> s.copy() }
         }
 
     private fun chestStacks(server: TestServerContext, pos: BlockPos): List =
diff --git a/src/gametest/resources/fabric.mod.json b/src/gametest/resources/fabric.mod.json
index dc00aa322..201563898 100644
--- a/src/gametest/resources/fabric.mod.json
+++ b/src/gametest/resources/fabric.mod.json
@@ -21,6 +21,10 @@
       {
         "adapter": "kotlin",
         "value": "com.lambda.TaskLifecycleGameTest"
+      },
+      {
+        "adapter": "kotlin",
+        "value": "com.lambda.CollectDropsGameTest"
       }
     ]
   }
diff --git a/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt b/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt
index 77c63a5a9..b3d9392ee 100644
--- a/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt
+++ b/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt
@@ -31,7 +31,7 @@ import com.lambda.config.automation.AutomationConfig
 import com.lambda.interaction.container.selection.containerSelection
 import com.lambda.interaction.container.selection.select
 import com.lambda.interaction.container.selection.stackSelection
-import com.lambda.interaction.handler.handlers.findContainers
+import com.lambda.interaction.handler.handlers.ContainerHandler.findContainers
 import com.lambda.task.Task
 import com.lambda.task.start
 import com.lambda.task.tasks.transfer
diff --git a/src/main/kotlin/com/lambda/gui/components/ClickGuiLayout.kt b/src/main/kotlin/com/lambda/gui/components/ClickGuiLayout.kt
index f294df0e1..973f570a0 100644
--- a/src/main/kotlin/com/lambda/gui/components/ClickGuiLayout.kt
+++ b/src/main/kotlin/com/lambda/gui/components/ClickGuiLayout.kt
@@ -136,11 +136,9 @@ object ClickGuiLayout : Loadable, Config(
 	const val BASE_SCALE = 100
 	const val BASE_SCALE_MULTI = 1.8
 
-	fun deviceScaleMultiplier() = try {
-		val monitorWidth = mc.window.monitor!!.currentVideoMode!!.width.toDouble()
-		(monitorWidth / 1920.0).coerceIn(0.5, 4.0)
-	} catch (_: Throwable) {
-		1.0
+	fun deviceScaleMultiplier(): Double {
+		val monitorWidth = mc.window.monitor?.currentVideoMode?.width?.toDouble() ?: return 1.0
+		return (monitorWidth / 1920.0).coerceIn(0.5, 4.0)
 	}
 
 	// General
diff --git a/src/main/kotlin/com/lambda/gui/components/HudGuiLayout.kt b/src/main/kotlin/com/lambda/gui/components/HudGuiLayout.kt
index b070261d3..908805e6f 100644
--- a/src/main/kotlin/com/lambda/gui/components/HudGuiLayout.kt
+++ b/src/main/kotlin/com/lambda/gui/components/HudGuiLayout.kt
@@ -123,9 +123,11 @@ object HudGuiLayout : Loadable, Config(
 
 	                if (!isLocked) {
 		                activeDragHudName?.let { drag ->
-			                if (mouseDown) updateDragAndSnapping(
-				                drag, lastBounds[drag]!!, dragOffsetX, dragOffsetY, pendingPositions, snapOverlays
-			                )
+			                lastBounds[drag]?.let { bounds ->
+				                if (mouseDown) updateDragAndSnapping(
+					                drag, bounds, dragOffsetX, dragOffsetY, pendingPositions, snapOverlays
+				                )
+			                }
 			                drawDragGrid()
 		                }
 	                }
diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/BreakSim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/BreakSim.kt
index b04c5f475..e3e0795d0 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/BreakSim.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/BreakSim.kt
@@ -33,7 +33,7 @@ import com.lambda.interaction.container.selection.StackAndSlot
 import com.lambda.interaction.container.selection.StackSelection
 import com.lambda.interaction.container.selection.select
 import com.lambda.interaction.container.selection.stackSelection
-import com.lambda.interaction.handler.handlers.findStack
+import com.lambda.interaction.handler.handlers.ContainerHandler.findStack
 import com.lambda.interaction.manager.managers.hotbar.HotbarManager
 import com.lambda.interaction.manager.managers.rotating.RotationManager
 import com.lambda.interaction.manager.managers.rotating.rotationRequest
diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/InteractSim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/InteractSim.kt
index 9de638861..c4cb40302 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/InteractSim.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/InteractSim.kt
@@ -30,7 +30,7 @@ import com.lambda.interaction.construction.verify.TargetState
 import com.lambda.interaction.container.containers.HotbarContainer
 import com.lambda.interaction.container.selection.select
 import com.lambda.interaction.container.selection.stackSelection
-import com.lambda.interaction.handler.handlers.findSlot
+import com.lambda.interaction.handler.handlers.ContainerHandler.findSlot
 import com.lambda.interaction.manager.managers.rotating.Rotation
 import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotation
 import com.lambda.interaction.manager.managers.rotating.RotationManager
diff --git a/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt b/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt
index 7f84e824b..e2451a63f 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt
@@ -20,7 +20,7 @@ package com.lambda.interaction.construction.verify
 import com.lambda.context.AutomatedSafeContext
 import com.lambda.context.SafeContext
 import com.lambda.interaction.container.selection.stackSelection
-import com.lambda.interaction.handler.handlers.findStack
+import com.lambda.interaction.handler.handlers.ContainerHandler.findStack
 import com.lambda.util.BlockUtils.blockState
 import com.lambda.util.BlockUtils.emptyState
 import com.lambda.util.BlockUtils.isEmpty
diff --git a/src/main/kotlin/com/lambda/interaction/container/containers/external/EnderChestContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/external/EnderChestContainer.kt
index 1b7c3e587..69f43d6e5 100644
--- a/src/main/kotlin/com/lambda/interaction/container/containers/external/EnderChestContainer.kt
+++ b/src/main/kotlin/com/lambda/interaction/container/containers/external/EnderChestContainer.kt
@@ -17,6 +17,7 @@
 
 package com.lambda.interaction.container.containers.external
 
+import com.lambda.Lambda.LOG
 import com.lambda.Lambda.mc
 import com.lambda.context.Automated
 import com.lambda.context.SafeContext
@@ -27,8 +28,9 @@ import com.lambda.interaction.container.OpenContainerTask
 import com.lambda.interaction.container.OpenedContainerContext
 import com.lambda.interaction.container.containers.HotbarContainer
 import com.lambda.interaction.container.selection.select
+import com.lambda.interaction.handler.handlers.ContainerHandler.findSlot
 import com.lambda.interaction.handler.handlers.ContainerHandler.lastInteractedBlockEntity
-import com.lambda.interaction.handler.handlers.findSlot
+import com.lambda.module.modules.client.Client.verboseDebug
 import com.lambda.task.Task.Ta5kBuilder
 import com.lambda.task.tasks.acquireStack
 import com.lambda.task.tasks.breakAndCollect
@@ -78,19 +80,21 @@ object EnderChestContainer : Container(ContainerType.EnderChest), ExternalContai
 	) : OpenContainerTask(description), Automated by automated {
 		override fun SafeContext.onStart() {
 			findBlock(Blocks.ENDER_CHEST, inventoryConfig.enderChestSearchRadius).withBranch(
-				onSuccess = { pos ->
-					openContainer(pos).onSuccess {
-						success(OpenedEnderChestContext(null))
-					}
-				},
+			onSuccess = { pos ->
+				openContainer(pos).onSuccess {
+					if (verboseDebug) LOG.info("[tweaks] ender open at $pos")
+					success(OpenedEnderChestContext(null))
+				}
+			},
 				onFailure = {
 					acquireStack(Items.ENDER_CHEST.select(1))
 						.then { placeContainer(findSlot(it, HotbarContainer.select())) }
-						.then { pos ->
-							openContainer(pos).onSuccess {
-								success(OpenedEnderChestContext(pos))
-							}
+					.then { pos ->
+						openContainer(pos).onSuccess {
+							if (verboseDebug) LOG.info("[tweaks] ender open at $pos")
+							success(OpenedEnderChestContext(pos))
 						}
+					}
 				}
 			).start()
 		}
diff --git a/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt
index 233d78d7a..a38158b0c 100644
--- a/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt
+++ b/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt
@@ -17,6 +17,7 @@
 
 package com.lambda.interaction.container.containers.external
 
+import com.lambda.Lambda.LOG
 import com.lambda.Lambda.mc
 import com.lambda.context.Automated
 import com.lambda.context.SafeContext
@@ -28,10 +29,13 @@ import com.lambda.interaction.container.OpenContainerTask
 import com.lambda.interaction.container.OpenedContainerContext
 import com.lambda.interaction.container.containers.HotbarContainer
 import com.lambda.interaction.container.selection.ContainerSelection
+import com.lambda.interaction.container.selection.mutate
 import com.lambda.interaction.container.selection.select
 import com.lambda.interaction.container.selection.stackSelection
+import com.lambda.interaction.handler.handlers.ContainerHandler.findSlot
 import com.lambda.interaction.handler.handlers.ContainerSearchScope
-import com.lambda.interaction.handler.handlers.findSlot
+import com.lambda.module.modules.client.Client.verboseDebug
+import com.lambda.task.Task
 import com.lambda.task.Task.Ta5kBuilder
 import com.lambda.task.tasks.breakAndCollect
 import com.lambda.task.tasks.openContainer
@@ -80,6 +84,7 @@ data class ShulkerBoxContainer(
         automated: Automated
     ) : OpenContainerTask(description), Automated by automated {
         override fun SafeContext.onStart() {
+            if (verboseDebug) LOG.info("[tweaks] open start ${description.string}")
             transfer(
                 stackSelection {
                     sortedWith { compareByDescending { it.slot?.index == index } }
@@ -89,9 +94,13 @@ data class ShulkerBoxContainer(
                 },
                 containedIn.select(ContainerSearchScope.Loaded),
                 HotbarContainer.select()
-            ).then { result -> placeContainer(findSlot(result.stackSelection, result.containerSelection)) }
+            ).then { result ->
+                if (verboseDebug) LOG.info("[tweaks] transfer done, placing")
+                placeContainer(findSlot(result.stackSelection.mutate { sortedByRecentChange() }, result.containerSelection))
+            }
                 .then { pos ->
                     openContainer(pos).onSuccess {
+                        if (verboseDebug) LOG.info("[tweaks] opened shulker at $pos")
                         success(OpenedShulkerBoxContext(pos, containedIn))
                     }
                 }
@@ -104,24 +113,33 @@ data class ShulkerBoxContainer(
         val fromContainer: Container
     ) : OpenedContainerContext {
         context(automated: Automated)
-        override fun close() =
-            taskOrNull {
+        override fun close(): Task<*>? {
+            if (verboseDebug) LOG.info("[tweaks] close start ${description.string}")
+            return taskOrNull {
                 if (!isAccessed) null
                 else actionTask { player.closeHandledScreen() }
             }.then { breakAndCollect(blockPos) }
-                .then {
+                .then { outcome ->
+                    if (verboseDebug) LOG.info("[tweaks] broke and collected ${outcome.pickups.size} pickup(s)")
+                    val predicted = outcome.pickups
+                        .asSequence()
+                        .filter { it.stack.item == shulkerItem && it.stack.name.string == itemName }
+                        .firstNotNullOfOrNull { it.slot }
+
                     stackSelection {
                         isItem(shulkerItem)
                         withName(itemName)
+                        predicted?.let { sortedBySlotMatch(it) }
                         sortedByBestContentMatch(stacks)
+                        sortedByRecentChange()
                     }.transfer(
                         ContainerSelection.HOTBAR_AND_INVENTORY,
                         fromContainer.select(ContainerSearchScope.Loaded),
                         stackSelection {
-                            sortedWith {
-                                compareByDescending {
-                                    it.slot?.index == index &&
-                                            it.slot.stack?.isEmpty != false
+                            sortedByMatches {
+                                stackSelection {
+                                    predicate { _, slot -> slot?.index == index }
+                                    isEmpty()
                                 }
                             }
                         },
@@ -129,6 +147,7 @@ data class ShulkerBoxContainer(
                     )
                 }
     }
+    }
 
     override fun hashCode() = hash(containedIn.hashCode(), index)
 
diff --git a/src/main/kotlin/com/lambda/interaction/container/selection/ContainerSelection.kt b/src/main/kotlin/com/lambda/interaction/container/selection/ContainerSelection.kt
index a41b0ab68..78b4ae527 100644
--- a/src/main/kotlin/com/lambda/interaction/container/selection/ContainerSelection.kt
+++ b/src/main/kotlin/com/lambda/interaction/container/selection/ContainerSelection.kt
@@ -51,12 +51,9 @@ fun ContainerSelection.mutate(
     builder: ContainerSelectionBuilder.() -> Unit = {}
 ) = ContainerSelectionBuilder(this, scope).apply(builder).build()
 
-/**
- * ContainerSelection is a class that holds a predicate for matching containers.
- */
 @Suppress("unused")
 class ContainerSelection @ContainerDslMarker internal constructor(
-    val selector: (Container) -> Boolean,
+    val selector: (Container) -> Boolean = { true },
     val scope: ContainerSearchScope = ContainerSearchScope.Loaded,
     val containersWhitelist: Collection = emptyList(),
     val loadedContainers: Collection = emptyList(),
@@ -95,7 +92,7 @@ class ContainerSelection @ContainerDslMarker internal constructor(
                 InventoryContainer,
                 scope = ContainerSearchScope.Player
             )
-        val PLAYER = containerSelection(scope = ContainerSearchScope.Player)
+        val PLAYER = ContainerSelection(scope = ContainerSearchScope.Player)
     }
 }
 
diff --git a/src/main/kotlin/com/lambda/interaction/container/selection/StackSelection.kt b/src/main/kotlin/com/lambda/interaction/container/selection/StackSelection.kt
index 2ca323773..3b81b3351 100644
--- a/src/main/kotlin/com/lambda/interaction/container/selection/StackSelection.kt
+++ b/src/main/kotlin/com/lambda/interaction/container/selection/StackSelection.kt
@@ -19,8 +19,10 @@
 
 package com.lambda.interaction.container.selection
 
+import com.lambda.Lambda.mc
 import com.lambda.interaction.container.Container
 import com.lambda.interaction.container.ContainerDslMarker
+import com.lambda.interaction.manager.managers.inventory.InventoryManager
 import com.lambda.util.EnchantmentUtils.getEnchantment
 import com.lambda.util.item.ItemStackUtils.shulkerBoxStacks
 import com.lambda.util.item.ItemUtils
@@ -36,6 +38,7 @@ import net.minecraft.registry.RegistryKey
 import net.minecraft.registry.tag.TagKey
 import net.minecraft.screen.slot.Slot
 import java.util.*
+import kotlin.time.Instant
 
 @ContainerDslMarker
 fun Item.select(count: Int = 1) = stackSelection(count) { isItem(this@select) }
@@ -71,12 +74,18 @@ class StackSelection @ContainerDslMarker internal constructor(
 	@ContainerDslMarker
 	fun bestMatch(slots: Iterable) = filter(slots).firstOrNull()
 
+	@ContainerDslMarker
+	fun bestMatch(stackAndSlots: Iterable>) = filter(stackAndSlots).firstOrNull()
+
 	@ContainerDslMarker
 	fun match(stacks: Iterable) = stacks.find(::matches)
 
 	@ContainerDslMarker
 	fun match(slots: Iterable) = slots.find(::matches)
 
+	@ContainerDslMarker
+	fun match(stackAndSlots: Iterable>) = stackAndSlots.find(::matches)
+
 	@ContainerDslMarker
 	fun matches(stack: ItemStack): Boolean {
 		val matchesSelf = selector(stack, null)
@@ -97,6 +106,10 @@ class StackSelection @ContainerDslMarker internal constructor(
 		}
 	}
 
+	@ContainerDslMarker
+	fun matches(stackAndSlot: StackAndSlot<*>) =
+		stackAndSlot.slot?.let(::matches) ?: matches(stackAndSlot.stack)
+
 	@ContainerDslMarker
 	@JvmName("filter1")
 	fun filter(
@@ -139,6 +152,25 @@ class StackSelection @ContainerDslMarker internal constructor(
 		}
 		.toList()
 
+	@ContainerDslMarker
+	@JvmName("filter3")
+	fun filter(
+		stackAndSlots: Iterable>,
+		sorted: Boolean = true
+	) = stackAndSlots
+		.asSequence()
+		.let { stackAndSlots ->
+			if (whitelistedSlots.isNotEmpty()) stackAndSlots.filter { it.slot in whitelistedSlots }
+			else stackAndSlots
+		}
+		.filter { it.slot !in blacklistedSlots }
+		.filter(::matches)
+		.let {
+			if (sorted) it.sortedWith(comparator)
+			else it
+		}
+		.toList()
+
 	@ContainerDslMarker
 	infix fun isIn(container: Container) =
 		container.count(this) >= count
@@ -175,7 +207,6 @@ class StackSelection @ContainerDslMarker internal constructor(
 	companion object {
 		val ANYTHING = StackSelection(0) { _, _ -> true }
 		val NOTHING = StackSelection { _, _ -> false }
-		val EMPTY = stackSelection { isEmpty() }
 	}
 }
 
@@ -401,20 +432,31 @@ class StackSelectionBuilder @ContainerDslMarker internal constructor(
 	}
 
 	fun canInsert(stack: ItemStack) {
-		if (!stack.isEmpty) {
-			appendSelector { _, slot -> slot == null || slot.canInsert(stack) }
-		}
+		appendSelector { _, slot -> slot?.canInsert(stack) == true }
+	}
+
+	fun cannotInsert(stack: ItemStack) {
+		appendSelector { _, slot -> slot?.canInsert(stack) == false }
 	}
 
 	fun canInsert(selection: StackSelection) {
 		selection.optimalStack?.let { canInsert(it) }
 	}
 
+	fun cannotInsert(selection: StackSelection) {
+		selection.optimalStack?.let { cannotInsert(it) }
+	}
+
 	fun canInsert() {
 		val optimal = itemStack ?: item?.let { ItemStack(it, count) }
 		optimal?.let { canInsert(it) }
 	}
 
+	fun cannotInsert() {
+		val optimal = itemStack ?: item?.let { ItemStack(it, count) }
+		optimal?.let { cannotInsert(it) }
+	}
+
 	fun sortedWith(newComparator: Comparator>) {
 		comparator = comparator?.thenComparing(newComparator) ?: newComparator
 	}
@@ -423,14 +465,38 @@ class StackSelectionBuilder @ContainerDslMarker internal constructor(
 		sortedWith(comparatorSupplier())
 	}
 
+	fun sortedByMatches(stackSelection: StackSelection) {
+		sortedWith {
+			compareByDescending { stackSelection.matches(it) }
+		}
+	}
+
+	fun sortedByMatches(stackSelectionSupplier: () -> StackSelection) {
+		sortedWith {
+			compareByDescending { stackSelectionSupplier().matches(it) }
+		}
+	}
+
+	fun sortedByMismatches(stackSelection: StackSelection) {
+		sortedWith {
+			compareBy { stackSelection.matches(it) }
+		}
+	}
+
+	fun sortedByMismatches(stackSelectionSupplier: () -> StackSelection) {
+		sortedWith {
+			compareBy { stackSelectionSupplier().matches(it) }
+		}
+	}
+
 	fun sortedByBestContentMatch(expectedContents: List) {
 		val expectedFrequencies = expectedContents
 			.filter { !it.isEmpty }
 			.groupingBy { it.item }
 			.eachCount()
 		sortedWith(
-			compareByDescending { stackAndSlot ->
-				val currentFrequencies = stackAndSlot.stack.shulkerBoxStacks
+			compareByDescending {
+				val currentFrequencies = it.stack.shulkerBoxStacks
 					.filter { !it.isEmpty }
 					.groupingBy { it.item }
 					.eachCount()
@@ -441,6 +507,68 @@ class StackSelectionBuilder @ContainerDslMarker internal constructor(
 		)
 	}
 
+	fun sortedByWorstContentMatch(expectedContents: List) {
+		val expectedFrequencies = expectedContents
+			.filter { !it.isEmpty }
+			.groupingBy { it.item }
+			.eachCount()
+		sortedWith(
+			compareBy {
+				val currentFrequencies = it.stack.shulkerBoxStacks
+					.filter { !it.isEmpty }
+					.groupingBy { it.item }
+					.eachCount()
+				expectedFrequencies.asSequence().fold(0) { acc, (item, count) ->
+					acc + minOf(count, currentFrequencies[item] ?: 0)
+				}
+			}
+		)
+	}
+
+	fun sortedBySlotMatch(slot: Slot) {
+		sortedWith {
+			compareByDescending {
+				it.slot?.inventory === slot.inventory &&
+						it.slot.index == slot.index
+			}
+		}
+	}
+
+	fun sortedBySlotMismatch(slot: Slot) {
+		sortedWith {
+			compareBy {
+				it.slot?.inventory === slot.inventory &&
+						it.slot.index == slot.index
+			}
+		}
+	}
+
+	fun sortedByRecentChange() {
+		sortedWith(
+			compareByDescending {
+				val syncId = mc.player?.currentScreenHandler?.syncId
+				it.slot?.let { slot ->
+					syncId?.let { id ->
+						InventoryManager.lastSlotChange(id, slot.id)
+					}
+				} ?: Instant.DISTANT_PAST
+			}
+		)
+	}
+
+	fun sortedByOldestChange() {
+		sortedWith {
+			compareBy {
+				val syncId = mc.player?.currentScreenHandler?.syncId
+				it.slot?.let { slot ->
+					syncId?.let { id ->
+						InventoryManager.lastSlotChange(id, slot.id)
+					}
+				} ?: Instant.DISTANT_PAST
+			}
+		}
+	}
+
 	@PublishedApi
 	internal fun appendSelector(selector: (ItemStack, Slot?) -> Boolean) {
 		val currentSelector = this.selector
diff --git a/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt
index 9dea16012..f0d4db5de 100644
--- a/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt
+++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/ContainerHandler.kt
@@ -48,6 +48,7 @@ import com.lambda.interaction.container.selection.StackSelection
 import com.lambda.interaction.container.selection.containerSelection
 import com.lambda.interaction.container.selection.mutate
 import com.lambda.interaction.container.selection.stackSelection
+import com.lambda.interaction.handler.handlers.ContainerHandler.findContainer
 import com.lambda.util.BlockUtils.blockEntity
 import com.lambda.util.FolderRegistry
 import com.lambda.util.ReflectionUtils.getInstances
@@ -151,6 +152,158 @@ object ContainerHandler : Loadable {
 		}
 	}
 
+	@ContainerDslMarker
+	context(_: Automated)
+	fun findSlot(
+		stackSelection: StackSelection = StackSelection.ANYTHING,
+		containerSelection: ContainerSelection = ContainerSelection.ACCESSED,
+		sorted: Boolean = true
+	) = findSlots(stackSelection, containerSelection, sorted)
+		?.firstOrNull()
+
+	@ContainerDslMarker
+	context(_: Automated)
+	fun findSlots(
+		stackSelection: StackSelection = StackSelection.ANYTHING,
+		containerSelection: ContainerSelection = ContainerSelection.ACCESSED,
+		sorted: Boolean = true
+	) = findContainers(containerSelection, false)
+		.flatMap { it.slots }
+		.let { stackSelection.filter(it, sorted) }
+		.takeIf { it.count >= stackSelection.count || stackSelection.count < 0 }
+		?.takeUpTo(stackSelection.count) { it.stack.count }
+
+	@ContainerDslMarker
+	context(_: Automated)
+	fun slotsAvailable(
+		stackSelection: StackSelection = StackSelection.ANYTHING,
+		containerSelection: ContainerSelection = ContainerSelection.ACCESSED
+	) = findSlots(stackSelection, containerSelection, false)
+		?.isNotEmpty() == true
+
+	@ContainerDslMarker
+	context(_: Automated)
+	fun findStack(
+		stackSelection: StackSelection = StackSelection.ANYTHING,
+		containerSelection: ContainerSelection = ContainerSelection.ACCESSED,
+		sorted: Boolean = true
+	) = findStacks(stackSelection, containerSelection, sorted)
+		?.firstOrNull()
+
+	@ContainerDslMarker
+	context(_: Automated)
+	fun findStacks(
+		stackSelection: StackSelection = StackSelection.ANYTHING,
+		containerSelection: ContainerSelection = ContainerSelection.ACCESSED,
+		sorted: Boolean = true
+	) = findContainers(containerSelection, false)
+		.flatMap { it.stacks }
+		.let { stackSelection.filter(it, sorted) }
+		.takeIf { it.count >= stackSelection.count || stackSelection.count < 0 }
+		?.takeUpTo(stackSelection.count) { it.count }
+
+	@ContainerDslMarker
+	context(_: Automated)
+	fun stacksAvailable(
+		stackSelection: StackSelection = StackSelection.ANYTHING,
+		containerSelection: ContainerSelection = ContainerSelection.ACCESSED
+	) = findStacks(stackSelection, containerSelection, false)
+		?.isNotEmpty() == true
+
+	@ContainerDslMarker
+	context(_: Automated)
+	fun findContainer(
+		containerSelection: ContainerSelection = ContainerSelection.ACCESSED,
+		sorted: Boolean = true
+	) = findContainers(containerSelection, sorted)
+		.firstOrNull()
+
+	@ContainerDslMarker
+	context(_: Automated)
+	fun findContainers(
+		containerSelection: ContainerSelection = ContainerSelection.ACCESSED,
+		sorted: Boolean = true
+	) = containerSelection
+		.filter(searchContainers(containerSelection).asIterable(), sorted)
+
+	@ContainerDslMarker
+	context(_: Automated)
+	fun containerAvailable(
+		containerSelection: ContainerSelection= ContainerSelection.ACCESSED
+	) = findContainer(containerSelection, false) != null
+
+	@ContainerDslMarker
+	context(_: Automated)
+	fun findContainerWithDisposable(
+		containerSelection: ContainerSelection = ContainerSelection.ACCESSED,
+		sorted: Boolean = true
+	) = findContainersWithDisposable(containerSelection, sorted)
+		.firstOrNull()
+
+	@ContainerDslMarker
+	context(automated: Automated)
+	fun findContainersWithDisposable(
+		containerSelection: ContainerSelection = ContainerSelection.ACCESSED,
+		sorted: Boolean = true
+	) =
+		with(automated) {
+			findContainers(
+				containerSelection(containerSelection.scope) {
+					matches(containerSelection)
+					hasStack(
+						stackSelection(1) { ofAnyItems(inventoryConfig.disposables) }
+					)
+				},
+				sorted
+			)
+		}
+
+	context(_: Automated)
+	private fun searchContainers(
+		selection: ContainerSelection
+	): Sequence {
+		fun Container.allNested(): Sequence =
+			sequence {
+				yield(this@allNested)
+				storedContainers.values.forEach { nested ->
+					yieldAll(nested.allNested())
+				}
+			}
+
+		val compiled = compileContainers.asSequence()
+		val placedSeq = sequenceOf(accessedPlacedContainer).filterNotNull()
+
+		val baseContainers =
+			when (selection.scope) {
+				ContainerSearchScope.Loaded -> compiled + placedSeq + selection.loadedContainers
+				ContainerSearchScope.Accessed -> placedSeq + compiled.filter { it.isAccessed }
+				ContainerSearchScope.Compiled -> compiled
+				ContainerSearchScope.Player -> compiled - EnderChestContainer
+				ContainerSearchScope.All -> compiled + placedSeq +
+						ContainerSerializer.serializedContainers.filter { diskContainer ->
+							val accessed = accessedPlacedContainer
+							accessed == null || !diskContainer.haveMatchingInventories(accessed)
+						}
+			}
+
+		return baseContainers
+			.flatMap { it.allNested() }
+			.distinct()
+			.filter { it.isAccessible }
+	}
+
+	private fun  List.takeUpTo(need: Int, countOf: (T) -> Int): List {
+		if (need <= 0) return this
+		var sum = 0
+		val taken = ArrayList()
+		for (item in this) {
+			taken += item
+			sum += countOf(item)
+			if (sum >= need) break
+		}
+		return taken
+	}
+
 	private fun SafeContext.onContainerUpdate(sh: ScreenHandler = player.currentScreenHandler) {
 		InventoryContainer.scanContainerContents()
 		HotbarContainer.scanContainerContents()
@@ -260,7 +413,7 @@ object ContainerHandler : Loadable {
 
 @ContainerDslMarker
 context(_: AutomatedSafeContext)
-fun StackSelection.move(
+fun StackSelection.moveStack(
 	fromSelection: ContainerSelection = ContainerSelection.ACCESSED,
 	toSelection: ContainerSelection = ContainerSelection.ACCESSED,
 	replaceSelection: StackSelection = StackSelection.ANYTHING
@@ -273,7 +426,7 @@ fun StackSelection.move(
 				isAccessed()
 			}
 		)
-		?: return false
+			?: return false
 
 	val toContainer =
 		findContainer(
@@ -282,8 +435,7 @@ fun StackSelection.move(
 				hasStack(replaceSelection)
 				isAccessed()
 			}
-		)
-		?: return false
+		) ?: return false
 
 	val (fromSlot, toSlot) = fromContainer.findMoveSlots(this, toContainer, replaceSelection)
 	if (fromSlot == null || toSlot == null) return false
@@ -291,130 +443,6 @@ fun StackSelection.move(
 	return fromContainer.swap(fromSlot, toSlot, toContainer)
 }
 
-@ContainerDslMarker
-context(automated: Automated)
-fun findSlot(
-	stackSelection: StackSelection = StackSelection.ANYTHING,
-	containerSelection: ContainerSelection = ContainerSelection.ACCESSED,
-	sorted: Boolean = true
-) = findSlots(stackSelection, containerSelection, sorted)
-	.firstOrNull()
-
-@ContainerDslMarker
-context(automated: Automated)
-fun findSlots(
-	stackSelection: StackSelection = StackSelection.ANYTHING,
-	containerSelection: ContainerSelection = ContainerSelection.ACCESSED,
-	sorted: Boolean = true
-) = containerSelection
-	.filter(searchContainers(containerSelection).asIterable(), sorted)
-	.mapNotNull { container ->
-		val slots = stackSelection.filter(container.slots)
-		if (slots.count >= stackSelection.count) slots
-		else null
-	}
-	.flatten()
-
-@ContainerDslMarker
-context(automated: Automated)
-fun findStack(
-	stackSelection: StackSelection = StackSelection.ANYTHING,
-	containerSelection: ContainerSelection = ContainerSelection.ACCESSED,
-	sorted: Boolean = true
-) = findStacks(stackSelection, containerSelection, sorted)
-	.firstOrNull()
-
-@ContainerDslMarker
-context(automated: Automated)
-fun findStacks(
-	stackSelection: StackSelection = StackSelection.ANYTHING,
-	containerSelection: ContainerSelection = ContainerSelection.ACCESSED,
-	sorted: Boolean = true
-) = containerSelection
-	.filter(searchContainers(containerSelection).asIterable(), sorted)
-	.mapNotNull { container ->
-		val stacks = stackSelection.filter(container.stacks)
-		if (stacks.count >= stackSelection.count) stacks
-		else null
-	}
-	.flatten()
-
-@ContainerDslMarker
-context(_: Automated)
-fun findContainer(
-	containerSelection: ContainerSelection = ContainerSelection.ACCESSED,
-	sorted: Boolean = true
-) = findContainers(containerSelection, sorted)
-	.firstOrNull()
-
-@ContainerDslMarker
-context(automated: Automated)
-fun findContainers(
-	containerSelection: ContainerSelection = ContainerSelection.ACCESSED,
-	sorted: Boolean = true
-) = containerSelection
-	.filter(searchContainers(containerSelection).asIterable(), sorted)
-
-@ContainerDslMarker
-context(_: Automated)
-fun findContainerWithDisposable(
-	containerSelection: ContainerSelection = ContainerSelection.ACCESSED,
-	sorted: Boolean = true
-) = findContainersWithDisposable(containerSelection, sorted)
-	.firstOrNull()
-
-@ContainerDslMarker
-context(automated: Automated)
-fun findContainersWithDisposable(
-	containerSelection: ContainerSelection = ContainerSelection.ACCESSED,
-	sorted: Boolean = true
-) =
-	with(automated) {
-		findContainers(
-			containerSelection(containerSelection.scope) {
-				matches(containerSelection)
-				hasStack(
-					stackSelection(1) { ofAnyItems(inventoryConfig.disposables) }
-				)
-			},
-			sorted
-		)
-	}
-
-context(automated: Automated)
-private fun searchContainers(
-	selection: ContainerSelection
-): Sequence {
-	fun Container.allNested(): Sequence =
-		sequence {
-			yield(this@allNested)
-			storedContainers.values.forEach { nested ->
-				yieldAll(nested.allNested())
-			}
-		}
-
-	val compiled = ContainerHandler.compileContainers.asSequence()
-	val placedSeq = sequenceOf(ContainerHandler.accessedPlacedContainer).filterNotNull()
-
-	val baseContainers =
-		when (selection.scope) {
-			ContainerSearchScope.Loaded -> compiled + placedSeq + selection.loadedContainers
-			ContainerSearchScope.Accessed -> placedSeq + compiled.filter { it.isAccessed }
-			ContainerSearchScope.Compiled -> compiled
-			ContainerSearchScope.Player -> compiled - EnderChestContainer
-			ContainerSearchScope.All -> compiled + placedSeq +
-					ContainerSerializer.serializedContainers.filter { diskContainer ->
-						val accessed = ContainerHandler.accessedPlacedContainer
-						accessed == null || !diskContainer.haveMatchingInventories(accessed)
-					}
-		}
-
-	return baseContainers
-		.flatMap { it.allNested() }
-		.distinct()
-		.filter { it.isAccessible }
-}
-
 enum class ContainerSearchScope {
 	Loaded,
 	Accessed,
diff --git a/src/main/kotlin/com/lambda/interaction/handler/handlers/GlideHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/GlideHandler.kt
index bf96fae00..6c32f77c8 100644
--- a/src/main/kotlin/com/lambda/interaction/handler/handlers/GlideHandler.kt
+++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/GlideHandler.kt
@@ -23,6 +23,7 @@ import com.lambda.event.listener.SafeListener.Companion.listen
 import com.lambda.interaction.container.selection.ContainerSelection
 import com.lambda.interaction.container.selection.StackAndSlot
 import com.lambda.interaction.container.selection.stackSelection
+import com.lambda.interaction.handler.handlers.ContainerHandler.stacksAvailable
 import com.lambda.module.modules.combat.AutoArmor
 import com.lambda.module.modules.movement.elytrafly.ElytraFly
 import com.lambda.module.modules.movement.elytrafly.ElytraFly.mode
@@ -173,11 +174,7 @@ object GlideHandler {
 			if (!autoSwapChecking || canGlideAlready) return canGlideAlready
 			val selection = if (fakeFly) CHESTPLATE_SELECTION else ELYTRA_SELECTION
 			return with(ElytraFly) {
-				findStack(
-					selection,
-					ContainerSelection.HOTBAR_AND_INVENTORY,
-					false
-				) != null
+				stacksAvailable(selection, ContainerSelection.HOTBAR_AND_INVENTORY)
 			}
 		}
 
diff --git a/src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/BrokenBlockHandler.kt b/src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/BrokenBlockHandler.kt
index bd1241726..440466031 100644
--- a/src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/BrokenBlockHandler.kt
+++ b/src/main/kotlin/com/lambda/interaction/handler/handlers/breaking/BrokenBlockHandler.kt
@@ -109,6 +109,7 @@ internal object BrokenBlockHandler : PostActionHandler() {
 					) {
 					destroyBlock(pending)
 				}
+				pendingActions.refresh(pending)
 				pending.internalOnBreak()
 				if (pending.callbacksCompleted) {
 					pending.stopPending()
diff --git a/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryManager.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryManager.kt
index 5ea3ccaae..e9bea9b45 100644
--- a/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryManager.kt
+++ b/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryManager.kt
@@ -47,6 +47,7 @@ import net.minecraft.network.packet.s2c.play.ScreenHandlerSlotUpdateS2CPacket
 import net.minecraft.network.packet.s2c.play.SetCursorItemS2CPacket
 import net.minecraft.screen.PlayerScreenHandler
 import net.minecraft.screen.ScreenHandler
+import kotlin.time.Instant
 
 /**
  * Manager designed to handle inventory actions. One of the key features being the inventory change detection to
@@ -190,7 +191,7 @@ object InventoryManager : Manager(1) {
 	 * [InventoryS2CPacket]; that packet is merged per slot in [onInventoryUpdate], which is why each
 	 * change is recorded exactly once here.
 	 */
-	fun SafeContext.indexInventoryChanges() {
+	private fun SafeContext.indexInventoryChanges() {
 		val current = player.currentScreenHandler
 		val recorded = filter.observeLocal(
 			current.syncId,
@@ -352,6 +353,12 @@ object InventoryManager : Manager(1) {
 		original.call(packet)
 	}
 
+	/**
+	 * When a slot last changed locally, for breaking ties between identical stacks.
+	 * Pure filter read, safe anywhere. Null means unknown: keep the old order.
+	 */
+	fun lastSlotChange(syncId: Int, slotId: Int): Instant? = filter.lastChange(syncId, slotId)
+
 	/**
 	 * Anchors tracking to a screen's live state: drops pendings that cannot belong to a fresh
 	 * screen id, and photographs the current truth. Called on opens, closes and respawns.
diff --git a/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryRequest.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryRequest.kt
index 8efd447d9..89970063f 100644
--- a/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryRequest.kt
+++ b/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventoryRequest.kt
@@ -87,12 +87,13 @@ class InvRequestBuilder internal constructor(
 
 	fun resyncInventory() {
 		InventoryAction.Inventory {
+			val sh = player.currentScreenHandler
+			val action = if (sh.cursorStack.isEmpty) SlotActionType.PICKUP_ALL else SlotActionType.CLONE
 			connection.sendPacket {
-				val sh = player.currentScreenHandler
 				ClickSlotC2SPacket(
 					sh.syncId,
-					-1, -1, 0,
-					SlotActionType.CLONE,
+					-1, 0, 0,
+					action,
 					Int2ObjectOpenHashMap(),
 					ItemStackHash.fromItemStack(sh.cursorStack, connection.componentHasher)
 				)
diff --git a/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventorySyncFilter.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventorySyncFilter.kt
index fe8d4b56b..eb3758646 100644
--- a/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventorySyncFilter.kt
+++ b/src/main/kotlin/com/lambda/interaction/manager/managers/inventory/InventorySyncFilter.kt
@@ -19,18 +19,21 @@ package com.lambda.interaction.manager.managers.inventory
 
 import com.lambda.interaction.manager.managers.inventory.InventoryLedger.SingleDecision.Drop
 import kotlin.time.Clock
+import kotlin.time.Duration.Companion.milliseconds
 import kotlin.time.Instant
 
 class InventorySyncFilter(
     private val equal: (a: T, b: T) -> Boolean,
     private val copy: (T) -> T,
     maxAgeMs: Long = 1500L,
-    clock: () -> Instant = { Clock.System.now() }
+    private val clock: () -> Instant = { Clock.System.now() }
 ) {
     private val ledger = InventoryLedger(equal, maxAgeMs, clock = clock)
     private val slotSnapshots = mutableMapOf>()
     private val cursorSnapshots = mutableMapOf()
 
+    private val changeTimes = mutableMapOf, Instant>()
+
     var maxAgeMs: Long
         get() = ledger.maxAgeMs
         set(value) {
@@ -60,6 +63,7 @@ class InventorySyncFilter(
             else if (!equal(old, live)) {
                 ledger.record(syncId, slotId, old, copy(live))
                 snap[slotId] = copy(live)
+                changeTimes[syncId to slotId] = clock()
                 recorded++
             }
         }
@@ -118,6 +122,22 @@ class InventorySyncFilter(
     @Synchronized
     fun clear(syncId: Int) {
         ledger.clear(syncId)
+        changeTimes.keys.removeIf { (sync, _) -> sync == syncId }
+    }
+
+    /**
+     * When [slotId] last changed locally, or null if never (or no longer ago than
+     * [maxAgeMs]). A fresh screen id must never inherit another screen's stamps.
+     */
+    @Synchronized
+    fun lastChange(syncId: Int, slotId: Int): Instant? {
+        val key = syncId to slotId
+        val at = changeTimes[key] ?: return null
+        if (clock() - at > maxAgeMs.milliseconds) {
+            changeTimes.remove(key)
+            return null
+        }
+        return at
     }
 
     @Synchronized
diff --git a/src/main/kotlin/com/lambda/module/modules/chat/ChatTimestamp.kt b/src/main/kotlin/com/lambda/module/modules/chat/ChatTimestamp.kt
index f3a7fded9..facc693f2 100644
--- a/src/main/kotlin/com/lambda/module/modules/chat/ChatTimestamp.kt
+++ b/src/main/kotlin/com/lambda/module/modules/chat/ChatTimestamp.kt
@@ -47,7 +47,11 @@ object ChatTimestamp : Module(
 ) {
 	private var color: Formatting by setting("Color", Formatting.GRAY)
 		.onValueChange { from, to -> if (to.colorIndex !in 0..15) color = from }
-	private val javaColor: Color get() = Color(color.colorValue!! and 16777215)
+	private val javaColor: Color
+		get() {
+			val rgb = color.colorValue ?: return Color.GRAY
+			return Color(rgb and 16777215)
+		}
 
 	val formatter by configBlock(FormatterSettings(this))
 		.withEdits {
diff --git a/src/main/kotlin/com/lambda/module/modules/chat/FriendHighlight.kt b/src/main/kotlin/com/lambda/module/modules/chat/FriendHighlight.kt
index 239dcec05..32a078dce 100644
--- a/src/main/kotlin/com/lambda/module/modules/chat/FriendHighlight.kt
+++ b/src/main/kotlin/com/lambda/module/modules/chat/FriendHighlight.kt
@@ -41,7 +41,11 @@ object FriendHighlight : Module(
 ) {
 	private var color: Formatting by setting("Color", Formatting.GREEN)
 		.onValueChange { from, to -> if (to.colorIndex !in 0..15) color = from }
-	private val javaColor: Color get() = Color(color.colorValue!! and 16777215)
+	private val javaColor: Color
+		get() {
+			val rgb = color.colorValue ?: return Color.GREEN
+			return Color(rgb and 16777215)
+		}
 
 	private val bold by setting("Bold", true)
 	private val italic by setting("Italic", false)
diff --git a/src/main/kotlin/com/lambda/module/modules/combat/AutoArmor.kt b/src/main/kotlin/com/lambda/module/modules/combat/AutoArmor.kt
index 94cd854d3..f571ee248 100644
--- a/src/main/kotlin/com/lambda/module/modules/combat/AutoArmor.kt
+++ b/src/main/kotlin/com/lambda/module/modules/combat/AutoArmor.kt
@@ -29,7 +29,7 @@ import com.lambda.interaction.container.selection.ContainerSelection
 import com.lambda.interaction.container.selection.StackAndSlot
 import com.lambda.interaction.container.selection.select
 import com.lambda.interaction.container.selection.stackSelection
-import com.lambda.interaction.handler.handlers.findSlots
+import com.lambda.interaction.handler.handlers.ContainerHandler.findSlots
 import com.lambda.interaction.manager.managers.inventory.inventoryRequest
 import com.lambda.module.Module
 import com.lambda.module.ModuleTag
@@ -122,8 +122,7 @@ object AutoArmor : Module(
 	fun SafeContext.tick() {
 		tickedThisTick = true
 		val armorSlots = findSlots(containerSelection = ArmorContainer.select())
-			.toList()
-			.takeUnless { it.isEmpty() }
+			?.toList()
 			?: return
 		
 		val swappable =
@@ -135,7 +134,8 @@ object AutoArmor : Module(
 				},
 				ContainerSelection.HOTBAR_AND_INVENTORY
 			)
-			.distinctBy { it.stack.armorSlot }
+			?.distinctBy { it.stack.armorSlot }
+				?: return
 
 		val swaps = mutableListOf>()
 		armorSlots
diff --git a/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt b/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt
index bb9a5e893..723314ec8 100644
--- a/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt
+++ b/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt
@@ -27,7 +27,7 @@ import com.lambda.interaction.container.containers.OffhandContainer
 import com.lambda.interaction.container.selection.ContainerSelection
 import com.lambda.interaction.container.selection.select
 import com.lambda.interaction.handler.handlers.FriendHandler
-import com.lambda.interaction.handler.handlers.move
+import com.lambda.interaction.handler.handlers.moveStack
 import com.lambda.module.Module
 import com.lambda.module.ModuleTag
 import com.lambda.threading.runSafeAutomated
@@ -68,8 +68,9 @@ object AutoTotem : Module(
             val swapWhileHolding = !ignoreWhenHolding || player.mainHandStack.item != Items.TOTEM_OF_UNDYING
             if (swapWhileHolding && player.offHandStack.item != Items.TOTEM_OF_UNDYING) {
                 runSafeAutomated {
-                    Items.TOTEM_OF_UNDYING.select()
-                        .move(
+                    Items.TOTEM_OF_UNDYING
+                        .select()
+                        .moveStack(
                             ContainerSelection.HOTBAR_AND_INVENTORY,
                             OffhandContainer.select()
                         )
diff --git a/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt b/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt
index f205ff8fb..e6a598777 100644
--- a/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt
+++ b/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt
@@ -31,7 +31,7 @@ import com.lambda.event.listener.SafeListener.Companion.listen
 import com.lambda.interaction.container.containers.HotbarContainer
 import com.lambda.interaction.container.selection.select
 import com.lambda.interaction.container.selection.stackSelection
-import com.lambda.interaction.handler.handlers.findSlot
+import com.lambda.interaction.handler.handlers.ContainerHandler.findSlot
 import com.lambda.interaction.manager.managers.hotbar.hotbarRequest
 import com.lambda.interaction.manager.managers.rotating.rotationRequest
 import com.lambda.module.Module
diff --git a/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt b/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt
index aa5284317..ffef18d87 100644
--- a/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt
+++ b/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt
@@ -26,8 +26,8 @@ import com.lambda.interaction.construction.blueprint.TickingBlueprint.Companion.
 import com.lambda.interaction.construction.verify.TargetState
 import com.lambda.interaction.container.selection.ContainerSelection
 import com.lambda.interaction.container.selection.stackSelection
+import com.lambda.interaction.handler.handlers.ContainerHandler.findStack
 import com.lambda.interaction.handler.handlers.FriendHandler.isFriend
-import com.lambda.interaction.handler.handlers.findStack
 import com.lambda.module.Module
 import com.lambda.module.ModuleTag
 import com.lambda.task.Task
diff --git a/src/main/kotlin/com/lambda/module/modules/combat/Surround.kt b/src/main/kotlin/com/lambda/module/modules/combat/Surround.kt
index 7316a1c89..42b4fea4d 100644
--- a/src/main/kotlin/com/lambda/module/modules/combat/Surround.kt
+++ b/src/main/kotlin/com/lambda/module/modules/combat/Surround.kt
@@ -25,7 +25,7 @@ import com.lambda.interaction.construction.blueprint.TickingBlueprint.Companion.
 import com.lambda.interaction.construction.verify.TargetState
 import com.lambda.interaction.container.selection.ContainerSelection
 import com.lambda.interaction.container.selection.stackSelection
-import com.lambda.interaction.handler.handlers.findStack
+import com.lambda.interaction.handler.handlers.ContainerHandler.findStack
 import com.lambda.module.Module
 import com.lambda.module.ModuleTag
 import com.lambda.module.modules.combat.PlayerTrap.getTrapPositions
diff --git a/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/Opportunity.kt b/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/Opportunity.kt
index a47a7e86a..3a85e4ca2 100644
--- a/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/Opportunity.kt
+++ b/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/Opportunity.kt
@@ -23,7 +23,7 @@ import com.lambda.interaction.container.containers.HotbarContainer
 import com.lambda.interaction.container.containers.OffhandContainer
 import com.lambda.interaction.container.selection.select
 import com.lambda.interaction.container.selection.stackSelection
-import com.lambda.interaction.handler.handlers.move
+import com.lambda.interaction.handler.handlers.moveStack
 import com.lambda.interaction.manager.managers.hotbar.hotbarRequest
 import com.lambda.interaction.manager.managers.rotating.Rotation.Companion.rotationTo
 import com.lambda.interaction.manager.managers.rotating.RotationManager
@@ -144,12 +144,12 @@ class Opportunity(
 				if (swapHand == Hand.MAIN_HAND) {
 					val itemStack = selection.bestMatch(player.hotbarStacks)
 					if (itemStack == null) { // retrieve to hotbar
-						if (!selection.move(toSelection = swapTo)) return@runSafe
+						if (!selection.moveStack(toSelection = swapTo)) return@runSafe
 					}
 					val s = player.hotbarStacks.indexOf(itemStack)
 					if ((!hotbarRequest(s, nowOrNothing = false).submit().done) && itemToUse == null) return@runSafe
 				} else { // retrieve to offhand
-					if (!selection.move(toSelection = swapTo)) return@runSafe
+					if (!selection.moveStack(toSelection = swapTo)) return@runSafe
 				}
 			}
 		}
diff --git a/src/main/kotlin/com/lambda/module/modules/debug/ContainerTransferTest.kt b/src/main/kotlin/com/lambda/module/modules/debug/ContainerTransferTest.kt
index 44a1db0f2..d0bebae75 100644
--- a/src/main/kotlin/com/lambda/module/modules/debug/ContainerTransferTest.kt
+++ b/src/main/kotlin/com/lambda/module/modules/debug/ContainerTransferTest.kt
@@ -25,8 +25,8 @@ import com.lambda.interaction.container.PlacedContainer
 import com.lambda.interaction.container.containers.external.DoubleChestContainer
 import com.lambda.interaction.container.selection.containerSelection
 import com.lambda.interaction.container.selection.select
+import com.lambda.interaction.handler.handlers.ContainerHandler.findContainers
 import com.lambda.interaction.handler.handlers.ContainerSearchScope
-import com.lambda.interaction.handler.handlers.findContainers
 import com.lambda.module.Module
 import com.lambda.module.ModuleTag
 import com.lambda.task.start
@@ -78,7 +78,8 @@ object ContainerTransferTest : Module(
                 event.cancel()
                 
                 runSafeAutomated {
-                    startTransferTask(sourcePos!!, destPos!!)
+                    val from = sourcePos
+                    if (from != null) startTransferTask(from, pos)
                 }
                 
                 disable()
diff --git a/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt b/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt
index 4d0c86f9a..44feec8e9 100644
--- a/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt
+++ b/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt
@@ -33,9 +33,9 @@ import com.lambda.interaction.container.containers.OffhandContainer
 import com.lambda.interaction.container.selection.select
 import com.lambda.interaction.container.selection.selectContainers
 import com.lambda.interaction.container.selection.stackSelection
+import com.lambda.interaction.handler.handlers.ContainerHandler.findSlot
+import com.lambda.interaction.handler.handlers.ContainerHandler.findStack
 import com.lambda.interaction.handler.handlers.GlideHandler
-import com.lambda.interaction.handler.handlers.findSlot
-import com.lambda.interaction.handler.handlers.findStack
 import com.lambda.interaction.manager.managers.hotbar.hotbarRequest
 import com.lambda.interaction.manager.managers.inventory.inventoryRequest
 import com.lambda.module.Module
diff --git a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt
index ee74cea47..4f3f41dc9 100644
--- a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt
+++ b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFlyMode.kt
@@ -27,9 +27,9 @@ import com.lambda.event.listener.SafeListener.Companion.listen
 import com.lambda.interaction.container.containers.ArmorContainer
 import com.lambda.interaction.container.selection.ContainerSelection
 import com.lambda.interaction.container.selection.select
+import com.lambda.interaction.handler.handlers.ContainerHandler.findSlot
+import com.lambda.interaction.handler.handlers.ContainerHandler.findSlots
 import com.lambda.interaction.handler.handlers.GlideHandler.ELYTRA_SELECTION
-import com.lambda.interaction.handler.handlers.findSlot
-import com.lambda.interaction.handler.handlers.findSlots
 import com.lambda.interaction.manager.managers.hotbar.hotbarRequest
 import com.lambda.interaction.manager.managers.inventory.InvRequestBuilder
 import com.lambda.interaction.manager.managers.inventory.inventoryRequest
@@ -97,6 +97,7 @@ abstract class ElytraFlyMode(
 		val elytraInHotbar = elytraSlot.index in 0..8
 
 		val chestSlot = findSlots(containerSelection = ArmorContainer.select())
+			.orEmpty()
 			.toList()
 			.getOrNull(1)
 			?: return false
diff --git a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/GrimControlElytraFly.kt b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/GrimControlElytraFly.kt
index 8893b9e7d..368ec50fc 100644
--- a/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/GrimControlElytraFly.kt
+++ b/src/main/kotlin/com/lambda/module/modules/movement/elytrafly/modes/GrimControlElytraFly.kt
@@ -25,7 +25,7 @@ import com.lambda.event.listener.SafeListener.Companion.listen
 import com.lambda.interaction.container.containers.HotbarContainer
 import com.lambda.interaction.container.containers.InventoryContainer
 import com.lambda.interaction.container.selection.select
-import com.lambda.interaction.handler.handlers.findStack
+import com.lambda.interaction.handler.handlers.ContainerHandler.findStack
 import com.lambda.interaction.manager.managers.rotating.RotationManager
 import com.lambda.interaction.manager.managers.rotating.rotationRequest
 import com.lambda.module.modules.movement.BetterFirework.startFirework
diff --git a/src/main/kotlin/com/lambda/module/modules/player/AutoElytraSwap.kt b/src/main/kotlin/com/lambda/module/modules/player/AutoElytraSwap.kt
index 6ffa13cd2..ad750337c 100644
--- a/src/main/kotlin/com/lambda/module/modules/player/AutoElytraSwap.kt
+++ b/src/main/kotlin/com/lambda/module/modules/player/AutoElytraSwap.kt
@@ -23,12 +23,12 @@ import com.lambda.interaction.container.containers.HotbarContainer
 import com.lambda.interaction.container.containers.InventoryContainer
 import com.lambda.interaction.container.selection.ContainerSelection
 import com.lambda.interaction.container.selection.select
+import com.lambda.interaction.handler.handlers.ContainerHandler.findSlot
+import com.lambda.interaction.handler.handlers.ContainerHandler.findSlots
 import com.lambda.interaction.handler.handlers.GlideHandler.CHESTPLATE_SELECTION
 import com.lambda.interaction.handler.handlers.GlideHandler.ELYTRA_SELECTION
 import com.lambda.interaction.handler.handlers.GlideHandler.manuallySwapped
 import com.lambda.interaction.handler.handlers.GlideHandler.swapped
-import com.lambda.interaction.handler.handlers.findSlot
-import com.lambda.interaction.handler.handlers.findSlots
 import com.lambda.module.Module
 import com.lambda.module.ModuleTag
 import com.lambda.module.modules.combat.AutoArmor
@@ -65,6 +65,7 @@ object AutoElytraSwap : Module(
 
 	private fun swapWithChestplate(swapSlot: Slot): Boolean {
 		val chestplateSlot = findSlots(containerSelection = ArmorContainer.select())
+			.orEmpty()
 			.toList()
 			.getOrNull(1)
 			?: return false
diff --git a/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt b/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt
index 09e30cfe3..08c61cd82 100644
--- a/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt
+++ b/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt
@@ -28,8 +28,8 @@ import com.lambda.interaction.container.NestedContainer
 import com.lambda.interaction.container.OpenedContainerContext
 import com.lambda.interaction.container.containers.external.EnderChestContainer
 import com.lambda.interaction.container.selection.containerSelection
+import com.lambda.interaction.handler.handlers.ContainerHandler.findContainer
 import com.lambda.interaction.handler.handlers.ContainerSearchScope
-import com.lambda.interaction.handler.handlers.findContainer
 import com.lambda.module.Module
 import com.lambda.module.ModuleTag
 import com.lambda.task.Task
diff --git a/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt b/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt
index 45795c9e2..0b4e6d7f3 100644
--- a/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt
+++ b/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt
@@ -28,7 +28,7 @@ import com.lambda.interaction.container.containers.InventoryContainer
 import com.lambda.interaction.container.containers.OffhandContainer
 import com.lambda.interaction.container.selection.select
 import com.lambda.interaction.container.selection.stackSelection
-import com.lambda.interaction.handler.handlers.findSlots
+import com.lambda.interaction.handler.handlers.ContainerHandler.findSlots
 import com.lambda.interaction.manager.managers.inventory.inventoryRequest
 import com.lambda.module.Module
 import com.lambda.module.ModuleTag
@@ -56,8 +56,8 @@ object StackReplenish : Module(
 
 		listen {
 			if (player.currentScreenHandler.cursorStack.item !== Items.AIR) return@listen
-			findSlots(containerSelection = HotbarContainer.select()).forEach { slot -> checkReplenish(slot) }
-			if (offhand) findSlots(containerSelection = OffhandContainer.select()).forEach { slot -> checkReplenish(slot) }
+			findSlots(containerSelection = HotbarContainer.select())?.forEach { slot -> checkReplenish(slot) }
+			if (offhand) findSlots(containerSelection = OffhandContainer.select())?.forEach { slot -> checkReplenish(slot) }
 		}
 	}
 
@@ -72,7 +72,7 @@ object StackReplenish : Module(
 					predicate { s, _ -> ItemStack.areItemsAndComponentsEqual(s, stack) }
 				},
 				InventoryContainer.select()
-			)
+			) ?: return
 
 		replenishSlots.forEach { replenishSlot ->
 			val invStack = replenishSlot.stack
diff --git a/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt b/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt
index f9d0cb5cb..4fc3eb191 100644
--- a/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt
+++ b/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt
@@ -27,7 +27,7 @@ import com.lambda.interaction.container.containers.HotbarContainer
 import com.lambda.interaction.container.containers.InventoryContainer
 import com.lambda.interaction.container.selection.select
 import com.lambda.interaction.container.selection.stackSelection
-import com.lambda.interaction.handler.handlers.findSlots
+import com.lambda.interaction.handler.handlers.ContainerHandler.findSlots
 import com.lambda.module.Module
 import com.lambda.module.ModuleTag
 import com.lambda.threading.runSafeAutomated
@@ -61,6 +61,7 @@ object ToolSaver : Module(
 			val inventorySlots = findSlots(containerSelection = InventoryContainer.select())
 
 			val swaps = endangeredSlots
+				.orEmpty()
 				.mapNotNull { endangered ->
 					val sorter = compareByDescending { swapSlot ->
 						if (!replace) 0
@@ -81,6 +82,7 @@ object ToolSaver : Module(
 						it.stack.isStackable
 					}
 					val swapWith = inventorySlots
+						.orEmpty()
 						.filter { !it.stack.isEndangered }
 						.sortedWith(sorter)
 						.firstOrNull()
diff --git a/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt b/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt
index e940f8a18..0d679a1ba 100644
--- a/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt
+++ b/src/main/kotlin/com/lambda/module/modules/world/AutoPortal.kt
@@ -38,7 +38,7 @@ import com.lambda.interaction.container.containers.InventoryContainer
 import com.lambda.interaction.container.selection.select
 import com.lambda.interaction.container.selection.stackSelection
 import com.lambda.interaction.handler.handlers.BaritoneHandler
-import com.lambda.interaction.handler.handlers.findSlot
+import com.lambda.interaction.handler.handlers.ContainerHandler.findSlot
 import com.lambda.interaction.manager.managers.hotbar.hotbarRequest
 import com.lambda.interaction.manager.managers.inventory.inventoryRequest
 import com.lambda.module.Module
diff --git a/src/main/kotlin/com/lambda/module/modules/world/MapDownloader.kt b/src/main/kotlin/com/lambda/module/modules/world/MapDownloader.kt
index 608deef16..b35d66736 100644
--- a/src/main/kotlin/com/lambda/module/modules/world/MapDownloader.kt
+++ b/src/main/kotlin/com/lambda/module/modules/world/MapDownloader.kt
@@ -20,7 +20,7 @@ package com.lambda.module.modules.world
 import com.lambda.event.events.TickEvent
 import com.lambda.event.listener.SafeListener.Companion.listen
 import com.lambda.interaction.container.selection.ContainerSelection
-import com.lambda.interaction.handler.handlers.findStacks
+import com.lambda.interaction.handler.handlers.ContainerHandler.findStacks
 import com.lambda.module.Module
 import com.lambda.module.ModuleTag
 import com.lambda.util.FileUtils.locationBoundDirectory
@@ -45,6 +45,7 @@ object MapDownloader : Module(
             val mapStates = entitySearch(128.0)
                 .mapNotNull { FilledMapItem.getMapState(it.heldItemStack, world) } +
                     findStacks(containerSelection = ContainerSelection.PLAYER)
+                        .orEmpty()
                         .mapNotNull { FilledMapItem.getMapState(it, world) }
 
             mapStates.forEach { map ->
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 d82106b60..f7ac85475 100644
--- a/src/main/kotlin/com/lambda/module/modules/world/Scaffold.kt
+++ b/src/main/kotlin/com/lambda/module/modules/world/Scaffold.kt
@@ -33,7 +33,7 @@ import com.lambda.interaction.construction.verify.TargetState
 import com.lambda.interaction.container.containers.HotbarContainer
 import com.lambda.interaction.container.selection.select
 import com.lambda.interaction.container.selection.stackSelection
-import com.lambda.interaction.handler.handlers.findStack
+import com.lambda.interaction.handler.handlers.ContainerHandler.findStack
 import com.lambda.interaction.manager.managers.interacting.interactRequest
 import com.lambda.module.Module
 import com.lambda.module.ModuleTag
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 d431b53fc..96767e5da 100644
--- a/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt
+++ b/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt
@@ -45,10 +45,10 @@ import com.lambda.interaction.container.selection.ContainerSelection
 import com.lambda.interaction.container.selection.select
 import com.lambda.interaction.container.selection.stackSelection
 import com.lambda.interaction.handler.handlers.BaritoneHandler
-import com.lambda.interaction.handler.handlers.findSlot
-import com.lambda.interaction.handler.handlers.findSlots
-import com.lambda.interaction.handler.handlers.findStack
-import com.lambda.interaction.handler.handlers.findStacks
+import com.lambda.interaction.handler.handlers.ContainerHandler.findSlot
+import com.lambda.interaction.handler.handlers.ContainerHandler.findSlots
+import com.lambda.interaction.handler.handlers.ContainerHandler.findStack
+import com.lambda.interaction.handler.handlers.ContainerHandler.findStacks
 import com.lambda.interaction.manager.managers.hotbar.hotbarRequest
 import com.lambda.interaction.manager.managers.inventory.inventoryRequest
 import com.lambda.interaction.manager.managers.rotating.Rotation
@@ -541,7 +541,9 @@ object StashMover : Module(
 			val move =
 				moveFromContainerToContainer(
 					screenHandler.containerSlots,
-					findStacks(containerSelection = ContainerSelection.HOTBAR_AND_INVENTORY).toList()
+					findStacks(containerSelection = ContainerSelection.HOTBAR_AND_INVENTORY)
+						.orEmpty()
+						.toList()
 				)
 			if (!move) return
 			pullContainer?.let { container ->
@@ -568,7 +570,9 @@ object StashMover : Module(
 			}
 			val move =
 				moveFromContainerToContainer(
-					findSlots(containerSelection = ContainerSelection.HOTBAR_AND_INVENTORY).toList(),
+					findSlots(containerSelection = ContainerSelection.HOTBAR_AND_INVENTORY)
+						.orEmpty()
+						.toList(),
 					screenHandler.containerStacks
 				)
 			if (move) {
@@ -608,7 +612,12 @@ object StashMover : Module(
 				rotation(rotation)
 			}.submit()
 			if (!rotationRequest.done || rotation dist RotationManager.serverRotation > 0.001) return
-			val throwSlots = findSlots(stackSelection { notEmpty() }, ContainerSelection.HOTBAR_AND_INVENTORY).toList()
+			val throwSlots =
+				findSlots(
+					stackSelection { notEmpty() },
+					ContainerSelection.HOTBAR_AND_INVENTORY
+				).orEmpty()
+					.toList()
 			if (throwSlots.isNotEmpty()) {
 				val inventoryRequest = inventoryRequest(settleForLess = true) {
 					throwSlots.forEach { slot ->
@@ -627,7 +636,9 @@ object StashMover : Module(
 			}
 			val move =
 				moveFromContainerToContainer(
-					findSlots(containerSelection = ContainerSelection.HOTBAR_AND_INVENTORY).toList(),
+					findSlots(containerSelection = ContainerSelection.HOTBAR_AND_INVENTORY)
+						.orEmpty()
+						.toList(),
 					screenHandler.containerStacks
 				)
 			if (!move) return
@@ -652,7 +663,9 @@ object StashMover : Module(
 			val move =
 				moveFromContainerToContainer(
 					screenHandler.containerSlots,
-					findStacks(containerSelection = ContainerSelection.HOTBAR_AND_INVENTORY).toList()
+					findStacks(containerSelection = ContainerSelection.HOTBAR_AND_INVENTORY)
+						.orEmpty()
+						.toList()
 				)
 			if (move) putOrThrowItems()
 		}
diff --git a/src/main/kotlin/com/lambda/task/tasks/AcquirePlacedBlockTask.kt b/src/main/kotlin/com/lambda/task/tasks/AcquirePlacedBlockTask.kt
index dc36b22be..5f3f903e1 100644
--- a/src/main/kotlin/com/lambda/task/tasks/AcquirePlacedBlockTask.kt
+++ b/src/main/kotlin/com/lambda/task/tasks/AcquirePlacedBlockTask.kt
@@ -23,7 +23,7 @@ import com.lambda.event.events.TickEvent
 import com.lambda.event.listener.SafeListener.Companion.listen
 import com.lambda.interaction.container.selection.ContainerSelection
 import com.lambda.interaction.container.selection.select
-import com.lambda.interaction.handler.handlers.findSlot
+import com.lambda.interaction.handler.handlers.ContainerHandler.findSlot
 import com.lambda.task.Task
 import com.lambda.task.Task.Ta5kBuilder
 import com.lambda.task.tasks.wrappers.softFail
diff --git a/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt b/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt
index 966a68200..20e4ebdab 100644
--- a/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt
+++ b/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt
@@ -120,6 +120,11 @@ fun breakAndCollect(
     finishOnDone, true, lifeMaintenance, async, automated, buildResultFilter
 )
 
+data class BuildOutcome(
+    val structure: Structure,
+    val pickups: List,
+)
+
 class BuildTask @Ta5kBuilder internal constructor(
     private val blueprint: Blueprint,
     private val finishOnDone: Boolean,
@@ -128,7 +133,7 @@ class BuildTask @Ta5kBuilder internal constructor(
     private val async: Boolean,
     automated: Automated,
     private val buildResultFilter: SafeContext.(BuildResult) -> Boolean,
-) : Task(), Automated by automated {
+) : Task(), Automated by automated {
     override val name: String get() = "Building $blueprint with ${(breaks / (age / 20.0 + 0.001)).format(precision = 1)} b/s ${(placements / (age / 20.0 + 0.001)).format(precision = 1)} p/s"
 
     private val pendingInteractions = ConcurrentLinkedQueue()
@@ -138,6 +143,7 @@ class BuildTask @Ta5kBuilder internal constructor(
     private var placements = 0
     private var breaks = 0
     private val dropsToCollect = mutableSetOf()
+    private val pickups = mutableListOf()
     var eatTask: Task<*>? = null
     var collectDropsTask: Task<*>? = null
 
@@ -312,7 +318,7 @@ class BuildTask @Ta5kBuilder internal constructor(
             is PreSimResult.NoPermission,
             is GenericResult.Ignored -> {
                 if (iteratePropagating()) return
-                if (finishOnDone) success(blueprint.structure)
+                if (finishOnDone) success(BuildOutcome(blueprint.structure, pickups.toList()))
             }
 
             is GenericResult.NotVisible,
@@ -362,6 +368,7 @@ class BuildTask @Ta5kBuilder internal constructor(
         if (pendingInteractions.isNotEmpty()) return true
         if (collectDropsTask != null) return true
         collectDropsTask = collectDrops(dropsToCollect)
+            .onSuccess { pickups.addAll(it) }
             .thenAction { collectDropsTask = null }
             .start()
         return true
diff --git a/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt b/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt
index c6209d0db..cb97f0599 100644
--- a/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt
+++ b/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt
@@ -18,67 +18,108 @@
 package com.lambda.task.tasks
 
 import baritone.api.pathing.goals.GoalBlock
+import com.lambda.Lambda.LOG
 import com.lambda.context.Automated
 import com.lambda.context.AutomatedSafeContext
+import com.lambda.event.events.PacketEvent
 import com.lambda.event.events.TickEvent
 import com.lambda.event.listener.SafeListener.Companion.listen
 import com.lambda.interaction.container.selection.ContainerSelection
 import com.lambda.interaction.container.selection.stackSelection
 import com.lambda.interaction.handler.handlers.BaritoneHandler
-import com.lambda.interaction.handler.handlers.findSlot
-import com.lambda.interaction.handler.handlers.findStack
+import com.lambda.interaction.handler.handlers.ContainerHandler.findSlot
+import com.lambda.interaction.handler.handlers.ContainerHandler.stacksAvailable
 import com.lambda.interaction.manager.managers.inventory.inventoryRequest
+import com.lambda.module.modules.client.Client.verboseDebug
 import com.lambda.task.Task
 import com.lambda.task.Task.Ta5kBuilder
 import com.lambda.threading.runSafeAutomated
+import com.lambda.util.player.SlotUtils.hotbarAndInventorySlots
 import net.minecraft.entity.ItemEntity
+import net.minecraft.item.ItemStack
+import net.minecraft.network.packet.s2c.play.ItemPickupAnimationS2CPacket
+import net.minecraft.screen.slot.Slot
+import net.minecraft.util.math.BlockPos
+import java.util.concurrent.ConcurrentHashMap
 
 @Ta5kBuilder
 context(automated: Automated)
 fun collectDrops(drops: MutableSet) = CollectDropsTask(drops, automated)
 
-/**
- * A task that collects a set of item entity drops by walking to them.
- *
- * Items are considered collected when they despawn from the world (picked up by the player).
- * If inventory is full, disposable items are thrown to make space.
- *
- * Succeeds when all drops have been collected.
- */
+data class PickedUpItem(val stack: ItemStack, val slot: Slot?)
+
 class CollectDropsTask @Ta5kBuilder internal constructor(
 	private val drops: MutableSet,
 	automated: Automated
-) : Task(), Automated by automated {
+) : Task>(), Automated by automated {
 	override val name get() = "Collecting ${drops.size} item drop(s)"
 
+	private val pickups = mutableListOf()
+	private var lastSeen: Map? = null
+	private var lastSeenSync: Int? = null
+	private val attributed = mutableMapOf()
+	private val closingAge = mutableMapOf()
+	private val dropStacks = mutableMapOf()
+	private val packets = ConcurrentHashMap()
+	private val lost = mutableMapOf()
+	private var selfId = -1
+	private var lastGoal: BlockPos? = null
+
 	init {
 		onCompletion { BaritoneHandler.cancel() }
 
+		listen { event ->
+			val packet = event.packet as? ItemPickupAnimationS2CPacket ?: return@listen
+			packets[packet.entityId] = packet.collectorEntityId
+		}
+
 		listen {
 			runSafeAutomated {
-				do {
-					val done = pollNextItem()
-				} while (!done)
+				pollNextItem()
 			}
 		}
 	}
 
-	private fun AutomatedSafeContext.pollNextItem(): Boolean {
-		val target =
-			drops.firstOrNull()
-				?: run {
-					BaritoneHandler.cancel()
-					success()
-					return true
-				}
+	private fun AutomatedSafeContext.pollNextItem() {
+		val live = liveStacks()
+		val syncId = player.currentScreenHandler.syncId
+		selfId = player.id
+		drops.forEach { stackOf(it) }
+		val previous = lastSeen
+		if (previous != null && lastSeenSync == syncId) {
+			attributeGrowth(previous, live)
+		}
+		ageClosing()
+		lastSeen = live
+		lastSeenSync = syncId
 
-		if (!world.entities.contains(target)) {
-			drops.remove(target)
+		val gone = drops.filter { !world.entities.contains(it) }
+		gone.forEach { drop ->
+			if (isTakenByOther(drop)) lost[drop] = packets[drop.id]
+			else if (remainderOf(drop) > 0) closingAge.putIfAbsent(drop, 0)
+		}
+		if (gone.isNotEmpty()) {
+			drops.removeAll(gone.toSet())
 			BaritoneHandler.cancel()
-			return false
 		}
 
-		if (findStack(stackSelection(0) { isEmpty() }, ContainerSelection.HOTBAR_AND_INVENTORY, false) == null) {
+		val target = drops.firstOrNull()
+		if (target == null) {
+			if (closingAge.isEmpty()) {
+				BaritoneHandler.cancel()
+				if (verboseDebug) LOG.info("[tweaks] collected ${pickups.size} pickup(s) in $age ticks")
+				if (lost.isEmpty()) success(pickups.toList())
+				else failure(describeLost())
+			}
+			return
+		}
+
+		val emptySlotAvailable =
+			stacksAvailable(
+				stackSelection(0) { isEmpty() },
+				ContainerSelection.HOTBAR_AND_INVENTORY
+			)
+		if (!emptySlotAvailable) {
 			val slotToThrow =
 				findSlot(
 					stackSelection { ofAnyItems(inventoryConfig.disposables) },
@@ -86,17 +127,106 @@ class CollectDropsTask @Ta5kBuilder internal constructor(
 					sorted = false
 				) ?: run {
 					failure("Inventory is full and no disposable items to throw")
-					return true
+					return
 				}
 
 			inventoryRequest {
 				throwStack(slotToThrow.id)
 			}.submit()
-			return true
+			return
+		}
+
+		if (target.blockPos != lastGoal) {
+			lastGoal = target.blockPos
+			BaritoneHandler.setGoalAndPath(GoalBlock(target.blockPos))
+		}
+	}
+
+	private fun AutomatedSafeContext.liveStacks() =
+		player.hotbarAndInventorySlots.associateWith { it.stack.copy() }
+
+	private fun stackOf(drop: ItemEntity): ItemStack {
+		val live = drop.stack
+		if (!live.isEmpty && dropStacks[drop]?.let { !ItemStack.areItemsAndComponentsEqual(live, it) } != false) {
+			dropStacks[drop] = live.copy()
 		}
+		return dropStacks[drop] ?: ItemStack.EMPTY
+	}
+
+	private fun remainderOf(drop: ItemEntity) = stackOf(drop).count - (attributed[drop] ?: 0)
 
-		BaritoneHandler.setGoalAndPath(GoalBlock(target.blockPos))
+	private fun attributeGrowth(before: Map, live: Map) {
+		live.forEach { (slot, now) ->
+			if (now.isEmpty) return@forEach
+			val was = before[slot]?.takeIf { ItemStack.areItemsAndComponentsEqual(it, now) }?.count ?: 0
+			if (now.count <= was) return@forEach
+			consumeGrowth(now, now.count - was, slot)
+		}
+	}
+
+	private fun isTakenByOther(drop: ItemEntity) = packets[drop.id]?.let { it != selfId } == true
+
+	private fun consumeGrowth(stack: ItemStack, count: Int, slot: Slot) {
+		var left = count
+		for (drop in closingAge.keys.toList()) {
+			if (left <= 0) break
+			left -= takeFrom(drop, stack, left, slot)
+		}
+		if (left <= 0) return
+		drops.forEach { drop ->
+			if (left <= 0) return
+			left -= takeFrom(drop, stack, left, slot)
+		}
+	}
+
+	private fun takeFrom(drop: ItemEntity, stack: ItemStack, left: Int, slot: Slot): Int {
+		if (isTakenByOther(drop)) return 0
+		if (!matchesDrop(stack, stackOf(drop))) return 0
+		val take = minOf(left, remainderOf(drop))
+		if (take <= 0) return 0
+		pickups += PickedUpItem(stack.copyWithCount(take), slot)
+		attributed[drop] = (attributed[drop] ?: 0) + take
+		if (remainderOf(drop) <= 0) closingAge.remove(drop)
+		return take
+	}
+
+	private fun matchesDrop(stack: ItemStack, snap: ItemStack): Boolean {
+		if (stack.isEmpty || snap.isEmpty) return false
+		return stack.item == snap.item && stack.name.string == snap.name.string
+	}
+
+	private fun ageClosing() {
+		if (verboseDebug && closingAge.isNotEmpty()) {
+			LOG.info("[tweaks] closing waits: " + closingAge.entries.joinToString { (drop, age) ->
+				"age=$age remainder=${remainderOf(drop)} snap=${stackOf(drop)}"
+			})
+		}
+		closingAge.entries.toList().forEach { (drop, age) ->
+			if (age < CLOSE_GRACE_TICKS) closingAge[drop] = age + 1
+			else {
+				val collector = packets.remove(drop.id)
+				val left = remainderOf(drop)
+				if (collector == selfId) {
+					if (left > 0) pickups += PickedUpItem(stackOf(drop).copyWithCount(left), null)
+					attributed.remove(drop)
+				} else {
+					lost[drop] = collector
+				}
+				closingAge.remove(drop)
+			}
+		}
+	}
+
+	private fun AutomatedSafeContext.describeLost(): String {
+		val parts = lost.map { (drop, collector) ->
+			val what = stackOf(drop).let { "${remainderOf(drop)}x ${it.name.string}" }
+			val who = collector?.let { id -> world.getEntityById(id)?.name?.string } ?: "unknown causes"
+			"$what lost to $who"
+		}
+		return "Failed to collect ${lost.size} drop(s): ${parts.joinToString("; ")}"
+	}
 
-		return true
+	private companion object {
+		const val CLOSE_GRACE_TICKS = 20
 	}
 }
diff --git a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt
index 09585e4d3..b69c88014 100644
--- a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt
+++ b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt
@@ -34,9 +34,10 @@ import com.lambda.interaction.container.selection.ContainerSelection
 import com.lambda.interaction.container.selection.StackSelection
 import com.lambda.interaction.container.selection.mutate
 import com.lambda.interaction.container.selection.selectContainers
+import com.lambda.interaction.container.selection.stackSelection
+import com.lambda.interaction.handler.handlers.ContainerHandler.findContainers
 import com.lambda.interaction.handler.handlers.PacketLimitHandler.availablePackets
 import com.lambda.interaction.handler.handlers.PacketType
-import com.lambda.interaction.handler.handlers.findContainers
 import com.lambda.interaction.manager.managers.inventory.InvRequestFailureReason
 import com.lambda.interaction.manager.managers.inventory.inventoryRequest
 import com.lambda.module.modules.client.Client.verboseDebug
@@ -88,7 +89,7 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 ) : Task(), Automated by automated {
 	override val name = "Transferring $fromStack"
 
-	private val unlimited = fromStack.count <= 0
+	private val unlimited = fromStack.count < 0
 
 	private var transferred = 0
 	private val remaining
@@ -218,7 +219,9 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 				containers = resultContainers.toTypedArray(),
 				scope = toSelection.scope
 			)
-		)
+		).also {
+			if (verboseDebug) LOG.info("[transfer] done, moved=$transferred")
+		}
 
 	private fun directChain(from: Container, to: Container): Task<*> {
 		val toContainers =
@@ -322,17 +325,21 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 		private val toContainers: List,
 		private val fromSelection: StackSelection,
 		toSelection: StackSelection,
-		private val limit: Int = if (unlimited) Int.MAX_VALUE else remaining,
+		private val limit: Int,
 		private val allowReplace: Boolean = true,
 		private val trackResult: Boolean = true
 	) : Task() {
 		override val name get() = "Moving ${this@MoveTask.fromSelection}"
 		private var moved = 0
 
-		// Raw caller ordering; the destination search only narrows it,
-		// so toStack stays the end-all for which slot gets used.
 		private val baseToSelection = toSelection
 
+		private val swappableSelection =
+			toSelection.mutate {
+				notEmpty()
+				withoutSelection(this@MoveTask.fromSelection)
+			}
+
 		init {
 			listen {
 				runSafeAutomated {
@@ -358,7 +365,6 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 					else minOf(currentLimit, fromSelection.count.coerceAtLeast(1))
 				) { notEmpty() }
 
-			// Find the best slot pair fresh — never cached across ticks
 			val pair =
 				fromContainers.firstNotNullOfOrNull { fromContainer ->
 					val fromSlot = fromContainer.findSlot(currentSelection) ?: return@firstNotNullOfOrNull null
@@ -381,23 +387,26 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 			return move(pair, atMost = currentLimit)
 		}
 
-		private fun findToSlot(fromSlot: Slot, toContainer: Container): Slot? {
+		private fun AutomatedSafeContext.findToSlot(fromSlot: Slot, toContainer: Container): Slot? {
 			val stack = fromSlot.stack
 			return toContainer.findSlot(
-				baseToSelection.mutate {
-					predicate { candidate, slot ->
-						when {
-							candidate.isEmpty -> true
-							candidate.sameItemAs(stack) -> candidate.hasSpace
-							else -> allowReplace &&
-								slot != null &&
-								!fromSelection.matches(slot) &&
-								fromSlot.canInsert(candidate)
-						}
-					}
+				stackSelection {
+					predicate { candidate, _ -> !candidate.isEmpty && candidate.hasSpace && candidate.sameItemAs(stack) }
 					canInsert(stack)
+					sortedWith(baseToSelection.comparator)
 				}
-			)
+			) ?: toContainer.findReplaceSlot(
+				stack,
+				stackSelection(0) {
+					isEmpty()
+					sortedWith(baseToSelection.comparator)
+				}
+			) ?: findSwappableSlot(fromSlot, toContainer)
+		}
+
+		private fun AutomatedSafeContext.findSwappableSlot(fromSlot: Slot, toContainer: Container): Slot? {
+			if (!allowReplace) return null
+			return toContainer.findReplaceSlot(fromSlot.stack, swappableSelection)?.takeIf { fromSlot.canInsert(it.stack) }
 		}
 
 		private fun AutomatedSafeContext.move(pair: SlotPair, atMost: Int): Boolean {
@@ -482,7 +491,6 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 			}
 			val stack = pair.fromSlot.stack
 			val cursor = CursorContainer.stacks.firstOrNull() ?: return true
-			// A foreign stack on the cursor is somebody else's business; wait until it has been put away.
 			if (cursor.isForeignTo(stack)) {
 				finish()
 				return true
@@ -498,7 +506,6 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 					clickBudget = budget
 				)
 			if (plan == null) {
-				// Even an untouched budget cannot fit the shortest plan: this pair can never be served, give it up.
 				if (budget >= buildConfig.inventoryLimit) finish()
 				return true
 			}
diff --git a/src/main/kotlin/com/lambda/task/tasks/EatTask.kt b/src/main/kotlin/com/lambda/task/tasks/EatTask.kt
index c9c5a00fb..d07ece07e 100644
--- a/src/main/kotlin/com/lambda/task/tasks/EatTask.kt
+++ b/src/main/kotlin/com/lambda/task/tasks/EatTask.kt
@@ -26,7 +26,7 @@ import com.lambda.event.listener.SafeListener.Companion.listen
 import com.lambda.interaction.container.containers.HotbarContainer
 import com.lambda.interaction.container.containers.InventoryContainer
 import com.lambda.interaction.container.selection.select
-import com.lambda.interaction.handler.handlers.findSlot
+import com.lambda.interaction.handler.handlers.ContainerHandler.findSlot
 import com.lambda.interaction.manager.managers.hotbar.hotbarRequest
 import com.lambda.task.Task
 import com.lambda.task.Task.Ta5kBuilder
diff --git a/src/main/kotlin/com/lambda/util/collections/LimitedDecayQueue.kt b/src/main/kotlin/com/lambda/util/collections/LimitedDecayQueue.kt
index a083c26d9..fdf2636ee 100644
--- a/src/main/kotlin/com/lambda/util/collections/LimitedDecayQueue.kt
+++ b/src/main/kotlin/com/lambda/util/collections/LimitedDecayQueue.kt
@@ -80,6 +80,14 @@ class LimitedDecayQueue(
         return queue.removeIf { it.first == element }
     }
 
+    @Synchronized
+    fun refresh(element: E): Boolean {
+        cleanUp()
+        if (!queue.removeIf { it.first == element }) return false
+        queue.add(element to Instant.now())
+        return true
+    }
+
     @Synchronized
     override fun removeAll(elements: Collection): Boolean {
         cleanUp()
@@ -97,23 +105,6 @@ class LimitedDecayQueue(
         queue.clear()
     }
 
-    /**
-     * Removes the first element matching the [predicate] and returns `true`, or `false` if no match was found.
-     * Unlike [removeIf], this stops after the first removal so that duplicate entries are consumed one at a time.
-     */
-    @Synchronized
-    fun removeFirstMatch(predicate: (E) -> Boolean): Boolean {
-        cleanUp()
-        val iterator = queue.iterator()
-        while (iterator.hasNext()) {
-            if (predicate(iterator.next().first)) {
-                iterator.remove()
-                return true
-            }
-        }
-        return false
-    }
-
     /**
      * Updates the maximum allowed size for the queue and triggers a cleanup operation
      * to remove elements exceeding the new size or falling outside the allowed time interval.
diff --git a/src/test/kotlin/InventorySyncFilterTest.kt b/src/test/kotlin/InventorySyncFilterTest.kt
index 0177adf43..12cefb885 100644
--- a/src/test/kotlin/InventorySyncFilterTest.kt
+++ b/src/test/kotlin/InventorySyncFilterTest.kt
@@ -163,4 +163,48 @@ class InventorySyncFilterTest {
         filter.observeLocal(0, playerSlots(0, empty), TestStack("dirt", 1))
         assertEquals(0, filter.totalPending())
     }
+
+    @Test
+    fun `slot change times track local changes only`() {
+        val clock = ManualClock()
+        val filter = filter(clock)
+
+        // Baselines establish truth without stamping anything.
+        filter.observeLocal(0, playerSlots(5, empty), empty)
+        filter.snapshot(5, mapOf(2 to empty), empty)
+        assertEquals(null, filter.lastChange(0, 5))
+        assertEquals(null, filter.lastChange(5, 2))
+
+        // A local change stamps exactly that slot at the recording time.
+        clock.now = Instant.fromEpochMilliseconds(1000)
+        filter.observeLocal(0, playerSlots(5, shulker("gear")), empty)
+        assertEquals(Instant.fromEpochMilliseconds(1000), filter.lastChange(0, 5))
+        assertEquals(null, filter.lastChange(0, 6))
+        assertEquals(null, filter.lastChange(5, 2))
+
+        // Unchanged slots re-diffed later keep their original stamp.
+        clock.now = Instant.fromEpochMilliseconds(2000)
+        filter.observeLocal(0, playerSlots(5, shulker("gear")), empty)
+        assertEquals(Instant.fromEpochMilliseconds(1000), filter.lastChange(0, 5))
+    }
+
+    @Test
+    fun `slot change times expire and clear with the screen`() {
+        val clock = ManualClock()
+        val filter = filter(clock, maxAgeMs = 1500L)
+
+        filter.observeLocal(0, playerSlots(5, empty), empty)
+        clock.now = Instant.fromEpochMilliseconds(1000)
+        filter.observeLocal(0, playerSlots(5, shulker("gear")), empty)
+        assertEquals(Instant.fromEpochMilliseconds(1000), filter.lastChange(0, 5))
+
+        // Older than maxAgeMs: unknown again, so lookups keep their old order.
+        clock.now = Instant.fromEpochMilliseconds(3000)
+        assertEquals(null, filter.lastChange(0, 5))
+
+        clock.now = Instant.fromEpochMilliseconds(4000)
+        filter.observeLocal(0, playerSlots(5, shulker("other")), empty)
+        filter.clear(0)
+        assertEquals(null, filter.lastChange(0, 5))
+    }
 }

From f2eadcdc062eb3ddebe4c6488661a6dc43c4b908 Mon Sep 17 00:00:00 2001
From: beanbag44 <107891830+beanbag44@users.noreply.github.com>
Date: Thu, 15 Oct 2026 15:21:59 +0100
Subject: [PATCH 40/43] cleanup

---
 .../construction/simulation/sims/BreakSim.kt  |  27 +--
 .../external/ShulkerBoxContainer.kt           | 183 +++++++++---------
 2 files changed, 106 insertions(+), 104 deletions(-)

diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/BreakSim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/BreakSim.kt
index e3e0795d0..d592829e0 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/BreakSim.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/sims/BreakSim.kt
@@ -138,21 +138,22 @@ class BreakSim internal constructor(simInfo: SimInfo)
 	}
 
 	private fun AutomatedSafeContext.getSwapStack(): Pair? {
-		val stackSelection = stackSelection {
-			if (breakConfig.efficientOnly) isEfficientForBreaking(state)
-			if (breakConfig.suitableToolsOnly) isSuitableForBreaking(state)
-			if (breakConfig.forceSilkTouch) withEnchantment(Enchantments.SILK_TOUCH)
-			if (breakConfig.forceFortunePickaxe) withEnchantment(Enchantments.FORTUNE)
-			sortedWith {
-				compareByDescending> {
-					it.stack.canBreak(CachedBlockPosition(world, pos, false))
-				}.thenByDescending {
-					state.calcItemBlockBreakingDelta(pos, it.stack)
-				}.thenByDescending {
-					it.stack.inventoryIndex == HotbarManager.serverSlot
+		val stackSelection =
+			stackSelection(0) {
+				if (breakConfig.efficientOnly) isEfficientForBreaking(state)
+				if (breakConfig.suitableToolsOnly) isSuitableForBreaking(state)
+				if (breakConfig.forceSilkTouch) withEnchantment(Enchantments.SILK_TOUCH)
+				if (breakConfig.forceFortunePickaxe) withEnchantment(Enchantments.FORTUNE)
+				sortedWith {
+					compareByDescending> {
+						it.stack.canBreak(CachedBlockPosition(world, pos, false))
+					}.thenByDescending {
+						state.calcItemBlockBreakingDelta(pos, it.stack)
+					}.thenByDescending {
+						it.stack.inventoryIndex == HotbarManager.serverSlot
+					}
 				}
 			}
-		}
 
 		val bestStack = findStack(stackSelection, HotbarContainer.select())
 			?: run {
diff --git a/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt
index a38158b0c..4a4484eb0 100644
--- a/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt
+++ b/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt
@@ -55,104 +55,105 @@ import net.minecraft.util.math.BlockPos
 import java.util.Objects.hash
 
 data class ShulkerBoxContainer(
-    val itemName: String,
-    val shulkerItem: Item,
-    override var stacks: List,
-    override val containedIn: Container,
-    override val index: Int,
+	val itemName: String,
+	val shulkerItem: Item,
+	override var stacks: List,
+	override val containedIn: Container,
+	override val index: Int,
 ) : NestedContainer(ContainerType.ShulkerBox), ExternalContainer {
-    override val slots
-        get(): List =
-            if (isAccessed) mc.player?.currentScreenHandler?.containerSlots ?: emptyList()
-            else emptyList()
+	override val slots
+		get(): List =
+			if (isAccessed) mc.player?.currentScreenHandler?.containerSlots ?: emptyList()
+			else emptyList()
 
-    override val description =
-        buildText {
-            highlighted(itemName)
-            literal(" in ")
-            highlighted(containedIn.name)
-            literal(" in slot index $index")
-        }
+	override val description =
+		buildText {
+			highlighted(itemName)
+			literal(" in ")
+			highlighted(containedIn.name)
+			literal(" in slot index $index")
+		}
 
-    override val isAccessed = false
+	override val isAccessed = false
 
-    @Ta5kBuilder
-    context(automated: Automated)
-    override fun access() = OpenShulkerBoxTask(automated)
+	@Ta5kBuilder
+	context(automated: Automated)
+	override fun access() = OpenShulkerBoxTask(automated)
 
-    inner class OpenShulkerBoxTask @Ta5kBuilder internal constructor(
-        automated: Automated
-    ) : OpenContainerTask(description), Automated by automated {
-        override fun SafeContext.onStart() {
-            if (verboseDebug) LOG.info("[tweaks] open start ${description.string}")
-            transfer(
-                stackSelection {
-                    sortedWith { compareByDescending { it.slot?.index == index } }
-                    isItem(shulkerItem)
-                    withName(itemName)
-                    sortedByBestContentMatch(stacks)
-                },
-                containedIn.select(ContainerSearchScope.Loaded),
-                HotbarContainer.select()
-            ).then { result ->
-                if (verboseDebug) LOG.info("[tweaks] transfer done, placing")
-                placeContainer(findSlot(result.stackSelection.mutate { sortedByRecentChange() }, result.containerSelection))
-            }
-                .then { pos ->
-                    openContainer(pos).onSuccess {
-                        if (verboseDebug) LOG.info("[tweaks] opened shulker at $pos")
-                        success(OpenedShulkerBoxContext(pos, containedIn))
-                    }
-                }
-                .start()
-        }
-    }
+	inner class OpenShulkerBoxTask @Ta5kBuilder internal constructor(
+		automated: Automated
+	) : OpenContainerTask(description), Automated by automated {
+		override fun SafeContext.onStart() {
+			transfer(
+				stackSelection {
+					sortedWith { compareByDescending { it.slot?.index == index } }
+					isItem(shulkerItem)
+					withName(itemName)
+					sortedByBestContentMatch(stacks)
+				},
+				containedIn.select(ContainerSearchScope.Loaded),
+				HotbarContainer.select()
+			).then { result ->
+				placeContainer(
+					findSlot(
+						result.stackSelection.mutate {
+							sortedByRecentChange()
+						},
+						result.containerSelection
+					)
+				)
+			}.then { pos ->
+				openContainer(pos).onSuccess {
+					success(OpenedShulkerBoxContext(pos))
+				}
+			}.start()
+		}
+	}
 
-    inner class OpenedShulkerBoxContext(
-        val blockPos: BlockPos,
-        val fromContainer: Container
-    ) : OpenedContainerContext {
-        context(automated: Automated)
-        override fun close(): Task<*>? {
-            if (verboseDebug) LOG.info("[tweaks] close start ${description.string}")
-            return taskOrNull {
-                if (!isAccessed) null
-                else actionTask { player.closeHandledScreen() }
-            }.then { breakAndCollect(blockPos) }
-                .then { outcome ->
-                    if (verboseDebug) LOG.info("[tweaks] broke and collected ${outcome.pickups.size} pickup(s)")
-                    val predicted = outcome.pickups
-                        .asSequence()
-                        .filter { it.stack.item == shulkerItem && it.stack.name.string == itemName }
-                        .firstNotNullOfOrNull { it.slot }
+	inner class OpenedShulkerBoxContext(
+		val blockPos: BlockPos,
+	) : OpenedContainerContext {
+		context(automated: Automated)
+		override fun close(): Task<*> {
+			if (verboseDebug) LOG.info("[tweaks] close start ${description.string}")
+			return taskOrNull {
+				if (!isAccessed) null
+				else actionTask { player.closeHandledScreen() }
+			}.then { breakAndCollect(blockPos) }
+				.then { outcome ->
+					if (verboseDebug) LOG.info("[tweaks] broke and collected ${outcome.pickups.size} pickup(s)")
+					val predicted = outcome.pickups
+						.asSequence()
+						.filter { it.stack.item == shulkerItem && it.stack.name.string == itemName }
+						.firstNotNullOfOrNull { it.slot }
 
-                    stackSelection {
-                        isItem(shulkerItem)
-                        withName(itemName)
-                        predicted?.let { sortedBySlotMatch(it) }
-                        sortedByBestContentMatch(stacks)
-                        sortedByRecentChange()
-                    }.transfer(
-                        ContainerSelection.HOTBAR_AND_INVENTORY,
-                        fromContainer.select(ContainerSearchScope.Loaded),
-                        stackSelection {
-                            sortedByMatches {
-                                stackSelection {
-                                    predicate { _, slot -> slot?.index == index }
-                                    isEmpty()
-                                }
-                            }
-                        },
-                        allowQuickMove = false
-                    )
-                }
-    }
-    }
+					stackSelection {
+						isItem(shulkerItem)
+						withName(itemName)
+						predicted?.let { sortedBySlotMatch(it) }
+						sortedByBestContentMatch(stacks)
+						sortedByRecentChange()
+					}.transfer(
+						ContainerSelection.HOTBAR_AND_INVENTORY,
+						containedIn.select(ContainerSearchScope.Loaded),
+						stackSelection {
+							sortedByMatches {
+								stackSelection {
+									predicate { _, slot -> slot?.index == index }
+									isEmpty()
+								}
+							}
+						},
+						allowQuickMove = false
+					)
+				}
+		}
+	}
 
-    override fun hashCode() = hash(containedIn.hashCode(), index)
+	override fun hashCode() = hash(containedIn.hashCode(), index)
 
-    override fun equals(other: Any?) =
-        other is ShulkerBoxContainer &&
-                containedIn == other.containedIn &&
-                index == other.index
+	override fun equals(other: Any?) =
+		other is ShulkerBoxContainer &&
+				containedIn == other.containedIn &&
+				index == other.index
 }
\ No newline at end of file

From 3b5a14d00996011ab4320a69dabe878918cc84b8 Mon Sep 17 00:00:00 2001
From: beanbag44 <107891830+beanbag44@users.noreply.github.com>
Date: Thu, 15 Oct 2026 15:46:11 +0100
Subject: [PATCH 41/43] more cleanup

---
 .../container/containers/external/EnderChestContainer.kt | 4 ----
 .../container/containers/external/ShulkerBoxContainer.kt | 4 ----
 .../manager/managers/breaking/BreakManager.kt            | 2 +-
 src/main/kotlin/com/lambda/task/Task.kt                  | 3 ---
 src/main/kotlin/com/lambda/task/tasks/BuildTask.kt       | 3 ---
 .../kotlin/com/lambda/task/tasks/CollectDropsTask.kt     | 8 --------
 .../com/lambda/task/tasks/ContainerTransferTask.kt       | 9 +--------
 7 files changed, 2 insertions(+), 31 deletions(-)

diff --git a/src/main/kotlin/com/lambda/interaction/container/containers/external/EnderChestContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/external/EnderChestContainer.kt
index 69f43d6e5..780eacc9f 100644
--- a/src/main/kotlin/com/lambda/interaction/container/containers/external/EnderChestContainer.kt
+++ b/src/main/kotlin/com/lambda/interaction/container/containers/external/EnderChestContainer.kt
@@ -17,7 +17,6 @@
 
 package com.lambda.interaction.container.containers.external
 
-import com.lambda.Lambda.LOG
 import com.lambda.Lambda.mc
 import com.lambda.context.Automated
 import com.lambda.context.SafeContext
@@ -30,7 +29,6 @@ import com.lambda.interaction.container.containers.HotbarContainer
 import com.lambda.interaction.container.selection.select
 import com.lambda.interaction.handler.handlers.ContainerHandler.findSlot
 import com.lambda.interaction.handler.handlers.ContainerHandler.lastInteractedBlockEntity
-import com.lambda.module.modules.client.Client.verboseDebug
 import com.lambda.task.Task.Ta5kBuilder
 import com.lambda.task.tasks.acquireStack
 import com.lambda.task.tasks.breakAndCollect
@@ -82,7 +80,6 @@ object EnderChestContainer : Container(ContainerType.EnderChest), ExternalContai
 			findBlock(Blocks.ENDER_CHEST, inventoryConfig.enderChestSearchRadius).withBranch(
 			onSuccess = { pos ->
 				openContainer(pos).onSuccess {
-					if (verboseDebug) LOG.info("[tweaks] ender open at $pos")
 					success(OpenedEnderChestContext(null))
 				}
 			},
@@ -91,7 +88,6 @@ object EnderChestContainer : Container(ContainerType.EnderChest), ExternalContai
 						.then { placeContainer(findSlot(it, HotbarContainer.select())) }
 					.then { pos ->
 						openContainer(pos).onSuccess {
-							if (verboseDebug) LOG.info("[tweaks] ender open at $pos")
 							success(OpenedEnderChestContext(pos))
 						}
 					}
diff --git a/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt
index 4a4484eb0..e3ccddd0e 100644
--- a/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt
+++ b/src/main/kotlin/com/lambda/interaction/container/containers/external/ShulkerBoxContainer.kt
@@ -17,7 +17,6 @@
 
 package com.lambda.interaction.container.containers.external
 
-import com.lambda.Lambda.LOG
 import com.lambda.Lambda.mc
 import com.lambda.context.Automated
 import com.lambda.context.SafeContext
@@ -34,7 +33,6 @@ import com.lambda.interaction.container.selection.select
 import com.lambda.interaction.container.selection.stackSelection
 import com.lambda.interaction.handler.handlers.ContainerHandler.findSlot
 import com.lambda.interaction.handler.handlers.ContainerSearchScope
-import com.lambda.module.modules.client.Client.verboseDebug
 import com.lambda.task.Task
 import com.lambda.task.Task.Ta5kBuilder
 import com.lambda.task.tasks.breakAndCollect
@@ -115,13 +113,11 @@ data class ShulkerBoxContainer(
 	) : OpenedContainerContext {
 		context(automated: Automated)
 		override fun close(): Task<*> {
-			if (verboseDebug) LOG.info("[tweaks] close start ${description.string}")
 			return taskOrNull {
 				if (!isAccessed) null
 				else actionTask { player.closeHandledScreen() }
 			}.then { breakAndCollect(blockPos) }
 				.then { outcome ->
-					if (verboseDebug) LOG.info("[tweaks] broke and collected ${outcome.pickups.size} pickup(s)")
 					val predicted = outcome.pickups
 						.asSequence()
 						.filter { it.stack.item == shulkerItem && it.stack.name.string == itemName }
diff --git a/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakManager.kt b/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakManager.kt
index 9a2f802e8..2e6bd7bf5 100644
--- a/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakManager.kt
+++ b/src/main/kotlin/com/lambda/interaction/manager/managers/breaking/BreakManager.kt
@@ -536,7 +536,7 @@ object BreakManager : Manager(0), PositionBlocking {
 	 * Simulates and updates the [abandonedBreak].
 	 */
 	private fun SafeContext.simulateAbandoned() {
-		// Canceled but double breaking so requires break manager to continue the simulation
+		// Cancelled but double breaking so requires break manager to continue the simulation
 		val abandonedInfo = abandonedBreak ?: return
 
 		abandonedInfo.request.runSafeAutomated {
diff --git a/src/main/kotlin/com/lambda/task/Task.kt b/src/main/kotlin/com/lambda/task/Task.kt
index 83da64c88..86d537e66 100644
--- a/src/main/kotlin/com/lambda/task/Task.kt
+++ b/src/main/kotlin/com/lambda/task/Task.kt
@@ -19,7 +19,6 @@
 
 package com.lambda.task
 
-import com.lambda.Lambda.LOG
 import com.lambda.context.SafeContext
 import com.lambda.event.EventFlow.unsubscribe
 import com.lambda.event.Muteable
@@ -101,10 +100,8 @@ abstract class Task : Nameable, Muteable {
 	    	parent.subTasks.add(this)
 	    	this@Task.parent = parent
 	    	if (pauseParent) {
-			    if (verboseDebug) LOG.info("$name pausing parent ${parent.name}")
 			    if (parent !is RootTask) parent.pause()
 		    }
-			if (verboseDebug) LOG.info("${parent.name} starting $name")
 			state = State.Running
 	    	runSafe {
 				runCatching {
diff --git a/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt b/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt
index 20e4ebdab..aecbf10ee 100644
--- a/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt
+++ b/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt
@@ -17,7 +17,6 @@
 
 package com.lambda.task.tasks
 
-import com.lambda.Lambda.LOG
 import com.lambda.config.blocks.EatConfig.Companion.reasonEating
 import com.lambda.context.Automated
 import com.lambda.context.AutomatedSafeContext
@@ -49,7 +48,6 @@ import com.lambda.interaction.handler.handlers.BaritoneHandler
 import com.lambda.interaction.manager.managers.breaking.breakRequest
 import com.lambda.interaction.manager.managers.interacting.interactRequest
 import com.lambda.module.modules.client.Client
-import com.lambda.module.modules.client.Client.verboseDebug
 import com.lambda.task.Task
 import com.lambda.task.Task.Ta5kBuilder
 import com.lambda.task.tasks.wrappers.softFail
@@ -355,7 +353,6 @@ class BuildTask @Ta5kBuilder internal constructor(
             is Dependent -> handleResult(result.lastDependency, allResults)
 
             is Resolvable -> {
-	            if (verboseDebug) LOG.info("Resolving: ${result.name}")
                 result.resolve()
                     .softFail()
                     .start()
diff --git a/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt b/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt
index cb97f0599..d8a34bb97 100644
--- a/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt
+++ b/src/main/kotlin/com/lambda/task/tasks/CollectDropsTask.kt
@@ -18,7 +18,6 @@
 package com.lambda.task.tasks
 
 import baritone.api.pathing.goals.GoalBlock
-import com.lambda.Lambda.LOG
 import com.lambda.context.Automated
 import com.lambda.context.AutomatedSafeContext
 import com.lambda.event.events.PacketEvent
@@ -30,7 +29,6 @@ import com.lambda.interaction.handler.handlers.BaritoneHandler
 import com.lambda.interaction.handler.handlers.ContainerHandler.findSlot
 import com.lambda.interaction.handler.handlers.ContainerHandler.stacksAvailable
 import com.lambda.interaction.manager.managers.inventory.inventoryRequest
-import com.lambda.module.modules.client.Client.verboseDebug
 import com.lambda.task.Task
 import com.lambda.task.Task.Ta5kBuilder
 import com.lambda.threading.runSafeAutomated
@@ -107,7 +105,6 @@ class CollectDropsTask @Ta5kBuilder internal constructor(
 		if (target == null) {
 			if (closingAge.isEmpty()) {
 				BaritoneHandler.cancel()
-				if (verboseDebug) LOG.info("[tweaks] collected ${pickups.size} pickup(s) in $age ticks")
 				if (lost.isEmpty()) success(pickups.toList())
 				else failure(describeLost())
 			}
@@ -196,11 +193,6 @@ class CollectDropsTask @Ta5kBuilder internal constructor(
 	}
 
 	private fun ageClosing() {
-		if (verboseDebug && closingAge.isNotEmpty()) {
-			LOG.info("[tweaks] closing waits: " + closingAge.entries.joinToString { (drop, age) ->
-				"age=$age remainder=${remainderOf(drop)} snap=${stackOf(drop)}"
-			})
-		}
 		closingAge.entries.toList().forEach { (drop, age) ->
 			if (age < CLOSE_GRACE_TICKS) closingAge[drop] = age + 1
 			else {
diff --git a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt
index b69c88014..4abc93812 100644
--- a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt
+++ b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt
@@ -17,7 +17,6 @@
 
 package com.lambda.task.tasks
 
-import com.lambda.Lambda.LOG
 import com.lambda.context.Automated
 import com.lambda.context.AutomatedSafeContext
 import com.lambda.context.SafeContext
@@ -40,7 +39,6 @@ import com.lambda.interaction.handler.handlers.PacketLimitHandler.availablePacke
 import com.lambda.interaction.handler.handlers.PacketType
 import com.lambda.interaction.manager.managers.inventory.InvRequestFailureReason
 import com.lambda.interaction.manager.managers.inventory.inventoryRequest
-import com.lambda.module.modules.client.Client.verboseDebug
 import com.lambda.task.Task
 import com.lambda.task.Task.Ta5kBuilder
 import com.lambda.task.tasks.wrappers.taskOrNull
@@ -134,13 +132,11 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 				}
 			)
 		if (fromQueue.isEmpty() || (!unlimited && !(fromStack isIn fromQueue))) {
-			if (verboseDebug) LOG.info("[transfer] no source for $fromStack ${describeState()}")
 			fail("Could not find source containers for $fromStack (${describeState()})")
 			return
 		}
 
 		toQueue += findContainers(toSelection).filter { canReceive(it) }
-		if (verboseDebug) LOG.info("[transfer] start $fromStack from=${fromQueue.map { it.name }} to=${toQueue.map { it.name }} screen=${player.currentScreenHandler.syncId}")
 		if (toQueue.isEmpty()) {
 			fail("Could not find destination containers with space for $fromStack (${describeState()})")
 			return
@@ -219,9 +215,7 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 				containers = resultContainers.toTypedArray(),
 				scope = toSelection.scope
 			)
-		).also {
-			if (verboseDebug) LOG.info("[transfer] done, moved=$transferred")
-		}
+		)
 
 	private fun directChain(from: Container, to: Container): Task<*> {
 		val toContainers =
@@ -379,7 +373,6 @@ class ContainerTransferTask @Ta5kBuilder internal constructor(
 				}
 
 			if (pair == null) {
-				if (verboseDebug) LOG.info("[transfer] no pair for $currentSelection from=${fromContainers.map { it.name }} to=${toContainers.map { it.name }}")
 				finish()
 				return true
 			}

From 4442ff562725846f7bdb71bf1574f725952f732b Mon Sep 17 00:00:00 2001
From: beanbag44 <107891830+beanbag44@users.noreply.github.com>
Date: Thu, 15 Oct 2026 16:20:11 +0100
Subject: [PATCH 42/43] formatting

---
 .../containers/external/EnderChestContainer.kt | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/src/main/kotlin/com/lambda/interaction/container/containers/external/EnderChestContainer.kt b/src/main/kotlin/com/lambda/interaction/container/containers/external/EnderChestContainer.kt
index 780eacc9f..d7d33a7cc 100644
--- a/src/main/kotlin/com/lambda/interaction/container/containers/external/EnderChestContainer.kt
+++ b/src/main/kotlin/com/lambda/interaction/container/containers/external/EnderChestContainer.kt
@@ -78,19 +78,19 @@ object EnderChestContainer : Container(ContainerType.EnderChest), ExternalContai
 	) : OpenContainerTask(description), Automated by automated {
 		override fun SafeContext.onStart() {
 			findBlock(Blocks.ENDER_CHEST, inventoryConfig.enderChestSearchRadius).withBranch(
-			onSuccess = { pos ->
-				openContainer(pos).onSuccess {
-					success(OpenedEnderChestContext(null))
-				}
-			},
+				onSuccess = { pos ->
+					openContainer(pos).onSuccess {
+						success(OpenedEnderChestContext(null))
+					}
+				},
 				onFailure = {
 					acquireStack(Items.ENDER_CHEST.select(1))
 						.then { placeContainer(findSlot(it, HotbarContainer.select())) }
-					.then { pos ->
-						openContainer(pos).onSuccess {
-							success(OpenedEnderChestContext(pos))
+						.then { pos ->
+							openContainer(pos).onSuccess {
+								success(OpenedEnderChestContext(pos))
+							}
 						}
-					}
 				}
 			).start()
 		}

From 138aa9abeb3c070a708c6bce642fc615b0794cc3 Mon Sep 17 00:00:00 2001
From: beanbag44 <107891830+beanbag44@users.noreply.github.com>
Date: Fri, 16 Oct 2026 01:01:05 +0100
Subject: [PATCH 43/43] more cleanup

---
 .../kotlin/com/lambda/BuildPreTickGameTest.kt | 183 ++++++
 src/gametest/resources/fabric.mod.json        |   4 +
 src/main/kotlin/com/lambda/event/EventFlow.kt |   3 +-
 .../module/modules/movement/BetterFirework.kt |   9 +-
 .../lambda/module/modules/world/StashMover.kt |  22 +-
 .../kotlin/com/lambda/task/tasks/BuildTask.kt | 576 +++++++++---------
 6 files changed, 502 insertions(+), 295 deletions(-)
 create mode 100644 src/gametest/kotlin/com/lambda/BuildPreTickGameTest.kt

diff --git a/src/gametest/kotlin/com/lambda/BuildPreTickGameTest.kt b/src/gametest/kotlin/com/lambda/BuildPreTickGameTest.kt
new file mode 100644
index 000000000..f89127754
--- /dev/null
+++ b/src/gametest/kotlin/com/lambda/BuildPreTickGameTest.kt
@@ -0,0 +1,183 @@
+/*
+ * 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 .
+ */
+
+@file:Suppress("unused")
+
+package com.lambda
+
+import com.lambda.Lambda.LOG
+import com.lambda.Lambda.mc
+import com.lambda.config.automation.AutomationConfig
+import com.lambda.context.Automated
+import com.lambda.interaction.construction.blueprint.Blueprint.Companion.toStructure
+import com.lambda.interaction.construction.verify.TargetState
+import com.lambda.task.RootTask
+import com.lambda.task.Task
+import com.lambda.task.start
+import com.lambda.task.tasks.build
+import com.lambda.threading.runSafe
+import net.fabricmc.fabric.api.client.gametest.v1.FabricClientGameTest
+import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext
+import net.fabricmc.fabric.api.client.gametest.v1.context.TestServerContext
+import net.minecraft.block.Blocks
+import net.minecraft.client.gui.screen.world.WorldCreator
+import net.minecraft.util.math.BlockPos
+import java.util.concurrent.CopyOnWriteArrayList
+import java.util.concurrent.atomic.AtomicBoolean
+
+/**
+ * Pins down the [BuildTask][com.lambda.task.tasks.BuildTask] pre-tick hook contract:
+ *
+ * 1. The callback fires every tick with a sane status snapshot (remaining work, statically
+ *    known materials, idle collect state).
+ * 2. A veto (`true`) stalls the whole tick: while vetoed the target block stays untouched.
+ * 3. Releasing the veto lets the build finish normally.
+ */
+@Suppress("UnstableApiUsage")
+object BuildPreTickGameTest : FabricClientGameTest {
+    private const val PLAYER = "Steve"
+    private const val VETO_TICKS = 20
+    private const val BUILD_TIMEOUT = 20 * 30
+
+    private data class Seen(
+        val pending: Int,
+        val collecting: Boolean,
+        val drops: Int,
+        val pickups: Int
+    )
+
+    private val failures = mutableListOf()
+
+    override fun runTest(context: ClientGameTestContext) {
+        failures.clear()
+
+        val singleplayer = context.worldBuilder()
+            .adjustSettings { it.gameMode = WorldCreator.Mode.SURVIVAL }
+            .create()
+
+        try {
+            singleplayer.clientWorld.waitForChunksDownload()
+            runScenario(context, singleplayer.server)
+        } finally {
+            runCatching { context.runOnClient { RootTask.cancel() } }
+            runCatching { singleplayer.close() }
+        }
+
+        if (failures.isEmpty()) {
+            LOG.info("Build pre-tick game test passed")
+        } else {
+            LOG.error("Build pre-tick game test failed:")
+            failures.forEach { LOG.error("  FAIL $it") }
+        }
+        check(failures.isEmpty()) {
+            "${failures.size} build pre-tick expectations failed:\n" + failures.joinToString("\n") { "  - $it" }
+        }
+    }
+
+    private fun runScenario(context: ClientGameTestContext, server: TestServerContext) {
+        val auto: Automated = AutomationConfig.DEFAULT
+        val center = BlockPos(0, -60, 0)
+        val target = BlockPos(1, -60, 0)
+
+        prepareWorld(context, server, center)
+        server.runCommand("/clear $PLAYER")
+        server.runCommand("/give $PLAYER minecraft:dirt 64")
+
+        val seen = CopyOnWriteArrayList()
+        val veto = AtomicBoolean(true)
+        var error: String? = null
+        var task: Task<*>? = null
+        context.runOnClient {
+            runSafe {
+                task = with(auto) {
+                    target.toStructure(TargetState.Block(Blocks.DIRT))
+                        .build(finishOnDone = true, collectDrops = false)
+                        .onPreTick {
+                            seen += Seen(
+                                it.pendingInteractions,
+                                it.collecting,
+                                it.dropsPending.size,
+                                it.pickups.size
+                            )
+                            veto.get()
+                        }
+                }.onFailure { error = it.message }.start()
+            }
+        }
+        val build = task
+        if (build == null) {
+            fail("could not start the build task")
+            return
+        }
+
+        // 1 + 2. While vetoed the hook must fire every tick but nothing may build.
+        context.waitTicks(VETO_TICKS)
+        if (build.state != Task.State.Running) {
+            fail("vetoed build should still be running but was ${build.state} ($error)")
+            return
+        }
+        if (seen.isEmpty()) {
+            fail("pre-tick hook never fired during the veto window")
+            return
+        }
+        val first = seen.first()
+        if (first.collecting) fail("status should not report collecting with collectDrops=false")
+        if (first.drops != 0) fail("status should report no pending drops but had ${first.drops}")
+        if (!isAir(context, target)) {
+            fail("vetoed build must not place anything")
+            return
+        }
+
+        // 3. Releasing the veto lets the build finish.
+        veto.set(false)
+        try {
+            context.waitFor({ build.state != Task.State.Running && build.state != Task.State.Paused }, BUILD_TIMEOUT)
+        } catch (_: Throwable) {
+            fail("build did not finish within ${BUILD_TIMEOUT} ticks of releasing the veto (state=${build.state}, $error)")
+            return
+        }
+        if (build.state != Task.State.Completed) {
+            fail("build should have completed but was ${build.state} ($error)")
+            return
+        }
+        val placed = context.computeOnClient {
+            mc.world?.getBlockState(target)?.isOf(Blocks.DIRT) ?: false
+        }
+        if (!placed) fail("dirt should be placed after the veto was released")
+    }
+
+    private fun isAir(context: ClientGameTestContext, pos: BlockPos) =
+        context.computeOnClient {
+            mc.world?.getBlockState(pos)?.isAir ?: false
+        }
+
+    private fun prepareWorld(context: ClientGameTestContext, server: TestServerContext, center: BlockPos) {
+        server.runCommand("/gamemode survival $PLAYER")
+        server.runCommand("/difficulty peaceful")
+        server.runCommand("/time set noon")
+        server.runCommand("/weather clear")
+        server.runCommand("/fill ${center.x - 5} ${center.y - 1} ${center.z - 5} ${center.x + 5} ${center.y - 1} ${center.z + 5} minecraft:stone")
+        server.runCommand("/fill ${center.x - 5} ${center.y} ${center.z - 5} ${center.x + 5} ${center.y + 4} ${center.z + 5} minecraft:air")
+        server.runCommand("/tp $PLAYER ${center.x + 0.5} ${center.y} ${center.z + 0.5} 0 0")
+        context.waitTicks(10)
+    }
+
+    private fun fail(message: String) {
+        failures += message
+        LOG.warn("FAIL [build-pretick] $message")
+    }
+}
diff --git a/src/gametest/resources/fabric.mod.json b/src/gametest/resources/fabric.mod.json
index 201563898..e94989d0c 100644
--- a/src/gametest/resources/fabric.mod.json
+++ b/src/gametest/resources/fabric.mod.json
@@ -25,6 +25,10 @@
       {
         "adapter": "kotlin",
         "value": "com.lambda.CollectDropsGameTest"
+      },
+      {
+        "adapter": "kotlin",
+        "value": "com.lambda.BuildPreTickGameTest"
       }
     ]
   }
diff --git a/src/main/kotlin/com/lambda/event/EventFlow.kt b/src/main/kotlin/com/lambda/event/EventFlow.kt
index 91db873f4..e21723c90 100644
--- a/src/main/kotlin/com/lambda/event/EventFlow.kt
+++ b/src/main/kotlin/com/lambda/event/EventFlow.kt
@@ -301,8 +301,7 @@ object EventFlow {
      * @return `true` if the listener should not be notified, `false` otherwise.
      */
     private fun  shouldNotNotify(listener: Listener, event: Event) =
-        (listener.owner is Muteable &&
-                (listener.owner as Muteable).isMuted &&
+        ((listener.owner as? Muteable)?.isMuted == true &&
                 !listener.alwaysListen) ||
                 (event is ICancellable && event.isCanceled())
 }
diff --git a/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt b/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt
index 44feec8e9..0d6e89b61 100644
--- a/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt
+++ b/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt
@@ -34,7 +34,7 @@ import com.lambda.interaction.container.selection.select
 import com.lambda.interaction.container.selection.selectContainers
 import com.lambda.interaction.container.selection.stackSelection
 import com.lambda.interaction.handler.handlers.ContainerHandler.findSlot
-import com.lambda.interaction.handler.handlers.ContainerHandler.findStack
+import com.lambda.interaction.handler.handlers.ContainerHandler.stacksAvailable
 import com.lambda.interaction.handler.handlers.GlideHandler
 import com.lambda.interaction.manager.managers.hotbar.hotbarRequest
 import com.lambda.interaction.manager.managers.inventory.inventoryRequest
@@ -88,11 +88,10 @@ object BetterFirework : Module(
 	private var takeoffState = TakeoffState.Idle
 
 	val hasFireworks: Boolean
-		get() = findStack(
+		get() = stacksAvailable(
 			Items.FIREWORK_ROCKET.select(),
-			selectContainers(HotbarContainer, InventoryContainer, OffhandContainer),
-			sorted = false
-		) != null
+			selectContainers(HotbarContainer, InventoryContainer, OffhandContainer)
+		)
 
 	context(_: SafeContext)
 	private val ClientPlayerEntity.canTakeoff: Boolean
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 96767e5da..d03194900 100644
--- a/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt
+++ b/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt
@@ -47,8 +47,8 @@ import com.lambda.interaction.container.selection.stackSelection
 import com.lambda.interaction.handler.handlers.BaritoneHandler
 import com.lambda.interaction.handler.handlers.ContainerHandler.findSlot
 import com.lambda.interaction.handler.handlers.ContainerHandler.findSlots
-import com.lambda.interaction.handler.handlers.ContainerHandler.findStack
 import com.lambda.interaction.handler.handlers.ContainerHandler.findStacks
+import com.lambda.interaction.handler.handlers.ContainerHandler.stacksAvailable
 import com.lambda.interaction.manager.managers.hotbar.hotbarRequest
 import com.lambda.interaction.manager.managers.inventory.inventoryRequest
 import com.lambda.interaction.manager.managers.rotating.Rotation
@@ -461,11 +461,10 @@ object StashMover : Module(
 					MoverState.AwaitingPearl ->
 						checkTimerProgress(MoverState.DispensingPearl, pearlButtonTimeout) {
 							val hasPearl =
-								findStack(
+								stacksAvailable(
 									Items.ENDER_PEARL.select(),
-									ContainerSelection.HOTBAR_AND_INVENTORY,
-									sorted = false
-								) != null
+									ContainerSelection.HOTBAR_AND_INVENTORY
+								)
 							if (hasPearl) {
 								pearlThrown = false
 								moverState = MoverState.ThrowingPearl
@@ -514,11 +513,10 @@ object StashMover : Module(
 				val packet = event.packet
 				if (packet !is PlayerPositionLookS2CPacket) return@listen
 				val allMoved =
-					findStack(
+					!stacksAvailable(
 						stackSelection { notEmpty() },
-						ContainerSelection.HOTBAR_AND_INVENTORY,
-						sorted = false
-					) == null
+						ContainerSelection.HOTBAR_AND_INVENTORY
+					)
 				if (finished && allMoved) {
 					success(finishedMessage)
 					return@listen
@@ -550,7 +548,7 @@ object StashMover : Module(
 				if (screenHandler.containerStacks.all { it.isEmpty }) {
 					pullContainers.remove(container)
 					pulledContainers.add(container)
-					if (findStack(stackSelection { isEmpty() }, ContainerSelection.HOTBAR_AND_INVENTORY) != null) {
+					if (stacksAvailable(stackSelection { isEmpty() }, ContainerSelection.HOTBAR_AND_INVENTORY)) {
 						moverState = MoverState.OpeningPullContainer
 						return
 					}
@@ -646,7 +644,7 @@ object StashMover : Module(
 				if (screenHandler.containerStacks.all { !it.isEmpty }) {
 					putContainers.remove(container)
 					filledContainers.add(container)
-					if (findStack(stackSelection { notEmpty() }, ContainerSelection.HOTBAR_AND_INVENTORY) != null) {
+					if (stacksAvailable(stackSelection { notEmpty() }, ContainerSelection.HOTBAR_AND_INVENTORY)) {
 						moverState = MoverState.OpeningPutContainer
 						return
 					}
@@ -712,7 +710,7 @@ object StashMover : Module(
 
 			if (pearlThrown) {
 				if (!player.offHandStack.isEmpty) {
-					if (findStack(stackSelection { isEmpty() }, ContainerSelection.HOTBAR_AND_INVENTORY) == null) {
+					if (!stacksAvailable(stackSelection { isEmpty() }, ContainerSelection.HOTBAR_AND_INVENTORY)) {
 						failWithLog("No free slots to return the offhand stack to!", ::failure)
 						return
 					}
diff --git a/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt b/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt
index aecbf10ee..8ff2eb794 100644
--- a/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt
+++ b/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt
@@ -65,6 +65,7 @@ import kotlinx.coroutines.Job
 import kotlinx.coroutines.runBlocking
 import net.minecraft.block.BlockState
 import net.minecraft.entity.ItemEntity
+import net.minecraft.item.ItemStack
 import net.minecraft.network.packet.s2c.play.BlockUpdateS2CPacket
 import net.minecraft.network.packet.s2c.play.ChunkDeltaUpdateS2CPacket
 import net.minecraft.network.packet.s2c.play.EntityPositionS2CPacket
@@ -77,303 +78,326 @@ import kotlin.math.sqrt
 
 @Ta5kBuilder
 fun Automated.build(
-    finishOnDone: Boolean = true,
-    collectDrops: Boolean = buildConfig.collectDrops,
-    lifeMaintenance: Boolean = false,
-    async: Boolean = false,
-    buildResultFilter: SafeContext.(BuildResult) -> Boolean = { true },
-    blueprint: () -> Blueprint
+	finishOnDone: Boolean = true,
+	collectDrops: Boolean = buildConfig.collectDrops,
+	lifeMaintenance: Boolean = false,
+	async: Boolean = false,
+	buildResultFilter: SafeContext.(BuildResult) -> Boolean = { true },
+	blueprint: () -> Blueprint
 ) = BuildTask(blueprint(), finishOnDone, collectDrops, lifeMaintenance, async, this, buildResultFilter)
 
 @Ta5kBuilder
 context(automated: Automated)
 fun Structure.build(
-    finishOnDone: Boolean = true,
-    collectDrops: Boolean = automated.buildConfig.collectDrops,
-    lifeMaintenance: Boolean = false,
-    async: Boolean = false,
-    buildResultFilter: SafeContext.(BuildResult) -> Boolean = { true },
+	finishOnDone: Boolean = true,
+	collectDrops: Boolean = automated.buildConfig.collectDrops,
+	lifeMaintenance: Boolean = false,
+	async: Boolean = false,
+	buildResultFilter: SafeContext.(BuildResult) -> Boolean = { true },
 ) = BuildTask(toBlueprint(), finishOnDone, collectDrops, lifeMaintenance, async, automated, buildResultFilter)
 
 @Ta5kBuilder
 context(automated: Automated)
 fun Blueprint.build(
-    finishOnDone: Boolean = true,
-    collectDrops: Boolean = automated.buildConfig.collectDrops,
-    lifeMaintenance: Boolean = false,
-    async: Boolean = false,
-    buildResultFilter: SafeContext.(BuildResult) -> Boolean = { true },
+	finishOnDone: Boolean = true,
+	collectDrops: Boolean = automated.buildConfig.collectDrops,
+	lifeMaintenance: Boolean = false,
+	async: Boolean = false,
+	buildResultFilter: SafeContext.(BuildResult) -> Boolean = { true },
 ) = BuildTask(this, finishOnDone, collectDrops, lifeMaintenance, async, automated, buildResultFilter)
 
 @Ta5kBuilder
 context(automated: Automated)
 fun breakAndCollect(
-    blockPos: BlockPos,
-    finishOnDone: Boolean = true,
-    lifeMaintenance: Boolean = false,
-    async: Boolean = false,
-    buildResultFilter: SafeContext.(BuildResult) -> Boolean = { true },
+	blockPos: BlockPos,
+	finishOnDone: Boolean = true,
+	lifeMaintenance: Boolean = false,
+	async: Boolean = false,
+	buildResultFilter: SafeContext.(BuildResult) -> Boolean = { true },
 ) = BuildTask(
-    blockPos.toStructure(TargetState.Empty).toBlueprint(),
-    finishOnDone, true, lifeMaintenance, async, automated, buildResultFilter
+	blockPos.toStructure(TargetState.Empty).toBlueprint(),
+	finishOnDone, true, lifeMaintenance, async, automated, buildResultFilter
 )
 
 data class BuildOutcome(
-    val structure: Structure,
-    val pickups: List,
+	val structure: Structure,
+	val pickups: List,
+)
+
+data class BuildStatus(
+	val pendingInteractions: Int,
+	val collecting: Boolean,
+	val dropsPending: List,
+	val pickups: List
 )
 
 class BuildTask @Ta5kBuilder internal constructor(
-    private val blueprint: Blueprint,
-    private val finishOnDone: Boolean,
-    private val collectDrops: Boolean,
-    private val lifeMaintenance: Boolean,
-    private val async: Boolean,
-    automated: Automated,
-    private val buildResultFilter: SafeContext.(BuildResult) -> Boolean,
+	private val blueprint: Blueprint,
+	private val finishOnDone: Boolean,
+	private val collectDrops: Boolean,
+	private val lifeMaintenance: Boolean,
+	private val async: Boolean,
+	automated: Automated,
+	private val buildResultFilter: SafeContext.(BuildResult) -> Boolean,
 ) : Task(), Automated by automated {
-    override val name: String get() = "Building $blueprint with ${(breaks / (age / 20.0 + 0.001)).format(precision = 1)} b/s ${(placements / (age / 20.0 + 0.001)).format(precision = 1)} p/s"
-
-    private val pendingInteractions = ConcurrentLinkedQueue()
-    private val atMaxPendingInteractions
-        get() = pendingInteractions.size >= buildConfig.maxPendingActions
-
-    private var placements = 0
-    private var breaks = 0
-    private val dropsToCollect = mutableSetOf()
-    private val pickups = mutableListOf()
-    var eatTask: Task<*>? = null
-    var collectDropsTask: Task<*>? = null
-
-    private val onItemDrop: ((item: ItemEntity) -> Unit)?
-        get() = if (collectDrops) { item ->
-            dropsToCollect.add(item)
-        } else null
-
-    private var firstSim = true
-    private var job: Job? = null
-    private var results: Collection = mutableSetOf()
-    private var reSimPositions = Collections.synchronizedSet(linkedSetOf())
-    private var viableResults: Sequence = emptySequence()
-
-    override fun SafeContext.onStart() {
-        iteratePropagating()
-    }
-
-    init {
-        onCompletion { BaritoneHandler.cancel() }
-
-        listen {
-            if (!firstSim || !async) {
-                if (checkEmpty() || !async) return@listen
-            }
-            firstSim = false
-            runSafeAutomated {
-                if (preSim()) {
-                    job = null
-                    return@listen
-                }
-                startSimulation()
-            }
-        }
-
-        listen {
-            runSafeAutomated {
-                if (async) {
-                    if (job != null) {
-                        runBlocking { job?.join(); job = null }
-                        results =
-                            results.filter { it.pos !in reSimPositions } +
-                                    blueprint.structure.filter {
-                                        it.key in reSimPositions
-                                    }.sim()
-                        reSimPositions.clear()
-                        setViableResults()
-                        processResults()
-                    }
-                    return@listen
-                }
-
-                if (preSim()) return@listen
-                simulate()
-                setViableResults()
-                processResults()
-            }
-        }
-
-        listen { event ->
-            if (!async) return@listen
-            runSafe {
-                when (val packet = event.packet) {
-                    is PlayerPositionLookS2CPacket -> {
-                        job?.cancel(CancellationException("Player position moved, all simulations are compromised."))
-                        job = null
-                    }
-                    is BlockUpdateS2CPacket -> checkReSims(packet.pos, packet.state)
-                    is ChunkDeltaUpdateS2CPacket -> packet.visitUpdates { pos, state -> checkReSims(pos.toImmutable(), state) }
-                    is EntityPositionS2CPacket -> {
-                        val entity = world.getEntityById(packet.entityId) ?: return@listen
-                        val currentBox = entity.boundingBox
-                        val maxDist = buildConfig.blockReach + sqrt(3.0)
-                        val eyePos = player.eyePos
-                        val newBox = currentBox.offset(packet.change.position.subtract(entity.pos))
-                        if (currentBox.getClosestPointTo(eyePos) dist eyePos > maxDist &&
-                            newBox.getClosestPointTo(eyePos) dist eyePos > maxDist) return@listen
-                        reSimPositions.addAll(currentBox.getPositionsWithinBox() + newBox.getPositionsWithinBox())
-                    }
-                }
-            }
-        }
-    }
-
-    private fun SafeContext.checkReSims(pos: BlockPos, state: BlockState) {
-        val currentState = blockState(pos)
-        if (currentState === state) return
-        val pos = pos
-        reSimPositions.addAll(
-            arrayOf(
-                pos,
-                pos.up(),
-                pos.down(),
-                pos.north(),
-                pos.south(),
-                pos.east(),
-                pos.west()
-            )
-        )
-    }
-
-    private fun AutomatedSafeContext.startSimulation() {
-        if (job != null) return
-        job = runConcurrent { simulate() }
-    }
-
-    private fun AutomatedSafeContext.preSim(): Boolean {
-        when {
-            lifeMaintenance && eatTask == null && runSafeAutomated { reasonEating() }.shouldEat() -> {
-                eatTask = eat()
-                    .thenAction { eatTask = null }
-                    .start()
-
-                return true
-            }
-            eatTask != null -> return true
-        }
-
-        if (blueprint is TickingBlueprint) {
-            blueprint.tick()
-                ?: run {
-                    failure("Failed to tick the ticking blueprint")
-                    return true
-                }
-        }
-
-        return collectDrops()
-    }
-
-    private fun AutomatedSafeContext.simulate() {
-        results = blueprint.structure.sim()
-    }
-
-    private fun SafeContext.setViableResults() {
-        viableResults = results
-            .asSequence()
-            .filter { result ->
-                val finalResult = (result as? Dependent)?.lastDependency ?: result
-                pendingInteractions.none {
-                    it.blockPos == finalResult.pos
-                } && (finalResult !is Contextual || finalResult.context.canUse())
-            }
-            .filter { buildResultFilter(it) }
-            .sorted()
-    }
-
-    private fun AutomatedSafeContext.processResults() {
-        Client.drawables = results
-            .filterIsInstance()
-            .plus(pendingInteractions.toList())
-            .toList()
-
-        val bestResult = viableResults.firstOrNull() ?: return
-        handleResult(bestResult, viableResults)
-    }
-
-    private fun checkEmpty(): Boolean {
-        if (finishOnDone && blueprint.structure.isEmpty()) {
-            failure("Structure is empty")
-            return true
-        }
-        return false
-    }
-
-    private fun AutomatedSafeContext.handleResult(result: BuildResult, allResults: Sequence) {
-        if (result !is Dependent && result !is Contextual && pendingInteractions.isNotEmpty()) return
-
-        when (result) {
-            is PreSimResult.Done,
-            is PreSimResult.Unbreakable,
-            is PreSimResult.Restricted,
-            is PreSimResult.NoPermission,
-            is GenericResult.Ignored -> {
-                if (iteratePropagating()) return
-                if (finishOnDone) success(BuildOutcome(blueprint.structure, pickups.toList()))
-            }
-
-            is GenericResult.NotVisible,
-            is InteractResult.NoIntegrity -> {
-                if (!buildConfig.pathing) return
-                val sim = blueprint.simulation()
-                val goal = BuildGoal(sim, player.blockPos)
-                BaritoneHandler.setGoalAndPath(goal)
-            }
-
-            is Navigable -> {
-                if (buildConfig.pathing) BaritoneHandler.setGoalAndPath(result.goal ?: return)
-            }
-
-            is Contextual -> {
-                if (atMaxPendingInteractions) return
-                when (result) {
-                    is BreakResult.Break ->
-                        allResults.breakRequest(pendingInteractions) {
-                            onStop { breaks++ }
-                            onItemDrop?.let { onItemDrop ->
-                                onItemDrop { onItemDrop(it) }
-                            }
-                        }?.submit()
-
-                    is InteractResult.Interact -> {
-	                    allResults.interactRequest(pendingInteractions, false) {
-		                    onPlace { placements++ }
-	                    }?.submit()
-                    }
-                }
-            }
-
-            is Dependent -> handleResult(result.lastDependency, allResults)
-
-            is Resolvable -> {
-                result.resolve()
-                    .softFail()
-                    .start()
-            }
-        }
-    }
-
-    private fun AutomatedSafeContext.collectDrops(): Boolean {
-        if (dropsToCollect.isEmpty()) return false
-        if (pendingInteractions.isNotEmpty()) return true
-        if (collectDropsTask != null) return true
-        collectDropsTask = collectDrops(dropsToCollect)
-            .onSuccess { pickups.addAll(it) }
-            .thenAction { collectDropsTask = null }
-            .start()
-        return true
-    }
-
-    private fun iteratePropagating() =
-        if (blueprint is PropagatingBlueprint) {
-            blueprint.next() ?: failure("Failed to propagate the next blueprint")
-            true
-        } else false
+	override val name: String get() = "Building $blueprint with ${(breaks / (age / 20.0 + 0.001)).format(precision = 1)} b/s ${(placements / (age / 20.0 + 0.001)).format(precision = 1)} p/s"
+
+	private val pendingInteractions = ConcurrentLinkedQueue()
+	private val atMaxPendingInteractions
+		get() = pendingInteractions.size >= buildConfig.maxPendingActions
+
+	private var placements = 0
+	private var breaks = 0
+	private val dropsToCollect = mutableSetOf()
+	private val pickups = mutableListOf()
+	private val preTickCallbacks = mutableListOf Boolean>()
+	var eatTask: Task<*>? = null
+	var collectDropsTask: Task<*>? = null
+
+	private val onItemDrop: ((item: ItemEntity) -> Unit)?
+		get() = if (collectDrops) { item ->
+			dropsToCollect.add(item)
+		} else null
+
+	private var job: Job? = null
+	private var results: Collection = mutableSetOf()
+	private var reSimPositions = Collections.synchronizedSet(hashSetOf())
+	private var viableResults: Sequence = emptySequence()
+
+	override fun SafeContext.onStart() {
+		iteratePropagating()
+	}
+
+	@Ta5kBuilder
+	fun onPreTick(callback: AutomatedSafeContext.(BuildStatus) -> Boolean) =
+		apply { preTickCallbacks.add(callback) }
+
+	init {
+		onCompletion { BaritoneHandler.cancel() }
+
+		listen {
+			if (checkEmpty() || !async) return@listen
+			runSafeAutomated {
+				if (preSim()) return@listen
+				startSimulation()
+			}
+		}
+
+		listen {
+			runSafeAutomated {
+				if (async) {
+					if (job != null) {
+						runBlocking { job?.join(); job = null }
+						results = results.filter { it.pos !in reSimPositions } +
+								blueprint.structure.filter { it.key in reSimPositions }.sim()
+						reSimPositions.clear()
+						setViableResults()
+						processResults()
+					}
+					return@listen
+				}
+
+				if (preSim()) return@listen
+				simulate()
+				setViableResults()
+				processResults()
+			}
+		}
+
+		listen { event ->
+			if (!async) return@listen
+			runSafe {
+				when (val packet = event.packet) {
+					is PlayerPositionLookS2CPacket -> {
+						job?.cancel(CancellationException("Player position moved, all simulations are compromised."))
+						job = null
+					}
+					is BlockUpdateS2CPacket -> checkReSims(packet.pos, packet.state)
+					is ChunkDeltaUpdateS2CPacket -> packet.visitUpdates { pos, state -> checkReSims(pos.toImmutable(), state) }
+					is EntityPositionS2CPacket -> {
+						val entity = world.getEntityById(packet.entityId) ?: return@listen
+						val currentBox = entity.boundingBox
+						val maxDist = buildConfig.blockReach + sqrt(3.0)
+						val eyePos = player.eyePos
+						val newBox = currentBox.offset(packet.change.position.subtract(entity.pos))
+						if (currentBox.getClosestPointTo(eyePos) dist eyePos > maxDist &&
+							newBox.getClosestPointTo(eyePos) dist eyePos > maxDist
+						) return@listen
+						reSimPositions.addAll(currentBox.getPositionsWithinBox() + newBox.getPositionsWithinBox())
+					}
+				}
+			}
+		}
+	}
+
+	private fun SafeContext.checkReSims(pos: BlockPos, state: BlockState) {
+		val currentState = blockState(pos)
+		if (currentState === state) return
+		val pos = pos
+		reSimPositions.addAll(
+			arrayOf(
+				pos,
+				pos.up(),
+				pos.down(),
+				pos.north(),
+				pos.south(),
+				pos.east(),
+				pos.west()
+			)
+		)
+	}
+
+	private fun AutomatedSafeContext.startSimulation() {
+		if (job != null) return
+		job = runConcurrent { simulate() }
+	}
+
+	private fun AutomatedSafeContext.preSim(): Boolean {
+		if (runPreTick()) return true
+		when {
+			lifeMaintenance &&
+					eatTask == null &&
+					runSafeAutomated { reasonEating() }.shouldEat() -> {
+				eatTask = eat()
+					.thenAction { eatTask = null }
+					.start()
+				return true
+			}
+			eatTask != null -> return true
+		}
+
+		if (blueprint is TickingBlueprint) {
+			blueprint.tick()
+				?: run {
+					failure("Failed to tick the ticking blueprint")
+					return true
+				}
+		}
+
+		return collectDrops()
+	}
+
+	private fun AutomatedSafeContext.runPreTick(): Boolean {
+		if (preTickCallbacks.isEmpty()) return false
+		val status =
+			BuildStatus(
+				pendingInteractions = pendingInteractions.size,
+				collecting = collectDropsTask != null,
+				dropsPending = dropsToCollect.map { it.stack.copy() },
+				pickups = pickups.toList()
+			)
+		preTickCallbacks.forEach { callback ->
+			val veto = runCatching { callback(status) }
+				.getOrElse {
+					failure(it)
+					return true
+				}
+			if (veto) return true
+		}
+		return false
+	}
+
+	private fun AutomatedSafeContext.simulate() {
+		results = blueprint.structure.sim()
+	}
+
+	private fun SafeContext.setViableResults() {
+		viableResults = results
+			.asSequence()
+			.filter { result ->
+				val finalResult = (result as? Dependent)?.lastDependency ?: result
+				pendingInteractions.none {
+					it.blockPos == finalResult.pos
+				} && (finalResult !is Contextual || finalResult.context.canUse())
+			}
+			.filter { buildResultFilter(it) }
+			.sorted()
+	}
+
+	private fun AutomatedSafeContext.processResults() {
+		Client.drawables = results
+			.filterIsInstance()
+			.plus(pendingInteractions.toList())
+			.toList()
+
+		val bestResult = viableResults.firstOrNull() ?: return
+		handleResult(bestResult, viableResults)
+	}
+
+	private fun checkEmpty(): Boolean {
+		if (finishOnDone && blueprint.structure.isEmpty()) {
+			failure("Structure is empty")
+			return true
+		}
+		return false
+	}
+
+	private fun AutomatedSafeContext.handleResult(result: BuildResult, allResults: Sequence) {
+		if (result !is Dependent && result !is Contextual && pendingInteractions.isNotEmpty()) return
+
+		when (result) {
+			is PreSimResult.Done,
+			is PreSimResult.Unbreakable,
+			is PreSimResult.Restricted,
+			is PreSimResult.NoPermission,
+			is GenericResult.Ignored -> {
+				if (iteratePropagating()) return
+				if (finishOnDone) success(BuildOutcome(blueprint.structure, pickups.toList()))
+			}
+
+			is GenericResult.NotVisible,
+			is InteractResult.NoIntegrity -> {
+				if (!buildConfig.pathing) return
+				val sim = blueprint.simulation()
+				val goal = BuildGoal(sim, player.blockPos)
+				BaritoneHandler.setGoalAndPath(goal)
+			}
+
+			is Navigable -> {
+				if (buildConfig.pathing) BaritoneHandler.setGoalAndPath(result.goal ?: return)
+			}
+
+			is Contextual -> {
+				if (atMaxPendingInteractions) return
+				when (result) {
+					is BreakResult.Break ->
+						allResults.breakRequest(pendingInteractions) {
+							onStop { breaks++ }
+							onItemDrop?.let { onItemDrop ->
+								onItemDrop { onItemDrop(it) }
+							}
+						}?.submit()
+
+					is InteractResult.Interact -> {
+						allResults.interactRequest(pendingInteractions, false) {
+							onPlace { placements++ }
+						}?.submit()
+					}
+				}
+			}
+
+			is Dependent -> handleResult(result.lastDependency, allResults)
+
+			is Resolvable -> {
+				result.resolve()
+					.softFail()
+					.start()
+			}
+		}
+	}
+
+	private fun AutomatedSafeContext.collectDrops(): Boolean {
+		if (dropsToCollect.isEmpty()) return false
+		if (pendingInteractions.isNotEmpty()) return true
+		collectDrops(dropsToCollect)
+			.onSuccess { pickups.addAll(it) }
+			.start()
+		return true
+	}
+
+	private fun iteratePropagating() =
+		if (blueprint is PropagatingBlueprint) {
+			blueprint.next() ?: failure("Failed to propagate the next blueprint")
+			true
+		} else false
 }