diff --git a/src/main/java/me/zeroeightsix/kami/module/modules/misc/AutoObsidian.kt b/src/main/java/me/zeroeightsix/kami/module/modules/misc/AutoObsidian.kt index 8c6868166..281ca3f95 100644 --- a/src/main/java/me/zeroeightsix/kami/module/modules/misc/AutoObsidian.kt +++ b/src/main/java/me/zeroeightsix/kami/module/modules/misc/AutoObsidian.kt @@ -1,180 +1,245 @@ package me.zeroeightsix.kami.module.modules.misc +import baritone.api.pathing.goals.Goal +import baritone.api.pathing.goals.GoalNear +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import me.zeroeightsix.kami.event.Phase import me.zeroeightsix.kami.event.SafeClientEvent -import me.zeroeightsix.kami.mixin.extension.rightClickDelayTimer +import me.zeroeightsix.kami.event.events.OnUpdateWalkingPlayerEvent +import me.zeroeightsix.kami.event.events.RenderWorldEvent +import me.zeroeightsix.kami.manager.managers.PlayerPacketManager import me.zeroeightsix.kami.module.Module import me.zeroeightsix.kami.process.AutoObsidianProcess import me.zeroeightsix.kami.setting.ModuleConfig.setting -import me.zeroeightsix.kami.util.BaritoneUtils -import me.zeroeightsix.kami.util.EntityUtils -import me.zeroeightsix.kami.util.EntityUtils.flooredPosition +import me.zeroeightsix.kami.util.* import me.zeroeightsix.kami.util.EntityUtils.getDroppedItem -import me.zeroeightsix.kami.util.InventoryUtils -import me.zeroeightsix.kami.util.WorldUtils.isPlaceableForChest -import me.zeroeightsix.kami.util.math.RotationUtils -import me.zeroeightsix.kami.util.text.MessageSendHelper.sendChatMessage +import me.zeroeightsix.kami.util.WorldUtils.placeBlock +import me.zeroeightsix.kami.util.color.ColorHolder +import me.zeroeightsix.kami.util.graphics.ESPRenderer +import me.zeroeightsix.kami.util.math.RotationUtils.getRotationTo +import me.zeroeightsix.kami.util.math.VectorUtils +import me.zeroeightsix.kami.util.math.VectorUtils.toVec3dCenter +import me.zeroeightsix.kami.util.text.MessageSendHelper +import me.zeroeightsix.kami.util.threads.defaultScope +import me.zeroeightsix.kami.util.threads.onMainThreadSafe import me.zeroeightsix.kami.util.threads.safeListener import net.minecraft.block.BlockShulkerBox +import net.minecraft.block.state.IBlockState import net.minecraft.client.audio.PositionedSoundRecord import net.minecraft.client.gui.inventory.GuiShulkerBox +import net.minecraft.enchantment.EnchantmentHelper import net.minecraft.init.Blocks +import net.minecraft.init.Enchantments +import net.minecraft.init.Items import net.minecraft.init.SoundEvents import net.minecraft.inventory.ClickType +import net.minecraft.network.play.client.CPacketEntityAction import net.minecraft.network.play.client.CPacketPlayerDigging +import net.minecraft.network.play.client.CPacketPlayerTryUseItemOnBlock import net.minecraft.util.EnumFacing import net.minecraft.util.EnumHand import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.RayTraceResult import net.minecraft.util.math.Vec3d import net.minecraftforge.fml.common.gameevent.TickEvent import org.kamiblue.commons.extension.ceilToInt -import java.util.concurrent.Executors -import java.util.concurrent.TimeUnit +import org.kamiblue.commons.interfaces.DisplayEnum +import org.kamiblue.event.listener.listener @Module.Info( name = "AutoObsidian", category = Module.Category.MISC, - description = "Mines ender chest automatically to fill inventory with obsidian" + description = "Breaks down Ender Chests to restock obsidian" ) object AutoObsidian : Module() { - private val searchShulker = setting("SearchShulker", false) - private val autoRefill = setting("AutoRefill", false) - private val threshold = setting("RefillThreshold", 8, 1..56, 1, { autoRefill.value }) - private val targetStacks = setting("TargetStacks", 1, 1..20, 1) - private val delayTicks = setting("DelayTicks", 5, 0..10, 1) + private val fillMode by setting("FillMode", FillMode.TARGET_STACKS) + private val searchShulker by setting("SearchShulker", false) + private val leaveEmptyShulkers by setting("LeaveEmptyShulkers", true, { searchShulker }) + private val autoRefill by setting("AutoRefill", false, { fillMode != FillMode.INFINITE }) + private val threshold by setting("RefillThreshold", 8, 1..56, 1, { autoRefill && fillMode != FillMode.INFINITE }) + private val targetStacks by setting("TargetStacks", 1, 1..20, 1, { fillMode == FillMode.TARGET_STACKS }) + private val delayTicks by setting("DelayTicks", 5, 0..10, 1) + private val rotationMode by setting("RotationMode", RotationMode.SPOOF) + private val maxReach by setting("MaxReach", 4.5f, 2.0f..6.0f, 0.1f) - enum class State { - SEARCHING, PLACING, PRE_MINING, MINING, COLLECTING, DONE + private enum class FillMode(override val displayName: String, val message: String) : DisplayEnum { + TARGET_STACKS("Target Stacks", "Target stacks reached"), + FILL_INVENTORY("Fill Inventory", "Inventory filled"), + INFINITE("Infinite", "") } - private enum class SearchingState { - PLACING, OPENING, PRE_MINING, MINING, COLLECTING, DONE + enum class State(override val displayName: String) : DisplayEnum { + SEARCHING("Searching"), + PLACING("Placing"), + PRE_MINING("Pre Mining"), + MINING("Mining"), + COLLECTING("Collecting"), + DONE("Done") } - var pathing = false - var goal: BlockPos? = null - var state = State.SEARCHING + private enum class SearchingState(override val displayName: String) : DisplayEnum { + PLACING("Placing"), + OPENING("Opening"), + PRE_MINING("Pre Mining"), + MINING("Mining"), + COLLECTING("Collecting"), + DONE("Done") + } + + @Suppress("UNUSED") + private enum class RotationMode(override val displayName: String) : DisplayEnum { + OFF("Off"), + SPOOF("Spoof"), + VIEW_LOCK("View Lock") + } + + var goal: Goal? = null; private set + var state = State.SEARCHING; private set + private var searchingState = SearchingState.PLACING private var active = false - private var searchingState = SearchingState.PLACING - private var playerPos = BlockPos(0, -1, 0) private var placingPos = BlockPos(0, -1, 0) private var shulkerBoxId = 0 - private var tickCount = 0 - private var openTime = 0L + private var lastHitVec: Vec3d? = null + + private val tickTimer = TickTimer(TimeUnit.TICKS) + private val rotateTimer = TickTimer(TimeUnit.TICKS) + private val shulkerOpenTimer = TickTimer(TimeUnit.TICKS) + private val renderer = ESPRenderer().apply { aFilled = 33; aOutline = 233 } override fun isActive(): Boolean { return isEnabled && active } override fun onEnable() { - if (mc.player == null) return state = State.SEARCHING } + override fun onDisable() { + reset() + } + init { safeListener { - if (it.phase != TickEvent.Phase.END) return@safeListener - - /* Just a delay */ - if (tickCount < delayTicks.value) { - tickCount++ - return@safeListener - } else tickCount = 0 + if (it.phase != TickEvent.Phase.END || !tickTimer.tick(delayTicks.toLong())) return@safeListener updateState() when (state) { - - /* Searching states */ State.SEARCHING -> { - if (searchShulker.value) { - when (searchingState) { - SearchingState.PLACING -> placeShulker(placingPos) - SearchingState.OPENING -> openShulker(placingPos) - SearchingState.PRE_MINING -> mineBlock(placingPos, true) - SearchingState.MINING -> mineBlock(placingPos, false) - SearchingState.COLLECTING -> collectDroppedItem(shulkerBoxId) - SearchingState.DONE -> { - /* Positions need to be updated after moving while collecting dropped shulker box */ - val currentPos = player.flooredPosition - playerPos = currentPos - setPlacingPos() - } - } - } else searchingState = SearchingState.DONE + searchingState() + } + State.PLACING -> { + placeEnderChest(placingPos) + } + State.PRE_MINING -> { + mineBlock(placingPos, true) + } + State.MINING -> { + mineBlock(placingPos, false) + } + State.COLLECTING -> { + collectDroppedItem(Blocks.OBSIDIAN.id) } - - /* Main states */ - State.PLACING -> placeEnderChest(placingPos) - State.PRE_MINING -> mineBlock(placingPos, true) - State.MINING -> mineBlock(placingPos, false) - State.COLLECTING -> collectDroppedItem(49) State.DONE -> { - if (!autoRefill.value) { - sendChatMessage("$chatName Reached target stacks, disabling.") - AutoObsidian.disable() + if (!autoRefill) { + MessageSendHelper.sendChatMessage("$chatName ${fillMode.message}, disabling.") + disable() } else { - if (active) sendChatMessage("$chatName Reached target stacks, stopping.") + if (active) MessageSendHelper.sendChatMessage("$chatName ${fillMode.message}, stopping.") reset() } } } } - } - override fun onDisable() { - BaritoneUtils.primary?.pathingControlManager?.mostRecentInControl()?.let { - if (it.isPresent && it.get() == AutoObsidianProcess) { - it.get().onLostControl() + listener { + if (state != State.DONE) renderer.render(clear = false, cull = true) + } + + safeListener { event -> + if (event.phase != Phase.PRE || rotateTimer.tick(20L, false)) return@safeListener + val rotation = lastHitVec?.let { getRotationTo(it) } ?: return@safeListener + + when (rotationMode) { + RotationMode.SPOOF -> { + val packet = PlayerPacketManager.PlayerPacket(rotating = true, rotation = rotation) + PlayerPacketManager.addPacket(this@AutoObsidian, packet) + } + RotationMode.VIEW_LOCK -> { + player.rotationYaw = rotation.x + player.rotationPitch = rotation.y + } + else -> { + // Rotation off + } } } - reset() } private fun SafeClientEvent.updateState() { - val currentPos = player.flooredPosition - if (state != State.DONE && placingPos.y == -1) { - playerPos = currentPos - setPlacingPos() - } + if (state != State.DONE) { + updatePlacingPos() - if (!active && state != State.DONE) { - active = true - BaritoneUtils.primary?.pathingControlManager?.registerProcess(AutoObsidianProcess) - } + if (!active) { + active = true + BaritoneUtils.primary?.pathingControlManager?.registerProcess(AutoObsidianProcess) + } - /* Tell baritone to get you back to position */ - if (state != State.DONE && state != State.COLLECTING && searchingState != SearchingState.COLLECTING) { - if (currentPos.x != playerPos.x || currentPos.z != playerPos.z) { - pathing = true - goal = playerPos - return - } else { - pathing = false + if (state != State.COLLECTING && searchingState != SearchingState.COLLECTING) { + goal = if (player.getDistanceSqToCenter(placingPos) > 2.0) { + GoalNear(placingPos, 2) + } else { + null + } } } - /* Updates main state */ updateMainState() + updateSearchingState() + } - /* Updates searching state */ - if (state == State.SEARCHING && searchingState != SearchingState.DONE) { - updateSearchingState() - } else if (state != State.SEARCHING) { - searchingState = SearchingState.PLACING + private fun SafeClientEvent.updatePlacingPos() { + val eyePos = player.getPositionEyes(1f) + if (isPositionValid(placingPos, world.getBlockState(placingPos), eyePos)) return + + val posList = VectorUtils.getBlockPosInSphere(eyePos, maxReach) + .sortedBy { it.distanceSqToCenter(eyePos.x, eyePos.y, eyePos.z) } + .map { it to world.getBlockState(it) } + .toList() + + val pair = posList.find { it.second.block == Blocks.ENDER_CHEST || it.second.block is BlockShulkerBox } + ?: posList.find { isPositionValid(it.first, it.second, eyePos) } + + if (pair != null) { + placingPos = pair.first + renderer.clear() + renderer.add(pair.first, ColorHolder(64, 255, 64)) + } else { + MessageSendHelper.sendChatMessage("$chatName No valid position for placing shulker box / ender chest nearby, disabling.") + mc.soundHandler.playSound(PositionedSoundRecord.getRecord(SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0f, 1.0f)) + disable() } } + private fun SafeClientEvent.isPositionValid(pos: BlockPos, blockState: IBlockState, eyePos: Vec3d) = + !world.getBlockState(pos.down()).material.isReplaceable + && (blockState.block.let { it == Blocks.ENDER_CHEST || it is BlockShulkerBox } + || WorldUtils.isPlaceable(pos)) + && world.isAirBlock(pos.up()) + && world.rayTraceBlocks(eyePos, pos.toVec3dCenter())?.let { it.typeOfHit == RayTraceResult.Type.MISS } ?: true + private fun SafeClientEvent.updateMainState() { - val obbyCount = countObby() + val passCountCheck = checkObbyCount() state = when { - state == State.DONE && autoRefill.value && InventoryUtils.countItemAll(49) <= threshold.value -> { + state == State.DONE && autoRefill && InventoryUtils.countItemAll(Blocks.OBSIDIAN.id) < threshold -> { State.SEARCHING } - state == State.COLLECTING && getDroppedItem(49, 8.0f) == null -> { + state == State.COLLECTING && (!canPickUpObby() || getDroppedItem(Blocks.OBSIDIAN.id, 8.0f) == null) -> { State.DONE } - state != State.DONE && world.isAirBlock(placingPos) && obbyCount >= targetStacks.value -> { + state != State.DONE && world.isAirBlock(placingPos) && !passCountCheck -> { State.COLLECTING } state == State.MINING && world.isAirBlock(placingPos) -> { @@ -183,199 +248,321 @@ object AutoObsidian : Module() { state == State.PLACING && !world.isAirBlock(placingPos) -> { State.PRE_MINING } - state == State.SEARCHING && searchingState == SearchingState.DONE && obbyCount < targetStacks.value -> { + state == State.SEARCHING && searchingState == SearchingState.DONE && passCountCheck -> { State.PLACING } - else -> state + else -> { + state + } } } + /** + * Check if we can pick up more obsidian: + * There must be at least one slot which is either empty, or contains a stack of obsidian less than 64 + */ + private fun SafeClientEvent.canPickUpObby(): Boolean { + return fillMode == FillMode.INFINITE || player.inventory?.mainInventory?.any { + it.isEmpty || it.item.id == Blocks.OBSIDIAN.id && it.count < 64 + } ?: false + } + + /** + * @return `true` if can still place more ender chest + */ + private fun SafeClientEvent.checkObbyCount(): Boolean { + val inventory = InventoryUtils.countItemAll(Blocks.OBSIDIAN.id) + val dropped = EntityUtils.getDroppedItems(Blocks.OBSIDIAN.id, 8.0f).sumBy { it.item.count } + + return when (fillMode) { + FillMode.TARGET_STACKS -> { + ((inventory + dropped) / 8.0f).ceilToInt() / 8 <= targetStacks + } + FillMode.FILL_INVENTORY -> { + countEmptySlots() - dropped >= 8 + } + FillMode.INFINITE -> { + true + } + } + } + + private fun SafeClientEvent.countEmptySlots(): Int { + return player.inventory?.mainInventory?.sumBy { + when { + it.isEmpty -> 64 + it.item.id == Blocks.OBSIDIAN.id -> 64 - it.count + else -> 0 + } + } ?: 0 + } + private fun SafeClientEvent.updateSearchingState() { - searchingState = when { - searchingState == SearchingState.PLACING && InventoryUtils.countItemAll(130) > 0 -> { - SearchingState.DONE - } - searchingState == SearchingState.COLLECTING && getDroppedItem(shulkerBoxId, 8.0f) == null -> { - SearchingState.DONE - } - searchingState == SearchingState.MINING && world.isAirBlock(placingPos) -> { - if (InventoryUtils.countItemAll(130) > 0) { - SearchingState.COLLECTING - } else { /* In case if the shulker wasn't placed due to server lag */ - SearchingState.PLACING + if (state == State.SEARCHING) { + if (searchingState != SearchingState.DONE) { + searchingState = when { + searchingState == SearchingState.PLACING && InventoryUtils.countItemAll(Blocks.ENDER_CHEST.id) > 0 -> { + SearchingState.DONE + } + searchingState == SearchingState.COLLECTING && getDroppedItem(shulkerBoxId, 8.0f) == null -> { + SearchingState.DONE + } + searchingState == SearchingState.MINING && world.isAirBlock(placingPos) -> { + if (InventoryUtils.countItemAll(Blocks.ENDER_CHEST.id) > 0) { + SearchingState.COLLECTING + } else { + // In case if the shulker wasn't placed due to server lag + SearchingState.PLACING + } + } + searchingState == SearchingState.OPENING && (InventoryUtils.countItemAll(Blocks.ENDER_CHEST.id) > 0 + || InventoryUtils.getSlots(0, 35, 0) == null) -> { + SearchingState.PRE_MINING + } + searchingState == SearchingState.PLACING && !world.isAirBlock(placingPos) -> { + if (world.getBlockState(placingPos).block is BlockShulkerBox) { + SearchingState.OPENING + } else { + // In case if the shulker wasn't placed due to server lag + SearchingState.PRE_MINING + } + } + else -> { + searchingState + } } } - searchingState == SearchingState.OPENING && (InventoryUtils.countItemAll(130) >= 64 || InventoryUtils.getSlots(0, 35, 0) == null) -> { - SearchingState.PRE_MINING - } - searchingState == SearchingState.PLACING && !world.isAirBlock(placingPos) -> { - if (world.getBlockState(placingPos).block is BlockShulkerBox) { - SearchingState.OPENING - } else { /* In case if the shulker wasn't placed due to server lag */ - SearchingState.PRE_MINING - } - } - else -> searchingState - } - } - - private fun countObby(): Int { - val inventory = InventoryUtils.countItemAll(49) - val dropped = EntityUtils.getDroppedItems(49, 8.0f).sumBy { it.item.count } - return ((inventory + dropped) / 8.0f).ceilToInt() / 8 - } - - private fun setPlacingPos() { - if (getPlacingPos().y != -1) { - placingPos = getPlacingPos() } else { - sendChatMessage("$chatName No valid position for placing shulker box / ender chest nearby, disabling.") - mc.soundHandler.playSound(PositionedSoundRecord.getRecord(SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0f, 1.0f)) - this.disable() - return + searchingState = SearchingState.PLACING } } - private fun getPlacingPos(): BlockPos { - val pos = playerPos - var facing = EnumFacing.NORTH - for (i in 1..4) { - val posOffset = pos.offset(facing) - val posOffsetDiagonal = posOffset.offset(facing.rotateY()) - when { - isPlaceableForChest(posOffset) -> return posOffset - isPlaceableForChest(posOffset.up()) -> return posOffset.up() - isPlaceableForChest(posOffsetDiagonal) -> return posOffsetDiagonal - isPlaceableForChest(posOffsetDiagonal.up()) -> return posOffsetDiagonal.up() - else -> facing = facing.rotateY() - } - } - return BlockPos(0, -1, 0) - } - - private fun SafeClientEvent.lookAtBlock(pos: BlockPos) { - val vec3d = Vec3d(pos).add(0.5, 0.0, 0.5) - val lookAt = RotationUtils.getRotationTo(vec3d) - player.rotationYaw = lookAt.x - player.rotationPitch = lookAt.y - } - - /* Tasks */ - private fun SafeClientEvent.placeShulker(pos: BlockPos) { - for (i in 219..234) { - if (InventoryUtils.getSlotsHotbar(i) == null) { - if (i != 234) continue else { - sendChatMessage("$chatName No shulker box was found in hotbar, disabling.") - mc.soundHandler.playSound(PositionedSoundRecord.getRecord(SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0f, 1.0f)) - disable() - return + private fun SafeClientEvent.searchingState() { + if (searchShulker) { + when (searchingState) { + SearchingState.PLACING -> { + placeShulker(placingPos) + } + SearchingState.OPENING -> { + openShulker(placingPos) + } + SearchingState.PRE_MINING -> { + mineBlock(placingPos, true) + } + SearchingState.MINING -> { + mineBlock(placingPos, false) + } + SearchingState.COLLECTING -> { + collectDroppedItem(shulkerBoxId) + } + SearchingState.DONE -> { + updatePlacingPos() } } - shulkerBoxId = i - InventoryUtils.swapSlotToItem(i) - break + } else { + searchingState = SearchingState.DONE + } + } + + private fun SafeClientEvent.placeShulker(pos: BlockPos) { + if (InventoryUtils.getSlotsHotbar(shulkerBoxId) == null && InventoryUtils.getSlotsNoHotbar(shulkerBoxId) != null) { + InventoryUtils.moveToHotbar(shulkerBoxId, Items.DIAMOND_PICKAXE.id) + } else { + for (i in 219..234) { + if (InventoryUtils.getSlotsHotbar(i) == null) { + if (i == 234) { + MessageSendHelper.sendChatMessage("$chatName No shulker box was found in hotbar, disabling.") + mc.soundHandler.playSound(PositionedSoundRecord.getRecord(SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0f, 1.0f)) + disable() + } + continue + } + shulkerBoxId = i + InventoryUtils.swapSlotToItem(i) + break + } } if (world.getBlockState(pos).block !is BlockShulkerBox) { - lookAtBlock(pos) - player.isSneaking = true - playerController.processRightClickBlock(player, world, pos.down(), EnumFacing.UP, mc.objectMouseOver.hitVec, EnumHand.MAIN_HAND) - player.swingArm(EnumHand.MAIN_HAND) - mc.rightClickDelayTimer = 4 + placeBlock(pos) } } private fun SafeClientEvent.placeEnderChest(pos: BlockPos) { - if (InventoryUtils.getSlotsHotbar(130) == null && InventoryUtils.getSlotsNoHotbar(130) != null) { - InventoryUtils.moveToHotbar(130, 278) + /* Case where we need to move ender chests into the hotbar */ + if (InventoryUtils.getSlotsHotbar(Blocks.ENDER_CHEST.id) == null && InventoryUtils.getSlotsNoHotbar(Blocks.ENDER_CHEST.id) != null) { + InventoryUtils.moveToHotbar(Blocks.ENDER_CHEST.id, Items.DIAMOND_PICKAXE.id) return - } else if (InventoryUtils.getSlots(0, 35, 130) == null) { - if (searchShulker.value) { + } else if (InventoryUtils.getSlots(0, 35, Blocks.ENDER_CHEST.id) == null) { + /* Case where we are out of ender chests */ + if (searchShulker) { state = State.SEARCHING } else { - sendChatMessage("$chatName No ender chest was found in inventory, disabling.") + MessageSendHelper.sendChatMessage("$chatName No ender chest was found in inventory, disabling.") mc.soundHandler.playSound(PositionedSoundRecord.getRecord(SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0f, 1.0f)) disable() return } } - InventoryUtils.swapSlotToItem(130) - lookAtBlock(pos) - player.isSneaking = true - playerController.processRightClickBlock(player, world, pos.down(), mc.objectMouseOver.sideHit, mc.objectMouseOver.hitVec, EnumHand.MAIN_HAND) - player.swingArm(EnumHand.MAIN_HAND) - mc.rightClickDelayTimer = 4 + /* Else, we already have ender chests in the hotbar */ + InventoryUtils.swapSlotToItem(Blocks.ENDER_CHEST.id) + + placeBlock(pos) } - private fun SafeClientEvent.openShulker(pos: BlockPos) { - lookAtBlock(pos) - if (mc.currentScreen !is GuiShulkerBox) { - /* Added a delay here so it doesn't spam right click and get you kicked */ - if (System.currentTimeMillis() >= openTime + 2000L) { - openTime = System.currentTimeMillis() - playerController.processRightClickBlock(player, world, pos, mc.objectMouseOver.sideHit, mc.objectMouseOver.hitVec, EnumHand.MAIN_HAND) - } - } else { - /* Extra delay here to wait for the item list to be loaded */ - Executors.newSingleThreadScheduledExecutor().schedule({ - val currentContainer = player.openContainer - var enderChestSlot = -1 - for (i in 0..26) { - if (currentContainer.inventory[i].item == Blocks.ENDER_CHEST) { - enderChestSlot = i - } - } - if (enderChestSlot != -1) { - playerController.windowClick(currentContainer.windowId, enderChestSlot, 0, ClickType.QUICK_MOVE, player) + if (mc.currentScreen is GuiShulkerBox) { + val container = player.openContainer + val slot = container.inventory.subList(0, 27).indexOfFirst { it.item.id == Blocks.ENDER_CHEST.id } + + if (slot != -1) { + InventoryUtils.inventoryClick(container.windowId, slot, 0, ClickType.QUICK_MOVE) + player.closeScreen() + } else if (shulkerOpenTimer.tick(100, false)) { // Wait for maximum of 5 seconds + if (leaveEmptyShulkers && container.inventory.subList(0, 27).indexOfFirst { it.item.id != Items.AIR.id } == -1) { + searchingState = SearchingState.PRE_MINING + player.closeScreen() } else { - sendChatMessage("$chatName No ender chest was found in shulker, disabling.") + MessageSendHelper.sendChatMessage("$chatName No ender chest was found in shulker, disabling.") mc.soundHandler.playSound(PositionedSoundRecord.getRecord(SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0f, 1.0f)) disable() } - }, delayTicks.value * 50L, TimeUnit.MILLISECONDS) + } + } else { + val side = EnumFacing.getDirectionFromEntityLiving(pos, player) + val hitVecOffset = WorldUtils.getHitVecOffset(side) + + lastHitVec = WorldUtils.getHitVec(pos, side) + rotateTimer.reset() + + if (shulkerOpenTimer.tick(50)) { + defaultScope.launch { + delay(10L) + onMainThreadSafe { + connection.sendPacket(CPacketPlayerTryUseItemOnBlock(pos, side, EnumHand.MAIN_HAND, hitVecOffset.x.toFloat(), hitVecOffset.y.toFloat(), hitVecOffset.z.toFloat())) + player.swingArm(EnumHand.MAIN_HAND) + } + } + } } } - private fun SafeClientEvent.mineBlock(pos: BlockPos, pre: Boolean) { - if (InventoryUtils.getSlotsHotbar(278) == null && InventoryUtils.getSlotsNoHotbar(278) != null) { - InventoryUtils.moveToHotbar(278, 130) - return - } else if (InventoryUtils.getSlots(0, 35, 278) == null) { - sendChatMessage("$chatName No pickaxe was found in inventory, disabling.") + private fun SafeClientEvent.placeBlock(pos: BlockPos) { + val pair = WorldUtils.getNeighbour(pos, 1, 6.5f) + ?: run { + MessageSendHelper.sendChatMessage("$chatName Can't find neighbour block") + return + } + + lastHitVec = WorldUtils.getHitVec(pair.second, pair.first) + rotateTimer.reset() + + connection.sendPacket(CPacketEntityAction(player, CPacketEntityAction.Action.START_SNEAKING)) + + defaultScope.launch { + delay(10L) + onMainThreadSafe { + placeBlock(pair.second, pair.first) + } + + delay(10L) + onMainThreadSafe { + connection.sendPacket(CPacketEntityAction(player, CPacketEntityAction.Action.STOP_SNEAKING)) + } + } + } + + /** + * Gets the first hotbar slot of a diamond pickaxe that does not have the silk touch enchantment. + * @return The position of the pickaxe. -1 if there is no match. + */ + private fun SafeClientEvent.getHotbarNonSilkTouchPick(): Int { + val slotsWithPickaxes = InventoryUtils.getSlotsHotbar(Items.DIAMOND_PICKAXE.id) + ?: return -1 + + for (slot in slotsWithPickaxes) { + if (EnchantmentHelper.getEnchantmentLevel(Enchantments.SILK_TOUCH, player.inventory.getStackInSlot(slot)) == 0) { + return slot + } + } + + return -1 + } + + /** + * Gets the first non-hotbar slot of a diamond pickaxe that does not have the silk touch enchantment. + * @return The position of the pickaxe. -1 if there is no match. + */ + private fun SafeClientEvent.getInventoryNonSilkTouchPick(): Int { + val slotsWithPickaxes = InventoryUtils.getSlotsNoHotbar(Items.DIAMOND_PICKAXE.id) + ?: return -1 + + for (slot in slotsWithPickaxes) { + if (EnchantmentHelper.getEnchantmentLevel(Enchantments.SILK_TOUCH, player.inventory.getStackInSlot(slot)) == 0) { + return slot + } + } + + return -1 + } + + /** + * Swaps the active hotbar slot to one which has a valid pickaxe (i.e. non-silk touch). If there is no valid pickaxe, + * disable the module. + */ + private fun SafeClientEvent.swapToValidPickaxe() { + var hotbarPickaxeSlot = getHotbarNonSilkTouchPick() + val inventoryPickaxeSlot = getInventoryNonSilkTouchPick() + + if ((hotbarPickaxeSlot == -1) && (inventoryPickaxeSlot != -1)) { + /* Note that slot 36 in windowId 0 is the same as slot 0 in the hotbar */ + InventoryUtils.moveToSlot(0, inventoryPickaxeSlot, 36) + hotbarPickaxeSlot = 0 + } else if (hotbarPickaxeSlot == -1) { + MessageSendHelper.sendChatMessage("No valid pickaxe was found in inventory.") mc.soundHandler.playSound(PositionedSoundRecord.getRecord(SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0f, 1.0f)) disable() return } - InventoryUtils.swapSlotToItem(278) - lookAtBlock(pos) - /* Packet mining lol */ - if (pre) { - connection.sendPacket(CPacketPlayerDigging(CPacketPlayerDigging.Action.START_DESTROY_BLOCK, pos, mc.objectMouseOver.sideHit)) - if (state != State.SEARCHING) state = State.MINING else searchingState = SearchingState.MINING - } else { - connection.sendPacket(CPacketPlayerDigging(CPacketPlayerDigging.Action.STOP_DESTROY_BLOCK, pos, mc.objectMouseOver.sideHit)) + InventoryUtils.swapSlot(hotbarPickaxeSlot) + } + + private fun SafeClientEvent.mineBlock(pos: BlockPos, pre: Boolean) { + if (pre) swapToValidPickaxe() + + val side = EnumFacing.getDirectionFromEntityLiving(pos, player) + lastHitVec = WorldUtils.getHitVec(pos, side) + rotateTimer.reset() + + defaultScope.launch { + delay(5L) + onMainThreadSafe { + if (pre) { + connection.sendPacket(CPacketPlayerDigging(CPacketPlayerDigging.Action.START_DESTROY_BLOCK, pos, side)) + if (state != State.SEARCHING) state = State.MINING else searchingState = SearchingState.MINING + } else { + connection.sendPacket(CPacketPlayerDigging(CPacketPlayerDigging.Action.STOP_DESTROY_BLOCK, pos, side)) + } + player.swingArm(EnumHand.MAIN_HAND) + } } - - player.swingArm(EnumHand.MAIN_HAND) } private fun collectDroppedItem(itemId: Int) { - pathing = if (getDroppedItem(itemId, 16.0f) != null) { - goal = getDroppedItem(itemId, 16.0f) - true - } else false + goal = if (getDroppedItem(itemId, 8.0f) != null) { + GoalNear(getDroppedItem(itemId, 8.0f), 0) + } else { + null + } } private fun reset() { active = false - pathing = false + goal = null searchingState = SearchingState.PLACING - playerPos = BlockPos(0, -1, 0) placingPos = BlockPos(0, -1, 0) - tickCount = 0 + lastHitVec = null } - /* End of tasks */ -} \ No newline at end of file +} diff --git a/src/main/java/me/zeroeightsix/kami/module/modules/misc/AutoTool.kt b/src/main/java/me/zeroeightsix/kami/module/modules/misc/AutoTool.kt index 97f6dac09..21bb5a439 100644 --- a/src/main/java/me/zeroeightsix/kami/module/modules/misc/AutoTool.kt +++ b/src/main/java/me/zeroeightsix/kami/module/modules/misc/AutoTool.kt @@ -3,6 +3,7 @@ package me.zeroeightsix.kami.module.modules.misc import me.zeroeightsix.kami.mixin.extension.syncCurrentPlayItem import me.zeroeightsix.kami.module.Module import me.zeroeightsix.kami.setting.ModuleConfig.setting +import me.zeroeightsix.kami.util.InventoryUtils import me.zeroeightsix.kami.util.combat.CombatUtils import me.zeroeightsix.kami.util.threads.safeListener import net.minecraft.block.state.IBlockState @@ -57,7 +58,7 @@ object AutoTool : Module() { } } - private fun equipBestTool(blockState: IBlockState) { + fun equipBestTool(blockState: IBlockState) { var bestSlot = -1 var max = 0.0 @@ -75,12 +76,7 @@ object AutoTool : Module() { } } } - if (bestSlot != -1) equip(bestSlot) - } - - private fun equip(slot: Int) { - mc.player.inventory.currentItem = slot - mc.playerController.syncCurrentPlayItem() + if (bestSlot != -1) InventoryUtils.swapSlot(bestSlot) } init { diff --git a/src/main/java/me/zeroeightsix/kami/module/modules/player/PacketLogger.kt b/src/main/java/me/zeroeightsix/kami/module/modules/player/PacketLogger.kt index 38e5acd7c..0847123ae 100644 --- a/src/main/java/me/zeroeightsix/kami/module/modules/player/PacketLogger.kt +++ b/src/main/java/me/zeroeightsix/kami/module/modules/player/PacketLogger.kt @@ -2,9 +2,14 @@ package me.zeroeightsix.kami.module.modules.player import me.zeroeightsix.kami.KamiMod import me.zeroeightsix.kami.event.events.PacketEvent +import me.zeroeightsix.kami.mixin.extension.pitch +import me.zeroeightsix.kami.mixin.extension.yaw import me.zeroeightsix.kami.module.Module import me.zeroeightsix.kami.setting.ModuleConfig.setting import me.zeroeightsix.kami.util.text.MessageSendHelper +import net.minecraft.network.play.client.CPacketPlayer +import net.minecraft.network.play.client.CPacketPlayerDigging +import net.minecraft.util.math.Vec3d import me.zeroeightsix.kami.util.threads.safeListener import net.minecraftforge.fml.common.gameevent.TickEvent import org.kamiblue.event.listener.listener @@ -41,7 +46,16 @@ object PacketLogger : Module() { return@listener } - lines.add("${sdf.format(Date())}\n${it.packet.javaClass.simpleName}\n${it.packet.javaClass}\n${it.packet}\n\n") + lines.add("${sdf.format(Date())}\n${it.packet.javaClass.simpleName}\n${it.packet.javaClass}\n${it.packet}") + when (it.packet) { + is CPacketPlayerDigging -> { + lines.add("\nMining - ${it.packet.position}@${it.packet.facing} - ${it.packet.action}") + } + is CPacketPlayer -> { + lines.add("\nRotation - Pitch: ${it.packet.pitch} Yaw: ${it.packet.yaw}") + } + } + lines.add("\n\n") } safeListener { diff --git a/src/main/java/me/zeroeightsix/kami/process/AutoObsidianProcess.kt b/src/main/java/me/zeroeightsix/kami/process/AutoObsidianProcess.kt index 6fa64f969..10a2e8bd2 100644 --- a/src/main/java/me/zeroeightsix/kami/process/AutoObsidianProcess.kt +++ b/src/main/java/me/zeroeightsix/kami/process/AutoObsidianProcess.kt @@ -1,15 +1,10 @@ package me.zeroeightsix.kami.process -import baritone.api.pathing.goals.GoalNear import baritone.api.process.IBaritoneProcess import baritone.api.process.PathingCommand import baritone.api.process.PathingCommandType import me.zeroeightsix.kami.module.modules.misc.AutoObsidian -/** - * Created by Xiaro on 13/07/20. - * Updated by Xiaro on 11/09/20 - */ object AutoObsidianProcess : IBaritoneProcess { override fun isTemporary(): Boolean { @@ -17,22 +12,22 @@ object AutoObsidianProcess : IBaritoneProcess { } override fun priority(): Double { - return 2.0 + return 3.0 } override fun onLostControl() {} override fun displayName0(): String { - return "AutoObsidian: " + AutoObsidian.state.toString().toLowerCase() + return "AutoObsidian: " + AutoObsidian.state.displayName } override fun isActive(): Boolean { return AutoObsidian.isActive() } - override fun onTick(p0: Boolean, p1: Boolean): PathingCommand? { - return if (AutoObsidian.pathing && AutoObsidian.goal != null) { - PathingCommand(GoalNear(AutoObsidian.goal, 0), PathingCommandType.SET_GOAL_AND_PATH) - } else PathingCommand(null, PathingCommandType.REQUEST_PAUSE) + override fun onTick(p0: Boolean, p1: Boolean): PathingCommand { + return AutoObsidian.goal?.let { + PathingCommand(it, PathingCommandType.SET_GOAL_AND_PATH) + } ?: PathingCommand(null, PathingCommandType.REQUEST_PAUSE) } } \ No newline at end of file diff --git a/src/main/java/me/zeroeightsix/kami/process/TemporaryPauseProcess.kt b/src/main/java/me/zeroeightsix/kami/process/TemporaryPauseProcess.kt index 23b4dc3bb..db0b1c665 100644 --- a/src/main/java/me/zeroeightsix/kami/process/TemporaryPauseProcess.kt +++ b/src/main/java/me/zeroeightsix/kami/process/TemporaryPauseProcess.kt @@ -12,7 +12,7 @@ object TemporaryPauseProcess : IBaritoneProcess { } override fun priority(): Double { - return 3.0 + return 5.0 } override fun isActive(): Boolean { diff --git a/src/main/java/me/zeroeightsix/kami/util/BlockUtils.kt b/src/main/java/me/zeroeightsix/kami/util/BlockUtils.kt index fc21155a0..f2840e40a 100644 --- a/src/main/java/me/zeroeightsix/kami/util/BlockUtils.kt +++ b/src/main/java/me/zeroeightsix/kami/util/BlockUtils.kt @@ -3,4 +3,6 @@ package me.zeroeightsix.kami.util import net.minecraft.block.Block import net.minecraft.item.Item -val Block.item: Item get() = Item.getItemFromBlock(this) \ No newline at end of file +val Block.item: Item get() = Item.getItemFromBlock(this) + +val Block.id: Int get() = Block.getIdFromBlock(this) \ No newline at end of file diff --git a/src/main/java/me/zeroeightsix/kami/util/WorldUtils.kt b/src/main/java/me/zeroeightsix/kami/util/WorldUtils.kt index 722964e3f..a12190880 100644 --- a/src/main/java/me/zeroeightsix/kami/util/WorldUtils.kt +++ b/src/main/java/me/zeroeightsix/kami/util/WorldUtils.kt @@ -4,6 +4,7 @@ import kotlinx.coroutines.delay import me.zeroeightsix.kami.event.SafeClientEvent import me.zeroeightsix.kami.manager.managers.PlayerPacketManager import me.zeroeightsix.kami.util.math.RotationUtils +import me.zeroeightsix.kami.util.math.corners import me.zeroeightsix.kami.util.threads.runSafeSuspend import net.minecraft.client.Minecraft import net.minecraft.entity.Entity @@ -105,6 +106,14 @@ object WorldUtils { return mc.world.getBlockState(pos).block == Blocks.WATER } + /** + * Checks if given [pos] is able to place block in it + * + * @return true playing is not colliding with [pos] and there is block below it + */ + fun isPlaceable(pos: BlockPos, ignoreSelfCollide: Boolean = false) = mc.world.getBlockState(pos).material.isReplaceable + && mc.world.checkNoEntityCollision(AxisAlignedBB(pos), if (ignoreSelfCollide) mc.player else null) + fun rayTraceTo(blockPos: BlockPos): RayTraceResult? { return mc.world.rayTraceBlocks(mc.player.getPositionEyes(1f), Vec3d(blockPos).add(0.5, 0.5, 0.5)) } @@ -123,23 +132,21 @@ object WorldUtils { return Vec3d(vec.x * 0.5 + 0.5, vec.y * 0.5 + 0.5, vec.z * 0.5 + 0.5) } - /** - * Checks if given [pos] is able to place block in it - * - * @return true playing is not colliding with [pos] and there is block below it - */ - fun isPlaceable(pos: BlockPos, ignoreSelfCollide: Boolean = false) = mc.world.getBlockState(pos).material.isReplaceable - && mc.world.checkNoEntityCollision(AxisAlignedBB(pos), if (ignoreSelfCollide) mc.player else null) + fun SafeClientEvent.rayTraceHitVec(pos: BlockPos) : RayTraceResult? { + val eyePos = player.getPositionEyes(1f) + val bb = world.getBlockState(pos).getSelectedBoundingBox(world, pos) - /** - * Checks if given [pos] is able to chest (air above) block in it - * - * @return true playing is not colliding with [pos] and there is block below it - */ - fun isPlaceableForChest(pos: BlockPos): Boolean { - return isPlaceable(pos) && !mc.world.getBlockState(pos.down()).material.isReplaceable && mc.world.isAirBlock(pos.up()) + return world.rayTraceBlocks(eyePos, bb.center, false, false, true)?.takeIf { + it.isEqualTo(pos) + } ?: bb.corners(0.95).mapNotNull { corner -> + world.rayTraceBlocks(eyePos, corner, false, false, true)?.takeIf { it.isEqualTo(pos) } + }.minByOrNull { + it.hitVec?.distanceTo(eyePos) ?: 69420.0 + } } + private fun RayTraceResult.isEqualTo(pos: BlockPos) = typeOfHit == RayTraceResult.Type.BLOCK && blockPos == pos + suspend fun buildStructure( placeSpeed: Float, getPlaceInfo: (HashSet) -> Pair? diff --git a/src/main/java/me/zeroeightsix/kami/util/math/BoundingBoxUtils.kt b/src/main/java/me/zeroeightsix/kami/util/math/BoundingBoxUtils.kt new file mode 100644 index 000000000..1dec35c5d --- /dev/null +++ b/src/main/java/me/zeroeightsix/kami/util/math/BoundingBoxUtils.kt @@ -0,0 +1,38 @@ +package me.zeroeightsix.kami.util.math + +import me.zeroeightsix.kami.util.math.VectorUtils.plus +import me.zeroeightsix.kami.util.math.VectorUtils.times +import me.zeroeightsix.kami.util.math.VectorUtils.toVec3d +import net.minecraft.util.EnumFacing +import net.minecraft.util.math.AxisAlignedBB +import net.minecraft.util.math.Vec3d + +val AxisAlignedBB.xLength get() = maxX - minX + +val AxisAlignedBB.yLength get() = maxY - minY + +val AxisAlignedBB.zLength get() = maxY - minY + +val AxisAlignedBB.lengths get() = Vec3d(xLength, yLength, zLength) + +fun AxisAlignedBB.corners(scale: Double) : Array { + val growSizes = lengths * (scale - 1.0) + return grow(growSizes.x, growSizes.y, growSizes.z).corners() +} + +fun AxisAlignedBB.corners() = arrayOf( + Vec3d(minX, minY, minZ), + Vec3d(minX, minY, maxZ), + Vec3d(minX, maxY, minZ), + Vec3d(minX, maxY, maxZ), + Vec3d(maxX, minY, minZ), + Vec3d(maxX, minY, maxZ), + Vec3d(maxX, maxY, minZ), + Vec3d(maxX, maxY, maxZ), +) + +fun AxisAlignedBB.side(side: EnumFacing, scale: Double = 0.5) : Vec3d { + val lengths = lengths + val sideDirectionVec = side.directionVec.toVec3d() + return lengths * sideDirectionVec * scale + center +} \ No newline at end of file diff --git a/src/main/java/me/zeroeightsix/kami/util/math/Direction.kt b/src/main/java/me/zeroeightsix/kami/util/math/Direction.kt index b4644a67a..1b478f161 100644 --- a/src/main/java/me/zeroeightsix/kami/util/math/Direction.kt +++ b/src/main/java/me/zeroeightsix/kami/util/math/Direction.kt @@ -13,16 +13,21 @@ import kotlin.math.roundToInt enum class Direction( override val displayName: String, val displayNameXY: String, - val directionVec: Vec3i + val directionVec: Vec3i, + val isDiagonal: Boolean ) : DisplayEnum { - NORTH("North", "-Z", Vec3i(0, 0, -1)), - NORTH_EAST("North East", "+X -Z", Vec3i(1, 0, -1)), - EAST("East", "+X", Vec3i(1, 0, 0)), - SOUTH_EAST("South East", "+X +Z", Vec3i(1, 0, 1)), - SOUTH("South", "+Z", Vec3i(0, 0, 1)), - SOUTH_WEST("South West", "-X +Z", Vec3i(-1, 0, 1)), - WEST("West", "-X", Vec3i(-1, 0, 0)), - NORTH_WEST("North West", "-X -Z", Vec3i(-1, 0, -1)); + NORTH("North", "-Z", Vec3i(0, 0, -1), false), + NORTH_EAST("North East", "+X -Z", Vec3i(1, 0, -1), true), + EAST("East", "+X", Vec3i(1, 0, 0), false), + SOUTH_EAST("South East", "+X +Z", Vec3i(1, 0, 1), true), + SOUTH("South", "+Z", Vec3i(0, 0, 1), false), + SOUTH_WEST("South West", "-X +Z", Vec3i(-1, 0, 1), true), + WEST("West", "-X", Vec3i(-1, 0, 0), false), + NORTH_WEST("North West", "-X -Z", Vec3i(-1, 0, -1), true); + + fun clockwise(n: Int = 1) = values()[Math.floorMod((ordinal + n), 8)] + + fun counterClockwise(n: Int = 1) = values()[Math.floorMod((ordinal - n), 8)] companion object { diff --git a/src/main/java/me/zeroeightsix/kami/util/math/VectorUtils.kt b/src/main/java/me/zeroeightsix/kami/util/math/VectorUtils.kt index 129ac5b6c..32f361336 100644 --- a/src/main/java/me/zeroeightsix/kami/util/math/VectorUtils.kt +++ b/src/main/java/me/zeroeightsix/kami/util/math/VectorUtils.kt @@ -72,7 +72,7 @@ object VectorUtils { } fun Vec3i.toVec3d(offSet: Vec3d): Vec3d { - return Vec3d(x+ offSet.x, y + offSet.y, z + offSet.z) + return Vec3d(x + offSet.x, y + offSet.y, z + offSet.z) } fun Vec3i.toVec3d(xOffset: Double, yOffset: Double, zOffset: Double): Vec3d { @@ -122,4 +122,16 @@ object VectorUtils { fun Entity.distanceTo(chunkPos: ChunkPos): Double { return hypot(chunkPos.x * 16 + 8 - posX, chunkPos.z * 16 + 8 - posZ) } + + fun Vec3i.multiply(multiplier: Int): Vec3i { + return Vec3i(x * multiplier, y * multiplier, z * multiplier) + } + + infix operator fun Vec3d.times(vec3d: Vec3d): Vec3d = Vec3d(x * vec3d.x, y * vec3d.y, z * vec3d.z) + + infix operator fun Vec3d.times(multiplier: Double): Vec3d = Vec3d(x * multiplier, y * multiplier, z * multiplier) + + infix operator fun Vec3d.plus(vec3d: Vec3d): Vec3d = add(vec3d) + + infix operator fun Vec3d.minus(vec3d: Vec3d): Vec3d = subtract(vec3d) } diff --git a/src/main/java/me/zeroeightsix/kami/util/threads/ThreadSafety.kt b/src/main/java/me/zeroeightsix/kami/util/threads/ThreadSafety.kt index c2e6e8dc2..ebd6f43ac 100644 --- a/src/main/java/me/zeroeightsix/kami/util/threads/ThreadSafety.kt +++ b/src/main/java/me/zeroeightsix/kami/util/threads/ThreadSafety.kt @@ -35,7 +35,7 @@ fun runSafe(block: SafeClientEvent.() -> Unit) { ClientEvent().toSafe()?.let { block(it) } } -fun runSafe(block: SafeClientEvent.() -> R): R? { +fun runSafeR(block: SafeClientEvent.() -> R): R? { return ClientEvent().toSafe()?.let { block(it) } }