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/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/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..4e105532f 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 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/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/AutomationConfig.kt b/src/main/kotlin/com/lambda/context/AutomationConfig.kt index 8925a5804..ff68762f3 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", 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) diff --git a/src/main/kotlin/com/lambda/interaction/material/transfer/TransferSelection.kt b/src/main/kotlin/com/lambda/context/IAutomatedSafeContext.kt similarity index 89% rename from src/main/kotlin/com/lambda/interaction/material/transfer/TransferSelection.kt rename to src/main/kotlin/com/lambda/context/IAutomatedSafeContext.kt index fe484e489..b01d8fe38 100644 --- a/src/main/kotlin/com/lambda/interaction/material/transfer/TransferSelection.kt +++ b/src/main/kotlin/com/lambda/context/IAutomatedSafeContext.kt @@ -15,6 +15,6 @@ * along with this program. If not, see . */ -package com.lambda.interaction.material.transfer +package com.lambda.context -class TransferSelection \ No newline at end of file +interface IAutomatedSafeContext : SafeContext, Automated \ No newline at end of file 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..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,10 +19,11 @@ 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.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,57 +42,63 @@ 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() { - if (!gamemode.isCreative) { - // ToDo: Maybe switch gamemode? - throw NotInCreativeModeException() - } + init { + listen { + if (!gamemode.isCreative) { + // ToDo: Maybe switch gamemode? + throw NotInCreativeModeException() + } - TransactionExecutor.transfer(player.currentScreenHandler) { - 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) + } } - } - }.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() { - 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 } - TransactionExecutor.transfer(player.currentScreenHandler) { - clickCreativeStack(optimalStack, 36 + player.inventory.selectedSlot) - }.finally { - success() - }.execute(this@CreativeWithdrawal) - return + throw NoOptimalStackException() } - - throw NoOptimalStackException() } } // 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..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,15 +19,13 @@ 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 -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 import net.minecraft.item.ItemStack @@ -40,41 +38,47 @@ 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() { - 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 - } - - transfer(player.currentScreenHandler) { - val stackInOffHand = moveStack.equal(player.offHandStack) - if (hand == Hand.MAIN_HAND && stackInOffHand) { - swapHands() - return@transfer + init { + listen { + val moveStack = InventoryContainer.matchingStacks(selection).firstOrNull() ?: run { + failure("No matching stacks found in inventory") + return@listen } - when (moveStack) { - in player.hotbar -> swapToHotbarSlot(player.hotbar.indexOf(moveStack)) - // ToDo: Use pickFromInventory - in player.storage -> swap(player.combined.indexOf(moveStack), 0) + val handStack = player.getStackInHand(hand) + if (moveStack.equal(handStack)) { + success() + return@listen } - if (hand == Hand.OFF_HAND) swapHands() - }.finally { - success() - }.execute(this@HandDeposit) + 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() + + 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..d8679e8a1 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,11 @@ class SlotTransfer @Ta5kBuilder constructor( val nextFrom = selectedFrom.firstOrNull() ?: return@listen val nextTo = selectedTo.firstOrNull() ?: return@listen - transfer(screen) { -// moveSlot(nextFrom.id, nextTo.id) + inventoryRequest { 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/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/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/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/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 e3a9ff85a..3ef12f5ca 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) @@ -387,12 +387,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) } @@ -498,7 +498,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) { @@ -544,7 +544,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 @@ -577,7 +577,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) @@ -605,7 +605,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 @@ -635,14 +635,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 @@ -681,7 +681,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 } @@ -700,7 +700,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 } @@ -773,7 +773,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 -> @@ -786,7 +786,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 -> {} @@ -798,7 +798,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 @@ -806,7 +806,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 3fedc91ec..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 @@ -29,20 +30,20 @@ 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 ) : 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 @@ -67,63 +68,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 - fun onStart(callback: (BlockPos) -> Unit) { + @BreakRequestDsl + fun onStart(callback: SafeContext.(BlockPos) -> Unit) { request.onStart = callback } - @BreakRequestBuilder - fun onUpdate(callback: (BlockPos) -> Unit) { + @BreakRequestDsl + fun onUpdate(callback: SafeContext.(BlockPos) -> Unit) { request.onUpdate = callback } - @BreakRequestBuilder - fun onStop(callback: (BlockPos) -> Unit) { + @BreakRequestDsl + fun onStop(callback: SafeContext.(BlockPos) -> Unit) { request.onStop = callback } - @BreakRequestBuilder - fun onCancel(callback: (BlockPos) -> Unit) { + @BreakRequestDsl + fun onCancel(callback: SafeContext.(BlockPos) -> Unit) { request.onCancel = callback } - @BreakRequestBuilder - fun onItemDrop(callback: (ItemEntity) -> Unit) { + @BreakRequestDsl + fun onItemDrop(callback: SafeContext.(ItemEntity) -> Unit) { request.onItemDrop = callback } - @BreakRequestBuilder - fun onReBreakStart(callback: (BlockPos) -> Unit) { + @BreakRequestDsl + fun onReBreakStart(callback: SafeContext.(BlockPos) -> Unit) { request.onReBreakStart = callback } - @BreakRequestBuilder - fun onReBreak(callback: (BlockPos) -> Unit) { + @BreakRequestDsl + fun onReBreak(callback: SafeContext.(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/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/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/material/transfer/transaction/QuickCraftTransaction.kt b/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryAction.kt similarity index 62% rename from src/main/kotlin/com/lambda/interaction/material/transfer/transaction/QuickCraftTransaction.kt rename to src/main/kotlin/com/lambda/interaction/request/inventory/InventoryAction.kt index 45a31cb6b..5297cc62f 100644 --- a/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/QuickCraftTransaction.kt +++ b/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryAction.kt @@ -15,17 +15,13 @@ * along with this program. If not, see . */ -package com.lambda.interaction.material.transfer.transaction +package com.lambda.interaction.request.inventory -import com.lambda.interaction.material.transfer.InventoryTransaction +import com.lambda.context.SafeContext -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" +sealed interface InventoryAction { + val action: SafeContext.() -> Unit - enum class Mode { - Single, Split - } + 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/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..df97b9888 100644 --- a/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryManager.kt +++ b/src/main/kotlin/com/lambda/interaction/request/inventory/InventoryManager.kt @@ -18,41 +18,194 @@ 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.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.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 object InventoryManager : RequestHandler( 1, TickEvent.Pre, TickEvent.Input.Pre, TickEvent.Input.Post, - TickEvent.Player.Post, - onOpen = { activeRequest?.let { processRequest(it) } } + TickEvent.Player.Post ), Logger { - var activeRequest: InventoryRequest? = null + private var actions = mutableListOf() + + 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 + private var actionsThisTick = 0 override val logger = inventoryManagerLogger override fun load(): String { super.load() + listen(priority = Int.MIN_VALUE) { + if (avoidDesync) indexInventoryChanges() + if (++secondCounter >= 20) { + secondCounter = 0 + actionsThisSecond = 0 + } + actionsThisTick = 0 + actions.clear() + } + return "Loaded Inventory Manager" } override fun AutomatedSafeContext.handleRequest(request: InventoryRequest) { - TODO("Not yet implemented") + 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 + + if (request.fresh) populateFrom(request) + + PlaceManager.logger.debug("Processing request", request) + + screenHandler = player.currentScreenHandler + val iterator = actions.iterator() + while (iterator.hasNext()) { + if (actionsThisSecond + 1 > maxActionsThisSecond && !request.mustPerform) break + iterator.next().action(this) + if (avoidDesync) indexInventoryChanges() + actionsThisTick++ + actionsThisSecond++ + iterator.remove() + } + + if (actions.isEmpty()) { + request.done = true + request.onComplete?.invoke(this) + } + + if (actionsThisTick > 0) activeThisTick = true } - fun SafeContext.processRequest(request: InventoryRequest) { + private fun populateFrom(request: InventoryRequest) { + PlaceManager.logger.debug("Populating from request", request) + actions = request.actions.toMutableList() + maxActionsThisSecond = request.inventoryConfig.actionsPerSecond + alteredSlots.setDecayTime(AutomationConfig.desyncTimeout * 50L) + } + + 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 = player.playerScreenHandler.getSlot(packet.slot).stack + 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() 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..8893ee390 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,30 @@ 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( - automated: Automated +@DslMarker +private annotation class InvRequestDsl + +class InventoryRequest private constructor( + val actions: List, + val settleForLess: Boolean, + val mustPerform: Boolean, + automated: Automated, + val onComplete: (SafeContext.() -> Unit)? ) : 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 +49,134 @@ 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() + var onComplete: (SafeContext.() -> Unit)? = null + + @InvRequestDsl + fun click(slotId: Int, button: Int, actionType: SlotActionType) { + InventoryAction.Inventory { clickSlot(slotId, button, actionType) }.addToActions() + } + + @InvRequestDsl + fun pickFromInventory(slotId: Int) { + InventoryAction.Inventory { + clickSlot(slotId, player.inventory.selectedSlot, SlotActionType.SWAP) + }.addToActions() + } + + @InvRequestDsl + fun dropItemInHand(entireStack: Boolean = true) { + InventoryAction.Inventory { player.dropSelectedItem(entireStack) }.addToActions() + } + + @InvRequestDsl + fun swapHands() { + 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) + connection.sendPacket( + PlayerActionC2SPacket( + PlayerActionC2SPacket.Action.SWAP_ITEM_WITH_OFFHAND, + BlockPos.ORIGIN, + Direction.DOWN + ) + ) + }.addToActions() + } + + @InvRequestDsl + fun clickCreativeStack(stack: ItemStack, slotId: Int) { + InventoryAction.Inventory { interaction.clickCreativeStack(stack, slotId) }.addToActions() + } + + @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) + } + + @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 { 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, onComplete) } } 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/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 e1d262a13..ed3de1265 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 @@ -163,7 +162,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) { @@ -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) @@ -352,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 @@ -376,7 +379,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) @@ -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/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/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/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 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 } 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..d20786aaa 100644 --- a/src/main/kotlin/com/lambda/util/player/SlotUtils.kt +++ b/src/main/kotlin/com/lambda/util/player/SlotUtils.kt @@ -29,20 +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) } - }