From 6e57aa9491208a4708b0e8829f917cce55b34266 Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Sat, 16 Aug 2025 20:05:22 +0100 Subject: [PATCH 01/12] build sim scaffold --- .../lambda/module/modules/player/Scaffold.kt | 375 ++---------------- 1 file changed, 36 insertions(+), 339 deletions(-) diff --git a/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt b/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt index 0541200b7..d2bb73058 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt @@ -17,366 +17,63 @@ package com.lambda.module.modules.player -import com.lambda.config.groups.InteractSettings +import com.lambda.config.groups.BuildSettings +import com.lambda.config.groups.HotbarSettings import com.lambda.config.groups.InteractionSettings +import com.lambda.config.groups.InventorySettings import com.lambda.config.groups.RotationSettings -import com.lambda.context.SafeContext -import com.lambda.event.events.MovementEvent -import com.lambda.event.events.RenderEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.graphics.renderer.esp.DirectionMask -import com.lambda.graphics.renderer.esp.DirectionMask.buildSideMesh -import com.lambda.graphics.renderer.esp.builders.ofBox -import com.lambda.interaction.blockplace.PlaceFinder.Companion.buildPlaceInfo -import com.lambda.interaction.blockplace.PlaceInfo -import com.lambda.interaction.blockplace.PlaceInteraction.placeBlock -import com.lambda.interaction.request.rotating.Rotation -import com.lambda.interaction.request.rotating.Rotation.Companion.angleDifference -import com.lambda.interaction.request.rotating.Rotation.Companion.dist -import com.lambda.interaction.request.rotating.Rotation.Companion.rotationTo -import com.lambda.interaction.request.rotating.Rotation.Companion.wrap -import com.lambda.interaction.request.rotating.RotationManager.activeRotation -import com.lambda.interaction.request.rotating.RotationManager.onRotate -import com.lambda.interaction.request.rotating.RotationRequest -import com.lambda.interaction.request.rotating.visibilty.VisibilityChecker.getVisibleSurfaces -import com.lambda.interaction.request.rotating.visibilty.VisibilityChecker.scanSurfaces -import com.lambda.interaction.request.rotating.visibilty.blockHit -import com.lambda.interaction.request.rotating.visibilty.lookAtHit +import com.lambda.interaction.construction.blueprint.Blueprint.Companion.toStructure +import com.lambda.interaction.construction.blueprint.StaticBlueprint.Companion.toBlueprint +import com.lambda.interaction.construction.context.BuildContext +import com.lambda.interaction.construction.result.PlaceResult +import com.lambda.interaction.construction.simulation.BuildSimulator.simulate +import com.lambda.interaction.construction.verify.TargetState +import com.lambda.interaction.request.Request.Companion.submit +import com.lambda.interaction.request.placing.PlaceRequest import com.lambda.module.Module -import com.lambda.module.modules.client.GuiSettings -import com.lambda.module.modules.client.TaskFlowModule import com.lambda.module.tag.ModuleTag import com.lambda.util.NamedEnum -import com.lambda.util.math.MathUtils.floorToInt -import com.lambda.util.math.dist -import com.lambda.util.math.distSq -import com.lambda.util.math.multAlpha -import com.lambda.util.math.step -import com.lambda.util.math.transform -import com.lambda.util.player.MovementUtils.calcMoveYaw -import com.lambda.util.player.MovementUtils.isInputting -import com.lambda.util.player.MovementUtils.newMovementInput -import com.lambda.util.player.MovementUtils.roundedForward -import com.lambda.util.player.MovementUtils.roundedStrafing import com.lambda.util.world.raycast.InteractionMask -import com.lambda.util.world.raycast.RayCastUtils.blockResult -import com.lambda.util.world.toFastVec -import net.minecraft.util.Hand -import net.minecraft.util.hit.BlockHitResult -import net.minecraft.util.math.BlockPos -import net.minecraft.util.math.Box -import net.minecraft.util.math.Direction -import net.minecraft.util.math.Vec3d -import java.util.* -import kotlin.math.floor -import kotlin.math.pow +import java.util.concurrent.ConcurrentLinkedQueue object Scaffold : Module( name = "Scaffold", description = "Places blocks under the player", tag = ModuleTag.PLAYER, ) { - private val keepY by setting("Keep Y", true).group(Group.General) - private val minPlaceDist by setting("Min Place Dist", 0.0, 0.0..0.2, 0.01).group(Group.General) - private val minRotateDist by setting("Min Rotate Dist", 0.10, 0.0..0.2, 0.01).group(Group.General) - - private val rotationConfig = RotationSettings(this, Group.Rotation) - private val safeWalk by setting("Sneak Before Rotation", true).group(Group.Rotation) - private val direction by setting("Direction", LookingDirection.FREE).group(Group.Rotation) - private val optimalPitch by setting("Optimal Pitch", 81.0, 70.0..85.0, 0.05).group(Group.Rotation) - - private val interactionConfig = InteractionSettings(this, Group.Interaction, InteractionMask.Block) - private val interactConfig = InteractSettings(this, listOf(Group.Interact)) - - // Placement - private var placeInfo: PlaceInfo? = null - private var keepLevel: Int? = null - private var lastRotation: Rotation? = null - private var edjeDistance = 0.0 - - private var lastRequest: RotationRequest? = null - - // Sneaking - private var placeInfoAge = 0 - private var sneakTicks = 0 - - // Rendering - private val renderInfo = HashSet>() - private val currentTime get() = System.currentTimeMillis() - - // Other - private val yawList = listOf(0.0, 90.0, 180.0, 270.0) - private val diagonalYawList = yawList.map { it + 45 } - private val builderSideMask = EnumSet.allOf(Direction::class.java).apply { - remove(Direction.UP) - } - - // Yaw values within this range will not make your movement unstable - private const val YAW_THRESHOLD = 15.0 - private enum class Group(override val displayName: String) : NamedEnum { - General("General"), + Build("Build"), Rotation("Rotation"), - Interact("Interact"), - Interaction("Interaction") + Interaction("Interaction"), + Hotbar("Hotbar"), + Inventory("Inventory") } - private enum class LookingDirection { - FREE, - ClAMPED, - STRAIGHT, - DIAGONAL - } - - init { - onRotate { - lastRotation = null - lastRequest = null - val info = updatePlaceInfo() ?: return@onRotate - - lastRequest = lookAtHit( - blockHit(info.clickPos, info.clickSide, interactionConfig.interactReach) - ) { rotate(info) }.requestBy(rotationConfig) - } + private val buildConfig = BuildSettings(this, Group.Build) + private val rotationConfig = RotationSettings(this, Group.Rotation) + private val interactionConfig = InteractionSettings(this, Group.Interaction, InteractionMask.Block) + private val hotbarConfig = HotbarSettings(this, Group.Hotbar) + private val inventoryConfig = InventorySettings(this, Group.Inventory) - listen { - if (sneakTicks > 0) it.sneak = true - } + private val pendingActions = ConcurrentLinkedQueue() + init { listen { - placeInfo?.let { info -> - tickPlacement(info) - } - - updateSneaking() - } - - listen { event -> - buildRenderer(event) - } - - onEnable { - placeInfo = null - renderInfo.clear() - - keepLevel = null - lastRequest = null - sneakTicks = 0 - } - } - - private fun SafeContext.updatePlaceInfo(): PlaceInfo? { - // Feet placing blockpos - var y = (floor(player.pos.y) - 0.00001).floorToInt() - - // KeepY update - if (keepY && isInputting) { - keepLevel?.let { - y = it - } - } - - // Getting the latest block of the placement sequence - placeInfo = buildPlaceInfo( - basePos = BlockPos(player.pos.x.floorToInt(), y, player.pos.z.floorToInt()), - range = interactionConfig.interactReach + 2, - sides = builderSideMask - ) - - placeInfo?.let { info -> - placeInfoAge = 0 - - // Ignore supporting blocks - if (info.placeSteps != 0) return@let - - // Updating keep level - keepLevel = info.placedPos.y - - edjeDistance = distanceToEdge(info.clickPos) - if (info.clickSide.axis == Direction.Axis.Y) edjeDistance = -1.0 - } - - return placeInfo - } - - private fun SafeContext.rotate(info: PlaceInfo): Rotation? { - val eye = player.eyePos - - val reach = interactionConfig.interactReach - val reachSq = reach.pow(2) - - val input = newMovementInput() - val moveYaw = calcMoveYaw(player.yaw, input.roundedForward, input.roundedStrafing) - - // Checking whether the player is moving diagonally - val isDiagonal = diagonalYawList.any { - angleDifference(moveYaw, it) < YAW_THRESHOLD - } - - // Assumed yaw values - val assumedYaw = assumeYawByDirection(moveYaw) - - // No need to rotate, already looking correctly - val lookingCorrectly = castRotation(activeRotation, info) != null - val isYawStable = angleDifference(activeRotation.yaw, assumedYaw) < YAW_THRESHOLD - if (lookingCorrectly && isYawStable) return activeRotation - - // Dividing the surface by segments and iterating through them - val pointScan = mutableSetOf().apply { - val box = Box(info.clickPos) - val sides = if (TaskFlowModule.interaction.checkSideVisibility) { - box.getVisibleSurfaces(eye) - } else Direction.entries.toSet() - scanSurfaces( - box, - sides, - resolution = interactionConfig.resolution - ) { _, vec -> - if (eye distSq vec > reachSq) return@scanSurfaces - - val rotation = eye.rotationTo(vec) - castRotation(rotation, info) ?: return@scanSurfaces - - add(rotation) - } - } - - // Iterating through assumed angle ranges - val angleScan = mutableSetOf().apply { - val pitchRange = 55.0..85.0 - val pitchList = pitchRange.step(0.1) - - pitchList.forEach { pitch -> - val rotation = Rotation(assumedYaw, pitch) - castRotation(rotation, info) ?: return@forEach - - add(rotation) - } - } - - var optimalPitch = optimalPitch - if (isDiagonal) optimalPitch++ - - val assumedRotation = Rotation(assumedYaw, optimalPitch) - - // Check if the assumed rotation is ok - if (castRotation(assumedRotation, info) != null) { - return assumedRotation - } - - val optimalRotation = when { - // Placing supporting block - info.placeSteps > 0 && !isDiagonal -> activeRotation - - // Placing base block - else -> assumedRotation - } - - // Otherwise selecting the most similar rotation - val rotation = (angleScan + pointScan).minByOrNull { rotation -> - optimalRotation dist rotation - }.also { - lastRotation = it - } - - if (isDiagonal) { - edjeDistance = -1.0 - } - - // Check the distance to the edge (stabilizes rotation) - if (edjeDistance > 0 && edjeDistance < minRotateDist) return null - - return rotation - } - - private fun SafeContext.tickPlacement(info: PlaceInfo) { - // Check the distance to the edge - if (edjeDistance > 0 && edjeDistance < minPlaceDist) return - - // Raycast the rotation - var blockResult: BlockHitResult? = lastRequest?.target?.hit?.hitIfValid()?.blockResult - - // Use fallback hit vec for nonstrict ac's - if (!interactionConfig.strictRayCast && blockResult == null) { - blockResult = BlockHitResult(info.hitVec, info.clickSide, info.clickPos, false) + player + .blockPos + .down() + .toStructure(TargetState.Solid) + .toBlueprint() + .simulate(player.eyePos, interactionConfig, rotationConfig, inventoryConfig, buildConfig) + .filterIsInstance() + .let { results -> + val contexts = results + .map { it.context } + .distinctBy { it.blockPos } + submit(PlaceRequest(contexts, buildConfig, rotationConfig, hotbarConfig, pendingActions)) + } } - - // Run placement - placeBlock(blockResult ?: return, Hand.MAIN_HAND, interactConfig.swingHand) - renderInfo.add(info to currentTime) - } - - private fun SafeContext.updateSneaking() { - sneakTicks-- - placeInfoAge++ - - if (!safeWalk) return - - /*val vec = movementVector(y = -0.5) * player.moveDelta - val predictedBox = player.boundingBox.offset(vec) - val isNearLedge = world.isBlockSpaceEmpty(player, predictedBox)*/ - - val sneak = lastRotation?.let { - activeRotation dist it > YAW_THRESHOLD && player.isOnGround - } ?: (sneakTicks > 0 && placeInfoAge < 4) - - if (sneak) sneakTicks = 3 - } - - private fun buildRenderer(event: RenderEvent.StaticESP) { - val c = GuiSettings.primaryColor - - renderInfo.removeIf { info -> - val (info, time) = info - - val pos = info.placedPos.toFastVec() - val seconds = (currentTime - time) / 1000.0 - - val sides = buildSideMesh(pos) { meshPos -> - renderInfo.any { it.first.placedPos.toFastVec() == meshPos } - } - - val box = Box(info.placedPos) - val alpha = transform(seconds, 0.0, 0.5, 1.0, 0.0).coerceIn(0.0, 1.0) - - event.renderer.ofBox( - box, - c.multAlpha(0.3 * alpha), - c.multAlpha(alpha), - sides, - DirectionMask.OutlineMode.AND - ) - - seconds > 1 - } - } - - private fun assumeYawByDirection(moveYaw: Double): Double { - val moveYawReversed = wrap(moveYaw + 180) - - return when (direction) { - LookingDirection.FREE -> listOf(moveYawReversed) - LookingDirection.ClAMPED -> yawList + diagonalYawList - LookingDirection.STRAIGHT -> yawList - LookingDirection.DIAGONAL -> diagonalYawList - }.minBy { angleDifference(moveYawReversed, it) } - } - - // Calculates the distance from the player to the edge of the block - private fun SafeContext.distanceToEdge(pos: BlockPos, from: Vec3d = player.pos) = - edgeOf(pos) dist Vec3d(from.x, pos.y.toDouble(), from.z) - - private fun SafeContext.edgeOf(pos: BlockPos): Vec3d { - val x = player.pos.x.coerceIn(pos.x.toDouble(), pos.x.toDouble() + 1) - val z = player.pos.z.coerceIn(pos.z.toDouble(), pos.z.toDouble() + 1) - return Vec3d(x, pos.y.toDouble(), z) - } - - // Checks if the rotation matches the placement requirements - private fun castRotation(rotation: Rotation, info: PlaceInfo): BlockHitResult? { - val blockResult = rotation.rayCast(interactionConfig.interactReach)?.blockResult ?: return null - if (blockResult.blockPos != info.clickPos || blockResult.side != info.clickSide) return null - return blockResult } } From e6674ba9a54853a41ed025aea718f5a82b872952 Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Sat, 23 Aug 2025 05:41:01 +0100 Subject: [PATCH 02/12] descend setting --- .../kotlin/com/lambda/module/modules/player/Scaffold.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt b/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt index d2bb73058..3e38f4304 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt @@ -34,8 +34,11 @@ import com.lambda.interaction.request.Request.Companion.submit import com.lambda.interaction.request.placing.PlaceRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag +import com.lambda.util.KeyCode +import com.lambda.util.KeyboardUtils.isKeyPressed import com.lambda.util.NamedEnum import com.lambda.util.world.raycast.InteractionMask +import net.minecraft.util.math.Direction import java.util.concurrent.ConcurrentLinkedQueue object Scaffold : Module( @@ -44,6 +47,7 @@ object Scaffold : Module( tag = ModuleTag.PLAYER, ) { private enum class Group(override val displayName: String) : NamedEnum { + General("General"), Build("Build"), Rotation("Rotation"), Interaction("Interaction"), @@ -51,6 +55,7 @@ object Scaffold : Module( Inventory("Inventory") } + private val descend by setting("Descend", KeyCode.UNBOUND, "Lower the place position by one to allow the player to lower y level").group(Group.General) private val buildConfig = BuildSettings(this, Group.Build) private val rotationConfig = RotationSettings(this, Group.Rotation) private val interactionConfig = InteractionSettings(this, Group.Interaction, InteractionMask.Block) @@ -63,7 +68,7 @@ object Scaffold : Module( listen { player .blockPos - .down() + .offset(Direction.DOWN, if (isKeyPressed(descend.code)) 2 else 1) .toStructure(TargetState.Solid) .toBlueprint() .simulate(player.eyePos, interactionConfig, rotationConfig, inventoryConfig, buildConfig) From 92184219b8f219b71cff982a9b63566109f59812 Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Sat, 23 Aug 2025 06:16:32 +0100 Subject: [PATCH 03/12] bridging --- .../lambda/module/modules/player/Scaffold.kt | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt b/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt index 3e38f4304..da1c5315a 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt @@ -22,11 +22,12 @@ import com.lambda.config.groups.HotbarSettings import com.lambda.config.groups.InteractionSettings import com.lambda.config.groups.InventorySettings import com.lambda.config.groups.RotationSettings +import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.construction.blueprint.Blueprint.Companion.toStructure import com.lambda.interaction.construction.blueprint.StaticBlueprint.Companion.toBlueprint import com.lambda.interaction.construction.context.BuildContext +import com.lambda.interaction.construction.context.PlaceContext import com.lambda.interaction.construction.result.PlaceResult import com.lambda.interaction.construction.simulation.BuildSimulator.simulate import com.lambda.interaction.construction.verify.TargetState @@ -34,10 +35,14 @@ import com.lambda.interaction.request.Request.Companion.submit import com.lambda.interaction.request.placing.PlaceRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag +import com.lambda.util.BlockUtils.blockPos +import com.lambda.util.BlockUtils.blockState import com.lambda.util.KeyCode import com.lambda.util.KeyboardUtils.isKeyPressed import com.lambda.util.NamedEnum +import com.lambda.util.math.distSq import com.lambda.util.world.raycast.InteractionMask +import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Direction import java.util.concurrent.ConcurrentLinkedQueue @@ -55,6 +60,7 @@ object Scaffold : Module( Inventory("Inventory") } + private val bridgeRange by setting("Bridge Range", 5, 0..5, 1, "The range at which blocks can be placed to help build support for the player").group(Group.General) private val descend by setting("Descend", KeyCode.UNBOUND, "Lower the place position by one to allow the player to lower y level").group(Group.General) private val buildConfig = BuildSettings(this, Group.Build) private val rotationConfig = RotationSettings(this, Group.Rotation) @@ -66,10 +72,11 @@ object Scaffold : Module( init { listen { - player - .blockPos - .offset(Direction.DOWN, if (isKeyPressed(descend.code)) 2 else 1) - .toStructure(TargetState.Solid) + val beneath = player.blockPos.offset(Direction.DOWN, if (isKeyPressed(descend.code)) 2 else 1) + val placements = getPlacements(beneath) + if (placements == null) return@listen + placements + .associate { it to TargetState.Solid } .toBlueprint() .simulate(player.eyePos, interactionConfig, rotationConfig, inventoryConfig, buildConfig) .filterIsInstance() @@ -77,8 +84,22 @@ object Scaffold : Module( val contexts = results .map { it.context } .distinctBy { it.blockPos } + .sortedWith { o1, o2 -> getBridgeCompareBy(beneath).compare(o1, o2) } submit(PlaceRequest(contexts, buildConfig, rotationConfig, hotbarConfig, pendingActions)) } } } + + private fun SafeContext.getPlacements(beneath: BlockPos): List? { + if (blockState(beneath).isSolidBlock(world, beneath)) return null + + return BlockPos + .iterateOutwards(beneath, bridgeRange, bridgeRange, bridgeRange) + .map { it.blockPos } + } + + private fun getBridgeCompareBy(blockPos: BlockPos) = + compareBy { + it.blockPos.toCenterPos() distSq blockPos.toCenterPos() + } } From f5f9723afe4ac76f966f7350e63ef9f88646dca2 Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Sat, 23 Aug 2025 14:49:46 +0100 Subject: [PATCH 04/12] isReplaceable over isSolidBlock --- src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt b/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt index da1c5315a..31081a70c 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt @@ -91,7 +91,7 @@ object Scaffold : Module( } private fun SafeContext.getPlacements(beneath: BlockPos): List? { - if (blockState(beneath).isSolidBlock(world, beneath)) return null + if (!blockState(beneath).isReplaceable) return null return BlockPos .iterateOutwards(beneath, bridgeRange, bridgeRange, bridgeRange) From e563d4bb59c4500176c6cd29d2c8ad42433c8302 Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Sat, 23 Aug 2025 14:50:05 +0100 Subject: [PATCH 05/12] only pause placement if the player should be sneaking and isnt --- .../com/lambda/interaction/request/placing/PlaceManager.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/lambda/interaction/request/placing/PlaceManager.kt b/src/main/kotlin/com/lambda/interaction/request/placing/PlaceManager.kt index d80e576fd..f0e7cdc66 100644 --- a/src/main/kotlin/com/lambda/interaction/request/placing/PlaceManager.kt +++ b/src/main/kotlin/com/lambda/interaction/request/placing/PlaceManager.kt @@ -78,7 +78,7 @@ object PlaceManager : RequestHandler( private var shouldSneak = false private val validSneak: (player: ClientPlayerEntity) -> Boolean = - { player -> shouldSneak == player.isSneaking } + { player -> shouldSneak && !player.isSneaking } override val blockedPositions get() = pendingActions.map { it.context.blockPos } From 9bd50686697b699709cd526fd5297259f3190f28 Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Sat, 23 Aug 2025 16:06:27 +0100 Subject: [PATCH 06/12] improved bridging logic and onlyBelow setting to avoid placement spam if its impossible to reach the supporting position --- .../request/placing/PlaceManager.kt | 2 +- .../request/placing/PlaceRequest.kt | 5 +++-- .../lambda/module/modules/player/Scaffold.kt | 21 ++++++++++++++++++- .../kotlin/com/lambda/task/tasks/BuildTask.kt | 2 +- 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/com/lambda/interaction/request/placing/PlaceManager.kt b/src/main/kotlin/com/lambda/interaction/request/placing/PlaceManager.kt index f0e7cdc66..731f442bf 100644 --- a/src/main/kotlin/com/lambda/interaction/request/placing/PlaceManager.kt +++ b/src/main/kotlin/com/lambda/interaction/request/placing/PlaceManager.kt @@ -78,7 +78,7 @@ object PlaceManager : RequestHandler( private var shouldSneak = false private val validSneak: (player: ClientPlayerEntity) -> Boolean = - { player -> shouldSneak && !player.isSneaking } + { player -> !shouldSneak || player.isSneaking } override val blockedPositions get() = pendingActions.map { it.context.blockPos } diff --git a/src/main/kotlin/com/lambda/interaction/request/placing/PlaceRequest.kt b/src/main/kotlin/com/lambda/interaction/request/placing/PlaceRequest.kt index 65e1efccd..552dc6eda 100644 --- a/src/main/kotlin/com/lambda/interaction/request/placing/PlaceRequest.kt +++ b/src/main/kotlin/com/lambda/interaction/request/placing/PlaceRequest.kt @@ -30,13 +30,14 @@ import net.minecraft.util.math.BlockPos data class PlaceRequest( val contexts: Collection, + val pendingInteractions: MutableCollection, val build: BuildConfig, - val rotation: RotationConfig, val hotbar: HotbarConfig, - val pendingInteractions: MutableCollection, + val rotation: RotationConfig, val onPlace: ((BlockPos) -> Unit)? = null ) : Request(), PlaceConfig by build.placing { override val config = build.placing + override val done: Boolean get() = runSafe { contexts.all { it.expectedState.matches(blockState(it.blockPos)) } diff --git a/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt b/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt index 31081a70c..164c32553 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt @@ -61,6 +61,7 @@ object Scaffold : Module( } private val bridgeRange by setting("Bridge Range", 5, 0..5, 1, "The range at which blocks can be placed to help build support for the player").group(Group.General) + private val onlyBelow by setting("Only Below", true, "Restricts bridging to only below the player to avoid place spam if it's impossible to reach the supporting position") { bridgeRange > 0 }.group(Group.General) private val descend by setting("Descend", KeyCode.UNBOUND, "Lower the place position by one to allow the player to lower y level").group(Group.General) private val buildConfig = BuildSettings(this, Group.Build) private val rotationConfig = RotationSettings(this, Group.Rotation) @@ -85,7 +86,7 @@ object Scaffold : Module( .map { it.context } .distinctBy { it.blockPos } .sortedWith { o1, o2 -> getBridgeCompareBy(beneath).compare(o1, o2) } - submit(PlaceRequest(contexts, buildConfig, rotationConfig, hotbarConfig, pendingActions)) + submit(PlaceRequest(contexts, pendingActions, buildConfig, hotbarConfig, rotationConfig)) } } } @@ -95,11 +96,29 @@ object Scaffold : Module( return BlockPos .iterateOutwards(beneath, bridgeRange, bridgeRange, bridgeRange) + .asSequence() .map { it.blockPos } + .run { + if (onlyBelow) filter { it.y <= beneath.y } + else this + } + .filter { placingCloserToCenter(it, beneath) } + .toList() } private fun getBridgeCompareBy(blockPos: BlockPos) = compareBy { it.blockPos.toCenterPos() distSq blockPos.toCenterPos() } + + private fun SafeContext.placingCloserToCenter(blockPos: BlockPos, center: BlockPos): Boolean { + val potentials = mutableListOf() + Direction.entries.forEach { direction -> + val offset = blockPos.offset(direction) + val offsetState = blockState(offset) + if (!offsetState.isReplaceable) potentials.add(offset) + } + val trueCenter = center.toCenterPos() + return potentials.any { it.toCenterPos() distSq trueCenter > blockPos.toCenterPos() distSq trueCenter } + } } diff --git a/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt b/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt index 5b71300df..1cf49db75 100644 --- a/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt @@ -162,7 +162,7 @@ class BuildTask @Ta5kBuilder constructor( .take(emptyPendingInteractionSlots) .map { it.context } - PlaceRequest(placeResults, build, rotation, hotbar, pendingInteractions) { placements++ }.submit() + PlaceRequest(placeResults, pendingInteractions, build, hotbar, rotation) { placements++ }.submit() } is InteractResult.Interact -> { val interactResults = resultsNotBlocked From 563137a365a13587792221f546b87283651032d6 Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Sat, 23 Aug 2025 16:08:40 +0100 Subject: [PATCH 07/12] =?UTF-8?q?=3F:=20return@listen=20over=20if=20null?= =?UTF-8?q?=20=F0=9F=A4=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt b/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt index 164c32553..d962f7927 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt @@ -74,8 +74,7 @@ object Scaffold : Module( init { listen { val beneath = player.blockPos.offset(Direction.DOWN, if (isKeyPressed(descend.code)) 2 else 1) - val placements = getPlacements(beneath) - if (placements == null) return@listen + val placements = getPlacements(beneath) ?: return@listen placements .associate { it to TargetState.Solid } .toBlueprint() From 57409c943438bdc71b5f2adf87f702499b1c55e8 Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Mon, 25 Aug 2025 15:59:31 +0100 Subject: [PATCH 08/12] only take first placement as other valid placements wont have the correct simulation immediately after one is placed. --- .../com/lambda/module/modules/player/Scaffold.kt | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt b/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt index d962f7927..60fd1e61f 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt @@ -37,6 +37,7 @@ import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.BlockUtils.blockPos import com.lambda.util.BlockUtils.blockState +import com.lambda.util.BlockUtils.isNotEmpty import com.lambda.util.KeyCode import com.lambda.util.KeyboardUtils.isKeyPressed import com.lambda.util.NamedEnum @@ -81,11 +82,12 @@ object Scaffold : Module( .simulate(player.eyePos, interactionConfig, rotationConfig, inventoryConfig, buildConfig) .filterIsInstance() .let { results -> - val contexts = results + val context = results .map { it.context } .distinctBy { it.blockPos } .sortedWith { o1, o2 -> getBridgeCompareBy(beneath).compare(o1, o2) } - submit(PlaceRequest(contexts, pendingActions, buildConfig, hotbarConfig, rotationConfig)) + .firstOrNull() ?: return@listen + submit(PlaceRequest(setOf(context), pendingActions, buildConfig, hotbarConfig, rotationConfig)) } } } @@ -115,9 +117,11 @@ object Scaffold : Module( Direction.entries.forEach { direction -> val offset = blockPos.offset(direction) val offsetState = blockState(offset) - if (!offsetState.isReplaceable) potentials.add(offset) + if (offsetState.isNotEmpty) potentials.add(offset) } val trueCenter = center.toCenterPos() - return potentials.any { it.toCenterPos() distSq trueCenter > blockPos.toCenterPos() distSq trueCenter } + return potentials.isEmpty() || potentials.any { + it.toCenterPos() distSq trueCenter > blockPos.toCenterPos() distSq trueCenter + } } } From f3532d7529fc9aa04c230afe2aaffeca432a507d Mon Sep 17 00:00:00 2001 From: Constructor Date: Mon, 25 Aug 2025 20:38:50 +0200 Subject: [PATCH 09/12] Cleanup --- .../com/lambda/config/groups/PlaceSettings.kt | 2 +- .../construction/simulation/BuildSimulator.kt | 10 +-- .../request/placing/PlaceConfig.kt | 4 +- .../lambda/module/modules/player/Scaffold.kt | 69 ++++++++----------- 4 files changed, 35 insertions(+), 50 deletions(-) diff --git a/src/main/kotlin/com/lambda/config/groups/PlaceSettings.kt b/src/main/kotlin/com/lambda/config/groups/PlaceSettings.kt index 139186442..d49d51c31 100644 --- a/src/main/kotlin/com/lambda/config/groups/PlaceSettings.kt +++ b/src/main/kotlin/com/lambda/config/groups/PlaceSettings.kt @@ -31,7 +31,7 @@ class PlaceSettings( ) : PlaceConfig { override val rotateForPlace by c.setting("Rotate For Place", true, "Rotate towards block while placing", visibility = vis).group(groupPath) override val airPlace by c.setting("Air Place", AirPlaceMode.None, "Allows for placing blocks without adjacent faces", visibility = vis).group(groupPath) - override val axisRotateSetting by c.setting("Axis Rotate", true, "Overrides the Rotate For Place setting and rotates the player on each axis to air place rotational blocks") { vis() && airPlace.isEnabled() }.group(groupPath) + override val axisRotateSetting by c.setting("Axis Rotate", true, "Overrides the Rotate For Place setting and rotates the player on each axis to air place rotational blocks") { vis() && airPlace.isEnabled }.group(groupPath) override val placeStageMask by c.setting("Place Sequence Mode", setOf(TickEvent.Pre, TickEvent.Input.Pre, TickEvent.Player.Post), description = "The sub-tick timing at which break actions are performed", visibility = vis).group(groupPath) override val placeConfirmationMode by c.setting("Place Confirmation", PlaceConfirmationMode.PlaceThenAwait, "Wait for block placement confirmation", visibility = vis).group(groupPath) override val maxPendingPlacements by c.setting("Max Pending Placements", 5, 0..30, 1, "The maximum amount of pending placements", visibility = vis).group(groupPath) diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt index ea05b6026..cb3156369 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt @@ -203,7 +203,7 @@ object BuildSimulator { val validHits = mutableListOf() val blockedHits = mutableSetOf() val misses = mutableSetOf() - val airPlace = placing && place.airPlace.isEnabled() + val airPlace = placing && place.airPlace.isEnabled boxes.forEach { box -> val refinedSides = if (interactionConfig.checkSideVisibility) { @@ -396,13 +396,13 @@ object BuildSimulator { if (!currentState.isReplaceable && !statePromoting) return acc preProcessing.sides.forEach { neighbor -> - val hitPos = if (!place.airPlace.isEnabled() && (currentState.isEmpty || statePromoting)) + val hitPos = if (!place.airPlace.isEnabled && (currentState.isEmpty || statePromoting)) pos.offset(neighbor) else pos val hitSide = neighbor.opposite val voxelShape = blockState(hitPos).getOutlineShape(world, hitPos).let { outlineShape -> - if (!outlineShape.isEmpty || !place.airPlace.isEnabled()) outlineShape + if (!outlineShape.isEmpty || !place.airPlace.isEnabled) outlineShape else VoxelShapes.fullCube() } if (voxelShape.isEmpty) return@forEach @@ -433,10 +433,10 @@ object BuildSimulator { val hit = if (interactionConfig.strictRayCast) { val rayCast = newRotation.rayCast(interactionConfig.interactReach, eye) when { - rayCast != null && (!place.airPlace.isEnabled() || eye distSq rayCast.pos <= distSquared) -> + rayCast != null && (!place.airPlace.isEnabled || eye distSq rayCast.pos <= distSquared) -> rayCast.blockResult - place.airPlace.isEnabled() -> { + place.airPlace.isEnabled -> { val hitVec = newRotation.castBox(box, interactionConfig.interactReach, eye) BlockHitResult(hitVec, hitSide, hitPos, false) } diff --git a/src/main/kotlin/com/lambda/interaction/request/placing/PlaceConfig.kt b/src/main/kotlin/com/lambda/interaction/request/placing/PlaceConfig.kt index 5240c2335..79841ab2c 100644 --- a/src/main/kotlin/com/lambda/interaction/request/placing/PlaceConfig.kt +++ b/src/main/kotlin/com/lambda/interaction/request/placing/PlaceConfig.kt @@ -28,7 +28,7 @@ interface PlaceConfig : RequestConfig { val airPlace: AirPlaceMode val axisRotateSetting: Boolean val axisRotate - get() = rotateForPlace && airPlace.isEnabled() && axisRotateSetting + get() = rotateForPlace && airPlace.isEnabled && axisRotateSetting val placeStageMask: Set val placeConfirmationMode: PlaceConfirmationMode val maxPendingPlacements: Int @@ -46,7 +46,7 @@ interface PlaceConfig : RequestConfig { Grim("Grim", "Use grim specific air placing.") ; - fun isEnabled() = this != None + val isEnabled get() = this != None } enum class PlaceConfirmationMode( diff --git a/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt b/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt index 60fd1e61f..c61e474bc 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt @@ -23,11 +23,11 @@ import com.lambda.config.groups.InteractionSettings import com.lambda.config.groups.InventorySettings import com.lambda.config.groups.RotationSettings import com.lambda.context.SafeContext +import com.lambda.event.events.MovementEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.construction.blueprint.StaticBlueprint.Companion.toBlueprint import com.lambda.interaction.construction.context.BuildContext -import com.lambda.interaction.construction.context.PlaceContext import com.lambda.interaction.construction.result.PlaceResult import com.lambda.interaction.construction.simulation.BuildSimulator.simulate import com.lambda.interaction.construction.verify.TargetState @@ -37,7 +37,6 @@ import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.BlockUtils.blockPos import com.lambda.util.BlockUtils.blockState -import com.lambda.util.BlockUtils.isNotEmpty import com.lambda.util.KeyCode import com.lambda.util.KeyboardUtils.isKeyPressed import com.lambda.util.NamedEnum @@ -61,9 +60,9 @@ object Scaffold : Module( Inventory("Inventory") } - private val bridgeRange by setting("Bridge Range", 5, 0..5, 1, "The range at which blocks can be placed to help build support for the player").group(Group.General) - private val onlyBelow by setting("Only Below", true, "Restricts bridging to only below the player to avoid place spam if it's impossible to reach the supporting position") { bridgeRange > 0 }.group(Group.General) + private val bridgeRange by setting("Bridge Range", 5, 0..5, 1, "The range at which blocks can be placed to help build support for the player", unit = " blocks").group(Group.General) private val descend by setting("Descend", KeyCode.UNBOUND, "Lower the place position by one to allow the player to lower y level").group(Group.General) + private val descendAmount by setting("Descend Amount", 1, 1..5, 1, "The amount to lower the place position by when descending", unit = " blocks") { descend != KeyCode.UNBOUND }.group(Group.General) private val buildConfig = BuildSettings(this, Group.Build) private val rotationConfig = RotationSettings(this, Group.Rotation) private val interactionConfig = InteractionSettings(this, Group.Interaction, InteractionMask.Block) @@ -74,54 +73,40 @@ object Scaffold : Module( init { listen { - val beneath = player.blockPos.offset(Direction.DOWN, if (isKeyPressed(descend.code)) 2 else 1) - val placements = getPlacements(beneath) ?: return@listen - placements - .associate { it to TargetState.Solid } + val offset = if (isKeyPressed(descend.code)) descendAmount + 1 else 1 + val beneath = player.blockPos.offset(Direction.DOWN, offset) + scaffoldPositions(beneath) + .associateWith { TargetState.Solid } .toBlueprint() .simulate(player.eyePos, interactionConfig, rotationConfig, inventoryConfig, buildConfig) .filterIsInstance() - .let { results -> - val context = results - .map { it.context } - .distinctBy { it.blockPos } - .sortedWith { o1, o2 -> getBridgeCompareBy(beneath).compare(o1, o2) } - .firstOrNull() ?: return@listen - submit(PlaceRequest(setOf(context), pendingActions, buildConfig, hotbarConfig, rotationConfig)) + .minByOrNull { it.blockPos distSq beneath } + ?.let { result -> + submit(PlaceRequest( + setOf(result.context), + pendingActions, + buildConfig, + hotbarConfig, + rotationConfig + )) } } + + listen { + if (descend != KeyCode.LEFT_SHIFT && descend != KeyCode.RIGHT_SHIFT) return@listen + it.sneak = false + } } - private fun SafeContext.getPlacements(beneath: BlockPos): List? { - if (!blockState(beneath).isReplaceable) return null + private fun SafeContext.scaffoldPositions(beneath: BlockPos): List { + if (!blockState(beneath).isReplaceable) return emptyList() + if (buildConfig.placing.airPlace.isEnabled) return listOf(beneath) - return BlockPos - .iterateOutwards(beneath, bridgeRange, bridgeRange, bridgeRange) + return BlockPos.iterateOutwards(beneath, bridgeRange, bridgeRange, bridgeRange) .asSequence() + .filter { it.y <= beneath.y } + .filter { blockState(it).isReplaceable } .map { it.blockPos } - .run { - if (onlyBelow) filter { it.y <= beneath.y } - else this - } - .filter { placingCloserToCenter(it, beneath) } .toList() } - - private fun getBridgeCompareBy(blockPos: BlockPos) = - compareBy { - it.blockPos.toCenterPos() distSq blockPos.toCenterPos() - } - - private fun SafeContext.placingCloserToCenter(blockPos: BlockPos, center: BlockPos): Boolean { - val potentials = mutableListOf() - Direction.entries.forEach { direction -> - val offset = blockPos.offset(direction) - val offsetState = blockState(offset) - if (offsetState.isNotEmpty) potentials.add(offset) - } - val trueCenter = center.toCenterPos() - return potentials.isEmpty() || potentials.any { - it.toCenterPos() distSq trueCenter > blockPos.toCenterPos() distSq trueCenter - } - } } From 3bd584f0019aa296ead2edc162fbc3b3dc4f21eb Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Mon, 25 Aug 2025 21:32:55 +0100 Subject: [PATCH 10/12] check bound sneak key although id like to remove this feature --- src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt b/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt index c61e474bc..d30433d56 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt @@ -93,7 +93,7 @@ object Scaffold : Module( } listen { - if (descend != KeyCode.LEFT_SHIFT && descend != KeyCode.RIGHT_SHIFT) return@listen + if (descend.code != mc.options.sneakKey.boundKey.code) return@listen it.sneak = false } } From 76958bf6efcef3af6cad86dbb7f12ec44ab9fd62 Mon Sep 17 00:00:00 2001 From: Constructor Date: Mon, 25 Aug 2025 23:17:02 +0200 Subject: [PATCH 11/12] Bring back only below setting --- src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt b/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt index d30433d56..400dcb4e8 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt @@ -61,6 +61,7 @@ object Scaffold : Module( } private val bridgeRange by setting("Bridge Range", 5, 0..5, 1, "The range at which blocks can be placed to help build support for the player", unit = " blocks").group(Group.General) + private val onlyBelow by setting("Only Below", true, "Restricts bridging to only below the player to avoid place spam if it's impossible to reach the supporting position") { bridgeRange > 0 }.group(Group.General) private val descend by setting("Descend", KeyCode.UNBOUND, "Lower the place position by one to allow the player to lower y level").group(Group.General) private val descendAmount by setting("Descend Amount", 1, 1..5, 1, "The amount to lower the place position by when descending", unit = " blocks") { descend != KeyCode.UNBOUND }.group(Group.General) private val buildConfig = BuildSettings(this, Group.Build) @@ -104,7 +105,7 @@ object Scaffold : Module( return BlockPos.iterateOutwards(beneath, bridgeRange, bridgeRange, bridgeRange) .asSequence() - .filter { it.y <= beneath.y } + .filter { !onlyBelow || it.y <= beneath.y } .filter { blockState(it).isReplaceable } .map { it.blockPos } .toList() From a18a3b0b1d13201d65df2e75b495f078106315aa Mon Sep 17 00:00:00 2001 From: Constructor Date: Mon, 25 Aug 2025 23:28:03 +0200 Subject: [PATCH 12/12] Slightly reduce overplacing --- .../kotlin/com/lambda/module/modules/player/Scaffold.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt b/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt index 400dcb4e8..9b6544e37 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt @@ -43,7 +43,6 @@ import com.lambda.util.NamedEnum import com.lambda.util.math.distSq import com.lambda.util.world.raycast.InteractionMask import net.minecraft.util.math.BlockPos -import net.minecraft.util.math.Direction import java.util.concurrent.ConcurrentLinkedQueue object Scaffold : Module( @@ -74,8 +73,11 @@ object Scaffold : Module( init { listen { - val offset = if (isKeyPressed(descend.code)) descendAmount + 1 else 1 - val beneath = player.blockPos.offset(Direction.DOWN, offset) + val playerSupport = player.blockPos.down() + val alreadySupported = blockState(playerSupport).hasSolidTopSurface(world, playerSupport, player) + if (alreadySupported) return@listen + val offset = if (isKeyPressed(descend.code)) descendAmount else 0 + val beneath = playerSupport.down(offset) scaffoldPositions(beneath) .associateWith { TargetState.Solid } .toBlueprint()