diff --git a/src/main/kotlin/com/lambda/client/command/commands/ScaffoldCommand.kt b/src/main/kotlin/com/lambda/client/command/commands/ScaffoldCommand.kt new file mode 100644 index 000000000..e4239b762 --- /dev/null +++ b/src/main/kotlin/com/lambda/client/command/commands/ScaffoldCommand.kt @@ -0,0 +1,109 @@ +package com.lambda.client.command.commands + +import com.lambda.client.command.ClientCommand +import com.lambda.client.module.modules.player.Scaffold +import com.lambda.client.util.items.shulkerList +import com.lambda.client.util.text.MessageSendHelper +import com.lambda.client.util.text.formatValue + +object ScaffoldCommand : ClientCommand( + name = "scaffold", + description = "Manage scaffold whitelist/blacklist" +) { + init { + literal("whitelist", "wl") { + literal("add", "+") { + literal("shulker_box") { + execute("Add all shulker box types to whitelist") { + Scaffold.blockSelectionWhitelist.editValue { whitelist -> shulkerList.forEach { whitelist.add(it.localizedName) } } + MessageSendHelper.sendChatMessage("All shulker boxes have been added to whitelist") + } + } + block("block") { blockArg -> + execute("Add a block to Scaffold whitelist") { + val blockName = blockArg.value.registryName.toString() + if (Scaffold.blockSelectionWhitelist.contains(blockName)) { + MessageSendHelper.sendErrorMessage("${formatValue(blockName)} is already added to scaffold whitelist") + } else { + Scaffold.blockSelectionWhitelist.editValue { it.add(blockName) } + MessageSendHelper.sendChatMessage("${formatValue(blockName)} has been added to scaffold whitelist") + } + } + } + } + literal("del", "-") { + literal("shulker_box") { + execute("Remove all shulker box types from whitelist") { + Scaffold.blockSelectionWhitelist.editValue { whitelist -> shulkerList.forEach { whitelist.remove(it.localizedName) } } + MessageSendHelper.sendChatMessage("All shulker boxes have been removed from whitelist") + } + } + block("block") { blockArg -> + execute("Removes a block from the Scaffold whitelist") { + val blockName = blockArg.value.registryName.toString() + Scaffold.blockSelectionWhitelist.editValue { it.remove(blockName) } + MessageSendHelper.sendChatMessage("${formatValue(blockName)} has been removed from scaffold whitelist") + } + } + } + literal("clear", "c") { + execute { + Scaffold.blockSelectionWhitelist.editValue { it.clear() } + MessageSendHelper.sendChatMessage("Whitelist has been cleared") + } + } + literal("list") { + execute { + MessageSendHelper.sendChatMessage("Blocks: ${Scaffold.blockSelectionWhitelist.joinToString()}") + } + } + } + literal("blacklist", "bl") { + literal("add", "+") { + literal("shulker_box") { + execute("Add all shulker box types to blacklist") { + Scaffold.blockSelectionBlacklist.editValue { blacklist -> shulkerList.forEach { blacklist.add(it.localizedName) } } + MessageSendHelper.sendChatMessage("All shulker boxes have been added to blacklist") + } + } + block("block") { blockArg -> + execute("Add a block to Scaffold blacklist") { + val blockName = blockArg.value.registryName.toString() + if (Scaffold.blockSelectionBlacklist.contains(blockName)) { + MessageSendHelper.sendErrorMessage("${formatValue(blockName)} is already added to scaffold blacklist") + } else { + Scaffold.blockSelectionBlacklist.editValue { it.add(blockName) } + MessageSendHelper.sendChatMessage("${formatValue(blockName)} has been added to scaffold blacklist") + } + } + } + } + literal("del", "-") { + literal("shulker_box") { + execute("Remove all shulker box types from blacklist") { + Scaffold.blockSelectionBlacklist.editValue { blacklist -> shulkerList.forEach { blacklist.remove(it.localizedName) } } + MessageSendHelper.sendChatMessage("All shulker boxes have been removed from blacklist") + } + } + block("block") { blockArg -> + execute("Removes a block from the Scaffold blacklist") { + val blockName = blockArg.value.registryName.toString() + Scaffold.blockSelectionBlacklist.editValue { it.remove(blockName) } + MessageSendHelper.sendChatMessage("${formatValue(blockName)} has been removed from scaffold blacklist") + } + } + } + literal("clear", "c") { + execute { + Scaffold.blockSelectionBlacklist.editValue { it.clear() } + MessageSendHelper.sendChatMessage("Blacklist has been cleared") + } + } + literal("list") { + execute { + MessageSendHelper.sendChatMessage("Blocks: ${Scaffold.blockSelectionBlacklist.joinToString()}") + } + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/module/modules/player/NoFall.kt b/src/main/kotlin/com/lambda/client/module/modules/player/NoFall.kt index d2debd413..13db8316a 100644 --- a/src/main/kotlin/com/lambda/client/module/modules/player/NoFall.kt +++ b/src/main/kotlin/com/lambda/client/module/modules/player/NoFall.kt @@ -27,12 +27,12 @@ object NoFall : Module( category = Category.PLAYER ) { private val distance by setting("Distance", 3, 1..10, 1) - private val mode by setting("Mode", Mode.CATCH) + var mode by setting("Mode", Mode.CATCH) private val fallModeSetting by setting("Fall", FallMode.PACKET, { mode == Mode.FALL }) private val catchModeSetting by setting("Catch", CatchMode.MOTION, { mode == Mode.CATCH }) private val voidOnly by setting("Void Only", false, { mode == Mode.CATCH }) - private enum class Mode { + enum class Mode { FALL, CATCH } diff --git a/src/main/kotlin/com/lambda/client/module/modules/player/Scaffold.kt b/src/main/kotlin/com/lambda/client/module/modules/player/Scaffold.kt index f33e6d938..979610a49 100644 --- a/src/main/kotlin/com/lambda/client/module/modules/player/Scaffold.kt +++ b/src/main/kotlin/com/lambda/client/module/modules/player/Scaffold.kt @@ -1,41 +1,46 @@ package com.lambda.client.module.modules.player +import com.lambda.client.LambdaMod +import com.lambda.client.commons.interfaces.DisplayEnum import com.lambda.client.event.Phase import com.lambda.client.event.SafeClientEvent -import com.lambda.client.event.events.OnUpdateWalkingPlayerEvent -import com.lambda.client.event.events.PacketEvent -import com.lambda.client.event.events.PlayerTravelEvent +import com.lambda.client.event.events.* import com.lambda.client.event.listener.listener -import com.lambda.client.manager.managers.HotbarManager.resetHotbar import com.lambda.client.manager.managers.HotbarManager.serverSideItem import com.lambda.client.manager.managers.HotbarManager.spoofHotbar import com.lambda.client.manager.managers.PlayerPacketManager.sendPlayerPacket import com.lambda.client.mixin.extension.syncCurrentPlayItem import com.lambda.client.module.Category import com.lambda.client.module.Module -import com.lambda.client.util.EntityUtils.prevPosVector +import com.lambda.client.module.modules.player.Scaffold.isUsableItem +import com.lambda.client.setting.settings.impl.collection.CollectionSetting +import com.lambda.client.util.EntityUtils.flooredPosition +import com.lambda.client.util.MovementUtils.speed import com.lambda.client.util.TickTimer import com.lambda.client.util.TimeUnit -import com.lambda.client.util.items.HotbarSlot -import com.lambda.client.util.items.firstItem -import com.lambda.client.util.items.hotbarSlots -import com.lambda.client.util.items.swapToSlot +import com.lambda.client.util.color.ColorHolder +import com.lambda.client.util.graphics.ESPRenderer +import com.lambda.client.util.items.* import com.lambda.client.util.math.RotationUtils.getRotationTo -import com.lambda.client.util.math.VectorUtils.toBlockPos import com.lambda.client.util.threads.safeListener import com.lambda.client.util.world.PlaceInfo import com.lambda.client.util.world.getNeighbour +import com.lambda.client.util.world.isFullBox import com.lambda.client.util.world.placeBlock import com.lambda.mixin.entity.MixinEntity +import net.minecraft.block.Block +import net.minecraft.block.state.IBlockState +import net.minecraft.init.Blocks +import net.minecraft.item.Item import net.minecraft.item.ItemBlock import net.minecraft.network.play.client.CPacketEntityAction +import net.minecraft.network.play.server.SPacketBlockChange import net.minecraft.network.play.server.SPacketPlayerPosLook -import net.minecraft.util.EnumFacing import net.minecraft.util.math.BlockPos -import net.minecraft.util.math.RayTraceResult -import net.minecraft.util.math.Vec3d -import kotlin.math.floor -import kotlin.math.roundToInt +import net.minecraftforge.client.event.InputUpdateEvent +import net.minecraftforge.fml.common.gameevent.TickEvent +import net.minecraftforge.fml.common.gameevent.TickEvent.ClientTickEvent +import java.util.concurrent.ConcurrentHashMap /** * @see MixinEntity.moveInvokeIsSneakingPre @@ -47,44 +52,131 @@ object Scaffold : Module( category = Category.PLAYER, modulePriority = 500 ) { - private val tower by setting("Tower", true) - private val spoofHotbar by setting("Spoof Hotbar", true) - val safeWalk by setting("Safe Walk", true) - private val sneak by setting("Sneak", true) - private val strictDirection by setting("Strict Direction", false) - private val delay by setting("Delay", 2, 1..10, 1, unit = " ticks") - private val maxRange by setting("Max Range", 1, 0..3, 1) - - private var lastHitVec: Vec3d? = null + private val page by setting("Page", Page.GENERAL) + + private val blockSelectionMode by setting("Block Selection Mode", ScaffoldBlockSelectionMode.ANY, { page == Page.GENERAL }) + private val tower by setting("Tower", true, { page == Page.GENERAL }) + private val spoofHotbar by setting("Spoof Hotbar", true, { page == Page.GENERAL }) + val safeWalk by setting("Safe Walk", true, { page == Page.GENERAL }) + private val useNoFall by setting("No Fall", false, { page == Page.GENERAL }) + private val descendOnSneak by setting("Descend on sneak", true, { page == Page.GENERAL }) + private val visibleSideCheck by setting("Visible side check", true, { page == Page.GENERAL }) + private val delay by setting("Delay", 0, 0..10, 1, { page == Page.GENERAL }, unit = " ticks") + private val timeout by setting("Timeout", 15, 1..40, 1, { page == Page.GENERAL }, unit = " ticks") + private val attempts by setting("Placement Search Depth", 3, 0..7, 1, { page == Page.GENERAL }) + private val maxPending by setting("Max Pending", 3, 0..10, 1, { page == Page.GENERAL }) + private val below by setting("Max Tower Distance", 0.3, 0.0..2.0, 0.01, { page == Page.GENERAL }) + private val filled by setting("Filled", true, { page == Page.RENDER }, description = "Renders surfaces") + private val outline by setting("Outline", true, { page == Page.RENDER }, description = "Renders outline") + private val alphaFilled by setting("Alpha Filled", 26, 0..255, 1, { filled && page == Page.RENDER }, description = "Alpha for surfaces") + private val alphaOutline by setting("Alpha Outline", 26, 0..255, 1, { outline && page == Page.RENDER }, description = "Alpha for outline") + private val thickness by setting("Outline Thickness", 2f, .25f..4f, .25f, { outline && page == Page.RENDER }, description = "Changes thickness of the outline") + private val pendingBlockColor by setting("Pending Color", ColorHolder(0, 0, 255), visibility = { page == Page.RENDER }) + + val blockSelectionWhitelist = setting(CollectionSetting("BlockWhitelist", linkedSetOf("minecraft:obsidian"), { false })) + val blockSelectionBlacklist = setting(CollectionSetting("BlockBlacklist", blockBlacklist.map { it.registryName.toString() }.toMutableSet(), { false })) + + private enum class Page { + GENERAL, RENDER + } + + private enum class ScaffoldBlockSelectionMode( + override val displayName: String, + val filter: (Item) -> Boolean): DisplayEnum { + ANY("Any", { it.isUsableItem() }), + WHITELIST("Whitelist", { blockSelectionWhitelist.contains(it.registryName.toString()) && it.isUsableItem() }), + BLACKLIST("Blacklist", { !blockSelectionBlacklist.contains(it.registryName.toString()) && it.isUsableItem() }) + } + private var placeInfo: PlaceInfo? = null - private var inactiveTicks = 69 + private val renderer = ESPRenderer() private val placeTimer = TickTimer(TimeUnit.TICKS) - private val rubberBandTimer = TickTimer(TimeUnit.TICKS) + private val towerTimer: TickTimer = TickTimer(TimeUnit.TICKS) + private val waterTowerTimer: TickTimer = TickTimer(TimeUnit.TICKS) + private val posLookTimer: TickTimer = TickTimer(TimeUnit.TICKS) + private var oldNoFall = false + private var oldFallMode = NoFall.Mode.CATCH + private var goDown = false - override fun isActive(): Boolean { - return isEnabled && inactiveTicks <= 5 - } + private val pendingBlocks = ConcurrentHashMap() init { + onEnable { + towerTimer.reset() + + if (!useNoFall) return@onEnable + + oldNoFall = NoFall.isEnabled + oldFallMode = NoFall.mode + + NoFall.mode = NoFall.Mode.CATCH + NoFall.enable() + } + onDisable { placeInfo = null - inactiveTicks = 69 + pendingBlocks.clear() + + if (!useNoFall) return@onDisable + if (!oldNoFall) NoFall.disable() + + NoFall.mode = oldFallMode + oldNoFall = false } - listener { - if (it.packet !is SPacketPlayerPosLook) return@listener - rubberBandTimer.reset() + safeListener { event -> + when (val packet = event.packet) { + is SPacketPlayerPosLook -> { + pendingBlocks.forEach { + world.setBlockState(it.key, it.value.blockState) + } + pendingBlocks.clear() + posLookTimer.reset() + } + is SPacketBlockChange -> { + pendingBlocks.remove(packet.blockPosition) + } + } } safeListener { - if (!tower || !mc.gameSettings.keyBindJump.isKeyDown || inactiveTicks > 5 || !isHoldingBlock) return@safeListener - if (rubberBandTimer.tick(10, false)) { - if (shouldTower) player.motionY = 0.41999998688697815 - } else if (player.fallDistance <= 2.0f) { - player.motionY = -0.169 + if (!tower || !mc.gameSettings.keyBindJump.isKeyDown || !isHoldingBlock || !posLookTimer.tick(15, false)) { + towerTimer.reset() + return@safeListener + } + if (player.isInWater || world.getBlockState(player.flooredPosition).material.isLiquid) { + player.motionY = .11 + towerTimer.reset() + waterTowerTimer.reset() + } else if (shouldTower) { + if (waterTowerTimer.tick(5, false)) { + player.jump() + if (towerTimer.tick(30)) { + // reset pos back onto top block + player.motionY = -0.3 + } + } else { + towerTimer.reset() + } } } + + listener { + renderer.aFilled = if (filled) alphaFilled else 0 + renderer.aOutline = if (outline) alphaOutline else 0 + renderer.thickness = thickness + + pendingBlocks.keys.forEach { + renderer.add(it, pendingBlockColor) + } + + renderer.render(clear = true) + } + + safeListener { + if (tower) it.cancel() + } } private val SafeClientEvent.isHoldingBlock: Boolean @@ -92,80 +184,127 @@ object Scaffold : Module( private val SafeClientEvent.shouldTower: Boolean get() = !player.onGround - && player.posY - floor(player.posY) <= 0.1 + && world.getCollisionBoxes(player, player.entityBoundingBox.offset(0.0, -below, 0.0)).isNotEmpty() + && world.getCollisionBoxes(player, player.entityBoundingBox).isEmpty() + && !player.capabilities.isFlying + && player.speed < 0.1 + && (getHeldScaffoldBlock() != null || getBlockSlot() != null) init { - safeListener { event -> - if (event.phase != Phase.PRE) return@safeListener + safeListener { event -> + if (event.phase != TickEvent.Phase.START) return@safeListener - inactiveTicks++ - placeInfo = calcNextPos()?.let { - getNeighbour(it, 1, visibleSideCheck = strictDirection, sides = arrayOf(EnumFacing.DOWN)) - ?: getNeighbour(it, 3, visibleSideCheck = strictDirection, sides = EnumFacing.HORIZONTALS) - } - - placeInfo?.let { - lastHitVec = it.hitVec - swapAndPlace(it) - } + pendingBlocks.values + .filter { it.age > timeout * 50L } + .forEach { pendingBlock -> + LambdaMod.LOG.warn("$chatName Timeout: ${pendingBlock.blockPos}") + pendingBlocks.remove(pendingBlock.blockPos) + world.setBlockState(pendingBlock.blockPos, pendingBlock.blockState) + } - if (inactiveTicks > 5) { - resetHotbar() - } else if (isHoldingBlock) { - lastHitVec?.let { + placeInfo?.let { placeInfo -> + pendingBlocks[placeInfo.placedPos]?.let { + if (it.age < timeout * 50L) { + return@safeListener + } + } + swap()?.let { block -> + place(placeInfo, block) sendPlayerPacket { - rotate(getRotationTo(it)) + rotate(getRotationTo(placeInfo.hitVec)) } } } } - } - private fun SafeClientEvent.calcNextPos(): BlockPos? { - val posVec = player.positionVector - val blockPos = posVec.toBlockPos() - return checkPos(blockPos) - ?: run { - val realMotion = posVec.subtract(player.prevPosVector) - val nextPos = blockPos.add(roundToRange(realMotion.x), 0, roundToRange(realMotion.z)) - checkPos(nextPos) + safeListener { event -> + if (event.phase != Phase.PRE) return@safeListener + + val origin = if (goDown && descendOnSneak) { + goDown = false + player.flooredPosition.down(2) + } else { + player.flooredPosition.down() } - } - private fun SafeClientEvent.checkPos(blockPos: BlockPos): BlockPos? { - val center = Vec3d(blockPos.x + 0.5, blockPos.y.toDouble(), blockPos.z + 0.5) - val rayTraceResult = world.rayTraceBlocks( - center, - center.subtract(0.0, 0.5, 0.0), - false, - true, - false - ) - return blockPos.down().takeIf { rayTraceResult?.typeOfHit != RayTraceResult.Type.BLOCK } - } + placeInfo = getNeighbour( + BlockPos(origin.x, origin.y.coerceIn(0..256), origin.z), + attempts, + visibleSideCheck = visibleSideCheck + ) + } + + safeListener { + if (!descendOnSneak + || !it.movementInput.sneak + || player.capabilities.isFlying + ) return@safeListener - private fun roundToRange(value: Double) = - (value * 2.5 * maxRange).roundToInt().coerceAtMost(maxRange) + goDown = true + it.movementInput.sneak = false + it.movementInput.moveStrafe *= 5f + it.movementInput.moveForward *= 5f + } + } - private fun SafeClientEvent.swapAndPlace(placeInfo: PlaceInfo) { + private fun SafeClientEvent.swap(): Block? { + getHeldScaffoldBlock()?.let { return it } getBlockSlot()?.let { slot -> if (spoofHotbar) spoofHotbar(slot) else swapToSlot(slot) + return slot.stack.item.block + } + if (swapToBlockOrMove(this@Scaffold, { blockSelectionMode.filter(it.item) } )) { + getBlockSlot()?.let { slot -> + if (spoofHotbar) spoofHotbar(slot) + else swapToSlot(slot) + return slot.stack.item.block + } + } + return null + } - inactiveTicks = 0 + private fun SafeClientEvent.place(placeInfo: PlaceInfo, blockToPlace: Block) { + if (placeTimer.tick(delay.toLong()) + && pendingBlocks.size < maxPending + ) { + val isBlacklisted = world.getBlockState(placeInfo.pos).block in blockBlacklist || blockToPlace in blockBlacklist - if (placeTimer.tick(delay.toLong())) { - val shouldSneak = sneak && !player.isSneaking - if (shouldSneak) connection.sendPacket(CPacketEntityAction(player, CPacketEntityAction.Action.START_SNEAKING)) - placeBlock(placeInfo) - if (shouldSneak) connection.sendPacket(CPacketEntityAction(player, CPacketEntityAction.Action.STOP_SNEAKING)) - } + if (isBlacklisted) connection.sendPacket(CPacketEntityAction(player, CPacketEntityAction.Action.START_SNEAKING)) + + placeBlock(placeInfo) + + if (isBlacklisted) connection.sendPacket(CPacketEntityAction(player, CPacketEntityAction.Action.STOP_SNEAKING)) + + pendingBlocks[placeInfo.placedPos] = PendingBlock(placeInfo.placedPos, world.getBlockState(placeInfo.placedPos), blockToPlace) + world.setBlockState(placeInfo.placedPos, Blocks.BARRIER.defaultState) } } + private fun SafeClientEvent.getHeldScaffoldBlock(): Block? { + playerController.syncCurrentPlayItem() + if (blockSelectionMode.filter(player.heldItemMainhand.item)) { + return player.heldItemMainhand.item.block + } + if (blockSelectionMode.filter(player.heldItemOffhand.item)) { + return player.heldItemOffhand.item.block + } + return null + } + private fun SafeClientEvent.getBlockSlot(): HotbarSlot? { playerController.syncCurrentPlayItem() - return player.hotbarSlots.firstItem() + return player.hotbarSlots.firstItem { blockSelectionMode.filter(it.item) } + } + + private data class PendingBlock( + val blockPos: BlockPos, + val blockState: IBlockState, + val block: Block, + val timestamp: Long = System.currentTimeMillis() + ) { + val age get() = System.currentTimeMillis() - timestamp } + private fun Item.isUsableItem() = this is ItemBlock && block.defaultState.isFullBox } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/util/items/Block.kt b/src/main/kotlin/com/lambda/client/util/items/Block.kt index b67e3ad22..cd9184b88 100644 --- a/src/main/kotlin/com/lambda/client/util/items/Block.kt +++ b/src/main/kotlin/com/lambda/client/util/items/Block.kt @@ -24,19 +24,53 @@ val shulkerList: Set = hashSetOf( ) val blockBlacklist: Set = hashSetOf( - Blocks.ENDER_CHEST, - Blocks.CHEST, - Blocks.TRAPPED_CHEST, - Blocks.CRAFTING_TABLE, Blocks.ANVIL, + Blocks.BEACON, + Blocks.BED, Blocks.BREWING_STAND, - Blocks.HOPPER, - Blocks.DROPPER, + Blocks.STONE_BUTTON, + Blocks.WOODEN_BUTTON, + Blocks.CAKE, + Blocks.CAULDRON, + Blocks.CHEST, + Blocks.TRAPPED_CHEST, + Blocks.COMMAND_BLOCK, + Blocks.CHAIN_COMMAND_BLOCK, + Blocks.REPEATING_COMMAND_BLOCK, + Blocks.DAYLIGHT_DETECTOR, + Blocks.DAYLIGHT_DETECTOR_INVERTED, Blocks.DISPENSER, - Blocks.TRAPDOOR, + Blocks.DROPPER, + Blocks.OAK_DOOR, + Blocks.DARK_OAK_DOOR, + Blocks.ACACIA_DOOR, + Blocks.BIRCH_DOOR, + Blocks.JUNGLE_DOOR, + Blocks.SPRUCE_DOOR, Blocks.ENCHANTING_TABLE, + Blocks.ENDER_CHEST, + Blocks.OAK_FENCE_GATE, + Blocks.ACACIA_FENCE_GATE, + Blocks.BIRCH_FENCE_GATE, + Blocks.DARK_OAK_FENCE_GATE, + Blocks.JUNGLE_FENCE_GATE, + Blocks.SPRUCE_FENCE_GATE, + Blocks.FLOWER_POT, + Blocks.FURNACE, + Blocks.HOPPER, + Blocks.JUKEBOX, + Blocks.LEVER, + Blocks.NOTEBLOCK, + Blocks.POWERED_COMPARATOR, + Blocks.UNPOWERED_COMPARATOR, + Blocks.REDSTONE_ORE, + Blocks.POWERED_REPEATER, + Blocks.UNPOWERED_REPEATER, Blocks.STANDING_SIGN, - Blocks.WALL_SIGN + Blocks.WALL_SIGN, + Blocks.STRUCTURE_BLOCK, + Blocks.TRAPDOOR, + Blocks.CRAFTING_TABLE, ).apply { addAll(shulkerList) } diff --git a/src/main/kotlin/com/lambda/client/util/world/Check.kt b/src/main/kotlin/com/lambda/client/util/world/Check.kt index 6225435fb..9e25de3d2 100644 --- a/src/main/kotlin/com/lambda/client/util/world/Check.kt +++ b/src/main/kotlin/com/lambda/client/util/world/Check.kt @@ -107,4 +107,6 @@ fun SafeClientEvent.hasNeighbour(pos: BlockPos): Boolean { */ fun World.isPlaceable(pos: BlockPos, ignoreSelfCollide: Boolean = false) = this.getBlockState(pos).isReplaceable - && this.checkNoEntityCollision(AxisAlignedBB(pos), if (ignoreSelfCollide) Wrapper.player else null) \ No newline at end of file + && checkNoEntityCollision(AxisAlignedBB(pos), if (ignoreSelfCollide) Wrapper.player else null) + && worldBorder.contains(pos) + && !isOutsideBuildHeight(pos) \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/client/util/world/Interact.kt b/src/main/kotlin/com/lambda/client/util/world/Interact.kt index 30d10fe41..1b09e15ed 100644 --- a/src/main/kotlin/com/lambda/client/util/world/Interact.kt +++ b/src/main/kotlin/com/lambda/client/util/world/Interact.kt @@ -83,18 +83,23 @@ private fun SafeClientEvent.getNeighbour( sides: Array, toIgnore: HashSet> ): PlaceInfo? { - for (side in sides) { - val result = checkNeighbour(eyePos, pos, side, range, visibleSideCheck, true, toIgnore) - if (result != null) return result + if (!world.isPlaceable(pos)) return null + + sides.forEach { side -> + checkNeighbour(eyePos, pos, side, range, visibleSideCheck, true, toIgnore)?.let { + return it + } } - if (attempts > 1) { - for (side in sides) { - val newPos = pos.offset(side) - if (!world.isPlaceable(newPos)) continue + if (attempts < 2) return null + + sides.forEach { posSide -> + val newPos = pos.offset(posSide) + if (!world.isPlaceable(newPos)) return@forEach + if (eyePos.distanceTo(newPos.toVec3dCenter()) > range + 1) return@forEach - return getNeighbour(eyePos, newPos, attempts - 1, range, visibleSideCheck, sides, toIgnore) - ?: continue + getNeighbour(eyePos, newPos, attempts - 1, range, visibleSideCheck, sides, toIgnore)?.let { + return it } } @@ -286,4 +291,4 @@ fun SafeClientEvent.placeBlock( } private fun PlaceInfo.toPlacePacket(hand: EnumHand) = - CPacketPlayerTryUseItemOnBlock(this.pos, this.side, hand, hitVecOffset.x.toFloat(), hitVecOffset.y.toFloat(), hitVecOffset.z.toFloat()) \ No newline at end of file + CPacketPlayerTryUseItemOnBlock(pos, side, hand, hitVecOffset.x.toFloat(), hitVecOffset.y.toFloat(), hitVecOffset.z.toFloat()) \ No newline at end of file