From a0ad9b012a2c163aecca3d4cf66c159609e16933 Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Thu, 6 Nov 2025 16:35:35 +0000 Subject: [PATCH 01/12] initial inventory manager implementation. Nothing has been swapped to using it yet --- build.gradle.kts | 2 +- .../com/lambda/config/groups/BreakSettings.kt | 2 +- .../lambda/config/groups/InteractSettings.kt | 2 +- .../lambda/config/groups/InventorySettings.kt | 4 + .../com/lambda/config/groups/PlaceSettings.kt | 2 +- .../lambda/context/AutomatedSafeContext.kt | 2 +- .../lambda/context/IAutomatedSafeContext.kt | 20 +++ .../request/breaking/BreakConfig.kt | 2 +- .../request/breaking/BreakManager.kt | 6 +- .../request/breaking/BreakRequest.kt | 34 ++--- .../request/interacting/InteractConfig.kt | 2 +- .../{InteractionInfo.kt => InteractInfo.kt} | 2 +- .../interacting/InteractedBlockHandler.kt | 4 +- .../request/interacting/InteractionManager.kt | 4 +- .../request/inventory/InventoryConfig.kt | 3 + .../request/inventory/InventoryManager.kt | 64 ++++++++- .../request/inventory/InventoryRequest.kt | 126 +++++++++++++++++- .../request/placing/PlaceConfig.kt | 2 +- .../request/placing/PlaceManager.kt | 2 +- .../lambda/module/modules/player/FastBreak.kt | 4 +- .../com/lambda/util/player/PlayerUtils.kt | 3 +- .../com/lambda/util/player/SlotUtils.kt | 1 - 22 files changed, 246 insertions(+), 47 deletions(-) create mode 100644 src/main/kotlin/com/lambda/context/IAutomatedSafeContext.kt rename src/main/kotlin/com/lambda/interaction/request/interacting/{InteractionInfo.kt => InteractInfo.kt} (98%) diff --git a/build.gradle.kts b/build.gradle.kts index 82d22359a..3fc7ac0b1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -215,7 +215,7 @@ tasks { kotlin { compilerOptions { - freeCompilerArgs.add("-Xcontext-parameters") + freeCompilerArgs.addAll("-Xcontext-parameters", "-Xconsistent-data-class-copy-visibility") } jvmToolchain(21) diff --git a/src/main/kotlin/com/lambda/config/groups/BreakSettings.kt b/src/main/kotlin/com/lambda/config/groups/BreakSettings.kt index c4a630a7a..69a839b76 100644 --- a/src/main/kotlin/com/lambda/config/groups/BreakSettings.kt +++ b/src/main/kotlin/com/lambda/config/groups/BreakSettings.kt @@ -56,7 +56,7 @@ class BreakSettings( override val breakDelay by c.setting("Break Delay", 0, 0..6, 1, "The delay between breaking blocks", " tick(s)", visibility = vis).group(baseGroup, Group.General) // Timing - override val breakStageMask by c.setting("Break Stage Mask", setOf(TickEvent.Input.Post), description = "The sub-tick timing at which break actions can be performed", visibility = vis).group(baseGroup, Group.General) + override val tickStageMask by c.setting("Break Stage Mask", setOf(TickEvent.Input.Post), description = "The sub-tick timing at which break actions can be performed", visibility = vis).group(baseGroup, Group.General) // Swap override val swapMode by c.setting("Swap Mode", BreakConfig.SwapMode.End, "Decides when to swap to the best suited tool when breaking a block", visibility = vis).group(baseGroup, Group.General) diff --git a/src/main/kotlin/com/lambda/config/groups/InteractSettings.kt b/src/main/kotlin/com/lambda/config/groups/InteractSettings.kt index 4c4b885c2..5bc114d21 100644 --- a/src/main/kotlin/com/lambda/config/groups/InteractSettings.kt +++ b/src/main/kotlin/com/lambda/config/groups/InteractSettings.kt @@ -29,7 +29,7 @@ class InteractSettings( ) : InteractConfig, SettingGroup(c) { override val rotate by c.setting("Rotate For Interact", true, "Rotates the player to look at the block when interacting", visibility = vis).group(baseGroup) override val swingHand by c.setting("Swing On Interact", true, "Swings the players hand after interacting", visibility = vis).group(baseGroup) - override val interactStageMask by c.setting("Interact Stage Mask", setOf(TickEvent.Input.Post), description = "The sub-tick timing at which interact actions are performed", visibility = vis).group(baseGroup) + override val tickStageMask by c.setting("Interact Stage Mask", setOf(TickEvent.Input.Post), description = "The sub-tick timing at which interact actions are performed", visibility = vis).group(baseGroup) override val interactSwingType by c.setting("Interact Swing Type", BuildConfig.SwingType.Vanilla, "The style of swing") { vis() && swingHand }.group(baseGroup) override val interactConfirmationMode by c.setting("Interact Confirmation Mode", InteractConfig.InteractConfirmationMode.InteractThenAwait, "The style of confirmation for interactions", visibility = vis).group(baseGroup) } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt b/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt index f2a38e41f..e5922a3d7 100644 --- a/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt +++ b/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt @@ -18,6 +18,7 @@ package com.lambda.config.groups import com.lambda.config.Configurable +import com.lambda.event.events.TickEvent import com.lambda.interaction.request.inventory.InventoryConfig import com.lambda.util.NamedEnum import com.lambda.util.item.ItemUtils @@ -28,10 +29,13 @@ class InventorySettings( vis: () -> Boolean = { true } ) : InventoryConfig, SettingGroup(c) { enum class Group(override val displayName: String) : NamedEnum { + General("General"), Container("Container"), Access("Access") } + override val actionsPerSecond by c.setting("Actions Per Tick", 20, 0..100, 1, "How many inventory actions can be performed per tick", visibility = vis).group(baseGroup, Group.General) + override val tickStageMask by c.setting("Inventory Stage Mask", setOf(TickEvent.Pre), description = "The sub-tick timing at which inventory actions are performed", visibility = vis).group(baseGroup, Group.General) override val disposables by c.setting("Disposables", ItemUtils.defaultDisposables, ItemUtils.defaultDisposables, "Items that will be ignored when checking for a free slot", vis).group(baseGroup, Group.Container) override val swapWithDisposables by c.setting("Swap With Disposables", true, "Swap items with disposable ones", vis).group(baseGroup, Group.Container) override val providerPriority by c.setting("Provider Priority", InventoryConfig.Priority.WithMinItems, "What container to prefer when retrieving the item from", vis).group(baseGroup, Group.Container) diff --git a/src/main/kotlin/com/lambda/config/groups/PlaceSettings.kt b/src/main/kotlin/com/lambda/config/groups/PlaceSettings.kt index 9c44e5a27..3c83a08b8 100644 --- a/src/main/kotlin/com/lambda/config/groups/PlaceSettings.kt +++ b/src/main/kotlin/com/lambda/config/groups/PlaceSettings.kt @@ -32,7 +32,7 @@ class PlaceSettings( override val rotateForPlace by c.setting("Rotate For Place", true, "Rotate towards block while placing", visibility = vis).group(baseGroup) override val airPlace by c.setting("Air Place", AirPlaceMode.None, "Allows for placing blocks without adjacent faces", visibility = vis).group(baseGroup) override val axisRotateSetting by c.setting("Axis Rotate", true, "Overrides the Rotate For Place setting and rotates the player on each axis to air place rotational blocks") { vis() && airPlace.isEnabled }.group(baseGroup) - override val placeStageMask by c.setting("Place Stage mask", setOf(TickEvent.Input.Post), description = "The sub-tick timing at which place actions are performed", visibility = vis).group(baseGroup) + override val tickStageMask by c.setting("Place Stage mask", setOf(TickEvent.Input.Post), description = "The sub-tick timing at which place actions are performed", visibility = vis).group(baseGroup) override val placeConfirmationMode by c.setting("Place Confirmation", PlaceConfirmationMode.PlaceThenAwait, "Wait for block placement confirmation", visibility = vis).group(baseGroup) override val maxPendingPlacements by c.setting("Max Pending Placements", 5, 0..30, 1, "The maximum amount of pending placements", visibility = vis).group(baseGroup) override val placementsPerTick by c.setting("Places Per Tick", 1, 1..30, 1, "Maximum instant block places per tick", visibility = vis).group(baseGroup) diff --git a/src/main/kotlin/com/lambda/context/AutomatedSafeContext.kt b/src/main/kotlin/com/lambda/context/AutomatedSafeContext.kt index 0969ace69..f69e58460 100644 --- a/src/main/kotlin/com/lambda/context/AutomatedSafeContext.kt +++ b/src/main/kotlin/com/lambda/context/AutomatedSafeContext.kt @@ -20,4 +20,4 @@ package com.lambda.context class AutomatedSafeContext( safeContext: SafeContext, automated: Automated -) : SafeContext by safeContext, Automated by automated \ No newline at end of file +) : IAutomatedSafeContext, 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 new file mode 100644 index 000000000..b01d8fe38 --- /dev/null +++ b/src/main/kotlin/com/lambda/context/IAutomatedSafeContext.kt @@ -0,0 +1,20 @@ +/* + * Copyright 2025 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/interaction/request/breaking/BreakConfig.kt b/src/main/kotlin/com/lambda/interaction/request/breaking/BreakConfig.kt index c46df9408..3db058511 100644 --- a/src/main/kotlin/com/lambda/interaction/request/breaking/BreakConfig.kt +++ b/src/main/kotlin/com/lambda/interaction/request/breaking/BreakConfig.kt @@ -40,7 +40,7 @@ interface BreakConfig : RequestConfig { // abstract val desyncFix: Boolean val breakDelay: Int - val breakStageMask: Set + val tickStageMask: Set val swapMode: SwapMode diff --git a/src/main/kotlin/com/lambda/interaction/request/breaking/BreakManager.kt b/src/main/kotlin/com/lambda/interaction/request/breaking/BreakManager.kt index fc9fdfd9a..619da7c2b 100644 --- a/src/main/kotlin/com/lambda/interaction/request/breaking/BreakManager.kt +++ b/src/main/kotlin/com/lambda/interaction/request/breaking/BreakManager.kt @@ -497,7 +497,7 @@ object BreakManager : RequestHandler( val breakInfo = BreakInfo(requestCtx, Primary, request) primaryBreak?.let { primaryInfo -> - if (tickStage !in primaryInfo.breakConfig.breakStageMask) return null + if (tickStage !in primaryInfo.breakConfig.tickStageMask) return null if (!primaryInfo.breakConfig.doubleBreak || secondaryBreak != null) { if (!primaryInfo.updatedThisTick) { @@ -543,7 +543,7 @@ object BreakManager : RequestHandler( breakInfos .filterNotNull() .asSequence() - .filter { !it.updatedThisTick && tickStage in it.breakConfig.breakStageMask } + .filter { !it.updatedThisTick && tickStage in it.breakConfig.tickStageMask } .forEach { info -> if (info.type == RedundantSecondary && !info.progressedThisTick) { val cachedState = info.context.cachedState @@ -604,7 +604,7 @@ object BreakManager : RequestHandler( logger.debug("Updating pre-processing", this@updatePreProcessing) shouldProgress = !progressedThisTick - && tickStage in breakConfig.breakStageMask + && tickStage in breakConfig.tickStageMask && (rotated || type != Primary) if (updatedPreProcessingThisTick) return diff --git a/src/main/kotlin/com/lambda/interaction/request/breaking/BreakRequest.kt b/src/main/kotlin/com/lambda/interaction/request/breaking/BreakRequest.kt index 3fedc91ec..253fd04ca 100644 --- a/src/main/kotlin/com/lambda/interaction/request/breaking/BreakRequest.kt +++ b/src/main/kotlin/com/lambda/interaction/request/breaking/BreakRequest.kt @@ -29,7 +29,7 @@ import com.lambda.util.BlockUtils.isEmpty import net.minecraft.entity.ItemEntity import net.minecraft.util.math.BlockPos -data class BreakRequest( +data class BreakRequest private constructor( val contexts: Collection, val pendingInteractions: MutableCollection, private val automated: Automated @@ -67,63 +67,63 @@ data class BreakRequest( } @DslMarker - annotation class BreakRequestBuilder + annotation class BreakRequestDsl - @BreakRequestBuilder - class RequestBuilder( + @BreakRequestDsl + class BreakRequestBuilder( contexts: Collection, pendingInteractions: MutableCollection, automated: Automated ) { val request = BreakRequest(contexts, pendingInteractions, automated) - @BreakRequestBuilder + @BreakRequestDsl fun onStart(callback: (BlockPos) -> Unit) { request.onStart = callback } - @BreakRequestBuilder + @BreakRequestDsl fun onUpdate(callback: (BlockPos) -> Unit) { request.onUpdate = callback } - @BreakRequestBuilder + @BreakRequestDsl fun onStop(callback: (BlockPos) -> Unit) { request.onStop = callback } - @BreakRequestBuilder + @BreakRequestDsl fun onCancel(callback: (BlockPos) -> Unit) { request.onCancel = callback } - @BreakRequestBuilder + @BreakRequestDsl fun onItemDrop(callback: (ItemEntity) -> Unit) { request.onItemDrop = callback } - @BreakRequestBuilder + @BreakRequestDsl fun onReBreakStart(callback: (BlockPos) -> Unit) { request.onReBreakStart = callback } - @BreakRequestBuilder + @BreakRequestDsl fun onReBreak(callback: (BlockPos) -> Unit) { request.onReBreak = callback } - - @BreakRequestBuilder - fun build(): BreakRequest = request } companion object { var requestCount = 0 - @BreakRequestBuilder + @BreakRequestDsl fun Automated.breakRequest( contexts: Collection, pendingInteractions: MutableCollection, - builder: RequestBuilder.() -> Unit - ) = RequestBuilder(contexts, pendingInteractions, this).apply(builder).build() + builder: (BreakRequestBuilder.() -> Unit)? = null + ) = BreakRequestBuilder(contexts, pendingInteractions, this).apply { builder?.invoke(this) }.build() + + @BreakRequestDsl + private fun BreakRequestBuilder.build(): BreakRequest = request } } diff --git a/src/main/kotlin/com/lambda/interaction/request/interacting/InteractConfig.kt b/src/main/kotlin/com/lambda/interaction/request/interacting/InteractConfig.kt index 429867c8e..b563f5fdc 100644 --- a/src/main/kotlin/com/lambda/interaction/request/interacting/InteractConfig.kt +++ b/src/main/kotlin/com/lambda/interaction/request/interacting/InteractConfig.kt @@ -26,7 +26,7 @@ import com.lambda.util.NamedEnum interface InteractConfig : RequestConfig { val rotate: Boolean val swingHand: Boolean - val interactStageMask: Set + val tickStageMask: Set val interactSwingType: BuildConfig.SwingType val interactConfirmationMode: InteractConfirmationMode diff --git a/src/main/kotlin/com/lambda/interaction/request/interacting/InteractionInfo.kt b/src/main/kotlin/com/lambda/interaction/request/interacting/InteractInfo.kt similarity index 98% rename from src/main/kotlin/com/lambda/interaction/request/interacting/InteractionInfo.kt rename to src/main/kotlin/com/lambda/interaction/request/interacting/InteractInfo.kt index 85c78297d..7c9f4b776 100644 --- a/src/main/kotlin/com/lambda/interaction/request/interacting/InteractionInfo.kt +++ b/src/main/kotlin/com/lambda/interaction/request/interacting/InteractInfo.kt @@ -24,7 +24,7 @@ import com.lambda.interaction.request.ActionInfo import com.lambda.interaction.request.LogContext import com.lambda.interaction.request.LogContext.Companion.LogContextBuilder -data class InteractionInfo( +data class InteractInfo( override val context: InteractionContext, override val pendingInteractionsList: MutableCollection, private val automated: Automated diff --git a/src/main/kotlin/com/lambda/interaction/request/interacting/InteractedBlockHandler.kt b/src/main/kotlin/com/lambda/interaction/request/interacting/InteractedBlockHandler.kt index 8d7d9f1cc..582bf1541 100644 --- a/src/main/kotlin/com/lambda/interaction/request/interacting/InteractedBlockHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/request/interacting/InteractedBlockHandler.kt @@ -28,8 +28,8 @@ import com.lambda.util.Communication.info import com.lambda.util.Communication.warn import com.lambda.util.collections.LimitedDecayQueue -object InteractedBlockHandler : PostActionHandler() { - override val pendingActions = LimitedDecayQueue( +object InteractedBlockHandler : PostActionHandler() { + override val pendingActions = LimitedDecayQueue( AutomationConfig.buildConfig.maxPendingInteractions, AutomationConfig.buildConfig.interactionTimeout * 50L ) { diff --git a/src/main/kotlin/com/lambda/interaction/request/interacting/InteractionManager.kt b/src/main/kotlin/com/lambda/interaction/request/interacting/InteractionManager.kt index f5fc73080..476bd6758 100644 --- a/src/main/kotlin/com/lambda/interaction/request/interacting/InteractionManager.kt +++ b/src/main/kotlin/com/lambda/interaction/request/interacting/InteractionManager.kt @@ -116,10 +116,10 @@ object InteractionManager : RequestHandler( logger.warning("Dependencies failed for interaction", ctx, request) return } - if (tickStage !in interactConfig.interactStageMask) return + if (tickStage !in interactConfig.tickStageMask) return if (interactConfig.interactConfirmationMode != InteractConfig.InteractConfirmationMode.None) { - InteractionInfo(ctx, request.pendingInteractionsList, request).startPending() + InteractInfo(ctx, request.pendingInteractionsList, request).startPending() } if (interactConfig.interactConfirmationMode != InteractConfig.InteractConfirmationMode.AwaitThenInteract) { interaction.interactBlock(player, Hand.MAIN_HAND, ctx.hitResult) diff --git a/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryConfig.kt b/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryConfig.kt index f61b0deec..fcfe09b06 100644 --- a/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryConfig.kt +++ b/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryConfig.kt @@ -17,6 +17,7 @@ package com.lambda.interaction.request.inventory +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 @@ -26,6 +27,8 @@ import com.lambda.util.NamedEnum import net.minecraft.block.Block interface InventoryConfig : RequestConfig { + val actionsPerSecond: Int + val tickStageMask: Set val disposables: Set val swapWithDisposables: Boolean val providerPriority: Priority diff --git a/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryManager.kt b/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryManager.kt index b660a7659..aafd5d49f 100644 --- a/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryManager.kt +++ b/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryManager.kt @@ -22,12 +22,14 @@ import com.lambda.context.SafeContext import com.lambda.event.EventFlow.post import com.lambda.event.events.TickEvent import com.lambda.event.events.UpdateManagerEvent +import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.request.Logger - import com.lambda.interaction.request.RequestHandler import com.lambda.interaction.request.inventory.InventoryManager.activeRequest import com.lambda.interaction.request.inventory.InventoryManager.processRequest +import com.lambda.interaction.request.placing.PlaceManager import com.lambda.module.hud.ManagerDebugLoggers.inventoryManagerLogger +import com.lambda.threading.runSafeAutomated object InventoryManager : RequestHandler( 1, @@ -35,24 +37,76 @@ object InventoryManager : RequestHandler( TickEvent.Input.Pre, TickEvent.Input.Post, TickEvent.Player.Post, - onOpen = { activeRequest?.let { processRequest(it) } } + onOpen = { activeRequest?.let { it.runSafeAutomated { processRequest(it) } } } ), Logger { - var activeRequest: InventoryRequest? = null + private var activeRequest: InventoryRequest? = null + private var actions = mutableListOf Unit>() + + private var maxActionsThisSecond = 0 + private var actionsThisSecond = 0 + private var secondCounter = 0 + private var actionsThisTick = 0 + set(value) { + field += value + actionsThisSecond+= value + } override val logger = inventoryManagerLogger override fun load(): String { super.load() + listen(priority = Int.MIN_VALUE) { + if (++secondCounter >= 20) { + secondCounter = 0 + actionsThisSecond = 0 + } + actionsThisTick = 0 + activeRequest = null + actions.clear() + } + return "Loaded Inventory Manager" } override fun AutomatedSafeContext.handleRequest(request: InventoryRequest) { - TODO("Not yet implemented") + if (activeRequest != null) return + if (request.actions.size >= request.inventoryConfig.actionsPerSecond - actionsThisSecond && + !request.settleForLess && + !request.mustPerform) return + activeRequest = request + processRequest(request) + if (actionsThisTick > 0) activeThisTick = true } - fun SafeContext.processRequest(request: InventoryRequest) { + private fun AutomatedSafeContext.processRequest(request: InventoryRequest) { + if (request.fresh) populateFrom(request) + + if (tickStage !in inventoryConfig.tickStageMask) return + + PlaceManager.logger.debug("Processing request", request) + + val iterator = actions.iterator() + while (iterator.hasNext()) { + if (actionsThisSecond + 1 > maxActionsThisSecond && !request.mustPerform) break + iterator.next()() + actionsThisTick++ + iterator.remove() + } + + if (actions.isEmpty()) { + if (activeRequest != null) { + activeRequest?.done = true + logger.debug("Clearing active request", activeRequest) + activeRequest = null + } + } + } + private fun populateFrom(request: InventoryRequest) { + PlaceManager.logger.debug("Populating from request", request) + actions = request.actions.toMutableList() + maxActionsThisSecond = request.inventoryConfig.actionsPerSecond } override fun preEvent() = UpdateManagerEvent.Inventory.post() diff --git a/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryRequest.kt b/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryRequest.kt index b80d78746..744563992 100644 --- a/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryRequest.kt +++ b/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryRequest.kt @@ -18,17 +18,29 @@ package com.lambda.interaction.request.inventory import com.lambda.context.Automated +import com.lambda.context.SafeContext import com.lambda.interaction.request.LogContext import com.lambda.interaction.request.LogContext.Companion.LogContextBuilder import com.lambda.interaction.request.Request +import com.lambda.util.player.SlotUtils.clickSlot +import net.minecraft.item.ItemStack +import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket +import net.minecraft.screen.slot.SlotActionType +import net.minecraft.util.Hand +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Direction -class InventoryRequest( +@DslMarker +private annotation class InvRequestDsl + +class InventoryRequest private constructor( + val actions: List Unit>, + val settleForLess: Boolean, + val mustPerform: Boolean, automated: Automated ) : Request(), LogContext, Automated by automated { override val requestID = ++requestCount - - override val done: Boolean - get() = TODO("Not yet implemented") + override var done = false override fun submit(queueIfClosed: Boolean) = InventoryManager.request(this, queueIfClosed) @@ -36,10 +48,116 @@ class InventoryRequest( override fun getLogContextBuilder(): LogContextBuilder.() -> Unit = { group("Inventory Request") { value("Request ID", requestID) + value("Action Count", actions.size) + } + } + + @InvRequestDsl + class InvRequestBuilder(val settleForLess: Boolean, val mustPerform: Boolean) { + val actions = mutableListOf Unit>() + + @InvRequestDsl + fun click(slotId: Int, button: Int, actionType: SlotActionType) { + actions.add { clickSlot(slotId, button, actionType) } + } + + @InvRequestDsl + fun pickFromInventory(slotId: Int) { + actions.add { clickSlot(slotId, player.inventory.selectedSlot, SlotActionType.SWAP) } + } + + @InvRequestDsl + fun dropItemInHand(entireStack: Boolean = true) { + actions.add { player.dropSelectedItem(entireStack) } + } + + @InvRequestDsl + fun swapHands() { + actions.add { + 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 + ) + ) + } + } + + @InvRequestDsl + fun clickCreativeStack(stack: ItemStack, slotId: Int) { + actions.add { interaction.clickCreativeStack(stack, slotId) } + } + + @InvRequestDsl + fun pickup(slotId: Int, button: Int = 0) = click(slotId, button, SlotActionType.PICKUP) + + // Quick move action (Shift-click) + @InvRequestDsl + fun quickMove(slotId: Int) = click(slotId, 0, SlotActionType.QUICK_MOVE) + + @InvRequestDsl + fun swap(slotId: Int, hotbarSlot: Int) = click(slotId, hotbarSlot, SlotActionType.SWAP) + + // Clone action (Creative mode) + @InvRequestDsl + fun clone(slotId: Int) = click(slotId, 2, SlotActionType.CLONE) + + // Throw stack or single item + @InvRequestDsl + fun throwStack(slotId: Int) = click(slotId, 1, SlotActionType.THROW) + + @InvRequestDsl + fun throwSingle(slotId: Int) = click(slotId, 0, SlotActionType.THROW) + + // Quick craft action + @InvRequestDsl + fun quickCraftStart(slotId: Int) = click(slotId, 0, SlotActionType.QUICK_CRAFT) + + @InvRequestDsl + fun quickCraftDrag(slotId: Int) = click(slotId, 1, SlotActionType.QUICK_CRAFT) + + @InvRequestDsl + fun quickCraftEnd(slotId: Int) = click(slotId, 2, SlotActionType.QUICK_CRAFT) + + // Pickup all items (double-click) + @InvRequestDsl + fun pickupAll(slotId: Int) = click(slotId, 0, SlotActionType.PICKUP_ALL) + + // Helper function: Move items from one slot to another + @InvRequestDsl + fun moveSlot(fromSlotId: Int, toSlotId: Int, button: Int = 0) { + pickup(fromSlotId, button) + pickup(toSlotId, button) + } + + // Helper function: Split a stack into two + @InvRequestDsl + 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 + @InvRequestDsl + fun mergeStacks(sourceSlotId: Int, targetSlotId: Int) { + pickup(sourceSlotId, 0) + pickup(targetSlotId, 0) } } companion object { var requestCount = 0 + + @InvRequestDsl + 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) } } diff --git a/src/main/kotlin/com/lambda/interaction/request/placing/PlaceConfig.kt b/src/main/kotlin/com/lambda/interaction/request/placing/PlaceConfig.kt index 440dfd87b..15cf19b4e 100644 --- a/src/main/kotlin/com/lambda/interaction/request/placing/PlaceConfig.kt +++ b/src/main/kotlin/com/lambda/interaction/request/placing/PlaceConfig.kt @@ -29,7 +29,7 @@ interface PlaceConfig : RequestConfig { val axisRotateSetting: Boolean val axisRotate get() = rotateForPlace && airPlace.isEnabled && axisRotateSetting - val placeStageMask: Set + val tickStageMask: Set val placeConfirmationMode: PlaceConfirmationMode val maxPendingPlacements: Int val placementsPerTick: Int diff --git a/src/main/kotlin/com/lambda/interaction/request/placing/PlaceManager.kt b/src/main/kotlin/com/lambda/interaction/request/placing/PlaceManager.kt index e1d262a13..c842298f0 100644 --- a/src/main/kotlin/com/lambda/interaction/request/placing/PlaceManager.kt +++ b/src/main/kotlin/com/lambda/interaction/request/placing/PlaceManager.kt @@ -163,7 +163,7 @@ object PlaceManager : RequestHandler( return } if (!validSneak(player)) return - if (tickStage !in placeConfig.placeStageMask) return + if (tickStage !in placeConfig.tickStageMask) return val actionResult = placeBlock(ctx, request, Hand.MAIN_HAND) if (!actionResult.isAccepted) { 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 36df520fd..2bbc86d26 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/FastBreak.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/FastBreak.kt @@ -28,7 +28,7 @@ import com.lambda.interaction.material.ContainerSelection.Companion.selectContai import com.lambda.interaction.material.StackSelection.Companion.selectStack import com.lambda.interaction.material.container.ContainerManager.containerWithMaterial import com.lambda.interaction.material.container.MaterialContainer -import com.lambda.interaction.request.breaking.BreakRequest +import com.lambda.interaction.request.breaking.BreakRequest.Companion.breakRequest import com.lambda.interaction.request.rotating.Rotation.Companion.rotation import com.lambda.interaction.request.rotating.RotationRequest import com.lambda.interaction.request.rotating.visibilty.lookAt @@ -176,7 +176,7 @@ object FastBreak : Module( this@FastBreak ) - BreakRequest(setOf(breakContext), pendingInteractions, this@FastBreak).submit() + breakRequest(setOf(breakContext), pendingInteractions).submit() } } } diff --git a/src/main/kotlin/com/lambda/util/player/PlayerUtils.kt b/src/main/kotlin/com/lambda/util/player/PlayerUtils.kt index b0d280893..3913cad33 100644 --- a/src/main/kotlin/com/lambda/util/player/PlayerUtils.kt +++ b/src/main/kotlin/com/lambda/util/player/PlayerUtils.kt @@ -27,8 +27,9 @@ import net.minecraft.entity.player.PlayerEntity import net.minecraft.item.ItemStack import net.minecraft.network.packet.c2s.play.HandSwingC2SPacket import net.minecraft.util.Hand +import net.minecraft.world.GameMode -val SafeContext.gamemode +val SafeContext.gamemode: GameMode get() = interaction.currentGameMode fun SafeContext.copyPlayer(entity: ClientPlayerEntity) = diff --git a/src/main/kotlin/com/lambda/util/player/SlotUtils.kt b/src/main/kotlin/com/lambda/util/player/SlotUtils.kt index 70f3753be..02785d326 100644 --- a/src/main/kotlin/com/lambda/util/player/SlotUtils.kt +++ b/src/main/kotlin/com/lambda/util/player/SlotUtils.kt @@ -44,5 +44,4 @@ object SlotUtils { player, ) } - } From 557c3afb3201d604ea5981b4b726fe9a2246a157 Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Thu, 6 Nov 2025 16:38:17 +0000 Subject: [PATCH 02/12] decouple actionsThisSecond and actionsThisTick setters --- .../lambda/interaction/request/inventory/InventoryManager.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryManager.kt b/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryManager.kt index aafd5d49f..54d7fdfed 100644 --- a/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryManager.kt +++ b/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryManager.kt @@ -46,10 +46,6 @@ object InventoryManager : RequestHandler( private var actionsThisSecond = 0 private var secondCounter = 0 private var actionsThisTick = 0 - set(value) { - field += value - actionsThisSecond+= value - } override val logger = inventoryManagerLogger @@ -91,6 +87,7 @@ object InventoryManager : RequestHandler( if (actionsThisSecond + 1 > maxActionsThisSecond && !request.mustPerform) break iterator.next()() actionsThisTick++ + actionsThisSecond++ iterator.remove() } From 705e8d9c412618639bc9ddf4efebc85a5667c695 Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Fri, 7 Nov 2025 11:39:08 +0000 Subject: [PATCH 03/12] swap old system for inventory manager requests --- .../container/containers/CreativeContainer.kt | 25 ++- .../container/containers/MainHandContainer.kt | 33 ++-- .../container/containers/OffHandContainer.kt | 2 +- .../material/transfer/InventoryTransaction.kt | 37 ----- .../material/transfer/SlotTransfer.kt | 9 +- .../material/transfer/TransactionExecutor.kt | 155 ------------------ .../material/transfer/TransferSelection.kt | 20 --- .../ClickCreativeStackTransaction.kt | 46 ------ .../transaction/ClickSlotTransaction.kt | 44 ----- .../transaction/DropItemInHandTransaction.kt | 38 ----- .../PickFromInventoryTransaction.kt | 45 ----- .../transaction/QuickCraftTransaction.kt | 31 ---- .../transaction/SwapHandsTransaction.kt | 50 ------ .../transaction/SwapHotbarSlotTransaction.kt | 35 ---- .../interaction/request/breaking/BreakInfo.kt | 6 +- .../request/breaking/BreakManager.kt | 26 +-- .../request/breaking/BreakRequest.kt | 29 ++-- .../request/breaking/BrokenBlockHandler.kt | 3 +- .../request/breaking/RebreakHandler.kt | 3 +- .../request/inventory/InventoryManager.kt | 5 +- .../request/inventory/InventoryRequest.kt | 11 +- .../interaction/request/placing/PlaceInfo.kt | 3 +- .../request/placing/PlaceManager.kt | 2 +- .../request/placing/PlaceRequest.kt | 3 +- .../request/placing/PlacedBlockHandler.kt | 2 +- .../kotlin/com/lambda/task/tasks/BuildTask.kt | 6 +- 26 files changed, 91 insertions(+), 578 deletions(-) delete mode 100644 src/main/kotlin/com/lambda/interaction/material/transfer/InventoryTransaction.kt delete mode 100644 src/main/kotlin/com/lambda/interaction/material/transfer/TransactionExecutor.kt delete mode 100644 src/main/kotlin/com/lambda/interaction/material/transfer/TransferSelection.kt delete mode 100644 src/main/kotlin/com/lambda/interaction/material/transfer/transaction/ClickCreativeStackTransaction.kt delete mode 100644 src/main/kotlin/com/lambda/interaction/material/transfer/transaction/ClickSlotTransaction.kt delete mode 100644 src/main/kotlin/com/lambda/interaction/material/transfer/transaction/DropItemInHandTransaction.kt delete mode 100644 src/main/kotlin/com/lambda/interaction/material/transfer/transaction/PickFromInventoryTransaction.kt delete mode 100644 src/main/kotlin/com/lambda/interaction/material/transfer/transaction/QuickCraftTransaction.kt delete mode 100644 src/main/kotlin/com/lambda/interaction/material/transfer/transaction/SwapHandsTransaction.kt delete mode 100644 src/main/kotlin/com/lambda/interaction/material/transfer/transaction/SwapHotbarSlotTransaction.kt diff --git a/src/main/kotlin/com/lambda/interaction/material/container/containers/CreativeContainer.kt b/src/main/kotlin/com/lambda/interaction/material/container/containers/CreativeContainer.kt index bd1bafd23..2582974a2 100644 --- a/src/main/kotlin/com/lambda/interaction/material/container/containers/CreativeContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/material/container/containers/CreativeContainer.kt @@ -22,7 +22,7 @@ import com.lambda.context.Automated import com.lambda.context.SafeContext import com.lambda.interaction.material.StackSelection import com.lambda.interaction.material.container.MaterialContainer -import com.lambda.interaction.material.transfer.TransactionExecutor +import com.lambda.interaction.request.inventory.InventoryRequest.Companion.inventoryRequest import com.lambda.task.Task import com.lambda.util.item.ItemStackUtils.equal import com.lambda.util.player.gamemode @@ -41,7 +41,7 @@ data object CreativeContainer : MaterialContainer(Rank.Creative) { override fun spaceAvailable(selection: StackSelection): Int = if (mc.player?.isCreative == true && selection.optimalStack != null) Int.MAX_VALUE else 0 - class CreativeDeposit @Ta5kBuilder constructor(val selection: StackSelection) : Task() { + class CreativeDeposit @Ta5kBuilder constructor(val selection: StackSelection, automated: Automated) : Task(), Automated by automated { override val name: String get() = "Removing $selection from creative inventory" override fun SafeContext.onStart() { @@ -50,22 +50,21 @@ data object CreativeContainer : MaterialContainer(Rank.Creative) { throw NotInCreativeModeException() } - TransactionExecutor.transfer(player.currentScreenHandler) { + inventoryRequest { player.currentScreenHandler?.slots?.let { slots -> selection.filterSlots(slots).forEach { clickCreativeStack(ItemStack.EMPTY, it.id) } } - }.finally { - success() - }.execute(this@CreativeDeposit) + onComplete { success() } + }.submit(queueIfClosed = false) } } context(automated: Automated) - override fun deposit(selection: StackSelection) = CreativeDeposit(selection) + override fun deposit(selection: StackSelection) = CreativeDeposit(selection, automated) - class CreativeWithdrawal @Ta5kBuilder constructor(val selection: StackSelection) : Task() { + class CreativeWithdrawal @Ta5kBuilder constructor(val selection: StackSelection, automated: Automated) : Task(), Automated by automated { override val name: String get() = "Withdrawing $selection from creative inventory" override fun SafeContext.onStart() { @@ -77,11 +76,11 @@ data object CreativeContainer : MaterialContainer(Rank.Creative) { throw NotInCreativeModeException() } - TransactionExecutor.transfer(player.currentScreenHandler) { + inventoryRequest { clickCreativeStack(optimalStack, 36 + player.inventory.selectedSlot) - }.finally { - success() - }.execute(this@CreativeWithdrawal) + player.inventory.selectedStack = optimalStack + onComplete { success() } + }.submit(queueIfClosed = false) return } @@ -91,7 +90,7 @@ data object CreativeContainer : MaterialContainer(Rank.Creative) { // Withdraws items from the creative menu to the player's main hand context(automated: Automated) - override fun withdraw(selection: StackSelection) = CreativeWithdrawal(selection) + override fun withdraw(selection: StackSelection) = CreativeWithdrawal(selection, automated) class NotInCreativeModeException : IllegalStateException("Insufficient permission: not in creative mode") class NoOptimalStackException : IllegalStateException("Cannot move item: no optimal stack") diff --git a/src/main/kotlin/com/lambda/interaction/material/container/containers/MainHandContainer.kt b/src/main/kotlin/com/lambda/interaction/material/container/containers/MainHandContainer.kt index f94dc35e9..32b0629b7 100644 --- a/src/main/kotlin/com/lambda/interaction/material/container/containers/MainHandContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/material/container/containers/MainHandContainer.kt @@ -23,10 +23,9 @@ import com.lambda.context.SafeContext import com.lambda.interaction.material.ContainerTask import com.lambda.interaction.material.StackSelection import com.lambda.interaction.material.container.MaterialContainer -import com.lambda.interaction.material.transfer.TransactionExecutor.Companion.transfer +import com.lambda.interaction.request.inventory.InventoryRequest.Companion.inventoryRequest import com.lambda.util.item.ItemStackUtils.equal import com.lambda.util.player.SlotUtils.combined -import com.lambda.util.player.SlotUtils.hotbar import com.lambda.util.player.SlotUtils.storage import com.lambda.util.text.buildText import com.lambda.util.text.literal @@ -40,7 +39,11 @@ object MainHandContainer : MaterialContainer(Rank.MainHand) { override val description = buildText { literal("MainHand") } - class HandDeposit @Ta5kBuilder constructor(val selection: StackSelection, val hand: Hand) : ContainerTask() { + class HandDeposit @Ta5kBuilder constructor( + val selection: StackSelection, + val hand: Hand, + automated: Automated + ) : ContainerTask(), Automated by automated { override val name: String get() = "Depositing [$selection] to ${hand.name.lowercase().replace("_", " ")}" override fun SafeContext.onStart() { @@ -55,26 +58,24 @@ object MainHandContainer : MaterialContainer(Rank.MainHand) { return } - transfer(player.currentScreenHandler) { + inventoryRequest { val stackInOffHand = moveStack.equal(player.offHandStack) - if (hand == Hand.MAIN_HAND && stackInOffHand) { + val stackInMainHand = moveStack.equal(player.mainHandStack) + if ((hand == Hand.MAIN_HAND && stackInOffHand) || (hand == Hand.OFF_HAND && stackInMainHand)) { swapHands() - return@transfer + return@inventoryRequest } - when (moveStack) { - in player.hotbar -> swapToHotbarSlot(player.hotbar.indexOf(moveStack)) - // ToDo: Use pickFromInventory - in player.storage -> swap(player.combined.indexOf(moveStack), 0) - } + if (moveStack in player.storage) swap(player.combined.indexOf(moveStack), 0) + else throw NotInInventoryException() - if (hand == Hand.OFF_HAND) swapHands() - }.finally { - success() - }.execute(this@HandDeposit) + onComplete { success() } + }.submit(queueIfClosed = false) } } context(automated: Automated) - override fun deposit(selection: StackSelection) = HandDeposit(selection, Hand.MAIN_HAND) + override fun deposit(selection: StackSelection) = HandDeposit(selection, Hand.MAIN_HAND, automated) + + class NotInInventoryException : IllegalStateException("Cannot find stack in inventory") } diff --git a/src/main/kotlin/com/lambda/interaction/material/container/containers/OffHandContainer.kt b/src/main/kotlin/com/lambda/interaction/material/container/containers/OffHandContainer.kt index b094df88c..1943e8fd8 100644 --- a/src/main/kotlin/com/lambda/interaction/material/container/containers/OffHandContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/material/container/containers/OffHandContainer.kt @@ -34,5 +34,5 @@ object OffHandContainer : MaterialContainer(Rank.OffHand) { override val description = buildText { literal("OffHand") } context(automated: Automated) - override fun deposit(selection: StackSelection) = MainHandContainer.HandDeposit(selection, Hand.OFF_HAND) + override fun deposit(selection: StackSelection) = MainHandContainer.HandDeposit(selection, Hand.OFF_HAND, automated) } diff --git a/src/main/kotlin/com/lambda/interaction/material/transfer/InventoryTransaction.kt b/src/main/kotlin/com/lambda/interaction/material/transfer/InventoryTransaction.kt deleted file mode 100644 index 620e12b95..000000000 --- a/src/main/kotlin/com/lambda/interaction/material/transfer/InventoryTransaction.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2025 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.material.transfer - -import com.lambda.context.SafeContext -import com.lambda.task.Task -import com.lambda.threading.runSafe - -abstract class InventoryTransaction : Task() { - lateinit var changes: InventoryChanges - - override fun SafeContext.onStart() { - changes = InventoryChanges(player.currentScreenHandler.slots) - } - - fun finish() { - runSafe { - changes.detectChanges() - success(changes) - } ?: failure("Failed to finish transaction") - } -} diff --git a/src/main/kotlin/com/lambda/interaction/material/transfer/SlotTransfer.kt b/src/main/kotlin/com/lambda/interaction/material/transfer/SlotTransfer.kt index 634dcb223..f0f0197d6 100644 --- a/src/main/kotlin/com/lambda/interaction/material/transfer/SlotTransfer.kt +++ b/src/main/kotlin/com/lambda/interaction/material/transfer/SlotTransfer.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.material.StackSelection -import com.lambda.interaction.material.transfer.TransactionExecutor.Companion.transfer +import com.lambda.interaction.request.inventory.InventoryRequest.Companion.inventoryRequest import com.lambda.task.Task import com.lambda.util.extension.containerSlots import com.lambda.util.extension.inventorySlots @@ -70,13 +70,12 @@ class SlotTransfer @Ta5kBuilder constructor( val nextFrom = selectedFrom.firstOrNull() ?: return@listen val nextTo = selectedTo.firstOrNull() ?: return@listen - transfer(screen) { + inventoryRequest { // moveSlot(nextFrom.id, nextTo.id) swap(nextTo.id, 1) swap(nextFrom.id, 1) - }.finally { change -> - changes merge change - }.execute(this@SlotTransfer) + onComplete { success() } + }.submit(queueIfClosed = false) } } diff --git a/src/main/kotlin/com/lambda/interaction/material/transfer/TransactionExecutor.kt b/src/main/kotlin/com/lambda/interaction/material/transfer/TransactionExecutor.kt deleted file mode 100644 index 59f6ebc24..000000000 --- a/src/main/kotlin/com/lambda/interaction/material/transfer/TransactionExecutor.kt +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright 2025 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.material.transfer - -import com.lambda.event.events.TickEvent -import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.material.transfer.transaction.ClickCreativeStackTransaction -import com.lambda.interaction.material.transfer.transaction.ClickSlotTransaction -import com.lambda.interaction.material.transfer.transaction.DropItemInHandTransaction -import com.lambda.interaction.material.transfer.transaction.PickFromInventoryTransaction -import com.lambda.interaction.material.transfer.transaction.SwapHandsTransaction -import com.lambda.interaction.material.transfer.transaction.SwapHotbarSlotTransaction -import com.lambda.task.Task -import net.minecraft.item.ItemStack -import net.minecraft.screen.ScreenHandler -import net.minecraft.screen.slot.SlotActionType - -class TransactionExecutor @Ta5kBuilder constructor( - private val screenHandler: ScreenHandler, - private val transactions: MutableList = mutableListOf(), -) : Task() { - override val name: String get() = "Execution of ${transactions.size} transactions left" - - private var changes = InventoryChanges(screenHandler.slots) - - init { - listen { - if (transactions.isEmpty()) { - success(changes) - return@listen - } - - if (player.currentScreenHandler != screenHandler) { - failure("Screen handler was closed") - return@listen - } - - transactions.removeFirstOrNull()?.finally { change -> - changes merge change - }?.execute(this@TransactionExecutor) - } - } - - @DslMarker - annotation class InvTransfer - - @InvTransfer - fun click(slotId: Int, button: Int, actionType: SlotActionType) { - transactions.add(ClickSlotTransaction(slotId, button, actionType)) - } - - @InvTransfer - fun pickFromInventory(slotId: Int) { - transactions.add(PickFromInventoryTransaction(slotId)) - } - - @InvTransfer - fun dropItemInHand(entireStack: Boolean = true) { - transactions.add(DropItemInHandTransaction(entireStack)) - } - - @InvTransfer - fun swapHands() { - transactions.add(SwapHandsTransaction()) - } - - @InvTransfer - fun swapToHotbarSlot(slotId: Int) { - transactions.add(SwapHotbarSlotTransaction(slotId)) - } - - @InvTransfer - fun pickup(slotId: Int, button: Int = 0) = click(slotId, button, SlotActionType.PICKUP) - - // Quick move action (Shift-click) - @InvTransfer - fun quickMove(slotId: Int) = click(slotId, 0, SlotActionType.QUICK_MOVE) - - @InvTransfer - fun swap(slotId: Int, hotbarSlot: Int) = click(slotId, hotbarSlot, SlotActionType.SWAP) - - // Clone action (Creative mode) - @InvTransfer - fun clone(slotId: Int) = click(slotId, 2, SlotActionType.CLONE) - - // Throw stack or single item - @InvTransfer - fun throwStack(slotId: Int) = click(slotId, 1, SlotActionType.THROW) - - @InvTransfer - fun throwSingle(slotId: Int) = click(slotId, 0, SlotActionType.THROW) - - // Quick craft action - @InvTransfer - fun quickCraftStart(slotId: Int) = click(slotId, 0, SlotActionType.QUICK_CRAFT) - - @InvTransfer - fun quickCraftDrag(slotId: Int) = click(slotId, 1, SlotActionType.QUICK_CRAFT) - - @InvTransfer - fun quickCraftEnd(slotId: Int) = click(slotId, 2, SlotActionType.QUICK_CRAFT) - - // Pickup all items (double-click) - @InvTransfer - fun pickupAll(slotId: Int) = click(slotId, 0, SlotActionType.PICKUP_ALL) - - // Helper function: Move items from one slot to another - @InvTransfer - fun moveSlot(fromSlotId: Int, toSlotId: Int, button: Int = 0) { - pickup(fromSlotId, button) - pickup(toSlotId, button) - } - - // Helper function: Split a stack into two - @InvTransfer - 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 - @InvTransfer - fun mergeStacks(sourceSlotId: Int, targetSlotId: Int) { - pickup(sourceSlotId, 0) - pickup(targetSlotId, 0) - } - - @InvTransfer - fun clickCreativeStack(stack: ItemStack, slotId: Int) { - transactions.add(ClickCreativeStackTransaction(stack, slotId)) - } - - companion object { - @InvTransfer - fun transfer(screenHandler: ScreenHandler, block: TransactionExecutor.() -> Unit) = - TransactionExecutor(screenHandler).apply { - block(this) - } - } -} diff --git a/src/main/kotlin/com/lambda/interaction/material/transfer/TransferSelection.kt b/src/main/kotlin/com/lambda/interaction/material/transfer/TransferSelection.kt deleted file mode 100644 index fe484e489..000000000 --- a/src/main/kotlin/com/lambda/interaction/material/transfer/TransferSelection.kt +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2025 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.material.transfer - -class TransferSelection \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/ClickCreativeStackTransaction.kt b/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/ClickCreativeStackTransaction.kt deleted file mode 100644 index 183614e87..000000000 --- a/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/ClickCreativeStackTransaction.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2025 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.material.transfer.transaction - -import com.lambda.event.events.InventoryEvent -import com.lambda.event.events.TickEvent -import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.material.transfer.InventoryTransaction -import net.minecraft.item.ItemStack - -class ClickCreativeStackTransaction @Ta5kBuilder constructor( - private val stack: ItemStack, - private val slotId: Int, -) : InventoryTransaction() { - override val name: String get() = "Creating stack $stack at #$slotId" - private var confirming = false - - init { - listen { - if (confirming) return@listen - - interaction.clickCreativeStack(stack, slotId) - confirming = true - } - - listen { - if (it.slot != slotId) return@listen - finish() - } - } -} diff --git a/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/ClickSlotTransaction.kt b/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/ClickSlotTransaction.kt deleted file mode 100644 index d0175ce8b..000000000 --- a/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/ClickSlotTransaction.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2025 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.material.transfer.transaction - -import com.lambda.event.events.TickEvent -import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.material.transfer.InventoryTransaction -import com.lambda.util.player.SlotUtils.clickSlot -import net.minecraft.screen.slot.SlotActionType - -class ClickSlotTransaction @Ta5kBuilder constructor( - private val slotId: Int, - private val button: Int, - private val actionType: SlotActionType, -) : InventoryTransaction() { - override val name: String get() = "Click slot #$slotId with action $actionType and button $button" - - init { - listen { - try { - clickSlot(slotId, button, actionType) - } catch (e: Exception) { - failure(e.message ?: "Unknown error") - return@listen - } - finish() - } - } -} diff --git a/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/DropItemInHandTransaction.kt b/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/DropItemInHandTransaction.kt deleted file mode 100644 index 72bcbbab1..000000000 --- a/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/DropItemInHandTransaction.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2025 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.material.transfer.transaction - -import com.lambda.event.events.TickEvent -import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.material.transfer.InventoryTransaction -import net.minecraft.util.Hand - -class DropItemInHandTransaction @Ta5kBuilder constructor( - private val entireStack: Boolean = false, -) : InventoryTransaction() { - override val name: String get() = "Dropping ${if (entireStack) "stack" else "item"} in hand" - - init { - listen { - if (!player.isSpectator && player.dropSelectedItem(entireStack)) { - player.swingHand(Hand.MAIN_HAND) - } - finish() - } - } -} diff --git a/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/PickFromInventoryTransaction.kt b/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/PickFromInventoryTransaction.kt deleted file mode 100644 index debd64b9f..000000000 --- a/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/PickFromInventoryTransaction.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2025 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.material.transfer.transaction - -import com.lambda.event.events.InventoryEvent -import com.lambda.event.events.TickEvent -import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.material.transfer.InventoryTransaction -import net.minecraft.screen.slot.SlotActionType - -class PickFromInventoryTransaction @Ta5kBuilder constructor( - val slot: Int, -) : InventoryTransaction() { - override val name: String get() = "Picking from slot #$slot" - private var confirming = false - - init { - listen { - if (confirming) return@listen - - interaction.clickSlot(0, slot, player.inventory.selectedSlot, SlotActionType.SWAP, player) - confirming = true - } - - listen { - if (it.slot != slot) return@listen - finish() - } - } -} diff --git a/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/QuickCraftTransaction.kt b/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/QuickCraftTransaction.kt deleted file mode 100644 index 45a31cb6b..000000000 --- a/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/QuickCraftTransaction.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2025 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.material.transfer.transaction - -import com.lambda.interaction.material.transfer.InventoryTransaction - -class QuickCraftTransaction @Ta5kBuilder constructor( - private val slots: List, - private val mode: Mode = Mode.Single, -) : InventoryTransaction() { - override val name: String get() = "Drag and drop ${slots.size} slots" - - enum class Mode { - Single, Split - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/SwapHandsTransaction.kt b/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/SwapHandsTransaction.kt deleted file mode 100644 index 77c77b145..000000000 --- a/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/SwapHandsTransaction.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2025 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.material.transfer.transaction - -import com.lambda.event.events.TickEvent -import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.material.transfer.InventoryTransaction -import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket -import net.minecraft.util.Hand -import net.minecraft.util.math.BlockPos -import net.minecraft.util.math.Direction - -class SwapHandsTransaction @Ta5kBuilder constructor() : InventoryTransaction() { - override val name: String get() = "Swap Hand Stacks" - - init { - listen { - if (player.isSpectator) { - failure("Spectators cannot swap hands") - return@listen - } - 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 - ) - ) - finish() - } - } -} diff --git a/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/SwapHotbarSlotTransaction.kt b/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/SwapHotbarSlotTransaction.kt deleted file mode 100644 index 58df667cb..000000000 --- a/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/SwapHotbarSlotTransaction.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2025 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.material.transfer.transaction - -import com.lambda.event.events.TickEvent -import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.material.transfer.InventoryTransaction - -class SwapHotbarSlotTransaction @Ta5kBuilder constructor( - val slot: Int, -) : InventoryTransaction() { - override val name: String get() = "Selecting slot #$slot" - - init { - listen { - player.inventory.selectedSlot = slot - finish() - } - } -} diff --git a/src/main/kotlin/com/lambda/interaction/request/breaking/BreakInfo.kt b/src/main/kotlin/com/lambda/interaction/request/breaking/BreakInfo.kt index 731d9e00c..b1edef638 100644 --- a/src/main/kotlin/com/lambda/interaction/request/breaking/BreakInfo.kt +++ b/src/main/kotlin/com/lambda/interaction/request/breaking/BreakInfo.kt @@ -81,17 +81,19 @@ data class BreakInfo( val callbacksCompleted get() = broken && (request.onItemDrop == null || item != null) + context(safeContext: SafeContext) fun internalOnBreak() { if (type != Rebreak) broken = true item?.let { item -> - request.onItemDrop?.invoke(item) + request.onItemDrop?.invoke(safeContext, item) } } + context(safeContext: SafeContext) fun internalOnItemDrop(item: ItemEntity) { if (type != Rebreak) this.item = item if (broken || type == Rebreak) { - request.onItemDrop?.invoke(item) + request.onItemDrop?.invoke(safeContext, item) } } diff --git a/src/main/kotlin/com/lambda/interaction/request/breaking/BreakManager.kt b/src/main/kotlin/com/lambda/interaction/request/breaking/BreakManager.kt index 619da7c2b..da44a8fbf 100644 --- a/src/main/kotlin/com/lambda/interaction/request/breaking/BreakManager.kt +++ b/src/main/kotlin/com/lambda/interaction/request/breaking/BreakManager.kt @@ -199,7 +199,7 @@ object BreakManager : RequestHandler( info.nullify() return@listen } - info.request.onStop?.invoke(info.context.blockPos) + info.request.onStop?.invoke(this@listen, info.context.blockPos) info.internalOnBreak() if (info.callbacksCompleted) RebreakHandler.offerRebreak(info) @@ -386,12 +386,12 @@ object BreakManager : RequestHandler( if ((!info.updatedThisTick || info.type == RedundantSecondary) || info.abandoned) { logger.debug("Updating info", info, ctx) if (info.type == RedundantSecondary) - info.request.onStart?.invoke(info.context.blockPos) + info.request.onStart?.invoke(this, info.context.blockPos) else if (info.abandoned) { info.abandoned = false - info.request.onStart?.invoke(info.context.blockPos) + info.request.onStart?.invoke(this, info.context.blockPos) } else - info.request.onUpdate?.invoke(info.context.blockPos) + info.request.onUpdate?.invoke(this, info.context.blockPos) info.updateInfo(ctx, request) } @@ -576,7 +576,7 @@ object BreakManager : RequestHandler( * @see startPending */ private fun AutomatedSafeContext.onBlockBreak(info: BreakInfo) { - info.request.onStop?.invoke(info.context.blockPos) + info.request.onStop?.invoke(this, info.context.blockPos) when (breakConfig.breakConfirmation) { BreakConfirmationMode.None -> { destroyBlock(info) @@ -634,14 +634,14 @@ object BreakManager : RequestHandler( nullify() setBreakingTextureStage(player, world, -1) abortBreakPacket() - request.onCancel?.invoke(context.blockPos) + request.onCancel?.invoke(this, context.blockPos) } Secondary -> { if (breakConfig.unsafeCancels) { logger.warning("Making break redundant", this@cancelBreak) type = RedundantSecondary setBreakingTextureStage(player, world, -1) - request.onCancel?.invoke(context.blockPos) + request.onCancel?.invoke(this, context.blockPos) } else { logger.warning("Abandoning break", this@cancelBreak) abandoned = true @@ -680,7 +680,7 @@ object BreakManager : RequestHandler( !swapped) return if (!startBreaking(info)) { info.nullify() - info.request.onCancel?.invoke(ctx.blockPos) + info.request.onCancel?.invoke(this, ctx.blockPos) } return } @@ -699,7 +699,7 @@ object BreakManager : RequestHandler( val blockState = blockState(ctx.blockPos) if (blockState.isEmpty) { info.nullify() - info.request.onCancel?.invoke(ctx.blockPos) + info.request.onCancel?.invoke(this, ctx.blockPos) logger.warning("Block state was unexpectedly empty", info) return } @@ -772,7 +772,7 @@ object BreakManager : RequestHandler( primaryBreak = rebreakResult.breakInfo.apply { type = Primary RebreakHandler.clearRebreak() - request.onStart?.invoke(ctx.blockPos) + request.onStart?.invoke(this@startBreaking, ctx.blockPos) } primaryBreak?.let { primary -> @@ -785,7 +785,7 @@ object BreakManager : RequestHandler( logger.debug("Rebroke", info) info.type = Rebreak info.nullify() - info.request.onReBreak?.invoke(ctx.blockPos) + info.request.onReBreak?.invoke(this, ctx.blockPos) return true } else -> {} @@ -797,7 +797,7 @@ object BreakManager : RequestHandler( if (gamemode.isCreative) { lastPosStarted = ctx.blockPos - info.request.onStart?.invoke(ctx.blockPos) + info.request.onStart?.invoke(this, ctx.blockPos) onBlockBreak(info) info.startBreakPacket() breakCooldown = breakConfig.breakDelay @@ -805,7 +805,7 @@ object BreakManager : RequestHandler( return true } if (info.breaking) return false - info.request.onStart?.invoke(ctx.blockPos) + info.request.onStart?.invoke(this, ctx.blockPos) lastPosStarted = ctx.blockPos diff --git a/src/main/kotlin/com/lambda/interaction/request/breaking/BreakRequest.kt b/src/main/kotlin/com/lambda/interaction/request/breaking/BreakRequest.kt index 253fd04ca..ba7795963 100644 --- a/src/main/kotlin/com/lambda/interaction/request/breaking/BreakRequest.kt +++ b/src/main/kotlin/com/lambda/interaction/request/breaking/BreakRequest.kt @@ -18,6 +18,7 @@ package com.lambda.interaction.request.breaking import com.lambda.context.Automated +import com.lambda.context.SafeContext import com.lambda.interaction.construction.context.BreakContext import com.lambda.interaction.construction.context.BuildContext import com.lambda.interaction.request.LogContext @@ -36,13 +37,13 @@ data class BreakRequest private constructor( ) : Request(), LogContext, Automated by automated { override val requestID = ++requestCount - var onStart: ((BlockPos) -> Unit)? = null - var onUpdate: ((BlockPos) -> Unit)? = null - var onStop: ((BlockPos) -> Unit)? = null - var onCancel: ((BlockPos) -> Unit)? = null - var onItemDrop: ((ItemEntity) -> Unit)? = null - var onReBreakStart: ((BlockPos) -> Unit)? = null - var onReBreak: ((BlockPos) -> Unit)? = null + 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 @@ -78,37 +79,37 @@ data class BreakRequest private constructor( val request = BreakRequest(contexts, pendingInteractions, automated) @BreakRequestDsl - fun onStart(callback: (BlockPos) -> Unit) { + fun onStart(callback: SafeContext.(BlockPos) -> Unit) { request.onStart = callback } @BreakRequestDsl - fun onUpdate(callback: (BlockPos) -> Unit) { + fun onUpdate(callback: SafeContext.(BlockPos) -> Unit) { request.onUpdate = callback } @BreakRequestDsl - fun onStop(callback: (BlockPos) -> Unit) { + fun onStop(callback: SafeContext.(BlockPos) -> Unit) { request.onStop = callback } @BreakRequestDsl - fun onCancel(callback: (BlockPos) -> Unit) { + fun onCancel(callback: SafeContext.(BlockPos) -> Unit) { request.onCancel = callback } @BreakRequestDsl - fun onItemDrop(callback: (ItemEntity) -> Unit) { + fun onItemDrop(callback: SafeContext.(ItemEntity) -> Unit) { request.onItemDrop = callback } @BreakRequestDsl - fun onReBreakStart(callback: (BlockPos) -> Unit) { + fun onReBreakStart(callback: SafeContext.(BlockPos) -> Unit) { request.onReBreakStart = callback } @BreakRequestDsl - fun onReBreak(callback: (BlockPos) -> Unit) { + fun onReBreak(callback: SafeContext.(BlockPos) -> Unit) { request.onReBreak = callback } } diff --git a/src/main/kotlin/com/lambda/interaction/request/breaking/BrokenBlockHandler.kt b/src/main/kotlin/com/lambda/interaction/request/breaking/BrokenBlockHandler.kt index 9fda8c1ff..b85c565aa 100644 --- a/src/main/kotlin/com/lambda/interaction/request/breaking/BrokenBlockHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/request/breaking/BrokenBlockHandler.kt @@ -70,8 +70,9 @@ object BrokenBlockHandler : PostActionHandler() { if (!info.broken && info.breakConfig.breakConfirmation != BreakConfirmationMode.AwaitThenBreak) { world.setBlockState(info.context.blockPos, info.context.cachedState) } + + info.request.onCancel?.invoke(this, info.context.blockPos) } - info.request.onCancel?.invoke(info.context.blockPos) info.pendingInteractionsList.remove(info.context) } diff --git a/src/main/kotlin/com/lambda/interaction/request/breaking/RebreakHandler.kt b/src/main/kotlin/com/lambda/interaction/request/breaking/RebreakHandler.kt index b35b88836..f6f9af58d 100644 --- a/src/main/kotlin/com/lambda/interaction/request/breaking/RebreakHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/request/breaking/RebreakHandler.kt @@ -47,6 +47,7 @@ object RebreakHandler { } } + context(safeContext: SafeContext) fun offerRebreak(info: BreakInfo) { if (!info.rebreakable) return @@ -55,7 +56,7 @@ object RebreakHandler { breaking = true resetCallbacks() } - info.request.onReBreakStart?.invoke(info.context.blockPos) + info.request.onReBreakStart?.invoke(safeContext, info.context.blockPos) } fun clearRebreak() { diff --git a/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryManager.kt b/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryManager.kt index 54d7fdfed..3acb1eba6 100644 --- a/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryManager.kt +++ b/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryManager.kt @@ -92,9 +92,10 @@ object InventoryManager : RequestHandler( } if (actions.isEmpty()) { - if (activeRequest != null) { - activeRequest?.done = true + activeRequest?.let { request -> logger.debug("Clearing active request", activeRequest) + request.done = true + request.onComplete?.invoke(this) activeRequest = null } } diff --git a/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryRequest.kt b/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryRequest.kt index 744563992..627f3befe 100644 --- a/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryRequest.kt +++ b/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryRequest.kt @@ -37,7 +37,8 @@ class InventoryRequest private constructor( val actions: List Unit>, val settleForLess: Boolean, val mustPerform: Boolean, - automated: Automated + automated: Automated, + val onComplete: (SafeContext.() -> Unit)? ) : Request(), LogContext, Automated by automated { override val requestID = ++requestCount override var done = false @@ -55,6 +56,7 @@ class InventoryRequest private constructor( @InvRequestDsl class InvRequestBuilder(val settleForLess: Boolean, val mustPerform: Boolean) { val actions = mutableListOf Unit>() + var onComplete: (SafeContext.() -> Unit)? = null @InvRequestDsl fun click(slotId: Int, button: Int, actionType: SlotActionType) { @@ -147,6 +149,11 @@ class InventoryRequest private constructor( pickup(sourceSlotId, 0) pickup(targetSlotId, 0) } + + @InvRequestDsl + fun onComplete(callback: SafeContext.() -> Unit) { + onComplete = callback + } } companion object { @@ -158,6 +165,6 @@ class InventoryRequest private constructor( @InvRequestDsl context(automated: Automated) - private fun InvRequestBuilder.build() = InventoryRequest(actions, settleForLess, mustPerform, automated) + private fun InvRequestBuilder.build() = InventoryRequest(actions, settleForLess, mustPerform, automated, onComplete) } } diff --git a/src/main/kotlin/com/lambda/interaction/request/placing/PlaceInfo.kt b/src/main/kotlin/com/lambda/interaction/request/placing/PlaceInfo.kt index 1335e2a59..718fe5909 100644 --- a/src/main/kotlin/com/lambda/interaction/request/placing/PlaceInfo.kt +++ b/src/main/kotlin/com/lambda/interaction/request/placing/PlaceInfo.kt @@ -17,6 +17,7 @@ package com.lambda.interaction.request.placing +import com.lambda.context.SafeContext import com.lambda.interaction.construction.context.BuildContext import com.lambda.interaction.construction.context.PlaceContext import com.lambda.interaction.request.ActionInfo @@ -27,7 +28,7 @@ import net.minecraft.util.math.BlockPos data class PlaceInfo( override val context: PlaceContext, override val pendingInteractionsList: MutableCollection, - val onPlace: ((BlockPos) -> Unit)?, + val onPlace: (SafeContext.(BlockPos) -> Unit)?, val placeConfig: PlaceConfig ) : ActionInfo, LogContext { override fun getLogContextBuilder(): LogContextBuilder.() -> Unit = { diff --git a/src/main/kotlin/com/lambda/interaction/request/placing/PlaceManager.kt b/src/main/kotlin/com/lambda/interaction/request/placing/PlaceManager.kt index c842298f0..816eb58c1 100644 --- a/src/main/kotlin/com/lambda/interaction/request/placing/PlaceManager.kt +++ b/src/main/kotlin/com/lambda/interaction/request/placing/PlaceManager.kt @@ -376,7 +376,7 @@ object PlaceManager : RequestHandler( if (placeConfig.sounds) placeSound(state, blockPos) if (placeConfig.placeConfirmationMode == PlaceConfig.PlaceConfirmationMode.None) { - request.onPlace?.invoke(placeContext.blockPos) + request.onPlace?.invoke(this, placeContext.blockPos) } logger.success("Placed ${placeContext.expectedState} at ${placeContext.blockPos}", placeContext, request) diff --git a/src/main/kotlin/com/lambda/interaction/request/placing/PlaceRequest.kt b/src/main/kotlin/com/lambda/interaction/request/placing/PlaceRequest.kt index a88c95a0d..c5576a1c3 100644 --- a/src/main/kotlin/com/lambda/interaction/request/placing/PlaceRequest.kt +++ b/src/main/kotlin/com/lambda/interaction/request/placing/PlaceRequest.kt @@ -18,6 +18,7 @@ package com.lambda.interaction.request.placing import com.lambda.context.Automated +import com.lambda.context.SafeContext import com.lambda.interaction.construction.context.BuildContext import com.lambda.interaction.construction.context.PlaceContext import com.lambda.interaction.request.LogContext @@ -32,7 +33,7 @@ data class PlaceRequest( val contexts: Collection, val pendingInteractions: MutableCollection, private val automated: Automated, - val onPlace: ((BlockPos) -> Unit)? = null + val onPlace: (SafeContext.(BlockPos) -> Unit)? = null ) : Request(), LogContext, Automated by automated { override val requestID = ++requestCount diff --git a/src/main/kotlin/com/lambda/interaction/request/placing/PlacedBlockHandler.kt b/src/main/kotlin/com/lambda/interaction/request/placing/PlacedBlockHandler.kt index d752f24c5..8e3a7ca1d 100644 --- a/src/main/kotlin/com/lambda/interaction/request/placing/PlacedBlockHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/request/placing/PlacedBlockHandler.kt @@ -68,7 +68,7 @@ object PlacedBlockHandler : PostActionHandler() { if (pending.placeConfig.placeConfirmationMode == PlaceConfig.PlaceConfirmationMode.AwaitThenPlace) with(pending.context) { placeSound(expectedState, blockPos) } - pending.onPlace?.invoke(pending.context.blockPos) + pending.onPlace?.invoke(this, pending.context.blockPos) } } } diff --git a/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt b/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt index 4319e3887..d8092a96b 100644 --- a/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt @@ -47,9 +47,9 @@ import com.lambda.interaction.construction.simulation.BuildGoal import com.lambda.interaction.construction.simulation.BuildSimulator.simulate import com.lambda.interaction.construction.simulation.Simulation.Companion.simulation import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.material.transfer.TransactionExecutor.Companion.transfer import com.lambda.interaction.request.breaking.BreakRequest.Companion.breakRequest import com.lambda.interaction.request.interacting.InteractRequest +import com.lambda.interaction.request.inventory.InventoryRequest.Companion.inventoryRequest import com.lambda.interaction.request.placing.PlaceRequest import com.lambda.task.Task import com.lambda.task.tasks.EatTask.Companion.eat @@ -238,9 +238,9 @@ class BuildTask private constructor( failure("No item in inventory to throw but inventory is full and cant pick up item drop") return@let true } - transfer(player.currentScreenHandler) { + inventoryRequest { throwStack(stackToThrow.id) - }.execute(this@BuildTask) + }.submit() return@let true } From 1e1c7e79a6fd41a9ee1af89f95e7d78ad59b09e2 Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Fri, 7 Nov 2025 12:57:27 +0000 Subject: [PATCH 04/12] fuck it idc --- .../material/container/containers/MainHandContainer.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/lambda/interaction/material/container/containers/MainHandContainer.kt b/src/main/kotlin/com/lambda/interaction/material/container/containers/MainHandContainer.kt index 32b0629b7..6ddfaaea8 100644 --- a/src/main/kotlin/com/lambda/interaction/material/container/containers/MainHandContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/material/container/containers/MainHandContainer.kt @@ -25,8 +25,6 @@ import com.lambda.interaction.material.StackSelection import com.lambda.interaction.material.container.MaterialContainer import com.lambda.interaction.request.inventory.InventoryRequest.Companion.inventoryRequest import com.lambda.util.item.ItemStackUtils.equal -import com.lambda.util.player.SlotUtils.combined -import com.lambda.util.player.SlotUtils.storage import com.lambda.util.text.buildText import com.lambda.util.text.literal import net.minecraft.item.ItemStack @@ -66,9 +64,13 @@ object MainHandContainer : MaterialContainer(Rank.MainHand) { return@inventoryRequest } - if (moveStack in player.storage) swap(player.combined.indexOf(moveStack), 0) + val slots = player.currentScreenHandler.slots.filter { it.stack == moveStack } + + if (slots.isNotEmpty()) swap(player.currentScreenHandler.slots.indexOf(slots.first()), player.inventory.selectedSlot) else throw NotInInventoryException() + if (hand == Hand.OFF_HAND) swapHands() + onComplete { success() } }.submit(queueIfClosed = false) } From 9ee4e5381048af73d9024759f74821805f1a3e7e Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Sat, 8 Nov 2025 16:38:16 +0000 Subject: [PATCH 05/12] better main hand container transfer --- .../material/container/containers/MainHandContainer.kt | 6 ++---- .../lambda/interaction/material/transfer/SlotTransfer.kt | 1 - 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/com/lambda/interaction/material/container/containers/MainHandContainer.kt b/src/main/kotlin/com/lambda/interaction/material/container/containers/MainHandContainer.kt index 6ddfaaea8..e50939f8f 100644 --- a/src/main/kotlin/com/lambda/interaction/material/container/containers/MainHandContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/material/container/containers/MainHandContainer.kt @@ -64,10 +64,8 @@ object MainHandContainer : MaterialContainer(Rank.MainHand) { return@inventoryRequest } - val slots = player.currentScreenHandler.slots.filter { it.stack == moveStack } - - if (slots.isNotEmpty()) swap(player.currentScreenHandler.slots.indexOf(slots.first()), player.inventory.selectedSlot) - else throw NotInInventoryException() + val slot = player.currentScreenHandler.slots.first { it.stack == moveStack } ?: throw NotInInventoryException() + swap(slot.id, player.inventory.selectedSlot) if (hand == Hand.OFF_HAND) swapHands() diff --git a/src/main/kotlin/com/lambda/interaction/material/transfer/SlotTransfer.kt b/src/main/kotlin/com/lambda/interaction/material/transfer/SlotTransfer.kt index f0f0197d6..d8679e8a1 100644 --- a/src/main/kotlin/com/lambda/interaction/material/transfer/SlotTransfer.kt +++ b/src/main/kotlin/com/lambda/interaction/material/transfer/SlotTransfer.kt @@ -71,7 +71,6 @@ class SlotTransfer @Ta5kBuilder constructor( val nextTo = selectedTo.firstOrNull() ?: return@listen inventoryRequest { -// moveSlot(nextFrom.id, nextTo.id) swap(nextTo.id, 1) swap(nextFrom.id, 1) onComplete { success() } From 4ff5cddbca9a528f5883701f8098c196059e8a82 Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Sun, 9 Nov 2025 03:59:28 +0000 Subject: [PATCH 06/12] anti desync measures for inventory packets --- .../com/lambda/context/AutomationConfig.kt | 3 + .../request/inventory/InventoryManager.kt | 85 ++++++++++++++----- 2 files changed, 67 insertions(+), 21 deletions(-) diff --git a/src/main/kotlin/com/lambda/context/AutomationConfig.kt b/src/main/kotlin/com/lambda/context/AutomationConfig.kt index 8925a5804..8f6222e0b 100644 --- a/src/main/kotlin/com/lambda/context/AutomationConfig.kt +++ b/src/main/kotlin/com/lambda/context/AutomationConfig.kt @@ -59,6 +59,9 @@ object AutomationConfig : Configurable(LambdaConfig), Automated { override val hotbarConfig = HotbarSettings(this, Group.Hotbar) override val eatConfig = EatSettings(this, Group.Eat) + val avoidDesync by setting("Avoid Desync", true, "Cancels incoming inventory update packets if they match previous actions").group(Group.Debug) + val maxDesyncCache by setting("Max Desync Cache", 10, 1..30, 1, "Maximum cached previous inventory actions") + val desyncTimeout by setting("Desync Timeout", 10, 1..30, 1, unit = " ticks", description = "Time to store previous inventory actions before dropping the cache").group(Group.Debug) val showAllEntries by setting("Show All Entries", false, "Show all entries in the task tree").group(Group.Debug) val shrinkFactor by setting("Shrink Factor", 0.001, 0.0..1.0, 0.001).group(Group.Debug) val ignoreItemDropWarnings by setting("Ignore Drop Warnings", false, "Hides the item drop warnings from the break manager").group(Group.Debug) diff --git a/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryManager.kt b/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryManager.kt index 3acb1eba6..f2040c2ff 100644 --- a/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryManager.kt +++ b/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryManager.kt @@ -18,30 +18,47 @@ package com.lambda.interaction.request.inventory import com.lambda.context.AutomatedSafeContext +import com.lambda.context.AutomationConfig +import com.lambda.context.AutomationConfig.avoidDesync import com.lambda.context.SafeContext import com.lambda.event.EventFlow.post +import com.lambda.event.events.PacketEvent import com.lambda.event.events.TickEvent import com.lambda.event.events.UpdateManagerEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.request.Logger import com.lambda.interaction.request.RequestHandler -import com.lambda.interaction.request.inventory.InventoryManager.activeRequest -import com.lambda.interaction.request.inventory.InventoryManager.processRequest import com.lambda.interaction.request.placing.PlaceManager import com.lambda.module.hud.ManagerDebugLoggers.inventoryManagerLogger -import com.lambda.threading.runSafeAutomated +import com.lambda.util.Communication.info +import com.lambda.util.collections.LimitedDecayQueue +import com.lambda.util.item.ItemStackUtils.equal +import net.minecraft.item.ItemStack +import net.minecraft.network.packet.s2c.play.InventoryS2CPacket +import net.minecraft.screen.ScreenHandler +import net.minecraft.screen.slot.Slot object InventoryManager : RequestHandler( 1, TickEvent.Pre, TickEvent.Input.Pre, TickEvent.Input.Post, - TickEvent.Player.Post, - onOpen = { activeRequest?.let { it.runSafeAutomated { processRequest(it) } } } + TickEvent.Player.Post ), Logger { - private var activeRequest: InventoryRequest? = null private var actions = mutableListOf Unit>() + private var slots = listOf() + private var alteredSlots = LimitedDecayQueue>>( + AutomationConfig.maxDesyncCache, AutomationConfig.desyncTimeout * 50L + ) + + private var screenHandler: ScreenHandler? = null + set(value) { + if (value != null && field !== value) + slots = getStacks(value.slots) + field = value + } + private var maxActionsThisSecond = 0 private var actionsThisSecond = 0 private var secondCounter = 0 @@ -52,13 +69,38 @@ object InventoryManager : RequestHandler( override fun load(): String { super.load() + listen(priority = Int.MIN_VALUE) { event -> + if (!avoidDesync) return@listen + val packet = event.packet as? InventoryS2CPacket ?: return@listen + screenHandler = player.currentScreenHandler + val packetScreenHandler = + if (packet.syncId == 0) player.playerScreenHandler + else player.currentScreenHandler + event.cancel() + val alteredContents = mutableListOf() + packet.contents.forEachIndexed { index, incomingStack -> + val matches = alteredSlots.removeIf { cached -> + incomingStack.equal(cached.second.second) + } + if (matches) alteredContents.add(packetScreenHandler.slots[index].stack) + else alteredContents.add(incomingStack) + if (matches) info(matches.toString()) + } + mc.executeSync { + packetScreenHandler.updateSlotStacks(packet.revision(), alteredContents, packet.cursorStack()) + } + } + listen(priority = Int.MIN_VALUE) { + if (avoidDesync) { + alteredSlots.addAll(gatherInventoryChanges()) + slots = getStacks(player.currentScreenHandler.slots) + } if (++secondCounter >= 20) { secondCounter = 0 actionsThisSecond = 0 } actionsThisTick = 0 - activeRequest = null actions.clear() } @@ -66,22 +108,16 @@ object InventoryManager : RequestHandler( } override fun AutomatedSafeContext.handleRequest(request: InventoryRequest) { - if (activeRequest != null) return if (request.actions.size >= request.inventoryConfig.actionsPerSecond - actionsThisSecond && !request.settleForLess && !request.mustPerform) return - activeRequest = request - processRequest(request) - if (actionsThisTick > 0) activeThisTick = true - } + if (tickStage !in inventoryConfig.tickStageMask) return - private fun AutomatedSafeContext.processRequest(request: InventoryRequest) { if (request.fresh) populateFrom(request) - if (tickStage !in inventoryConfig.tickStageMask) return - PlaceManager.logger.debug("Processing request", request) + screenHandler = player.currentScreenHandler val iterator = actions.iterator() while (iterator.hasNext()) { if (actionsThisSecond + 1 > maxActionsThisSecond && !request.mustPerform) break @@ -92,13 +128,11 @@ object InventoryManager : RequestHandler( } if (actions.isEmpty()) { - activeRequest?.let { request -> - logger.debug("Clearing active request", activeRequest) - request.done = true - request.onComplete?.invoke(this) - activeRequest = null - } + request.done = true + request.onComplete?.invoke(this) } + + if (actionsThisTick > 0) activeThisTick = true } private fun populateFrom(request: InventoryRequest) { @@ -107,5 +141,14 @@ object InventoryManager : RequestHandler( maxActionsThisSecond = request.inventoryConfig.actionsPerSecond } + private fun SafeContext.gatherInventoryChanges() = + if (player.currentScreenHandler !== screenHandler) emptyList() + else screenHandler?.slots + ?.filter { it.stack != slots[it.id] } + ?.map { Pair(it.id, Pair(slots[it.id], it.stack.copy())) } + ?: emptyList() + + private fun getStacks(slots: Collection) = slots.map { it.stack.copy() } + override fun preEvent() = UpdateManagerEvent.Inventory.post() } From a9202c20cc484c44c49bb668a0fa9cfe4865fea7 Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Sun, 9 Nov 2025 17:57:40 +0000 Subject: [PATCH 07/12] better anti desync measures --- .../ClientPlayNetworkHandlerMixin.java | 13 ++ .../com/lambda/context/AutomationConfig.kt | 4 +- .../request/inventory/InventoryManager.kt | 120 +++++++++++++----- 3 files changed, 103 insertions(+), 34 deletions(-) diff --git a/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java b/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java index e546b7b7b..9762bed0a 100644 --- a/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java +++ b/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java @@ -20,8 +20,11 @@ import com.lambda.event.EventFlow; import com.lambda.event.events.InventoryEvent; import com.lambda.event.events.WorldEvent; +import com.lambda.interaction.request.inventory.InventoryManager; import com.lambda.module.modules.movement.Velocity; import com.lambda.module.modules.render.NoRender; +import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import net.minecraft.client.network.ClientPlayNetworkHandler; import net.minecraft.client.network.PlayerListEntry; import net.minecraft.network.packet.s2c.play.*; @@ -103,4 +106,14 @@ public boolean onServerMetadata(ClientPlayNetworkHandler clientPlayNetworkHandle void injectVelocity(ExplosionS2CPacket packet, CallbackInfo ci) { if (Velocity.getExplosion() && Velocity.INSTANCE.isEnabled()) ci.cancel(); } + + @WrapMethod(method = "onScreenHandlerSlotUpdate") + private void wrapOnScreenHandlerSlotUpdate(ScreenHandlerSlotUpdateS2CPacket packet, Operation original) { + InventoryManager.onSlotUpdate(packet, original); + } + + @WrapMethod(method = "onInventory") + private void wrapOnInventory(InventoryS2CPacket packet, Operation original) { + InventoryManager.onInventoryUpdate(packet, original); + } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/context/AutomationConfig.kt b/src/main/kotlin/com/lambda/context/AutomationConfig.kt index 8f6222e0b..ad03a42df 100644 --- a/src/main/kotlin/com/lambda/context/AutomationConfig.kt +++ b/src/main/kotlin/com/lambda/context/AutomationConfig.kt @@ -60,8 +60,8 @@ object AutomationConfig : Configurable(LambdaConfig), Automated { override val eatConfig = EatSettings(this, Group.Eat) val avoidDesync by setting("Avoid Desync", true, "Cancels incoming inventory update packets if they match previous actions").group(Group.Debug) - val maxDesyncCache by setting("Max Desync Cache", 10, 1..30, 1, "Maximum cached previous inventory actions") - val desyncTimeout by setting("Desync Timeout", 10, 1..30, 1, unit = " ticks", description = "Time to store previous inventory actions before dropping the cache").group(Group.Debug) + val maxDesyncCache by setting("Max Desync Cache", 10, 1..30, 1, "Maximum cached previous inventory actions") { avoidDesync }.group(Group.Debug) + val desyncTimeout by setting("Desync Timeout", 10, 1..30, 1, unit = " ticks", description = "Time to store previous inventory actions before dropping the cache") { avoidDesync }.group(Group.Debug) val showAllEntries by setting("Show All Entries", false, "Show all entries in the task tree").group(Group.Debug) val shrinkFactor by setting("Shrink Factor", 0.001, 0.0..1.0, 0.001).group(Group.Debug) val ignoreItemDropWarnings by setting("Ignore Drop Warnings", false, "Hides the item drop warnings from the break manager").group(Group.Debug) diff --git a/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryManager.kt b/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryManager.kt index f2040c2ff..b6474a610 100644 --- a/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryManager.kt +++ b/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryManager.kt @@ -22,7 +22,6 @@ import com.lambda.context.AutomationConfig import com.lambda.context.AutomationConfig.avoidDesync import com.lambda.context.SafeContext import com.lambda.event.EventFlow.post -import com.lambda.event.events.PacketEvent import com.lambda.event.events.TickEvent import com.lambda.event.events.UpdateManagerEvent import com.lambda.event.listener.SafeListener.Companion.listen @@ -30,11 +29,15 @@ import com.lambda.interaction.request.Logger import com.lambda.interaction.request.RequestHandler import com.lambda.interaction.request.placing.PlaceManager import com.lambda.module.hud.ManagerDebugLoggers.inventoryManagerLogger -import com.lambda.util.Communication.info +import com.lambda.threading.runSafe 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 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 @@ -69,33 +72,8 @@ object InventoryManager : RequestHandler( override fun load(): String { super.load() - listen(priority = Int.MIN_VALUE) { event -> - if (!avoidDesync) return@listen - val packet = event.packet as? InventoryS2CPacket ?: return@listen - screenHandler = player.currentScreenHandler - val packetScreenHandler = - if (packet.syncId == 0) player.playerScreenHandler - else player.currentScreenHandler - event.cancel() - val alteredContents = mutableListOf() - packet.contents.forEachIndexed { index, incomingStack -> - val matches = alteredSlots.removeIf { cached -> - incomingStack.equal(cached.second.second) - } - if (matches) alteredContents.add(packetScreenHandler.slots[index].stack) - else alteredContents.add(incomingStack) - if (matches) info(matches.toString()) - } - mc.executeSync { - packetScreenHandler.updateSlotStacks(packet.revision(), alteredContents, packet.cursorStack()) - } - } - listen(priority = Int.MIN_VALUE) { - if (avoidDesync) { - alteredSlots.addAll(gatherInventoryChanges()) - slots = getStacks(player.currentScreenHandler.slots) - } + if (avoidDesync) indexInventoryChanges() if (++secondCounter >= 20) { secondCounter = 0 actionsThisSecond = 0 @@ -122,6 +100,7 @@ object InventoryManager : RequestHandler( while (iterator.hasNext()) { if (actionsThisSecond + 1 > maxActionsThisSecond && !request.mustPerform) break iterator.next()() + if (avoidDesync) indexInventoryChanges() actionsThisTick++ actionsThisSecond++ iterator.remove() @@ -141,14 +120,91 @@ object InventoryManager : RequestHandler( maxActionsThisSecond = request.inventoryConfig.actionsPerSecond } - private fun SafeContext.gatherInventoryChanges() = - if (player.currentScreenHandler !== screenHandler) emptyList() - else screenHandler?.slots - ?.filter { it.stack != slots[it.id] } + private fun SafeContext.indexInventoryChanges() { + if (player.currentScreenHandler !== screenHandler) return + val changes = screenHandler?.slots + ?.filter { !it.stack.equal(slots[it.id]) } ?.map { Pair(it.id, Pair(slots[it.id], it.stack.copy())) } ?: emptyList() + alteredSlots.addAll(changes) + slots = getStacks(player.currentScreenHandler.slots) + } private fun getStacks(slots: Collection) = slots.map { it.stack.copy() } + /** + * A modified version of the [net.minecraft.client.network.ClientPlayNetworkHandler.onInventory] method + */ + @JvmStatic + fun onInventoryUpdate(packet: InventoryS2CPacket, original: Operation){ + runSafe { + if (!mc.isOnThread || !avoidDesync) { + original.call(packet) + return + } + screenHandler = player.currentScreenHandler + val packetScreenHandler = + when (packet.syncId) { + 0 -> player.playerScreenHandler + screenHandler?.syncId -> player.currentScreenHandler + else -> return@runSafe + } + val alteredContents = mutableListOf() + packet.contents.forEachIndexed { index, incomingStack -> + val matches = alteredSlots.removeIf { cached -> + incomingStack.equal(cached.second.second) + } + if (matches) alteredContents.add(packetScreenHandler.slots[index].stack) + else alteredContents.add(incomingStack) + } + packetScreenHandler.updateSlotStacks(packet.revision(), alteredContents, packet.cursorStack()) + return + } + original.call(packet) + } + + /** + * A modified version of the vanilla [net.minecraft.client.network.ClientPlayNetworkHandler.onScreenHandlerSlotUpdate] method + */ + @JvmStatic + fun onSlotUpdate(packet: ScreenHandlerSlotUpdateS2CPacket, original: Operation) { + runSafe { + screenHandler = player.currentScreenHandler + if (!mc.isOnThread || !avoidDesync) { + original.call(packet) + return + } + val itemStack = packet.stack + mc.tutorialManager.onSlotUpdate(itemStack) + + val bl = (mc.currentScreen as? CreativeInventoryScreen)?.let { + !it.isInventoryTabSelected + } ?: false + + val matches = alteredSlots.removeIf { + it.first == packet.slot && it.second.second.equal(itemStack) + } + + if (packet.syncId == 0) { + if (PlayerScreenHandler.isInHotbar(packet.slot) && !itemStack.isEmpty) { + val itemStack2 = screenHandler?.getSlot(packet.slot)?.stack ?: return + if (itemStack2.isEmpty || itemStack2.count < itemStack.count) { + itemStack.bobbingAnimationTime = 5 + } + } + + if (!matches) player.playerScreenHandler.setStackInSlot(packet.slot, packet.revision, itemStack) + } else if (packet.syncId == player.currentScreenHandler.syncId && (packet.syncId != 0 || !bl)) + if (!matches) player.currentScreenHandler.setStackInSlot(packet.slot, packet.revision, itemStack) + + if (mc.currentScreen is CreativeInventoryScreen) { + player.playerScreenHandler.setReceivedStack(packet.slot, itemStack) + player.playerScreenHandler.sendContentUpdates() + } + return + } + original.call(packet) + } + override fun preEvent() = UpdateManagerEvent.Inventory.post() } From 0bbcb0ed25dc5f84cc78bce5877fc895d5c858ce Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Sun, 9 Nov 2025 19:40:45 +0000 Subject: [PATCH 08/12] air place request inventory manager --- .../lambda/config/groups/InventorySettings.kt | 4 +- .../request/inventory/InventoryAction.kt | 27 +++++++++++++ .../request/inventory/InventoryManager.kt | 7 ++-- .../request/inventory/InventoryRequest.kt | 28 ++++++++++---- .../request/placing/PlaceManager.kt | 38 +++++++------------ .../com/lambda/util/player/SlotUtils.kt | 15 +------- 6 files changed, 69 insertions(+), 50 deletions(-) create mode 100644 src/main/kotlin/com/lambda/interaction/request/inventory/InventoryAction.kt diff --git a/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt b/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt index e5922a3d7..4e105532f 100644 --- a/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt +++ b/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt @@ -34,8 +34,8 @@ class InventorySettings( Access("Access") } - override val actionsPerSecond by c.setting("Actions Per Tick", 20, 0..100, 1, "How many inventory actions can be performed per tick", visibility = vis).group(baseGroup, Group.General) - override val tickStageMask by c.setting("Inventory Stage Mask", setOf(TickEvent.Pre), description = "The sub-tick timing at which inventory actions are performed", visibility = vis).group(baseGroup, Group.General) + override val actionsPerSecond by c.setting("Actions Per Second", 100, 0..100, 1, "How many inventory actions can be performed per tick", visibility = vis).group(baseGroup, Group.General) + override val tickStageMask by c.setting("Inventory Stage Mask", setOf(TickEvent.Pre, TickEvent.Input.Pre, TickEvent.Input.Post, TickEvent.Player.Post), description = "The sub-tick timing at which inventory actions are performed", visibility = vis).group(baseGroup, Group.General) override val disposables by c.setting("Disposables", ItemUtils.defaultDisposables, ItemUtils.defaultDisposables, "Items that will be ignored when checking for a free slot", vis).group(baseGroup, Group.Container) override val swapWithDisposables by c.setting("Swap With Disposables", true, "Swap items with disposable ones", vis).group(baseGroup, Group.Container) override val providerPriority by c.setting("Provider Priority", InventoryConfig.Priority.WithMinItems, "What container to prefer when retrieving the item from", vis).group(baseGroup, Group.Container) diff --git a/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryAction.kt b/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryAction.kt new file mode 100644 index 000000000..5297cc62f --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryAction.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2025 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.request.inventory + +import com.lambda.context.SafeContext + +sealed interface InventoryAction { + val action: SafeContext.() -> Unit + + class Inventory(override val action: SafeContext.() -> Unit) : InventoryAction + class Other(override val action: SafeContext.() -> Unit) : InventoryAction +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryManager.kt b/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryManager.kt index b6474a610..ea5884b83 100644 --- a/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryManager.kt +++ b/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryManager.kt @@ -48,7 +48,7 @@ object InventoryManager : RequestHandler( TickEvent.Input.Post, TickEvent.Player.Post ), Logger { - private var actions = mutableListOf Unit>() + private var actions = mutableListOf() private var slots = listOf() private var alteredSlots = LimitedDecayQueue>>( @@ -86,7 +86,8 @@ object InventoryManager : RequestHandler( } override fun AutomatedSafeContext.handleRequest(request: InventoryRequest) { - if (request.actions.size >= request.inventoryConfig.actionsPerSecond - actionsThisSecond && + val inventoryActionCount = request.actions.count { it is InventoryAction.Inventory } + if (inventoryActionCount >= request.inventoryConfig.actionsPerSecond - actionsThisSecond && !request.settleForLess && !request.mustPerform) return if (tickStage !in inventoryConfig.tickStageMask) return @@ -99,7 +100,7 @@ object InventoryManager : RequestHandler( val iterator = actions.iterator() while (iterator.hasNext()) { if (actionsThisSecond + 1 > maxActionsThisSecond && !request.mustPerform) break - iterator.next()() + iterator.next().action(this) if (avoidDesync) indexInventoryChanges() actionsThisTick++ actionsThisSecond++ diff --git a/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryRequest.kt b/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryRequest.kt index 627f3befe..8893ee390 100644 --- a/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryRequest.kt +++ b/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryRequest.kt @@ -34,7 +34,7 @@ import net.minecraft.util.math.Direction private annotation class InvRequestDsl class InventoryRequest private constructor( - val actions: List Unit>, + val actions: List, val settleForLess: Boolean, val mustPerform: Boolean, automated: Automated, @@ -55,27 +55,29 @@ class InventoryRequest private constructor( @InvRequestDsl class InvRequestBuilder(val settleForLess: Boolean, val mustPerform: Boolean) { - val actions = mutableListOf Unit>() + val actions = mutableListOf() var onComplete: (SafeContext.() -> Unit)? = null @InvRequestDsl fun click(slotId: Int, button: Int, actionType: SlotActionType) { - actions.add { clickSlot(slotId, button, actionType) } + InventoryAction.Inventory { clickSlot(slotId, button, actionType) }.addToActions() } @InvRequestDsl fun pickFromInventory(slotId: Int) { - actions.add { clickSlot(slotId, player.inventory.selectedSlot, SlotActionType.SWAP) } + InventoryAction.Inventory { + clickSlot(slotId, player.inventory.selectedSlot, SlotActionType.SWAP) + }.addToActions() } @InvRequestDsl fun dropItemInHand(entireStack: Boolean = true) { - actions.add { player.dropSelectedItem(entireStack) } + InventoryAction.Inventory { player.dropSelectedItem(entireStack) }.addToActions() } @InvRequestDsl fun swapHands() { - actions.add { + InventoryAction.Inventory { val offhandStack = player.getStackInHand(Hand.OFF_HAND) player.setStackInHand(Hand.OFF_HAND, player.getStackInHand(Hand.MAIN_HAND)) player.setStackInHand(Hand.MAIN_HAND, offhandStack) @@ -86,12 +88,12 @@ class InventoryRequest private constructor( Direction.DOWN ) ) - } + }.addToActions() } @InvRequestDsl fun clickCreativeStack(stack: ItemStack, slotId: Int) { - actions.add { interaction.clickCreativeStack(stack, slotId) } + InventoryAction.Inventory { interaction.clickCreativeStack(stack, slotId) }.addToActions() } @InvRequestDsl @@ -150,10 +152,20 @@ class InventoryRequest private constructor( pickup(targetSlotId, 0) } + @InvRequestDsl + fun action(action: SafeContext.() -> Unit) { + InventoryAction.Other(action).addToActions() + } + @InvRequestDsl fun onComplete(callback: SafeContext.() -> Unit) { onComplete = callback } + + @InvRequestDsl + private fun InventoryAction.addToActions() { + actions.add(this) + } } companion object { diff --git a/src/main/kotlin/com/lambda/interaction/request/placing/PlaceManager.kt b/src/main/kotlin/com/lambda/interaction/request/placing/PlaceManager.kt index 816eb58c1..45f3b1b09 100644 --- a/src/main/kotlin/com/lambda/interaction/request/placing/PlaceManager.kt +++ b/src/main/kotlin/com/lambda/interaction/request/placing/PlaceManager.kt @@ -35,6 +35,7 @@ import com.lambda.interaction.request.PositionBlocking import com.lambda.interaction.request.RequestHandler import com.lambda.interaction.request.breaking.BreakManager import com.lambda.interaction.request.interacting.InteractionManager +import com.lambda.interaction.request.inventory.InventoryRequest.Companion.inventoryRequest import com.lambda.interaction.request.placing.PlaceManager.activeRequest import com.lambda.interaction.request.placing.PlaceManager.maxPlacementsThisTick import com.lambda.interaction.request.placing.PlaceManager.placeBlock @@ -59,14 +60,12 @@ import net.minecraft.item.BlockItem import net.minecraft.item.ItemPlacementContext import net.minecraft.item.ItemStack import net.minecraft.item.ItemUsageContext -import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket import net.minecraft.network.packet.c2s.play.PlayerInteractBlockC2SPacket import net.minecraft.sound.SoundCategory import net.minecraft.util.ActionResult import net.minecraft.util.Hand import net.minecraft.util.hit.BlockHitResult import net.minecraft.util.math.BlockPos -import net.minecraft.util.math.Direction import net.minecraft.world.GameMode import kotlin.math.min @@ -328,21 +327,25 @@ object PlaceManager : RequestHandler( return ActionResult.FAIL } - val stackInHand = player.getStackInHand(hand) - val stackCountPre = stackInHand.count - if (placeConfig.placeConfirmationMode != PlaceConfig.PlaceConfirmationMode.None) { - PlaceInfo(placeContext, request.pendingInteractions, request.onPlace, placeConfig).startPending() - } - if (placeConfig.airPlace == PlaceConfig.AirPlaceMode.Grim) { val placeHand = if (hand == Hand.MAIN_HAND) Hand.OFF_HAND else Hand.MAIN_HAND - airPlaceOffhandSwap() - sendPlacePacket(placeHand, hitResult) - airPlaceOffhandSwap() + val inventoryRequest = inventoryRequest { + swapHands() + action { sendPlacePacket(placeHand, hitResult) } + swapHands() + }.submit(queueIfClosed = false) + if (!inventoryRequest.done) return ActionResult.FAIL } else { sendPlacePacket(hand, hitResult) } + if (placeConfig.placeConfirmationMode != PlaceConfig.PlaceConfirmationMode.None) { + PlaceInfo(placeContext, request.pendingInteractions, request.onPlace, placeConfig).startPending() + } + + val stackInHand = player.getStackInHand(hand) + val stackCountPre = stackInHand.count + if (placeConfig.swing) { swingHand(placeConfig.swingType, hand) @@ -407,18 +410,5 @@ object PlaceManager : RequestHandler( ) } - /** - * Must be called before and after placing a block to bypass grim's air place checks. - */ - private fun SafeContext.airPlaceOffhandSwap() { - connection.sendPacket( - PlayerActionC2SPacket( - PlayerActionC2SPacket.Action.SWAP_ITEM_WITH_OFFHAND, - BlockPos.ORIGIN, - Direction.DOWN - ) - ) - } - override fun preEvent(): Event = UpdateManagerEvent.Place.post() } diff --git a/src/main/kotlin/com/lambda/util/player/SlotUtils.kt b/src/main/kotlin/com/lambda/util/player/SlotUtils.kt index 02785d326..d20786aaa 100644 --- a/src/main/kotlin/com/lambda/util/player/SlotUtils.kt +++ b/src/main/kotlin/com/lambda/util/player/SlotUtils.kt @@ -29,19 +29,8 @@ object SlotUtils { val ClientPlayerEntity.hotbarAndStorage: List get() = inventory.mainStacks val ClientPlayerEntity.combined: List get() = hotbarAndStorage + equipment - fun SafeContext.clickSlot( - slotId: Int, - button: Int, - actionType: SlotActionType, - ) { + fun SafeContext.clickSlot(slotId: Int, button: Int, actionType: SlotActionType) { val syncId = player.currentScreenHandler?.syncId ?: return - - interaction.clickSlot( - syncId, - slotId, - button, - actionType, - player, - ) + interaction.clickSlot(syncId, slotId, button, actionType, player) } } From a15720b6f8dc9c1219010ded9888eb5e76cc4b67 Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Sun, 9 Nov 2025 20:04:08 +0000 Subject: [PATCH 09/12] update decay timeout and use player screen handler when syncId is 0 in onSlotUpdate --- .../lambda/interaction/request/inventory/InventoryManager.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryManager.kt b/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryManager.kt index ea5884b83..df97b9888 100644 --- a/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryManager.kt +++ b/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryManager.kt @@ -119,6 +119,7 @@ object InventoryManager : RequestHandler( PlaceManager.logger.debug("Populating from request", request) actions = request.actions.toMutableList() maxActionsThisSecond = request.inventoryConfig.actionsPerSecond + alteredSlots.setDecayTime(AutomationConfig.desyncTimeout * 50L) } private fun SafeContext.indexInventoryChanges() { @@ -188,7 +189,7 @@ object InventoryManager : RequestHandler( if (packet.syncId == 0) { if (PlayerScreenHandler.isInHotbar(packet.slot) && !itemStack.isEmpty) { - val itemStack2 = screenHandler?.getSlot(packet.slot)?.stack ?: return + val itemStack2 = player.playerScreenHandler.getSlot(packet.slot).stack if (itemStack2.isEmpty || itemStack2.count < itemStack.count) { itemStack.bobbingAnimationTime = 5 } From a1637c339f276d988266a417b74ded74861918e3 Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Sun, 9 Nov 2025 20:09:37 +0000 Subject: [PATCH 10/12] higher default values --- src/main/kotlin/com/lambda/context/AutomationConfig.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/lambda/context/AutomationConfig.kt b/src/main/kotlin/com/lambda/context/AutomationConfig.kt index ad03a42df..ff68762f3 100644 --- a/src/main/kotlin/com/lambda/context/AutomationConfig.kt +++ b/src/main/kotlin/com/lambda/context/AutomationConfig.kt @@ -60,8 +60,8 @@ object AutomationConfig : Configurable(LambdaConfig), Automated { override val eatConfig = EatSettings(this, Group.Eat) val avoidDesync by setting("Avoid Desync", true, "Cancels incoming inventory update packets if they match previous actions").group(Group.Debug) - val maxDesyncCache by setting("Max Desync Cache", 10, 1..30, 1, "Maximum cached previous inventory actions") { avoidDesync }.group(Group.Debug) - val desyncTimeout by setting("Desync Timeout", 10, 1..30, 1, unit = " ticks", description = "Time to store previous inventory actions before dropping the cache") { avoidDesync }.group(Group.Debug) + val maxDesyncCache by setting("Max Desync Cache", 30, 1..30, 1, "Maximum cached previous inventory actions") { avoidDesync }.group(Group.Debug) + val desyncTimeout by setting("Desync Timeout", 30, 1..30, 1, unit = " ticks", description = "Time to store previous inventory actions before dropping the cache") { avoidDesync }.group(Group.Debug) val showAllEntries by setting("Show All Entries", false, "Show all entries in the task tree").group(Group.Debug) val shrinkFactor by setting("Shrink Factor", 0.001, 0.0..1.0, 0.001).group(Group.Debug) val ignoreItemDropWarnings by setting("Ignore Drop Warnings", false, "Hides the item drop warnings from the break manager").group(Group.Debug) From a88b2e5980d07b08ed189e48fd554cb477ec4866 Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Mon, 10 Nov 2025 13:25:31 +0000 Subject: [PATCH 11/12] use decrementUnlessCreative --- .../com/lambda/interaction/request/placing/PlaceManager.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/lambda/interaction/request/placing/PlaceManager.kt b/src/main/kotlin/com/lambda/interaction/request/placing/PlaceManager.kt index 45f3b1b09..ed3de1265 100644 --- a/src/main/kotlin/com/lambda/interaction/request/placing/PlaceManager.kt +++ b/src/main/kotlin/com/lambda/interaction/request/placing/PlaceManager.kt @@ -355,7 +355,7 @@ object PlaceManager : RequestHandler( } val itemStack = itemPlacementContext.stack - if (!player.abilities.creativeMode) itemStack.decrement(1) + itemStack.decrementUnlessCreative(1, player) if (placeConfig.placeConfirmationMode == PlaceConfig.PlaceConfirmationMode.AwaitThenPlace) return ActionResult.SUCCESS From 0c14c1e4c648e4102e6c44e8f0462ee391eb1c3d Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Mon, 10 Nov 2025 16:02:48 +0000 Subject: [PATCH 12/12] use tick pre listener over onStart for inventory request actions in tasks in case they cant be completed --- .../container/containers/CreativeContainer.kt | 62 +++++++++++-------- .../container/containers/MainHandContainer.kt | 49 ++++++++------- src/main/kotlin/com/lambda/task/Task.kt | 4 +- 3 files changed, 62 insertions(+), 53 deletions(-) diff --git a/src/main/kotlin/com/lambda/interaction/material/container/containers/CreativeContainer.kt b/src/main/kotlin/com/lambda/interaction/material/container/containers/CreativeContainer.kt index 2582974a2..b4a0e3b36 100644 --- a/src/main/kotlin/com/lambda/interaction/material/container/containers/CreativeContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/material/container/containers/CreativeContainer.kt @@ -19,7 +19,8 @@ package com.lambda.interaction.material.container.containers import com.lambda.Lambda.mc 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.material.StackSelection import com.lambda.interaction.material.container.MaterialContainer import com.lambda.interaction.request.inventory.InventoryRequest.Companion.inventoryRequest @@ -44,20 +45,22 @@ data object CreativeContainer : MaterialContainer(Rank.Creative) { class CreativeDeposit @Ta5kBuilder constructor(val selection: StackSelection, automated: Automated) : Task(), Automated by automated { override val name: String get() = "Removing $selection from creative inventory" - override fun SafeContext.onStart() { - if (!gamemode.isCreative) { - // ToDo: Maybe switch gamemode? - throw NotInCreativeModeException() - } + init { + listen { + if (!gamemode.isCreative) { + // ToDo: Maybe switch gamemode? + throw NotInCreativeModeException() + } - inventoryRequest { - player.currentScreenHandler?.slots?.let { slots -> - selection.filterSlots(slots).forEach { - clickCreativeStack(ItemStack.EMPTY, it.id) + inventoryRequest { + player.currentScreenHandler?.slots?.let { slots -> + selection.filterSlots(slots).forEach { + clickCreativeStack(ItemStack.EMPTY, it.id) + } } - } - onComplete { success() } - }.submit(queueIfClosed = false) + onComplete { success() } + }.submit(queueIfClosed = false) + } } } @@ -67,24 +70,29 @@ data object CreativeContainer : MaterialContainer(Rank.Creative) { class CreativeWithdrawal @Ta5kBuilder constructor(val selection: StackSelection, automated: Automated) : Task(), Automated by automated { override val name: String get() = "Withdrawing $selection from creative inventory" - override fun SafeContext.onStart() { - selection.optimalStack?.let { optimalStack -> - if (player.mainHandStack.equal(optimalStack)) return + init { + listen { + selection.optimalStack?.let { optimalStack -> + if (player.mainHandStack.equal(optimalStack)) { + success() + return@listen + } - if (!gamemode.isCreative) { - // ToDo: Maybe switch gamemode? - throw NotInCreativeModeException() + if (!gamemode.isCreative) { + // ToDo: Maybe switch gamemode? + throw NotInCreativeModeException() + } + + inventoryRequest { + clickCreativeStack(optimalStack, 36 + player.inventory.selectedSlot) + action { player.inventory.selectedStack = optimalStack } + onComplete { success() } + }.submit(queueIfClosed = false) + return@listen } - inventoryRequest { - clickCreativeStack(optimalStack, 36 + player.inventory.selectedSlot) - player.inventory.selectedStack = optimalStack - onComplete { success() } - }.submit(queueIfClosed = false) - return + throw NoOptimalStackException() } - - throw NoOptimalStackException() } } diff --git a/src/main/kotlin/com/lambda/interaction/material/container/containers/MainHandContainer.kt b/src/main/kotlin/com/lambda/interaction/material/container/containers/MainHandContainer.kt index e50939f8f..1b0e41f2a 100644 --- a/src/main/kotlin/com/lambda/interaction/material/container/containers/MainHandContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/material/container/containers/MainHandContainer.kt @@ -19,7 +19,8 @@ package com.lambda.interaction.material.container.containers import com.lambda.Lambda.mc 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.material.ContainerTask import com.lambda.interaction.material.StackSelection import com.lambda.interaction.material.container.MaterialContainer @@ -44,33 +45,35 @@ object MainHandContainer : MaterialContainer(Rank.MainHand) { ) : ContainerTask(), Automated by automated { override val name: String get() = "Depositing [$selection] to ${hand.name.lowercase().replace("_", " ")}" - override fun SafeContext.onStart() { - val moveStack = InventoryContainer.matchingStacks(selection).firstOrNull() ?: run { - failure("No matching stacks found in inventory") - return - } - - val handStack = player.getStackInHand(hand) - if (moveStack.equal(handStack)) { - success() - return - } + init { + listen { + val moveStack = InventoryContainer.matchingStacks(selection).firstOrNull() ?: run { + failure("No matching stacks found in inventory") + return@listen + } - inventoryRequest { - val stackInOffHand = moveStack.equal(player.offHandStack) - val stackInMainHand = moveStack.equal(player.mainHandStack) - if ((hand == Hand.MAIN_HAND && stackInOffHand) || (hand == Hand.OFF_HAND && stackInMainHand)) { - swapHands() - return@inventoryRequest + val handStack = player.getStackInHand(hand) + if (moveStack.equal(handStack)) { + success() + return@listen } - val slot = player.currentScreenHandler.slots.first { it.stack == moveStack } ?: throw NotInInventoryException() - swap(slot.id, player.inventory.selectedSlot) + inventoryRequest { + val stackInOffHand = moveStack.equal(player.offHandStack) + val stackInMainHand = moveStack.equal(player.mainHandStack) + if ((hand == Hand.MAIN_HAND && stackInOffHand) || (hand == Hand.OFF_HAND && stackInMainHand)) { + swapHands() + return@inventoryRequest + } + + val slot = player.currentScreenHandler.slots.first { it.stack == moveStack } ?: throw NotInInventoryException() + swap(slot.id, player.inventory.selectedSlot) - if (hand == Hand.OFF_HAND) swapHands() + if (hand == Hand.OFF_HAND) swapHands() - onComplete { success() } - }.submit(queueIfClosed = false) + onComplete { success() } + }.submit(queueIfClosed = false) + } } } diff --git a/src/main/kotlin/com/lambda/task/Task.kt b/src/main/kotlin/com/lambda/task/Task.kt index b3f47bbc5..502337293 100644 --- a/src/main/kotlin/com/lambda/task/Task.kt +++ b/src/main/kotlin/com/lambda/task/Task.kt @@ -128,9 +128,7 @@ abstract class Task : Nameable, Muteable { unsubscribe() state = State.Completed if (!AutomationConfig.showAllEntries) parent?.subTasks?.remove(this) - runSafe { - executeNextTask(result) - } + runSafe { executeNextTask(result) } } @Ta5kBuilder