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/interaction/request/placing/PlaceManager.kt b/src/main/kotlin/com/lambda/interaction/request/placing/PlaceManager.kt index d80e576fd..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 0541200b7..9b6544e37 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,99 @@ 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.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.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.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 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) + 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() + 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() + .simulate(player.eyePos, interactionConfig, rotationConfig, inventoryConfig, buildConfig) + .filterIsInstance() + .minByOrNull { it.blockPos distSq beneath } + ?.let { result -> + submit(PlaceRequest( + setOf(result.context), + pendingActions, + buildConfig, + hotbarConfig, + rotationConfig + )) + } } - 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) - } - - // 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 + listen { + if (descend.code != mc.options.sneakKey.boundKey.code) return@listen + it.sneak = false } } - 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) - } + private fun SafeContext.scaffoldPositions(beneath: BlockPos): List { + if (!blockState(beneath).isReplaceable) return emptyList() + if (buildConfig.placing.airPlace.isEnabled) return listOf(beneath) - // 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 + return BlockPos.iterateOutwards(beneath, bridgeRange, bridgeRange, bridgeRange) + .asSequence() + .filter { !onlyBelow || it.y <= beneath.y } + .filter { blockState(it).isReplaceable } + .map { it.blockPos } + .toList() } } 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