diff --git a/gradle.properties b/gradle.properties index daf920f..9d248a3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,5 +9,5 @@ forgeVersion=14.23.5.2860 mappingsChannel=stable mappingsVersion=39-1.12 -kotlinVersion=1.7.0 +kotlinVersion=1.7.10 kotlinxCoroutinesVersion=1.6.2 \ No newline at end of file diff --git a/src/main/kotlin/HighwayTools.kt b/src/main/kotlin/HighwayTools.kt index 00c83e8..de58da2 100644 --- a/src/main/kotlin/HighwayTools.kt +++ b/src/main/kotlin/HighwayTools.kt @@ -6,6 +6,7 @@ import com.lambda.client.event.listener.listener import com.lambda.client.module.Category import com.lambda.client.plugin.api.PluginModule import com.lambda.client.setting.settings.impl.collection.CollectionSetting +import com.lambda.client.util.Bind import com.lambda.client.util.items.shulkerList import com.lambda.client.util.threads.* import net.minecraft.block.Block @@ -24,7 +25,8 @@ import trombone.Trombone.active import trombone.Trombone.tick import trombone.Trombone.onDisable import trombone.Trombone.onEnable -import trombone.handler.Packet.handlePacket +import trombone.refactor.pathfinding.Navigator +import trombone.refactor.task.TaskProcessor /** * @author Avanatiker @@ -53,6 +55,16 @@ object HighwayTools : PluginModule( "minecraft:barrier" ) + private val defaultEjectList = linkedSetOf( + "minecraft:grass", + "minecraft:dirt", + "minecraft:netherrack", + "minecraft:gravel", + "minecraft:sand", + "minecraft:stone", + "minecraft:cobblestone" + ) + // blueprint val mode by setting("Mode", Structure.HIGHWAY, { page == Page.BLUEPRINT }, description = "Choose the structure") val width by setting("Width", 6, 1..11, 1, { page == Page.BLUEPRINT }, description = "Sets the width of blueprint", unit = " blocks") @@ -71,13 +83,15 @@ object HighwayTools : PluginModule( private val fillerMatSaved = setting("FillerMat", "minecraft:netherrack", { false }) private val foodItem = setting("FoodItem", "minecraft:golden_apple", { false }) val ignoreBlocks = setting(CollectionSetting("IgnoreList", defaultIgnoreBlocks, { false })) + val ejectList = setting(CollectionSetting("Eject List", defaultEjectList)) // behavior val maxReach by setting("Max Reach", 4.9f, 1.0f..7.0f, 0.1f, { page == Page.BEHAVIOR }, description = "Sets the range of the blueprint. Decrease when tasks fail!", unit = " blocks") - val multiBuilding by setting("Shuffle Tasks", false, { page == Page.PLACING }, description = "Only activate when working with several players") val rubberbandTimeout by setting("Rubberband Timeout", 50, 5..100, 5, { page == Page.BEHAVIOR }, description = "Timeout for pausing after a lag") val taskTimeout by setting("Task Timeout", 8, 0..20, 1, { page == Page.BEHAVIOR }, description = "Timeout for waiting for the server to try again", unit = " ticks") val moveSpeed by setting("Packet Move Speed", 0.2f, 0.0f..1.0f, 0.01f, { page == Page.BEHAVIOR }, description = "Maximum player velocity per tick", unit = "m/t") + val movementStrategy by setting("Movement Strategy", Navigator.EnumMoveStrategy.PROPAGATE, { page == Page.BEHAVIOR }, description = "Sets the movement strategy") + val taskStrategy by setting("Task Selection Strategy", TaskProcessor.EnumTaskSequenceStrategy.ORIGIN, { page == Page.BEHAVIOR }, description = "Choose the strategy for task selection") // mining val breakDelay by setting("Break Delay", 1, 1..20, 1, { page == Page.MINING }, description = "Sets the delay ticks between break tasks", unit = " ticks") @@ -85,7 +99,6 @@ object HighwayTools : PluginModule( val interactionLimit by setting("Interaction Limit", 20, 1..100, 1, { page == Page.MINING }, description = "Set the interaction limit per second", unit = " interactions/s") val multiBreak by setting("Multi Break", true, { page == Page.MINING }, description = "Breaks multiple instant breaking blocks intersecting with view vector") val packetFlood by setting("Packet Flood", false, { page == Page.MINING }, description = "Exploit for faster packet breaks. Sends START and STOP packet on same tick.") - val instantMine by setting("Ender Chest Instant Mine", false, { page == Page.MINING && packetFlood }, description = "Instant mine NCP exploit") // placing val placeDelay by setting("Place Delay", 3, 1..20, 1, { page == Page.PLACING }, description = "Sets the delay ticks between placement tasks") @@ -99,18 +112,20 @@ object HighwayTools : PluginModule( val searchEChest by setting("Search Ender Chest", false, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "Allow access to your ender chest") val leaveEmptyShulkers by setting("Leave Empty Shulkers", true, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "Does not break empty shulkers") val grindObsidian by setting("Grind Obsidian", true, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "Destroy Ender Chests to obtain Obsidian") + val pickupRadius by setting("Pickup radius", 8, 1..50, 1, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "Sets the radius for pickup", unit = " blocks") val fastFill by setting("Fast Fill", true, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "Moves as many item stacks to inventory as possible") val keepFreeSlots by setting("Free Slots", 1, 0..30, 1, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "How many inventory slots are untouched on refill", unit = " slots") - val preferEnderChests by setting("Prefer Ender Chests", false, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "Prevent using raw material shulkers") + val lockSlotHotkey by setting("Lock Slot Hotkey", Bind(), { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "Sets the hotkey for locking a slot") val manageFood by setting("Manage Food", true, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "Choose to manage food") - val saveMaterial by setting("Save Material", 12, 0..64, 1, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "How many material blocks are saved") - val saveTools by setting("Save Tools", 1, 0..36, 1, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "How many tools are saved") - val saveEnder by setting("Save Ender Chests", 1, 0..64, 1, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "How many ender chests are saved") - val saveFood by setting("Save Food", 1, 0..64, 1, { page == Page.STORAGE_MANAGEMENT && manageFood && storageManagement}, description = "How many food items are saved") + val leastMaterial by setting("Least Material", 12, 0..64, 1, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "How many material blocks are saved") + val leastTools by setting("Least Tools", 1, 0..36, 1, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "How many tools are saved") + val leastEnder by setting("Least Ender Chests", 1, 0..64, 1, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "How many ender chests are saved") + val leastFood by setting("Least Food", 1, 0..64, 1, { page == Page.STORAGE_MANAGEMENT && manageFood && storageManagement}, description = "How many food items are saved") val minDistance by setting("Min Container Distance", 1.5, 0.0..3.0, 0.1, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "Avoid player movement collision with placement.", unit = " blocks") val disableMode by setting("Disable Mode", DisableMode.NONE, { page == Page.STORAGE_MANAGEMENT }, description = "Choose action when bot is out of materials or tools") val usingProxy by setting("Proxy", false, { disableMode == DisableMode.LOGOUT && page == Page.STORAGE_MANAGEMENT }, description = "Enable this if you are using a proxy to call the given command") val proxyCommand by setting("Proxy Command", "/dc", { usingProxy && disableMode == DisableMode.LOGOUT && page == Page.STORAGE_MANAGEMENT }, description = "Command to be sent to log out") + val preferEnderChests by setting("Prefer Ender Chests", false, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "Prevent using raw material shulkers") // render val anonymizeStats by setting("Anonymize", false, { page == Page.RENDER }, description = "Censors all coordinates in HUD and Chat") diff --git a/src/main/kotlin/trombone/Trombone.kt b/src/main/kotlin/trombone/Trombone.kt index 23d13ba..0cbe59f 100644 --- a/src/main/kotlin/trombone/Trombone.kt +++ b/src/main/kotlin/trombone/Trombone.kt @@ -3,8 +3,8 @@ package trombone import HighwayTools import HighwayTools.info import com.lambda.client.event.SafeClientEvent -import trombone.BaritoneHelper.resetBaritone -import trombone.BaritoneHelper.setupBaritone +import trombone.refactor.pathfinding.BaritoneHelper.resetBaritone +import trombone.refactor.pathfinding.BaritoneHelper.setupBaritone import trombone.IO.pauseCheck import trombone.IO.printDisable import trombone.IO.printEnable diff --git a/src/main/kotlin/trombone/handler/Container.kt b/src/main/kotlin/trombone/handler/Container.kt index ebeba04..39a7d35 100644 --- a/src/main/kotlin/trombone/handler/Container.kt +++ b/src/main/kotlin/trombone/handler/Container.kt @@ -6,10 +6,10 @@ import HighwayTools.material import HighwayTools.maxReach import HighwayTools.minDistance import HighwayTools.preferEnderChests -import HighwayTools.saveEnder -import HighwayTools.saveFood -import HighwayTools.saveMaterial -import HighwayTools.saveTools +import HighwayTools.leastEnder +import HighwayTools.leastFood +import HighwayTools.leastMaterial +import HighwayTools.leastTools import HighwayTools.searchEChest import com.lambda.client.commons.extension.ceilToInt import com.lambda.client.event.SafeClientEvent @@ -68,12 +68,12 @@ object Container { if (grindObsidian && item.block == Blocks.OBSIDIAN) { // Case 2: desired item is Obsidian and grinding E-Chests is allowed - if (player.inventorySlots.countBlock(Blocks.ENDER_CHEST) <= saveEnder) { + if (player.inventorySlots.countBlock(Blocks.ENDER_CHEST) <= leastEnder) { handleRestock(Blocks.ENDER_CHEST.item) return } - if (player.inventorySlots.countBlock(Blocks.ENDER_CHEST) > saveEnder) { + if (player.inventorySlots.countBlock(Blocks.ENDER_CHEST) > leastEnder) { if (grindCycles > 0) { getRemotePos()?.let { pos -> containerTask = BlockTask(pos, TaskState.PLACE, Blocks.ENDER_CHEST, item = Blocks.OBSIDIAN.item) @@ -113,7 +113,7 @@ object Container { } private fun SafeClientEvent.dispatchEnderChest(item: Item) { - if (player.inventorySlots.countBlock(Blocks.ENDER_CHEST) > saveEnder) { + if (player.inventorySlots.countBlock(Blocks.ENDER_CHEST) > leastEnder) { getRemotePos()?.let { pos -> containerTask = BlockTask(pos, TaskState.PLACE, Blocks.ENDER_CHEST, item = item) containerTask.itemID = Blocks.OBSIDIAN.id @@ -215,10 +215,10 @@ object Container { private fun SafeClientEvent.insufficientMaterial(item: Item): String { val itemCount = player.inventorySlots.countItem(item) var message = "" - if (saveMaterial > 0 && item == material.item) message += insufficientMaterialPrint(itemCount, saveMaterial, material.localizedName) - if (saveEnder > 0 && item.block == Blocks.ENDER_CHEST) message += insufficientMaterialPrint(itemCount, saveEnder, Blocks.ENDER_CHEST.localizedName) - if (saveTools > 0 && item == Items.DIAMOND_PICKAXE) message += insufficientMaterialPrint(itemCount, saveTools, "Diamond Pickaxe(s)") - if (saveFood > 0 && item == Items.GOLDEN_APPLE) message += insufficientMaterialPrint(itemCount, saveFood, "Golden Apple(s)") + if (leastMaterial > 0 && item == material.item) message += insufficientMaterialPrint(itemCount, leastMaterial, material.localizedName) + if (leastEnder > 0 && item.block == Blocks.ENDER_CHEST) message += insufficientMaterialPrint(itemCount, leastEnder, Blocks.ENDER_CHEST.localizedName) + if (leastTools > 0 && item == Items.DIAMOND_PICKAXE) message += insufficientMaterialPrint(itemCount, leastTools, "Diamond Pickaxe(s)") + if (leastFood > 0 && item == Items.GOLDEN_APPLE) message += insufficientMaterialPrint(itemCount, leastFood, "Golden Apple(s)") return "$message\nTo continue anyways, set setting in ${TextFormatting.GRAY}Storage Management > Save ${TextFormatting.RESET} to zero." } diff --git a/src/main/kotlin/trombone/handler/Inventory.kt b/src/main/kotlin/trombone/handler/Inventory.kt index 3cd6838..f10d382 100644 --- a/src/main/kotlin/trombone/handler/Inventory.kt +++ b/src/main/kotlin/trombone/handler/Inventory.kt @@ -2,8 +2,8 @@ package trombone.handler import HighwayTools.keepFreeSlots import HighwayTools.material -import HighwayTools.saveMaterial -import HighwayTools.saveTools +import HighwayTools.leastMaterial +import HighwayTools.leastTools import HighwayTools.storageManagement import com.lambda.client.event.SafeClientEvent import com.lambda.client.manager.managers.PlayerInventoryManager @@ -38,11 +38,6 @@ object Inventory { val packetLimiter = ConcurrentLinkedDeque() - @Suppress("UNUSED") - enum class RotationMode { - OFF, SPOOF - } - fun SafeClientEvent.updateRotation() { if (lastHitVec == Vec3d.ZERO) return val rotation = getRotationTo(lastHitVec) @@ -103,7 +98,7 @@ object Inventory { private fun SafeClientEvent.findMaterial(blockTask: BlockTask): Block { return if (blockTask.targetBlock == material) { - if (player.inventorySlots.countBlock(material) > saveMaterial) { + if (player.inventorySlots.countBlock(material) > leastMaterial) { material } else { restockFallback(blockTask) @@ -121,7 +116,7 @@ object Inventory { } if (possibleMaterials.isEmpty()) { - if (player.inventorySlots.countBlock(material) > saveMaterial) { + if (player.inventorySlots.countBlock(material) > leastMaterial) { material } else { restockFallback(blockTask) @@ -143,7 +138,7 @@ object Inventory { } fun SafeClientEvent.swapOrMoveBestTool(blockTask: BlockTask): Boolean { - if (player.inventorySlots.countItem(Items.DIAMOND_PICKAXE) <= saveTools) { + if (player.inventorySlots.countItem(Items.DIAMOND_PICKAXE) <= leastTools) { return if (containerTask.taskState == TaskState.DONE && storageManagement) { handleRestock(Items.DIAMOND_PICKAXE) false diff --git a/src/main/kotlin/trombone/handler/Liquid.kt b/src/main/kotlin/trombone/handler/Liquid.kt index 8c32517..fd4f618 100644 --- a/src/main/kotlin/trombone/handler/Liquid.kt +++ b/src/main/kotlin/trombone/handler/Liquid.kt @@ -29,9 +29,7 @@ object Liquid { if (world.getBlockState(neighbourPos).block !is BlockLiquid) continue - if (player.distanceTo(neighbourPos) > maxReach - || getNeighbourSequence(neighbourPos, placementSearch, maxReach, !illegalPlacements).isEmpty() - ) { + if (getNeighbourSequence(neighbourPos, placementSearch, maxReach, !illegalPlacements).isEmpty()) { if (debugLevel == IO.DebugLevel.VERBOSE) { LambdaMod.LOG.info("[Trombone] Skipping liquid block at ${neighbourPos.asString()} due to distance") } @@ -45,7 +43,7 @@ object Liquid { updateLiquidTask(it) } ?: run { val newTask = BlockTask(neighbourPos, TaskState.LIQUID, fillerMat) - val blueprintTask = BlueprintTask(fillerMat, isFiller = true, isSupport = false) + val blueprintTask = BlueprintTask(fillerMat, isFiller = true) addTask(newTask, blueprintTask) } diff --git a/src/main/kotlin/trombone/handler/Packet.kt b/src/main/kotlin/trombone/handler/Packet.kt deleted file mode 100644 index ffea242..0000000 --- a/src/main/kotlin/trombone/handler/Packet.kt +++ /dev/null @@ -1,81 +0,0 @@ -package trombone.handler - -import com.lambda.client.LambdaMod -import com.lambda.client.event.SafeClientEvent -import com.lambda.client.util.items.hotbarSlots -import net.minecraft.init.Blocks -import net.minecraft.network.Packet -import net.minecraft.network.play.server.SPacketBlockChange -import net.minecraft.network.play.server.SPacketOpenWindow -import net.minecraft.network.play.server.SPacketPlayerPosLook -import net.minecraft.network.play.server.SPacketSetSlot -import net.minecraft.network.play.server.SPacketWindowItems -import trombone.blueprint.BlueprintGenerator.isInsideBlueprint -import trombone.Pathfinder.rubberbandTimer -import trombone.Statistics.durabilityUsages -import trombone.handler.Container.containerTask -import trombone.task.TaskManager.tasks -import trombone.task.TaskState - -object Packet { - fun SafeClientEvent.handlePacket(packet: Packet<*>) { - when (packet) { - is SPacketBlockChange -> { - val pos = packet.blockPosition - if (!isInsideBlueprint(pos)) return - - val prev = world.getBlockState(pos).block - val new = packet.getBlockState().block - - if (prev != new) { - val task = if (pos == containerTask.blockPos) { - containerTask - } else { - tasks[pos] ?: return - } - - when (task.taskState) { - TaskState.PENDING_BREAK, TaskState.BREAKING -> { - if (new == Blocks.AIR) { - task.updateState(TaskState.BROKEN) - } - } - TaskState.PENDING_PLACE -> { - if (new != Blocks.AIR - && (task.targetBlock == new || task.isFiller) - ) { - task.updateState(TaskState.PLACED) - } - } - else -> { - // Ignored - } - } - } - } - is SPacketPlayerPosLook -> { - rubberbandTimer.reset() - } - is SPacketOpenWindow -> { - if (containerTask.taskState != TaskState.DONE && - packet.guiId == "minecraft:shulker_box" && containerTask.isShulker() || - packet.guiId == "minecraft:container" && !containerTask.isShulker()) { - containerTask.isOpen = true - } - } - is SPacketWindowItems -> { - if (containerTask.isOpen) containerTask.isLoaded = true - } - is SPacketSetSlot -> { - val currentStack = player.hotbarSlots[player.inventory.currentItem].stack - if (packet.slot == player.inventory.currentItem + 36 - && packet.stack.item == currentStack.item - && packet.stack.itemDamage > currentStack.itemDamage - ) { - durabilityUsages += packet.stack.itemDamage - currentStack.itemDamage - } - } - else -> { /* Ignored */ } - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/trombone/refactor/PacketReceiver.kt b/src/main/kotlin/trombone/refactor/PacketReceiver.kt new file mode 100644 index 0000000..d19c8b5 --- /dev/null +++ b/src/main/kotlin/trombone/refactor/PacketReceiver.kt @@ -0,0 +1,62 @@ +package trombone.refactor + +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.util.items.hotbarSlots +import net.minecraft.network.Packet +import net.minecraft.network.play.server.SPacketBlockChange +import net.minecraft.network.play.server.SPacketOpenWindow +import net.minecraft.network.play.server.SPacketPlayerPosLook +import net.minecraft.network.play.server.SPacketSetSlot +import net.minecraft.network.play.server.SPacketWindowItems +import trombone.Pathfinder.rubberbandTimer +import trombone.Statistics.durabilityUsages +import trombone.refactor.task.tasks.BreakTask +import trombone.refactor.task.tasks.PlaceTask +import trombone.refactor.task.TaskProcessor +import trombone.refactor.task.tasks.RestockTask + +object PacketReceiver { + fun SafeClientEvent.handlePacket(packet: Packet<*>) { + when (packet) { + is SPacketBlockChange -> { + TaskProcessor.tasks[packet.blockPosition]?.let { + with(it) { + if (currentBlockState != packet.getBlockState()) { + when (it) { + is PlaceTask -> it.acceptPacketState(packet.getBlockState()) + is BreakTask -> it.acceptPacketState(packet.getBlockState()) + } + } + } + } + } + is SPacketPlayerPosLook -> { + rubberbandTimer.reset() + } + is SPacketOpenWindow -> { + TaskProcessor.getContainerTasks().filterIsInstance().forEach { + with(it) { + acceptPacketOpen(packet) + } + } + } + is SPacketWindowItems -> { + TaskProcessor.getContainerTasks().filterIsInstance().forEach { + with(it) { + acceptPacketLoaded() + } + } + } + is SPacketSetSlot -> { + val currentStack = player.hotbarSlots[player.inventory.currentItem].stack + if (packet.slot == player.inventory.currentItem + 36 + && packet.stack.item == currentStack.item + && packet.stack.itemDamage > currentStack.itemDamage + ) { + durabilityUsages += packet.stack.itemDamage - currentStack.itemDamage + } + } + else -> { /* Ignored */ } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/trombone/refactor/Renderer.kt b/src/main/kotlin/trombone/refactor/Renderer.kt new file mode 100644 index 0000000..66491ef --- /dev/null +++ b/src/main/kotlin/trombone/refactor/Renderer.kt @@ -0,0 +1,79 @@ +package trombone.refactor + +import HighwayTools.aFilled +import HighwayTools.aOutline +import HighwayTools.filled +import HighwayTools.outline +import HighwayTools.popUp +import HighwayTools.popUpSpeed +import HighwayTools.showCurrentPos +import HighwayTools.thickness +import com.lambda.client.util.color.ColorHolder +import com.lambda.client.util.graphics.ESPRenderer +import com.lambda.client.util.graphics.GeometryMasks +import net.minecraft.init.Blocks +import trombone.Pathfinder +import trombone.refactor.task.BuildTask +import trombone.refactor.task.TaskProcessor +import trombone.refactor.task.tasks.BreakTask +import trombone.refactor.task.tasks.DoneTask +import trombone.refactor.task.tasks.PlaceTask +import kotlin.math.PI +import kotlin.math.cos +import kotlin.math.sin + +object Renderer { + private val renderer = ESPRenderer() + + fun renderWorld() { + renderer.clear() + renderer.aFilled = if (filled) aFilled else 0 + renderer.aOutline = if (outline) aOutline else 0 + renderer.thickness = thickness + val currentTime = System.currentTimeMillis() + + if (showCurrentPos) renderer.add(Pathfinder.currentBlockPos, ColorHolder(255, 255, 255)) + + TaskProcessor.tasks.values.forEach { + if (it.targetBlock == Blocks.AIR && it is DoneTask) return@forEach + if (it.toRemove) { + addToRenderer(it, currentTime, true) + } else { + addToRenderer(it, currentTime) + } + } + renderer.render(false) + } + + private fun addToRenderer(buildTask: BuildTask, currentTime: Long, reverse: Boolean = false) { + var aabb = buildTask.aabb + + if (popUp) { + val age = (currentTime - buildTask.timeStamp).toDouble() + val ageX = age.coerceAtMost(popUpSpeed * PI / 2) / popUpSpeed + + val sizeFactor = if (reverse) cos(ageX) else sin(ageX) + + aabb = buildTask.aabb.shrink((0.5 - sizeFactor * 0.5)) + } + + renderer.add(aabb, buildTask.color) + + when (buildTask) { + is BreakTask -> { + buildTask.breakInfo?.let { breakInfo -> + GeometryMasks.FACEMAP[breakInfo.side]?.let { geoSide -> + renderer.add(aabb, buildTask.color.multiply(1.1f), geoSide) + } + } + } + is PlaceTask -> { + buildTask.placeInfo?.let { placeInfo -> + GeometryMasks.FACEMAP[placeInfo.side]?.let { geoSide -> + renderer.add(aabb, buildTask.color.multiply(1.1f), geoSide) + } + } + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/trombone/BaritoneHelper.kt b/src/main/kotlin/trombone/refactor/pathfinding/BaritoneHelper.kt similarity index 97% rename from src/main/kotlin/trombone/BaritoneHelper.kt rename to src/main/kotlin/trombone/refactor/pathfinding/BaritoneHelper.kt index c8a4f44..e4696ad 100644 --- a/src/main/kotlin/trombone/BaritoneHelper.kt +++ b/src/main/kotlin/trombone/refactor/pathfinding/BaritoneHelper.kt @@ -1,4 +1,4 @@ -package trombone +package trombone.refactor.pathfinding import HighwayTools.goalRender import com.lambda.client.util.BaritoneUtils diff --git a/src/main/kotlin/trombone/refactor/pathfinding/BaritonePathfindingProcess.kt b/src/main/kotlin/trombone/refactor/pathfinding/BaritonePathfindingProcess.kt new file mode 100644 index 0000000..bad3c58 --- /dev/null +++ b/src/main/kotlin/trombone/refactor/pathfinding/BaritonePathfindingProcess.kt @@ -0,0 +1,19 @@ +package trombone.refactor.pathfinding + +import baritone.api.process.IBaritoneProcess + +object BaritonePathfindingProcess : IBaritoneProcess { + override fun isTemporary() = true + + override fun priority() = 2.0 + + override fun onLostControl() { + Navigator.onLostControl() + } + + override fun displayName0() = Navigator.processInfo() + + override fun isActive() = HighwayTools.isActive() + + override fun onTick(p0: Boolean, p1: Boolean) = Navigator.currentPathingCommand +} \ No newline at end of file diff --git a/src/main/kotlin/trombone/refactor/pathfinding/MovementStrategy.kt b/src/main/kotlin/trombone/refactor/pathfinding/MovementStrategy.kt new file mode 100644 index 0000000..a03127f --- /dev/null +++ b/src/main/kotlin/trombone/refactor/pathfinding/MovementStrategy.kt @@ -0,0 +1,9 @@ +package trombone.refactor.pathfinding + +import baritone.api.process.PathingCommand +import com.lambda.client.event.SafeClientEvent + +interface MovementStrategy { + fun SafeClientEvent.generatePathingCommand(): PathingCommand + fun onLostControl() +} \ No newline at end of file diff --git a/src/main/kotlin/trombone/refactor/pathfinding/Navigator.kt b/src/main/kotlin/trombone/refactor/pathfinding/Navigator.kt new file mode 100644 index 0000000..ceea633 --- /dev/null +++ b/src/main/kotlin/trombone/refactor/pathfinding/Navigator.kt @@ -0,0 +1,48 @@ +package trombone.refactor.pathfinding + +import HighwayTools.movementStrategy +import baritone.api.process.PathingCommand +import baritone.api.process.PathingCommandType +import com.lambda.client.event.SafeClientEvent +import net.minecraft.util.math.BlockPos +import trombone.refactor.pathfinding.strategies.BackAndForthStrategy +import trombone.refactor.pathfinding.strategies.DistanceStrategy +import trombone.refactor.pathfinding.strategies.PropagateStrategy +import trombone.refactor.pathfinding.strategies.StayStrategy + +object Navigator { + private var origin: BlockPos = BlockPos.ORIGIN + var strategy: MovementStrategy = movementStrategy.getInstance() + var currentPathingCommand = PathingCommand(null, PathingCommandType.REQUEST_PAUSE) + + enum class EnumMoveStrategy { + PROPAGATE { override fun getInstance() = PropagateStrategy }, + STAY { override fun getInstance() = StayStrategy }, + DISTANCE { override fun getInstance() = DistanceStrategy }, + BACK_AND_FORTH { override fun getInstance() = BackAndForthStrategy }; + + abstract fun getInstance(): MovementStrategy + } + + inline fun changeStrategy() { + strategy = T::class.java.newInstance() + } + + fun setStrategyToDefault() { + strategy = movementStrategy.getInstance() + } + + private fun SafeClientEvent.executeStrategy() { + with(strategy) { + currentPathingCommand = generatePathingCommand() + } + } + + fun onLostControl() { + strategy.onLostControl() + } + + fun processInfo(): String { + return "Trombone: ${currentPathingCommand.commandType.name}@${currentPathingCommand.goal ?: origin}" + } +} \ No newline at end of file diff --git a/src/main/kotlin/trombone/refactor/pathfinding/strategies/BackAndForthStrategy.kt b/src/main/kotlin/trombone/refactor/pathfinding/strategies/BackAndForthStrategy.kt new file mode 100644 index 0000000..235c9ef --- /dev/null +++ b/src/main/kotlin/trombone/refactor/pathfinding/strategies/BackAndForthStrategy.kt @@ -0,0 +1,17 @@ +package trombone.refactor.pathfinding.strategies + +import baritone.api.process.PathingCommand +import baritone.api.process.PathingCommandType +import com.lambda.client.event.SafeClientEvent +import net.minecraft.util.math.BlockPos +import trombone.refactor.pathfinding.MovementStrategy + +object BackAndForthStrategy : MovementStrategy { + override fun SafeClientEvent.generatePathingCommand(): PathingCommand { + return PathingCommand(null, PathingCommandType.REQUEST_PAUSE) + } + + override fun onLostControl() { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/src/main/kotlin/trombone/refactor/pathfinding/strategies/DistanceStrategy.kt b/src/main/kotlin/trombone/refactor/pathfinding/strategies/DistanceStrategy.kt new file mode 100644 index 0000000..def9093 --- /dev/null +++ b/src/main/kotlin/trombone/refactor/pathfinding/strategies/DistanceStrategy.kt @@ -0,0 +1,17 @@ +package trombone.refactor.pathfinding.strategies + +import baritone.api.process.PathingCommand +import baritone.api.process.PathingCommandType +import com.lambda.client.event.SafeClientEvent +import net.minecraft.util.math.BlockPos +import trombone.refactor.pathfinding.MovementStrategy + +object DistanceStrategy : MovementStrategy { + override fun SafeClientEvent.generatePathingCommand(): PathingCommand { + return PathingCommand(null, PathingCommandType.REQUEST_PAUSE) + } + + override fun onLostControl() { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/src/main/kotlin/trombone/refactor/pathfinding/strategies/PickupStrategy.kt b/src/main/kotlin/trombone/refactor/pathfinding/strategies/PickupStrategy.kt new file mode 100644 index 0000000..103c949 --- /dev/null +++ b/src/main/kotlin/trombone/refactor/pathfinding/strategies/PickupStrategy.kt @@ -0,0 +1,16 @@ +package trombone.refactor.pathfinding.strategies + +import baritone.api.process.PathingCommand +import baritone.api.process.PathingCommandType +import com.lambda.client.event.SafeClientEvent +import trombone.refactor.pathfinding.MovementStrategy + +object PickupStrategy : MovementStrategy { + override fun SafeClientEvent.generatePathingCommand(): PathingCommand { + return PathingCommand(null, PathingCommandType.REQUEST_PAUSE) + } + + override fun onLostControl() { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/src/main/kotlin/trombone/refactor/pathfinding/strategies/PropagateStrategy.kt b/src/main/kotlin/trombone/refactor/pathfinding/strategies/PropagateStrategy.kt new file mode 100644 index 0000000..c8f9c3b --- /dev/null +++ b/src/main/kotlin/trombone/refactor/pathfinding/strategies/PropagateStrategy.kt @@ -0,0 +1,16 @@ +package trombone.refactor.pathfinding.strategies + +import baritone.api.process.PathingCommand +import baritone.api.process.PathingCommandType +import com.lambda.client.event.SafeClientEvent +import trombone.refactor.pathfinding.MovementStrategy + +object PropagateStrategy : MovementStrategy { + override fun SafeClientEvent.generatePathingCommand(): PathingCommand { + return PathingCommand(null, PathingCommandType.REQUEST_PAUSE) + } + + override fun onLostControl() { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/src/main/kotlin/trombone/refactor/pathfinding/strategies/RestockStrategy.kt b/src/main/kotlin/trombone/refactor/pathfinding/strategies/RestockStrategy.kt new file mode 100644 index 0000000..c3a7062 --- /dev/null +++ b/src/main/kotlin/trombone/refactor/pathfinding/strategies/RestockStrategy.kt @@ -0,0 +1,16 @@ +package trombone.refactor.pathfinding.strategies + +import baritone.api.process.PathingCommand +import baritone.api.process.PathingCommandType +import com.lambda.client.event.SafeClientEvent +import trombone.refactor.pathfinding.MovementStrategy + +object RestockStrategy : MovementStrategy { + override fun SafeClientEvent.generatePathingCommand(): PathingCommand { + return PathingCommand(null, PathingCommandType.REQUEST_PAUSE) + } + + override fun onLostControl() { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/src/main/kotlin/trombone/refactor/pathfinding/strategies/ScaffoldStrategy.kt b/src/main/kotlin/trombone/refactor/pathfinding/strategies/ScaffoldStrategy.kt new file mode 100644 index 0000000..ec140fd --- /dev/null +++ b/src/main/kotlin/trombone/refactor/pathfinding/strategies/ScaffoldStrategy.kt @@ -0,0 +1,16 @@ +package trombone.refactor.pathfinding.strategies + +import baritone.api.process.PathingCommand +import baritone.api.process.PathingCommandType +import com.lambda.client.event.SafeClientEvent +import trombone.refactor.pathfinding.MovementStrategy + +object ScaffoldStrategy : MovementStrategy { + override fun SafeClientEvent.generatePathingCommand(): PathingCommand { + return PathingCommand(null, PathingCommandType.REQUEST_PAUSE) + } + + override fun onLostControl() { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/src/main/kotlin/trombone/refactor/pathfinding/strategies/StayStrategy.kt b/src/main/kotlin/trombone/refactor/pathfinding/strategies/StayStrategy.kt new file mode 100644 index 0000000..d0266bb --- /dev/null +++ b/src/main/kotlin/trombone/refactor/pathfinding/strategies/StayStrategy.kt @@ -0,0 +1,17 @@ +package trombone.refactor.pathfinding.strategies + +import baritone.api.process.PathingCommand +import baritone.api.process.PathingCommandType +import com.lambda.client.event.SafeClientEvent +import net.minecraft.util.math.BlockPos +import trombone.refactor.pathfinding.MovementStrategy + +object StayStrategy : MovementStrategy { + override fun SafeClientEvent.generatePathingCommand(): PathingCommand { + return PathingCommand(null, PathingCommandType.REQUEST_PAUSE) + } + + override fun onLostControl() { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/src/main/kotlin/trombone/refactor/task/BuildTask.kt b/src/main/kotlin/trombone/refactor/task/BuildTask.kt new file mode 100644 index 0000000..363f834 --- /dev/null +++ b/src/main/kotlin/trombone/refactor/task/BuildTask.kt @@ -0,0 +1,104 @@ +package trombone.refactor.task + +import HighwayTools.ejectList +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.util.color.ColorHolder +import com.lambda.client.util.math.CoordinateConverter.asString +import net.minecraft.block.Block +import net.minecraft.block.BlockLiquid +import net.minecraft.block.state.IBlockState +import net.minecraft.init.Items +import net.minecraft.inventory.Slot +import net.minecraft.item.Item +import net.minecraft.util.math.AxisAlignedBB +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Vec3d + +abstract class BuildTask( + val blockPos: BlockPos, + val targetBlock: Block, + var isFillerTask: Boolean = false, + var isContainerTask: Boolean = false, + var isSupportTask: Boolean = false +) { + abstract var priority: Int // low value is high priority + abstract val timeout: Int + abstract var threshold: Int + abstract val color: ColorHolder + abstract var hitVec3d: Vec3d? + + val timeStamp = System.currentTimeMillis() + var toRemove = false + var timeTicking = 0 + var timesFailed = 0 + var aabb = AxisAlignedBB(blockPos) + private var debugInfo: MutableList> = mutableListOf() + + val slotToUseForPlace: Slot? = null + val desiredItem: Item = Items.AIR + var destroyAfterPlace = false + var pickupAfterBreak = false + val itemIdToPickup = 0 + + /** + * checks if requirements are met for the task + * @return [Boolean] is true when all requirements are met + */ + abstract fun SafeClientEvent.isValid(): Boolean + + /** + * checks for changed circumstances + * @return [Boolean] is true when changes were made and next task needs to be reconsidered + */ + abstract fun SafeClientEvent.update(): Boolean + + /** + * executes the task + */ + abstract fun SafeClientEvent.execute() + + fun SafeClientEvent.runUpdate(): Boolean { + aabb = axisAlignedBB + debugInfo = gatherAllDebugInfo() + + return update() + } + + fun SafeClientEvent.runExecute() { + timeTicking++ + execute() + } + + private fun SafeClientEvent.gatherAllDebugInfo(): MutableList> { + val info: MutableList> = mutableListOf() + + info.add(Pair("blockPos", blockPos.asString())) + info.add(Pair("targetBlock", targetBlock.localizedName)) + info.add(Pair("isFillerTask", isFillerTask.toString())) + info.add(Pair("isContainerTask", isContainerTask.toString())) + info.add(Pair("isSupportTask", isSupportTask.toString())) + info.add(Pair("priority", priority.toString())) + info.add(Pair("timeout", timeout.toString())) + info.add(Pair("threshold", threshold.toString())) + info.add(Pair("color", color.toString())) + info.add(Pair("timeStamp", timeStamp.toString())) + info.add(Pair("timeTicking", timeTicking.toString())) + + info.addAll(gatherDebugInfo()) + + return info + } + + override fun toString() = "${javaClass.name} blockPos=(${blockPos.asString()}) targetBlock=${targetBlock.localizedName}${if (isFillerTask) " isFillerTask" else ""}${if (isContainerTask) " isContainerTask" else ""}${if (isSupportTask) " isSupportTask" else ""} ${gatherInfoToString()}" + + /* helper functions */ + val SafeClientEvent.currentBlockState: IBlockState get() = world.getBlockState(blockPos) + val SafeClientEvent.currentBlock: Block get() = currentBlockState.block + val SafeClientEvent.isLiquidBlock get() = currentBlock is BlockLiquid + private val SafeClientEvent.axisAlignedBB: AxisAlignedBB get() = currentBlockState.getSelectedBoundingBox(world, blockPos).also { aabb = it } + fun itemIsInEjectList(item: Item) = item.registryName.toString() in ejectList + + abstract fun gatherInfoToString(): String + + abstract fun SafeClientEvent.gatherDebugInfo(): MutableList> +} \ No newline at end of file diff --git a/src/main/kotlin/trombone/refactor/task/RestockHandler.kt b/src/main/kotlin/trombone/refactor/task/RestockHandler.kt new file mode 100644 index 0000000..4c26c8a --- /dev/null +++ b/src/main/kotlin/trombone/refactor/task/RestockHandler.kt @@ -0,0 +1,65 @@ +package trombone.refactor.task + +import HighwayTools.preferEnderChests +import HighwayTools.storageManagement +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.util.items.block +import com.lambda.client.util.items.inventorySlots +import net.minecraft.block.state.IBlockState +import net.minecraft.init.Blocks +import net.minecraft.inventory.ItemStackHelper +import net.minecraft.inventory.Slot +import net.minecraft.item.Item +import net.minecraft.item.ItemShulkerBox +import net.minecraft.item.ItemStack +import net.minecraft.util.NonNullList +import trombone.IO.disableError + +object RestockHandler { + inline fun handleRestock() { + + } + + inline fun SafeClientEvent.handleRestock() { + handleRestock(T::class.java.newInstance()) + } + + fun SafeClientEvent.handleRestock(item: Item) { + if (!storageManagement) { + disableError("Storage management is disabled. Can't restock ${item.registryName}") + return + } + + if (preferEnderChests && item.block == Blocks.OBSIDIAN) { + grindObsidian() + return + } + + getShulkerWith(player.inventorySlots, item) + } + + fun getShulkerWith(slots: List, item: Item) = + slots.filter { + it.stack.item is ItemShulkerBox && getShulkerData(it.stack, item) > 0 + }.minByOrNull { + getShulkerData(it.stack, item) + } + + private fun getShulkerData(stack: ItemStack, item: Item): Int { + if (stack.item !is ItemShulkerBox) return 0 + + stack.tagCompound?.let { tagCompound -> + if (tagCompound.hasKey("BlockEntityTag", 10)) { + val blockEntityTag = tagCompound.getCompoundTag("BlockEntityTag") + + if (blockEntityTag.hasKey("Items", 9)) { + val shulkerInventory = NonNullList.withSize(27, ItemStack.EMPTY) + ItemStackHelper.loadAllItems(blockEntityTag, shulkerInventory) + return shulkerInventory.count { it.item == item } + } + } + } + + return 0 + } +} \ No newline at end of file diff --git a/src/main/kotlin/trombone/refactor/task/TaskFactory.kt b/src/main/kotlin/trombone/refactor/task/TaskFactory.kt new file mode 100644 index 0000000..1e12c2a --- /dev/null +++ b/src/main/kotlin/trombone/refactor/task/TaskFactory.kt @@ -0,0 +1,21 @@ +package trombone.refactor.task + +import com.lambda.client.event.SafeClientEvent +import net.minecraft.block.Block +import net.minecraft.util.math.BlockPos + +object TaskFactory { + enum class StructurePreset { + HIGHWAY, TUNNEL, FLAT, STEALTH_TUNNEL + } + + fun SafeClientEvent.populateTasks() { + + } + + fun isInsideBlueprint(blockPos: BlockPos) = true + + fun isInsideBlueprintBuilding(blockPos: BlockPos) = true + + data class BlueprintTask(val targetBlock: Block, val isFiller: Boolean = false, val isSupport: Boolean = false) +} \ No newline at end of file diff --git a/src/main/kotlin/trombone/refactor/task/TaskProcessor.kt b/src/main/kotlin/trombone/refactor/task/TaskProcessor.kt new file mode 100644 index 0000000..997fc06 --- /dev/null +++ b/src/main/kotlin/trombone/refactor/task/TaskProcessor.kt @@ -0,0 +1,94 @@ +package trombone.refactor.task + +import HighwayTools.interactionLimit +import HighwayTools.taskStrategy +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.manager.managers.PlayerPacketManager.sendPlayerPacket +import com.lambda.client.util.math.RotationUtils.getRotationTo +import net.minecraft.util.math.BlockPos +import trombone.Trombone.module +import trombone.refactor.task.sequence.TaskSequenceStrategy +import trombone.refactor.task.sequence.strategies.LeftToRightStrategy +import trombone.refactor.task.sequence.strategies.OriginStrategy +import trombone.refactor.task.sequence.strategies.RandomStrategy +import trombone.refactor.task.tasks.DoneTask +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.ConcurrentLinkedDeque + +object TaskProcessor { + val tasks = ConcurrentHashMap() + private var currentTask: BuildTask? = null + var waitTicks = 0 + var waitPenalty = 0 + var taskSequenceStrategy = taskStrategy.getInstance() + + val packetLimiter = ConcurrentLinkedDeque() + + enum class EnumTaskSequenceStrategy { + ORIGIN { override fun getInstance() = OriginStrategy }, + RANDOM { override fun getInstance() = RandomStrategy }, + LEFT_TO_RIGHT { override fun getInstance() = LeftToRightStrategy }; + + abstract fun getInstance(): TaskSequenceStrategy + } + + fun SafeClientEvent.doTick() { + /* update old tasks */ + tasks.values.forEach { + with(it) { + runUpdate() + } + } + + /* wait given delay */ + if (waitTicks > 0) { + waitTicks-- + return + } + + /* get task with the highest priority based on selection strategy */ + val containerTasks = getContainerTasks() + + currentTask = if (containerTasks.isEmpty()) { + taskSequenceStrategy.getNextTask(tasks.values.toList()) + } else { + taskSequenceStrategy.getNextTask(containerTasks) + } + + currentTask?.let { currentTask -> + with(currentTask) { + if (isValid() && !runUpdate()) { + runExecute() + + hitVec3d?.let { + module.sendPlayerPacket { + rotate(getRotationTo(it)) + } + } + } + } + } + } + + /* allows tasks to convert into different types */ + inline fun BuildTask.convertTo( + isSupportTask: Boolean = this.isSupportTask, + isFillerTask: Boolean = this.isFillerTask, + isContainerTask: Boolean = this.isContainerTask + ) { + val newTask = T::class.java.getDeclaredConstructor().newInstance(blockPos, targetBlock) + newTask.isSupportTask = isSupportTask + newTask.isFillerTask = isFillerTask + newTask.isContainerTask = isContainerTask + + tasks[blockPos] = newTask + } + + fun addTask(buildTask: BuildTask) { + tasks[buildTask.blockPos] = buildTask + } + + fun getContainerTasks() = tasks.values.filter { it.isContainerTask } + + val interactionLimitNotReached = packetLimiter.size < interactionLimit +} diff --git a/src/main/kotlin/trombone/refactor/task/sequence/TaskSequenceStrategy.kt b/src/main/kotlin/trombone/refactor/task/sequence/TaskSequenceStrategy.kt new file mode 100644 index 0000000..ab24041 --- /dev/null +++ b/src/main/kotlin/trombone/refactor/task/sequence/TaskSequenceStrategy.kt @@ -0,0 +1,7 @@ +package trombone.refactor.task.sequence + +import trombone.refactor.task.BuildTask + +interface TaskSequenceStrategy { + fun getNextTask(tasks: List): BuildTask? +} \ No newline at end of file diff --git a/src/main/kotlin/trombone/refactor/task/sequence/strategies/LeftToRightStrategy.kt b/src/main/kotlin/trombone/refactor/task/sequence/strategies/LeftToRightStrategy.kt new file mode 100644 index 0000000..3f2379a --- /dev/null +++ b/src/main/kotlin/trombone/refactor/task/sequence/strategies/LeftToRightStrategy.kt @@ -0,0 +1,10 @@ +package trombone.refactor.task.sequence.strategies + +import trombone.refactor.task.BuildTask +import trombone.refactor.task.sequence.TaskSequenceStrategy + +object LeftToRightStrategy : TaskSequenceStrategy { + override fun getNextTask(tasks: List): BuildTask? { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/src/main/kotlin/trombone/refactor/task/sequence/strategies/OriginStrategy.kt b/src/main/kotlin/trombone/refactor/task/sequence/strategies/OriginStrategy.kt new file mode 100644 index 0000000..b19fa92 --- /dev/null +++ b/src/main/kotlin/trombone/refactor/task/sequence/strategies/OriginStrategy.kt @@ -0,0 +1,12 @@ +package trombone.refactor.task.sequence.strategies + +import trombone.refactor.task.BuildTask +import trombone.refactor.task.sequence.TaskSequenceStrategy + +object OriginStrategy : TaskSequenceStrategy { + override fun getNextTask(tasks: List): BuildTask? { + val sortedTasks = tasks.sortedWith(compareBy { it.priority }) + + return sortedTasks.firstOrNull() + } +} \ No newline at end of file diff --git a/src/main/kotlin/trombone/refactor/task/sequence/strategies/RandomStrategy.kt b/src/main/kotlin/trombone/refactor/task/sequence/strategies/RandomStrategy.kt new file mode 100644 index 0000000..eda58ea --- /dev/null +++ b/src/main/kotlin/trombone/refactor/task/sequence/strategies/RandomStrategy.kt @@ -0,0 +1,10 @@ +package trombone.refactor.task.sequence.strategies + +import trombone.refactor.task.BuildTask +import trombone.refactor.task.sequence.TaskSequenceStrategy + +object RandomStrategy : TaskSequenceStrategy { + override fun getNextTask(tasks: List): BuildTask? { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/src/main/kotlin/trombone/refactor/task/tasks/BreakTask.kt b/src/main/kotlin/trombone/refactor/task/tasks/BreakTask.kt new file mode 100644 index 0000000..8a9750d --- /dev/null +++ b/src/main/kotlin/trombone/refactor/task/tasks/BreakTask.kt @@ -0,0 +1,414 @@ +package trombone.refactor.task.tasks + +import HighwayTools.breakDelay +import HighwayTools.fillerMat +import HighwayTools.ignoreBlocks +import HighwayTools.illegalPlacements +import HighwayTools.maxReach +import HighwayTools.miningSpeedFactor +import HighwayTools.multiBreak +import HighwayTools.packetFlood +import HighwayTools.leastTools +import HighwayTools.pickupRadius +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.util.EntityUtils.getDroppedItems +import com.lambda.client.util.color.ColorHolder +import com.lambda.client.util.items.* +import com.lambda.client.util.math.VectorUtils +import com.lambda.client.util.math.isInSight +import com.lambda.client.util.threads.defaultScope +import com.lambda.client.util.world.* +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import net.minecraft.block.Block +import net.minecraft.block.BlockAir +import net.minecraft.block.BlockFire +import net.minecraft.block.BlockLiquid +import net.minecraft.block.state.IBlockState +import net.minecraft.enchantment.EnchantmentHelper +import net.minecraft.init.Enchantments +import net.minecraft.item.ItemPickaxe +import net.minecraft.item.ItemStack +import net.minecraft.network.play.client.CPacketPlayerDigging +import net.minecraft.util.EnumFacing +import net.minecraft.util.EnumHand +import net.minecraft.util.math.AxisAlignedBB +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Vec3d +import trombone.Statistics +import trombone.Trombone +import trombone.Trombone.module +import trombone.refactor.task.RestockHandler.handleRestock +import trombone.refactor.task.BuildTask +import trombone.refactor.task.TaskFactory +import trombone.refactor.task.TaskProcessor +import trombone.refactor.task.TaskProcessor.addTask +import trombone.refactor.task.TaskProcessor.convertTo +import trombone.refactor.task.TaskProcessor.interactionLimitNotReached +import trombone.refactor.task.TaskProcessor.packetLimiter +import trombone.refactor.task.TaskProcessor.waitPenalty +import kotlin.math.ceil + +class BreakTask( + blockPos: BlockPos, + targetBlock: Block, + isFillerTask: Boolean = false, + isContainerTask: Boolean = false, + isSupportTask: Boolean = false +) : BuildTask(blockPos, targetBlock, isFillerTask, isContainerTask, isSupportTask) { + private var state = State.INVALID + private var ticksMined = 0 + private var alreadyCheckedMultiBreak = false + var breakInfo: BreakInfo? = null + private var toolToUse = ItemStack.EMPTY + var collectPos: BlockPos? = null + + override var priority = 1 + state.prioOffset + override val timeout = 20 + override var threshold = 1 + override val color = state.colorHolder + override var hitVec3d: Vec3d? = null + + private enum class State(val colorHolder: ColorHolder, val prioOffset: Int) { + INVALID(ColorHolder(22, 0, 0), 20), + VALID(ColorHolder(222, 0, 0), 0), + GET_TOOL(ColorHolder(252, 3, 207), 0), + BREAK(ColorHolder(222, 0, 0), 0), + BREAKING(ColorHolder(240, 222, 60), -100), + PENDING(ColorHolder(42, 0, 0), 20), + ACCEPTED(ColorHolder(53, 222, 66), 0), + PICKUP(ColorHolder(), 0) + } + + override fun SafeClientEvent.isValid() = + player.onGround + && interactionLimitNotReached + && breakInfo != null + + override fun SafeClientEvent.update(): Boolean { + var wasUpdated = true + + if (isValid() && state == State.INVALID) state = State.VALID +// priority = 1 + state.prioOffset + threshold = 1 + ticksNeeded + hitVec3d = breakInfo?.hitVec3d + + when { + shouldBeIgnored() || isIllegal() -> { + convertTo() + } + currentBlock is BlockAir -> { + convertTo() + } + currentBlock == targetBlock -> { + convertTo() + } + isLiquidBlock -> { + convertTo() + } + state.ordinal < 2 && updateLiquidNeighbours() -> { + /* Change already done */ + } + state.ordinal < 2 && gatherBreakInformation() -> { + /* Change already done */ + } + else -> { + wasUpdated = false + } + } + + return wasUpdated + } + + override fun SafeClientEvent.execute() { + when (state) { + State.INVALID -> { + /* Wait */ + } + State.VALID -> { + state = State.GET_TOOL + execute() + } + State.GET_TOOL -> { + if (equipBestTool(this@BreakTask)) { + state = State.BREAK + execute() + } + } + State.BREAK -> { + breakInfo?.let { + TaskProcessor.waitTicks = breakDelay + if (it.isInstant) { + state = State.PENDING + + sendMiningPackets(it) + + if (multiBreak && !alreadyCheckedMultiBreak) breakIntersectingBlocks() + + defaultScope.launch { + delay(threshold * 50L) + + if (state == State.PENDING) { + state = State.INVALID + waitPenalty++ + } + } + } else { + state = State.BREAKING + + sendMiningPackets(it) + } + + ticksMined++ + } ?: run { + state = State.INVALID + } + } + State.BREAKING -> { + breakInfo?.let { + sendMiningPackets(it) + ticksMined++ + } ?: run { + state = State.INVALID + } + } + State.PENDING -> { + /* Wait */ + } + State.ACCEPTED -> { + Statistics.totalBlocksBroken++ + Statistics.simpleMovingAverageBreaks.add(System.currentTimeMillis()) + + TaskProcessor.tasks.values.filterIsInstance().forEach { + it.timesFailed = 0 + } + + if (currentBlock is BlockAir) { + if (isContainerTask && pickupAfterBreak) { + state = State.PICKUP + execute() + return + } + + convertTo() + } else { + convertTo() + } + } + State.PICKUP -> { + getCollectingPosition()?.let { goal -> + collectPos = goal + + player.inventorySlots.firstByStack { itemIsInEjectList(it.item) }?.let { + if (timeTicking > 20) { + throwAllInSlot(module, it) + timeTicking = 0 + } + } + return + } + + collectPos = null + convertTo() + } + } + } + + private fun SafeClientEvent.getLiquidNeighbours(): List = + EnumFacing.values().filter { + it != EnumFacing.DOWN + && world.getBlockState(blockPos.offset(it)).block is BlockLiquid + } + + + private fun SafeClientEvent.updateLiquidNeighbours(): Boolean { + val newTasks = getLiquidNeighbours().filter { !TaskProcessor.tasks.contains(blockPos.offset(it)) } + + if (newTasks.isNotEmpty()) { + newTasks.forEach { + addTask(PlaceTask(blockPos.offset(it), fillerMat, isFillerTask = true)) + } + return true + } + + return false + } + + private fun SafeClientEvent.gatherBreakInformation(): Boolean { + if (currentBlock is BlockFire) { + getNeighbour(blockPos, 1, maxReach, !illegalPlacements)?.let { + breakInfo = BreakInfo(it.pos, it.side, getHitVec(it.pos, it.side), start = true) + return false + } ?: run { + convertTo(isFillerTask = true) + return true + } + } + + getMiningSide(blockPos)?.let { side -> + val eyePos = player.getPositionEyes(1.0f) + val hitVec = getHitVec(blockPos, side) + + if (eyePos.distanceTo(hitVec) > maxReach) { + return false + } + + breakInfo = when { + ticksNeeded == 1 || ticksMined == 0 || player.capabilities.isCreativeMode -> BreakInfo(blockPos, side, hitVec, start = true, isInstant = true) + ticksMined < ticksNeeded -> BreakInfo(blockPos, side, hitVec) + else -> BreakInfo(blockPos, side, hitVec, stop = true) + } + } + return false + } + + private fun SafeClientEvent.breakIntersectingBlocks() { + breakInfo?.let { breakInfo -> + val eyePos = player.getPositionEyes(1.0f) + val viewVec = breakInfo.hitVec3d.subtract(eyePos).normalize() + + TaskProcessor.tasks.values + .filterIsInstance() + .filter { it != this@BreakTask + && (ticksNeeded == 1 || player.capabilities.isCreativeMode) + && getLiquidNeighbours().isEmpty() + }.forEach { foundInstantTask -> + val rayTraceResult = AxisAlignedBB(blockPos).isInSight(eyePos, viewVec, range = maxReach.toDouble(), tolerance = 0.0) ?: return@forEach + + foundInstantTask.breakInfo = BreakInfo(foundInstantTask.blockPos, rayTraceResult.sideHit, breakInfo.hitVec3d, start = true, isInstant = true) + foundInstantTask.state = State.BREAK + foundInstantTask.alreadyCheckedMultiBreak = true + + with(foundInstantTask) { + execute() + } + } + } + } + + private fun SafeClientEvent.sendMiningPackets(breakInfo: BreakInfo) { + if (breakInfo.start || packetFlood) { + sendAction(breakInfo, CPacketPlayerDigging.Action.START_DESTROY_BLOCK) + } + if (breakInfo.abort) { + sendAction(breakInfo, CPacketPlayerDigging.Action.ABORT_DESTROY_BLOCK) + } + if (breakInfo.stop || packetFlood) { + sendAction(breakInfo, CPacketPlayerDigging.Action.STOP_DESTROY_BLOCK) + } + player.swingArm(EnumHand.MAIN_HAND) + } + + private fun SafeClientEvent.sendAction(breakInfo: BreakInfo, action: CPacketPlayerDigging.Action) { + connection.sendPacket(CPacketPlayerDigging(action, breakInfo.pos, breakInfo.side)) + packetLimiter.add(System.currentTimeMillis()) + } + + private fun SafeClientEvent.equipBestTool(breakTask: BreakTask): Boolean { + if (player.inventorySlots.countItem() <= leastTools) { + handleRestock() + return false + } + + with(breakTask) { + getSlotWithBestTool(currentBlockState)?.let { slotFrom -> + toolToUse = slotFrom.stack + + slotFrom.toHotbarSlotOrNull()?.let { + swapToSlot(it) + } ?: run { + val slotTo = player.hotbarSlots.firstEmpty()?.hotbarSlot ?: 0 + moveToHotbar(Trombone.module, slotFrom.slotNumber, slotTo) + } + return true + } + } + + return false + } + + private fun SafeClientEvent.getSlotWithBestTool(blockState: IBlockState) = + player.inventorySlots.asReversed().maxByOrNull { + val stack = it.stack + if (stack.isEmpty) { + 0.0f + } else { + var speed = stack.getDestroySpeed(blockState) + + if (speed > 1.0f) { + val efficiency = EnchantmentHelper.getEnchantmentLevel(Enchantments.EFFICIENCY, stack) + if (efficiency > 0) { + speed += efficiency * efficiency + 1.0f + } + } + + speed + } + } + + private fun SafeClientEvent.getCollectingPosition(): BlockPos? { + getDroppedItems(itemIdToPickup, range = pickupRadius.toFloat()) + .minByOrNull { player.getDistance(it) } + ?.positionVector + ?.let { itemVec -> + return VectorUtils.getBlockPosInSphere(itemVec, pickupRadius.toFloat()).asSequence() + .filter { pos -> + world.isAirBlock(pos.up()) + && world.isPlaceable(pos) + && !world.getBlockState(pos.down()).isReplaceable + } + .sortedWith( + compareBy { + it.distanceSqToCenter(itemVec.x, itemVec.y, itemVec.z) + }.thenBy { + it.y + } + ).firstOrNull() + } + return null + } + + fun acceptPacketState(packetBlockState: IBlockState) { + if ((state == State.PENDING || state == State.BREAKING) + && packetBlockState.block is BlockAir + ) { + state = State.ACCEPTED + } + } + + private val SafeClientEvent.relativeHardness: Float + get() = currentBlockState.getPlayerRelativeBlockHardness(player, world, blockPos) + + private val SafeClientEvent.ticksNeeded: Int + get() = ceil((1 / relativeHardness) * miningSpeedFactor).toInt() + + private fun SafeClientEvent.shouldBeIgnored() = ignoreBlocks.contains(currentBlock.registryName.toString()) + && !isContainerTask + && !TaskFactory.isInsideBlueprintBuilding(blockPos) + + private fun SafeClientEvent.isIllegal() = currentBlockState.getBlockHardness(world, blockPos) == -1.0f + + override fun gatherInfoToString() = "state=${state.name} ticksMined=$ticksMined${if (alreadyCheckedMultiBreak) " alreadyCheckedMultiBreak" else ""}${if (!toolToUse.isEmpty) toolToUse.displayName else ""}" + + override fun SafeClientEvent.gatherDebugInfo(): MutableList> { + val data: MutableList> = mutableListOf() + + data.add(Pair("state", state.name)) + data.add(Pair("ticksMined", ticksMined.toString())) + data.add(Pair("checkedMB", alreadyCheckedMultiBreak.toString())) + breakInfo?.let { data.add(Pair("breakInfo", it.toString())) } + if (!toolToUse.isEmpty) data.add(Pair("toolToUse", toolToUse.displayName)) + + return data + } + + data class BreakInfo( + val pos: BlockPos, + val side: EnumFacing, + val hitVec3d: Vec3d, + var start: Boolean = false, + var stop: Boolean = false, + val abort: Boolean = false, + val isInstant: Boolean = false, + ) +} \ No newline at end of file diff --git a/src/main/kotlin/trombone/refactor/task/tasks/DoneTask.kt b/src/main/kotlin/trombone/refactor/task/tasks/DoneTask.kt new file mode 100644 index 0000000..0f21506 --- /dev/null +++ b/src/main/kotlin/trombone/refactor/task/tasks/DoneTask.kt @@ -0,0 +1,34 @@ +package trombone.refactor.task.tasks + +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.util.color.ColorHolder +import net.minecraft.block.Block +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Vec3d +import trombone.refactor.task.BuildTask +import trombone.refactor.task.TaskProcessor + +class DoneTask(blockPos: BlockPos, + targetBlock: Block, + isFiller: Boolean = false, + isContainer: Boolean = false, + isSupport: Boolean = false +) : BuildTask(blockPos, targetBlock, isFiller, isContainer, isSupport) { + override var priority = 0 + override val timeout = 0 + override var threshold = 0 + override val color = ColorHolder(50, 50, 50) + override var hitVec3d: Vec3d? = null + + override fun SafeClientEvent.isValid() = true + + override fun SafeClientEvent.update() = true + + override fun SafeClientEvent.execute() { + TaskProcessor.tasks.remove(blockPos) + } + + override fun gatherInfoToString() = "" + + override fun SafeClientEvent.gatherDebugInfo(): MutableList> = mutableListOf() +} \ No newline at end of file diff --git a/src/main/kotlin/trombone/refactor/task/tasks/PlaceTask.kt b/src/main/kotlin/trombone/refactor/task/tasks/PlaceTask.kt new file mode 100644 index 0000000..ef1b59a --- /dev/null +++ b/src/main/kotlin/trombone/refactor/task/tasks/PlaceTask.kt @@ -0,0 +1,255 @@ +package trombone.refactor.task.tasks + +import HighwayTools.dynamicDelay +import HighwayTools.ejectList +import HighwayTools.fakeSounds +import HighwayTools.fillerMat +import HighwayTools.illegalPlacements +import HighwayTools.material +import HighwayTools.maxReach +import HighwayTools.placeDelay +import HighwayTools.placementSearch +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.util.color.ColorHolder +import com.lambda.client.util.items.* +import com.lambda.client.util.threads.defaultScope +import com.lambda.client.util.world.* +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import net.minecraft.block.Block +import net.minecraft.block.Block.getBlockFromName +import net.minecraft.block.BlockLiquid +import net.minecraft.block.state.IBlockState +import net.minecraft.inventory.Slot +import net.minecraft.network.play.client.CPacketEntityAction +import net.minecraft.network.play.client.CPacketPlayerTryUseItemOnBlock +import net.minecraft.util.EnumFacing +import net.minecraft.util.EnumHand +import net.minecraft.util.SoundCategory +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Vec3d +import trombone.Pathfinder +import trombone.Pathfinder.moveState +import trombone.Pathfinder.shouldBridge +import trombone.Statistics +import trombone.Trombone.module +import trombone.refactor.task.RestockHandler.handleRestock +import trombone.refactor.task.BuildTask +import trombone.refactor.task.TaskProcessor +import trombone.refactor.task.TaskProcessor.addTask +import trombone.refactor.task.TaskProcessor.convertTo +import trombone.refactor.task.TaskProcessor.interactionLimitNotReached +import trombone.refactor.task.TaskProcessor.waitPenalty + +class PlaceTask( + blockPos: BlockPos, + targetBlock: Block, + isFillerTask: Boolean = false, + isContainerTask: Boolean = false, + isSupportTask: Boolean = false +) : BuildTask(blockPos, targetBlock, isFillerTask, isContainerTask, isSupportTask) { + private var state = State.INVALID + var placeInfo: PlaceInfo? = null + private val SafeClientEvent.isLiquidSource get() = isLiquidBlock && currentBlockState.getValue(BlockLiquid.LEVEL) == 0 + private val SafeClientEvent.placeInfoSequence get() = getNeighbourSequence(blockPos, placementSearch, maxReach, !illegalPlacements) + + override var priority = 2 + override val timeout = 20 + override var threshold = 20 + override val color = state.colorHolder + override var hitVec3d: Vec3d? = null + + enum class State(val colorHolder: ColorHolder, val prioOffset: Int) { + INVALID(ColorHolder(16, 74, 94), 10), + VALID(ColorHolder(35, 188, 254), 0), + GET_BLOCK(ColorHolder(252, 3, 207), 0), + PLACE(ColorHolder(35, 188, 254), 0), + PENDING(ColorHolder(42, 0, 0), 20), + ACCEPTED(ColorHolder(53, 222, 66), 0) + } + + override fun SafeClientEvent.isValid() = + interactionLimitNotReached + && placeInfo != null + && world.checkNoEntityCollision(aabb, player) + + override fun SafeClientEvent.update(): Boolean { + var wasUpdated = true + + priority = 2 + state.prioOffset + if (isLiquidSource) 10 else 0 + if (placeInfoSequence.size == 1) placeInfo = placeInfoSequence.firstOrNull() + hitVec3d = placeInfo?.hitVec + + if (isValid() + && state == State.INVALID + ) state = State.VALID + + when { + currentBlock == targetBlock -> { + convertTo() + } + isSupportTask && alreadyIsSupported() -> { + convertTo() + } + isFillerTask && alreadyIsFilled() -> { + convertTo() + } + !currentBlockState.isReplaceable -> { + convertTo() + } + else -> { + wasUpdated = false + } + } + + return wasUpdated + } + + override fun SafeClientEvent.execute() { + when (state) { + State.INVALID -> { + if (placeInfoSequence.isNotEmpty()) { + placeInfoSequence.filter { + !TaskProcessor.tasks.containsKey(it.placedPos) + }.forEach { + addTask(PlaceTask(it.placedPos, fillerMat, isFillerTask = true)) + } + return + } + + if (shouldBridge()) moveState = Pathfinder.MovementState.BRIDGE + } + State.VALID -> { + state = State.GET_BLOCK + execute() + } + State.GET_BLOCK -> { + if (equipBlockToPlace()) { + state = State.PLACE + execute() + } + } + State.PLACE -> { + placeInfo?.let { + TaskProcessor.waitTicks = placeDelay + waitPenalty + + state = State.PENDING + + sendPlacingPackets(it.placedPos, it.side, getHitVecOffset(it.side)) + } + } + State.PENDING -> { + /* Wait */ + } + State.ACCEPTED -> { + Statistics.totalBlocksPlaced++ + Statistics.simpleMovingAveragePlaces.add(System.currentTimeMillis()) + + if (fakeSounds) { + val soundType = currentBlock.soundType + world.playSound( + player, + blockPos, + soundType.placeSound, + SoundCategory.BLOCKS, + (soundType.getVolume() + 1.0f) / 2.0f, + soundType.getPitch() * 0.8f + ) + } + + TaskProcessor.tasks.values.filterIsInstance().forEach { + it.timesFailed = 0 + } + + waitPenalty /= 2 + + if (isContainerTask) { + if (destroyAfterPlace) { + convertTo() + } else { + convertTo() + } + return + } + + convertTo() + } + } + } + + private fun SafeClientEvent.equipBlockToPlace(): Boolean { + if (isContainerTask && slotToUseForPlace != null) { + swapToSlotOrMove(slotToUseForPlace) + } + + if (swapToItemOrMove(module, targetBlock.item)) { + return true + } + + if (isFillerTask) { + ejectList.forEach { stringName -> + getBlockFromName(stringName)?.let { + if (swapToBlockOrMove(module, it)) return true + } + } + } + + handleRestock(targetBlock.item) + return false + } + + private fun SafeClientEvent.swapToSlotOrMove(slot: Slot) { + slot.toHotbarSlotOrNull()?.let { + swapToSlot(it) + } ?: run { + val slotTo = player.hotbarSlots.firstEmpty()?.hotbarSlot ?: 0 + moveToHotbar(module, slot.slotNumber, slotTo) + } + } + + private fun SafeClientEvent.sendPlacingPackets(blockPos: BlockPos, side: EnumFacing, hitVecOffset: Vec3d) { + val isBlackListed = currentBlock in blockBlacklist + + if (isBlackListed) { + connection.sendPacket(CPacketEntityAction(player, CPacketEntityAction.Action.START_SNEAKING)) + } + + val placePacket = CPacketPlayerTryUseItemOnBlock(blockPos, side, EnumHand.MAIN_HAND, hitVecOffset.x.toFloat(), hitVecOffset.y.toFloat(), hitVecOffset.z.toFloat()) + connection.sendPacket(placePacket) + player.swingArm(EnumHand.MAIN_HAND) + + if (isBlackListed) { + connection.sendPacket(CPacketEntityAction(player, CPacketEntityAction.Action.START_SNEAKING)) + } + + defaultScope.launch { + delay(threshold * 50L) + + if (state == State.PENDING) { + state = State.INVALID + if (dynamicDelay) waitPenalty++ + } + } + } + + fun acceptPacketState(packetBlockState: IBlockState) { + if (state == State.PENDING + && (targetBlock == packetBlockState.block || isFillerTask) + ) { + state = State.ACCEPTED + } + } + + private fun SafeClientEvent.alreadyIsFilled() = world.getCollisionBox(blockPos) != null + private fun SafeClientEvent.alreadyIsSupported() = world.getBlockState(blockPos.up()).block == material + + override fun gatherInfoToString() = "state=$state" + + override fun SafeClientEvent.gatherDebugInfo(): MutableList> { + val data: MutableList> = mutableListOf() + + data.add(Pair("state", state.name)) + + return data + } +} \ No newline at end of file diff --git a/src/main/kotlin/trombone/refactor/task/tasks/RestockTask.kt b/src/main/kotlin/trombone/refactor/task/tasks/RestockTask.kt new file mode 100644 index 0000000..c7fbfd1 --- /dev/null +++ b/src/main/kotlin/trombone/refactor/task/tasks/RestockTask.kt @@ -0,0 +1,289 @@ +package trombone.refactor.task.tasks + +import HighwayTools.anonymizeStats +import HighwayTools.debugLevel +import HighwayTools.fastFill +import HighwayTools.keepFreeSlots +import HighwayTools.leaveEmptyShulkers +import HighwayTools.material +import HighwayTools.mode +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.manager.managers.PlayerInventoryManager +import com.lambda.client.manager.managers.PlayerInventoryManager.addInventoryTask +import com.lambda.client.util.color.ColorHolder +import com.lambda.client.util.items.* +import com.lambda.client.util.math.CoordinateConverter.asString +import com.lambda.client.util.math.VectorUtils.toVec3dCenter +import com.lambda.client.util.text.MessageSendHelper +import com.lambda.client.util.world.getHitVec +import com.lambda.client.util.world.getHitVecOffset +import net.minecraft.block.Block +import net.minecraft.block.BlockContainer +import net.minecraft.block.BlockShulkerBox +import net.minecraft.client.gui.inventory.GuiContainer +import net.minecraft.init.Items +import net.minecraft.inventory.ClickType +import net.minecraft.inventory.Container +import net.minecraft.inventory.Slot +import net.minecraft.item.ItemPickaxe +import net.minecraft.network.play.client.CPacketPlayerTryUseItemOnBlock +import net.minecraft.network.play.server.SPacketOpenWindow +import net.minecraft.util.EnumFacing +import net.minecraft.util.EnumHand +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Vec3d +import trombone.IO +import trombone.IO.disableError +import trombone.Trombone +import trombone.Trombone.module +import trombone.refactor.task.RestockHandler +import trombone.refactor.task.BuildTask +import trombone.refactor.task.TaskProcessor.convertTo + +class RestockTask( + blockPos: BlockPos, + targetBlock: Block +) : BuildTask(blockPos, targetBlock, false, true, false) { + private var state = State.INIT + private var stopPull = false + private var movedStuff = false + + override var priority = 3 + state.prioOffset + override val timeout = 20 + override var threshold = 200 + override val color = state.colorHolder + override var hitVec3d: Vec3d? = null + + enum class State(val colorHolder: ColorHolder, val prioOffset: Int) { + INIT(ColorHolder(252, 3, 207 ), 0), + OPEN_CONTAINER(ColorHolder(252, 3, 207), 0), + PENDING_OPEN(ColorHolder(252, 3, 207), 0), + PENDING_ITEM_LIST(ColorHolder(252, 3, 207), 0), + MOVE_ITEMS(ColorHolder(252, 3, 207), 0), + CLOSE(ColorHolder(252, 3, 207), 0) + } + + override fun SafeClientEvent.isValid() = currentBlock is BlockContainer && isContainerTask && desiredItem != Items.AIR + + override fun SafeClientEvent.update(): Boolean { + var wasUpdated = true + + hitVec3d = getHitVec(blockPos, getSideToOpen()) + + when { + currentBlock !is BlockContainer -> { + convertTo() + } + else -> { + wasUpdated = false + } + } + + return wasUpdated + } + + override fun SafeClientEvent.execute() { + when (state) { + State.INIT -> { + state = State.OPEN_CONTAINER + execute() + } + State.OPEN_CONTAINER -> { + val side = getSideToOpen() + val hitVecNormalized = getHitVecOffset(side) + + connection.sendPacket(CPacketPlayerTryUseItemOnBlock(blockPos, side, EnumHand.MAIN_HAND, hitVecNormalized.x.toFloat(), hitVecNormalized.y.toFloat(), hitVecNormalized.z.toFloat())) + player.swingArm(EnumHand.MAIN_HAND) + + state = State.PENDING_OPEN + } + State.PENDING_OPEN -> { + if (timeTicking > 20) { + state = State.OPEN_CONTAINER + timeTicking = 0 + } + } + State.PENDING_ITEM_LIST -> { + if (timeTicking > 20) { + state = State.OPEN_CONTAINER + timeTicking = 0 + } + } + State.MOVE_ITEMS -> { + if (mc.currentScreen !is GuiContainer) { + state = State.OPEN_CONTAINER + return + } + + val openContainer = player.openContainer + + if (leaveEmptyShulkers + && currentBlock is BlockShulkerBox + && !itemIsInEjectList(desiredItem) + && openContainer.getSlots(0..26).onlyContainsEjectables() + ) { + if (debugLevel != IO.DebugLevel.OFF) { + if (!anonymizeStats) { + MessageSendHelper.sendChatMessage("${module.chatName} Left empty ${targetBlock.localizedName}@(${blockPos.asString()})") + } else { + MessageSendHelper.sendChatMessage("${module.chatName} Left empty ${targetBlock.localizedName}") + } + } + + state = State.CLOSE + execute() + return + } + + val freeSlots = openContainer.getSlots(27..62).count { + it.stack.isEmpty || itemIsInEjectList(it.stack.item) + } - 1 - keepFreeSlots + + if (freeSlots < 1 || stopPull) { + state = State.CLOSE + execute() + return + } + + openContainer.getSlots(0..26).firstItem(desiredItem)?.let { + moveToInventory(openContainer, it) + stopPull = true + movedStuff = true + + if (fastFill) { + if (mode == Trombone.Structure.TUNNEL && desiredItem is ItemPickaxe) stopPull = false + if (mode != Trombone.Structure.TUNNEL && desiredItem == material.item) stopPull = false + } + return + } + + if (movedStuff) { + state = State.CLOSE + execute() + return + } + + RestockHandler.getShulkerWith(openContainer.getSlots(0..26), desiredItem)?.let { + moveToInventory(openContainer, it) + state = State.CLOSE + execute() + return + } + + disableError("No ${desiredItem.registryName} left in any container.") + } + State.CLOSE -> { + player.closeScreen() + + convertTo() + } + } + } + + private fun SafeClientEvent.getSideToOpen(): EnumFacing { + val center = blockPos.toVec3dCenter() + val diff = player.getPositionEyes(1f).subtract(center) + val normalizedVec = diff.normalize() + + return EnumFacing.getFacingFromVector(normalizedVec.x.toFloat(), normalizedVec.y.toFloat(), normalizedVec.z.toFloat()) + } + + private fun List.onlyContainsEjectables() = all { + it.stack.isEmpty || itemIsInEjectList(it.stack.item) + } + + private fun List.firstEmptyOrEjectableOrNull() = firstOrNull { + it.stack.isEmpty || itemIsInEjectList(it.stack.item) + } + + private fun SafeClientEvent.moveToInventory(openContainer: Container, originSlot: Slot) { + openContainer.getSlots(27..62).firstOrNull { + originSlot.stack.item == it.stack.item + && it.stack.count < originSlot.stack.maxStackSize - originSlot.stack.count + }?.let { + module.addInventoryTask( + PlayerInventoryManager.ClickInfo( + openContainer.windowId, + originSlot.slotNumber, + 0, + ClickType.QUICK_MOVE + ) + ) + return + } + + openContainer.getSlots(54..62).firstEmptyOrEjectableOrNull()?.let { + module.addInventoryTask( + PlayerInventoryManager.ClickInfo( + openContainer.windowId, + originSlot.slotNumber, + it.slotNumber - 54, + ClickType.SWAP + ) + ) + return + } + + openContainer.getSlots(27..53).firstEmptyOrEjectableOrNull()?.let { + module.addInventoryTask( + PlayerInventoryManager.ClickInfo( + openContainer.windowId, + 0, + it.slotNumber, + ClickType.SWAP + ), + PlayerInventoryManager.ClickInfo( + openContainer.windowId, + it.slotNumber, + 0, + ClickType.SWAP + ) + ) + return + } + + zipInventory() + } + + private fun SafeClientEvent.zipInventory() { + val compressibleStacks = player.inventorySlots.filter { comp -> + comp.stack.count < comp.stack.maxStackSize + && player.inventorySlots.countByStack { comp.stack.item == it.item } > 1 + } + + if (compressibleStacks.isEmpty()) { + disableError("Inventory full. (Considering that $keepFreeSlots slots are supposed to stay free)") + return + } + + compressibleStacks.forEach { slot -> + module.addInventoryTask( + PlayerInventoryManager.ClickInfo(slot = slot.slotNumber, type = ClickType.QUICK_MOVE) + ) + } + } + + fun acceptPacketOpen(packet: SPacketOpenWindow) { + if (state == State.PENDING_OPEN) { + if (packet.guiId == "minecraft:shulker_box" + || packet.guiId == "minecraft:container" + ) state = State.PENDING_ITEM_LIST + } + } + + fun acceptPacketLoaded() { + if (state == State.PENDING_ITEM_LIST) { + state = State.MOVE_ITEMS + } + } + + override fun gatherInfoToString() = "state=$state" + + override fun SafeClientEvent.gatherDebugInfo(): MutableList> { + val data: MutableList> = mutableListOf() + + data.add(Pair("state", state.name)) + + return data + } +} \ No newline at end of file diff --git a/src/main/kotlin/trombone/task/TaskExecutor.kt b/src/main/kotlin/trombone/task/TaskExecutor.kt index cd81e2c..532ada8 100644 --- a/src/main/kotlin/trombone/task/TaskExecutor.kt +++ b/src/main/kotlin/trombone/task/TaskExecutor.kt @@ -281,8 +281,7 @@ object TaskExecutor { Break.prePrimedPos = blockTask.blockPos Statistics.simpleMovingAveragePlaces.add(System.currentTimeMillis()) - if ( - dynamicDelay && Place.extraPlaceDelay > 0) Place.extraPlaceDelay /= 2 + if (dynamicDelay && Place.extraPlaceDelay > 0) Place.extraPlaceDelay /= 2 if (blockTask == containerTask) { if (containerTask.destroy) { diff --git a/src/main/kotlin/trombone/task/TaskManager.kt b/src/main/kotlin/trombone/task/TaskManager.kt index b1184e7..80624cd 100644 --- a/src/main/kotlin/trombone/task/TaskManager.kt +++ b/src/main/kotlin/trombone/task/TaskManager.kt @@ -9,12 +9,11 @@ import HighwayTools.manageFood import HighwayTools.material import HighwayTools.maxReach import HighwayTools.multiBuilding -import HighwayTools.saveFood -import HighwayTools.saveTools +import HighwayTools.leastFood +import HighwayTools.leastTools import HighwayTools.storageManagement import HighwayTools.width import com.lambda.client.event.SafeClientEvent -import com.lambda.client.manager.managers.PlayerInventoryManager import com.lambda.client.util.items.countItem import com.lambda.client.util.items.inventorySlots import com.lambda.client.util.items.item @@ -33,7 +32,6 @@ import net.minecraft.item.ItemFood import net.minecraft.item.ItemPickaxe import net.minecraft.util.math.AxisAlignedBB import net.minecraft.util.math.BlockPos -import net.minecraft.util.math.Vec3d import trombone.blueprint.BlueprintGenerator.blueprint import trombone.blueprint.BlueprintGenerator.generateBluePrint import trombone.blueprint.BlueprintGenerator.isInsideBlueprintBuild @@ -183,7 +181,7 @@ object TaskManager { /* Check tools */ storageManagement - && player.inventorySlots.countItem() <= saveTools -> { + && player.inventorySlots.countItem() <= leastTools -> { // TODO: ItemPickaxe support handleRestock(Items.DIAMOND_PICKAXE) } @@ -191,7 +189,7 @@ object TaskManager { /* Fulfill basic needs */ storageManagement && manageFood - && player.inventorySlots.countItem() <= saveFood -> { + && player.inventorySlots.countItem() <= leastFood -> { // TODO: ItemFood support handleRestock(food) }