From 1480c5694a538e1e33c2e54e7840ea72d206e8fb Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Sun, 19 Oct 2025 14:07:27 +0100 Subject: [PATCH 01/30] initial simulation separation and dependency result structure --- .../construction/context/BreakContext.kt | 18 +- .../construction/context/BuildContext.kt | 6 +- .../context/InteractionContext.kt | 16 +- .../construction/context/PlaceContext.kt | 14 +- .../construction/result/BuildResult.kt | 281 +----- .../construction/result/ComparableResult.kt | 9 +- .../construction/result/Contextual.kt | 24 + .../construction/result/Dependable.kt | 24 + .../construction/result/Dependent.kt | 32 + .../interaction/construction/result/Rank.kt | 55 +- .../result/{ => results}/BreakResult.kt | 94 +- .../result/results/GenericResult.kt | 146 +++ .../result/{ => results}/InteractResult.kt | 26 +- .../result/{ => results}/PlaceResult.kt | 96 +- .../result/results/PostSimResult.kt | 32 + .../result/results/PreSimResult.kt | 136 +++ .../construction/simulation/BuildSimulator.kt | 872 +----------------- .../construction/simulation/SimChecker.kt | 48 + .../construction/simulation/SimInfo.kt | 36 + .../construction/simulation/Simulation.kt | 4 +- .../simulation/checks/BreakChecks.kt | 338 +++++++ .../simulation/checks/PlaceChecks.kt | 323 +++++++ .../simulation/checks/PostProcessingChecks.kt | 224 +++++ .../simulation/checks/RequirementChecks.kt | 75 ++ .../construction/verify/StateMatcher.kt | 9 +- .../construction/verify/TargetState.kt | 53 +- .../interaction/request/breaking/BreakInfo.kt | 2 +- .../request/breaking/BreakManager.kt | 8 +- .../request/interacting/InteractionManager.kt | 4 +- .../request/placing/PlaceManager.kt | 2 +- .../module/modules/player/PacketMine.kt | 4 +- .../lambda/module/modules/player/Scaffold.kt | 6 +- .../kotlin/com/lambda/task/tasks/BuildTask.kt | 180 ++-- .../com/lambda/task/tasks/PlaceContainer.kt | 16 +- 34 files changed, 1824 insertions(+), 1389 deletions(-) create mode 100644 src/main/kotlin/com/lambda/interaction/construction/result/Contextual.kt create mode 100644 src/main/kotlin/com/lambda/interaction/construction/result/Dependable.kt create mode 100644 src/main/kotlin/com/lambda/interaction/construction/result/Dependent.kt rename src/main/kotlin/com/lambda/interaction/construction/result/{ => results}/BreakResult.kt (59%) create mode 100644 src/main/kotlin/com/lambda/interaction/construction/result/results/GenericResult.kt rename src/main/kotlin/com/lambda/interaction/construction/result/{ => results}/InteractResult.kt (53%) rename src/main/kotlin/com/lambda/interaction/construction/result/{ => results}/PlaceResult.kt (57%) create mode 100644 src/main/kotlin/com/lambda/interaction/construction/result/results/PostSimResult.kt create mode 100644 src/main/kotlin/com/lambda/interaction/construction/result/results/PreSimResult.kt create mode 100644 src/main/kotlin/com/lambda/interaction/construction/simulation/SimChecker.kt create mode 100644 src/main/kotlin/com/lambda/interaction/construction/simulation/SimInfo.kt create mode 100644 src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecks.kt create mode 100644 src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecks.kt create mode 100644 src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingChecks.kt create mode 100644 src/main/kotlin/com/lambda/interaction/construction/simulation/checks/RequirementChecks.kt diff --git a/src/main/kotlin/com/lambda/interaction/construction/context/BreakContext.kt b/src/main/kotlin/com/lambda/interaction/construction/context/BreakContext.kt index e6df5e6b9..ebe3c889d 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/context/BreakContext.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/context/BreakContext.kt @@ -41,8 +41,8 @@ import kotlin.math.sqrt import kotlin.random.Random data class BreakContext( - override val result: BlockHitResult, - override val rotation: RotationRequest, + override val hitResult: BlockHitResult, + override val rotationRequest: RotationRequest, override var hotbarIndex: Int, var itemSelection: StackSelection, var instantBreak: Boolean, @@ -53,7 +53,7 @@ data class BreakContext( private val baseColor = Color(222, 0, 0, 25) private val sideColor = Color(222, 0, 0, 100) - override val blockPos: BlockPos = result.blockPos + override val blockPos: BlockPos = hitResult.blockPos override val expectedState = cachedState.emptyState val random = Random.nextDouble() @@ -66,9 +66,9 @@ data class BreakContext( }.thenBy { when (sortMode) { BreakConfig.SortMode.Tool, - BreakConfig.SortMode.Closest -> player.eyePos.distance(it.result.pos, it.cachedState.block) - BreakConfig.SortMode.Farthest -> -player.eyePos.distance(it.result.pos, it.cachedState.block) - BreakConfig.SortMode.Rotation -> it.rotation.target.angleDistance + BreakConfig.SortMode.Closest -> player.eyePos.distance(it.hitResult.pos, it.cachedState.block) + BreakConfig.SortMode.Farthest -> -player.eyePos.distance(it.hitResult.pos, it.cachedState.block) + BreakConfig.SortMode.Rotation -> it.rotationRequest.target.angleDistance BreakConfig.SortMode.Random -> it.random } }.thenByDescending { @@ -92,14 +92,14 @@ data class BreakContext( } override fun ShapeBuilder.buildRenderer() { - box(blockPos, cachedState, baseColor, sideColor, DirectionMask.ALL.exclude(result.side)) + box(blockPos, cachedState, baseColor, sideColor, DirectionMask.ALL.exclude(hitResult.side)) } override fun getLogContextBuilder(): LogContextBuilder.() -> Unit = { group("Break Context") { text(blockPos.getLogContextBuilder()) - text(result.getLogContextBuilder()) - text(rotation.getLogContextBuilder()) + text(hitResult.getLogContextBuilder()) + text(rotationRequest.getLogContextBuilder()) value("Hotbar Index", hotbarIndex) value("Instant Break", instantBreak) value("Cached State", cachedState) diff --git a/src/main/kotlin/com/lambda/interaction/construction/context/BuildContext.kt b/src/main/kotlin/com/lambda/interaction/construction/context/BuildContext.kt index 3a4598d3b..2cd5893b1 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/context/BuildContext.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/context/BuildContext.kt @@ -26,14 +26,14 @@ import net.minecraft.util.hit.BlockHitResult import net.minecraft.util.math.BlockPos abstract class BuildContext : Comparable, Drawable, Automated { - abstract val result: BlockHitResult - abstract val rotation: RotationRequest + abstract val hitResult: BlockHitResult + abstract val rotationRequest: RotationRequest abstract val hotbarIndex: Int abstract val cachedState: BlockState abstract val expectedState: BlockState abstract val blockPos: BlockPos val distance by lazy { - runSafe { player.eyePos.distanceTo(result.pos) } ?: Double.MAX_VALUE + runSafe { player.eyePos.distanceTo(hitResult.pos) } ?: Double.MAX_VALUE } } diff --git a/src/main/kotlin/com/lambda/interaction/construction/context/InteractionContext.kt b/src/main/kotlin/com/lambda/interaction/construction/context/InteractionContext.kt index 8e2bd04e2..720bb69c6 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/context/InteractionContext.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/context/InteractionContext.kt @@ -35,8 +35,8 @@ import net.minecraft.util.math.BlockPos import java.awt.Color class InteractionContext( - override val result: BlockHitResult, - override val rotation: RotationRequest, + override val hitResult: BlockHitResult, + override val rotationRequest: RotationRequest, override var hotbarIndex: Int, override var cachedState: BlockState, override val expectedState: BlockState, @@ -45,7 +45,7 @@ class InteractionContext( private val baseColor = Color(35, 254, 79, 25) private val sideColor = Color(35, 254, 79, 100) - override val blockPos: BlockPos = result.blockPos + override val blockPos: BlockPos = hitResult.blockPos override fun compareTo(other: BuildContext) = when { @@ -54,7 +54,7 @@ class InteractionContext( }.thenByDescending { it.cachedState.fluidState.level }.thenBy { - it.rotation.target.angleDistance + it.rotationRequest.target.angleDistance }.thenBy { it.hotbarIndex == HotbarManager.serverSlot }.thenBy { @@ -65,20 +65,20 @@ class InteractionContext( } override fun ShapeBuilder.buildRenderer() { - box(blockPos, expectedState, baseColor, sideColor, result.side.mask) + box(blockPos, expectedState, baseColor, sideColor, hitResult.side.mask) } fun requestDependencies(request: InteractRequest): Boolean { val hotbarRequest = submit(HotbarRequest(hotbarIndex, request), false) - val validRotation = if (request.interactConfig.rotate) submit(rotation, false).done else true + val validRotation = if (request.interactConfig.rotate) submit(rotationRequest, false).done else true return hotbarRequest.done && validRotation } override fun getLogContextBuilder(): LogContextBuilder.() -> Unit = { group("Interaction Context") { text(blockPos.getLogContextBuilder()) - text(result.getLogContextBuilder()) - text(rotation.getLogContextBuilder()) + text(hitResult.getLogContextBuilder()) + text(rotationRequest.getLogContextBuilder()) value("Hotbar Index", hotbarIndex) value("Cached State", cachedState) value("Expected State", expectedState) diff --git a/src/main/kotlin/com/lambda/interaction/construction/context/PlaceContext.kt b/src/main/kotlin/com/lambda/interaction/construction/context/PlaceContext.kt index 752bce524..575429153 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/context/PlaceContext.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/context/PlaceContext.kt @@ -36,8 +36,8 @@ import net.minecraft.util.math.BlockPos import java.awt.Color data class PlaceContext( - override val result: BlockHitResult, - override val rotation: RotationRequest, + override val hitResult: BlockHitResult, + override val rotationRequest: RotationRequest, override var hotbarIndex: Int, override val blockPos: BlockPos, override var cachedState: BlockState, @@ -61,7 +61,7 @@ data class PlaceContext( }.thenBy { it.sneak == (mc.player?.isSneaking ?: false) }.thenBy { - it.rotation.target.angleDistance + it.rotationRequest.target.angleDistance }.thenBy { it.hotbarIndex == HotbarManager.serverSlot }.thenBy { @@ -74,13 +74,13 @@ data class PlaceContext( } override fun ShapeBuilder.buildRenderer() { - box(blockPos, expectedState, baseColor, sideColor, result.side.mask) + box(blockPos, expectedState, baseColor, sideColor, hitResult.side.mask) } fun requestDependencies(request: PlaceRequest): Boolean { val hotbarRequest = submit(HotbarRequest(hotbarIndex, this), false) val validRotation = if (request.placeConfig.rotateForPlace) { - submit(rotation, false).done && currentDirIsValid + submit(rotationRequest, false).done && currentDirIsValid } else true return hotbarRequest.done && validRotation } @@ -88,8 +88,8 @@ data class PlaceContext( override fun getLogContextBuilder(): LogContextBuilder.() -> Unit = { group("Place Context") { text(blockPos.getLogContextBuilder()) - text(result.getLogContextBuilder()) - text(rotation.getLogContextBuilder()) + text(hitResult.getLogContextBuilder()) + text(rotationRequest.getLogContextBuilder()) value("Hotbar Index", hotbarIndex) value("Cached State", cachedState) value("Expected State", expectedState) diff --git a/src/main/kotlin/com/lambda/interaction/construction/result/BuildResult.kt b/src/main/kotlin/com/lambda/interaction/construction/result/BuildResult.kt index 9b2c98e7e..4a6e5eb57 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/result/BuildResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/result/BuildResult.kt @@ -17,281 +17,16 @@ package com.lambda.interaction.construction.result -import baritone.api.pathing.goals.GoalBlock -import baritone.api.pathing.goals.GoalNear -import com.lambda.context.Automated -import com.lambda.graphics.renderer.esp.ShapeBuilder -import com.lambda.interaction.construction.context.BuildContext -import com.lambda.interaction.material.StackSelection -import com.lambda.interaction.material.StackSelection.Companion.select -import com.lambda.interaction.material.container.ContainerManager.transfer -import com.lambda.interaction.material.container.MaterialContainer -import com.lambda.interaction.material.container.containers.MainHandContainer import com.lambda.util.Nameable -import net.minecraft.block.BlockState -import net.minecraft.item.ItemStack import net.minecraft.util.math.BlockPos -import net.minecraft.util.math.Direction -import net.minecraft.util.math.Vec3d -import java.awt.Color -abstract class BuildResult : ComparableResult, Nameable { - abstract val blockPos: BlockPos - open val pausesParent = false - override val name: String get() = "${this::class.simpleName} at ${blockPos.toShortString()}" +abstract class BuildResult : Nameable, ComparableResult() { + abstract val pos: BlockPos + override val compareBy = this - interface Contextual { - val context: BuildContext - } + final override fun compareTo(other: ComparableResult) = + compareBy.compareResult(other.compareBy) - /** - * The build action is done. - */ - data class Done( - override val blockPos: BlockPos, - ) : BuildResult() { - override val name: String - get() = "Build at $blockPos is done." - override val rank = Rank.DONE - } - - /** - * The build action is ignored. - */ - data class Ignored( - override val blockPos: BlockPos, - ) : BuildResult() { - override val name: String - get() = "Build at $blockPos is ignored." - override val rank = Rank.IGNORED - } - - /** - * The chunk at the target is not loaded. - * @param blockPos The position of the block that is in an unloaded chunk. - */ - data class ChunkNotLoaded( - override val blockPos: BlockPos, - ) : Navigable, Drawable, BuildResult() { - override val name: String get() = "Chunk at $blockPos is not loaded." - override val rank = Rank.CHUNK_NOT_LOADED - private val color = Color(252, 165, 3, 100) - - override val goal = GoalBlock(blockPos) - - override fun ShapeBuilder.buildRenderer() { - box(blockPos, color, color) - } - - override fun compareTo(other: ComparableResult): Int { - return when (other) { - is ChunkNotLoaded -> blockPos.compareTo(other.blockPos) - else -> super.compareTo(other) - } - } - } - - /** - * The player has no permission to interact with the block. (E.g.: Adventure mode) - * @param blockPos The position of the block that is restricted. - */ - data class Restricted( - override val blockPos: BlockPos, - ) : Drawable, BuildResult() { - override val name: String get() = "Restricted at $blockPos." - override val rank = Rank.BREAK_RESTRICTED - private val color = Color(255, 0, 0, 100) - - override fun ShapeBuilder.buildRenderer() { - box(blockPos, color, color) - } - } - - /** - * The block needs server permission to be broken. (Needs op) - * @param blockPos The position of the block that needs permission. - * @param blockState The state of the block that needs permission. - */ - data class NoPermission( - override val blockPos: BlockPos, - val blockState: BlockState, - ) : Drawable, BuildResult() { - override val name: String get() = "No permission at $blockPos." - override val rank get() = Rank.BREAK_NO_PERMISSION - private val color = Color(255, 0, 0, 100) - - override fun ShapeBuilder.buildRenderer() { - box(blockPos, color, color) - } - } - - /** - * The break target is out of the world border or height limit. - * @param blockPos The position of the block that is out of the world. - */ - data class OutOfWorld( - override val blockPos: BlockPos, - ) : Drawable, BuildResult() { - override val name: String get() = "$blockPos is out of the world." - override val rank = Rank.OUT_OF_WORLD - private val color = Color(3, 148, 252, 100) - - override fun ShapeBuilder.buildRenderer() { - box(blockPos, color, color) - } - } - - /** - * The block is unbreakable. - * @param blockPos The position of the block that is unbreakable. - * @param blockState The state of the block that is unbreakable. - */ - data class Unbreakable( - override val blockPos: BlockPos, - val blockState: BlockState, - ) : Drawable, BuildResult() { - override val name: String get() = "Unbreakable at $blockPos." - override val rank = Rank.UNBREAKABLE - private val color = Color(11, 11, 11, 100) - - override fun ShapeBuilder.buildRenderer() { - box(blockPos, color, color) - } - } - - /** - * The checked configuration hits on a side not in the player direction. - * @param blockPos The position of the block that is not exposed. - * @param side The side that is not exposed. - */ - data class NotVisible( - override val blockPos: BlockPos, - val hitPos: BlockPos, - val side: Direction, - val distance: Double, - ) : Drawable, BuildResult() { - override val name: String get() = "Not visible at $blockPos." - override val rank = Rank.NOT_VISIBLE - private val color = Color(46, 0, 0, 80) - - override fun ShapeBuilder.buildRenderer() { - box(blockPos, color, color) - } - - override fun compareTo(other: ComparableResult): Int { - return when (other) { - is NotVisible -> distance.compareTo(other.distance) - else -> super.compareTo(other) - } - } - } - - /** - * Player has an inefficient tool equipped. - * @param neededSelection The best tool for the block state. - */ - data class WrongItemSelection( - override val blockPos: BlockPos, - val context: BuildContext, - val neededSelection: StackSelection, - val currentItem: ItemStack - ) : Drawable, Resolvable, BuildResult() { - override val name: String get() = "Wrong item ($currentItem) for ${blockPos.toShortString()} need $neededSelection" - override val rank = Rank.WRONG_ITEM - private val color = Color(3, 252, 169, 25) - - override val pausesParent get() = true - - context(automated: Automated) - override fun resolve() = - neededSelection.transfer(MainHandContainer) - ?: MaterialContainer.AwaitItemTask( - "Couldn't find $neededSelection anywhere.", - neededSelection, - automated - ) - - override fun ShapeBuilder.buildRenderer() { - box(blockPos, color, color) - } - - override fun compareTo(other: ComparableResult): Int { - return when (other) { - is WrongItemSelection -> context.compareTo(other.context) - else -> super.compareTo(other) - } - } - } - - /** - * The Player has the wrong item stack selected. - * @param blockPos The position of the block that needs a different tool. - * @param neededStack The best tool for the block state. - */ - data class WrongStack( - override val blockPos: BlockPos, - val context: BuildContext, - val neededStack: ItemStack - ) : Drawable, Resolvable, BuildResult() { - override val name: String get() = "Wrong stack for ${blockPos.toShortString()} need $neededStack." - override val rank = Rank.WRONG_ITEM - private val color = Color(3, 252, 169, 25) - - override val pausesParent get() = true - - context(automated: Automated) - override fun resolve() = - neededStack.select().let { selection -> - selection.transfer(MainHandContainer) - ?: MaterialContainer.AwaitItemTask( - "Couldn't find ${neededStack.item.name.string} anywhere.", - selection, - automated - ) - } - - override fun ShapeBuilder.buildRenderer() { - box(blockPos, color, color) - } - - override fun compareTo(other: ComparableResult): Int { - return when (other) { - is WrongItemSelection -> context.compareTo(other.context) - else -> super.compareTo(other) - } - } - } - - /** - * Represents a break out of reach. - * @param blockPos The position of the block that is out of reach. - * @param pov The point of view of the player. - * @param misses The points that are out of reach. - */ - data class OutOfReach( - override val blockPos: BlockPos, - val pov: Vec3d, - val misses: Set, - ) : Navigable, Drawable, BuildResult() { - override val name: String get() = "Out of reach at $blockPos." - override val rank = Rank.OUT_OF_REACH - private val color = Color(252, 3, 207, 25) - - val distance: Double by lazy { - misses.minOfOrNull { pov.distanceTo(it) } ?: 0.0 - } - - override val goal = GoalNear(blockPos, 3) - - override fun ShapeBuilder.buildRenderer() { - box(blockPos, color, color) - } - - override fun compareTo(other: ComparableResult): Int { - return when (other) { - is OutOfReach -> distance.compareTo(other.distance) - else -> super.compareTo(other) - } - } - } -} + open fun compareResult(other: ComparableResult) = + compareBy.rank.compareTo(other.compareBy.rank) +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/construction/result/ComparableResult.kt b/src/main/kotlin/com/lambda/interaction/construction/result/ComparableResult.kt index 26639b67e..86c02597e 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/result/ComparableResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/result/ComparableResult.kt @@ -17,10 +17,7 @@ package com.lambda.interaction.construction.result -sealed interface ComparableResult> : Comparable> { - val rank: T - - override fun compareTo(other: ComparableResult): Int { - return rank.compareTo(other.rank) - } +abstract class ComparableResult> : Comparable> { + abstract val rank: T + abstract val compareBy: ComparableResult } diff --git a/src/main/kotlin/com/lambda/interaction/construction/result/Contextual.kt b/src/main/kotlin/com/lambda/interaction/construction/result/Contextual.kt new file mode 100644 index 000000000..71b314d9a --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/construction/result/Contextual.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.interaction.construction.result + +import com.lambda.interaction.construction.context.BuildContext + +interface Contextual { + val context: BuildContext +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/construction/result/Dependable.kt b/src/main/kotlin/com/lambda/interaction/construction/result/Dependable.kt new file mode 100644 index 000000000..52fedcc5e --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/construction/result/Dependable.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.interaction.construction.result + +import com.lambda.interaction.construction.simulation.SimInfo + +interface Dependable { + fun SimInfo.asDependant(buildResult: BuildResult): BuildResult +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/construction/result/Dependent.kt b/src/main/kotlin/com/lambda/interaction/construction/result/Dependent.kt new file mode 100644 index 000000000..d688892bc --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/construction/result/Dependent.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.interaction.construction.result + +interface Dependent { + val dependency: BuildResult + val lastDependency: BuildResult + + companion object { + val Dependent.iterator + get() = generateSequence(dependency) { (it as? Dependent)?.dependency } + } + + class Nested(override val dependency: BuildResult) : Dependent { + override val lastDependency = iterator.last() + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/construction/result/Rank.kt b/src/main/kotlin/com/lambda/interaction/construction/result/Rank.kt index 699ea70cf..8296780c8 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/result/Rank.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/result/Rank.kt @@ -19,37 +19,38 @@ package com.lambda.interaction.construction.result enum class Rank { // solvable - BREAK_SUCCESS, - INTERACT_SUCCESS, - PLACE_SUCCESS, - WRONG_ITEM, - BREAK_ITEM_CANT_MINE, - PLACE_BLOCKED_BY_PLAYER, - NOT_VISIBLE, - OUT_OF_REACH, - BREAK_NOT_EXPOSED, - CHUNK_NOT_LOADED, - PLACE_CANT_REPLACE, - BREAK_PLAYER_ON_TOP, - PLACE_NOT_ITEM_BLOCK, + PlaceSuccess, + BreakSuccess, + InteractSuccess, + WrongItem, + BreakItemCantMine, + PlaceBlockedByPlayer, + NotVisible, + OutOfReach, + BreakNotExposed, + ChunkNotLoaded, + PlaceCantReplace, + BreakPlayerOnTop, + PlaceNotItemBlock, // not solvable - OUT_OF_WORLD, - BREAK_RESTRICTED, - PLACE_NO_INTEGRITY, - BREAK_SUBMERGE, - BREAK_IS_BLOCKED_BY_FLUID, - UNBREAKABLE, - BREAK_NO_PERMISSION, - PLACE_SCAFFOLD_EXCEEDED, - PLACE_BLOCK_FEATURE_DISABLED, - UNEXPECTED_POSITION, - PLACE_ILLEGAL_USAGE, + OutOfWorld, + BreakRestricted, + PlaceNoIntegrity, + BreakSubmerge, + BreakIsBlockedByFluid, + Unbreakable, + BreakNoPermission, + PlaceScaffoldExceeded, + PlaceBlockFeatureDisabled, + UnexpectedPosition, + PlaceIllegalUsage, // not an issue - DONE, - IGNORED; + Done, + Ignored, + NoMatch; val solvable: Boolean - get() = ordinal < PLACE_NOT_ITEM_BLOCK.ordinal + get() = ordinal < PlaceNotItemBlock.ordinal } diff --git a/src/main/kotlin/com/lambda/interaction/construction/result/BreakResult.kt b/src/main/kotlin/com/lambda/interaction/construction/result/results/BreakResult.kt similarity index 59% rename from src/main/kotlin/com/lambda/interaction/construction/result/BreakResult.kt rename to src/main/kotlin/com/lambda/interaction/construction/result/results/BreakResult.kt index b7b8ef340..2362b7bb0 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/result/BreakResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/result/results/BreakResult.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.construction.result +package com.lambda.interaction.construction.result.results import baritone.api.pathing.goals.GoalBlock import baritone.api.pathing.goals.GoalInverted @@ -23,6 +23,14 @@ import com.lambda.context.Automated import com.lambda.graphics.renderer.esp.DirectionMask.mask import com.lambda.graphics.renderer.esp.ShapeBuilder import com.lambda.interaction.construction.context.BreakContext +import com.lambda.interaction.construction.result.BuildResult +import com.lambda.interaction.construction.result.ComparableResult +import com.lambda.interaction.construction.result.Contextual +import com.lambda.interaction.construction.result.Dependent +import com.lambda.interaction.construction.result.Drawable +import com.lambda.interaction.construction.result.Navigable +import com.lambda.interaction.construction.result.Rank +import com.lambda.interaction.construction.result.Resolvable import com.lambda.interaction.material.StackSelection.Companion.selectStack import com.lambda.interaction.material.container.ContainerManager.transfer import com.lambda.interaction.material.container.MaterialContainer @@ -34,51 +42,50 @@ import net.minecraft.util.math.Direction import java.awt.Color sealed class BreakResult : BuildResult() { - /** * Represents a successful break. All checks have been passed. * @param context The context of the break. */ data class Break( - override val blockPos: BlockPos, + override val pos: BlockPos, override val context: BreakContext, ) : Drawable, Contextual, BreakResult() { - override val rank = Rank.BREAK_SUCCESS + override val name: String get() = "${this::class.simpleName} at ${pos.toShortString()}" + override val rank = Rank.BreakSuccess override fun ShapeBuilder.buildRenderer() { with(context) { buildRenderer() } } - override fun compareTo(other: ComparableResult): Int { - return when (other) { + override fun compareResult(other: ComparableResult) = + when (other) { is Break -> context.compareTo(other.context) - else -> super.compareTo(other) + else -> super.compareResult(other) } - } } /** * Represents a break configuration where the hit side is not exposed to air. - * @param blockPos The position of the block that is not exposed. + * @param pos The position of the block that is not exposed. * @param side The side that is not exposed. */ data class NotExposed( - override val blockPos: BlockPos, + override val pos: BlockPos, val side: Direction, ) : Drawable, BreakResult() { - override val rank = Rank.BREAK_NOT_EXPOSED + override val name: String get() = "${this::class.simpleName} at ${pos.toShortString()}" + override val rank = Rank.BreakNotExposed private val color = Color(46, 0, 0, 30) override fun ShapeBuilder.buildRenderer() { - box(blockPos, color, color, side.mask) + box(pos, color, color, side.mask) } - override fun compareTo(other: ComparableResult): Int { - return when (other) { - is NotExposed -> blockPos.compareTo(other.blockPos) - else -> super.compareTo(other) + override fun compareResult(other: ComparableResult) = + when (other) { + is NotExposed -> pos.compareTo(other.pos) + else -> super.compareResult(other) } - } } /** @@ -87,15 +94,14 @@ sealed class BreakResult : BuildResult() { * @param badItem The item that is being used. */ data class ItemCantMine( - override val blockPos: BlockPos, + override val pos: BlockPos, val blockState: BlockState, val badItem: Item ) : Drawable, Resolvable, BreakResult() { - override val rank = Rank.BREAK_ITEM_CANT_MINE + override val name: String get() = "${this::class.simpleName} at ${pos.toShortString()}" + override val rank = Rank.BreakItemCantMine private val color = Color(255, 0, 0, 100) - override val pausesParent get() = true - context(automated: Automated) override fun resolve() = selectStack { @@ -110,31 +116,30 @@ sealed class BreakResult : BuildResult() { } override fun ShapeBuilder.buildRenderer() { - box(blockPos, color, color) + box(pos, color, color) } - override fun compareTo(other: ComparableResult): Int { - return when (other) { + override fun compareResult(other: ComparableResult) = + when (other) { is ItemCantMine -> badItem.name.string.compareTo(other.badItem.name.string) - else -> super.compareTo(other) + else -> super.compareResult(other) } - } } /** * The block is a liquid and first has to be submerged. - * @param blockPos The position of the block that is a liquid. + * @param pos The position of the block that is a liquid. */ data class Submerge( - override val blockPos: BlockPos, - val blockState: BlockState, - val submerge: Set, + override val pos: BlockPos, + val blockState: BlockState ) : Drawable, BreakResult() { - override val rank = Rank.BREAK_SUBMERGE + override val name: String get() = "${this::class.simpleName} at ${pos.toShortString()}" + override val rank = Rank.BreakSubmerge private val color = Color(114, 27, 255, 100) override fun ShapeBuilder.buildRenderer() { - box(blockPos, color, color) + box(pos, color, color) } } @@ -142,14 +147,15 @@ sealed class BreakResult : BuildResult() { * The block is blocked by another liquid block that first has to be submerged. */ data class BlockedByFluid( - override val blockPos: BlockPos, + override val pos: BlockPos, val blockState: BlockState, ) : Drawable, BreakResult() { - override val rank = Rank.BREAK_IS_BLOCKED_BY_FLUID + override val name: String get() = "${this::class.simpleName} at ${pos.toShortString()}" + override val rank = Rank.BreakIsBlockedByFluid private val color = Color(50, 12, 112, 100) override fun ShapeBuilder.buildRenderer() { - box(blockPos, color, color) + box(pos, color, color) } } @@ -157,16 +163,26 @@ sealed class BreakResult : BuildResult() { * The player is standing on the block. */ data class PlayerOnTop( - override val blockPos: BlockPos, + override val pos: BlockPos, val blockState: BlockState, ) : Navigable, Drawable, BreakResult() { - override val rank = Rank.BREAK_PLAYER_ON_TOP + override val name: String get() = "${this::class.simpleName} at ${pos.toShortString()}" + override val rank = Rank.BreakPlayerOnTop private val color = Color(252, 3, 207, 100) - override val goal = GoalInverted(GoalBlock(blockPos)) + override val goal = GoalInverted(GoalBlock(pos)) override fun ShapeBuilder.buildRenderer() { - box(blockPos, color, color) + box(pos, color, color) } } + + data class Dependency( + override val pos: BlockPos, + override val dependency: BuildResult + ) : BreakResult(), Dependent by Dependent.Nested(dependency) { + override val name: String get() = "${this::class.simpleName} at ${pos.toShortString()}" + override val rank = dependency.rank + override val compareBy = lastDependency + } } diff --git a/src/main/kotlin/com/lambda/interaction/construction/result/results/GenericResult.kt b/src/main/kotlin/com/lambda/interaction/construction/result/results/GenericResult.kt new file mode 100644 index 000000000..b016eea71 --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/construction/result/results/GenericResult.kt @@ -0,0 +1,146 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.interaction.construction.result.results + +import baritone.api.pathing.goals.GoalNear +import com.lambda.context.Automated +import com.lambda.graphics.renderer.esp.ShapeBuilder +import com.lambda.interaction.construction.context.BuildContext +import com.lambda.interaction.construction.result.BuildResult +import com.lambda.interaction.construction.result.ComparableResult +import com.lambda.interaction.construction.result.Drawable +import com.lambda.interaction.construction.result.Navigable +import com.lambda.interaction.construction.result.Rank +import com.lambda.interaction.construction.result.Resolvable +import com.lambda.interaction.material.StackSelection +import com.lambda.interaction.material.container.ContainerManager.transfer +import com.lambda.interaction.material.container.MaterialContainer +import com.lambda.interaction.material.container.containers.MainHandContainer +import net.minecraft.item.ItemStack +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Direction +import net.minecraft.util.math.Vec3d +import java.awt.Color + +sealed class GenericResult : BuildResult() { + /** + * The checked configuration hits on a side not in the player direction. + * @param pos The position of the block that is not exposed. + * @param side The side that is not exposed. + */ + data class NotVisible( + override val pos: BlockPos, + val hitPos: BlockPos, + val side: Direction, + val distance: Double, + ) : Drawable, GenericResult() { + override val name: String get() = "Not visible at $pos." + override val rank = Rank.NotVisible + private val color = Color(46, 0, 0, 80) + + override fun ShapeBuilder.buildRenderer() { + box(pos, color, color) + } + + override fun compareResult(other: ComparableResult): Int { + return when (other) { + is NotVisible -> distance.compareTo(other.distance) + else -> super.compareResult(other) + } + } + } + + /** + * The build action is ignored. + */ + data class Ignored( + override val pos: BlockPos, + ) : GenericResult() { + override val name: String + get() = "Build at $pos is ignored." + override val rank = Rank.Ignored + } + + /** + * Player has an inefficient tool equipped. + * @param neededSelection The best tool for the block state. + */ + data class WrongItemSelection( + override val pos: BlockPos, + val context: BuildContext, + val neededSelection: StackSelection, + val currentItem: ItemStack + ) : Drawable, Resolvable, GenericResult() { + override val name: String get() = "Wrong item ($currentItem) for ${pos.toShortString()} need $neededSelection" + override val rank = Rank.WrongItem + private val color = Color(3, 252, 169, 25) + + context(automated: Automated) + override fun resolve() = + neededSelection.transfer(MainHandContainer) + ?: MaterialContainer.AwaitItemTask( + "Couldn't find $neededSelection anywhere.", + neededSelection, + automated + ) + + override fun ShapeBuilder.buildRenderer() { + box(pos, color, color) + } + + override fun compareResult(other: ComparableResult): Int { + return when (other) { + is WrongItemSelection -> context.compareTo(other.context) + else -> super.compareResult(other) + } + } + } + + /** + * Represents a break out of reach. + * @param pos The position of the block that is out of reach. + * @param pov The point of view of the player. + * @param misses The points that are out of reach. + */ + data class OutOfReach( + override val pos: BlockPos, + val pov: Vec3d, + val misses: Set, + ) : Navigable, Drawable, GenericResult() { + override val name: String get() = "Out of reach at $pos." + override val rank = Rank.OutOfReach + private val color = Color(252, 3, 207, 25) + + val distance: Double by lazy { + misses.minOfOrNull { pov.distanceTo(it) } ?: 0.0 + } + + override val goal = GoalNear(pos, 3) + + override fun ShapeBuilder.buildRenderer() { + box(pos, color, color) + } + + override fun compareResult(other: ComparableResult): Int { + return when (other) { + is OutOfReach -> distance.compareTo(other.distance) + else -> super.compareResult(other) + } + } + } +} diff --git a/src/main/kotlin/com/lambda/interaction/construction/result/InteractResult.kt b/src/main/kotlin/com/lambda/interaction/construction/result/results/InteractResult.kt similarity index 53% rename from src/main/kotlin/com/lambda/interaction/construction/result/InteractResult.kt rename to src/main/kotlin/com/lambda/interaction/construction/result/results/InteractResult.kt index 6b05538d4..729854f60 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/result/InteractResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/result/results/InteractResult.kt @@ -15,27 +15,43 @@ * along with this program. If not, see . */ -package com.lambda.interaction.construction.result +package com.lambda.interaction.construction.result.results import com.lambda.graphics.renderer.esp.ShapeBuilder import com.lambda.interaction.construction.context.InteractionContext +import com.lambda.interaction.construction.result.BuildResult +import com.lambda.interaction.construction.result.ComparableResult +import com.lambda.interaction.construction.result.Contextual +import com.lambda.interaction.construction.result.Dependent +import com.lambda.interaction.construction.result.Drawable +import com.lambda.interaction.construction.result.Rank import net.minecraft.util.math.BlockPos sealed class InteractResult : BuildResult() { data class Interact( - override val blockPos: BlockPos, + override val pos: BlockPos, override val context: InteractionContext ) : Contextual, Drawable, InteractResult() { - override val rank = Rank.INTERACT_SUCCESS + override val name: String get() = "${this::class.simpleName} at ${pos.toShortString()}" + override val rank = Rank.InteractSuccess override fun ShapeBuilder.buildRenderer() { with(context) { buildRenderer() } } - override fun compareTo(other: ComparableResult) = + override fun compareResult(other: ComparableResult) = when (other) { is Interact -> context.compareTo(other.context) - else -> super.compareTo(other) + else -> super.compareResult(other) } } + + data class Dependency( + override val pos: BlockPos, + override val dependency: BuildResult + ) : InteractResult(), Dependent by Dependent.Nested(dependency) { + override val name: String get() = "${this::class.simpleName} at ${pos.toShortString()}" + override val rank = dependency.rank + override val compareBy = lastDependency + } } diff --git a/src/main/kotlin/com/lambda/interaction/construction/result/PlaceResult.kt b/src/main/kotlin/com/lambda/interaction/construction/result/results/PlaceResult.kt similarity index 57% rename from src/main/kotlin/com/lambda/interaction/construction/result/PlaceResult.kt rename to src/main/kotlin/com/lambda/interaction/construction/result/results/PlaceResult.kt index 4cfd034a0..69ba47754 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/result/PlaceResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/result/results/PlaceResult.kt @@ -15,13 +15,21 @@ * along with this program. If not, see . */ -package com.lambda.interaction.construction.result +package com.lambda.interaction.construction.result.results import baritone.api.pathing.goals.GoalBlock import baritone.api.pathing.goals.GoalInverted import com.lambda.context.Automated import com.lambda.graphics.renderer.esp.ShapeBuilder import com.lambda.interaction.construction.context.PlaceContext +import com.lambda.interaction.construction.result.BuildResult +import com.lambda.interaction.construction.result.ComparableResult +import com.lambda.interaction.construction.result.Contextual +import com.lambda.interaction.construction.result.Dependent +import com.lambda.interaction.construction.result.Drawable +import com.lambda.interaction.construction.result.Navigable +import com.lambda.interaction.construction.result.Rank +import com.lambda.interaction.construction.result.Resolvable import com.lambda.task.tasks.BuildTask.Companion.breakBlock import net.minecraft.block.BlockState import net.minecraft.item.ItemPlacementContext @@ -32,9 +40,9 @@ import java.awt.Color /** * [PlaceResult] represents the result of a placement simulation. * Holds data about the placement and the result of the simulation. - * Every [BuildResult] can [resolve] its own problem. - * Every [BuildResult] can be compared to another [BuildResult]. - * First based on the context, then based on the [Rank]. + * Every [GenericResult] can [resolve] its own problem. + * Every [GenericResult] can be compared to another [GenericResult]. + * First based on the context, then based on the [com.lambda.interaction.construction.result.Rank]. */ sealed class PlaceResult : BuildResult() { /** @@ -42,19 +50,20 @@ sealed class PlaceResult : BuildResult() { * @param context The context of the placement. */ data class Place( - override val blockPos: BlockPos, + override val pos: BlockPos, override val context: PlaceContext, ) : Contextual, Drawable, PlaceResult() { - override val rank = Rank.PLACE_SUCCESS + override val name: String get() = "${this::class.simpleName} at ${pos.toShortString()}" + override val rank = Rank.PlaceSuccess override fun ShapeBuilder.buildRenderer() { with(context) { buildRenderer() } } - override fun compareTo(other: ComparableResult) = + override fun compareResult(other: ComparableResult) = when (other) { is Place -> context.compareTo(other.context) - else -> super.compareTo(other) + else -> super.compareResult(other) } } @@ -64,118 +73,135 @@ sealed class PlaceResult : BuildResult() { * This class is used to provide details about a block placement issue in which the actual block * placed does not match the expected state, or additional integrity conditions are not met. * - * @property blockPos The position of the block being inspected or placed. + * @property pos The position of the block being inspected or placed. * @property expected The expected state of the block. * @property simulated The context of the item placement simulation. * @property actual The expected */ data class NoIntegrity( - override val blockPos: BlockPos, + override val pos: BlockPos, val expected: BlockState, val simulated: ItemPlacementContext, val actual: BlockState? = null, ) : Drawable, PlaceResult() { - override val rank = Rank.PLACE_NO_INTEGRITY + override val name: String get() = "${this::class.simpleName} at ${pos.toShortString()}" + override val rank = Rank.PlaceNoIntegrity private val color = Color(252, 3, 3, 100) override fun ShapeBuilder.buildRenderer() { - box(blockPos, expected, color, color) + box(pos, expected, color, color) } } /** * Represents a scenario where block placement is obstructed by an entity. * - * @property blockPos The position of the block that was attempted to be placed. + * @property pos The position of the block that was attempted to be placed. */ data class BlockedByEntity( - override val blockPos: BlockPos, + override val pos: BlockPos, ) : Navigable, PlaceResult() { - override val rank = Rank.PLACE_BLOCKED_BY_PLAYER + override val name: String get() = "${this::class.simpleName} at ${pos.toShortString()}" + override val rank = Rank.PlaceBlockedByPlayer // ToDo: check what type of entity. player -> leave box, other entity -> kill? - override val goal = GoalInverted(GoalBlock(blockPos)) + override val goal = GoalInverted(GoalBlock(pos)) } /** * Represents a result indicating that a block cannot be replaced during a placement operation. * - * @property blockPos The position of the block that cannot be replaced. + * @property pos The position of the block that cannot be replaced. * @property simulated The context of the item placement simulation. */ data class CantReplace( - override val blockPos: BlockPos, + override val pos: BlockPos, val simulated: ItemPlacementContext, ) : Resolvable, PlaceResult() { - override val rank = Rank.PLACE_CANT_REPLACE + override val name: String get() = "${this::class.simpleName} at ${pos.toShortString()}" + override val rank = Rank.PlaceCantReplace context(automated: Automated) - override fun resolve() = automated.breakBlock(blockPos) + override fun resolve() = automated.breakBlock(pos) } /** * Represents a placement result indicating that the scaffolding placement has exceeded the allowed limits. * - * @property blockPos The position of the block where the placement attempt occurred. + * @property pos The position of the block where the placement attempt occurred. * @property simulated The context of the simulated item placement attempt. */ data class ScaffoldExceeded( - override val blockPos: BlockPos, + override val pos: BlockPos, val simulated: ItemPlacementContext, ) : PlaceResult() { - override val rank = Rank.PLACE_SCAFFOLD_EXCEEDED + override val name: String get() = "${this::class.simpleName} at ${pos.toShortString()}" + override val rank = Rank.PlaceScaffoldExceeded } /** * Represents a result where a block placement operation was prevented because * the relevant block feature is disabled. * - * @property blockPos The position of the block that could not be placed. + * @property pos The position of the block that could not be placed. * @property itemStack The item stack associated with the attempted placement. */ data class BlockFeatureDisabled( - override val blockPos: BlockPos, + override val pos: BlockPos, val itemStack: ItemStack, ) : PlaceResult() { - override val rank = Rank.PLACE_BLOCK_FEATURE_DISABLED + override val name: String get() = "${this::class.simpleName} at ${pos.toShortString()}" + override val rank = Rank.PlaceBlockFeatureDisabled } /** * Represents a result state where the placement or manipulation of a block resulted in an unexpected position. * - * @property blockPos The intended position of the block. + * @property pos The intended position of the block. * @property actualPos The actual position of the block, which differs from the intended position. */ data class UnexpectedPosition( - override val blockPos: BlockPos, + override val pos: BlockPos, val actualPos: BlockPos, ) : PlaceResult() { - override val rank = Rank.UNEXPECTED_POSITION + override val name: String get() = "${this::class.simpleName} at ${pos.toShortString()}" + override val rank = Rank.UnexpectedPosition } /** * Represents a result indicating an illegal usage during a placement operation. * E.g., the player can't modify the world or the block cannot be placed against the surface. * - * @property blockPos The position of the block associated with the illegal usage result. + * @property pos The position of the block associated with the illegal usage result. * @property rank The ranking of this result, which is always `PLACE_ILLEGAL_USAGE`. */ data class IllegalUsage( - override val blockPos: BlockPos, + override val pos: BlockPos, ) : PlaceResult() { - override val rank = Rank.PLACE_ILLEGAL_USAGE + override val name: String get() = "${this::class.simpleName} at ${pos.toShortString()}" + override val rank = Rank.PlaceIllegalUsage } /** * Represents the result of a place operation where the provided item does not match the expected item block type. * - * @property blockPos The position of the block where the operation was attempted. + * @property pos The position of the block where the operation was attempted. * @property itemStack The item stack that was checked during the place operation. */ data class NotItemBlock( - override val blockPos: BlockPos, + override val pos: BlockPos, val itemStack: ItemStack, ) : PlaceResult() { - override val rank = Rank.PLACE_NOT_ITEM_BLOCK + override val name: String get() = "${this::class.simpleName} at ${pos.toShortString()}" + override val rank = Rank.PlaceNotItemBlock + } + + data class Dependency( + override val pos: BlockPos, + override val dependency: BuildResult + ) : PlaceResult(), Dependent by Dependent.Nested(dependency) { + override val name: String get() = "${this::class.simpleName} at ${pos.toShortString()}" + override val rank = lastDependency.rank + override val compareBy = lastDependency } } diff --git a/src/main/kotlin/com/lambda/interaction/construction/result/results/PostSimResult.kt b/src/main/kotlin/com/lambda/interaction/construction/result/results/PostSimResult.kt new file mode 100644 index 000000000..2f016a0f7 --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/construction/result/results/PostSimResult.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.interaction.construction.result.results + +import com.lambda.interaction.construction.result.BuildResult +import com.lambda.interaction.construction.result.Rank +import net.minecraft.util.math.BlockPos + +sealed class PostSimResult : BuildResult() { + override val name: String get() = "${this::class.simpleName} at ${pos.toShortString()}" + + data class NoMatch( + override val pos: BlockPos, + ) : PostSimResult() { + override val rank = Rank.NoMatch + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/construction/result/results/PreSimResult.kt b/src/main/kotlin/com/lambda/interaction/construction/result/results/PreSimResult.kt new file mode 100644 index 000000000..29d72968e --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/construction/result/results/PreSimResult.kt @@ -0,0 +1,136 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.interaction.construction.result.results + +import baritone.api.pathing.goals.GoalBlock +import com.lambda.graphics.renderer.esp.ShapeBuilder +import com.lambda.interaction.construction.result.BuildResult +import com.lambda.interaction.construction.result.ComparableResult +import com.lambda.interaction.construction.result.Drawable +import com.lambda.interaction.construction.result.Navigable +import com.lambda.interaction.construction.result.Rank +import net.minecraft.block.BlockState +import net.minecraft.util.math.BlockPos +import java.awt.Color + +sealed class PreSimResult : BuildResult() { + override val name: String get() = "${this::class.simpleName} at ${pos.toShortString()}" + + /** + * The build action is done. + */ + data class Done( + override val pos: BlockPos, + ) : PreSimResult() { + override val name: String + get() = "Build at $pos is done." + override val rank = Rank.Done + } + + /** + * The chunk at the target is not loaded. + * @param pos The position of the block that is in an unloaded chunk. + */ + data class ChunkNotLoaded( + override val pos: BlockPos, + ) : Navigable, Drawable, PreSimResult() { + override val name: String get() = "Chunk at $pos is not loaded." + override val rank = Rank.ChunkNotLoaded + private val color = Color(252, 165, 3, 100) + + override val goal = GoalBlock(pos) + + override fun ShapeBuilder.buildRenderer() { + box(pos, color, color) + } + + override fun compareResult(other: ComparableResult) = + when (other) { + is ChunkNotLoaded -> pos.compareTo(other.pos) + else -> super.compareResult(other) + } + } + + /** + * The player has no permission to interact with the block. (E.g.: Adventure mode) + * @param pos The position of the block that is restricted. + */ + data class Restricted( + override val pos: BlockPos, + ) : Drawable, PreSimResult() { + override val name: String get() = "Restricted at $pos." + override val rank = Rank.BreakRestricted + private val color = Color(255, 0, 0, 100) + + override fun ShapeBuilder.buildRenderer() { + box(pos, color, color) + } + } + + /** + * The block needs server permission to be broken. (Needs op) + * @param pos The position of the block that needs permission. + * @param blockState The state of the block that needs permission. + */ + data class NoPermission( + override val pos: BlockPos, + val blockState: BlockState, + ) : Drawable, PreSimResult() { + override val name: String get() = "No permission at $pos." + override val rank get() = Rank.BreakNoPermission + private val color = Color(255, 0, 0, 100) + + override fun ShapeBuilder.buildRenderer() { + box(pos, color, color) + } + } + + /** + * The break target is out of the world border or height limit. + * @param pos The position of the block that is out of the world. + */ + data class OutOfWorld( + override val pos: BlockPos, + ) : Drawable, PreSimResult() { + override val name: String get() = "$pos is out of the world." + override val rank = Rank.OutOfWorld + private val color = Color(3, 148, 252, 100) + + override fun ShapeBuilder.buildRenderer() { + box(pos, color, color) + } + } + + /** + * The block is unbreakable. + * @param pos The position of the block that is unbreakable. + * @param blockState The state of the block that is unbreakable. + */ + data class Unbreakable( + override val pos: BlockPos, + val blockState: BlockState, + ) : Drawable, PreSimResult() { + override val name: String get() = "Unbreakable at $pos." + override val rank = Rank.Unbreakable + private val color = Color(11, 11, 11, 100) + + override fun ShapeBuilder.buildRenderer() { + box(pos, color, color) + } + } +} \ No newline at end of file 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 53a607ffb..20671534d 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt @@ -19,869 +19,49 @@ package com.lambda.interaction.construction.simulation import com.lambda.context.AutomatedSafeContext import com.lambda.interaction.construction.blueprint.Blueprint -import com.lambda.interaction.construction.context.BreakContext -import com.lambda.interaction.construction.context.InteractionContext -import com.lambda.interaction.construction.context.PlaceContext -import com.lambda.interaction.construction.processing.PreProcessingInfo import com.lambda.interaction.construction.processing.ProcessorRegistry.getProcessingInfo -import com.lambda.interaction.construction.result.BreakResult import com.lambda.interaction.construction.result.BuildResult -import com.lambda.interaction.construction.result.InteractResult -import com.lambda.interaction.construction.result.PlaceResult -import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.material.ContainerSelection.Companion.selectContainer -import com.lambda.interaction.material.StackSelection -import com.lambda.interaction.material.StackSelection.Companion.select -import com.lambda.interaction.material.StackSelection.Companion.selectStack -import com.lambda.interaction.material.container.ContainerManager.containerWithMaterial -import com.lambda.interaction.material.container.MaterialContainer -import com.lambda.interaction.request.rotating.Rotation.Companion.rotation -import com.lambda.interaction.request.rotating.Rotation.Companion.rotationTo -import com.lambda.interaction.request.rotating.RotationManager -import com.lambda.interaction.request.rotating.RotationRequest -import com.lambda.interaction.request.rotating.visibilty.PlaceDirection -import com.lambda.interaction.request.rotating.visibilty.VisibilityChecker.CheckedHit -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.lookAt -import com.lambda.interaction.request.rotating.visibilty.lookAtBlock -import com.lambda.interaction.request.rotating.visibilty.lookInDirection -import com.lambda.util.BlockUtils +import com.lambda.interaction.construction.result.results.PostSimResult +import com.lambda.interaction.construction.simulation.checks.BreakChecks.checkBreaks +import com.lambda.interaction.construction.simulation.checks.PlaceChecks.checkPlacements +import com.lambda.interaction.construction.simulation.checks.PostProcessingChecks.checkPostProcessing +import com.lambda.interaction.construction.simulation.checks.RequirementChecks.checkRequirements import com.lambda.util.BlockUtils.blockState -import com.lambda.util.BlockUtils.calcItemBlockBreakingDelta -import com.lambda.util.BlockUtils.hasFluid -import com.lambda.util.BlockUtils.instantBreakable -import com.lambda.util.BlockUtils.isNotEmpty -import com.lambda.util.Communication.warn -import com.lambda.util.math.distSq -import com.lambda.util.math.vec3d -import com.lambda.util.player.SlotUtils.hotbar -import com.lambda.util.player.copyPlayer -import com.lambda.util.player.gamemode -import com.lambda.util.world.WorldUtils.isLoaded -import com.lambda.util.world.raycast.RayCastUtils.blockResult +import io.ktor.util.collections.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.runBlocking -import net.minecraft.block.BlockState -import net.minecraft.block.FallingBlock -import net.minecraft.block.OperatorBlock -import net.minecraft.block.SlabBlock -import net.minecraft.block.Waterloggable -import net.minecraft.block.enums.SlabType -import net.minecraft.block.pattern.CachedBlockPosition -import net.minecraft.enchantment.Enchantments -import net.minecraft.fluid.FlowableFluid -import net.minecraft.fluid.LavaFluid -import net.minecraft.fluid.WaterFluid -import net.minecraft.item.BlockItem -import net.minecraft.item.Item -import net.minecraft.item.ItemPlacementContext -import net.minecraft.item.ItemStack -import net.minecraft.item.ItemUsageContext -import net.minecraft.registry.tag.ItemTags.DIAMOND_TOOL_MATERIALS -import net.minecraft.registry.tag.ItemTags.GOLD_TOOL_MATERIALS -import net.minecraft.registry.tag.ItemTags.IRON_TOOL_MATERIALS -import net.minecraft.registry.tag.ItemTags.NETHERITE_TOOL_MATERIALS -import net.minecraft.registry.tag.ItemTags.STONE_TOOL_MATERIALS -import net.minecraft.registry.tag.ItemTags.WOODEN_TOOL_MATERIALS -import net.minecraft.state.property.Properties -import net.minecraft.util.Hand -import net.minecraft.util.hit.BlockHitResult -import net.minecraft.util.math.BlockPos -import net.minecraft.util.math.Direction -import net.minecraft.util.math.Vec3d -import net.minecraft.util.shape.VoxelShapes -import kotlin.math.pow -object BuildSimulator { +object BuildSimulator : SimChecker() { context(context: AutomatedSafeContext) - fun Blueprint.simulate(eye: Vec3d): Set = - runBlocking(Dispatchers.Default) { + fun Blueprint.simulate(): Set = runBlocking(Dispatchers.Default) { + val concurrentSet = ConcurrentSet() + with(context) { structure.entries - .map { (pos, target) -> + .map { (pos, targetState) -> async { - val preProcessing = target.getProcessingInfo(pos) ?: return@async emptySet() - - with(context) { - checkRequirements(pos, target).let { results -> - if (results.isNotEmpty()) return@async results - } - checkPostProcessResults(pos, eye, preProcessing, target).let { results -> - if (results.isNotEmpty()) return@async results - } - checkPlaceResults(pos, eye, preProcessing, target).let { results -> - if (results.isNotEmpty()) return@async results - } - checkBreakResults(pos, eye, preProcessing).let { results -> - if (results.isNotEmpty()) return@async results - } - } - - warn("Nothing matched $pos $target") - emptySet() - } - } - .awaitAll() - .flatMap { it } - .toSet() - } - - private fun AutomatedSafeContext.checkRequirements(pos: BlockPos, target: TargetState): Set { - val acc = mutableSetOf() - - /* the chunk is not loaded */ - if (!isLoaded(pos)) { - acc.add(BuildResult.ChunkNotLoaded(pos)) - return acc - } - - val state = blockState(pos) - - /* block is already in the correct state */ - if (target.matches(state, pos, world)) { - acc.add(BuildResult.Done(pos)) - return acc - } - - /* block should be ignored */ - if (state.block in breakConfig.ignoredBlocks && target.type == TargetState.Type.AIR) { - acc.add(BuildResult.Ignored(pos)) - return acc - } - - /* the player is in the wrong game mode to alter the block state */ - if (player.isBlockBreakingRestricted(world, pos, gamemode)) { - acc.add(BuildResult.Restricted(pos)) - return acc - } - - /* the player has no permissions to alter the block state */ - if (state.block is OperatorBlock && !player.isCreativeLevelTwoOp) { - acc.add(BuildResult.NoPermission(pos, state)) - return acc - } - - /* block is outside the world so it cant be altered */ - if (!world.worldBorder.contains(pos) || world.isOutOfHeightLimit(pos)) { - acc.add(BuildResult.OutOfWorld(pos)) - return acc - } - - /* block is unbreakable, so it cant be broken or replaced */ - if (state.getHardness(world, pos) < 0 && !gamemode.isCreative) { - acc.add(BuildResult.Unbreakable(pos, state)) - return acc - } - - return acc - } - - private fun AutomatedSafeContext.checkPostProcessResults( - pos: BlockPos, - eye: Vec3d, - preProcessing: PreProcessingInfo, - targetState: TargetState - ): Set { - if (targetState !is TargetState.State) return emptySet() - - val acc = mutableSetOf() - - val state = blockState(pos) - if (!targetState.matches(state, pos, world, preProcessing.ignore)) - return acc - - val interactBlock: (BlockState, Set?, Item?, Boolean) -> Unit = - interactBlock@ { expectedState, sides, item, placing -> - val boxes = state.getOutlineShape(world, pos).boundingBoxes.map { it.offset(pos) } - val validHits = mutableListOf() - val blockedHits = mutableSetOf() - val misses = mutableSetOf() - val airPlace = placing && placeConfig.airPlace.isEnabled - - boxes.forEach { box -> - val refinedSides = if (buildConfig.checkSideVisibility) { - box.getVisibleSurfaces(eye).let { visibleSides -> - sides?.let { specific -> - visibleSides.intersect(specific) - } ?: visibleSides.toSet() - } - } else sides ?: Direction.entries.toSet() - - scanSurfaces( - box, - refinedSides, - buildConfig.resolution, - preProcessing.surfaceScan - ) { hitSide, vec -> - val distSquared = eye distSq vec - if (distSquared > buildConfig.interactReach.pow(2)) { - misses.add(vec) - return@scanSurfaces - } - - val newRotation = eye.rotationTo(vec) - - val hit = if (buildConfig.strictRayCast) { - val rayCast = newRotation.rayCast(buildConfig.interactReach, eye) - when { - rayCast != null && (!airPlace || eye distSq rayCast.pos <= distSquared) -> - rayCast.blockResult - - airPlace -> { - val hitVec = newRotation.castBox(box, buildConfig.interactReach, eye) - BlockHitResult(hitVec, hitSide, pos, false) - } - - else -> null - } - } else { - val hitVec = newRotation.castBox(box, buildConfig.interactReach, eye) - BlockHitResult(hitVec, hitSide, pos, false) - } ?: return@scanSurfaces - - val checked = CheckedHit(hit, newRotation, buildConfig.interactReach) - if (hit.blockResult?.blockPos != pos) { - blockedHits.add(vec) - return@scanSurfaces - } - - validHits.add(checked) - } - } - - if (validHits.isEmpty()) { - if (misses.isNotEmpty()) { - acc.add(BuildResult.OutOfReach(pos, eye, misses)) - } else { - //ToDo: Must clean up surface scan usage / renders. Added temporary direction until changes are made - acc.add( - BuildResult.NotVisible( - pos, - pos, - Direction.UP, - eye.distanceTo(pos.offset(Direction.UP).vec3d) - ) - ) - } - return@interactBlock - } - - buildConfig.pointSelection.select(validHits)?.let { checkedHit -> - val checkedResult = checkedHit.hit - val rotationTarget = lookAt(checkedHit.targetRotation, 0.001) - val context = InteractionContext( - checkedResult.blockResult ?: return@interactBlock, - RotationRequest(rotationTarget, this), - player.inventory.selectedSlot, - state, - expectedState, - this - ) - - val stackSelection = (item ?: player.mainHandStack.item).select() - val hotbarCandidates = selectContainer { - matches(stackSelection) and ofAnyType(MaterialContainer.Rank.HOTBAR) - }.let { predicate -> - stackSelection.containerWithMaterial( predicate) - } - - if (hotbarCandidates.isEmpty()) { - acc.add( - BuildResult.WrongItemSelection( - pos, - context, - stackSelection, - player.mainHandStack - ) - ) - return@interactBlock - } else { - context.hotbarIndex = - player.hotbar.indexOf(hotbarCandidates.first().matchingStacks(stackSelection).first()) - } - - acc.add(InteractResult.Interact(pos, context)) - } - } - - val mismatchedProperties = state.properties.filter { state.get(it) != targetState.blockState.get(it) } - mismatchedProperties.forEach { property -> - when (property) { - Properties.EYE -> { - if (state.get(Properties.EYE)) return@forEach - val expectedState = state.with(Properties.EYE, true) - interactBlock(expectedState, null, null, false) - } - - Properties.INVERTED -> { - val expectedState = state.with(Properties.INVERTED, !state.get(Properties.INVERTED)) - interactBlock(expectedState, null, null, false) - } - - Properties.DELAY -> { - val expectedState = - state.with(Properties.DELAY, state.cycle(Properties.DELAY).get(Properties.DELAY)) - interactBlock(expectedState, null, null, false) - } - - Properties.COMPARATOR_MODE -> { - val expectedState = state.with( - Properties.COMPARATOR_MODE, - state.cycle(Properties.COMPARATOR_MODE).get(Properties.COMPARATOR_MODE) - ) - interactBlock(expectedState, null, null, false) - } - - Properties.OPEN -> { - val expectedState = state.with(Properties.OPEN, !state.get(Properties.OPEN)) - interactBlock(expectedState, null, null, false) - } - - Properties.SLAB_TYPE -> { - if (targetState.blockState.get(Properties.SLAB_TYPE) != SlabType.DOUBLE) return@forEach - checkPlaceResults( - pos, - eye, - preProcessing, - targetState - ).let { placeResults -> - acc.addAll(placeResults) - } - } - } - } - - if (acc.isEmpty()) { - acc.add(BuildResult.Done(pos)) - } - - return acc - } - - private fun AutomatedSafeContext.checkPlaceResults( - pos: BlockPos, - eye: Vec3d, - preProcessing: PreProcessingInfo, - targetState: TargetState - ): Set { - val acc = mutableSetOf() - val currentState = blockState(pos) - - val statePromoting = currentState.block is SlabBlock - && targetState.matches(currentState, pos, world, preProcessing.ignore) - // If the target state is air then the only possible blocks it could place are to remove liquids so we use the Solid TargetState - val nextTargetState = if (targetState is TargetState.Air) { - if (currentState.hasFluid) TargetState.Solid - else return acc - } else if (targetState.isEmpty()) { - // Otherwise if the target state is empty, there's no situation where placement would be required so we can return - return acc - } else targetState - // For example, slabs count as state promoting because you are placing another block to promote the current state to the target state - if (!currentState.isReplaceable && !statePromoting) return acc - - preProcessing.sides.forEach { neighbor -> - val hitPos = if (!placeConfig.airPlace.isEnabled && (currentState.isAir || statePromoting)) - pos.offset(neighbor) else pos - val hitSide = neighbor.opposite - if (!world.worldBorder.contains(hitPos)) return@forEach - - val voxelShape = blockState(hitPos).getOutlineShape(world, hitPos).let { outlineShape -> - if (!outlineShape.isEmpty || !placeConfig.airPlace.isEnabled) outlineShape - else VoxelShapes.fullCube() - } - if (voxelShape.isEmpty) return@forEach - - val boxes = voxelShape.boundingBoxes.map { it.offset(hitPos) } - val verify: CheckedHit.() -> Boolean = { - hit.blockResult?.blockPos == hitPos && hit.blockResult?.side == hitSide - } - - val validHits = mutableListOf() - val misses = mutableSetOf() - val reachSq = buildConfig.interactReach.pow(2) - - boxes.forEach { box -> - val sides = if (buildConfig.checkSideVisibility) { - box.getVisibleSurfaces(eye).intersect(setOf(hitSide)) - } else setOf(hitSide) - - scanSurfaces(box, sides, buildConfig.resolution, preProcessing.surfaceScan) { _, vec -> - val distSquared = eye distSq vec - if (distSquared > reachSq) { - misses.add(vec) - return@scanSurfaces - } - - val newRotation = eye.rotationTo(vec) - - val hit = if (buildConfig.strictRayCast) { - val rayCast = newRotation.rayCast(buildConfig.interactReach, eye) - when { - rayCast != null && (!placeConfig.airPlace.isEnabled || eye distSq rayCast.pos <= distSquared) -> - rayCast.blockResult - - placeConfig.airPlace.isEnabled -> { - val hitVec = newRotation.castBox(box, buildConfig.interactReach, eye) - BlockHitResult(hitVec, hitSide, hitPos, false) - } - - else -> null - } - } else { - val hitVec = newRotation.castBox(box, buildConfig.interactReach, eye) - BlockHitResult(hitVec, hitSide, hitPos, false) - } ?: return@scanSurfaces - - val checked = CheckedHit(hit, newRotation, buildConfig.interactReach) - if (!checked.verify()) return@scanSurfaces - - validHits.add(checked) - } - } - - if (validHits.isEmpty()) { - if (misses.isNotEmpty()) { - acc.add(BuildResult.OutOfReach(pos, eye, misses)) - return@forEach - } - - acc.add(BuildResult.NotVisible(pos, hitPos, hitSide, eye.distanceTo(hitPos.offset(hitSide).vec3d))) - return@forEach - } - - buildConfig.pointSelection.select(validHits)?.let { checkedHit -> - val optimalStack = nextTargetState.getStack(world, pos, this) - - // ToDo: For each hand and sneak or not? - val fakePlayer = copyPlayer(player).apply { - this.rotation = RotationManager.serverRotation - } - - val checkedResult = checkedHit.hit - - // ToDo: Override the stack used for this to account for blocks where replaceability is dependent on the held item - val usageContext = ItemUsageContext( - fakePlayer, - Hand.MAIN_HAND, - checkedResult.blockResult, - ) - val cachePos = CachedBlockPosition( - usageContext.world, usageContext.blockPos, false - ) - val canBePlacedOn = optimalStack.canPlaceOn(cachePos) - if (!player.abilities.allowModifyWorld && !canBePlacedOn) { - acc.add(PlaceResult.IllegalUsage(pos)) - return@forEach - } - - var context = ItemPlacementContext(usageContext) - - if (context.blockPos != pos) { - acc.add(PlaceResult.UnexpectedPosition(pos, context.blockPos)) - return@forEach - } - - if (!optimalStack.item.isEnabled(world.enabledFeatures)) { - acc.add(PlaceResult.BlockFeatureDisabled(pos, optimalStack)) - return@forEach - } - - if (!context.canPlace() && !statePromoting) { - acc.add(PlaceResult.CantReplace(pos, context)) - return@forEach - } - - val blockItem = optimalStack.item as? BlockItem ?: run { - acc.add(PlaceResult.NotItemBlock(pos, optimalStack)) - return@forEach - } - - context = blockItem.getPlacementContext(context) - ?: run { - acc.add(PlaceResult.ScaffoldExceeded(pos, context)) - return@forEach - } - - lateinit var resultState: BlockState - var rot = fakePlayer.rotation - - val simulatePlaceState = placeState@{ - resultState = blockItem.getPlacementState(context) - ?: return@placeState PlaceResult.BlockedByEntity(pos) - - return@placeState if (!nextTargetState.matches(resultState, pos, world, preProcessing.ignore)) - PlaceResult.NoIntegrity( + val preProcessing = targetState.getProcessingInfo(pos) ?: return@async + val simInfo = SimInfo( pos, - resultState, - context, - (nextTargetState as? TargetState.State)?.blockState + blockState(pos), + targetState, + preProcessing, + concurrentSet ) - else null - } - - val currentDirIsValid = simulatePlaceState()?.let { basePlaceResult -> - if (!placeConfig.rotateForPlace) { - acc.add(basePlaceResult) - return@forEach - } - false - } != false - - run rotate@{ - if (!placeConfig.axisRotate) { - fakePlayer.rotation = checkedHit.targetRotation - simulatePlaceState()?.let { rotatedPlaceResult -> - acc.add(rotatedPlaceResult) - return@forEach - } - rot = fakePlayer.rotation - return@rotate - } - - fakePlayer.rotation = player.rotation - if (simulatePlaceState() == null) { - rot = fakePlayer.rotation - return@rotate - } - - PlaceDirection.entries.asReversed().forEachIndexed direction@{ index, direction -> - fakePlayer.rotation = direction.rotation - when (val placeResult = simulatePlaceState()) { - is PlaceResult.BlockedByEntity -> { - acc.add(placeResult) - return@forEach - } - - is PlaceResult.NoIntegrity -> { - if (index != PlaceDirection.entries.lastIndex) return@direction - acc.add(placeResult) - return@forEach - } - - else -> { - rot = fakePlayer.rotation - return@rotate + with(simInfo) { + with(null) { + checkRequirements() + checkPostProcessing() + checkPlacements() + checkBreaks() } } } - } - - val blockHit = checkedResult.blockResult ?: return@forEach - val hitBlock = blockState(blockHit.blockPos).block - val shouldSneak = hitBlock::class in BlockUtils.interactionBlocks - - val rotationRequest = if (placeConfig.axisRotate) { - lookInDirection(PlaceDirection.fromRotation(rot)) - } else lookAt(rot, 0.001) - - val placeContext = PlaceContext( - blockHit, - RotationRequest(rotationRequest, this), - player.inventory.selectedSlot, - context.blockPos, - blockState(context.blockPos), - resultState, - shouldSneak, - false, - currentDirIsValid, - this - ) - - val selection = optimalStack.item.select() - val containerSelection = selectContainer { ofAnyType(MaterialContainer.Rank.HOTBAR) } - val container = selection.containerWithMaterial(containerSelection).firstOrNull() ?: run { - acc.add( - BuildResult.WrongItemSelection( - pos, - placeContext, - optimalStack.item.select(), - player.mainHandStack - ) - ) - return acc - } - val stack = selection.filterStacks(container.stacks).run { - firstOrNull { player.inventory.getSlotWithStack(it) == player.inventory.selectedSlot } - ?: first() - } - - placeContext.hotbarIndex = player.inventory.getSlotWithStack(stack) - - acc.add(PlaceResult.Place(pos, placeContext)) - } + }.awaitAll() } - return acc - } - - private fun AutomatedSafeContext.checkBreakResults( - pos: BlockPos, - eye: Vec3d, - preProcessing: PreProcessingInfo - ): Set { - val acc = mutableSetOf() - val state = blockState(pos) - - /* is a block that will be destroyed by breaking adjacent blocks */ - if (!breakConfig.breakWeakBlocks && state.block.hardness == 0f && !state.isAir && state.isNotEmpty) { - acc.add(BuildResult.Ignored(pos)) - return acc - } - - /* player is standing on top of the block */ - if (breakConfig.avoidSupporting) player.supportingBlockPos.orElse(null)?.let { support -> - if (support != pos) return@let - acc.add(BreakResult.PlayerOnTop(pos, state)) - return acc - } - - /* liquid needs to be submerged first to be broken */ - if (!state.fluidState.isEmpty && state.isReplaceable) { - val submerge = checkPlaceResults(pos, eye, preProcessing, TargetState.Solid) - acc.add(BreakResult.Submerge(pos, state, submerge)) - acc.addAll(submerge) - return acc - } - - if (breakConfig.avoidLiquids) { - val affectedBlocks = hashSetOf(pos) - val checkQueue = hashSetOf(pos) - - while (checkQueue.isNotEmpty()) { - val checkPos = checkQueue.first() - checkQueue.remove(checkPos) - for (offset in Direction.entries) { - val adjacentPos = checkPos.offset(offset) - - if (blockState(adjacentPos).block !is FallingBlock) continue - if (adjacentPos in affectedBlocks) continue - - if (offset == Direction.UP || FallingBlock.canFallThrough(blockState(adjacentPos.down()))) { - checkQueue.add(adjacentPos) - affectedBlocks.add(adjacentPos) - } - } - } - - val affectedFluids = affectedBlocks.fold(hashMapOf()) { accumulator, affectedPos -> - Direction.entries.forEach { offset -> - if (offset == Direction.DOWN) return@forEach - - val offsetPos = affectedPos.offset(offset) - val offsetState = blockState(offsetPos) - val fluidState = offsetState.fluidState - val fluid = fluidState.fluid - - if (fluidState.isEmpty || fluid !is FlowableFluid) return@forEach - - if (offset == Direction.UP) { - accumulator[offsetPos] = offsetState - return@fold accumulator - } - - if (offsetState.block is Waterloggable && !fluidState.isEmpty) { - accumulator[offsetPos] = offsetState - return@fold accumulator - } - - val levelDecreasePerBlock = - when (fluid) { - is WaterFluid -> fluid.getLevelDecreasePerBlock(world) - is LavaFluid -> fluid.getLevelDecreasePerBlock(world) - else -> 0 - } - - if (fluidState.level - levelDecreasePerBlock > 0) { - accumulator[offsetPos] = offsetState - return@fold accumulator - } - } - - return@fold accumulator - } - - if (affectedFluids.isNotEmpty()) { - val liquidOutOfBounds = affectedFluids.any { !world.worldBorder.contains(it.key) } - if (liquidOutOfBounds) { - acc.add(BuildResult.Ignored(pos)) - return acc - } - - affectedFluids.forEach { (liquidPos, liquidState) -> - val submerge = checkPlaceResults(liquidPos, eye, preProcessing, TargetState.Solid) - acc.add(BreakResult.Submerge(liquidPos, liquidState, submerge)) - acc.addAll(submerge) - } - acc.add(BreakResult.BlockedByFluid(pos, state)) - return acc - } - } - - val currentRotation = RotationManager.activeRotation - val currentCast = currentRotation.rayCast(buildConfig.interactReach, eye) - - val voxelShape = state.getOutlineShape(world, pos) - voxelShape.getClosestPointTo(eye).ifPresent { - // ToDo: Use closest point of shape of only visible faces - } - - val boxes = voxelShape.boundingBoxes.map { it.offset(pos) } - val verify: CheckedHit.() -> Boolean = { - hit.blockResult?.blockPos == pos - } - - // ToDo: Move this to a location where more of the context parameters can be properly set - /* the player is buried inside the block */ - if (boxes.any { it.contains(eye) }) { - currentCast?.blockResult?.let { blockHit -> - val rotationRequest = RotationRequest( - lookAtBlock(pos), this - ) - val breakContext = BreakContext( - blockHit, - rotationRequest, - player.inventory.selectedSlot, - StackSelection.EVERYTHING.select(), - instantBreakable(state, pos, breakConfig.breakThreshold), - state, - breakConfig.sorter, - this - ) - acc.add(BreakResult.Break(pos, breakContext)) - return acc - } - } - - val validHits = mutableListOf() - val misses = mutableSetOf() - val reachSq = buildConfig.interactReach.pow(2) - - boxes.forEach { box -> - val sides = if (buildConfig.checkSideVisibility) { - box.getVisibleSurfaces(eye).intersect(Direction.entries) - } else Direction.entries.toSet() - // ToDo: Rewrite Rotation request system to allow support for all sim features and use the rotation finder - scanSurfaces(box, sides, buildConfig.resolution) { side, vec -> - if (eye distSq vec > reachSq) { - misses.add(vec) - return@scanSurfaces - } - - val newRotation = eye.rotationTo(vec) - - val hit = if (buildConfig.strictRayCast) { - newRotation.rayCast(buildConfig.interactReach, eye)?.blockResult - } else { - val hitVec = newRotation.castBox(box, buildConfig.interactReach, eye) - BlockHitResult(hitVec, side, pos, false) - } ?: return@scanSurfaces - - val checked = CheckedHit(hit, newRotation, buildConfig.interactReach) - if (!checked.verify()) return@scanSurfaces - - validHits.add(checked) - } - } - - if (validHits.isEmpty()) { - // ToDo: If we can only mine exposed surfaces we need to add not visible result here - acc.add(BuildResult.OutOfReach(pos, eye, misses)) - return acc - } - - val bestHit = buildConfig.pointSelection.select(validHits) ?: return acc - val blockHit = bestHit.hit.blockResult ?: return acc - val target = lookAt(bestHit.targetRotation, 0.001) - val rotationRequest = RotationRequest(target, this) - val instant = instantBreakable(state, pos, breakConfig.breakThreshold) - - val breakContext = BreakContext( - blockHit, - rotationRequest, - player.inventory.selectedSlot, - StackSelection.EVERYTHING.select(), - instant, - state, - breakConfig.sorter, - this - ) - - if (gamemode.isCreative) { - acc.add(BreakResult.Break(pos, breakContext)) - return acc - } - - val stackSelection = selectStack( - sorter = compareByDescending { - it.canBreak(CachedBlockPosition(world, pos, false)) - }.thenByDescending { - state.calcItemBlockBreakingDelta(pos, it) - } - ) { - isTool() and if (breakConfig.suitableToolsOnly) { - isSuitableForBreaking(state) - } else any() and if (breakConfig.forceSilkTouch) { - hasEnchantment(Enchantments.SILK_TOUCH) - } else any() and if (breakConfig.forceFortunePickaxe) { - hasEnchantment(Enchantments.FORTUNE) - } else any() and if (!breakConfig.useWoodenTools) { - hasTag(WOODEN_TOOL_MATERIALS).not() - } else any() and if (!breakConfig.useStoneTools) { - hasTag(STONE_TOOL_MATERIALS).not() - } else any() and if (!breakConfig.useIronTools) { - hasTag(IRON_TOOL_MATERIALS).not() - } else any() and if (!breakConfig.useDiamondTools) { - hasTag(DIAMOND_TOOL_MATERIALS).not() - } else any() and if (!breakConfig.useGoldTools) { - hasTag(GOLD_TOOL_MATERIALS).not() - } else any() and if (!breakConfig.useNetheriteTools) { - hasTag(NETHERITE_TOOL_MATERIALS).not() - } else any() - } - - val silentSwapSelection = selectContainer { - ofAnyType(MaterialContainer.Rank.HOTBAR) - } - - val swapCandidates = stackSelection.containerWithMaterial(silentSwapSelection) - if (swapCandidates.isEmpty()) { - acc.add(BuildResult.WrongItemSelection(pos, breakContext, stackSelection, player.mainHandStack)) - return acc - } - - val swapStack = swapCandidates - .map { it.matchingStacks(stackSelection) } - .asSequence() - .flatten() - .let { containerStacks -> - var bestStack = ItemStack.EMPTY - var bestBreakDelta = -1f - containerStacks.forEach { stack -> - val breakDelta = state.calcItemBlockBreakingDelta(pos, stack) - if (breakDelta > bestBreakDelta || - (stack == player.mainHandStack && breakDelta >= bestBreakDelta) - ) { - bestBreakDelta = breakDelta - bestStack = stack - } - } - bestStack - } - - breakContext.apply { - hotbarIndex = player.hotbar.indexOf(swapStack) - itemSelection = stackSelection - instantBreak = instantBreakable( - state, - pos, - if (breakConfig.swapMode.isEnabled()) swapStack else player.mainHandStack, - breakConfig.breakThreshold - ) - } - acc.add(BreakResult.Break(pos, breakContext)) - return acc + return@runBlocking concurrentSet } } + diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/SimChecker.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/SimChecker.kt new file mode 100644 index 000000000..d1fd45171 --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/SimChecker.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.interaction.construction.simulation + +import com.lambda.context.SafeContext +import com.lambda.interaction.construction.result.BuildResult +import com.lambda.interaction.construction.result.Dependable +import com.lambda.interaction.construction.result.results.GenericResult +import net.minecraft.util.math.Vec3d + +abstract class SimChecker { + val SafeContext.eye: Vec3d get() = player.eyePos + + fun SimInfo.checkDependant(caller: Dependable?) { + if (caller == null) { + dependencyStack.clear() + return + } + dependencyStack.push(caller) + } + + fun SimInfo.result(result: GenericResult) = addResult(result) + fun SimInfo.result(result: T) = addResult(result) + private fun SimInfo.addResult(result: BuildResult) { + concurrentResults.add( + dependencyStack + .asReversed() + .fold(result) { acc, dependable -> + with(dependable) { asDependant(acc) } + } + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/SimInfo.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/SimInfo.kt new file mode 100644 index 000000000..ce2296664 --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/SimInfo.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.interaction.construction.simulation + +import com.lambda.interaction.construction.processing.PreProcessingInfo +import com.lambda.interaction.construction.result.BuildResult +import com.lambda.interaction.construction.result.Dependable +import com.lambda.interaction.construction.verify.TargetState +import net.minecraft.block.BlockState +import net.minecraft.util.math.BlockPos +import java.util.* + +data class SimInfo( + val pos: BlockPos, + val state: BlockState, + val targetState: TargetState, + val preProcessing: PreProcessingInfo, + val concurrentResults: MutableSet +) { + val dependencyStack = Stack() +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt index 1fda4fbf8..019b2c399 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt @@ -49,13 +49,13 @@ data class Simulation( val view = pos.toView() val isOutOfBounds = blueprint.isOutOfBounds(view) val isTooFar = blueprint.getClosestPointTo(view).distanceTo(view) > 10.0 - return runSafeAutomated { + return@getOrPut runSafeAutomated { if (isOutOfBounds && isTooFar) return@getOrPut emptySet() val blockPos = pos.toBlockPos() val isWalkable = blockState(blockPos.down()).isSideSolidFullSquare(world, blockPos, Direction.UP) if (!isWalkable) return@getOrPut emptySet() if (!playerFitsIn(blockPos)) return@getOrPut emptySet() - blueprint.simulate(view) + blueprint.simulate() } ?: emptySet() } diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecks.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecks.kt new file mode 100644 index 000000000..bdcbefc5c --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecks.kt @@ -0,0 +1,338 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.interaction.construction.simulation.checks + +import com.lambda.context.AutomatedSafeContext +import com.lambda.interaction.construction.context.BreakContext +import com.lambda.interaction.construction.result.BuildResult +import com.lambda.interaction.construction.result.Dependable +import com.lambda.interaction.construction.result.results.BreakResult +import com.lambda.interaction.construction.result.results.GenericResult +import com.lambda.interaction.construction.simulation.SimChecker +import com.lambda.interaction.construction.simulation.SimInfo +import com.lambda.interaction.construction.simulation.checks.PlaceChecks.checkPlacements +import com.lambda.interaction.construction.verify.TargetState +import com.lambda.interaction.material.ContainerSelection.Companion.selectContainer +import com.lambda.interaction.material.StackSelection +import com.lambda.interaction.material.StackSelection.Companion.select +import com.lambda.interaction.material.StackSelection.Companion.selectStack +import com.lambda.interaction.material.container.ContainerManager.containerWithMaterial +import com.lambda.interaction.material.container.MaterialContainer +import com.lambda.interaction.request.rotating.Rotation.Companion.rotationTo +import com.lambda.interaction.request.rotating.RotationManager +import com.lambda.interaction.request.rotating.RotationRequest +import com.lambda.interaction.request.rotating.visibilty.VisibilityChecker.CheckedHit +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.lookAt +import com.lambda.interaction.request.rotating.visibilty.lookAtBlock +import com.lambda.util.BlockUtils.blockState +import com.lambda.util.BlockUtils.calcItemBlockBreakingDelta +import com.lambda.util.BlockUtils.instantBreakable +import com.lambda.util.BlockUtils.isNotEmpty +import com.lambda.util.math.distSq +import com.lambda.util.player.SlotUtils.hotbar +import com.lambda.util.player.gamemode +import com.lambda.util.world.raycast.RayCastUtils.blockResult +import net.minecraft.block.BlockState +import net.minecraft.block.FallingBlock +import net.minecraft.block.Waterloggable +import net.minecraft.block.pattern.CachedBlockPosition +import net.minecraft.enchantment.Enchantments +import net.minecraft.fluid.FlowableFluid +import net.minecraft.fluid.LavaFluid +import net.minecraft.fluid.WaterFluid +import net.minecraft.item.ItemStack +import net.minecraft.registry.tag.ItemTags.DIAMOND_TOOL_MATERIALS +import net.minecraft.registry.tag.ItemTags.GOLD_TOOL_MATERIALS +import net.minecraft.registry.tag.ItemTags.IRON_TOOL_MATERIALS +import net.minecraft.registry.tag.ItemTags.NETHERITE_TOOL_MATERIALS +import net.minecraft.registry.tag.ItemTags.STONE_TOOL_MATERIALS +import net.minecraft.registry.tag.ItemTags.WOODEN_TOOL_MATERIALS +import net.minecraft.util.hit.BlockHitResult +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Direction +import net.minecraft.util.math.Vec3d +import kotlin.math.pow + +object BreakChecks : SimChecker(), Dependable { + override fun SimInfo.asDependant(buildResult: BuildResult) = + BreakResult.Dependency(pos, buildResult) + + context(automatedSafeContext: AutomatedSafeContext, dependable: Dependable?) + fun SimInfo.checkBreaks(): Unit = with(automatedSafeContext) { + checkDependant(dependable) + + /* is a block that will be destroyed by breaking adjacent blocks */ + if (!breakConfig.breakWeakBlocks && state.block.hardness == 0f && !state.isAir && state.isNotEmpty) { + result(GenericResult.Ignored(pos)) + return + } + + /* player is standing on top of the block */ + if (breakConfig.avoidSupporting) player.supportingBlockPos.orElse(null)?.let { support -> + if (support != pos) return@let + result(BreakResult.PlayerOnTop(pos, state)) + return + } + + /* liquid needs to be submerged first to be broken */ + if (!state.fluidState.isEmpty && state.isReplaceable) { + result(BreakResult.Submerge(pos, state)) + this@BreakChecks.run { checkPlacements() } + return + } + + if (breakConfig.avoidLiquids) { + val affectedBlocks = hashSetOf(pos) + val checkQueue = hashSetOf(pos) + + while (checkQueue.isNotEmpty()) { + val checkPos = checkQueue.first() + checkQueue.remove(checkPos) + for (offset in Direction.entries) { + val adjacentPos = checkPos.offset(offset) + + if (blockState(adjacentPos).block !is FallingBlock) continue + if (adjacentPos in affectedBlocks) continue + + if (offset == Direction.UP || FallingBlock.canFallThrough(blockState(adjacentPos.down()))) { + checkQueue.add(adjacentPos) + affectedBlocks.add(adjacentPos) + } + } + } + + val affectedFluids = affectedBlocks.fold(hashMapOf()) { accumulator, affectedPos -> + Direction.entries.forEach { offset -> + if (offset == Direction.DOWN) return@forEach + + val offsetPos = affectedPos.offset(offset) + val offsetState = blockState(offsetPos) + val fluidState = offsetState.fluidState + val fluid = fluidState.fluid + + if (fluidState.isEmpty || fluid !is FlowableFluid) return@forEach + + if (offset == Direction.UP) { + accumulator[offsetPos] = offsetState + return@fold accumulator + } + + if (offsetState.block is Waterloggable && !fluidState.isEmpty) { + accumulator[offsetPos] = offsetState + return@fold accumulator + } + + val levelDecreasePerBlock = + when (fluid) { + is WaterFluid -> fluid.getLevelDecreasePerBlock(world) + is LavaFluid -> fluid.getLevelDecreasePerBlock(world) + else -> 0 + } + + if (fluidState.level - levelDecreasePerBlock > 0) { + accumulator[offsetPos] = offsetState + return@fold accumulator + } + } + + return@fold accumulator + } + + if (affectedFluids.isNotEmpty()) { + val liquidOutOfBounds = affectedFluids.any { !world.worldBorder.contains(it.key) } + if (liquidOutOfBounds) { + result(GenericResult.Ignored(pos)) + return + } + + affectedFluids.forEach { (liquidPos, liquidState) -> + result(BreakResult.Submerge(liquidPos, liquidState)) + this@BreakChecks.run { checkPlacements(liquidPos, liquidState, TargetState.Solid) } + } + result(BreakResult.BlockedByFluid(pos, state)) + return + } + } + + val currentRotation = RotationManager.activeRotation + val currentCast = currentRotation.rayCast(buildConfig.interactReach, eye) + + val voxelShape = state.getOutlineShape(world, pos) + voxelShape.getClosestPointTo(eye).ifPresent { + // ToDo: Use closest point of shape of only visible faces + } + + val boxes = voxelShape.boundingBoxes.map { it.offset(pos) } + val verify: CheckedHit.() -> Boolean = { + hit.blockResult?.blockPos == pos + } + + // ToDo: Move this to a location where more of the context parameters can be properly set + /* the player is buried inside the block */ + if (boxes.any { it.contains(eye) }) { + currentCast?.blockResult?.let { blockHit -> + val rotationRequest = RotationRequest( + lookAtBlock(pos), this + ) + val breakContext = BreakContext( + blockHit, + rotationRequest, + player.inventory.selectedSlot, + StackSelection.EVERYTHING.select(), + instantBreakable(state, pos, breakConfig.breakThreshold), + state, + breakConfig.sorter, + this + ) + result(BreakResult.Break(pos, breakContext)) + return + } + } + + val validHits = mutableListOf() + val misses = mutableSetOf() + val reachSq = buildConfig.interactReach.pow(2) + + boxes.forEach { box -> + val sides = if (buildConfig.checkSideVisibility) { + box.getVisibleSurfaces(eye).intersect(Direction.entries) + } else Direction.entries.toSet() + // ToDo: Rewrite Rotation request system to allow support for all sim features and use the rotation finder + scanSurfaces(box, sides, buildConfig.resolution) { side, vec -> + if (eye distSq vec > reachSq) { + misses.add(vec) + return@scanSurfaces + } + + val newRotation = eye.rotationTo(vec) + + val hit = if (buildConfig.strictRayCast) { + newRotation.rayCast(buildConfig.interactReach, eye)?.blockResult + } else { + val hitVec = newRotation.castBox(box, buildConfig.interactReach, eye) + BlockHitResult(hitVec, side, pos, false) + } ?: return@scanSurfaces + + val checked = CheckedHit(hit, newRotation, buildConfig.interactReach) + if (!checked.verify()) return@scanSurfaces + + validHits.add(checked) + } + } + + if (validHits.isEmpty()) { + // ToDo: If we can only mine exposed surfaces we need to add not visible result here + result(GenericResult.OutOfReach(pos, eye, misses)) + return + } + + val bestHit = buildConfig.pointSelection.select(validHits) ?: return + val blockHit = bestHit.hit.blockResult ?: return + val target = lookAt(bestHit.targetRotation, 0.001) + val rotationRequest = RotationRequest(target, this) + val instant = instantBreakable(state, pos, breakConfig.breakThreshold) + + val breakContext = BreakContext( + blockHit, + rotationRequest, + player.inventory.selectedSlot, + StackSelection.EVERYTHING.select(), + instant, + state, + breakConfig.sorter, + this + ) + + if (gamemode.isCreative) { + result(BreakResult.Break(pos, breakContext)) + return + } + + val stackSelection = selectStack( + sorter = compareByDescending { + it.canBreak(CachedBlockPosition(world, pos, false)) + }.thenByDescending { + state.calcItemBlockBreakingDelta(pos, it) + } + ) { + isTool() and if (breakConfig.suitableToolsOnly) { + isSuitableForBreaking(state) + } else any() and if (breakConfig.forceSilkTouch) { + hasEnchantment(Enchantments.SILK_TOUCH) + } else any() and if (breakConfig.forceFortunePickaxe) { + hasEnchantment(Enchantments.FORTUNE) + } else any() and if (!breakConfig.useWoodenTools) { + hasTag(WOODEN_TOOL_MATERIALS).not() + } else any() and if (!breakConfig.useStoneTools) { + hasTag(STONE_TOOL_MATERIALS).not() + } else any() and if (!breakConfig.useIronTools) { + hasTag(IRON_TOOL_MATERIALS).not() + } else any() and if (!breakConfig.useDiamondTools) { + hasTag(DIAMOND_TOOL_MATERIALS).not() + } else any() and if (!breakConfig.useGoldTools) { + hasTag(GOLD_TOOL_MATERIALS).not() + } else any() and if (!breakConfig.useNetheriteTools) { + hasTag(NETHERITE_TOOL_MATERIALS).not() + } else any() + } + + val silentSwapSelection = selectContainer { + ofAnyType(MaterialContainer.Rank.HOTBAR) + } + + val swapCandidates = stackSelection.containerWithMaterial(silentSwapSelection) + if (swapCandidates.isEmpty()) { + result(GenericResult.WrongItemSelection(pos, breakContext, stackSelection, player.mainHandStack)) + return + } + + val swapStack = swapCandidates + .map { it.matchingStacks(stackSelection) } + .asSequence() + .flatten() + .let { containerStacks -> + var bestStack = ItemStack.EMPTY + var bestBreakDelta = -1f + containerStacks.forEach { stack -> + val breakDelta = state.calcItemBlockBreakingDelta(pos, stack) + if (breakDelta > bestBreakDelta || + (stack == player.mainHandStack && breakDelta >= bestBreakDelta) + ) { + bestBreakDelta = breakDelta + bestStack = stack + } + } + bestStack + } + + breakContext.apply { + hotbarIndex = player.hotbar.indexOf(swapStack) + itemSelection = stackSelection + instantBreak = instantBreakable( + state, + pos, + if (breakConfig.swapMode.isEnabled()) swapStack else player.mainHandStack, + breakConfig.breakThreshold + ) + } + result(BreakResult.Break(pos, breakContext)) + return + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecks.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecks.kt new file mode 100644 index 000000000..7f66a1962 --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecks.kt @@ -0,0 +1,323 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.interaction.construction.simulation.checks + +import com.lambda.context.AutomatedSafeContext +import com.lambda.interaction.construction.context.PlaceContext +import com.lambda.interaction.construction.result.BuildResult +import com.lambda.interaction.construction.result.Dependable +import com.lambda.interaction.construction.result.results.GenericResult +import com.lambda.interaction.construction.result.results.PlaceResult +import com.lambda.interaction.construction.simulation.SimChecker +import com.lambda.interaction.construction.simulation.SimInfo +import com.lambda.interaction.construction.verify.TargetState +import com.lambda.interaction.material.ContainerSelection.Companion.selectContainer +import com.lambda.interaction.material.StackSelection.Companion.select +import com.lambda.interaction.material.container.ContainerManager.containerWithMaterial +import com.lambda.interaction.material.container.MaterialContainer +import com.lambda.interaction.request.rotating.Rotation.Companion.rotation +import com.lambda.interaction.request.rotating.Rotation.Companion.rotationTo +import com.lambda.interaction.request.rotating.RotationManager +import com.lambda.interaction.request.rotating.RotationRequest +import com.lambda.interaction.request.rotating.visibilty.PlaceDirection +import com.lambda.interaction.request.rotating.visibilty.VisibilityChecker.CheckedHit +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.lookAt +import com.lambda.interaction.request.rotating.visibilty.lookInDirection +import com.lambda.util.BlockUtils +import com.lambda.util.BlockUtils.blockState +import com.lambda.util.BlockUtils.hasFluid +import com.lambda.util.BlockUtils.isEmpty +import com.lambda.util.math.distSq +import com.lambda.util.math.vec3d +import com.lambda.util.player.copyPlayer +import com.lambda.util.world.raycast.RayCastUtils.blockResult +import net.minecraft.block.BlockState +import net.minecraft.block.SlabBlock +import net.minecraft.block.pattern.CachedBlockPosition +import net.minecraft.item.BlockItem +import net.minecraft.item.ItemPlacementContext +import net.minecraft.item.ItemUsageContext +import net.minecraft.util.Hand +import net.minecraft.util.hit.BlockHitResult +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Vec3d +import net.minecraft.util.shape.VoxelShapes +import kotlin.math.pow + +object PlaceChecks : SimChecker(), Dependable { + override fun SimInfo.asDependant(buildResult: BuildResult) = + PlaceResult.Dependency(pos, buildResult) + + context(automatedSafeContext: AutomatedSafeContext, dependable: Dependable?) + fun SimInfo.checkPlacements( + pos: BlockPos = this.pos, + state: BlockState = this.state, + targetState: TargetState = this.targetState + ): Unit = with(automatedSafeContext) { + checkDependant(dependable) + + val statePromoting = state.block is SlabBlock && + targetState.matches(state, pos, preProcessing.ignore) + // If the target state is air then the only possible blocks it could place are to remove liquids so we use the Solid TargetState + val nextTargetState = if (targetState is TargetState.Air) { + if (state.hasFluid) TargetState.Solid + else return + } else if (targetState.isEmpty()) { + // Otherwise if the target state is empty, there's no situation where placement would be required so we can return + return + } else targetState + // For example, slabs count as state promoting because you are placing another block to promote the current state to the target state + if (!state.isReplaceable && !statePromoting) return + + preProcessing.sides.forEach { neighbor -> + val hitPos = if (!placeConfig.airPlace.isEnabled && (state.isEmpty || statePromoting)) + pos.offset(neighbor) else pos + val hitSide = neighbor.opposite + if (!world.worldBorder.contains(hitPos)) return@forEach + + val voxelShape = blockState(hitPos).getOutlineShape(world, hitPos).let { outlineShape -> + if (!outlineShape.isEmpty || !placeConfig.airPlace.isEnabled) outlineShape + else VoxelShapes.fullCube() + } + if (voxelShape.isEmpty) return@forEach + + val boxes = voxelShape.boundingBoxes.map { it.offset(hitPos) } + val verify: CheckedHit.() -> Boolean = { + hit.blockResult?.blockPos == hitPos && hit.blockResult?.side == hitSide + } + + val validHits = mutableListOf() + val misses = mutableSetOf() + val reachSq = buildConfig.interactReach.pow(2) + + boxes.forEach { box -> + val sides = if (buildConfig.checkSideVisibility) { + box.getVisibleSurfaces(eye).intersect(setOf(hitSide)) + } else setOf(hitSide) + + scanSurfaces(box, sides, buildConfig.resolution, preProcessing.surfaceScan) { _, vec -> + val distSquared = eye distSq vec + if (distSquared > reachSq) { + misses.add(vec) + return@scanSurfaces + } + + val newRotation = eye.rotationTo(vec) + + val hit = if (buildConfig.strictRayCast) { + val rayCast = newRotation.rayCast(buildConfig.interactReach, eye) + when { + rayCast != null && (!placeConfig.airPlace.isEnabled || eye distSq rayCast.pos <= distSquared) -> + rayCast.blockResult + + placeConfig.airPlace.isEnabled -> { + val hitVec = newRotation.castBox(box, buildConfig.interactReach, eye) + BlockHitResult(hitVec, hitSide, hitPos, false) + } + + else -> null + } + } else { + val hitVec = newRotation.castBox(box, buildConfig.interactReach, eye) + BlockHitResult(hitVec, hitSide, hitPos, false) + } ?: return@scanSurfaces + + val checked = CheckedHit(hit, newRotation, buildConfig.interactReach) + if (!checked.verify()) return@scanSurfaces + + validHits.add(checked) + } + } + + if (validHits.isEmpty()) { + if (misses.isNotEmpty()) { + result(GenericResult.OutOfReach(pos, eye, misses)) + return@forEach + } + + result(GenericResult.NotVisible(pos, hitPos, hitSide, eye.distanceTo(hitPos.offset(hitSide).vec3d))) + return@forEach + } + + buildConfig.pointSelection.select(validHits)?.let { checkedHit -> + val optimalStack = nextTargetState.getStack(pos) + + // ToDo: For each hand and sneak or not? + val fakePlayer = copyPlayer(player).apply { + this.rotation = RotationManager.serverRotation + } + + val checkedResult = checkedHit.hit + + // ToDo: Override the stack used for this to account for blocks where replaceability is dependent on the held item + val usageContext = ItemUsageContext( + fakePlayer, + Hand.MAIN_HAND, + checkedResult.blockResult, + ) + val cachePos = CachedBlockPosition( + usageContext.world, usageContext.blockPos, false + ) + val canBePlacedOn = optimalStack.canPlaceOn(cachePos) + if (!player.abilities.allowModifyWorld && !canBePlacedOn) { + result(PlaceResult.IllegalUsage(pos)) + return@forEach + } + + var context = ItemPlacementContext(usageContext) + + if (context.blockPos != pos) { + result(PlaceResult.UnexpectedPosition(pos, context.blockPos)) + return@forEach + } + + if (!optimalStack.item.isEnabled(world.enabledFeatures)) { + result(PlaceResult.BlockFeatureDisabled(pos, optimalStack)) + return@forEach + } + + if (!context.canPlace() && !statePromoting) { + result(PlaceResult.CantReplace(pos, context)) + return@forEach + } + + val blockItem = optimalStack.item as? BlockItem ?: run { + result(PlaceResult.NotItemBlock(pos, optimalStack)) + return@forEach + } + + context = blockItem.getPlacementContext(context) + ?: run { + result(PlaceResult.ScaffoldExceeded(pos, context)) + return@forEach + } + + lateinit var resultState: BlockState + var rot = fakePlayer.rotation + + val simulatePlaceState = placeState@{ + resultState = blockItem.getPlacementState(context) + ?: return@placeState PlaceResult.BlockedByEntity(pos) + + return@placeState if (!nextTargetState.matches(resultState, pos, preProcessing.ignore)) + PlaceResult.NoIntegrity( + pos, + resultState, + context, + (nextTargetState as? TargetState.State)?.blockState + ) + else null + } + + val currentDirIsValid = simulatePlaceState()?.let { basePlaceResult -> + if (!placeConfig.rotateForPlace) { + result(basePlaceResult) + return@forEach + } + false + } != false + + run rotate@{ + if (!placeConfig.axisRotate) { + fakePlayer.rotation = checkedHit.targetRotation + simulatePlaceState()?.let { rotatedPlaceResult -> + result(rotatedPlaceResult) + return@forEach + } + rot = fakePlayer.rotation + return@rotate + } + + fakePlayer.rotation = player.rotation + if (simulatePlaceState() == null) { + rot = fakePlayer.rotation + return@rotate + } + + PlaceDirection.entries.asReversed().forEachIndexed direction@{ index, direction -> + fakePlayer.rotation = direction.rotation + when (val placeResult = simulatePlaceState()) { + is PlaceResult.BlockedByEntity -> { + result(placeResult) + return@forEach + } + + is PlaceResult.NoIntegrity -> { + if (index != PlaceDirection.entries.lastIndex) return@direction + result(placeResult) + return@forEach + } + + else -> { + rot = fakePlayer.rotation + return@rotate + } + } + } + } + + val blockHit = checkedResult.blockResult ?: return@forEach + val hitBlock = blockState(blockHit.blockPos).block + val shouldSneak = hitBlock::class in BlockUtils.interactionBlocks + + val rotationRequest = if (placeConfig.axisRotate) { + lookInDirection(PlaceDirection.fromRotation(rot)) + } else lookAt(rot, 0.001) + + val placeContext = PlaceContext( + blockHit, + RotationRequest(rotationRequest, this), + player.inventory.selectedSlot, + context.blockPos, + blockState(context.blockPos), + resultState, + shouldSneak, + false, + currentDirIsValid, + this + ) + + val selection = optimalStack.item.select() + val containerSelection = selectContainer { ofAnyType(MaterialContainer.Rank.HOTBAR) } + val container = selection.containerWithMaterial(containerSelection).firstOrNull() ?: run { + result( + GenericResult.WrongItemSelection( + pos, + placeContext, + optimalStack.item.select(), + player.mainHandStack + ) + ) + return + } + val stack = selection.filterStacks(container.stacks).run { + firstOrNull { player.inventory.getSlotWithStack(it) == player.inventory.selectedSlot } + ?: first() + } + + placeContext.hotbarIndex = player.inventory.getSlotWithStack(stack) + + result(PlaceResult.Place(pos, placeContext)) + } + } + + return + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingChecks.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingChecks.kt new file mode 100644 index 000000000..6b9030510 --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingChecks.kt @@ -0,0 +1,224 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.interaction.construction.simulation.checks + +import com.lambda.context.AutomatedSafeContext +import com.lambda.interaction.construction.context.InteractionContext +import com.lambda.interaction.construction.result.BuildResult +import com.lambda.interaction.construction.result.Dependable +import com.lambda.interaction.construction.result.results.GenericResult +import com.lambda.interaction.construction.result.results.InteractResult +import com.lambda.interaction.construction.simulation.SimChecker +import com.lambda.interaction.construction.simulation.SimInfo +import com.lambda.interaction.construction.simulation.checks.PlaceChecks.checkPlacements +import com.lambda.interaction.construction.verify.TargetState +import com.lambda.interaction.material.ContainerSelection.Companion.selectContainer +import com.lambda.interaction.material.StackSelection.Companion.select +import com.lambda.interaction.material.container.ContainerManager.containerWithMaterial +import com.lambda.interaction.material.container.MaterialContainer +import com.lambda.interaction.request.rotating.Rotation.Companion.rotationTo +import com.lambda.interaction.request.rotating.RotationRequest +import com.lambda.interaction.request.rotating.visibilty.VisibilityChecker.CheckedHit +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.lookAt +import com.lambda.util.math.distSq +import com.lambda.util.math.vec3d +import com.lambda.util.player.SlotUtils.hotbar +import com.lambda.util.world.raycast.RayCastUtils.blockResult +import net.minecraft.block.BlockState +import net.minecraft.block.enums.SlabType +import net.minecraft.item.Item +import net.minecraft.state.property.Properties +import net.minecraft.util.hit.BlockHitResult +import net.minecraft.util.math.Direction +import net.minecraft.util.math.Vec3d +import kotlin.math.pow + +object PostProcessingChecks : SimChecker(), Dependable { + override fun SimInfo.asDependant(buildResult: BuildResult) = + InteractResult.Dependency(pos, buildResult) + + context(automatedSafeContext: AutomatedSafeContext, dependable: Dependable?) + fun SimInfo.checkPostProcessing(): Unit = with(automatedSafeContext) { + checkDependant(dependable) + + if (targetState !is TargetState.State) return@with + + if (!targetState.matches(state, pos, preProcessing.ignore)) return + + val mismatchedProperties = state.properties.filter { state.get(it) != targetState.blockState.get(it) } + mismatchedProperties.forEach { property -> + when (property) { + Properties.EYE -> { + if (state.get(Properties.EYE)) return@forEach + val expectedState = state.with(Properties.EYE, true) + simInteraction(expectedState, null, null, false) + } + + Properties.INVERTED -> { + val expectedState = state.with(Properties.INVERTED, !state.get(Properties.INVERTED)) + simInteraction(expectedState, null, null, false) + } + + Properties.DELAY -> { + val expectedState = + state.with(Properties.DELAY, state.cycle(Properties.DELAY).get(Properties.DELAY)) + simInteraction(expectedState, null, null, false) + } + + Properties.COMPARATOR_MODE -> { + val expectedState = state.with( + Properties.COMPARATOR_MODE, + state.cycle(Properties.COMPARATOR_MODE).get(Properties.COMPARATOR_MODE) + ) + simInteraction(expectedState, null, null, false) + } + + Properties.OPEN -> { + val expectedState = state.with(Properties.OPEN, !state.get(Properties.OPEN)) + simInteraction(expectedState, null, null, false) + } + + Properties.SLAB_TYPE -> { + if (targetState.blockState.get(Properties.SLAB_TYPE) != SlabType.DOUBLE) return@forEach + this@PostProcessingChecks.run { checkPlacements() } + } + } + } + } + + context(automatedSafeContext: AutomatedSafeContext) + private fun SimInfo.simInteraction( + expectedState: BlockState, + sides: Set?, + item: Item?, + placing: Boolean + ): Unit = with(automatedSafeContext) { + val boxes = state.getOutlineShape(world, pos).boundingBoxes.map { it.offset(pos) } + val validHits = mutableListOf() + val blockedHits = mutableSetOf() + val misses = mutableSetOf() + val airPlace = placing && placeConfig.airPlace.isEnabled + + boxes.forEach { box -> + val refinedSides = if (buildConfig.checkSideVisibility) { + box.getVisibleSurfaces(eye).let { visibleSides -> + sides?.let { specific -> + visibleSides.intersect(specific) + } ?: visibleSides.toSet() + } + } else sides ?: Direction.entries.toSet() + + scanSurfaces( + box, + refinedSides, + buildConfig.resolution, + preProcessing.surfaceScan + ) { hitSide, vec -> + val distSquared = eye distSq vec + if (distSquared > buildConfig.interactReach.pow(2)) { + misses.add(vec) + return@scanSurfaces + } + + val newRotation = eye.rotationTo(vec) + + val hit = if (buildConfig.strictRayCast) { + val rayCast = newRotation.rayCast(buildConfig.interactReach, eye) + when { + rayCast != null && (!airPlace || eye distSq rayCast.pos <= distSquared) -> + rayCast.blockResult + + airPlace -> { + val hitVec = newRotation.castBox(box, buildConfig.interactReach, eye) + BlockHitResult(hitVec, hitSide, pos, false) + } + + else -> null + } + } else { + val hitVec = newRotation.castBox(box, buildConfig.interactReach, eye) + BlockHitResult(hitVec, hitSide, pos, false) + } ?: return@scanSurfaces + + val checked = CheckedHit(hit, newRotation, buildConfig.interactReach) + if (hit.blockResult?.blockPos != pos) { + blockedHits.add(vec) + return@scanSurfaces + } + + validHits.add(checked) + } + } + + if (validHits.isEmpty()) { + if (misses.isNotEmpty()) { + result(GenericResult.OutOfReach(pos, eye, misses)) + } else { + //ToDo: Must clean up surface scan usage / renders. Added temporary direction until changes are made + result( + GenericResult.NotVisible( + pos, + pos, + Direction.UP, + eye.distanceTo(pos.offset(Direction.UP).vec3d) + ) + ) + } + return + } + + buildConfig.pointSelection.select(validHits)?.let { checkedHit -> + val checkedResult = checkedHit.hit + val rotationTarget = lookAt(checkedHit.targetRotation, 0.001) + val context = InteractionContext( + checkedResult.blockResult ?: return, + RotationRequest(rotationTarget, this), + player.inventory.selectedSlot, + state, + expectedState, + this + ) + + val stackSelection = (item ?: player.mainHandStack.item).select() + val hotbarCandidates = selectContainer { + matches(stackSelection) and ofAnyType(MaterialContainer.Rank.HOTBAR) + }.let { predicate -> + stackSelection.containerWithMaterial( predicate) + } + + if (hotbarCandidates.isEmpty()) { + result( + GenericResult.WrongItemSelection( + pos, + context, + stackSelection, + player.mainHandStack + ) + ) + return + } else { + context.hotbarIndex = + player.hotbar.indexOf(hotbarCandidates.first().matchingStacks(stackSelection).first()) + } + + result(InteractResult.Interact(pos, context)) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/RequirementChecks.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/RequirementChecks.kt new file mode 100644 index 000000000..830fd92b1 --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/RequirementChecks.kt @@ -0,0 +1,75 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.interaction.construction.simulation.checks + +import com.lambda.context.AutomatedSafeContext +import com.lambda.interaction.construction.result.results.GenericResult +import com.lambda.interaction.construction.result.results.PreSimResult +import com.lambda.interaction.construction.simulation.SimChecker +import com.lambda.interaction.construction.simulation.SimInfo +import com.lambda.interaction.construction.verify.TargetState +import com.lambda.util.player.gamemode +import com.lambda.util.world.WorldUtils.isLoaded +import net.minecraft.block.OperatorBlock + +object RequirementChecks : SimChecker() { + context(automatedSafeContext: AutomatedSafeContext) + fun SimInfo.checkRequirements(): Unit = with(automatedSafeContext) { + // the chunk is not loaded + if (!isLoaded(pos)) { + result(PreSimResult.ChunkNotLoaded(pos)) + return + } + + // block is already in the correct state + if (targetState.matches(state, pos)) { + result(PreSimResult.Done(pos)) + return + } + + // block should be ignored + if (state.block in breakConfig.ignoredBlocks && targetState.type == TargetState.Type.AIR) { + result(GenericResult.Ignored(pos)) + return + } + + // the player is in the wrong game mode to alter the block state + if (player.isBlockBreakingRestricted(world, pos, gamemode)) { + result(PreSimResult.Restricted(pos)) + return + } + + // the player has no permissions to alter the block state + if (state.block is OperatorBlock && !player.isCreativeLevelTwoOp) { + result(PreSimResult.NoPermission(pos, state)) + return + } + + // block is outside the world so it cant be altered + if (!world.worldBorder.contains(pos) || world.isOutOfHeightLimit(pos)) { + result(PreSimResult.OutOfWorld(pos)) + return + } + + // block is unbreakable, so it cant be broken or replaced + if (state.getHardness(world, pos) < 0 && !gamemode.isCreative) { + result(PreSimResult.Unbreakable(pos, state)) + return + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/construction/verify/StateMatcher.kt b/src/main/kotlin/com/lambda/interaction/construction/verify/StateMatcher.kt index 25eab2808..7f385c3a3 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/verify/StateMatcher.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/verify/StateMatcher.kt @@ -17,21 +17,22 @@ package com.lambda.interaction.construction.verify -import com.lambda.context.Automated +import com.lambda.context.AutomatedSafeContext +import com.lambda.context.SafeContext import net.minecraft.block.BlockState -import net.minecraft.client.world.ClientWorld import net.minecraft.item.ItemStack import net.minecraft.state.property.Property import net.minecraft.util.math.BlockPos interface StateMatcher { + context(safeContext: SafeContext) fun matches( state: BlockState, pos: BlockPos, - world: ClientWorld, ignoredProperties: Collection> = emptySet() ): Boolean - fun getStack(world: ClientWorld, pos: BlockPos, automated: Automated): ItemStack + context(automatedSafeContext: AutomatedSafeContext) + fun getStack(pos: BlockPos): ItemStack fun isEmpty(): Boolean } diff --git a/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt b/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt index af52771d9..739ca0164 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt @@ -17,14 +17,14 @@ package com.lambda.interaction.construction.verify -import com.lambda.context.Automated +import com.lambda.context.AutomatedSafeContext +import com.lambda.context.SafeContext import com.lambda.interaction.material.container.ContainerManager.findDisposable import com.lambda.util.BlockUtils.isEmpty import com.lambda.util.BlockUtils.matches import com.lambda.util.StringUtils.capitalize import com.lambda.util.item.ItemUtils.block import net.minecraft.block.BlockState -import net.minecraft.client.world.ClientWorld import net.minecraft.item.ItemStack import net.minecraft.item.Items import net.minecraft.state.property.Property @@ -40,14 +40,15 @@ sealed class TargetState(val type: Type) : StateMatcher { data object Empty : TargetState(Type.EMPTY) { override fun toString() = "Empty" + context(safeContext: SafeContext) override fun matches( state: BlockState, pos: BlockPos, - world: ClientWorld, ignoredProperties: Collection> ) = state.isEmpty - override fun getStack(world: ClientWorld, pos: BlockPos, automated: Automated): ItemStack = + context(automatedSafeContext: AutomatedSafeContext) + override fun getStack(pos: BlockPos): ItemStack = ItemStack.EMPTY override fun isEmpty() = true @@ -56,14 +57,15 @@ sealed class TargetState(val type: Type) : StateMatcher { data object Air : TargetState(Type.AIR) { override fun toString() = "Air" + context(safeContext: SafeContext) override fun matches( state: BlockState, pos: BlockPos, - world: ClientWorld, ignoredProperties: Collection> ) = state.isAir - override fun getStack(world: ClientWorld, pos: BlockPos, automated: Automated): ItemStack = + context(automatedSafeContext: AutomatedSafeContext) + override fun getStack(pos: BlockPos): ItemStack = ItemStack.EMPTY override fun isEmpty() = true @@ -72,16 +74,16 @@ sealed class TargetState(val type: Type) : StateMatcher { data object Solid : TargetState(Type.SOLID) { override fun toString() = "Solid" + context(safeContext: SafeContext) override fun matches( state: BlockState, pos: BlockPos, - world: ClientWorld, ignoredProperties: Collection> - ) = - state.isSolidBlock(world, pos) + ) = with(safeContext) { state.isSolidBlock(world, pos) } - override fun getStack(world: ClientWorld, pos: BlockPos, automated: Automated) = - with(automated) { + context(automatedSafeContext: AutomatedSafeContext) + override fun getStack(pos: BlockPos) = + with(automatedSafeContext) { findDisposable()?.stacks?.firstOrNull { it.item.block in inventoryConfig.disposables } ?: ItemStack(Items.NETHERRACK) @@ -93,17 +95,19 @@ sealed class TargetState(val type: Type) : StateMatcher { data class Support(val direction: Direction) : TargetState(Type.SUPPORT) { override fun toString() = "Support for ${direction.name}" + context(safeContext: SafeContext) override fun matches( state: BlockState, pos: BlockPos, - world: ClientWorld, ignoredProperties: Collection> - ) = + ) = with(safeContext) { world.getBlockState(pos.offset(direction)).isSolidBlock(world, pos.offset(direction)) || state.isSolidBlock(world, pos) + } - override fun getStack(world: ClientWorld, pos: BlockPos, automated: Automated) = - with(automated) { + context(automatedSafeContext: AutomatedSafeContext) + override fun getStack(pos: BlockPos) = + with(automatedSafeContext) { findDisposable()?.stacks?.firstOrNull { it.item.block in inventoryConfig.disposables } ?: ItemStack(Items.NETHERRACK) @@ -115,16 +119,17 @@ sealed class TargetState(val type: Type) : StateMatcher { data class State(val blockState: BlockState) : TargetState(Type.STATE) { override fun toString() = "State of $blockState" + context(safeContext: SafeContext) override fun matches( state: BlockState, pos: BlockPos, - world: ClientWorld, ignoredProperties: Collection> ) = state.matches(blockState, ignoredProperties) - override fun getStack(world: ClientWorld, pos: BlockPos, automated: Automated): ItemStack = - blockState.block.getPickStack(world, pos, blockState, true) + context(automatedSafeContext: AutomatedSafeContext) + override fun getStack(pos: BlockPos): ItemStack = + blockState.block.getPickStack(automatedSafeContext.world, pos, blockState, true) override fun isEmpty() = blockState.isEmpty } @@ -132,16 +137,17 @@ sealed class TargetState(val type: Type) : StateMatcher { data class Block(val block: net.minecraft.block.Block) : TargetState(Type.BLOCK) { override fun toString() = "Block of ${block.name.string.capitalize()}" + context(safeContext: SafeContext) override fun matches( state: BlockState, pos: BlockPos, - world: ClientWorld, ignoredProperties: Collection> ) = state.block == block - override fun getStack(world: ClientWorld, pos: BlockPos, automated: Automated): ItemStack = - block.getPickStack(world, pos, block.defaultState, true) + context(automatedSafeContext: AutomatedSafeContext) + override fun getStack(pos: BlockPos): ItemStack = + block.getPickStack(automatedSafeContext.world, pos, block.defaultState, true) override fun isEmpty() = block.defaultState.isEmpty } @@ -152,15 +158,16 @@ sealed class TargetState(val type: Type) : StateMatcher { private val block = itemStack.item.block + context(safeContext: SafeContext) override fun matches( state: BlockState, pos: BlockPos, - world: ClientWorld, ignoredProperties: Collection> ) = state.block == block - override fun getStack(world: ClientWorld, pos: BlockPos, automated: Automated): ItemStack = + context(automatedSafeContext: AutomatedSafeContext) + override fun getStack(pos: BlockPos): ItemStack = itemStack override fun isEmpty() = false diff --git a/src/main/kotlin/com/lambda/interaction/request/breaking/BreakInfo.kt b/src/main/kotlin/com/lambda/interaction/request/breaking/BreakInfo.kt index 98d9379cd..731d9e00c 100644 --- a/src/main/kotlin/com/lambda/interaction/request/breaking/BreakInfo.kt +++ b/src/main/kotlin/com/lambda/interaction/request/breaking/BreakInfo.kt @@ -156,7 +156,7 @@ data class BreakInfo( PlayerActionC2SPacket( action, context.blockPos, - context.result.side, + context.hitResult.side, sequence ) } diff --git a/src/main/kotlin/com/lambda/interaction/request/breaking/BreakManager.kt b/src/main/kotlin/com/lambda/interaction/request/breaking/BreakManager.kt index c9b7b449c..fc9fdfd9a 100644 --- a/src/main/kotlin/com/lambda/interaction/request/breaking/BreakManager.kt +++ b/src/main/kotlin/com/lambda/interaction/request/breaking/BreakManager.kt @@ -33,7 +33,7 @@ import com.lambda.graphics.renderer.esp.DynamicAABB import com.lambda.interaction.construction.blueprint.Blueprint.Companion.toStructure import com.lambda.interaction.construction.blueprint.StaticBlueprint.Companion.toBlueprint import com.lambda.interaction.construction.context.BreakContext -import com.lambda.interaction.construction.result.BreakResult +import com.lambda.interaction.construction.result.results.BreakResult import com.lambda.interaction.construction.simulation.BuildSimulator.simulate import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.material.StackSelection @@ -433,7 +433,7 @@ object BreakManager : RequestHandler( .let { infos -> rotationRequest = infos.firstOrNull { info -> info.breakConfig.rotateForBreak } ?.let { info -> - val rotation = info.context.rotation + val rotation = info.context.rotationRequest logger.debug("Requesting rotation", rotation) rotation.submit(false) } @@ -528,7 +528,7 @@ object BreakManager : RequestHandler( abandonedInfo.context.blockPos .toStructure(TargetState.Empty) .toBlueprint() - .simulate(player.eyePos) + .simulate() .asSequence() .filterIsInstance() .filter { canAccept(it.context) } @@ -685,7 +685,7 @@ object BreakManager : RequestHandler( return } - val hitResult = ctx.result + val hitResult = ctx.hitResult if (gamemode.isCreative && world.worldBorder.contains(ctx.blockPos)) { breakCooldown = breakConfig.breakDelay diff --git a/src/main/kotlin/com/lambda/interaction/request/interacting/InteractionManager.kt b/src/main/kotlin/com/lambda/interaction/request/interacting/InteractionManager.kt index 927827f7a..f5fc73080 100644 --- a/src/main/kotlin/com/lambda/interaction/request/interacting/InteractionManager.kt +++ b/src/main/kotlin/com/lambda/interaction/request/interacting/InteractionManager.kt @@ -122,10 +122,10 @@ object InteractionManager : RequestHandler( InteractionInfo(ctx, request.pendingInteractionsList, request).startPending() } if (interactConfig.interactConfirmationMode != InteractConfig.InteractConfirmationMode.AwaitThenInteract) { - interaction.interactBlock(player, Hand.MAIN_HAND, ctx.result) + interaction.interactBlock(player, Hand.MAIN_HAND, ctx.hitResult) } else { interaction.sendSequencedPacket(world) { sequence -> - PlayerInteractBlockC2SPacket(Hand.MAIN_HAND, ctx.result, sequence) + PlayerInteractBlockC2SPacket(Hand.MAIN_HAND, ctx.hitResult, sequence) } } if (interactConfig.swingHand) { 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 9bfc7ded4..e1d262a13 100644 --- a/src/main/kotlin/com/lambda/interaction/request/placing/PlaceManager.kt +++ b/src/main/kotlin/com/lambda/interaction/request/placing/PlaceManager.kt @@ -212,7 +212,7 @@ object PlaceManager : RequestHandler( */ private fun AutomatedSafeContext.placeBlock(placeContext: PlaceContext, request: PlaceRequest, hand: Hand): ActionResult { interaction.syncSelectedSlot() - val hitResult = placeContext.result + val hitResult = placeContext.hitResult if (!world.worldBorder.contains(hitResult.blockPos)) { logger.error("Placement position outside the world border", placeContext, request) return ActionResult.FAIL diff --git a/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt b/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt index dca765b20..5acfb7201 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt @@ -31,7 +31,7 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.construction.blueprint.StaticBlueprint.Companion.toBlueprint import com.lambda.interaction.construction.context.BreakContext import com.lambda.interaction.construction.context.BuildContext -import com.lambda.interaction.construction.result.BreakResult +import com.lambda.interaction.construction.result.results.BreakResult import com.lambda.interaction.construction.simulation.BuildSimulator.simulate import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.request.breaking.BreakConfig @@ -250,7 +250,7 @@ object PacketMine : Module( .filterNotNull() .associateWith { TargetState.State(blockState(it).fluidState.blockState) } .toBlueprint() - .simulate(player.eyePos) + .simulate() .asSequence() .filterIsInstance() .map { it.context } 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 fec4193d3..00fcc9292 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt @@ -27,7 +27,7 @@ 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.result.PlaceResult +import com.lambda.interaction.construction.result.results.PlaceResult import com.lambda.interaction.construction.simulation.BuildSimulator.simulate import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.request.Request.Companion.submit @@ -94,9 +94,9 @@ object Scaffold : Module( scaffoldPositions(beneath) .associateWith { TargetState.Solid } .toBlueprint() - .simulate(player.eyePos) + .simulate() .filterIsInstance() - .minByOrNull { it.blockPos distSq beneath } + .minByOrNull { it.pos distSq beneath } ?.let { result -> submit(PlaceRequest( setOf(result.context), diff --git a/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt b/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt index 46033ba3c..5d4bb80ee 100644 --- a/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt @@ -32,13 +32,18 @@ import com.lambda.interaction.construction.blueprint.PropagatingBlueprint import com.lambda.interaction.construction.blueprint.StaticBlueprint.Companion.toBlueprint import com.lambda.interaction.construction.blueprint.TickingBlueprint import com.lambda.interaction.construction.context.BuildContext -import com.lambda.interaction.construction.result.BreakResult import com.lambda.interaction.construction.result.BuildResult +import com.lambda.interaction.construction.result.Contextual +import com.lambda.interaction.construction.result.Dependent +import com.lambda.interaction.construction.result.Dependent.Companion.iterator import com.lambda.interaction.construction.result.Drawable -import com.lambda.interaction.construction.result.InteractResult import com.lambda.interaction.construction.result.Navigable -import com.lambda.interaction.construction.result.PlaceResult import com.lambda.interaction.construction.result.Resolvable +import com.lambda.interaction.construction.result.results.BreakResult +import com.lambda.interaction.construction.result.results.GenericResult +import com.lambda.interaction.construction.result.results.InteractResult +import com.lambda.interaction.construction.result.results.PlaceResult +import com.lambda.interaction.construction.result.results.PreSimResult import com.lambda.interaction.construction.simulation.BuildGoal import com.lambda.interaction.construction.simulation.BuildSimulator.simulate import com.lambda.interaction.construction.simulation.Simulation.Companion.simulation @@ -105,96 +110,109 @@ class BuildTask private constructor( if (collectDrops()) return@listen - val results = runSafeAutomated { blueprint.simulate(player.eyePos) } + simulateAndProcess() + } - AutomationConfig.drawables = results - .filterIsInstance() - .plus(pendingInteractions.toList()) + listen { + if (finishOnDone && blueprint.structure.isEmpty()) { + failure("Structure is empty") + return@listen + } + } + } - val resultsNotBlocked = results - .filter { result -> pendingInteractions.none { it.blockPos == result.blockPos } } - .sorted() + private fun SafeContext.simulateAndProcess() { + val results = runSafeAutomated { blueprint.simulate() } - val bestResult = resultsNotBlocked.firstOrNull() ?: return@listen - if (bestResult !is BuildResult.Contextual && pendingInteractions.isNotEmpty()) - return@listen - when (bestResult) { - is BuildResult.Done, - is BuildResult.Ignored, - is BuildResult.Unbreakable, - is BuildResult.Restricted, - is BuildResult.NoPermission -> { - if (iteratePropagating()) return@listen - - if (finishOnDone) success(blueprint.structure) - } + AutomationConfig.drawables = results + .filterIsInstance() + .plus(pendingInteractions.toList()) - is BuildResult.NotVisible, - is PlaceResult.NoIntegrity -> { - if (!buildConfig.pathing) return@listen - val sim = blueprint.simulation() - val goal = BuildGoal(sim, player.blockPos) - BaritoneManager.setGoalAndPath(goal) - } + val resultsNotBlocked = results + .filter { result -> pendingInteractions.none { it.blockPos == result.pos } } + .sorted() - is Navigable -> { - if (buildConfig.pathing) BaritoneManager.setGoalAndPath(bestResult.goal) - } + val bestResult = resultsNotBlocked.firstOrNull() ?: return + handleResult(bestResult, resultsNotBlocked) + } - is BuildResult.Contextual -> { - if (atMaxPendingInteractions) return@listen - when (bestResult) { - is BreakResult.Break -> { - val breakResults = resultsNotBlocked - .filterIsInstance() - .map { it.context } - - breakRequest(breakResults, pendingInteractions) { - onStop { breaks++ } - onItemDrop?.let { onItemDrop -> - onItemDrop { onItemDrop(it) } - } - }.submit() - return@listen - } - is PlaceResult.Place -> { - val placeResults = resultsNotBlocked - .filterIsInstance() - .map { it.context } - - PlaceRequest( - placeResults, - pendingInteractions, - this@BuildTask - ) { placements++ }.submit() - } - is InteractResult.Interact -> { - val interactResults = resultsNotBlocked - .filterIsInstance() - .map { it.context } - - InteractRequest( - interactResults, - pendingInteractions, - this@BuildTask, - null - ).submit() - } - } + private fun SafeContext.handleResult(result: BuildResult, allResults: List) { + if (result !is Contextual && pendingInteractions.isNotEmpty()) + return + + when (result) { + is PreSimResult.Done, + is PreSimResult.Unbreakable, + is PreSimResult.Restricted, + is PreSimResult.NoPermission, + is GenericResult.Ignored -> { + if (iteratePropagating()) { + simulateAndProcess() + return } - is Resolvable -> { - LOG.info("Resolving: ${bestResult.name}") + if (finishOnDone) success(blueprint.structure) + } + + is GenericResult.NotVisible, + is PlaceResult.NoIntegrity -> { + if (!buildConfig.pathing) return + val sim = blueprint.simulation() + val goal = BuildGoal(sim, player.blockPos) + BaritoneManager.setGoalAndPath(goal) + } - bestResult.resolve().execute(this@BuildTask) + is Navigable -> { + if (buildConfig.pathing) BaritoneManager.setGoalAndPath(result.goal) + } + + is Contextual -> { + if (atMaxPendingInteractions) return + when (result) { + is BreakResult.Break -> { + val breakResults = allResults + .filterIsInstance() + .map { it.context } + + breakRequest(breakResults, pendingInteractions) { + onStop { breaks++ } + onItemDrop?.let { onItemDrop -> + onItemDrop { onItemDrop(it) } + } + }.submit() + return + } + is PlaceResult.Place -> { + val placeResults = allResults + .filterIsInstance() + .map { it.context } + + PlaceRequest( + placeResults, + pendingInteractions, + this@BuildTask + ) { placements++ }.submit() + } + is InteractResult.Interact -> { + val interactResults = allResults + .filterIsInstance() + .map { it.context } + + InteractRequest( + interactResults, + pendingInteractions, + this@BuildTask, + null + ).submit() + } } } - } - listen { - if (finishOnDone && blueprint.structure.isEmpty()) { - failure("Structure is empty") - return@listen + is Dependent -> handleResult(result.iterator.last(), allResults) + + is Resolvable -> { + LOG.info("Resolving: ${result.name}") + result.resolve().execute(this@BuildTask) } } } diff --git a/src/main/kotlin/com/lambda/task/tasks/PlaceContainer.kt b/src/main/kotlin/com/lambda/task/tasks/PlaceContainer.kt index a8b59dbf7..99b8f1149 100644 --- a/src/main/kotlin/com/lambda/task/tasks/PlaceContainer.kt +++ b/src/main/kotlin/com/lambda/task/tasks/PlaceContainer.kt @@ -21,8 +21,8 @@ import com.lambda.context.Automated import com.lambda.context.SafeContext import com.lambda.interaction.construction.blueprint.Blueprint.Companion.toStructure import com.lambda.interaction.construction.blueprint.StaticBlueprint.Companion.toBlueprint -import com.lambda.interaction.construction.result.BuildResult -import com.lambda.interaction.construction.result.PlaceResult +import com.lambda.interaction.construction.result.results.GenericResult +import com.lambda.interaction.construction.result.results.PlaceResult import com.lambda.interaction.construction.simulation.BuildSimulator.simulate import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.request.ManagerUtils @@ -55,19 +55,19 @@ class PlaceContainer @Ta5kBuilder constructor( .flatMap { it.toStructure(TargetState.Stack(startStack)) .toBlueprint() - .simulate(player.eyePos) + .simulate() } } val options = results.filterIsInstance().filter { - canBeOpened(startStack, it.blockPos, it.context.result.side) - } + results.filterIsInstance().filter { - canBeOpened(startStack, it.blockPos, it.context.result.side) + canBeOpened(startStack, it.pos, it.context.hitResult.side) + } + results.filterIsInstance().filter { + canBeOpened(startStack, it.pos, it.context.hitResult.side) } val containerPosition = options.filter { // ToDo: Check based on if we can move the player close enough rather than y level once the custom pathfinder is merged - it.blockPos.y == player.blockPos.y - }.minByOrNull { it.blockPos distSq player.pos }?.blockPos ?: run { + it.pos.y == player.blockPos.y + }.minByOrNull { it.pos distSq player.pos }?.pos ?: run { failure("Couldn't find a valid container placement position for ${startStack.name.string}") return@onStart } From e01b41e64669cc72ecd2a0b9bb742e4215b50a93 Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Sun, 19 Oct 2025 16:09:46 +0100 Subject: [PATCH 02/30] dependent over dependant --- .../com/lambda/interaction/construction/result/Dependable.kt | 2 +- .../lambda/interaction/construction/simulation/SimChecker.kt | 4 ++-- .../interaction/construction/simulation/checks/BreakChecks.kt | 4 ++-- .../interaction/construction/simulation/checks/PlaceChecks.kt | 4 ++-- .../construction/simulation/checks/PostProcessingChecks.kt | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/com/lambda/interaction/construction/result/Dependable.kt b/src/main/kotlin/com/lambda/interaction/construction/result/Dependable.kt index 52fedcc5e..7bf30ac9c 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/result/Dependable.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/result/Dependable.kt @@ -20,5 +20,5 @@ package com.lambda.interaction.construction.result import com.lambda.interaction.construction.simulation.SimInfo interface Dependable { - fun SimInfo.asDependant(buildResult: BuildResult): BuildResult + fun SimInfo.asDependent(buildResult: BuildResult): BuildResult } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/SimChecker.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/SimChecker.kt index d1fd45171..e7b0b12e1 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/SimChecker.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/SimChecker.kt @@ -26,7 +26,7 @@ import net.minecraft.util.math.Vec3d abstract class SimChecker { val SafeContext.eye: Vec3d get() = player.eyePos - fun SimInfo.checkDependant(caller: Dependable?) { + fun SimInfo.checkDependent(caller: Dependable?) { if (caller == null) { dependencyStack.clear() return @@ -41,7 +41,7 @@ abstract class SimChecker { dependencyStack .asReversed() .fold(result) { acc, dependable -> - with(dependable) { asDependant(acc) } + with(dependable) { asDependent(acc) } } ) } diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecks.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecks.kt index bdcbefc5c..5fc31ca7d 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecks.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecks.kt @@ -71,12 +71,12 @@ import net.minecraft.util.math.Vec3d import kotlin.math.pow object BreakChecks : SimChecker(), Dependable { - override fun SimInfo.asDependant(buildResult: BuildResult) = + override fun SimInfo.asDependent(buildResult: BuildResult) = BreakResult.Dependency(pos, buildResult) context(automatedSafeContext: AutomatedSafeContext, dependable: Dependable?) fun SimInfo.checkBreaks(): Unit = with(automatedSafeContext) { - checkDependant(dependable) + checkDependent(dependable) /* is a block that will be destroyed by breaking adjacent blocks */ if (!breakConfig.breakWeakBlocks && state.block.hardness == 0f && !state.isAir && state.isNotEmpty) { diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecks.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecks.kt index 7f66a1962..2b6c8a92e 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecks.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecks.kt @@ -62,7 +62,7 @@ import net.minecraft.util.shape.VoxelShapes import kotlin.math.pow object PlaceChecks : SimChecker(), Dependable { - override fun SimInfo.asDependant(buildResult: BuildResult) = + override fun SimInfo.asDependent(buildResult: BuildResult) = PlaceResult.Dependency(pos, buildResult) context(automatedSafeContext: AutomatedSafeContext, dependable: Dependable?) @@ -71,7 +71,7 @@ object PlaceChecks : SimChecker(), Dependable { state: BlockState = this.state, targetState: TargetState = this.targetState ): Unit = with(automatedSafeContext) { - checkDependant(dependable) + checkDependent(dependable) val statePromoting = state.block is SlabBlock && targetState.matches(state, pos, preProcessing.ignore) diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingChecks.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingChecks.kt index 6b9030510..9e470bd02 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingChecks.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingChecks.kt @@ -51,12 +51,12 @@ import net.minecraft.util.math.Vec3d import kotlin.math.pow object PostProcessingChecks : SimChecker(), Dependable { - override fun SimInfo.asDependant(buildResult: BuildResult) = + override fun SimInfo.asDependent(buildResult: BuildResult) = InteractResult.Dependency(pos, buildResult) context(automatedSafeContext: AutomatedSafeContext, dependable: Dependable?) fun SimInfo.checkPostProcessing(): Unit = with(automatedSafeContext) { - checkDependant(dependable) + checkDependent(dependable) if (targetState !is TargetState.State) return@with From 3387261cc1f100b4725ddf2752a51c4db05ce81f Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Tue, 21 Oct 2025 14:58:42 +0100 Subject: [PATCH 03/30] sim cleanup and improvement --- .../com/lambda/config/groups/BreakSettings.kt | 1 - .../construction/context/BreakContext.kt | 12 +- .../processing/ProcessorRegistry.kt | 16 +- .../construction/result/Dependable.kt | 6 +- .../interaction/construction/result/Rank.kt | 4 - .../result/results/GenericResult.kt | 9 - .../result/results/PlaceResult.kt | 14 - .../construction/simulation/BuildSimulator.kt | 35 +- .../construction/simulation/ISimInfo.kt | 72 ++++ .../construction/simulation/SimChecker.kt | 17 +- .../construction/simulation/SimInfo.kt | 36 -- .../simulation/checks/BreakChecker.kt | 347 ++++++++++++++++++ .../simulation/checks/BreakChecks.kt | 338 ----------------- .../simulation/checks/PlaceChecker.kt | 346 +++++++++++++++++ .../simulation/checks/PlaceChecks.kt | 323 ---------------- ...singChecks.kt => PostProcessingChecker.kt} | 61 +-- .../simulation/checks/RequirementChecks.kt | 20 +- .../construction/verify/StateMatcher.kt | 2 + .../construction/verify/TargetState.kt | 54 ++- .../interaction/material/StackSelection.kt | 16 +- .../request/breaking/BreakConfig.kt | 1 - .../interaction/request/rotating/Rotation.kt | 2 - .../rotating/visibilty/VisibilityChecker.kt | 7 +- .../lambda/module/modules/player/FastBreak.kt | 3 - .../module/modules/player/PacketMine.kt | 1 - .../kotlin/com/lambda/task/tasks/BuildTask.kt | 6 +- .../com/lambda/task/tasks/PlaceContainer.kt | 5 +- .../com/lambda/util/item/ItemStackUtils.kt | 11 +- .../kotlin/com/lambda/util/item/ItemUtils.kt | 3 + 29 files changed, 913 insertions(+), 855 deletions(-) create mode 100644 src/main/kotlin/com/lambda/interaction/construction/simulation/ISimInfo.kt delete mode 100644 src/main/kotlin/com/lambda/interaction/construction/simulation/SimInfo.kt create mode 100644 src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecker.kt delete mode 100644 src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecks.kt create mode 100644 src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt delete mode 100644 src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecks.kt rename src/main/kotlin/com/lambda/interaction/construction/simulation/checks/{PostProcessingChecks.kt => PostProcessingChecker.kt} (84%) diff --git a/src/main/kotlin/com/lambda/config/groups/BreakSettings.kt b/src/main/kotlin/com/lambda/config/groups/BreakSettings.kt index 704ca6fea..05da95f10 100644 --- a/src/main/kotlin/com/lambda/config/groups/BreakSettings.kt +++ b/src/main/kotlin/com/lambda/config/groups/BreakSettings.kt @@ -76,7 +76,6 @@ class BreakSettings( // Block override val avoidLiquids by c.setting("Avoid Liquids", true, "Avoids breaking blocks that would cause liquid to spill", visibility = vis).group(baseGroup, Group.General) override val avoidSupporting by c.setting("Avoid Supporting", true, "Avoids breaking the block supporting the player", visibility = vis).group(baseGroup, Group.General) - override val breakWeakBlocks by c.setting("Break Weak Blocks", false, "Break blocks that dont have structural integrity (e.g: grass)", visibility = vis).group(baseGroup, Group.General) override val ignoredBlocks by c.setting("Ignored Blocks", allSigns, description = "Blocks that wont be broken", visibility = vis).group(baseGroup, Group.General) // Tool diff --git a/src/main/kotlin/com/lambda/interaction/construction/context/BreakContext.kt b/src/main/kotlin/com/lambda/interaction/construction/context/BreakContext.kt index ebe3c889d..2ba2a003c 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/context/BreakContext.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/context/BreakContext.kt @@ -43,11 +43,10 @@ import kotlin.random.Random data class BreakContext( override val hitResult: BlockHitResult, override val rotationRequest: RotationRequest, - override var hotbarIndex: Int, - var itemSelection: StackSelection, - var instantBreak: Boolean, + override val hotbarIndex: Int, + val itemSelection: StackSelection, + val instantBreak: Boolean, override var cachedState: BlockState, - val sortMode: BreakConfig.SortMode, private val automated: Automated ) : BuildContext(), LogContext, Automated by automated { private val baseColor = Color(222, 0, 0, 25) @@ -61,10 +60,10 @@ data class BreakContext( override fun compareTo(other: BuildContext): Int = runSafe { return when (other) { is BreakContext -> compareByDescending { - if (sortMode == BreakConfig.SortMode.Tool) it.hotbarIndex == HotbarManager.serverSlot + if (breakConfig.sorter == BreakConfig.SortMode.Tool) it.hotbarIndex == HotbarManager.serverSlot else 0 }.thenBy { - when (sortMode) { + when (breakConfig.sorter) { BreakConfig.SortMode.Tool, BreakConfig.SortMode.Closest -> player.eyePos.distance(it.hitResult.pos, it.cachedState.block) BreakConfig.SortMode.Farthest -> -player.eyePos.distance(it.hitResult.pos, it.cachedState.block) @@ -104,7 +103,6 @@ data class BreakContext( value("Instant Break", instantBreak) value("Cached State", cachedState) value("Expected State", expectedState) - value("Sort Mode", sortMode) } } } diff --git a/src/main/kotlin/com/lambda/interaction/construction/processing/ProcessorRegistry.kt b/src/main/kotlin/com/lambda/interaction/construction/processing/ProcessorRegistry.kt index a11e7bb04..c966e6526 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/processing/ProcessorRegistry.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/processing/ProcessorRegistry.kt @@ -21,8 +21,6 @@ import com.lambda.core.Loadable import com.lambda.interaction.construction.verify.TargetState import com.lambda.util.reflections.getInstances import net.minecraft.block.BlockState -import net.minecraft.block.SlabBlock -import net.minecraft.block.enums.SlabType import net.minecraft.state.property.Properties import net.minecraft.util.math.BlockPos @@ -108,7 +106,8 @@ object ProcessorRegistry : Loadable { override fun load() = "Loaded ${processors.size} pre processors" fun TargetState.getProcessingInfo(pos: BlockPos) = - if (this is TargetState.State) { + if (this !is TargetState.State) PreProcessingInfo.DEFAULT + else { val get: () -> PreProcessingInfo? = get@{ val infoAccumulator = PreProcessingInfoAccumulator() @@ -122,15 +121,6 @@ object ProcessorRegistry : Loadable { infoAccumulator.complete() } - if (isExemptFromCache()) { - get() - } else { - processorCache.getOrPut(blockState, get) - } - } else { - PreProcessingInfo.DEFAULT + processorCache.getOrPut(blockState, get) } - - private fun TargetState.State.isExemptFromCache() = - blockState.block is SlabBlock && blockState.get(Properties.SLAB_TYPE) == SlabType.DOUBLE } diff --git a/src/main/kotlin/com/lambda/interaction/construction/result/Dependable.kt b/src/main/kotlin/com/lambda/interaction/construction/result/Dependable.kt index 7bf30ac9c..45eafefd9 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/result/Dependable.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/result/Dependable.kt @@ -17,8 +17,8 @@ package com.lambda.interaction.construction.result -import com.lambda.interaction.construction.simulation.SimInfo +import com.lambda.interaction.construction.simulation.ISimInfo -interface Dependable { - fun SimInfo.asDependent(buildResult: BuildResult): BuildResult +interface Dependable : ISimInfo { + fun asDependent(buildResult: BuildResult): BuildResult } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/construction/result/Rank.kt b/src/main/kotlin/com/lambda/interaction/construction/result/Rank.kt index 8296780c8..f7b4007f6 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/result/Rank.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/result/Rank.kt @@ -31,7 +31,6 @@ enum class Rank { ChunkNotLoaded, PlaceCantReplace, BreakPlayerOnTop, - PlaceNotItemBlock, // not solvable OutOfWorld, @@ -50,7 +49,4 @@ enum class Rank { Done, Ignored, NoMatch; - - val solvable: Boolean - get() = ordinal < PlaceNotItemBlock.ordinal } diff --git a/src/main/kotlin/com/lambda/interaction/construction/result/results/GenericResult.kt b/src/main/kotlin/com/lambda/interaction/construction/result/results/GenericResult.kt index b016eea71..1b9b5f306 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/result/results/GenericResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/result/results/GenericResult.kt @@ -20,7 +20,6 @@ package com.lambda.interaction.construction.result.results import baritone.api.pathing.goals.GoalNear import com.lambda.context.Automated import com.lambda.graphics.renderer.esp.ShapeBuilder -import com.lambda.interaction.construction.context.BuildContext import com.lambda.interaction.construction.result.BuildResult import com.lambda.interaction.construction.result.ComparableResult import com.lambda.interaction.construction.result.Drawable @@ -82,7 +81,6 @@ sealed class GenericResult : BuildResult() { */ data class WrongItemSelection( override val pos: BlockPos, - val context: BuildContext, val neededSelection: StackSelection, val currentItem: ItemStack ) : Drawable, Resolvable, GenericResult() { @@ -102,13 +100,6 @@ sealed class GenericResult : BuildResult() { override fun ShapeBuilder.buildRenderer() { box(pos, color, color) } - - override fun compareResult(other: ComparableResult): Int { - return when (other) { - is WrongItemSelection -> context.compareTo(other.context) - else -> super.compareResult(other) - } - } } /** diff --git a/src/main/kotlin/com/lambda/interaction/construction/result/results/PlaceResult.kt b/src/main/kotlin/com/lambda/interaction/construction/result/results/PlaceResult.kt index 69ba47754..0a8fce8a4 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/result/results/PlaceResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/result/results/PlaceResult.kt @@ -182,20 +182,6 @@ sealed class PlaceResult : BuildResult() { override val rank = Rank.PlaceIllegalUsage } - /** - * Represents the result of a place operation where the provided item does not match the expected item block type. - * - * @property pos The position of the block where the operation was attempted. - * @property itemStack The item stack that was checked during the place operation. - */ - data class NotItemBlock( - override val pos: BlockPos, - val itemStack: ItemStack, - ) : PlaceResult() { - override val name: String get() = "${this::class.simpleName} at ${pos.toShortString()}" - override val rank = Rank.PlaceNotItemBlock - } - data class Dependency( override val pos: BlockPos, override val dependency: BuildResult 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 20671534d..dd72b217c 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt @@ -19,46 +19,45 @@ package com.lambda.interaction.construction.simulation import com.lambda.context.AutomatedSafeContext import com.lambda.interaction.construction.blueprint.Blueprint -import com.lambda.interaction.construction.processing.ProcessorRegistry.getProcessingInfo import com.lambda.interaction.construction.result.BuildResult import com.lambda.interaction.construction.result.results.PostSimResult -import com.lambda.interaction.construction.simulation.checks.BreakChecks.checkBreaks -import com.lambda.interaction.construction.simulation.checks.PlaceChecks.checkPlacements -import com.lambda.interaction.construction.simulation.checks.PostProcessingChecks.checkPostProcessing +import com.lambda.interaction.construction.simulation.ISimInfo.Companion.simInfo +import com.lambda.interaction.construction.simulation.checks.BreakChecker.Companion.checkBreaks +import com.lambda.interaction.construction.simulation.checks.PlaceChecker.Companion.checkPlacements +import com.lambda.interaction.construction.simulation.checks.PostProcessingChecker.Companion.checkPostProcessing import com.lambda.interaction.construction.simulation.checks.RequirementChecks.checkRequirements import com.lambda.util.BlockUtils.blockState import io.ktor.util.collections.* import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.joinAll +import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking object BuildSimulator : SimChecker() { - context(context: AutomatedSafeContext) + context(automatedSafeContext: AutomatedSafeContext) fun Blueprint.simulate(): Set = runBlocking(Dispatchers.Default) { val concurrentSet = ConcurrentSet() - with(context) { + with(automatedSafeContext) { structure.entries .map { (pos, targetState) -> - async { - val preProcessing = targetState.getProcessingInfo(pos) ?: return@async - val simInfo = SimInfo( + launch { + val simInfo = simInfo( pos, blockState(pos), targetState, - preProcessing, concurrentSet - ) + ) ?: return@launch with(simInfo) { with(null) { - checkRequirements() - checkPostProcessing() - checkPlacements() - checkBreaks() + if (checkRequirements() || + checkPostProcessing() || + checkPlacements() || + checkBreaks()) return@launch + else result(PostSimResult.NoMatch(pos)) } } } - }.awaitAll() + }.joinAll() } return@runBlocking concurrentSet diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/ISimInfo.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/ISimInfo.kt new file mode 100644 index 000000000..ab5c076c2 --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/ISimInfo.kt @@ -0,0 +1,72 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.interaction.construction.simulation + +import com.lambda.context.Automated +import com.lambda.interaction.construction.processing.PreProcessingInfo +import com.lambda.interaction.construction.processing.ProcessorRegistry.getProcessingInfo +import com.lambda.interaction.construction.result.BuildResult +import com.lambda.interaction.construction.result.Dependable +import com.lambda.interaction.construction.verify.TargetState +import net.minecraft.block.BlockState +import net.minecraft.util.math.BlockPos +import java.util.* + +interface ISimInfo : Automated { + val pos: BlockPos + val state: BlockState + val targetState: TargetState + val preProcessing: PreProcessingInfo + val concurrentResults: MutableSet + val dependencyStack: Stack + + companion object { + @SimCheckerDsl + fun Automated.simInfo( + pos: BlockPos, + state: BlockState, + targetState: TargetState, + concurrentResults: MutableSet, + ): SimInfo? { + val preProcessingInfo = targetState.getProcessingInfo(pos) ?: return null + return SimInfo(pos, state, targetState, preProcessingInfo, concurrentResults, this) + } + + @SimCheckerDsl + fun ISimInfo.simInfo( + pos: BlockPos = this.pos, + state: BlockState = this.state, + targetState: TargetState = this.targetState, + concurrentResults: MutableSet = this.concurrentResults + ): SimInfo? { + val preProcessingInfo = targetState.getProcessingInfo(pos) ?: return null + return SimInfo(pos, state, targetState, preProcessingInfo, concurrentResults, this) + } + } +} + +data class SimInfo( + override val pos: BlockPos, + override val state: BlockState, + override val targetState: TargetState, + override val preProcessing: PreProcessingInfo, + override val concurrentResults: MutableSet, + val automated: Automated +) : ISimInfo, Automated by automated { + override val dependencyStack = Stack() +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/SimChecker.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/SimChecker.kt index e7b0b12e1..c9c86fb93 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/SimChecker.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/SimChecker.kt @@ -23,10 +23,14 @@ import com.lambda.interaction.construction.result.Dependable import com.lambda.interaction.construction.result.results.GenericResult import net.minecraft.util.math.Vec3d +@DslMarker +annotation class SimCheckerDsl + +@SimCheckerDsl abstract class SimChecker { val SafeContext.eye: Vec3d get() = player.eyePos - fun SimInfo.checkDependent(caller: Dependable?) { + protected fun ISimInfo.checkDependent(caller: Dependable?) { if (caller == null) { dependencyStack.clear() return @@ -34,10 +38,13 @@ abstract class SimChecker { dependencyStack.push(caller) } - fun SimInfo.result(result: GenericResult) = addResult(result) - fun SimInfo.result(result: T) = addResult(result) - private fun SimInfo.addResult(result: BuildResult) { - concurrentResults.add( + fun ISimInfo.result(result: GenericResult) = addResult(result) + + fun ISimInfo.result(result: T) = addResult(result) + + private fun ISimInfo.addResult(result: BuildResult) { + if (this@SimChecker is BuildSimulator) concurrentResults.add(result) + else concurrentResults.add( dependencyStack .asReversed() .fold(result) { acc, dependable -> diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/SimInfo.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/SimInfo.kt deleted file mode 100644 index ce2296664..000000000 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/SimInfo.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2025 Lambda - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.lambda.interaction.construction.simulation - -import com.lambda.interaction.construction.processing.PreProcessingInfo -import com.lambda.interaction.construction.result.BuildResult -import com.lambda.interaction.construction.result.Dependable -import com.lambda.interaction.construction.verify.TargetState -import net.minecraft.block.BlockState -import net.minecraft.util.math.BlockPos -import java.util.* - -data class SimInfo( - val pos: BlockPos, - val state: BlockState, - val targetState: TargetState, - val preProcessing: PreProcessingInfo, - val concurrentResults: MutableSet -) { - val dependencyStack = Stack() -} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecker.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecker.kt new file mode 100644 index 000000000..9fe4ebe5a --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecker.kt @@ -0,0 +1,347 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.interaction.construction.simulation.checks + +import com.lambda.context.AutomatedSafeContext +import com.lambda.context.SafeContext +import com.lambda.interaction.construction.context.BreakContext +import com.lambda.interaction.construction.result.BuildResult +import com.lambda.interaction.construction.result.Dependable +import com.lambda.interaction.construction.result.results.BreakResult +import com.lambda.interaction.construction.result.results.GenericResult +import com.lambda.interaction.construction.simulation.ISimInfo +import com.lambda.interaction.construction.simulation.ISimInfo.Companion.simInfo +import com.lambda.interaction.construction.simulation.SimChecker +import com.lambda.interaction.construction.simulation.SimCheckerDsl +import com.lambda.interaction.construction.simulation.SimInfo +import com.lambda.interaction.construction.simulation.checks.PlaceChecker.Companion.checkPlacements +import com.lambda.interaction.construction.verify.TargetState +import com.lambda.interaction.material.ContainerSelection.Companion.selectContainer +import com.lambda.interaction.material.StackSelection +import com.lambda.interaction.material.StackSelection.Companion.EVERYTHING +import com.lambda.interaction.material.StackSelection.Companion.select +import com.lambda.interaction.material.StackSelection.Companion.selectStack +import com.lambda.interaction.material.container.ContainerManager.containerWithMaterial +import com.lambda.interaction.material.container.MaterialContainer +import com.lambda.interaction.request.rotating.Rotation.Companion.rotationTo +import com.lambda.interaction.request.rotating.RotationManager +import com.lambda.interaction.request.rotating.RotationRequest +import com.lambda.interaction.request.rotating.visibilty.VisibilityChecker.CheckedHit +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.lookAt +import com.lambda.interaction.request.rotating.visibilty.lookAtBlock +import com.lambda.threading.runSafe +import com.lambda.util.BlockUtils.blockState +import com.lambda.util.BlockUtils.calcItemBlockBreakingDelta +import com.lambda.util.BlockUtils.instantBreakable +import com.lambda.util.item.ItemStackUtils.inventoryIndex +import com.lambda.util.math.distSq +import com.lambda.util.world.raycast.RayCastUtils.blockResult +import io.ktor.util.collections.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.joinAll +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import net.minecraft.block.BlockState +import net.minecraft.block.FallingBlock +import net.minecraft.block.Waterloggable +import net.minecraft.block.pattern.CachedBlockPosition +import net.minecraft.enchantment.Enchantments +import net.minecraft.fluid.FlowableFluid +import net.minecraft.fluid.LavaFluid +import net.minecraft.fluid.WaterFluid +import net.minecraft.item.ItemStack +import net.minecraft.registry.tag.ItemTags.DIAMOND_TOOL_MATERIALS +import net.minecraft.registry.tag.ItemTags.GOLD_TOOL_MATERIALS +import net.minecraft.registry.tag.ItemTags.IRON_TOOL_MATERIALS +import net.minecraft.registry.tag.ItemTags.NETHERITE_TOOL_MATERIALS +import net.minecraft.registry.tag.ItemTags.STONE_TOOL_MATERIALS +import net.minecraft.registry.tag.ItemTags.WOODEN_TOOL_MATERIALS +import net.minecraft.util.hit.BlockHitResult +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Direction +import net.minecraft.util.math.Vec3d +import kotlin.jvm.optionals.getOrNull +import kotlin.math.pow + +class BreakChecker @SimCheckerDsl private constructor(simInfo: SimInfo) + : SimChecker(), Dependable, + ISimInfo by simInfo +{ + val stackSelection: StackSelection by lazy { + runSafe { + selectStack( + sorter = compareByDescending { + it.canBreak(CachedBlockPosition(world, pos, false)) + }.thenByDescending { + state.calcItemBlockBreakingDelta(pos, it) + } + ) { + EVERYTHING + .andIf(breakConfig.suitableToolsOnly) { + isSuitableForBreaking(state) + }.andIf(breakConfig.forceSilkTouch) { + hasEnchantment(Enchantments.SILK_TOUCH) + }.andIf(breakConfig.forceFortunePickaxe) { + hasEnchantment(Enchantments.FORTUNE) + }.andIf(!breakConfig.useWoodenTools) { + hasTag(WOODEN_TOOL_MATERIALS).not() + }.andIf(!breakConfig.useStoneTools) { + hasTag(STONE_TOOL_MATERIALS).not() + }.andIf(!breakConfig.useIronTools) { + hasTag(IRON_TOOL_MATERIALS).not() + }.andIf(!breakConfig.useDiamondTools) { + hasTag(DIAMOND_TOOL_MATERIALS).not() + }.andIf(!breakConfig.useGoldTools) { + hasTag(GOLD_TOOL_MATERIALS).not() + }.andIf(!breakConfig.useNetheriteTools) { + hasTag(NETHERITE_TOOL_MATERIALS).not() + } + } + } ?: EVERYTHING.select() + } + + override fun asDependent(buildResult: BuildResult) = + BreakResult.Dependency(pos, buildResult) + + companion object { + @SimCheckerDsl + context(automatedSafeContext: AutomatedSafeContext, dependable: Dependable?) + suspend fun SimInfo.checkBreaks() = + BreakChecker(this).run { + checkDependent(dependable) + automatedSafeContext.checkBreaks() + } + } + + private suspend fun AutomatedSafeContext.checkBreaks(): Boolean { + if (!targetState.isEmpty()) return false + + /* player is standing on top of the block */ + if (breakConfig.avoidSupporting) player.supportingBlockPos.getOrNull()?.let { support -> + if (support != pos) return@let + result(BreakResult.PlayerOnTop(pos, state)) + return true + } + + /* liquid needs to be submerged first to be broken */ + if (targetState.getState(pos).isAir && !state.fluidState.isEmpty && state.isReplaceable) { + result(BreakResult.Submerge(pos, state)) + return simInfo(pos, state, TargetState.Solid)?.checkPlacements() ?: true + } + + if (breakConfig.avoidLiquids && affectsFluids()) return true + + val voxelShape = state.getOutlineShape(world, pos) + + val boxes = voxelShape.boundingBoxes.map { it.offset(pos) } + + val swapStack = getSwapStack() ?: return true + val instant = instantBreakable( + state, pos, + if (breakConfig.swapMode.isEnabled()) swapStack else player.mainHandStack, + breakConfig.breakThreshold + ) + + val currentRotation = RotationManager.activeRotation + val currentCast = currentRotation.rayCast(buildConfig.interactReach, eye) + + /* the player is buried inside the block */ + if (boxes.any { it.contains(eye) }) { + currentCast?.blockResult?.let { blockHit -> + val rotationRequest = RotationRequest(lookAtBlock(pos), this) + val breakContext = BreakContext( + blockHit, + rotationRequest, + swapStack.inventoryIndex, + stackSelection, + instant, + state, + this + ) + result(BreakResult.Break(pos, breakContext)) + } + return true + } + + val validHits = ConcurrentSet() + val misses = ConcurrentSet() + val reachSq = buildConfig.interactReach.pow(2) + + withContext(Dispatchers.Default) { + boxes.map { box -> + launch { + val sides = if (buildConfig.checkSideVisibility) { + box.getVisibleSurfaces(eye).intersect(Direction.entries) + } else Direction.entries.toSet() + scanSurfaces(box, sides, buildConfig.resolution) { side, vec -> + if (eye distSq vec > reachSq) { + misses.add(vec) + return@scanSurfaces + } + + val newRotation = eye.rotationTo(vec) + + val hit = if (buildConfig.strictRayCast) { + newRotation.rayCast(buildConfig.interactReach, eye)?.blockResult + } else { + val hitVec = newRotation.castBox(box, buildConfig.interactReach, eye) + BlockHitResult(hitVec, side, pos, false) + } ?: return@scanSurfaces + + if (hit.blockResult?.blockPos != pos) return@scanSurfaces + val checked = CheckedHit(hit, newRotation, buildConfig.interactReach) + + validHits.add(checked) + } + } + }.joinAll() + } + + if (validHits.isEmpty()) { + // ToDo: If we can only mine exposed surfaces we need to add not visible result here + result(GenericResult.OutOfReach(pos, eye, misses)) + return true + } + + val bestHit = buildConfig.pointSelection.select(validHits) ?: return true + val blockHit = bestHit.hit.blockResult ?: return true + val target = lookAt(bestHit.targetRotation, 0.001) + val rotationRequest = RotationRequest(target, this) + + val breakContext = BreakContext( + blockHit, + rotationRequest, + swapStack.inventoryIndex, + stackSelection, + instant, + state, + this + ) + + result(BreakResult.Break(pos, breakContext)) + return true + } + + private fun SafeContext.getSwapStack(): ItemStack? { + val silentSwapSelection = selectContainer { + ofAnyType(MaterialContainer.Rank.HOTBAR) + } + + val swapCandidates = stackSelection.containerWithMaterial(silentSwapSelection) + if (swapCandidates.isEmpty()) { + result(GenericResult.WrongItemSelection(pos, stackSelection, player.mainHandStack)) + return null + } + + return swapCandidates + .map { it.matchingStacks(stackSelection) } + .asSequence() + .flatten() + .let { containerStacks -> + var bestStack = ItemStack.EMPTY + var bestBreakDelta = -1f + containerStacks.forEach { stack -> + val breakDelta = state.calcItemBlockBreakingDelta(pos, stack) + if (breakDelta > bestBreakDelta || + (stack == player.mainHandStack && breakDelta >= bestBreakDelta) + ) { + bestBreakDelta = breakDelta + bestStack = stack + } + } + bestStack + } + } + + private suspend fun AutomatedSafeContext.affectsFluids(): Boolean { + val affectedBlocks = hashSetOf(pos) + val checkQueue = hashSetOf(pos) + + while (checkQueue.isNotEmpty()) { + val checkPos = checkQueue.first() + checkQueue.remove(checkPos) + for (offset in Direction.entries) { + val adjacentPos = checkPos.offset(offset) + + if (blockState(adjacentPos).block !is FallingBlock) continue + if (adjacentPos in affectedBlocks) continue + + if (offset == Direction.UP || FallingBlock.canFallThrough(blockState(adjacentPos.down()))) { + checkQueue.add(adjacentPos) + affectedBlocks.add(adjacentPos) + } + } + } + + val affectedFluids = affectedBlocks.fold(hashMapOf()) { accumulator, affectedPos -> + Direction.entries.forEach { offset -> + if (offset == Direction.DOWN) return@forEach + + val offsetPos = affectedPos.offset(offset) + val offsetState = blockState(offsetPos) + val fluidState = offsetState.fluidState + val fluid = fluidState.fluid + + if (fluidState.isEmpty || fluid !is FlowableFluid) return@forEach + + if (offset == Direction.UP) { + accumulator[offsetPos] = offsetState + return@fold accumulator + } + + if (offsetState.block is Waterloggable && !fluidState.isEmpty) { + accumulator[offsetPos] = offsetState + return@fold accumulator + } + + val levelDecreasePerBlock = + when (fluid) { + is WaterFluid -> fluid.getLevelDecreasePerBlock(world) + is LavaFluid -> fluid.getLevelDecreasePerBlock(world) + else -> 0 + } + + if (fluidState.level - levelDecreasePerBlock > 0) { + accumulator[offsetPos] = offsetState + return@fold accumulator + } + } + + return@fold accumulator + } + + if (affectedFluids.isNotEmpty()) { + val liquidOutOfBounds = affectedFluids.any { !world.worldBorder.contains(it.key) } + if (liquidOutOfBounds) { + result(GenericResult.Ignored(pos)) + return true + } + + affectedFluids.forEach { (liquidPos, liquidState) -> + result(BreakResult.Submerge(liquidPos, liquidState)) + simInfo(liquidPos, liquidState, TargetState.Solid)?.checkPlacements() + } + result(BreakResult.BlockedByFluid(pos, state)) + return true + } + + return false + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecks.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecks.kt deleted file mode 100644 index 5fc31ca7d..000000000 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecks.kt +++ /dev/null @@ -1,338 +0,0 @@ -/* - * Copyright 2025 Lambda - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.lambda.interaction.construction.simulation.checks - -import com.lambda.context.AutomatedSafeContext -import com.lambda.interaction.construction.context.BreakContext -import com.lambda.interaction.construction.result.BuildResult -import com.lambda.interaction.construction.result.Dependable -import com.lambda.interaction.construction.result.results.BreakResult -import com.lambda.interaction.construction.result.results.GenericResult -import com.lambda.interaction.construction.simulation.SimChecker -import com.lambda.interaction.construction.simulation.SimInfo -import com.lambda.interaction.construction.simulation.checks.PlaceChecks.checkPlacements -import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.material.ContainerSelection.Companion.selectContainer -import com.lambda.interaction.material.StackSelection -import com.lambda.interaction.material.StackSelection.Companion.select -import com.lambda.interaction.material.StackSelection.Companion.selectStack -import com.lambda.interaction.material.container.ContainerManager.containerWithMaterial -import com.lambda.interaction.material.container.MaterialContainer -import com.lambda.interaction.request.rotating.Rotation.Companion.rotationTo -import com.lambda.interaction.request.rotating.RotationManager -import com.lambda.interaction.request.rotating.RotationRequest -import com.lambda.interaction.request.rotating.visibilty.VisibilityChecker.CheckedHit -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.lookAt -import com.lambda.interaction.request.rotating.visibilty.lookAtBlock -import com.lambda.util.BlockUtils.blockState -import com.lambda.util.BlockUtils.calcItemBlockBreakingDelta -import com.lambda.util.BlockUtils.instantBreakable -import com.lambda.util.BlockUtils.isNotEmpty -import com.lambda.util.math.distSq -import com.lambda.util.player.SlotUtils.hotbar -import com.lambda.util.player.gamemode -import com.lambda.util.world.raycast.RayCastUtils.blockResult -import net.minecraft.block.BlockState -import net.minecraft.block.FallingBlock -import net.minecraft.block.Waterloggable -import net.minecraft.block.pattern.CachedBlockPosition -import net.minecraft.enchantment.Enchantments -import net.minecraft.fluid.FlowableFluid -import net.minecraft.fluid.LavaFluid -import net.minecraft.fluid.WaterFluid -import net.minecraft.item.ItemStack -import net.minecraft.registry.tag.ItemTags.DIAMOND_TOOL_MATERIALS -import net.minecraft.registry.tag.ItemTags.GOLD_TOOL_MATERIALS -import net.minecraft.registry.tag.ItemTags.IRON_TOOL_MATERIALS -import net.minecraft.registry.tag.ItemTags.NETHERITE_TOOL_MATERIALS -import net.minecraft.registry.tag.ItemTags.STONE_TOOL_MATERIALS -import net.minecraft.registry.tag.ItemTags.WOODEN_TOOL_MATERIALS -import net.minecraft.util.hit.BlockHitResult -import net.minecraft.util.math.BlockPos -import net.minecraft.util.math.Direction -import net.minecraft.util.math.Vec3d -import kotlin.math.pow - -object BreakChecks : SimChecker(), Dependable { - override fun SimInfo.asDependent(buildResult: BuildResult) = - BreakResult.Dependency(pos, buildResult) - - context(automatedSafeContext: AutomatedSafeContext, dependable: Dependable?) - fun SimInfo.checkBreaks(): Unit = with(automatedSafeContext) { - checkDependent(dependable) - - /* is a block that will be destroyed by breaking adjacent blocks */ - if (!breakConfig.breakWeakBlocks && state.block.hardness == 0f && !state.isAir && state.isNotEmpty) { - result(GenericResult.Ignored(pos)) - return - } - - /* player is standing on top of the block */ - if (breakConfig.avoidSupporting) player.supportingBlockPos.orElse(null)?.let { support -> - if (support != pos) return@let - result(BreakResult.PlayerOnTop(pos, state)) - return - } - - /* liquid needs to be submerged first to be broken */ - if (!state.fluidState.isEmpty && state.isReplaceable) { - result(BreakResult.Submerge(pos, state)) - this@BreakChecks.run { checkPlacements() } - return - } - - if (breakConfig.avoidLiquids) { - val affectedBlocks = hashSetOf(pos) - val checkQueue = hashSetOf(pos) - - while (checkQueue.isNotEmpty()) { - val checkPos = checkQueue.first() - checkQueue.remove(checkPos) - for (offset in Direction.entries) { - val adjacentPos = checkPos.offset(offset) - - if (blockState(adjacentPos).block !is FallingBlock) continue - if (adjacentPos in affectedBlocks) continue - - if (offset == Direction.UP || FallingBlock.canFallThrough(blockState(adjacentPos.down()))) { - checkQueue.add(adjacentPos) - affectedBlocks.add(adjacentPos) - } - } - } - - val affectedFluids = affectedBlocks.fold(hashMapOf()) { accumulator, affectedPos -> - Direction.entries.forEach { offset -> - if (offset == Direction.DOWN) return@forEach - - val offsetPos = affectedPos.offset(offset) - val offsetState = blockState(offsetPos) - val fluidState = offsetState.fluidState - val fluid = fluidState.fluid - - if (fluidState.isEmpty || fluid !is FlowableFluid) return@forEach - - if (offset == Direction.UP) { - accumulator[offsetPos] = offsetState - return@fold accumulator - } - - if (offsetState.block is Waterloggable && !fluidState.isEmpty) { - accumulator[offsetPos] = offsetState - return@fold accumulator - } - - val levelDecreasePerBlock = - when (fluid) { - is WaterFluid -> fluid.getLevelDecreasePerBlock(world) - is LavaFluid -> fluid.getLevelDecreasePerBlock(world) - else -> 0 - } - - if (fluidState.level - levelDecreasePerBlock > 0) { - accumulator[offsetPos] = offsetState - return@fold accumulator - } - } - - return@fold accumulator - } - - if (affectedFluids.isNotEmpty()) { - val liquidOutOfBounds = affectedFluids.any { !world.worldBorder.contains(it.key) } - if (liquidOutOfBounds) { - result(GenericResult.Ignored(pos)) - return - } - - affectedFluids.forEach { (liquidPos, liquidState) -> - result(BreakResult.Submerge(liquidPos, liquidState)) - this@BreakChecks.run { checkPlacements(liquidPos, liquidState, TargetState.Solid) } - } - result(BreakResult.BlockedByFluid(pos, state)) - return - } - } - - val currentRotation = RotationManager.activeRotation - val currentCast = currentRotation.rayCast(buildConfig.interactReach, eye) - - val voxelShape = state.getOutlineShape(world, pos) - voxelShape.getClosestPointTo(eye).ifPresent { - // ToDo: Use closest point of shape of only visible faces - } - - val boxes = voxelShape.boundingBoxes.map { it.offset(pos) } - val verify: CheckedHit.() -> Boolean = { - hit.blockResult?.blockPos == pos - } - - // ToDo: Move this to a location where more of the context parameters can be properly set - /* the player is buried inside the block */ - if (boxes.any { it.contains(eye) }) { - currentCast?.blockResult?.let { blockHit -> - val rotationRequest = RotationRequest( - lookAtBlock(pos), this - ) - val breakContext = BreakContext( - blockHit, - rotationRequest, - player.inventory.selectedSlot, - StackSelection.EVERYTHING.select(), - instantBreakable(state, pos, breakConfig.breakThreshold), - state, - breakConfig.sorter, - this - ) - result(BreakResult.Break(pos, breakContext)) - return - } - } - - val validHits = mutableListOf() - val misses = mutableSetOf() - val reachSq = buildConfig.interactReach.pow(2) - - boxes.forEach { box -> - val sides = if (buildConfig.checkSideVisibility) { - box.getVisibleSurfaces(eye).intersect(Direction.entries) - } else Direction.entries.toSet() - // ToDo: Rewrite Rotation request system to allow support for all sim features and use the rotation finder - scanSurfaces(box, sides, buildConfig.resolution) { side, vec -> - if (eye distSq vec > reachSq) { - misses.add(vec) - return@scanSurfaces - } - - val newRotation = eye.rotationTo(vec) - - val hit = if (buildConfig.strictRayCast) { - newRotation.rayCast(buildConfig.interactReach, eye)?.blockResult - } else { - val hitVec = newRotation.castBox(box, buildConfig.interactReach, eye) - BlockHitResult(hitVec, side, pos, false) - } ?: return@scanSurfaces - - val checked = CheckedHit(hit, newRotation, buildConfig.interactReach) - if (!checked.verify()) return@scanSurfaces - - validHits.add(checked) - } - } - - if (validHits.isEmpty()) { - // ToDo: If we can only mine exposed surfaces we need to add not visible result here - result(GenericResult.OutOfReach(pos, eye, misses)) - return - } - - val bestHit = buildConfig.pointSelection.select(validHits) ?: return - val blockHit = bestHit.hit.blockResult ?: return - val target = lookAt(bestHit.targetRotation, 0.001) - val rotationRequest = RotationRequest(target, this) - val instant = instantBreakable(state, pos, breakConfig.breakThreshold) - - val breakContext = BreakContext( - blockHit, - rotationRequest, - player.inventory.selectedSlot, - StackSelection.EVERYTHING.select(), - instant, - state, - breakConfig.sorter, - this - ) - - if (gamemode.isCreative) { - result(BreakResult.Break(pos, breakContext)) - return - } - - val stackSelection = selectStack( - sorter = compareByDescending { - it.canBreak(CachedBlockPosition(world, pos, false)) - }.thenByDescending { - state.calcItemBlockBreakingDelta(pos, it) - } - ) { - isTool() and if (breakConfig.suitableToolsOnly) { - isSuitableForBreaking(state) - } else any() and if (breakConfig.forceSilkTouch) { - hasEnchantment(Enchantments.SILK_TOUCH) - } else any() and if (breakConfig.forceFortunePickaxe) { - hasEnchantment(Enchantments.FORTUNE) - } else any() and if (!breakConfig.useWoodenTools) { - hasTag(WOODEN_TOOL_MATERIALS).not() - } else any() and if (!breakConfig.useStoneTools) { - hasTag(STONE_TOOL_MATERIALS).not() - } else any() and if (!breakConfig.useIronTools) { - hasTag(IRON_TOOL_MATERIALS).not() - } else any() and if (!breakConfig.useDiamondTools) { - hasTag(DIAMOND_TOOL_MATERIALS).not() - } else any() and if (!breakConfig.useGoldTools) { - hasTag(GOLD_TOOL_MATERIALS).not() - } else any() and if (!breakConfig.useNetheriteTools) { - hasTag(NETHERITE_TOOL_MATERIALS).not() - } else any() - } - - val silentSwapSelection = selectContainer { - ofAnyType(MaterialContainer.Rank.HOTBAR) - } - - val swapCandidates = stackSelection.containerWithMaterial(silentSwapSelection) - if (swapCandidates.isEmpty()) { - result(GenericResult.WrongItemSelection(pos, breakContext, stackSelection, player.mainHandStack)) - return - } - - val swapStack = swapCandidates - .map { it.matchingStacks(stackSelection) } - .asSequence() - .flatten() - .let { containerStacks -> - var bestStack = ItemStack.EMPTY - var bestBreakDelta = -1f - containerStacks.forEach { stack -> - val breakDelta = state.calcItemBlockBreakingDelta(pos, stack) - if (breakDelta > bestBreakDelta || - (stack == player.mainHandStack && breakDelta >= bestBreakDelta) - ) { - bestBreakDelta = breakDelta - bestStack = stack - } - } - bestStack - } - - breakContext.apply { - hotbarIndex = player.hotbar.indexOf(swapStack) - itemSelection = stackSelection - instantBreak = instantBreakable( - state, - pos, - if (breakConfig.swapMode.isEnabled()) swapStack else player.mainHandStack, - breakConfig.breakThreshold - ) - } - result(BreakResult.Break(pos, breakContext)) - return - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt new file mode 100644 index 000000000..4cb4afea5 --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt @@ -0,0 +1,346 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.interaction.construction.simulation.checks + +import com.lambda.context.AutomatedSafeContext +import com.lambda.context.SafeContext +import com.lambda.interaction.construction.context.PlaceContext +import com.lambda.interaction.construction.result.BuildResult +import com.lambda.interaction.construction.result.Dependable +import com.lambda.interaction.construction.result.results.GenericResult +import com.lambda.interaction.construction.result.results.PlaceResult +import com.lambda.interaction.construction.simulation.ISimInfo +import com.lambda.interaction.construction.simulation.SimChecker +import com.lambda.interaction.construction.simulation.SimCheckerDsl +import com.lambda.interaction.construction.simulation.SimInfo +import com.lambda.interaction.construction.verify.TargetState +import com.lambda.interaction.material.ContainerSelection.Companion.selectContainer +import com.lambda.interaction.material.StackSelection.Companion.select +import com.lambda.interaction.material.container.ContainerManager.containerWithMaterial +import com.lambda.interaction.material.container.MaterialContainer +import com.lambda.interaction.request.rotating.Rotation.Companion.rotation +import com.lambda.interaction.request.rotating.Rotation.Companion.rotationTo +import com.lambda.interaction.request.rotating.RotationManager +import com.lambda.interaction.request.rotating.RotationRequest +import com.lambda.interaction.request.rotating.visibilty.PlaceDirection +import com.lambda.interaction.request.rotating.visibilty.VisibilityChecker.CheckedHit +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.lookAt +import com.lambda.interaction.request.rotating.visibilty.lookInDirection +import com.lambda.threading.runSafeAutomated +import com.lambda.util.BlockUtils +import com.lambda.util.BlockUtils.blockState +import com.lambda.util.BlockUtils.isEmpty +import com.lambda.util.item.ItemStackUtils.inventoryIndex +import com.lambda.util.item.ItemUtils.blockItem +import com.lambda.util.math.distSq +import com.lambda.util.math.vec3d +import com.lambda.util.player.copyPlayer +import com.lambda.util.world.raycast.RayCastUtils.blockResult +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.cancel +import kotlinx.coroutines.joinAll +import kotlinx.coroutines.launch +import kotlinx.coroutines.supervisorScope +import kotlinx.coroutines.withContext +import net.minecraft.block.BlockState +import net.minecraft.block.pattern.CachedBlockPosition +import net.minecraft.client.network.ClientPlayerEntity +import net.minecraft.item.ItemPlacementContext +import net.minecraft.item.ItemStack +import net.minecraft.item.ItemUsageContext +import net.minecraft.item.Items +import net.minecraft.util.Hand +import net.minecraft.util.hit.BlockHitResult +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Vec3d +import net.minecraft.util.shape.VoxelShapes +import kotlin.math.pow + +class PlaceChecker @SimCheckerDsl private constructor(simInfo: SimInfo) + : SimChecker(), Dependable, + ISimInfo by simInfo +{ + lateinit var resultState: BlockState + var rot = RotationManager.serverRotation + + private val swapStack by lazy { + runSafeAutomated { + val optimalStack = targetState.getStack(pos) + val stackSelection = optimalStack.item.select() + val containerSelection = selectContainer { ofAnyType(MaterialContainer.Rank.HOTBAR) } + val container = stackSelection.containerWithMaterial(containerSelection).firstOrNull() ?: run { + result( + GenericResult.WrongItemSelection( + pos, + optimalStack.item.select(), + player.mainHandStack + ) + ) + return@runSafeAutomated ItemStack(Items.AIR) + } + return@runSafeAutomated stackSelection.filterStacks(container.stacks).run { + firstOrNull { it.inventoryIndex == player.inventory.selectedSlot } + ?: first() + } + } ?: ItemStack(Items.AIR) + } + private val blockItem get() = swapStack.blockItem + + private var currentDirIsValid = false + + override fun asDependent(buildResult: BuildResult) = + PlaceResult.Dependency(pos, buildResult) + + companion object { + @SimCheckerDsl + context(automatedSafeContext: AutomatedSafeContext, dependable: Dependable?) + suspend fun SimInfo.checkPlacements() = + PlaceChecker(this).run { + checkDependent(dependable) + automatedSafeContext.checkPlacements() + } + } + + private suspend fun AutomatedSafeContext.checkPlacements(): Boolean { + if (targetState.isEmpty()) return false + + supervisorScope { + withContext(Dispatchers.Default) { + preProcessing.sides.map { neighbor -> + launch { + val hitBlockPos = if (!placeConfig.airPlace.isEnabled && state.isEmpty) + pos.offset(neighbor) else pos + val hitSide = neighbor.opposite + if (!world.worldBorder.contains(hitBlockPos)) return@launch + + val voxelShape = blockState(hitBlockPos).getOutlineShape(world, hitBlockPos).let { outlineShape -> + if (!outlineShape.isEmpty || !placeConfig.airPlace.isEnabled) outlineShape + else VoxelShapes.fullCube() + } + if (voxelShape.isEmpty) return@launch + + val boxes = voxelShape.boundingBoxes.map { it.offset(hitBlockPos) } + val verify: CheckedHit.() -> Boolean = { + hit.blockResult?.blockPos == hitBlockPos && hit.blockResult?.side == hitSide + } + + val validHits = mutableListOf() + val misses = mutableSetOf() + val reachSq = buildConfig.interactReach.pow(2) + + boxes.map { box -> + launch { + val sides = if (buildConfig.checkSideVisibility) { + box.getVisibleSurfaces(eye).intersect(setOf(hitSide)) + } else setOf(hitSide) + + scanSurfaces(box, sides, buildConfig.resolution, preProcessing.surfaceScan) { _, vec -> + val distSquared = eye distSq vec + if (distSquared > reachSq) { + misses.add(vec) + return@scanSurfaces + } + + val newRotation = eye.rotationTo(vec) + + val hit = if (buildConfig.strictRayCast) { + val rayCast = newRotation.rayCast(buildConfig.interactReach, eye) + when { + rayCast != null && (!placeConfig.airPlace.isEnabled || eye distSq rayCast.pos <= distSquared) -> + rayCast.blockResult + + placeConfig.airPlace.isEnabled -> { + val hitVec = newRotation.castBox(box, buildConfig.interactReach, eye) + BlockHitResult(hitVec, hitSide, hitBlockPos, false) + } + + else -> null + } + } else { + val hitVec = newRotation.castBox(box, buildConfig.interactReach, eye) + BlockHitResult(hitVec, hitSide, hitBlockPos, false) + } ?: return@scanSurfaces + + val checked = CheckedHit(hit, newRotation, buildConfig.interactReach) + if (!checked.verify()) return@scanSurfaces + + validHits.add(checked) + } + } + }.joinAll() + + if (validHits.isEmpty()) { + if (misses.isNotEmpty()) { + result(GenericResult.OutOfReach(pos, eye, misses)) + return@launch + } + + result(GenericResult.NotVisible(pos, hitBlockPos, hitSide, eye.distanceTo(hitBlockPos.offset(hitSide).vec3d))) + return@launch + } + + if (swapStack.item == Items.AIR) + this@supervisorScope.cancel() + else if (!swapStack.item.isEnabled(world.enabledFeatures)) { + result(PlaceResult.BlockFeatureDisabled(pos, swapStack)) + this@supervisorScope.cancel() + } else selectHitPos(validHits) + } + }.joinAll() + } + } + + return true + } + + private fun AutomatedSafeContext.selectHitPos(validHits: List) { + buildConfig.pointSelection.select(validHits)?.let { checkedHit -> + // ToDo: For each hand and sneak or not? + val fakePlayer = copyPlayer(player).apply { + this.rotation = RotationManager.serverRotation + } + + val blockHit = checkedHit.hit.blockResult ?: return + + // ToDo: Override the stack used for this to account for blocks where replaceability is dependent on the held item + val usageContext = ItemUsageContext( + fakePlayer, + Hand.MAIN_HAND, + blockHit, + ) + val cachePos = CachedBlockPosition( + usageContext.world, usageContext.blockPos, false + ) + + if (!player.abilities.allowModifyWorld && !swapStack.canPlaceOn(cachePos)) { + result(PlaceResult.IllegalUsage(pos)) + return + } + + var context = ItemPlacementContext(usageContext) + + if (context.blockPos != pos) { + result(PlaceResult.UnexpectedPosition(pos, context.blockPos)) + return + } + + if (!context.canPlace()) { + result(PlaceResult.CantReplace(pos, context)) + return + } + + context = swapStack.blockItem.getPlacementContext(context) ?: run { + result(PlaceResult.ScaffoldExceeded(pos, context)) + return + } + + if (!simRotation(fakePlayer, checkedHit, context)) return + + val hitBlock = blockState(blockHit.blockPos).block + val shouldSneak = hitBlock::class in BlockUtils.interactionBlocks + + val rotationRequest = if (placeConfig.axisRotate) { + lookInDirection(PlaceDirection.fromRotation(rot)) + } else lookAt(rot, 0.001) + + val placeContext = PlaceContext( + blockHit, + RotationRequest(rotationRequest, this@PlaceChecker), + swapStack.inventoryIndex, + context.blockPos, + blockState(context.blockPos), + resultState, + shouldSneak, + false, + currentDirIsValid, + this@PlaceChecker + ) + + result(PlaceResult.Place(pos, placeContext)) + } + + return + } + + private fun SafeContext.simRotation( + fakePlayer: ClientPlayerEntity, + checkedHit: CheckedHit, + context: ItemPlacementContext + ): Boolean { + currentDirIsValid = if (testPlaceState(pos, targetState, context) != PlaceTestResult.Success) { + if (!placeConfig.rotateForPlace) return false + else false + } else true + + if (!placeConfig.axisRotate) { + fakePlayer.rotation = checkedHit.targetRotation + if (testPlaceState(pos, targetState, context) != PlaceTestResult.Success) return false + rot = fakePlayer.rotation + return true + } + + fakePlayer.rotation = player.rotation + if (testPlaceState(pos, targetState, context) == PlaceTestResult.Success) { + rot = fakePlayer.rotation + return true + } + + PlaceDirection.entries.asReversed().forEachIndexed direction@{ index, direction -> + fakePlayer.rotation = direction.rotation + when (testPlaceState(pos, targetState, context)) { + PlaceTestResult.BlockedByEntity -> return@direction + + PlaceTestResult.NoIntegrity -> { + if (index != PlaceDirection.entries.lastIndex) return@direction + return false + } + + else -> { + rot = fakePlayer.rotation + return true + } + } + } + + return true + } + + private fun SafeContext.testPlaceState( + pos: BlockPos, + targetState: TargetState, + context: ItemPlacementContext + ): PlaceTestResult { + resultState = blockItem.getPlacementState(context) ?: run { + result(PlaceResult.BlockedByEntity(pos)) + return PlaceTestResult.BlockedByEntity + } + + return if (!targetState.matches(resultState, pos, preProcessing.ignore)) { + result(PlaceResult.NoIntegrity(pos, resultState, context, (targetState as? TargetState.State)?.blockState)) + PlaceTestResult.NoIntegrity + } else PlaceTestResult.Success + } + + private enum class PlaceTestResult { + Success, + BlockedByEntity, + NoIntegrity + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecks.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecks.kt deleted file mode 100644 index 2b6c8a92e..000000000 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecks.kt +++ /dev/null @@ -1,323 +0,0 @@ -/* - * Copyright 2025 Lambda - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.lambda.interaction.construction.simulation.checks - -import com.lambda.context.AutomatedSafeContext -import com.lambda.interaction.construction.context.PlaceContext -import com.lambda.interaction.construction.result.BuildResult -import com.lambda.interaction.construction.result.Dependable -import com.lambda.interaction.construction.result.results.GenericResult -import com.lambda.interaction.construction.result.results.PlaceResult -import com.lambda.interaction.construction.simulation.SimChecker -import com.lambda.interaction.construction.simulation.SimInfo -import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.material.ContainerSelection.Companion.selectContainer -import com.lambda.interaction.material.StackSelection.Companion.select -import com.lambda.interaction.material.container.ContainerManager.containerWithMaterial -import com.lambda.interaction.material.container.MaterialContainer -import com.lambda.interaction.request.rotating.Rotation.Companion.rotation -import com.lambda.interaction.request.rotating.Rotation.Companion.rotationTo -import com.lambda.interaction.request.rotating.RotationManager -import com.lambda.interaction.request.rotating.RotationRequest -import com.lambda.interaction.request.rotating.visibilty.PlaceDirection -import com.lambda.interaction.request.rotating.visibilty.VisibilityChecker.CheckedHit -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.lookAt -import com.lambda.interaction.request.rotating.visibilty.lookInDirection -import com.lambda.util.BlockUtils -import com.lambda.util.BlockUtils.blockState -import com.lambda.util.BlockUtils.hasFluid -import com.lambda.util.BlockUtils.isEmpty -import com.lambda.util.math.distSq -import com.lambda.util.math.vec3d -import com.lambda.util.player.copyPlayer -import com.lambda.util.world.raycast.RayCastUtils.blockResult -import net.minecraft.block.BlockState -import net.minecraft.block.SlabBlock -import net.minecraft.block.pattern.CachedBlockPosition -import net.minecraft.item.BlockItem -import net.minecraft.item.ItemPlacementContext -import net.minecraft.item.ItemUsageContext -import net.minecraft.util.Hand -import net.minecraft.util.hit.BlockHitResult -import net.minecraft.util.math.BlockPos -import net.minecraft.util.math.Vec3d -import net.minecraft.util.shape.VoxelShapes -import kotlin.math.pow - -object PlaceChecks : SimChecker(), Dependable { - override fun SimInfo.asDependent(buildResult: BuildResult) = - PlaceResult.Dependency(pos, buildResult) - - context(automatedSafeContext: AutomatedSafeContext, dependable: Dependable?) - fun SimInfo.checkPlacements( - pos: BlockPos = this.pos, - state: BlockState = this.state, - targetState: TargetState = this.targetState - ): Unit = with(automatedSafeContext) { - checkDependent(dependable) - - val statePromoting = state.block is SlabBlock && - targetState.matches(state, pos, preProcessing.ignore) - // If the target state is air then the only possible blocks it could place are to remove liquids so we use the Solid TargetState - val nextTargetState = if (targetState is TargetState.Air) { - if (state.hasFluid) TargetState.Solid - else return - } else if (targetState.isEmpty()) { - // Otherwise if the target state is empty, there's no situation where placement would be required so we can return - return - } else targetState - // For example, slabs count as state promoting because you are placing another block to promote the current state to the target state - if (!state.isReplaceable && !statePromoting) return - - preProcessing.sides.forEach { neighbor -> - val hitPos = if (!placeConfig.airPlace.isEnabled && (state.isEmpty || statePromoting)) - pos.offset(neighbor) else pos - val hitSide = neighbor.opposite - if (!world.worldBorder.contains(hitPos)) return@forEach - - val voxelShape = blockState(hitPos).getOutlineShape(world, hitPos).let { outlineShape -> - if (!outlineShape.isEmpty || !placeConfig.airPlace.isEnabled) outlineShape - else VoxelShapes.fullCube() - } - if (voxelShape.isEmpty) return@forEach - - val boxes = voxelShape.boundingBoxes.map { it.offset(hitPos) } - val verify: CheckedHit.() -> Boolean = { - hit.blockResult?.blockPos == hitPos && hit.blockResult?.side == hitSide - } - - val validHits = mutableListOf() - val misses = mutableSetOf() - val reachSq = buildConfig.interactReach.pow(2) - - boxes.forEach { box -> - val sides = if (buildConfig.checkSideVisibility) { - box.getVisibleSurfaces(eye).intersect(setOf(hitSide)) - } else setOf(hitSide) - - scanSurfaces(box, sides, buildConfig.resolution, preProcessing.surfaceScan) { _, vec -> - val distSquared = eye distSq vec - if (distSquared > reachSq) { - misses.add(vec) - return@scanSurfaces - } - - val newRotation = eye.rotationTo(vec) - - val hit = if (buildConfig.strictRayCast) { - val rayCast = newRotation.rayCast(buildConfig.interactReach, eye) - when { - rayCast != null && (!placeConfig.airPlace.isEnabled || eye distSq rayCast.pos <= distSquared) -> - rayCast.blockResult - - placeConfig.airPlace.isEnabled -> { - val hitVec = newRotation.castBox(box, buildConfig.interactReach, eye) - BlockHitResult(hitVec, hitSide, hitPos, false) - } - - else -> null - } - } else { - val hitVec = newRotation.castBox(box, buildConfig.interactReach, eye) - BlockHitResult(hitVec, hitSide, hitPos, false) - } ?: return@scanSurfaces - - val checked = CheckedHit(hit, newRotation, buildConfig.interactReach) - if (!checked.verify()) return@scanSurfaces - - validHits.add(checked) - } - } - - if (validHits.isEmpty()) { - if (misses.isNotEmpty()) { - result(GenericResult.OutOfReach(pos, eye, misses)) - return@forEach - } - - result(GenericResult.NotVisible(pos, hitPos, hitSide, eye.distanceTo(hitPos.offset(hitSide).vec3d))) - return@forEach - } - - buildConfig.pointSelection.select(validHits)?.let { checkedHit -> - val optimalStack = nextTargetState.getStack(pos) - - // ToDo: For each hand and sneak or not? - val fakePlayer = copyPlayer(player).apply { - this.rotation = RotationManager.serverRotation - } - - val checkedResult = checkedHit.hit - - // ToDo: Override the stack used for this to account for blocks where replaceability is dependent on the held item - val usageContext = ItemUsageContext( - fakePlayer, - Hand.MAIN_HAND, - checkedResult.blockResult, - ) - val cachePos = CachedBlockPosition( - usageContext.world, usageContext.blockPos, false - ) - val canBePlacedOn = optimalStack.canPlaceOn(cachePos) - if (!player.abilities.allowModifyWorld && !canBePlacedOn) { - result(PlaceResult.IllegalUsage(pos)) - return@forEach - } - - var context = ItemPlacementContext(usageContext) - - if (context.blockPos != pos) { - result(PlaceResult.UnexpectedPosition(pos, context.blockPos)) - return@forEach - } - - if (!optimalStack.item.isEnabled(world.enabledFeatures)) { - result(PlaceResult.BlockFeatureDisabled(pos, optimalStack)) - return@forEach - } - - if (!context.canPlace() && !statePromoting) { - result(PlaceResult.CantReplace(pos, context)) - return@forEach - } - - val blockItem = optimalStack.item as? BlockItem ?: run { - result(PlaceResult.NotItemBlock(pos, optimalStack)) - return@forEach - } - - context = blockItem.getPlacementContext(context) - ?: run { - result(PlaceResult.ScaffoldExceeded(pos, context)) - return@forEach - } - - lateinit var resultState: BlockState - var rot = fakePlayer.rotation - - val simulatePlaceState = placeState@{ - resultState = blockItem.getPlacementState(context) - ?: return@placeState PlaceResult.BlockedByEntity(pos) - - return@placeState if (!nextTargetState.matches(resultState, pos, preProcessing.ignore)) - PlaceResult.NoIntegrity( - pos, - resultState, - context, - (nextTargetState as? TargetState.State)?.blockState - ) - else null - } - - val currentDirIsValid = simulatePlaceState()?.let { basePlaceResult -> - if (!placeConfig.rotateForPlace) { - result(basePlaceResult) - return@forEach - } - false - } != false - - run rotate@{ - if (!placeConfig.axisRotate) { - fakePlayer.rotation = checkedHit.targetRotation - simulatePlaceState()?.let { rotatedPlaceResult -> - result(rotatedPlaceResult) - return@forEach - } - rot = fakePlayer.rotation - return@rotate - } - - fakePlayer.rotation = player.rotation - if (simulatePlaceState() == null) { - rot = fakePlayer.rotation - return@rotate - } - - PlaceDirection.entries.asReversed().forEachIndexed direction@{ index, direction -> - fakePlayer.rotation = direction.rotation - when (val placeResult = simulatePlaceState()) { - is PlaceResult.BlockedByEntity -> { - result(placeResult) - return@forEach - } - - is PlaceResult.NoIntegrity -> { - if (index != PlaceDirection.entries.lastIndex) return@direction - result(placeResult) - return@forEach - } - - else -> { - rot = fakePlayer.rotation - return@rotate - } - } - } - } - - val blockHit = checkedResult.blockResult ?: return@forEach - val hitBlock = blockState(blockHit.blockPos).block - val shouldSneak = hitBlock::class in BlockUtils.interactionBlocks - - val rotationRequest = if (placeConfig.axisRotate) { - lookInDirection(PlaceDirection.fromRotation(rot)) - } else lookAt(rot, 0.001) - - val placeContext = PlaceContext( - blockHit, - RotationRequest(rotationRequest, this), - player.inventory.selectedSlot, - context.blockPos, - blockState(context.blockPos), - resultState, - shouldSneak, - false, - currentDirIsValid, - this - ) - - val selection = optimalStack.item.select() - val containerSelection = selectContainer { ofAnyType(MaterialContainer.Rank.HOTBAR) } - val container = selection.containerWithMaterial(containerSelection).firstOrNull() ?: run { - result( - GenericResult.WrongItemSelection( - pos, - placeContext, - optimalStack.item.select(), - player.mainHandStack - ) - ) - return - } - val stack = selection.filterStacks(container.stacks).run { - firstOrNull { player.inventory.getSlotWithStack(it) == player.inventory.selectedSlot } - ?: first() - } - - placeContext.hotbarIndex = player.inventory.getSlotWithStack(stack) - - result(PlaceResult.Place(pos, placeContext)) - } - } - - return - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingChecks.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingChecker.kt similarity index 84% rename from src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingChecks.kt rename to src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingChecker.kt index 9e470bd02..9ebc53bf8 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingChecks.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingChecker.kt @@ -23,9 +23,10 @@ import com.lambda.interaction.construction.result.BuildResult import com.lambda.interaction.construction.result.Dependable import com.lambda.interaction.construction.result.results.GenericResult import com.lambda.interaction.construction.result.results.InteractResult +import com.lambda.interaction.construction.simulation.ISimInfo import com.lambda.interaction.construction.simulation.SimChecker +import com.lambda.interaction.construction.simulation.SimCheckerDsl import com.lambda.interaction.construction.simulation.SimInfo -import com.lambda.interaction.construction.simulation.checks.PlaceChecks.checkPlacements import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.material.ContainerSelection.Companion.selectContainer import com.lambda.interaction.material.StackSelection.Companion.select @@ -42,7 +43,6 @@ import com.lambda.util.math.vec3d import com.lambda.util.player.SlotUtils.hotbar import com.lambda.util.world.raycast.RayCastUtils.blockResult import net.minecraft.block.BlockState -import net.minecraft.block.enums.SlabType import net.minecraft.item.Item import net.minecraft.state.property.Properties import net.minecraft.util.hit.BlockHitResult @@ -50,17 +50,27 @@ import net.minecraft.util.math.Direction import net.minecraft.util.math.Vec3d import kotlin.math.pow -object PostProcessingChecks : SimChecker(), Dependable { - override fun SimInfo.asDependent(buildResult: BuildResult) = +class PostProcessingChecker @SimCheckerDsl private constructor(simInfo: SimInfo) + : SimChecker(), Dependable, + ISimInfo by simInfo +{ + override fun asDependent(buildResult: BuildResult) = InteractResult.Dependency(pos, buildResult) - context(automatedSafeContext: AutomatedSafeContext, dependable: Dependable?) - fun SimInfo.checkPostProcessing(): Unit = with(automatedSafeContext) { - checkDependent(dependable) + companion object { + @SimCheckerDsl + context(automatedSafeContext: AutomatedSafeContext, dependable: Dependable?) + fun SimInfo.checkPostProcessing() = + PostProcessingChecker(this).run { + checkDependent(dependable) + automatedSafeContext.checkPostProcessing() + } + } - if (targetState !is TargetState.State) return@with + private fun AutomatedSafeContext.checkPostProcessing(): Boolean { + val targetState = (targetState as? TargetState.State) ?: return false - if (!targetState.matches(state, pos, preProcessing.ignore)) return + if (!targetState.matches(state, pos, preProcessing.ignore)) return false val mismatchedProperties = state.properties.filter { state.get(it) != targetState.blockState.get(it) } mismatchedProperties.forEach { property -> @@ -68,18 +78,18 @@ object PostProcessingChecks : SimChecker(), Dependable { Properties.EYE -> { if (state.get(Properties.EYE)) return@forEach val expectedState = state.with(Properties.EYE, true) - simInteraction(expectedState, null, null, false) + simInteraction(expectedState) } Properties.INVERTED -> { val expectedState = state.with(Properties.INVERTED, !state.get(Properties.INVERTED)) - simInteraction(expectedState, null, null, false) + simInteraction(expectedState) } Properties.DELAY -> { val expectedState = state.with(Properties.DELAY, state.cycle(Properties.DELAY).get(Properties.DELAY)) - simInteraction(expectedState, null, null, false) + simInteraction(expectedState) } Properties.COMPARATOR_MODE -> { @@ -87,29 +97,25 @@ object PostProcessingChecks : SimChecker(), Dependable { Properties.COMPARATOR_MODE, state.cycle(Properties.COMPARATOR_MODE).get(Properties.COMPARATOR_MODE) ) - simInteraction(expectedState, null, null, false) + simInteraction(expectedState) } Properties.OPEN -> { val expectedState = state.with(Properties.OPEN, !state.get(Properties.OPEN)) - simInteraction(expectedState, null, null, false) - } - - Properties.SLAB_TYPE -> { - if (targetState.blockState.get(Properties.SLAB_TYPE) != SlabType.DOUBLE) return@forEach - this@PostProcessingChecks.run { checkPlacements() } + simInteraction(expectedState) } } } + + return true } - context(automatedSafeContext: AutomatedSafeContext) - private fun SimInfo.simInteraction( + private fun AutomatedSafeContext.simInteraction( expectedState: BlockState, - sides: Set?, - item: Item?, - placing: Boolean - ): Unit = with(automatedSafeContext) { + sides: Set? = null, + item: Item? = null, + placing: Boolean = false + ) { val boxes = state.getOutlineShape(world, pos).boundingBoxes.map { it.offset(pos) } val validHits = mutableListOf() val blockedHits = mutableSetOf() @@ -185,10 +191,10 @@ object PostProcessingChecks : SimChecker(), Dependable { } buildConfig.pointSelection.select(validHits)?.let { checkedHit -> - val checkedResult = checkedHit.hit + val checkedResult = checkedHit.hit.blockResult ?: return val rotationTarget = lookAt(checkedHit.targetRotation, 0.001) val context = InteractionContext( - checkedResult.blockResult ?: return, + checkedResult, RotationRequest(rotationTarget, this), player.inventory.selectedSlot, state, @@ -207,7 +213,6 @@ object PostProcessingChecks : SimChecker(), Dependable { result( GenericResult.WrongItemSelection( pos, - context, stackSelection, player.mainHandStack ) diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/RequirementChecks.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/RequirementChecks.kt index 830fd92b1..e50a1dee3 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/RequirementChecks.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/RequirementChecks.kt @@ -29,47 +29,49 @@ import net.minecraft.block.OperatorBlock object RequirementChecks : SimChecker() { context(automatedSafeContext: AutomatedSafeContext) - fun SimInfo.checkRequirements(): Unit = with(automatedSafeContext) { + fun SimInfo.checkRequirements(): Boolean = with(automatedSafeContext) { // the chunk is not loaded if (!isLoaded(pos)) { result(PreSimResult.ChunkNotLoaded(pos)) - return + return true } // block is already in the correct state if (targetState.matches(state, pos)) { result(PreSimResult.Done(pos)) - return + return true } // block should be ignored - if (state.block in breakConfig.ignoredBlocks && targetState.type == TargetState.Type.AIR) { + if (state.block in breakConfig.ignoredBlocks && targetState.type == TargetState.Type.Air) { result(GenericResult.Ignored(pos)) - return + return true } // the player is in the wrong game mode to alter the block state if (player.isBlockBreakingRestricted(world, pos, gamemode)) { result(PreSimResult.Restricted(pos)) - return + return true } // the player has no permissions to alter the block state if (state.block is OperatorBlock && !player.isCreativeLevelTwoOp) { result(PreSimResult.NoPermission(pos, state)) - return + return true } // block is outside the world so it cant be altered if (!world.worldBorder.contains(pos) || world.isOutOfHeightLimit(pos)) { result(PreSimResult.OutOfWorld(pos)) - return + return true } // block is unbreakable, so it cant be broken or replaced if (state.getHardness(world, pos) < 0 && !gamemode.isCreative) { result(PreSimResult.Unbreakable(pos, state)) - return + return true } + + return false } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/construction/verify/StateMatcher.kt b/src/main/kotlin/com/lambda/interaction/construction/verify/StateMatcher.kt index 7f385c3a3..6121c6288 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/verify/StateMatcher.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/verify/StateMatcher.kt @@ -34,5 +34,7 @@ interface StateMatcher { context(automatedSafeContext: AutomatedSafeContext) fun getStack(pos: BlockPos): ItemStack + context(automatedSafeContext: AutomatedSafeContext) + fun getState(pos: BlockPos): BlockState fun isEmpty(): Boolean } diff --git a/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt b/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt index 739ca0164..d0e614200 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt @@ -20,11 +20,14 @@ package com.lambda.interaction.construction.verify import com.lambda.context.AutomatedSafeContext import com.lambda.context.SafeContext import com.lambda.interaction.material.container.ContainerManager.findDisposable +import com.lambda.util.BlockUtils.blockState +import com.lambda.util.BlockUtils.emptyState import com.lambda.util.BlockUtils.isEmpty import com.lambda.util.BlockUtils.matches import com.lambda.util.StringUtils.capitalize import com.lambda.util.item.ItemUtils.block import net.minecraft.block.BlockState +import net.minecraft.block.Blocks import net.minecraft.item.ItemStack import net.minecraft.item.Items import net.minecraft.state.property.Property @@ -34,10 +37,10 @@ import net.minecraft.util.math.Direction sealed class TargetState(val type: Type) : StateMatcher { enum class Type { - EMPTY, AIR, SOLID, SUPPORT, STATE, BLOCK, STACK + Empty, Air, Solid, Support, State, Block, Stack } - data object Empty : TargetState(Type.EMPTY) { + data object Empty : TargetState(Type.Empty) { override fun toString() = "Empty" context(safeContext: SafeContext) @@ -47,14 +50,16 @@ sealed class TargetState(val type: Type) : StateMatcher { ignoredProperties: Collection> ) = state.isEmpty + context(_: AutomatedSafeContext) + override fun getStack(pos: BlockPos): ItemStack = ItemStack.EMPTY + context(automatedSafeContext: AutomatedSafeContext) - override fun getStack(pos: BlockPos): ItemStack = - ItemStack.EMPTY + override fun getState(pos: BlockPos) = automatedSafeContext.blockState(pos).emptyState override fun isEmpty() = true } - data object Air : TargetState(Type.AIR) { + data object Air : TargetState(Type.Air) { override fun toString() = "Air" context(safeContext: SafeContext) @@ -64,14 +69,16 @@ sealed class TargetState(val type: Type) : StateMatcher { ignoredProperties: Collection> ) = state.isAir - context(automatedSafeContext: AutomatedSafeContext) - override fun getStack(pos: BlockPos): ItemStack = - ItemStack.EMPTY + context(_: AutomatedSafeContext) + override fun getStack(pos: BlockPos): ItemStack = ItemStack.EMPTY + + context(_: AutomatedSafeContext) + override fun getState(pos: BlockPos): BlockState = Blocks.AIR.defaultState override fun isEmpty() = true } - data object Solid : TargetState(Type.SOLID) { + data object Solid : TargetState(Type.Solid) { override fun toString() = "Solid" context(safeContext: SafeContext) @@ -89,10 +96,13 @@ sealed class TargetState(val type: Type) : StateMatcher { } ?: ItemStack(Items.NETHERRACK) } + context(_: AutomatedSafeContext) + override fun getState(pos: BlockPos): BlockState = getStack(pos).item.block.defaultState + override fun isEmpty() = false } - data class Support(val direction: Direction) : TargetState(Type.SUPPORT) { + data class Support(val direction: Direction) : TargetState(Type.Support) { override fun toString() = "Support for ${direction.name}" context(safeContext: SafeContext) @@ -113,10 +123,13 @@ sealed class TargetState(val type: Type) : StateMatcher { } ?: ItemStack(Items.NETHERRACK) } + context(_: AutomatedSafeContext) + override fun getState(pos: BlockPos): BlockState = getStack(pos).item.block.defaultState + override fun isEmpty() = false } - data class State(val blockState: BlockState) : TargetState(Type.STATE) { + data class State(val blockState: BlockState) : TargetState(Type.State) { override fun toString() = "State of $blockState" context(safeContext: SafeContext) @@ -131,10 +144,13 @@ sealed class TargetState(val type: Type) : StateMatcher { override fun getStack(pos: BlockPos): ItemStack = blockState.block.getPickStack(automatedSafeContext.world, pos, blockState, true) + context(_: AutomatedSafeContext) + override fun getState(pos: BlockPos): BlockState = blockState + override fun isEmpty() = blockState.isEmpty } - data class Block(val block: net.minecraft.block.Block) : TargetState(Type.BLOCK) { + data class Block(val block: net.minecraft.block.Block) : TargetState(Type.Block) { override fun toString() = "Block of ${block.name.string.capitalize()}" context(safeContext: SafeContext) @@ -142,17 +158,19 @@ sealed class TargetState(val type: Type) : StateMatcher { state: BlockState, pos: BlockPos, ignoredProperties: Collection> - ) = - state.block == block + ) = state.block == block context(automatedSafeContext: AutomatedSafeContext) override fun getStack(pos: BlockPos): ItemStack = block.getPickStack(automatedSafeContext.world, pos, block.defaultState, true) + context(_: AutomatedSafeContext) + override fun getState(pos: BlockPos): BlockState = block.defaultState + override fun isEmpty() = block.defaultState.isEmpty } - data class Stack(val itemStack: ItemStack) : TargetState(Type.STACK) { + data class Stack(val itemStack: ItemStack) : TargetState(Type.Stack) { private val startStack: ItemStack = itemStack.copy() override fun toString() = "Stack of ${startStack.item.name.string.capitalize()}" @@ -163,13 +181,15 @@ sealed class TargetState(val type: Type) : StateMatcher { state: BlockState, pos: BlockPos, ignoredProperties: Collection> - ) = - state.block == block + ) = state.block == block context(automatedSafeContext: AutomatedSafeContext) override fun getStack(pos: BlockPos): ItemStack = itemStack + context(_: AutomatedSafeContext) + override fun getState(pos: BlockPos): BlockState = block.defaultState + override fun isEmpty() = false } } diff --git a/src/main/kotlin/com/lambda/interaction/material/StackSelection.kt b/src/main/kotlin/com/lambda/interaction/material/StackSelection.kt index a1a5e7c9b..3090dc00e 100644 --- a/src/main/kotlin/com/lambda/interaction/material/StackSelection.kt +++ b/src/main/kotlin/com/lambda/interaction/material/StackSelection.kt @@ -27,7 +27,6 @@ import net.minecraft.component.DataComponentTypes import net.minecraft.enchantment.Enchantment import net.minecraft.item.Item import net.minecraft.item.ItemStack -import net.minecraft.item.ToolMaterial import net.minecraft.item.consume.UseAction import net.minecraft.registry.RegistryKey import net.minecraft.registry.tag.TagKey @@ -230,27 +229,24 @@ class StackSelection { * Returns the negation of the original predicate. * @return A new predicate that matches if the original predicate does not match. */ - fun ((ItemStack) -> Boolean).not(): (ItemStack) -> Boolean { - return { !this(it) } - } + fun ((ItemStack) -> Boolean).not(): (ItemStack) -> Boolean = { !this(it) } /** * Combines two predicates using the logical AND operator. * @param otherPredicate The second predicate. * @return A new predicate that matches if both inputs predicate match. */ - infix fun ((ItemStack) -> Boolean).and(otherPredicate: (ItemStack) -> Boolean): (ItemStack) -> Boolean { - return { this(it) && otherPredicate(it) } - } + infix fun ((ItemStack) -> Boolean).and(otherPredicate: (ItemStack) -> Boolean): (ItemStack) -> Boolean = { this(it) && otherPredicate(it) } /** * Combines two predicates using the logical OR operator. * @param otherPredicate The second predicate. * @return A new predicate that matches if either input predicate matches. */ - infix fun ((ItemStack) -> Boolean).or(otherPredicate: (ItemStack) -> Boolean): (ItemStack) -> Boolean { - return { this(it) || otherPredicate(it) } - } + infix fun ((ItemStack) -> Boolean).or(otherPredicate: (ItemStack) -> Boolean): (ItemStack) -> Boolean = { this(it) || otherPredicate(it) } + + fun ((ItemStack) -> Boolean).andIf(predicate: Boolean, otherPredicate: () -> (ItemStack) -> Boolean): (ItemStack) -> Boolean = + if (predicate) { { this(it) && otherPredicate()(it) } } else this override fun toString() = buildString { append("selection of ${count}x ") diff --git a/src/main/kotlin/com/lambda/interaction/request/breaking/BreakConfig.kt b/src/main/kotlin/com/lambda/interaction/request/breaking/BreakConfig.kt index 7ba6875d3..454a3b655 100644 --- a/src/main/kotlin/com/lambda/interaction/request/breaking/BreakConfig.kt +++ b/src/main/kotlin/com/lambda/interaction/request/breaking/BreakConfig.kt @@ -55,7 +55,6 @@ interface BreakConfig : RequestConfig { val avoidLiquids: Boolean val avoidSupporting: Boolean - val breakWeakBlocks: Boolean val ignoredBlocks: Set val suitableToolsOnly: Boolean diff --git a/src/main/kotlin/com/lambda/interaction/request/rotating/Rotation.kt b/src/main/kotlin/com/lambda/interaction/request/rotating/Rotation.kt index 7d17bbe20..d10326ba7 100644 --- a/src/main/kotlin/com/lambda/interaction/request/rotating/Rotation.kt +++ b/src/main/kotlin/com/lambda/interaction/request/rotating/Rotation.kt @@ -17,11 +17,9 @@ package com.lambda.interaction.request.rotating -import com.lambda.Lambda.mc import com.lambda.threading.runSafe import com.lambda.util.math.MathUtils.toDegree import com.lambda.util.math.MathUtils.toRadian -import com.lambda.util.math.Vec2d import com.lambda.util.math.plus import com.lambda.util.math.times import com.lambda.util.world.raycast.InteractionMask diff --git a/src/main/kotlin/com/lambda/interaction/request/rotating/visibilty/VisibilityChecker.kt b/src/main/kotlin/com/lambda/interaction/request/rotating/visibilty/VisibilityChecker.kt index 31aad1e65..1dbbea5f4 100644 --- a/src/main/kotlin/com/lambda/interaction/request/rotating/visibilty/VisibilityChecker.kt +++ b/src/main/kotlin/com/lambda/interaction/request/rotating/visibilty/VisibilityChecker.kt @@ -165,10 +165,13 @@ object VisibilityChecker { sides: Set = emptySet(), resolution: Int = 5, scan: SurfaceScan = SurfaceScan.DEFAULT, - check: (Direction, Vec3d) -> Unit, + check: (Direction, Vec3d) -> Unit ) { sides.forEach { side -> - val (minX, minY, minZ, maxX, maxY, maxZ) = box.contract(AutomationConfig.shrinkFactor).bounds(side) + val (minX, minY, minZ, maxX, maxY, maxZ) = box + .contract(AutomationConfig.shrinkFactor) + .offset(side.doubleVector.multiply(AutomationConfig.shrinkFactor)) + .bounds(side) // Determine the bounds to scan based on the axis and mode. Skip if no part of the face is in the desired bounds val (startX, endX) = if (scan.axis == Direction.Axis.X && maxX != minX) { diff --git a/src/main/kotlin/com/lambda/module/modules/player/FastBreak.kt b/src/main/kotlin/com/lambda/module/modules/player/FastBreak.kt index 2b1cd3998..7eb709d70 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/FastBreak.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/FastBreak.kt @@ -71,14 +71,12 @@ object FastBreak : Module( ::doubleBreak ) { defaultValue(false) } ::breaksPerTick.edit { defaultValue(1) } - ::breakWeakBlocks.edit { defaultValue(true) } hide( ::sorter, ::doubleBreak, ::unsafeCancels, ::rotateForBreak, ::breaksPerTick, - ::breakWeakBlocks ) } override val inventoryConfig = InventorySettings(this, Group.Inventory).apply { @@ -174,7 +172,6 @@ object FastBreak : Module( breakConfig.breakThreshold ), state, - breakConfig.sorter, this@FastBreak ) diff --git a/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt b/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt index 5acfb7201..8baa5fc49 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt @@ -80,7 +80,6 @@ object PacketMine : Module( override val breakConfig = BreakSettings(this, Group.Break).apply { editTyped(::avoidLiquids, ::avoidSupporting, ::suitableToolsOnly) { defaultValue(false) } - ::breakWeakBlocks.edit { defaultValue(true) } ::swing.edit { defaultValue(BreakConfig.SwingMode.Start) } ::rebreak.insert(::rebreakMode, SettingGroup.InsertMode.Below) diff --git a/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt b/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt index 5d4bb80ee..4319e3887 100644 --- a/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt @@ -35,7 +35,6 @@ import com.lambda.interaction.construction.context.BuildContext import com.lambda.interaction.construction.result.BuildResult import com.lambda.interaction.construction.result.Contextual import com.lambda.interaction.construction.result.Dependent -import com.lambda.interaction.construction.result.Dependent.Companion.iterator import com.lambda.interaction.construction.result.Drawable import com.lambda.interaction.construction.result.Navigable import com.lambda.interaction.construction.result.Resolvable @@ -171,6 +170,7 @@ class BuildTask private constructor( when (result) { is BreakResult.Break -> { val breakResults = allResults + .map { if (it is Dependent) it.lastDependency else it } .filterIsInstance() .map { it.context } @@ -184,6 +184,7 @@ class BuildTask private constructor( } is PlaceResult.Place -> { val placeResults = allResults + .map { if (it is Dependent) it.lastDependency else it } .filterIsInstance() .map { it.context } @@ -195,6 +196,7 @@ class BuildTask private constructor( } is InteractResult.Interact -> { val interactResults = allResults + .map { if (it is Dependent) it.lastDependency else it } .filterIsInstance() .map { it.context } @@ -208,7 +210,7 @@ class BuildTask private constructor( } } - is Dependent -> handleResult(result.iterator.last(), allResults) + is Dependent -> handleResult(result.lastDependency, allResults) is Resolvable -> { LOG.info("Resolving: ${result.name}") diff --git a/src/main/kotlin/com/lambda/task/tasks/PlaceContainer.kt b/src/main/kotlin/com/lambda/task/tasks/PlaceContainer.kt index 99b8f1149..9e7aabc49 100644 --- a/src/main/kotlin/com/lambda/task/tasks/PlaceContainer.kt +++ b/src/main/kotlin/com/lambda/task/tasks/PlaceContainer.kt @@ -61,9 +61,8 @@ class PlaceContainer @Ta5kBuilder constructor( val options = results.filterIsInstance().filter { canBeOpened(startStack, it.pos, it.context.hitResult.side) - } + results.filterIsInstance().filter { - canBeOpened(startStack, it.pos, it.context.hitResult.side) - } + } + results.filterIsInstance() + val containerPosition = options.filter { // ToDo: Check based on if we can move the player close enough rather than y level once the custom pathfinder is merged it.pos.y == player.blockPos.y diff --git a/src/main/kotlin/com/lambda/util/item/ItemStackUtils.kt b/src/main/kotlin/com/lambda/util/item/ItemStackUtils.kt index b060b3aa0..91aa5963a 100644 --- a/src/main/kotlin/com/lambda/util/item/ItemStackUtils.kt +++ b/src/main/kotlin/com/lambda/util/item/ItemStackUtils.kt @@ -17,8 +17,8 @@ package com.lambda.util.item +import com.lambda.context.SafeContext import com.lambda.util.collections.Cacheable.Companion.cacheable -import net.minecraft.component.ComponentType import net.minecraft.component.DataComponentTypes import net.minecraft.component.type.AttributeModifiersComponent import net.minecraft.entity.EquipmentSlot @@ -79,6 +79,9 @@ object ItemStackUtils { val List.count: Int get() = sumOf { it.count } val List.copy: List get() = map { it.copy() } + context(safeContext: SafeContext) + val ItemStack.inventoryIndex get() = safeContext.player.inventory.getSlotWithStack(this) + val List.compressed: List get() = fold(mutableListOf()) { acc, itemStack -> @@ -86,11 +89,7 @@ object ItemStackUtils { acc } - infix fun List.merge(other: ItemStack): List { - return flatMap { - it merge other - } - } + infix fun List.merge(other: ItemStack): List = flatMap { it merge other } infix fun ItemStack.merge(other: ItemStack): List { if (!isStackable || !other.isStackable) { diff --git a/src/main/kotlin/com/lambda/util/item/ItemUtils.kt b/src/main/kotlin/com/lambda/util/item/ItemUtils.kt index 2364191f4..c91b3776a 100644 --- a/src/main/kotlin/com/lambda/util/item/ItemUtils.kt +++ b/src/main/kotlin/com/lambda/util/item/ItemUtils.kt @@ -20,7 +20,9 @@ package com.lambda.util.item import net.minecraft.block.Block import net.minecraft.block.Blocks import net.minecraft.component.DataComponentTypes +import net.minecraft.item.BlockItem import net.minecraft.item.Item +import net.minecraft.item.ItemStack import net.minecraft.item.Items object ItemUtils { @@ -121,6 +123,7 @@ object ItemUtils { ) val Item.block: Block get() = Block.getBlockFromItem(this) + val ItemStack.blockItem: BlockItem get() = (item as? BlockItem ?: Items.AIR) as BlockItem val Item.nutrition: Int get() = components.get(DataComponentTypes.FOOD)?.nutrition ?: 0 From 5cf47e02ceb0d855a9282115637fd8496cbceec3 Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Tue, 21 Oct 2025 16:40:39 +0100 Subject: [PATCH 04/30] assign item usage context the swap stack --- .../interaction/construction/simulation/checks/PlaceChecker.kt | 2 ++ src/main/kotlin/com/lambda/util/player/SlotUtils.kt | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt index 4cb4afea5..a18549d14 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt @@ -221,8 +221,10 @@ class PlaceChecker @SimCheckerDsl private constructor(simInfo: SimInfo) // ToDo: Override the stack used for this to account for blocks where replaceability is dependent on the held item val usageContext = ItemUsageContext( + world, fakePlayer, Hand.MAIN_HAND, + swapStack, blockHit, ) val cachePos = CachedBlockPosition( diff --git a/src/main/kotlin/com/lambda/util/player/SlotUtils.kt b/src/main/kotlin/com/lambda/util/player/SlotUtils.kt index 9d56a4650..70f3753be 100644 --- a/src/main/kotlin/com/lambda/util/player/SlotUtils.kt +++ b/src/main/kotlin/com/lambda/util/player/SlotUtils.kt @@ -29,7 +29,6 @@ object SlotUtils { val ClientPlayerEntity.hotbarAndStorage: List get() = inventory.mainStacks val ClientPlayerEntity.combined: List get() = hotbarAndStorage + equipment - fun SafeContext.clickSlot( slotId: Int, button: Int, From 5424da05ef872ac9aa8a51caf5e6046d677ffd3a Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Wed, 22 Oct 2025 01:20:01 +0100 Subject: [PATCH 05/30] check both current block and neighboring blocks to ensure potential replacement places are scanned. Slabs for example are only replaceable under a more specific context than a simple isReplaceable check --- .../simulation/checks/PlaceChecker.kt | 154 +++++++++--------- .../checks/PostProcessingChecker.kt | 2 + .../rotating/visibilty/PointSelection.kt | 2 +- 3 files changed, 84 insertions(+), 74 deletions(-) diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt index a18549d14..e5c5b5a49 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt @@ -46,13 +46,13 @@ import com.lambda.interaction.request.rotating.visibilty.lookInDirection import com.lambda.threading.runSafeAutomated import com.lambda.util.BlockUtils import com.lambda.util.BlockUtils.blockState -import com.lambda.util.BlockUtils.isEmpty import com.lambda.util.item.ItemStackUtils.inventoryIndex import com.lambda.util.item.ItemUtils.blockItem import com.lambda.util.math.distSq import com.lambda.util.math.vec3d import com.lambda.util.player.copyPlayer import com.lambda.util.world.raycast.RayCastUtils.blockResult +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.cancel import kotlinx.coroutines.joinAll @@ -69,6 +69,7 @@ import net.minecraft.item.Items import net.minecraft.util.Hand import net.minecraft.util.hit.BlockHitResult import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Direction import net.minecraft.util.math.Vec3d import net.minecraft.util.shape.VoxelShapes import kotlin.math.pow @@ -123,91 +124,98 @@ class PlaceChecker @SimCheckerDsl private constructor(simInfo: SimInfo) supervisorScope { withContext(Dispatchers.Default) { - preProcessing.sides.map { neighbor -> + preProcessing.sides.map { side -> launch { - val hitBlockPos = if (!placeConfig.airPlace.isEnabled && state.isEmpty) - pos.offset(neighbor) else pos - val hitSide = neighbor.opposite - if (!world.worldBorder.contains(hitBlockPos)) return@launch - - val voxelShape = blockState(hitBlockPos).getOutlineShape(world, hitBlockPos).let { outlineShape -> - if (!outlineShape.isEmpty || !placeConfig.airPlace.isEnabled) outlineShape - else VoxelShapes.fullCube() - } - if (voxelShape.isEmpty) return@launch + val neighborPos = pos.offset(side) + val neighborSide = side.opposite + if (!placeConfig.airPlace.isEnabled) + testBlock(neighborPos, neighborSide, this@supervisorScope) + testBlock(pos, side, this@supervisorScope) + } + }.joinAll() + } + } + + return true + } + + private suspend fun AutomatedSafeContext.testBlock(pos: BlockPos, side: Direction, supervisorScope: CoroutineScope) { + if (!world.worldBorder.contains(pos)) return + + val voxelShape = blockState(pos).getOutlineShape(world, pos).let { outlineShape -> + if (!outlineShape.isEmpty || !placeConfig.airPlace.isEnabled) outlineShape + else VoxelShapes.fullCube() + } + if (voxelShape.isEmpty) return + + val boxes = voxelShape.boundingBoxes.map { it.offset(pos) } + val verify: CheckedHit.() -> Boolean = { + hit.blockResult?.blockPos == pos && hit.blockResult?.side == side + } - val boxes = voxelShape.boundingBoxes.map { it.offset(hitBlockPos) } - val verify: CheckedHit.() -> Boolean = { - hit.blockResult?.blockPos == hitBlockPos && hit.blockResult?.side == hitSide + val validHits = mutableListOf() + val misses = mutableSetOf() + val reachSq = buildConfig.interactReach.pow(2) + + withContext(Dispatchers.Default) { + boxes.map { box -> + launch { + val sides = if (buildConfig.checkSideVisibility) { + box.getVisibleSurfaces(eye).intersect(setOf(side)) + } else setOf(side) + + scanSurfaces(box, sides, buildConfig.resolution, preProcessing.surfaceScan) { _, vec -> + val distSquared = eye distSq vec + if (distSquared > reachSq) { + misses.add(vec) + return@scanSurfaces } - val validHits = mutableListOf() - val misses = mutableSetOf() - val reachSq = buildConfig.interactReach.pow(2) - - boxes.map { box -> - launch { - val sides = if (buildConfig.checkSideVisibility) { - box.getVisibleSurfaces(eye).intersect(setOf(hitSide)) - } else setOf(hitSide) - - scanSurfaces(box, sides, buildConfig.resolution, preProcessing.surfaceScan) { _, vec -> - val distSquared = eye distSq vec - if (distSquared > reachSq) { - misses.add(vec) - return@scanSurfaces - } - - val newRotation = eye.rotationTo(vec) - - val hit = if (buildConfig.strictRayCast) { - val rayCast = newRotation.rayCast(buildConfig.interactReach, eye) - when { - rayCast != null && (!placeConfig.airPlace.isEnabled || eye distSq rayCast.pos <= distSquared) -> - rayCast.blockResult - - placeConfig.airPlace.isEnabled -> { - val hitVec = newRotation.castBox(box, buildConfig.interactReach, eye) - BlockHitResult(hitVec, hitSide, hitBlockPos, false) - } - - else -> null - } - } else { - val hitVec = newRotation.castBox(box, buildConfig.interactReach, eye) - BlockHitResult(hitVec, hitSide, hitBlockPos, false) - } ?: return@scanSurfaces - - val checked = CheckedHit(hit, newRotation, buildConfig.interactReach) - if (!checked.verify()) return@scanSurfaces - - validHits.add(checked) + val newRotation = eye.rotationTo(vec) + + val hit = if (buildConfig.strictRayCast) { + val rayCast = newRotation.rayCast(buildConfig.interactReach, eye) + when { + rayCast != null && (!placeConfig.airPlace.isEnabled || eye distSq rayCast.pos <= distSquared) -> + rayCast.blockResult + + placeConfig.airPlace.isEnabled -> { + val hitVec = newRotation.castBox(box, buildConfig.interactReach, eye) + BlockHitResult(hitVec, side, pos, false) } - } - }.joinAll() - if (validHits.isEmpty()) { - if (misses.isNotEmpty()) { - result(GenericResult.OutOfReach(pos, eye, misses)) - return@launch + else -> null } + } else { + val hitVec = newRotation.castBox(box, buildConfig.interactReach, eye) + BlockHitResult(hitVec, side, pos, false) + } ?: return@scanSurfaces - result(GenericResult.NotVisible(pos, hitBlockPos, hitSide, eye.distanceTo(hitBlockPos.offset(hitSide).vec3d))) - return@launch - } + val checked = CheckedHit(hit, newRotation, buildConfig.interactReach) + if (!checked.verify()) return@scanSurfaces - if (swapStack.item == Items.AIR) - this@supervisorScope.cancel() - else if (!swapStack.item.isEnabled(world.enabledFeatures)) { - result(PlaceResult.BlockFeatureDisabled(pos, swapStack)) - this@supervisorScope.cancel() - } else selectHitPos(validHits) + validHits.add(checked) } - }.joinAll() + } + }.joinAll() + } + + if (validHits.isEmpty()) { + if (misses.isNotEmpty()) { + result(GenericResult.OutOfReach(pos, eye, misses)) + return } + + result(GenericResult.NotVisible(pos, pos, side, eye.distanceTo(pos.offset(side).vec3d))) + return } - return true + if (swapStack.item == Items.AIR) + supervisorScope.cancel() + else if (!swapStack.item.isEnabled(world.enabledFeatures)) { + result(PlaceResult.BlockFeatureDisabled(pos, swapStack)) + supervisorScope.cancel() + } else selectHitPos(validHits) } private fun AutomatedSafeContext.selectHitPos(validHits: List) { diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingChecker.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingChecker.kt index 9ebc53bf8..26097003e 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingChecker.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingChecker.kt @@ -104,6 +104,8 @@ class PostProcessingChecker @SimCheckerDsl private constructor(simInfo: SimInfo) val expectedState = state.with(Properties.OPEN, !state.get(Properties.OPEN)) simInteraction(expectedState) } + + Properties.SLAB_TYPE -> return false } } diff --git a/src/main/kotlin/com/lambda/interaction/request/rotating/visibilty/PointSelection.kt b/src/main/kotlin/com/lambda/interaction/request/rotating/visibilty/PointSelection.kt index 6395d12ad..592c34645 100644 --- a/src/main/kotlin/com/lambda/interaction/request/rotating/visibilty/PointSelection.kt +++ b/src/main/kotlin/com/lambda/interaction/request/rotating/visibilty/PointSelection.kt @@ -41,7 +41,7 @@ enum class PointSelection( "Choose the point closest to the average of all candidates (balanced and stable aim).", select = { hits -> val optimum = hits - .mapNotNull { it.hit.pos } + .mapNotNull { it?.hit?.pos } .reduceOrNull { acc, pos -> acc.add(pos) } ?.times(1 / hits.size.toDouble()) From cd3dbe588906ddd555598f1d58ae5d9827743dc3 Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Wed, 22 Oct 2025 01:49:34 +0100 Subject: [PATCH 06/30] inventoryIndexOrSelected over inventoryIndex within the break manager in order to avoid getSlotWithStack causing problems --- .../construction/simulation/checks/BreakChecker.kt | 6 +++--- src/main/kotlin/com/lambda/util/item/ItemStackUtils.kt | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecker.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecker.kt index 9fe4ebe5a..11662b6d7 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecker.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecker.kt @@ -50,7 +50,7 @@ import com.lambda.threading.runSafe import com.lambda.util.BlockUtils.blockState import com.lambda.util.BlockUtils.calcItemBlockBreakingDelta import com.lambda.util.BlockUtils.instantBreakable -import com.lambda.util.item.ItemStackUtils.inventoryIndex +import com.lambda.util.item.ItemStackUtils.inventoryIndexOrSelected import com.lambda.util.math.distSq import com.lambda.util.world.raycast.RayCastUtils.blockResult import io.ktor.util.collections.* @@ -169,7 +169,7 @@ class BreakChecker @SimCheckerDsl private constructor(simInfo: SimInfo) val breakContext = BreakContext( blockHit, rotationRequest, - swapStack.inventoryIndex, + swapStack.inventoryIndexOrSelected, stackSelection, instant, state, @@ -228,7 +228,7 @@ class BreakChecker @SimCheckerDsl private constructor(simInfo: SimInfo) val breakContext = BreakContext( blockHit, rotationRequest, - swapStack.inventoryIndex, + swapStack.inventoryIndexOrSelected, stackSelection, instant, state, diff --git a/src/main/kotlin/com/lambda/util/item/ItemStackUtils.kt b/src/main/kotlin/com/lambda/util/item/ItemStackUtils.kt index 91aa5963a..e67ec2c48 100644 --- a/src/main/kotlin/com/lambda/util/item/ItemStackUtils.kt +++ b/src/main/kotlin/com/lambda/util/item/ItemStackUtils.kt @@ -81,6 +81,12 @@ object ItemStackUtils { context(safeContext: SafeContext) val ItemStack.inventoryIndex get() = safeContext.player.inventory.getSlotWithStack(this) + context(safeContext: SafeContext) + val ItemStack.inventoryIndexOrSelected get() = with(safeContext) { + player.inventory.getSlotWithStack(this@inventoryIndexOrSelected).let { + if (it == -1) player.inventory.selectedSlot else it + } + } val List.compressed: List get() = From eee5af42d8d8b02ba1eb03b3847bc8f8751f5805 Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Thu, 23 Oct 2025 11:40:22 +0100 Subject: [PATCH 07/30] modify fake player to sneak and therefore adjust eye position accordingly if sneaking is required for placement --- .../com/lambda/config/groups/BuildSettings.kt | 2 +- .../simulation/checks/PlaceChecker.kt | 95 ++++++++----------- .../rotating/visibilty/PointSelection.kt | 2 +- src/main/resources/lambda.accesswidener | 1 + 4 files changed, 41 insertions(+), 59 deletions(-) diff --git a/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt b/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt index 4205fe605..b8f4397aa 100644 --- a/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt +++ b/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt @@ -47,8 +47,8 @@ class BuildSettings( override val scanReach: Double get() = max(attackReach, interactReach) - override val strictRayCast by c.setting("Strict Raycast", false, "Whether to include the environment to the ray cast context", vis).group(*baseGroup, Group.Scan) override val checkSideVisibility by c.setting("Visibility Check", true, "Whether to check if an AABB side is visible", vis).group(*baseGroup, Group.Scan) + override val strictRayCast by c.setting("Strict Raycast", false, "Whether to include the environment to the ray cast context", vis).group(*baseGroup, Group.Scan) override val resolution by c.setting("Resolution", 5, 1..20, 1, "The amount of grid divisions per surface of the hit box", "", vis).group(*baseGroup, Group.Scan) override val pointSelection by c.setting("Point Selection", PointSelection.Optimum, "The strategy to select the best hit point", vis).group(*baseGroup, Group.Scan) diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt index e5c5b5a49..20a5748d6 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt @@ -50,6 +50,7 @@ import com.lambda.util.item.ItemStackUtils.inventoryIndex import com.lambda.util.item.ItemUtils.blockItem import com.lambda.util.math.distSq import com.lambda.util.math.vec3d +import com.lambda.util.player.MovementUtils.sneaking import com.lambda.util.player.copyPlayer import com.lambda.util.world.raycast.RayCastUtils.blockResult import kotlinx.coroutines.CoroutineScope @@ -64,7 +65,6 @@ import net.minecraft.block.pattern.CachedBlockPosition import net.minecraft.client.network.ClientPlayerEntity import net.minecraft.item.ItemPlacementContext import net.minecraft.item.ItemStack -import net.minecraft.item.ItemUsageContext import net.minecraft.item.Items import net.minecraft.util.Hand import net.minecraft.util.hit.BlockHitResult @@ -142,25 +142,34 @@ class PlaceChecker @SimCheckerDsl private constructor(simInfo: SimInfo) private suspend fun AutomatedSafeContext.testBlock(pos: BlockPos, side: Direction, supervisorScope: CoroutineScope) { if (!world.worldBorder.contains(pos)) return - val voxelShape = blockState(pos).getOutlineShape(world, pos).let { outlineShape -> + val testBlockState = blockState(pos) + val voxelShape = testBlockState.getOutlineShape(world, pos).let { outlineShape -> if (!outlineShape.isEmpty || !placeConfig.airPlace.isEnabled) outlineShape else VoxelShapes.fullCube() } if (voxelShape.isEmpty) return val boxes = voxelShape.boundingBoxes.map { it.offset(pos) } - val verify: CheckedHit.() -> Boolean = { - hit.blockResult?.blockPos == pos && hit.blockResult?.side == side - } val validHits = mutableListOf() val misses = mutableSetOf() val reachSq = buildConfig.interactReach.pow(2) + // ToDo: For each hand and sneak or not? + val fakePlayer = copyPlayer(player).apply { + this.rotation = RotationManager.serverRotation + if (testBlockState.block::class in BlockUtils.interactionBlocks) { + input.sneaking = true + updatePose() + } + } + + val eye = fakePlayer.eyePos + withContext(Dispatchers.Default) { boxes.map { box -> launch { - val sides = if (buildConfig.checkSideVisibility) { + val sides = if (buildConfig.checkSideVisibility || buildConfig.strictRayCast) { box.getVisibleSurfaces(eye).intersect(setOf(side)) } else setOf(side) @@ -174,25 +183,14 @@ class PlaceChecker @SimCheckerDsl private constructor(simInfo: SimInfo) val newRotation = eye.rotationTo(vec) val hit = if (buildConfig.strictRayCast) { - val rayCast = newRotation.rayCast(buildConfig.interactReach, eye) - when { - rayCast != null && (!placeConfig.airPlace.isEnabled || eye distSq rayCast.pos <= distSquared) -> - rayCast.blockResult - - placeConfig.airPlace.isEnabled -> { - val hitVec = newRotation.castBox(box, buildConfig.interactReach, eye) - BlockHitResult(hitVec, side, pos, false) - } - - else -> null - } + newRotation.rayCast(buildConfig.interactReach, eye)?.blockResult ?: return@scanSurfaces } else { - val hitVec = newRotation.castBox(box, buildConfig.interactReach, eye) + val hitVec = newRotation.castBox(box, buildConfig.interactReach, eye) ?: return@scanSurfaces BlockHitResult(hitVec, side, pos, false) - } ?: return@scanSurfaces + } + if (hit.blockPos != pos || hit.side != side) return@scanSurfaces val checked = CheckedHit(hit, newRotation, buildConfig.interactReach) - if (!checked.verify()) return@scanSurfaces validHits.add(checked) } @@ -215,39 +213,29 @@ class PlaceChecker @SimCheckerDsl private constructor(simInfo: SimInfo) else if (!swapStack.item.isEnabled(world.enabledFeatures)) { result(PlaceResult.BlockFeatureDisabled(pos, swapStack)) supervisorScope.cancel() - } else selectHitPos(validHits) + } else selectHitPos(validHits, fakePlayer) } - private fun AutomatedSafeContext.selectHitPos(validHits: List) { + private fun AutomatedSafeContext.selectHitPos(validHits: List, fakePlayer: ClientPlayerEntity) { buildConfig.pointSelection.select(validHits)?.let { checkedHit -> - // ToDo: For each hand and sneak or not? - val fakePlayer = copyPlayer(player).apply { - this.rotation = RotationManager.serverRotation - } - - val blockHit = checkedHit.hit.blockResult ?: return + val hitResult = checkedHit.hit.blockResult ?: return - // ToDo: Override the stack used for this to account for blocks where replaceability is dependent on the held item - val usageContext = ItemUsageContext( + var context = ItemPlacementContext( world, fakePlayer, Hand.MAIN_HAND, swapStack, - blockHit, - ) - val cachePos = CachedBlockPosition( - usageContext.world, usageContext.blockPos, false + hitResult, ) - if (!player.abilities.allowModifyWorld && !swapStack.canPlaceOn(cachePos)) { - result(PlaceResult.IllegalUsage(pos)) + if (context.blockPos != pos) { + result(PlaceResult.UnexpectedPosition(pos, context.blockPos)) return } - var context = ItemPlacementContext(usageContext) - - if (context.blockPos != pos) { - result(PlaceResult.UnexpectedPosition(pos, context.blockPos)) + val cachePos = CachedBlockPosition(context.world, context.blockPos, false) + if (!player.abilities.allowModifyWorld && !swapStack.canPlaceOn(cachePos)) { + result(PlaceResult.IllegalUsage(pos)) return } @@ -263,21 +251,18 @@ class PlaceChecker @SimCheckerDsl private constructor(simInfo: SimInfo) if (!simRotation(fakePlayer, checkedHit, context)) return - val hitBlock = blockState(blockHit.blockPos).block - val shouldSneak = hitBlock::class in BlockUtils.interactionBlocks - val rotationRequest = if (placeConfig.axisRotate) { lookInDirection(PlaceDirection.fromRotation(rot)) } else lookAt(rot, 0.001) val placeContext = PlaceContext( - blockHit, + hitResult, RotationRequest(rotationRequest, this@PlaceChecker), swapStack.inventoryIndex, - context.blockPos, - blockState(context.blockPos), + pos, + state, resultState, - shouldSneak, + fakePlayer.isSneaking, false, currentDirIsValid, this@PlaceChecker @@ -294,27 +279,27 @@ class PlaceChecker @SimCheckerDsl private constructor(simInfo: SimInfo) checkedHit: CheckedHit, context: ItemPlacementContext ): Boolean { - currentDirIsValid = if (testPlaceState(pos, targetState, context) != PlaceTestResult.Success) { + currentDirIsValid = if (testPlaceState(context) != PlaceTestResult.Success) { if (!placeConfig.rotateForPlace) return false else false } else true if (!placeConfig.axisRotate) { fakePlayer.rotation = checkedHit.targetRotation - if (testPlaceState(pos, targetState, context) != PlaceTestResult.Success) return false + if (testPlaceState(context) != PlaceTestResult.Success) return false rot = fakePlayer.rotation return true } fakePlayer.rotation = player.rotation - if (testPlaceState(pos, targetState, context) == PlaceTestResult.Success) { + if (testPlaceState(context) == PlaceTestResult.Success) { rot = fakePlayer.rotation return true } PlaceDirection.entries.asReversed().forEachIndexed direction@{ index, direction -> fakePlayer.rotation = direction.rotation - when (testPlaceState(pos, targetState, context)) { + when (testPlaceState(context)) { PlaceTestResult.BlockedByEntity -> return@direction PlaceTestResult.NoIntegrity -> { @@ -332,11 +317,7 @@ class PlaceChecker @SimCheckerDsl private constructor(simInfo: SimInfo) return true } - private fun SafeContext.testPlaceState( - pos: BlockPos, - targetState: TargetState, - context: ItemPlacementContext - ): PlaceTestResult { + private fun SafeContext.testPlaceState(context: ItemPlacementContext): PlaceTestResult { resultState = blockItem.getPlacementState(context) ?: run { result(PlaceResult.BlockedByEntity(pos)) return PlaceTestResult.BlockedByEntity diff --git a/src/main/kotlin/com/lambda/interaction/request/rotating/visibilty/PointSelection.kt b/src/main/kotlin/com/lambda/interaction/request/rotating/visibilty/PointSelection.kt index 592c34645..6395d12ad 100644 --- a/src/main/kotlin/com/lambda/interaction/request/rotating/visibilty/PointSelection.kt +++ b/src/main/kotlin/com/lambda/interaction/request/rotating/visibilty/PointSelection.kt @@ -41,7 +41,7 @@ enum class PointSelection( "Choose the point closest to the average of all candidates (balanced and stable aim).", select = { hits -> val optimum = hits - .mapNotNull { it?.hit?.pos } + .mapNotNull { it.hit.pos } .reduceOrNull { acc, pos -> acc.add(pos) } ?.times(1 / hits.size.toDouble()) diff --git a/src/main/resources/lambda.accesswidener b/src/main/resources/lambda.accesswidener index 0cc97047a..b62668c1e 100644 --- a/src/main/resources/lambda.accesswidener +++ b/src/main/resources/lambda.accesswidener @@ -48,6 +48,7 @@ transitive-accessible method net/minecraft/entity/LivingEntity getHandSwingDurat transitive-accessible method net/minecraft/client/network/ClientPlayerInteractionManager syncSelectedSlot ()V transitive-accessible method net/minecraft/util/math/Direction listClosest (Lnet/minecraft/util/math/Direction;Lnet/minecraft/util/math/Direction;Lnet/minecraft/util/math/Direction;)[Lnet/minecraft/util/math/Direction; transitive-accessible field net/minecraft/client/network/ClientPlayerInteractionManager gameMode Lnet/minecraft/world/GameMode; +transitive-accessible method net/minecraft/entity/player/PlayerEntity updatePose ()V # Camera transitive-accessible method net/minecraft/client/render/Camera setPos (DDD)V From c2dd7d7e535576ea16be8b3a7bb0dba26f312de9 Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Fri, 24 Oct 2025 15:31:02 +0100 Subject: [PATCH 08/30] replacement blocks for solid target state, and fixed race condition issue in place checker --- .../construction/simulation/BuildSimulator.kt | 4 +- .../simulation/checks/BreakChecker.kt | 14 ++-- .../simulation/checks/PlaceChecker.kt | 72 +++++++++---------- .../construction/verify/TargetState.kt | 6 +- .../module/modules/player/HighwayTools.kt | 8 +-- .../com/lambda/module/modules/player/Nuker.kt | 7 +- .../lambda/module/modules/player/Scaffold.kt | 2 +- 7 files changed, 54 insertions(+), 59 deletions(-) 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 dd72b217c..698c0c878 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt @@ -51,8 +51,8 @@ object BuildSimulator : SimChecker() { with(null) { if (checkRequirements() || checkPostProcessing() || - checkPlacements() || - checkBreaks()) return@launch + checkBreaks() || + checkPlacements()) return@launch else result(PostSimResult.NoMatch(pos)) } } diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecker.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecker.kt index 11662b6d7..43821b6cb 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecker.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecker.kt @@ -50,6 +50,7 @@ import com.lambda.threading.runSafe import com.lambda.util.BlockUtils.blockState import com.lambda.util.BlockUtils.calcItemBlockBreakingDelta import com.lambda.util.BlockUtils.instantBreakable +import com.lambda.util.BlockUtils.isEmpty import com.lambda.util.item.ItemStackUtils.inventoryIndexOrSelected import com.lambda.util.math.distSq import com.lambda.util.world.raycast.RayCastUtils.blockResult @@ -87,6 +88,7 @@ class BreakChecker @SimCheckerDsl private constructor(simInfo: SimInfo) val stackSelection: StackSelection by lazy { runSafe { selectStack( + count = 0, sorter = compareByDescending { it.canBreak(CachedBlockPosition(world, pos, false)) }.thenByDescending { @@ -131,8 +133,6 @@ class BreakChecker @SimCheckerDsl private constructor(simInfo: SimInfo) } private suspend fun AutomatedSafeContext.checkBreaks(): Boolean { - if (!targetState.isEmpty()) return false - /* player is standing on top of the block */ if (breakConfig.avoidSupporting) player.supportingBlockPos.getOrNull()?.let { support -> if (support != pos) return@let @@ -143,9 +143,11 @@ class BreakChecker @SimCheckerDsl private constructor(simInfo: SimInfo) /* liquid needs to be submerged first to be broken */ if (targetState.getState(pos).isAir && !state.fluidState.isEmpty && state.isReplaceable) { result(BreakResult.Submerge(pos, state)) - return simInfo(pos, state, TargetState.Solid)?.checkPlacements() ?: true + return simInfo(pos, state, TargetState.Solid(emptySet()))?.checkPlacements() ?: true } + if (state.isEmpty) return false + if (breakConfig.avoidLiquids && affectsFluids()) return true val voxelShape = state.getOutlineShape(world, pos) @@ -159,11 +161,9 @@ class BreakChecker @SimCheckerDsl private constructor(simInfo: SimInfo) breakConfig.breakThreshold ) - val currentRotation = RotationManager.activeRotation - val currentCast = currentRotation.rayCast(buildConfig.interactReach, eye) - /* the player is buried inside the block */ if (boxes.any { it.contains(eye) }) { + val currentCast = RotationManager.activeRotation.rayCast(buildConfig.interactReach, eye) currentCast?.blockResult?.let { blockHit -> val rotationRequest = RotationRequest(lookAtBlock(pos), this) val breakContext = BreakContext( @@ -336,7 +336,7 @@ class BreakChecker @SimCheckerDsl private constructor(simInfo: SimInfo) affectedFluids.forEach { (liquidPos, liquidState) -> result(BreakResult.Submerge(liquidPos, liquidState)) - simInfo(liquidPos, liquidState, TargetState.Solid)?.checkPlacements() + simInfo(liquidPos, liquidState, TargetState.Solid(emptySet()))?.checkPlacements() } result(BreakResult.BlockedByFluid(pos, state)) return true diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt index 20a5748d6..9aabadf0d 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt @@ -33,6 +33,7 @@ import com.lambda.interaction.material.ContainerSelection.Companion.selectContai import com.lambda.interaction.material.StackSelection.Companion.select import com.lambda.interaction.material.container.ContainerManager.containerWithMaterial import com.lambda.interaction.material.container.MaterialContainer +import com.lambda.interaction.request.rotating.Rotation import com.lambda.interaction.request.rotating.Rotation.Companion.rotation import com.lambda.interaction.request.rotating.Rotation.Companion.rotationTo import com.lambda.interaction.request.rotating.RotationManager @@ -78,9 +79,6 @@ class PlaceChecker @SimCheckerDsl private constructor(simInfo: SimInfo) : SimChecker(), Dependable, ISimInfo by simInfo { - lateinit var resultState: BlockState - var rot = RotationManager.serverRotation - private val swapStack by lazy { runSafeAutomated { val optimalStack = targetState.getStack(pos) @@ -104,8 +102,6 @@ class PlaceChecker @SimCheckerDsl private constructor(simInfo: SimInfo) } private val blockItem get() = swapStack.blockItem - private var currentDirIsValid = false - override fun asDependent(buildResult: BuildResult) = PlaceResult.Dependency(pos, buildResult) @@ -120,8 +116,6 @@ class PlaceChecker @SimCheckerDsl private constructor(simInfo: SimInfo) } private suspend fun AutomatedSafeContext.checkPlacements(): Boolean { - if (targetState.isEmpty()) return false - supervisorScope { withContext(Dispatchers.Default) { preProcessing.sides.map { side -> @@ -155,7 +149,7 @@ class PlaceChecker @SimCheckerDsl private constructor(simInfo: SimInfo) val misses = mutableSetOf() val reachSq = buildConfig.interactReach.pow(2) - // ToDo: For each hand and sneak or not? + // ToDo: For each hand val fakePlayer = copyPlayer(player).apply { this.rotation = RotationManager.serverRotation if (testBlockState.block::class in BlockUtils.interactionBlocks) { @@ -249,11 +243,12 @@ class PlaceChecker @SimCheckerDsl private constructor(simInfo: SimInfo) return } - if (!simRotation(fakePlayer, checkedHit, context)) return + val rotatePlaceTest = simRotatePlace(fakePlayer, checkedHit, context) ?: return + if (!rotatePlaceTest.isValid) return val rotationRequest = if (placeConfig.axisRotate) { - lookInDirection(PlaceDirection.fromRotation(rot)) - } else lookAt(rot, 0.001) + lookInDirection(PlaceDirection.fromRotation(rotatePlaceTest.rotation)) + } else lookAt(rotatePlaceTest.rotation, 0.001) val placeContext = PlaceContext( hitResult, @@ -261,10 +256,10 @@ class PlaceChecker @SimCheckerDsl private constructor(simInfo: SimInfo) swapStack.inventoryIndex, pos, state, - resultState, + rotatePlaceTest.placeTest.resultState, fakePlayer.isSneaking, false, - currentDirIsValid, + rotatePlaceTest.currentDirIsValid, this@PlaceChecker ) @@ -274,61 +269,64 @@ class PlaceChecker @SimCheckerDsl private constructor(simInfo: SimInfo) return } - private fun SafeContext.simRotation( + private fun SafeContext.simRotatePlace( fakePlayer: ClientPlayerEntity, checkedHit: CheckedHit, context: ItemPlacementContext - ): Boolean { - currentDirIsValid = if (testPlaceState(context) != PlaceTestResult.Success) { - if (!placeConfig.rotateForPlace) return false - else false - } else true + ): RotatePlaceTest? { + val currentDirIsValid = testPlaceState(context).let { currentDirTest -> + if (currentDirTest.testResult != PlaceTestResult.Success) { + if (!placeConfig.rotateForPlace) + return RotatePlaceTest(currentDirTest, false, fakePlayer.rotation) + else false + } else true + } if (!placeConfig.axisRotate) { fakePlayer.rotation = checkedHit.targetRotation - if (testPlaceState(context) != PlaceTestResult.Success) return false - rot = fakePlayer.rotation - return true + return RotatePlaceTest(testPlaceState(context), currentDirIsValid, fakePlayer.rotation) } fakePlayer.rotation = player.rotation - if (testPlaceState(context) == PlaceTestResult.Success) { - rot = fakePlayer.rotation - return true + testPlaceState(context).let { playerRotTest -> + if (playerRotTest.testResult == PlaceTestResult.Success) + return RotatePlaceTest(playerRotTest, currentDirIsValid, fakePlayer.rotation) } PlaceDirection.entries.asReversed().forEachIndexed direction@{ index, direction -> fakePlayer.rotation = direction.rotation - when (testPlaceState(context)) { + val axisRotateTest = testPlaceState(context) + when (axisRotateTest.testResult) { PlaceTestResult.BlockedByEntity -> return@direction PlaceTestResult.NoIntegrity -> { if (index != PlaceDirection.entries.lastIndex) return@direction - return false + return RotatePlaceTest(axisRotateTest, currentDirIsValid, fakePlayer.rotation) } - else -> { - rot = fakePlayer.rotation - return true - } + else -> return RotatePlaceTest(axisRotateTest, currentDirIsValid, fakePlayer.rotation) } } - return true + return null } - private fun SafeContext.testPlaceState(context: ItemPlacementContext): PlaceTestResult { - resultState = blockItem.getPlacementState(context) ?: run { + private fun SafeContext.testPlaceState(context: ItemPlacementContext): PlaceTest { + val resultState = blockItem.getPlacementState(context) ?: run { result(PlaceResult.BlockedByEntity(pos)) - return PlaceTestResult.BlockedByEntity + return PlaceTest(state, PlaceTestResult.BlockedByEntity) } return if (!targetState.matches(resultState, pos, preProcessing.ignore)) { result(PlaceResult.NoIntegrity(pos, resultState, context, (targetState as? TargetState.State)?.blockState)) - PlaceTestResult.NoIntegrity - } else PlaceTestResult.Success + PlaceTest(resultState, PlaceTestResult.NoIntegrity) + } else PlaceTest(resultState, PlaceTestResult.Success) } + private data class RotatePlaceTest(val placeTest: PlaceTest, val currentDirIsValid: Boolean, val rotation: Rotation) { + val isValid = placeTest.testResult == PlaceTestResult.Success + } + private data class PlaceTest(val resultState: BlockState, val testResult: PlaceTestResult) private enum class PlaceTestResult { Success, BlockedByEntity, diff --git a/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt b/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt index d0e614200..5a438a193 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt @@ -78,7 +78,7 @@ sealed class TargetState(val type: Type) : StateMatcher { override fun isEmpty() = true } - data object Solid : TargetState(Type.Solid) { + data class Solid(val replace: Set) : TargetState(Type.Solid) { override fun toString() = "Solid" context(safeContext: SafeContext) @@ -86,13 +86,13 @@ sealed class TargetState(val type: Type) : StateMatcher { state: BlockState, pos: BlockPos, ignoredProperties: Collection> - ) = with(safeContext) { state.isSolidBlock(world, pos) } + ) = with(safeContext) { state.isSolidBlock(world, pos) && state.block !in replace } context(automatedSafeContext: AutomatedSafeContext) override fun getStack(pos: BlockPos) = with(automatedSafeContext) { findDisposable()?.stacks?.firstOrNull { - it.item.block in inventoryConfig.disposables + it.item.block in inventoryConfig.disposables && it.item.block !in replace } ?: ItemStack(Items.NETHERRACK) } diff --git a/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt b/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt index 63fb744af..46327ce69 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt @@ -81,6 +81,7 @@ object HighwayTools : Module( private val wallMaterial by setting("Wall Material", Blocks.NETHERRACK, "Material to build the walls with") { rightWall == Material.Block || leftWall == Material.Block }.group(Group.Structure) private val ceiling by setting("Ceiling", Material.None, "Material for the ceiling").group(Group.Structure) private val ceilingMaterial by setting("Ceiling Material", Blocks.OBSIDIAN, "Material to build the ceiling with") { ceiling == Material.Block }.group(Group.Structure) + private val replaceableSolids by setting("Replaceable Solids", setOf(Blocks.MAGMA_BLOCK, Blocks.SOUL_SAND)).group(Group.Structure) private val distance by setting("Distance", -1, -1..1000000, 1, "Distance to build the highway/tunnel (negative for infinite)").group(Group.Structure) private val sliceSize by setting("Slice Size", 3, 1..5, 1, "Number of slices to build at once").group(Group.Structure) @@ -265,11 +266,10 @@ object HighwayTools : Module( return transformed } - private fun target(target: Material, material: net.minecraft.block.Block): TargetState { - return when (target) { - Material.Solid -> TargetState.Solid + private fun target(target: Material, material: net.minecraft.block.Block) = + when (target) { + Material.Solid -> TargetState.Solid(replaceableSolids) Material.Block -> TargetState.Block(material) else -> throw IllegalStateException("Invalid material") } - } } diff --git a/src/main/kotlin/com/lambda/module/modules/player/Nuker.kt b/src/main/kotlin/com/lambda/module/modules/player/Nuker.kt index df042db97..a967db0b4 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/Nuker.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/Nuker.kt @@ -17,7 +17,6 @@ package com.lambda.module.modules.player -import com.lambda.context.AutomationConfig import com.lambda.interaction.BaritoneManager import com.lambda.interaction.construction.blueprint.TickingBlueprint.Companion.tickingBlueprint import com.lambda.interaction.construction.verify.TargetState @@ -27,7 +26,7 @@ import com.lambda.task.RootTask.run import com.lambda.task.Task import com.lambda.task.tasks.BuildTask.Companion.build import com.lambda.util.BlockUtils.blockPos -import com.lambda.util.BlockUtils.blockState +import net.minecraft.block.Blocks import net.minecraft.util.math.BlockPos object Nuker : Module( @@ -39,7 +38,6 @@ object Nuker : Module( private val width by setting("Width", 4, 1..8, 1) private val flatten by setting("Flatten", true) private val fillFluids by setting("Fill Fluids", false, "Removes liquids by filling them in before breaking") - private val instantOnly by setting("Instant Only", false) private val fillFloor by setting("Fill Floor", false) private val baritoneSelection by setting("Baritone Selection", false, "Restricts nuker to your baritone selection") @@ -53,7 +51,6 @@ object Nuker : Module( .map { it.blockPos } .filter { !world.isAir(it) } .filter { !flatten || it.y >= player.blockPos.y } - .filter { !instantOnly || blockState(it).getHardness(world, it) <= AutomationConfig.breakConfig.breakThreshold } .filter { pos -> if (!baritoneSelection) true else BaritoneManager.primary.selectionManager.selections.any { @@ -69,7 +66,7 @@ object Nuker : Module( if (fillFloor) { val floor = BlockPos.iterateOutwards(player.blockPos.down(), width, 0, width) .map { it.blockPos } - .associateWith { TargetState.Solid } + .associateWith { TargetState.Solid(setOf(Blocks.MAGMA_BLOCK)) } return@tickingBlueprint selection + floor } 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 3ebc4b0dd..d70eada13 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt @@ -92,7 +92,7 @@ object Scaffold : Module( val beneath = playerSupport.down(offset) runSafeAutomated { scaffoldPositions(beneath) - .associateWith { TargetState.Solid } + .associateWith { TargetState.Solid(emptySet()) } .toBlueprint() .simulate() .filterIsInstance() From aee1d804fe4c03b3adc6a3590c5c4995e1de39ec Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Fri, 24 Oct 2025 22:18:51 +0100 Subject: [PATCH 09/30] better place rotation logic --- .../simulation/checks/PlaceChecker.kt | 52 +++++++++---------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt index 9aabadf0d..7c3e38d9e 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt @@ -28,6 +28,7 @@ import com.lambda.interaction.construction.simulation.ISimInfo import com.lambda.interaction.construction.simulation.SimChecker import com.lambda.interaction.construction.simulation.SimCheckerDsl import com.lambda.interaction.construction.simulation.SimInfo +import com.lambda.interaction.construction.simulation.checks.PlaceChecker.RotatePlaceTest.Companion.rotatePlaceTest import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.material.ContainerSelection.Companion.selectContainer import com.lambda.interaction.material.StackSelection.Companion.select @@ -151,7 +152,6 @@ class PlaceChecker @SimCheckerDsl private constructor(simInfo: SimInfo) // ToDo: For each hand val fakePlayer = copyPlayer(player).apply { - this.rotation = RotationManager.serverRotation if (testBlockState.block::class in BlockUtils.interactionBlocks) { input.sneaking = true updatePose() @@ -244,7 +244,6 @@ class PlaceChecker @SimCheckerDsl private constructor(simInfo: SimInfo) } val rotatePlaceTest = simRotatePlace(fakePlayer, checkedHit, context) ?: return - if (!rotatePlaceTest.isValid) return val rotationRequest = if (placeConfig.axisRotate) { lookInDirection(PlaceDirection.fromRotation(rotatePlaceTest.rotation)) @@ -256,7 +255,7 @@ class PlaceChecker @SimCheckerDsl private constructor(simInfo: SimInfo) swapStack.inventoryIndex, pos, state, - rotatePlaceTest.placeTest.resultState, + rotatePlaceTest.resultState, fakePlayer.isSneaking, false, rotatePlaceTest.currentDirIsValid, @@ -274,37 +273,23 @@ class PlaceChecker @SimCheckerDsl private constructor(simInfo: SimInfo) checkedHit: CheckedHit, context: ItemPlacementContext ): RotatePlaceTest? { - val currentDirIsValid = testPlaceState(context).let { currentDirTest -> - if (currentDirTest.testResult != PlaceTestResult.Success) { - if (!placeConfig.rotateForPlace) - return RotatePlaceTest(currentDirTest, false, fakePlayer.rotation) - else false - } else true - } + fakePlayer.rotation = RotationManager.serverRotation + val currentDirIsValid = testPlaceState(context).testResult == PlaceTestResult.Success if (!placeConfig.axisRotate) { fakePlayer.rotation = checkedHit.targetRotation - return RotatePlaceTest(testPlaceState(context), currentDirIsValid, fakePlayer.rotation) + return rotatePlaceTest(testPlaceState(context), currentDirIsValid, fakePlayer.rotation) } fakePlayer.rotation = player.rotation - testPlaceState(context).let { playerRotTest -> - if (playerRotTest.testResult == PlaceTestResult.Success) - return RotatePlaceTest(playerRotTest, currentDirIsValid, fakePlayer.rotation) + testPlaceState(context).takeIf { it.testResult == PlaceTestResult.Success }?.let { playerRotTest -> + return rotatePlaceTest(playerRotTest, currentDirIsValid, fakePlayer.rotation) } - PlaceDirection.entries.asReversed().forEachIndexed direction@{ index, direction -> + PlaceDirection.entries.asReversed().forEach direction@{ direction -> fakePlayer.rotation = direction.rotation - val axisRotateTest = testPlaceState(context) - when (axisRotateTest.testResult) { - PlaceTestResult.BlockedByEntity -> return@direction - - PlaceTestResult.NoIntegrity -> { - if (index != PlaceDirection.entries.lastIndex) return@direction - return RotatePlaceTest(axisRotateTest, currentDirIsValid, fakePlayer.rotation) - } - - else -> return RotatePlaceTest(axisRotateTest, currentDirIsValid, fakePlayer.rotation) + testPlaceState(context).takeIf { it.testResult == PlaceTestResult.Success }?.let { axisRotateTest -> + return rotatePlaceTest(axisRotateTest, currentDirIsValid, fakePlayer.rotation) } } @@ -323,8 +308,21 @@ class PlaceChecker @SimCheckerDsl private constructor(simInfo: SimInfo) } else PlaceTest(resultState, PlaceTestResult.Success) } - private data class RotatePlaceTest(val placeTest: PlaceTest, val currentDirIsValid: Boolean, val rotation: Rotation) { - val isValid = placeTest.testResult == PlaceTestResult.Success + private class RotatePlaceTest private constructor( + val resultState: BlockState, + val currentDirIsValid: Boolean, + val rotation: Rotation + ) { + companion object { + fun rotatePlaceTest( + placeTest: PlaceTest, + currentDirIsValid: Boolean, + rotation: Rotation + ): RotatePlaceTest? { + return if (placeTest.testResult != PlaceTestResult.Success) null + else RotatePlaceTest(placeTest.resultState, currentDirIsValid, rotation) + } + } } private data class PlaceTest(val resultState: BlockState, val testResult: PlaceTestResult) private enum class PlaceTestResult { From 25f1c9b8c0ef6f06b3a79819c66eb14d90af6814 Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Sat, 25 Oct 2025 04:01:01 +0100 Subject: [PATCH 10/30] cleanup --- .../construction/result/results/GenericResult.kt | 4 +--- .../simulation/checks/BreakChecker.kt | 16 +++++++++++----- .../simulation/checks/PlaceChecker.kt | 9 +++++---- .../simulation/checks/PostProcessingChecker.kt | 14 ++++---------- 4 files changed, 21 insertions(+), 22 deletions(-) diff --git a/src/main/kotlin/com/lambda/interaction/construction/result/results/GenericResult.kt b/src/main/kotlin/com/lambda/interaction/construction/result/results/GenericResult.kt index 1b9b5f306..e54216738 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/result/results/GenericResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/result/results/GenericResult.kt @@ -32,7 +32,6 @@ import com.lambda.interaction.material.container.MaterialContainer import com.lambda.interaction.material.container.containers.MainHandContainer import net.minecraft.item.ItemStack import net.minecraft.util.math.BlockPos -import net.minecraft.util.math.Direction import net.minecraft.util.math.Vec3d import java.awt.Color @@ -45,8 +44,7 @@ sealed class GenericResult : BuildResult() { data class NotVisible( override val pos: BlockPos, val hitPos: BlockPos, - val side: Direction, - val distance: Double, + val distance: Double ) : Drawable, GenericResult() { override val name: String get() = "Not visible at $pos." override val rank = Rank.NotVisible diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecker.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecker.kt index 43821b6cb..505862d3a 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecker.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecker.kt @@ -53,6 +53,7 @@ import com.lambda.util.BlockUtils.instantBreakable import com.lambda.util.BlockUtils.isEmpty import com.lambda.util.item.ItemStackUtils.inventoryIndexOrSelected import com.lambda.util.math.distSq +import com.lambda.util.math.vec3d import com.lambda.util.world.raycast.RayCastUtils.blockResult import io.ktor.util.collections.* import kotlinx.coroutines.Dispatchers @@ -187,9 +188,10 @@ class BreakChecker @SimCheckerDsl private constructor(simInfo: SimInfo) withContext(Dispatchers.Default) { boxes.map { box -> launch { - val sides = if (buildConfig.checkSideVisibility) { - box.getVisibleSurfaces(eye).intersect(Direction.entries) - } else Direction.entries.toSet() + val sides = if (buildConfig.checkSideVisibility) + box.getVisibleSurfaces(eye) + else Direction.entries.toSet() + scanSurfaces(box, sides, buildConfig.resolution) { side, vec -> if (eye distSq vec > reachSq) { misses.add(vec) @@ -215,8 +217,12 @@ class BreakChecker @SimCheckerDsl private constructor(simInfo: SimInfo) } if (validHits.isEmpty()) { - // ToDo: If we can only mine exposed surfaces we need to add not visible result here - result(GenericResult.OutOfReach(pos, eye, misses)) + if (misses.isNotEmpty()) { + result(GenericResult.OutOfReach(pos, eye, misses)) + return true + } + + result(GenericResult.NotVisible(pos, pos, eye.distanceTo(pos.vec3d))) return true } diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt index 7c3e38d9e..3593a75ef 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt @@ -55,6 +55,7 @@ import com.lambda.util.math.vec3d import com.lambda.util.player.MovementUtils.sneaking import com.lambda.util.player.copyPlayer import com.lambda.util.world.raycast.RayCastUtils.blockResult +import io.ktor.util.collections.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.cancel @@ -146,8 +147,8 @@ class PlaceChecker @SimCheckerDsl private constructor(simInfo: SimInfo) val boxes = voxelShape.boundingBoxes.map { it.offset(pos) } - val validHits = mutableListOf() - val misses = mutableSetOf() + val validHits = ConcurrentSet() + val misses = ConcurrentSet() val reachSq = buildConfig.interactReach.pow(2) // ToDo: For each hand @@ -198,7 +199,7 @@ class PlaceChecker @SimCheckerDsl private constructor(simInfo: SimInfo) return } - result(GenericResult.NotVisible(pos, pos, side, eye.distanceTo(pos.offset(side).vec3d))) + result(GenericResult.NotVisible(pos, pos, eye.distanceTo(pos.offset(side).vec3d))) return } @@ -210,7 +211,7 @@ class PlaceChecker @SimCheckerDsl private constructor(simInfo: SimInfo) } else selectHitPos(validHits, fakePlayer) } - private fun AutomatedSafeContext.selectHitPos(validHits: List, fakePlayer: ClientPlayerEntity) { + private fun AutomatedSafeContext.selectHitPos(validHits: Collection, fakePlayer: ClientPlayerEntity) { buildConfig.pointSelection.select(validHits)?.let { checkedHit -> val hitResult = checkedHit.hit.blockResult ?: return diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingChecker.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingChecker.kt index 26097003e..139380d31 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingChecker.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingChecker.kt @@ -178,17 +178,11 @@ class PostProcessingChecker @SimCheckerDsl private constructor(simInfo: SimInfo) if (validHits.isEmpty()) { if (misses.isNotEmpty()) { result(GenericResult.OutOfReach(pos, eye, misses)) - } else { - //ToDo: Must clean up surface scan usage / renders. Added temporary direction until changes are made - result( - GenericResult.NotVisible( - pos, - pos, - Direction.UP, - eye.distanceTo(pos.offset(Direction.UP).vec3d) - ) - ) + return } + + //ToDo: Must clean up surface scan usage / renders. Added temporary direction until changes are made + result(GenericResult.NotVisible(pos, pos, eye.distanceTo(pos.vec3d))) return } From 2ba5f5885241ada1bd97304050ef02a940e61fa3 Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Sat, 25 Oct 2025 17:00:55 +0100 Subject: [PATCH 11/30] sort contextual build results by current tool, if not build context --- .../construction/context/BreakContext.kt | 3 ++- .../construction/result/BuildResult.kt | 8 ++------ .../construction/result/ComparableResult.kt | 12 +++++++++--- .../interaction/construction/result/Contextual.kt | 15 ++++++++++++++- .../construction/result/results/BreakResult.kt | 13 ++++--------- .../construction/result/results/InteractResult.kt | 6 +++--- .../construction/result/results/PlaceResult.kt | 13 +++---------- 7 files changed, 37 insertions(+), 33 deletions(-) diff --git a/src/main/kotlin/com/lambda/interaction/construction/context/BreakContext.kt b/src/main/kotlin/com/lambda/interaction/construction/context/BreakContext.kt index 2ba2a003c..a6f6c17d2 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/context/BreakContext.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/context/BreakContext.kt @@ -60,7 +60,8 @@ data class BreakContext( override fun compareTo(other: BuildContext): Int = runSafe { return when (other) { is BreakContext -> compareByDescending { - if (breakConfig.sorter == BreakConfig.SortMode.Tool) it.hotbarIndex == HotbarManager.serverSlot + if (breakConfig.sorter == BreakConfig.SortMode.Tool) + it.hotbarIndex == HotbarManager.serverSlot else 0 }.thenBy { when (breakConfig.sorter) { diff --git a/src/main/kotlin/com/lambda/interaction/construction/result/BuildResult.kt b/src/main/kotlin/com/lambda/interaction/construction/result/BuildResult.kt index 4a6e5eb57..7f1158d68 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/result/BuildResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/result/BuildResult.kt @@ -20,13 +20,9 @@ package com.lambda.interaction.construction.result import com.lambda.util.Nameable import net.minecraft.util.math.BlockPos -abstract class BuildResult : Nameable, ComparableResult() { +abstract class BuildResult : Nameable, ComparableResult { abstract val pos: BlockPos override val compareBy = this - final override fun compareTo(other: ComparableResult) = - compareBy.compareResult(other.compareBy) - - open fun compareResult(other: ComparableResult) = - compareBy.rank.compareTo(other.compareBy.rank) + final override fun compareTo(other: ComparableResult) = super.compareTo(other) } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/construction/result/ComparableResult.kt b/src/main/kotlin/com/lambda/interaction/construction/result/ComparableResult.kt index 86c02597e..efd8c1c49 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/result/ComparableResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/result/ComparableResult.kt @@ -17,7 +17,13 @@ package com.lambda.interaction.construction.result -abstract class ComparableResult> : Comparable> { - abstract val rank: T - abstract val compareBy: ComparableResult +interface ComparableResult> : Comparable> { + val rank: T + val compareBy: ComparableResult + + override fun compareTo(other: ComparableResult) = + compareBy.compareResult(other.compareBy) + + fun compareResult(other: ComparableResult): Int = + compareBy.rank.compareTo(other.compareBy.rank) } diff --git a/src/main/kotlin/com/lambda/interaction/construction/result/Contextual.kt b/src/main/kotlin/com/lambda/interaction/construction/result/Contextual.kt index 71b314d9a..a95f01cb5 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/result/Contextual.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/result/Contextual.kt @@ -18,7 +18,20 @@ package com.lambda.interaction.construction.result import com.lambda.interaction.construction.context.BuildContext +import com.lambda.interaction.request.hotbar.HotbarManager -interface Contextual { +interface Contextual : ComparableResult { val context: BuildContext + + override fun compareResult(other: ComparableResult) = + when (other) { + + is Contextual -> compareByDescending { + it.context.hotbarIndex == HotbarManager.serverSlot + }.thenBy { + it.compareBy.rank + }.compare(this, other) + + else -> compareBy.rank.compareTo(other.compareBy.rank) + } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/construction/result/results/BreakResult.kt b/src/main/kotlin/com/lambda/interaction/construction/result/results/BreakResult.kt index 2362b7bb0..d442fba00 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/result/results/BreakResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/result/results/BreakResult.kt @@ -42,6 +42,8 @@ import net.minecraft.util.math.Direction import java.awt.Color sealed class BreakResult : BuildResult() { + override val name: String get() = "${this::class.simpleName} at ${pos.toShortString()}" + /** * Represents a successful break. All checks have been passed. * @param context The context of the break. @@ -49,8 +51,7 @@ sealed class BreakResult : BuildResult() { data class Break( override val pos: BlockPos, override val context: BreakContext, - ) : Drawable, Contextual, BreakResult() { - override val name: String get() = "${this::class.simpleName} at ${pos.toShortString()}" + ) : Contextual, Drawable, BreakResult() { override val rank = Rank.BreakSuccess override fun ShapeBuilder.buildRenderer() { @@ -60,7 +61,7 @@ sealed class BreakResult : BuildResult() { override fun compareResult(other: ComparableResult) = when (other) { is Break -> context.compareTo(other.context) - else -> super.compareResult(other) + else -> super.compareResult(other) } } @@ -73,7 +74,6 @@ sealed class BreakResult : BuildResult() { override val pos: BlockPos, val side: Direction, ) : Drawable, BreakResult() { - override val name: String get() = "${this::class.simpleName} at ${pos.toShortString()}" override val rank = Rank.BreakNotExposed private val color = Color(46, 0, 0, 30) @@ -98,7 +98,6 @@ sealed class BreakResult : BuildResult() { val blockState: BlockState, val badItem: Item ) : Drawable, Resolvable, BreakResult() { - override val name: String get() = "${this::class.simpleName} at ${pos.toShortString()}" override val rank = Rank.BreakItemCantMine private val color = Color(255, 0, 0, 100) @@ -134,7 +133,6 @@ sealed class BreakResult : BuildResult() { override val pos: BlockPos, val blockState: BlockState ) : Drawable, BreakResult() { - override val name: String get() = "${this::class.simpleName} at ${pos.toShortString()}" override val rank = Rank.BreakSubmerge private val color = Color(114, 27, 255, 100) @@ -150,7 +148,6 @@ sealed class BreakResult : BuildResult() { override val pos: BlockPos, val blockState: BlockState, ) : Drawable, BreakResult() { - override val name: String get() = "${this::class.simpleName} at ${pos.toShortString()}" override val rank = Rank.BreakIsBlockedByFluid private val color = Color(50, 12, 112, 100) @@ -166,7 +163,6 @@ sealed class BreakResult : BuildResult() { override val pos: BlockPos, val blockState: BlockState, ) : Navigable, Drawable, BreakResult() { - override val name: String get() = "${this::class.simpleName} at ${pos.toShortString()}" override val rank = Rank.BreakPlayerOnTop private val color = Color(252, 3, 207, 100) @@ -181,7 +177,6 @@ sealed class BreakResult : BuildResult() { override val pos: BlockPos, override val dependency: BuildResult ) : BreakResult(), Dependent by Dependent.Nested(dependency) { - override val name: String get() = "${this::class.simpleName} at ${pos.toShortString()}" override val rank = dependency.rank override val compareBy = lastDependency } diff --git a/src/main/kotlin/com/lambda/interaction/construction/result/results/InteractResult.kt b/src/main/kotlin/com/lambda/interaction/construction/result/results/InteractResult.kt index 729854f60..67a6012ad 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/result/results/InteractResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/result/results/InteractResult.kt @@ -28,11 +28,12 @@ import com.lambda.interaction.construction.result.Rank import net.minecraft.util.math.BlockPos sealed class InteractResult : BuildResult() { + override val name: String get() = "${this::class.simpleName} at ${pos.toShortString()}" + data class Interact( override val pos: BlockPos, override val context: InteractionContext ) : Contextual, Drawable, InteractResult() { - override val name: String get() = "${this::class.simpleName} at ${pos.toShortString()}" override val rank = Rank.InteractSuccess override fun ShapeBuilder.buildRenderer() { @@ -42,7 +43,7 @@ sealed class InteractResult : BuildResult() { override fun compareResult(other: ComparableResult) = when (other) { is Interact -> context.compareTo(other.context) - else -> super.compareResult(other) + else -> super.compareResult(other) } } @@ -50,7 +51,6 @@ sealed class InteractResult : BuildResult() { override val pos: BlockPos, override val dependency: BuildResult ) : InteractResult(), Dependent by Dependent.Nested(dependency) { - override val name: String get() = "${this::class.simpleName} at ${pos.toShortString()}" override val rank = dependency.rank override val compareBy = lastDependency } diff --git a/src/main/kotlin/com/lambda/interaction/construction/result/results/PlaceResult.kt b/src/main/kotlin/com/lambda/interaction/construction/result/results/PlaceResult.kt index 0a8fce8a4..1f3b8cb53 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/result/results/PlaceResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/result/results/PlaceResult.kt @@ -45,6 +45,8 @@ import java.awt.Color * First based on the context, then based on the [com.lambda.interaction.construction.result.Rank]. */ sealed class PlaceResult : BuildResult() { + override val name: String get() = "${this::class.simpleName} at ${pos.toShortString()}" + /** * Represents a successful placement. All checks have been passed. * @param context The context of the placement. @@ -53,7 +55,6 @@ sealed class PlaceResult : BuildResult() { override val pos: BlockPos, override val context: PlaceContext, ) : Contextual, Drawable, PlaceResult() { - override val name: String get() = "${this::class.simpleName} at ${pos.toShortString()}" override val rank = Rank.PlaceSuccess override fun ShapeBuilder.buildRenderer() { @@ -63,7 +64,7 @@ sealed class PlaceResult : BuildResult() { override fun compareResult(other: ComparableResult) = when (other) { is Place -> context.compareTo(other.context) - else -> super.compareResult(other) + else -> super.compareResult(other) } } @@ -84,7 +85,6 @@ sealed class PlaceResult : BuildResult() { val simulated: ItemPlacementContext, val actual: BlockState? = null, ) : Drawable, PlaceResult() { - override val name: String get() = "${this::class.simpleName} at ${pos.toShortString()}" override val rank = Rank.PlaceNoIntegrity private val color = Color(252, 3, 3, 100) @@ -101,7 +101,6 @@ sealed class PlaceResult : BuildResult() { data class BlockedByEntity( override val pos: BlockPos, ) : Navigable, PlaceResult() { - override val name: String get() = "${this::class.simpleName} at ${pos.toShortString()}" override val rank = Rank.PlaceBlockedByPlayer // ToDo: check what type of entity. player -> leave box, other entity -> kill? @@ -118,7 +117,6 @@ sealed class PlaceResult : BuildResult() { override val pos: BlockPos, val simulated: ItemPlacementContext, ) : Resolvable, PlaceResult() { - override val name: String get() = "${this::class.simpleName} at ${pos.toShortString()}" override val rank = Rank.PlaceCantReplace context(automated: Automated) @@ -135,7 +133,6 @@ sealed class PlaceResult : BuildResult() { override val pos: BlockPos, val simulated: ItemPlacementContext, ) : PlaceResult() { - override val name: String get() = "${this::class.simpleName} at ${pos.toShortString()}" override val rank = Rank.PlaceScaffoldExceeded } @@ -150,7 +147,6 @@ sealed class PlaceResult : BuildResult() { override val pos: BlockPos, val itemStack: ItemStack, ) : PlaceResult() { - override val name: String get() = "${this::class.simpleName} at ${pos.toShortString()}" override val rank = Rank.PlaceBlockFeatureDisabled } @@ -164,7 +160,6 @@ sealed class PlaceResult : BuildResult() { override val pos: BlockPos, val actualPos: BlockPos, ) : PlaceResult() { - override val name: String get() = "${this::class.simpleName} at ${pos.toShortString()}" override val rank = Rank.UnexpectedPosition } @@ -178,7 +173,6 @@ sealed class PlaceResult : BuildResult() { data class IllegalUsage( override val pos: BlockPos, ) : PlaceResult() { - override val name: String get() = "${this::class.simpleName} at ${pos.toShortString()}" override val rank = Rank.PlaceIllegalUsage } @@ -186,7 +180,6 @@ sealed class PlaceResult : BuildResult() { override val pos: BlockPos, override val dependency: BuildResult ) : PlaceResult(), Dependent by Dependent.Nested(dependency) { - override val name: String get() = "${this::class.simpleName} at ${pos.toShortString()}" override val rank = lastDependency.rank override val compareBy = lastDependency } From ff78db95614365c1da462ee23c7fa97d6f97e103 Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Sat, 25 Oct 2025 17:54:28 +0100 Subject: [PATCH 12/30] reintroduce custom eye pos --- .../construction/simulation/BuildSimulator.kt | 4 ++- .../construction/simulation/ISimInfo.kt | 9 ++++-- .../construction/simulation/SimChecker.kt | 4 --- .../construction/simulation/Simulation.kt | 29 +++++++++---------- .../simulation/checks/BreakChecker.kt | 18 ++++++------ .../simulation/checks/PlaceChecker.kt | 19 +++++++----- .../checks/PostProcessingChecker.kt | 18 ++++++------ 7 files changed, 53 insertions(+), 48 deletions(-) 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 698c0c878..12d3e822b 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt @@ -32,10 +32,11 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.joinAll import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking +import net.minecraft.util.math.Vec3d object BuildSimulator : SimChecker() { context(automatedSafeContext: AutomatedSafeContext) - fun Blueprint.simulate(): Set = runBlocking(Dispatchers.Default) { + fun Blueprint.simulate(pov: Vec3d = automatedSafeContext.player.eyePos): Set = runBlocking(Dispatchers.Default) { val concurrentSet = ConcurrentSet() with(automatedSafeContext) { structure.entries @@ -45,6 +46,7 @@ object BuildSimulator : SimChecker() { pos, blockState(pos), targetState, + pov, concurrentSet ) ?: return@launch with(simInfo) { diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/ISimInfo.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/ISimInfo.kt index ab5c076c2..3181733d5 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/ISimInfo.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/ISimInfo.kt @@ -25,6 +25,7 @@ import com.lambda.interaction.construction.result.Dependable import com.lambda.interaction.construction.verify.TargetState import net.minecraft.block.BlockState import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Vec3d import java.util.* interface ISimInfo : Automated { @@ -32,6 +33,7 @@ interface ISimInfo : Automated { val state: BlockState val targetState: TargetState val preProcessing: PreProcessingInfo + val pov: Vec3d val concurrentResults: MutableSet val dependencyStack: Stack @@ -41,10 +43,11 @@ interface ISimInfo : Automated { pos: BlockPos, state: BlockState, targetState: TargetState, + pov: Vec3d, concurrentResults: MutableSet, ): SimInfo? { val preProcessingInfo = targetState.getProcessingInfo(pos) ?: return null - return SimInfo(pos, state, targetState, preProcessingInfo, concurrentResults, this) + return SimInfo(pos, state, targetState, preProcessingInfo, pov, concurrentResults, this) } @SimCheckerDsl @@ -52,10 +55,11 @@ interface ISimInfo : Automated { pos: BlockPos = this.pos, state: BlockState = this.state, targetState: TargetState = this.targetState, + pov: Vec3d = this.pov, concurrentResults: MutableSet = this.concurrentResults ): SimInfo? { val preProcessingInfo = targetState.getProcessingInfo(pos) ?: return null - return SimInfo(pos, state, targetState, preProcessingInfo, concurrentResults, this) + return SimInfo(pos, state, targetState, preProcessingInfo, pov, concurrentResults, this) } } } @@ -65,6 +69,7 @@ data class SimInfo( override val state: BlockState, override val targetState: TargetState, override val preProcessing: PreProcessingInfo, + override val pov: Vec3d, override val concurrentResults: MutableSet, val automated: Automated ) : ISimInfo, Automated by automated { diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/SimChecker.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/SimChecker.kt index c9c86fb93..92f7a61ae 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/SimChecker.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/SimChecker.kt @@ -17,19 +17,15 @@ package com.lambda.interaction.construction.simulation -import com.lambda.context.SafeContext import com.lambda.interaction.construction.result.BuildResult import com.lambda.interaction.construction.result.Dependable import com.lambda.interaction.construction.result.results.GenericResult -import net.minecraft.util.math.Vec3d @DslMarker annotation class SimCheckerDsl @SimCheckerDsl abstract class SimChecker { - val SafeContext.eye: Vec3d get() = player.eyePos - protected fun ISimInfo.checkDependent(caller: Dependable?) { if (caller == null) { dependencyStack.clear() diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt index 019b2c399..df216b8c3 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt @@ -43,21 +43,20 @@ data class Simulation( private val cache: MutableMap> = mutableMapOf() private fun FastVector.toView(): Vec3d = toVec3d().add(0.5, ClientPlayerEntity.DEFAULT_EYE_HEIGHT.toDouble(), 0.5) - fun simulate( - pos: FastVector, - ) = cache.getOrPut(pos) { - val view = pos.toView() - val isOutOfBounds = blueprint.isOutOfBounds(view) - val isTooFar = blueprint.getClosestPointTo(view).distanceTo(view) > 10.0 - return@getOrPut runSafeAutomated { - if (isOutOfBounds && isTooFar) return@getOrPut emptySet() - val blockPos = pos.toBlockPos() - val isWalkable = blockState(blockPos.down()).isSideSolidFullSquare(world, blockPos, Direction.UP) - if (!isWalkable) return@getOrPut emptySet() - if (!playerFitsIn(blockPos)) return@getOrPut emptySet() - blueprint.simulate() - } ?: emptySet() - } + fun simulate(pos: FastVector) = + cache.getOrPut(pos) { + val pov = pos.toView() + val isOutOfBounds = blueprint.isOutOfBounds(pov) + val isTooFar = blueprint.getClosestPointTo(pov).distanceTo(pov) > 10.0 + return@getOrPut runSafeAutomated { + if (isOutOfBounds && isTooFar) return@getOrPut emptySet() + val blockPos = pos.toBlockPos() + val isWalkable = blockState(blockPos.down()).isSideSolidFullSquare(world, blockPos, Direction.UP) + if (!isWalkable) return@getOrPut emptySet() + if (!playerFitsIn(blockPos)) return@getOrPut emptySet() + blueprint.simulate(pov) + } ?: emptySet() + } fun goodPositions() = cache .filter { entry -> entry.value.any { it.rank.ordinal < 4 } } diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecker.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecker.kt index 505862d3a..a6e3c6ea9 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecker.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecker.kt @@ -163,8 +163,8 @@ class BreakChecker @SimCheckerDsl private constructor(simInfo: SimInfo) ) /* the player is buried inside the block */ - if (boxes.any { it.contains(eye) }) { - val currentCast = RotationManager.activeRotation.rayCast(buildConfig.interactReach, eye) + if (boxes.any { it.contains(pov) }) { + val currentCast = RotationManager.activeRotation.rayCast(buildConfig.interactReach, pov) currentCast?.blockResult?.let { blockHit -> val rotationRequest = RotationRequest(lookAtBlock(pos), this) val breakContext = BreakContext( @@ -189,21 +189,21 @@ class BreakChecker @SimCheckerDsl private constructor(simInfo: SimInfo) boxes.map { box -> launch { val sides = if (buildConfig.checkSideVisibility) - box.getVisibleSurfaces(eye) + box.getVisibleSurfaces(pov) else Direction.entries.toSet() scanSurfaces(box, sides, buildConfig.resolution) { side, vec -> - if (eye distSq vec > reachSq) { + if (pov distSq vec > reachSq) { misses.add(vec) return@scanSurfaces } - val newRotation = eye.rotationTo(vec) + val newRotation = pov.rotationTo(vec) val hit = if (buildConfig.strictRayCast) { - newRotation.rayCast(buildConfig.interactReach, eye)?.blockResult + newRotation.rayCast(buildConfig.interactReach, pov)?.blockResult } else { - val hitVec = newRotation.castBox(box, buildConfig.interactReach, eye) + val hitVec = newRotation.castBox(box, buildConfig.interactReach, pov) BlockHitResult(hitVec, side, pos, false) } ?: return@scanSurfaces @@ -218,11 +218,11 @@ class BreakChecker @SimCheckerDsl private constructor(simInfo: SimInfo) if (validHits.isEmpty()) { if (misses.isNotEmpty()) { - result(GenericResult.OutOfReach(pos, eye, misses)) + result(GenericResult.OutOfReach(pos, pov, misses)) return true } - result(GenericResult.NotVisible(pos, pos, eye.distanceTo(pos.vec3d))) + result(GenericResult.NotVisible(pos, pos, pov.distanceTo(pos.vec3d))) return true } diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt index 3593a75ef..ba7ead506 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt @@ -51,6 +51,7 @@ import com.lambda.util.BlockUtils.blockState import com.lambda.util.item.ItemStackUtils.inventoryIndex import com.lambda.util.item.ItemUtils.blockItem import com.lambda.util.math.distSq +import com.lambda.util.math.minus import com.lambda.util.math.vec3d import com.lambda.util.player.MovementUtils.sneaking import com.lambda.util.player.copyPlayer @@ -153,34 +154,36 @@ class PlaceChecker @SimCheckerDsl private constructor(simInfo: SimInfo) // ToDo: For each hand val fakePlayer = copyPlayer(player).apply { + val newPos = pov - (this.eyePos - this.pos) + setPos(newPos.x, newPos.y, newPos.z) if (testBlockState.block::class in BlockUtils.interactionBlocks) { input.sneaking = true updatePose() } } - val eye = fakePlayer.eyePos + val pov = fakePlayer.eyePos withContext(Dispatchers.Default) { boxes.map { box -> launch { val sides = if (buildConfig.checkSideVisibility || buildConfig.strictRayCast) { - box.getVisibleSurfaces(eye).intersect(setOf(side)) + box.getVisibleSurfaces(pov).intersect(setOf(side)) } else setOf(side) scanSurfaces(box, sides, buildConfig.resolution, preProcessing.surfaceScan) { _, vec -> - val distSquared = eye distSq vec + val distSquared = pov distSq vec if (distSquared > reachSq) { misses.add(vec) return@scanSurfaces } - val newRotation = eye.rotationTo(vec) + val newRotation = pov.rotationTo(vec) val hit = if (buildConfig.strictRayCast) { - newRotation.rayCast(buildConfig.interactReach, eye)?.blockResult ?: return@scanSurfaces + newRotation.rayCast(buildConfig.interactReach, pov)?.blockResult ?: return@scanSurfaces } else { - val hitVec = newRotation.castBox(box, buildConfig.interactReach, eye) ?: return@scanSurfaces + val hitVec = newRotation.castBox(box, buildConfig.interactReach, pov) ?: return@scanSurfaces BlockHitResult(hitVec, side, pos, false) } @@ -195,11 +198,11 @@ class PlaceChecker @SimCheckerDsl private constructor(simInfo: SimInfo) if (validHits.isEmpty()) { if (misses.isNotEmpty()) { - result(GenericResult.OutOfReach(pos, eye, misses)) + result(GenericResult.OutOfReach(pos, pov, misses)) return } - result(GenericResult.NotVisible(pos, pos, eye.distanceTo(pos.offset(side).vec3d))) + result(GenericResult.NotVisible(pos, pos, pov.distanceTo(pos.offset(side).vec3d))) return } diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingChecker.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingChecker.kt index 139380d31..8fa51a3d0 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingChecker.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingChecker.kt @@ -126,7 +126,7 @@ class PostProcessingChecker @SimCheckerDsl private constructor(simInfo: SimInfo) boxes.forEach { box -> val refinedSides = if (buildConfig.checkSideVisibility) { - box.getVisibleSurfaces(eye).let { visibleSides -> + box.getVisibleSurfaces(pov).let { visibleSides -> sides?.let { specific -> visibleSides.intersect(specific) } ?: visibleSides.toSet() @@ -139,29 +139,29 @@ class PostProcessingChecker @SimCheckerDsl private constructor(simInfo: SimInfo) buildConfig.resolution, preProcessing.surfaceScan ) { hitSide, vec -> - val distSquared = eye distSq vec + val distSquared = pov distSq vec if (distSquared > buildConfig.interactReach.pow(2)) { misses.add(vec) return@scanSurfaces } - val newRotation = eye.rotationTo(vec) + val newRotation = pov.rotationTo(vec) val hit = if (buildConfig.strictRayCast) { - val rayCast = newRotation.rayCast(buildConfig.interactReach, eye) + val rayCast = newRotation.rayCast(buildConfig.interactReach, pov) when { - rayCast != null && (!airPlace || eye distSq rayCast.pos <= distSquared) -> + rayCast != null && (!airPlace || pov distSq rayCast.pos <= distSquared) -> rayCast.blockResult airPlace -> { - val hitVec = newRotation.castBox(box, buildConfig.interactReach, eye) + val hitVec = newRotation.castBox(box, buildConfig.interactReach, pov) BlockHitResult(hitVec, hitSide, pos, false) } else -> null } } else { - val hitVec = newRotation.castBox(box, buildConfig.interactReach, eye) + val hitVec = newRotation.castBox(box, buildConfig.interactReach, pov) BlockHitResult(hitVec, hitSide, pos, false) } ?: return@scanSurfaces @@ -177,12 +177,12 @@ class PostProcessingChecker @SimCheckerDsl private constructor(simInfo: SimInfo) if (validHits.isEmpty()) { if (misses.isNotEmpty()) { - result(GenericResult.OutOfReach(pos, eye, misses)) + result(GenericResult.OutOfReach(pos, pov, misses)) return } //ToDo: Must clean up surface scan usage / renders. Added temporary direction until changes are made - result(GenericResult.NotVisible(pos, pos, eye.distanceTo(pos.vec3d))) + result(GenericResult.NotVisible(pos, pos, pov.distanceTo(pos.vec3d))) return } From 54ee6e7fcb613325f5a89f11f6edf342b34dc10a Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Sat, 25 Oct 2025 23:56:59 +0100 Subject: [PATCH 13/30] more cleanup --- .../preprocessors/BlockHalfPreProcessor.kt | 4 +- .../preprocessors/DoorHingePreProcessor.kt | 16 +-- .../preprocessors/SlabPreProcessor.kt | 8 +- .../construction/simulation/SimChecker.kt | 77 +++++++++++ .../simulation/checks/BreakChecker.kt | 76 +--------- .../simulation/checks/PlaceChecker.kt | 122 ++++------------ .../checks/PostProcessingChecker.kt | 130 +++++------------- .../construction/verify/ScanMode.kt | 6 +- .../construction/verify/SurfaceScan.kt | 2 +- .../rotating/visibilty/VisibilityChecker.kt | 18 +-- 10 files changed, 171 insertions(+), 288 deletions(-) diff --git a/src/main/kotlin/com/lambda/interaction/construction/processing/preprocessors/BlockHalfPreProcessor.kt b/src/main/kotlin/com/lambda/interaction/construction/processing/preprocessors/BlockHalfPreProcessor.kt index ef4ea9515..4b883645f 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/processing/preprocessors/BlockHalfPreProcessor.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/processing/preprocessors/BlockHalfPreProcessor.kt @@ -37,8 +37,8 @@ object BlockHalfPreProcessor : PlacementProcessor() { val slab = state.get(Properties.BLOCK_HALF) ?: return val surfaceScan = when (slab) { - BlockHalf.BOTTOM -> SurfaceScan(ScanMode.LESSER_BLOCK_HALF, Direction.Axis.Y) - BlockHalf.TOP -> SurfaceScan(ScanMode.GREATER_BLOCK_HALF, Direction.Axis.Y) + BlockHalf.BOTTOM -> SurfaceScan(ScanMode.LesserBlockHalf, Direction.Axis.Y) + BlockHalf.TOP -> SurfaceScan(ScanMode.GreaterBlockHalf, Direction.Axis.Y) } accumulator.offerSurfaceScan(surfaceScan) diff --git a/src/main/kotlin/com/lambda/interaction/construction/processing/preprocessors/DoorHingePreProcessor.kt b/src/main/kotlin/com/lambda/interaction/construction/processing/preprocessors/DoorHingePreProcessor.kt index 07abad61d..251965755 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/processing/preprocessors/DoorHingePreProcessor.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/processing/preprocessors/DoorHingePreProcessor.kt @@ -39,19 +39,19 @@ object DoorHingePreProcessor : PlacementProcessor() { val side = state.get(Properties.DOOR_HINGE) ?: return@runSafe val scanner = when (state.get(Properties.HORIZONTAL_FACING) ?: return@runSafe) { Direction.NORTH -> - if (side == DoorHinge.LEFT) SurfaceScan(ScanMode.LESSER_BLOCK_HALF, Direction.Axis.X) - else SurfaceScan(ScanMode.GREATER_BLOCK_HALF, Direction.Axis.X) + if (side == DoorHinge.LEFT) SurfaceScan(ScanMode.LesserBlockHalf, Direction.Axis.X) + else SurfaceScan(ScanMode.GreaterBlockHalf, Direction.Axis.X) Direction.EAST -> - if (side == DoorHinge.LEFT) SurfaceScan(ScanMode.LESSER_BLOCK_HALF, Direction.Axis.Z) - else SurfaceScan(ScanMode.GREATER_BLOCK_HALF, Direction.Axis.Z) + if (side == DoorHinge.LEFT) SurfaceScan(ScanMode.LesserBlockHalf, Direction.Axis.Z) + else SurfaceScan(ScanMode.GreaterBlockHalf, Direction.Axis.Z) Direction.SOUTH -> - if (side == DoorHinge.LEFT) SurfaceScan(ScanMode.GREATER_BLOCK_HALF, Direction.Axis.X) - else SurfaceScan(ScanMode.LESSER_BLOCK_HALF, Direction.Axis.X) + if (side == DoorHinge.LEFT) SurfaceScan(ScanMode.GreaterBlockHalf, Direction.Axis.X) + else SurfaceScan(ScanMode.LesserBlockHalf, Direction.Axis.X) Direction.DOWN, Direction.UP, Direction.WEST -> - if (side == DoorHinge.LEFT) SurfaceScan(ScanMode.GREATER_BLOCK_HALF, Direction.Axis.Z) - else SurfaceScan(ScanMode.LESSER_BLOCK_HALF, Direction.Axis.Z) + if (side == DoorHinge.LEFT) SurfaceScan(ScanMode.GreaterBlockHalf, Direction.Axis.Z) + else SurfaceScan(ScanMode.LesserBlockHalf, Direction.Axis.Z) } accumulator.offerSurfaceScan(scanner) } ?: Unit diff --git a/src/main/kotlin/com/lambda/interaction/construction/processing/preprocessors/SlabPreProcessor.kt b/src/main/kotlin/com/lambda/interaction/construction/processing/preprocessors/SlabPreProcessor.kt index 08bb334ca..d1fefe8ba 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/processing/preprocessors/SlabPreProcessor.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/processing/preprocessors/SlabPreProcessor.kt @@ -40,14 +40,14 @@ object SlabPreProcessor : PlacementProcessor() { val currentState = runSafe { blockState(pos) } ?: return val surfaceScan = when (slab) { - SlabType.BOTTOM -> SurfaceScan(ScanMode.LESSER_BLOCK_HALF, Direction.Axis.Y) - SlabType.TOP -> SurfaceScan(ScanMode.GREATER_BLOCK_HALF, Direction.Axis.Y) + SlabType.BOTTOM -> SurfaceScan(ScanMode.LesserBlockHalf, Direction.Axis.Y) + SlabType.TOP -> SurfaceScan(ScanMode.GreaterBlockHalf, Direction.Axis.Y) SlabType.DOUBLE -> { accumulator.addIgnores(Properties.SLAB_TYPE) if (currentState.block !is SlabBlock) SurfaceScan.DEFAULT else when (currentState.get(Properties.SLAB_TYPE)) { - SlabType.BOTTOM -> SurfaceScan(ScanMode.GREATER_BLOCK_HALF, Direction.Axis.Y) - else -> SurfaceScan(ScanMode.LESSER_BLOCK_HALF, Direction.Axis.Y) + SlabType.BOTTOM -> SurfaceScan(ScanMode.GreaterBlockHalf, Direction.Axis.Y) + else -> SurfaceScan(ScanMode.LesserBlockHalf, Direction.Axis.Y) } } } diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/SimChecker.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/SimChecker.kt index 92f7a61ae..e1ab60fa0 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/SimChecker.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/SimChecker.kt @@ -17,9 +17,28 @@ package com.lambda.interaction.construction.simulation +import com.lambda.interaction.construction.processing.PreProcessingInfo import com.lambda.interaction.construction.result.BuildResult import com.lambda.interaction.construction.result.Dependable import com.lambda.interaction.construction.result.results.GenericResult +import com.lambda.interaction.request.rotating.Rotation.Companion.rotationTo +import com.lambda.interaction.request.rotating.visibilty.VisibilityChecker.CheckedHit +import com.lambda.interaction.request.rotating.visibilty.VisibilityChecker.getVisibleSurfaces +import com.lambda.interaction.request.rotating.visibilty.VisibilityChecker.scanSurfaces +import com.lambda.util.math.distSq +import com.lambda.util.math.vec3d +import com.lambda.util.world.raycast.RayCastUtils.blockResult +import io.ktor.util.collections.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.joinAll +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import net.minecraft.util.hit.BlockHitResult +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Direction +import net.minecraft.util.math.Vec3d +import net.minecraft.util.shape.VoxelShape +import kotlin.math.pow @DslMarker annotation class SimCheckerDsl @@ -48,4 +67,62 @@ abstract class SimChecker { } ) } + + suspend fun ISimInfo.scanShape( + pov: Vec3d, + voxelShape: VoxelShape, + pos: BlockPos, + sides: Set, + preProcessing: PreProcessingInfo + ): Set? { + val boxes = voxelShape.boundingBoxes.map { it.offset(pos) } + + val reachSq = buildConfig.interactReach.pow(2) + + val validHits = ConcurrentSet() + val misses = ConcurrentSet() + + withContext(Dispatchers.Default) { + boxes.map { box -> + launch { + val sides = if (buildConfig.checkSideVisibility || buildConfig.strictRayCast) { + sides.intersect(box.getVisibleSurfaces(pov)) + } else sides + + scanSurfaces(box, sides, buildConfig.resolution, preProcessing.surfaceScan) { side, vec -> + if (pov distSq vec > reachSq) { + misses.add(vec) + return@scanSurfaces + } + + val newRotation = pov.rotationTo(vec) + + val hit = if (buildConfig.strictRayCast) { + newRotation.rayCast(buildConfig.interactReach, pov)?.blockResult ?: return@scanSurfaces + } else { + val hitVec = newRotation.castBox(box, buildConfig.interactReach, pov) ?: return@scanSurfaces + BlockHitResult(hitVec, side, pos, false) + } + + if (hit.blockPos != pos || hit.side != side) return@scanSurfaces + val checked = CheckedHit(hit, newRotation, buildConfig.interactReach) + + validHits.add(checked) + } + } + }.joinAll() + } + + if (validHits.isEmpty()) { + if (misses.isNotEmpty()) { + result(GenericResult.OutOfReach(pos, pov, misses)) + return null + } + + result(GenericResult.NotVisible(pos, pos, pov.distanceTo(pos.vec3d))) + return null + } + + return validHits + } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecker.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecker.kt index a6e3c6ea9..167ab62f0 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecker.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecker.kt @@ -18,7 +18,6 @@ package com.lambda.interaction.construction.simulation.checks import com.lambda.context.AutomatedSafeContext -import com.lambda.context.SafeContext import com.lambda.interaction.construction.context.BreakContext import com.lambda.interaction.construction.result.BuildResult import com.lambda.interaction.construction.result.Dependable @@ -38,12 +37,8 @@ import com.lambda.interaction.material.StackSelection.Companion.select import com.lambda.interaction.material.StackSelection.Companion.selectStack import com.lambda.interaction.material.container.ContainerManager.containerWithMaterial import com.lambda.interaction.material.container.MaterialContainer -import com.lambda.interaction.request.rotating.Rotation.Companion.rotationTo import com.lambda.interaction.request.rotating.RotationManager import com.lambda.interaction.request.rotating.RotationRequest -import com.lambda.interaction.request.rotating.visibilty.VisibilityChecker.CheckedHit -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.lookAt import com.lambda.interaction.request.rotating.visibilty.lookAtBlock import com.lambda.threading.runSafe @@ -52,14 +47,7 @@ import com.lambda.util.BlockUtils.calcItemBlockBreakingDelta import com.lambda.util.BlockUtils.instantBreakable import com.lambda.util.BlockUtils.isEmpty import com.lambda.util.item.ItemStackUtils.inventoryIndexOrSelected -import com.lambda.util.math.distSq -import com.lambda.util.math.vec3d import com.lambda.util.world.raycast.RayCastUtils.blockResult -import io.ktor.util.collections.* -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.joinAll -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import net.minecraft.block.BlockState import net.minecraft.block.FallingBlock import net.minecraft.block.Waterloggable @@ -75,12 +63,9 @@ import net.minecraft.registry.tag.ItemTags.IRON_TOOL_MATERIALS import net.minecraft.registry.tag.ItemTags.NETHERITE_TOOL_MATERIALS import net.minecraft.registry.tag.ItemTags.STONE_TOOL_MATERIALS import net.minecraft.registry.tag.ItemTags.WOODEN_TOOL_MATERIALS -import net.minecraft.util.hit.BlockHitResult import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Direction -import net.minecraft.util.math.Vec3d import kotlin.jvm.optionals.getOrNull -import kotlin.math.pow class BreakChecker @SimCheckerDsl private constructor(simInfo: SimInfo) : SimChecker(), Dependable, @@ -134,14 +119,12 @@ class BreakChecker @SimCheckerDsl private constructor(simInfo: SimInfo) } private suspend fun AutomatedSafeContext.checkBreaks(): Boolean { - /* player is standing on top of the block */ if (breakConfig.avoidSupporting) player.supportingBlockPos.getOrNull()?.let { support -> if (support != pos) return@let result(BreakResult.PlayerOnTop(pos, state)) return true } - /* liquid needs to be submerged first to be broken */ if (targetState.getState(pos).isAir && !state.fluidState.isEmpty && state.isReplaceable) { result(BreakResult.Submerge(pos, state)) return simInfo(pos, state, TargetState.Solid(emptySet()))?.checkPlacements() ?: true @@ -151,10 +134,6 @@ class BreakChecker @SimCheckerDsl private constructor(simInfo: SimInfo) if (breakConfig.avoidLiquids && affectsFluids()) return true - val voxelShape = state.getOutlineShape(world, pos) - - val boxes = voxelShape.boundingBoxes.map { it.offset(pos) } - val swapStack = getSwapStack() ?: return true val instant = instantBreakable( state, pos, @@ -162,8 +141,9 @@ class BreakChecker @SimCheckerDsl private constructor(simInfo: SimInfo) breakConfig.breakThreshold ) - /* the player is buried inside the block */ - if (boxes.any { it.contains(pov) }) { + val shape = state.getOutlineShape(world, pos) + + if (shape.boundingBoxes.map { it.offset(pos) }.any { it.contains(pov) }) { val currentCast = RotationManager.activeRotation.rayCast(buildConfig.interactReach, pov) currentCast?.blockResult?.let { blockHit -> val rotationRequest = RotationRequest(lookAtBlock(pos), this) @@ -181,58 +161,14 @@ class BreakChecker @SimCheckerDsl private constructor(simInfo: SimInfo) return true } - val validHits = ConcurrentSet() - val misses = ConcurrentSet() - val reachSq = buildConfig.interactReach.pow(2) - - withContext(Dispatchers.Default) { - boxes.map { box -> - launch { - val sides = if (buildConfig.checkSideVisibility) - box.getVisibleSurfaces(pov) - else Direction.entries.toSet() - - scanSurfaces(box, sides, buildConfig.resolution) { side, vec -> - if (pov distSq vec > reachSq) { - misses.add(vec) - return@scanSurfaces - } - - val newRotation = pov.rotationTo(vec) - - val hit = if (buildConfig.strictRayCast) { - newRotation.rayCast(buildConfig.interactReach, pov)?.blockResult - } else { - val hitVec = newRotation.castBox(box, buildConfig.interactReach, pov) - BlockHitResult(hitVec, side, pos, false) - } ?: return@scanSurfaces - - if (hit.blockResult?.blockPos != pos) return@scanSurfaces - val checked = CheckedHit(hit, newRotation, buildConfig.interactReach) - - validHits.add(checked) - } - } - }.joinAll() - } - - if (validHits.isEmpty()) { - if (misses.isNotEmpty()) { - result(GenericResult.OutOfReach(pos, pov, misses)) - return true - } - - result(GenericResult.NotVisible(pos, pos, pov.distanceTo(pos.vec3d))) - return true - } + val validHits = scanShape(pov, shape, pos, Direction.entries.toSet(), preProcessing) ?: return true val bestHit = buildConfig.pointSelection.select(validHits) ?: return true - val blockHit = bestHit.hit.blockResult ?: return true val target = lookAt(bestHit.targetRotation, 0.001) val rotationRequest = RotationRequest(target, this) val breakContext = BreakContext( - blockHit, + bestHit.hit.blockResult ?: return true, rotationRequest, swapStack.inventoryIndexOrSelected, stackSelection, @@ -245,7 +181,7 @@ class BreakChecker @SimCheckerDsl private constructor(simInfo: SimInfo) return true } - private fun SafeContext.getSwapStack(): ItemStack? { + private fun AutomatedSafeContext.getSwapStack(): ItemStack? { val silentSwapSelection = selectContainer { ofAnyType(MaterialContainer.Rank.HOTBAR) } diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt index ba7ead506..b50a28211 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt @@ -18,7 +18,6 @@ package com.lambda.interaction.construction.simulation.checks import com.lambda.context.AutomatedSafeContext -import com.lambda.context.SafeContext import com.lambda.interaction.construction.context.PlaceContext import com.lambda.interaction.construction.result.BuildResult import com.lambda.interaction.construction.result.Dependable @@ -36,27 +35,20 @@ import com.lambda.interaction.material.container.ContainerManager.containerWithM import com.lambda.interaction.material.container.MaterialContainer import com.lambda.interaction.request.rotating.Rotation import com.lambda.interaction.request.rotating.Rotation.Companion.rotation -import com.lambda.interaction.request.rotating.Rotation.Companion.rotationTo import com.lambda.interaction.request.rotating.RotationManager import com.lambda.interaction.request.rotating.RotationRequest import com.lambda.interaction.request.rotating.visibilty.PlaceDirection import com.lambda.interaction.request.rotating.visibilty.VisibilityChecker.CheckedHit -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.lookAt import com.lambda.interaction.request.rotating.visibilty.lookInDirection -import com.lambda.threading.runSafeAutomated import com.lambda.util.BlockUtils import com.lambda.util.BlockUtils.blockState import com.lambda.util.item.ItemStackUtils.inventoryIndex import com.lambda.util.item.ItemUtils.blockItem -import com.lambda.util.math.distSq import com.lambda.util.math.minus -import com.lambda.util.math.vec3d import com.lambda.util.player.MovementUtils.sneaking import com.lambda.util.player.copyPlayer import com.lambda.util.world.raycast.RayCastUtils.blockResult -import io.ktor.util.collections.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.cancel @@ -69,42 +61,15 @@ import net.minecraft.block.pattern.CachedBlockPosition import net.minecraft.client.network.ClientPlayerEntity import net.minecraft.item.ItemPlacementContext import net.minecraft.item.ItemStack -import net.minecraft.item.Items import net.minecraft.util.Hand -import net.minecraft.util.hit.BlockHitResult import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Direction -import net.minecraft.util.math.Vec3d import net.minecraft.util.shape.VoxelShapes -import kotlin.math.pow class PlaceChecker @SimCheckerDsl private constructor(simInfo: SimInfo) : SimChecker(), Dependable, ISimInfo by simInfo { - private val swapStack by lazy { - runSafeAutomated { - val optimalStack = targetState.getStack(pos) - val stackSelection = optimalStack.item.select() - val containerSelection = selectContainer { ofAnyType(MaterialContainer.Rank.HOTBAR) } - val container = stackSelection.containerWithMaterial(containerSelection).firstOrNull() ?: run { - result( - GenericResult.WrongItemSelection( - pos, - optimalStack.item.select(), - player.mainHandStack - ) - ) - return@runSafeAutomated ItemStack(Items.AIR) - } - return@runSafeAutomated stackSelection.filterStacks(container.stacks).run { - firstOrNull { it.inventoryIndex == player.inventory.selectedSlot } - ?: first() - } - } ?: ItemStack(Items.AIR) - } - private val blockItem get() = swapStack.blockItem - override fun asDependent(buildResult: BuildResult) = PlaceResult.Dependency(pos, buildResult) @@ -140,17 +105,11 @@ class PlaceChecker @SimCheckerDsl private constructor(simInfo: SimInfo) if (!world.worldBorder.contains(pos)) return val testBlockState = blockState(pos) - val voxelShape = testBlockState.getOutlineShape(world, pos).let { outlineShape -> + val shape = testBlockState.getOutlineShape(world, pos).let { outlineShape -> if (!outlineShape.isEmpty || !placeConfig.airPlace.isEnabled) outlineShape else VoxelShapes.fullCube() } - if (voxelShape.isEmpty) return - - val boxes = voxelShape.boundingBoxes.map { it.offset(pos) } - - val validHits = ConcurrentSet() - val misses = ConcurrentSet() - val reachSq = buildConfig.interactReach.pow(2) + if (shape.isEmpty) return // ToDo: For each hand val fakePlayer = copyPlayer(player).apply { @@ -161,60 +120,39 @@ class PlaceChecker @SimCheckerDsl private constructor(simInfo: SimInfo) updatePose() } } - val pov = fakePlayer.eyePos - withContext(Dispatchers.Default) { - boxes.map { box -> - launch { - val sides = if (buildConfig.checkSideVisibility || buildConfig.strictRayCast) { - box.getVisibleSurfaces(pov).intersect(setOf(side)) - } else setOf(side) - - scanSurfaces(box, sides, buildConfig.resolution, preProcessing.surfaceScan) { _, vec -> - val distSquared = pov distSq vec - if (distSquared > reachSq) { - misses.add(vec) - return@scanSurfaces - } - - val newRotation = pov.rotationTo(vec) - - val hit = if (buildConfig.strictRayCast) { - newRotation.rayCast(buildConfig.interactReach, pov)?.blockResult ?: return@scanSurfaces - } else { - val hitVec = newRotation.castBox(box, buildConfig.interactReach, pov) ?: return@scanSurfaces - BlockHitResult(hitVec, side, pos, false) - } - - if (hit.blockPos != pos || hit.side != side) return@scanSurfaces - val checked = CheckedHit(hit, newRotation, buildConfig.interactReach) - - validHits.add(checked) - } - } - }.joinAll() - } - - if (validHits.isEmpty()) { - if (misses.isNotEmpty()) { - result(GenericResult.OutOfReach(pos, pov, misses)) - return - } + val validHits = scanShape(pov, shape, pos, setOf(side), preProcessing) ?: return - result(GenericResult.NotVisible(pos, pos, pov.distanceTo(pos.offset(side).vec3d))) + val swapStack = getSwapStack() ?: return + if (!swapStack.item.isEnabled(world.enabledFeatures)) { + result(PlaceResult.BlockFeatureDisabled(pos, swapStack)) + supervisorScope.cancel() return } - if (swapStack.item == Items.AIR) - supervisorScope.cancel() - else if (!swapStack.item.isEnabled(world.enabledFeatures)) { - result(PlaceResult.BlockFeatureDisabled(pos, swapStack)) - supervisorScope.cancel() - } else selectHitPos(validHits, fakePlayer) + selectHitPos(validHits, fakePlayer, swapStack) } - private fun AutomatedSafeContext.selectHitPos(validHits: Collection, fakePlayer: ClientPlayerEntity) { + private fun AutomatedSafeContext.getSwapStack(): ItemStack? { + val optimalStack = targetState.getStack(pos) + val stackSelection = optimalStack.item.select() + val containerSelection = selectContainer { ofAnyType(MaterialContainer.Rank.HOTBAR) } + val container = stackSelection.containerWithMaterial(containerSelection).firstOrNull() ?: run { + result(GenericResult.WrongItemSelection(pos, optimalStack.item.select(), player.mainHandStack)) + return null + } + return stackSelection.filterStacks(container.stacks).run { + firstOrNull { it.inventoryIndex == player.inventory.selectedSlot } + ?: firstOrNull() + } + } + + private fun AutomatedSafeContext.selectHitPos( + validHits: Collection, + fakePlayer: ClientPlayerEntity, + swapStack: ItemStack + ) { buildConfig.pointSelection.select(validHits)?.let { checkedHit -> val hitResult = checkedHit.hit.blockResult ?: return @@ -272,7 +210,7 @@ class PlaceChecker @SimCheckerDsl private constructor(simInfo: SimInfo) return } - private fun SafeContext.simRotatePlace( + private fun AutomatedSafeContext.simRotatePlace( fakePlayer: ClientPlayerEntity, checkedHit: CheckedHit, context: ItemPlacementContext @@ -300,8 +238,8 @@ class PlaceChecker @SimCheckerDsl private constructor(simInfo: SimInfo) return null } - private fun SafeContext.testPlaceState(context: ItemPlacementContext): PlaceTest { - val resultState = blockItem.getPlacementState(context) ?: run { + private fun AutomatedSafeContext.testPlaceState(context: ItemPlacementContext): PlaceTest { + val resultState = context.stack.blockItem.getPlacementState(context) ?: run { result(PlaceResult.BlockedByEntity(pos)) return PlaceTest(state, PlaceTestResult.BlockedByEntity) } diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingChecker.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingChecker.kt index 8fa51a3d0..663db0d11 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingChecker.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingChecker.kt @@ -32,23 +32,16 @@ import com.lambda.interaction.material.ContainerSelection.Companion.selectContai import com.lambda.interaction.material.StackSelection.Companion.select import com.lambda.interaction.material.container.ContainerManager.containerWithMaterial import com.lambda.interaction.material.container.MaterialContainer -import com.lambda.interaction.request.rotating.Rotation.Companion.rotationTo import com.lambda.interaction.request.rotating.RotationRequest import com.lambda.interaction.request.rotating.visibilty.VisibilityChecker.CheckedHit -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.lookAt -import com.lambda.util.math.distSq -import com.lambda.util.math.vec3d -import com.lambda.util.player.SlotUtils.hotbar +import com.lambda.util.item.ItemStackUtils.inventoryIndex import com.lambda.util.world.raycast.RayCastUtils.blockResult import net.minecraft.block.BlockState import net.minecraft.item.Item +import net.minecraft.item.ItemStack import net.minecraft.state.property.Properties -import net.minecraft.util.hit.BlockHitResult import net.minecraft.util.math.Direction -import net.minecraft.util.math.Vec3d -import kotlin.math.pow class PostProcessingChecker @SimCheckerDsl private constructor(simInfo: SimInfo) : SimChecker(), Dependable, @@ -60,14 +53,14 @@ class PostProcessingChecker @SimCheckerDsl private constructor(simInfo: SimInfo) companion object { @SimCheckerDsl context(automatedSafeContext: AutomatedSafeContext, dependable: Dependable?) - fun SimInfo.checkPostProcessing() = + suspend fun SimInfo.checkPostProcessing() = PostProcessingChecker(this).run { checkDependent(dependable) automatedSafeContext.checkPostProcessing() } } - private fun AutomatedSafeContext.checkPostProcessing(): Boolean { + private suspend fun AutomatedSafeContext.checkPostProcessing(): Boolean { val targetState = (targetState as? TargetState.State) ?: return false if (!targetState.matches(state, pos, preProcessing.ignore)) return false @@ -112,113 +105,52 @@ class PostProcessingChecker @SimCheckerDsl private constructor(simInfo: SimInfo) return true } - private fun AutomatedSafeContext.simInteraction( + private suspend fun AutomatedSafeContext.simInteraction( expectedState: BlockState, - sides: Set? = null, - item: Item? = null, - placing: Boolean = false + sides: Set = Direction.entries.toSet(), + item: Item? = null ) { - val boxes = state.getOutlineShape(world, pos).boundingBoxes.map { it.offset(pos) } - val validHits = mutableListOf() - val blockedHits = mutableSetOf() - val misses = mutableSetOf() - val airPlace = placing && placeConfig.airPlace.isEnabled - - boxes.forEach { box -> - val refinedSides = if (buildConfig.checkSideVisibility) { - box.getVisibleSurfaces(pov).let { visibleSides -> - sides?.let { specific -> - visibleSides.intersect(specific) - } ?: visibleSides.toSet() - } - } else sides ?: Direction.entries.toSet() - - scanSurfaces( - box, - refinedSides, - buildConfig.resolution, - preProcessing.surfaceScan - ) { hitSide, vec -> - val distSquared = pov distSq vec - if (distSquared > buildConfig.interactReach.pow(2)) { - misses.add(vec) - return@scanSurfaces - } + val validHits = scanShape(pov, state.getOutlineShape(world, pos), pos, sides, preProcessing) + ?: return - val newRotation = pov.rotationTo(vec) - - val hit = if (buildConfig.strictRayCast) { - val rayCast = newRotation.rayCast(buildConfig.interactReach, pov) - when { - rayCast != null && (!airPlace || pov distSq rayCast.pos <= distSquared) -> - rayCast.blockResult - - airPlace -> { - val hitVec = newRotation.castBox(box, buildConfig.interactReach, pov) - BlockHitResult(hitVec, hitSide, pos, false) - } - - else -> null - } - } else { - val hitVec = newRotation.castBox(box, buildConfig.interactReach, pov) - BlockHitResult(hitVec, hitSide, pos, false) - } ?: return@scanSurfaces - - val checked = CheckedHit(hit, newRotation, buildConfig.interactReach) - if (hit.blockResult?.blockPos != pos) { - blockedHits.add(vec) - return@scanSurfaces - } + val swapStack = getSwapStack(item ?: player.mainHandStack.item) ?: return - validHits.add(checked) - } - } + selectHit(validHits, expectedState, swapStack) + } - if (validHits.isEmpty()) { - if (misses.isNotEmpty()) { - result(GenericResult.OutOfReach(pos, pov, misses)) - return - } + private fun AutomatedSafeContext.getSwapStack(item: Item): ItemStack? { + val stackSelection = item.select() + val hotbarCandidates = selectContainer { + ofAnyType(MaterialContainer.Rank.HOTBAR) + }.let { predicate -> + stackSelection.containerWithMaterial( predicate) + } - //ToDo: Must clean up surface scan usage / renders. Added temporary direction until changes are made - result(GenericResult.NotVisible(pos, pos, pov.distanceTo(pos.vec3d))) - return + if (hotbarCandidates.isEmpty()) { + result(GenericResult.WrongItemSelection(pos, stackSelection, player.mainHandStack)) + return null } + return hotbarCandidates.first().matchingStacks(stackSelection).first() + } + + private fun AutomatedSafeContext.selectHit( + validHits: Collection, + expectedState: BlockState, + swapStack: ItemStack + ) { buildConfig.pointSelection.select(validHits)?.let { checkedHit -> val checkedResult = checkedHit.hit.blockResult ?: return val rotationTarget = lookAt(checkedHit.targetRotation, 0.001) val context = InteractionContext( checkedResult, RotationRequest(rotationTarget, this), - player.inventory.selectedSlot, + swapStack.inventoryIndex, state, expectedState, this ) - val stackSelection = (item ?: player.mainHandStack.item).select() - val hotbarCandidates = selectContainer { - matches(stackSelection) and ofAnyType(MaterialContainer.Rank.HOTBAR) - }.let { predicate -> - stackSelection.containerWithMaterial( predicate) - } - - if (hotbarCandidates.isEmpty()) { - result( - GenericResult.WrongItemSelection( - pos, - stackSelection, - player.mainHandStack - ) - ) - return - } else { - context.hotbarIndex = - player.hotbar.indexOf(hotbarCandidates.first().matchingStacks(stackSelection).first()) - } - result(InteractResult.Interact(pos, context)) } } diff --git a/src/main/kotlin/com/lambda/interaction/construction/verify/ScanMode.kt b/src/main/kotlin/com/lambda/interaction/construction/verify/ScanMode.kt index ba83a1d8d..79a02b0e6 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/verify/ScanMode.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/verify/ScanMode.kt @@ -18,7 +18,7 @@ package com.lambda.interaction.construction.verify enum class ScanMode(val priority: Int) { - GREATER_BLOCK_HALF(1), - LESSER_BLOCK_HALF(1), - FULL(0) + GreaterBlockHalf(1), + LesserBlockHalf(1), + Full(0) } diff --git a/src/main/kotlin/com/lambda/interaction/construction/verify/SurfaceScan.kt b/src/main/kotlin/com/lambda/interaction/construction/verify/SurfaceScan.kt index c8d9d507e..4a0a79d4a 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/verify/SurfaceScan.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/verify/SurfaceScan.kt @@ -24,6 +24,6 @@ data class SurfaceScan( val axis: Direction.Axis, ) { companion object { - val DEFAULT = SurfaceScan(ScanMode.FULL, Direction.Axis.Y) + val DEFAULT = SurfaceScan(ScanMode.Full, Direction.Axis.Y) } } diff --git a/src/main/kotlin/com/lambda/interaction/request/rotating/visibilty/VisibilityChecker.kt b/src/main/kotlin/com/lambda/interaction/request/rotating/visibilty/VisibilityChecker.kt index 1dbbea5f4..684f4650a 100644 --- a/src/main/kotlin/com/lambda/interaction/request/rotating/visibilty/VisibilityChecker.kt +++ b/src/main/kotlin/com/lambda/interaction/request/rotating/visibilty/VisibilityChecker.kt @@ -176,43 +176,43 @@ object VisibilityChecker { // Determine the bounds to scan based on the axis and mode. Skip if no part of the face is in the desired bounds val (startX, endX) = if (scan.axis == Direction.Axis.X && maxX != minX) { when (scan.mode) { - ScanMode.GREATER_BLOCK_HALF -> (floor(minX) + 0.501).let { center -> + ScanMode.GreaterBlockHalf -> (floor(minX) + 0.501).let { center -> if (maxX < center) return@forEach minX.coerceAtLeast(center) to maxX } - ScanMode.LESSER_BLOCK_HALF -> (floor(maxX) + 0.499).let { center -> + ScanMode.LesserBlockHalf -> (floor(maxX) + 0.499).let { center -> if (minX > center) return@forEach minX to maxX.coerceAtMost(center) } - ScanMode.FULL -> minX to maxX + ScanMode.Full -> minX to maxX } } else minX to maxX val (startY, endY) = if (scan.axis == Direction.Axis.Y && maxY != minY) { when (scan.mode) { - ScanMode.GREATER_BLOCK_HALF -> (floor(minY) + 0.501).let { center -> + ScanMode.GreaterBlockHalf -> (floor(minY) + 0.501).let { center -> if (maxY < center) return@forEach minY.coerceAtLeast(center) to maxY } - ScanMode.LESSER_BLOCK_HALF -> (floor(maxY) + 0.499).let { center -> + ScanMode.LesserBlockHalf -> (floor(maxY) + 0.499).let { center -> if (minY > center) return@forEach minY to maxY.coerceAtMost(center) } - ScanMode.FULL -> minY to maxY + ScanMode.Full -> minY to maxY } } else minY to maxY val (startZ, endZ) = if (scan.axis == Direction.Axis.Z && maxZ != minZ) { when (scan.mode) { - ScanMode.GREATER_BLOCK_HALF -> (floor(minZ) + 0.501).let { center -> + ScanMode.GreaterBlockHalf -> (floor(minZ) + 0.501).let { center -> if (maxZ < center) return@forEach minZ.coerceAtLeast(center) to maxZ } - ScanMode.LESSER_BLOCK_HALF -> (floor(maxZ) + 0.499).let { center -> + ScanMode.LesserBlockHalf -> (floor(maxZ) + 0.499).let { center -> if (minZ > center) return@forEach minZ to maxZ.coerceAtMost(center) } - ScanMode.FULL -> minZ to maxZ + ScanMode.Full -> minZ to maxZ } } else minZ to maxZ From 3d7a602b39d0c568e5df0608ee2300bd2cde8794 Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Sun, 26 Oct 2025 10:37:09 +0000 Subject: [PATCH 14/30] fix cave air edge case in isEmpty check --- .../construction/simulation/checks/BreakChecker.kt | 4 ++-- src/main/kotlin/com/lambda/util/BlockUtils.kt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecker.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecker.kt index 167ab62f0..c957a8331 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecker.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecker.kt @@ -119,6 +119,8 @@ class BreakChecker @SimCheckerDsl private constructor(simInfo: SimInfo) } private suspend fun AutomatedSafeContext.checkBreaks(): Boolean { + if (state.isEmpty) return false + if (breakConfig.avoidSupporting) player.supportingBlockPos.getOrNull()?.let { support -> if (support != pos) return@let result(BreakResult.PlayerOnTop(pos, state)) @@ -130,8 +132,6 @@ class BreakChecker @SimCheckerDsl private constructor(simInfo: SimInfo) return simInfo(pos, state, TargetState.Solid(emptySet()))?.checkPlacements() ?: true } - if (state.isEmpty) return false - if (breakConfig.avoidLiquids && affectsFluids()) return true val swapStack = getSwapStack() ?: return true diff --git a/src/main/kotlin/com/lambda/util/BlockUtils.kt b/src/main/kotlin/com/lambda/util/BlockUtils.kt index 0423c8e6d..a500dce1e 100644 --- a/src/main/kotlin/com/lambda/util/BlockUtils.kt +++ b/src/main/kotlin/com/lambda/util/BlockUtils.kt @@ -305,7 +305,7 @@ object BlockUtils { return speedMultiplier } - val BlockState.isEmpty get() = matches(emptyState) + val BlockState.isEmpty get() = isAir || matches(emptyState) val BlockState.isNotEmpty get() = !isEmpty val BlockState.hasFluid get() = !fluidState.isEmpty val BlockState.emptyState: BlockState get() = fluidState.blockState From 14080b0d74da5fcf6a5922f510fae4d9fbde0df6 Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Sun, 26 Oct 2025 11:09:41 +0000 Subject: [PATCH 15/30] sim checker dsl marker on checkRequirements check --- .../construction/simulation/checks/RequirementChecks.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/RequirementChecks.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/RequirementChecks.kt index e50a1dee3..95854a93e 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/RequirementChecks.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/RequirementChecks.kt @@ -21,6 +21,7 @@ import com.lambda.context.AutomatedSafeContext import com.lambda.interaction.construction.result.results.GenericResult import com.lambda.interaction.construction.result.results.PreSimResult import com.lambda.interaction.construction.simulation.SimChecker +import com.lambda.interaction.construction.simulation.SimCheckerDsl import com.lambda.interaction.construction.simulation.SimInfo import com.lambda.interaction.construction.verify.TargetState import com.lambda.util.player.gamemode @@ -28,6 +29,7 @@ import com.lambda.util.world.WorldUtils.isLoaded import net.minecraft.block.OperatorBlock object RequirementChecks : SimChecker() { + @SimCheckerDsl context(automatedSafeContext: AutomatedSafeContext) fun SimInfo.checkRequirements(): Boolean = with(automatedSafeContext) { // the chunk is not loaded From f4605a2a8edb3a906478865ced2b789a788bd8c9 Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Sun, 26 Oct 2025 13:45:43 +0000 Subject: [PATCH 16/30] efficient tools only setting for blocks that could be broken faster with tools. They will only allow those tools --- .../com/lambda/config/groups/BreakSettings.kt | 1 + .../processing/ProcessorRegistry.kt | 3 +- .../simulation/checks/BreakChecker.kt | 110 +++++++++--------- .../interaction/material/StackSelection.kt | 12 ++ .../request/breaking/BreakConfig.kt | 1 + .../lambda/module/modules/player/FastBreak.kt | 1 + .../module/modules/player/PacketMine.kt | 7 +- 7 files changed, 77 insertions(+), 58 deletions(-) diff --git a/src/main/kotlin/com/lambda/config/groups/BreakSettings.kt b/src/main/kotlin/com/lambda/config/groups/BreakSettings.kt index 05da95f10..c4a630a7a 100644 --- a/src/main/kotlin/com/lambda/config/groups/BreakSettings.kt +++ b/src/main/kotlin/com/lambda/config/groups/BreakSettings.kt @@ -79,6 +79,7 @@ class BreakSettings( override val ignoredBlocks by c.setting("Ignored Blocks", allSigns, description = "Blocks that wont be broken", visibility = vis).group(baseGroup, Group.General) // Tool + override val efficientOnly by c.setting("Efficient Tools Only", true, "Only use tools suitable for the given block (will get the item drop)") { vis() && swapMode.isEnabled() }.group(baseGroup, Group.General) override val suitableToolsOnly by c.setting("Suitable Tools Only", true, "Only use tools suitable for the given block (will get the item drop)") { vis() && swapMode.isEnabled() }.group(baseGroup, Group.General) override val forceSilkTouch by c.setting("Force Silk Touch", false, "Force silk touch when breaking blocks") { vis() && swapMode.isEnabled() }.group(baseGroup, Group.General) override val forceFortunePickaxe by c.setting("Force Fortune Pickaxe", false, "Force fortune pickaxe when breaking blocks") { vis() && swapMode.isEnabled() }.group(baseGroup, Group.General) diff --git a/src/main/kotlin/com/lambda/interaction/construction/processing/ProcessorRegistry.kt b/src/main/kotlin/com/lambda/interaction/construction/processing/ProcessorRegistry.kt index c966e6526..96138babb 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/processing/ProcessorRegistry.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/processing/ProcessorRegistry.kt @@ -23,10 +23,11 @@ import com.lambda.util.reflections.getInstances import net.minecraft.block.BlockState import net.minecraft.state.property.Properties import net.minecraft.util.math.BlockPos +import java.util.* object ProcessorRegistry : Loadable { private val processors = getInstances() - private val processorCache = mutableMapOf() + private val processorCache = Collections.synchronizedMap(mutableMapOf()) val postProcessedProperties = setOf( Properties.EXTENDED, diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecker.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecker.kt index c957a8331..3e9d4b209 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecker.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecker.kt @@ -33,19 +33,19 @@ import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.material.ContainerSelection.Companion.selectContainer import com.lambda.interaction.material.StackSelection import com.lambda.interaction.material.StackSelection.Companion.EVERYTHING -import com.lambda.interaction.material.StackSelection.Companion.select import com.lambda.interaction.material.StackSelection.Companion.selectStack import com.lambda.interaction.material.container.ContainerManager.containerWithMaterial import com.lambda.interaction.material.container.MaterialContainer +import com.lambda.interaction.request.hotbar.HotbarManager import com.lambda.interaction.request.rotating.RotationManager import com.lambda.interaction.request.rotating.RotationRequest import com.lambda.interaction.request.rotating.visibilty.lookAt import com.lambda.interaction.request.rotating.visibilty.lookAtBlock -import com.lambda.threading.runSafe import com.lambda.util.BlockUtils.blockState import com.lambda.util.BlockUtils.calcItemBlockBreakingDelta import com.lambda.util.BlockUtils.instantBreakable import com.lambda.util.BlockUtils.isEmpty +import com.lambda.util.item.ItemStackUtils.inventoryIndex import com.lambda.util.item.ItemStackUtils.inventoryIndexOrSelected import com.lambda.util.world.raycast.RayCastUtils.blockResult import net.minecraft.block.BlockState @@ -71,40 +71,6 @@ class BreakChecker @SimCheckerDsl private constructor(simInfo: SimInfo) : SimChecker(), Dependable, ISimInfo by simInfo { - val stackSelection: StackSelection by lazy { - runSafe { - selectStack( - count = 0, - sorter = compareByDescending { - it.canBreak(CachedBlockPosition(world, pos, false)) - }.thenByDescending { - state.calcItemBlockBreakingDelta(pos, it) - } - ) { - EVERYTHING - .andIf(breakConfig.suitableToolsOnly) { - isSuitableForBreaking(state) - }.andIf(breakConfig.forceSilkTouch) { - hasEnchantment(Enchantments.SILK_TOUCH) - }.andIf(breakConfig.forceFortunePickaxe) { - hasEnchantment(Enchantments.FORTUNE) - }.andIf(!breakConfig.useWoodenTools) { - hasTag(WOODEN_TOOL_MATERIALS).not() - }.andIf(!breakConfig.useStoneTools) { - hasTag(STONE_TOOL_MATERIALS).not() - }.andIf(!breakConfig.useIronTools) { - hasTag(IRON_TOOL_MATERIALS).not() - }.andIf(!breakConfig.useDiamondTools) { - hasTag(DIAMOND_TOOL_MATERIALS).not() - }.andIf(!breakConfig.useGoldTools) { - hasTag(GOLD_TOOL_MATERIALS).not() - }.andIf(!breakConfig.useNetheriteTools) { - hasTag(NETHERITE_TOOL_MATERIALS).not() - } - } - } ?: EVERYTHING.select() - } - override fun asDependent(buildResult: BuildResult) = BreakResult.Dependency(pos, buildResult) @@ -134,7 +100,7 @@ class BreakChecker @SimCheckerDsl private constructor(simInfo: SimInfo) if (breakConfig.avoidLiquids && affectsFluids()) return true - val swapStack = getSwapStack() ?: return true + val (swapStack, stackSelection) = getSwapStack() ?: return true val instant = instantBreakable( state, pos, if (breakConfig.swapMode.isEnabled()) swapStack else player.mainHandStack, @@ -181,35 +147,67 @@ class BreakChecker @SimCheckerDsl private constructor(simInfo: SimInfo) return true } - private fun AutomatedSafeContext.getSwapStack(): ItemStack? { + private fun AutomatedSafeContext.getSwapStack(): Pair? { + val stackSelection = selectStack( + count = 0, + sorter = compareByDescending { + it.canBreak(CachedBlockPosition(world, pos, false)) + }.thenByDescending { + state.calcItemBlockBreakingDelta(pos, it) + }.thenByDescending { + it.inventoryIndex == HotbarManager.serverSlot + } + ) { + EVERYTHING + .andIf(breakConfig.efficientOnly) { + isEfficientForBreaking(state) + }.andIf(breakConfig.suitableToolsOnly) { + isSuitableForBreaking(state) + }.andIf(breakConfig.forceSilkTouch) { + hasEnchantment(Enchantments.SILK_TOUCH) + }.andIf(breakConfig.forceFortunePickaxe) { + hasEnchantment(Enchantments.FORTUNE) + }.andIf(!breakConfig.useWoodenTools) { + hasTag(WOODEN_TOOL_MATERIALS).not() + }.andIf(!breakConfig.useStoneTools) { + hasTag(STONE_TOOL_MATERIALS).not() + }.andIf(!breakConfig.useIronTools) { + hasTag(IRON_TOOL_MATERIALS).not() + }.andIf(!breakConfig.useDiamondTools) { + hasTag(DIAMOND_TOOL_MATERIALS).not() + }.andIf(!breakConfig.useGoldTools) { + hasTag(GOLD_TOOL_MATERIALS).not() + }.andIf(!breakConfig.useNetheriteTools) { + hasTag(NETHERITE_TOOL_MATERIALS).not() + } + } + val silentSwapSelection = selectContainer { ofAnyType(MaterialContainer.Rank.HOTBAR) } - val swapCandidates = stackSelection.containerWithMaterial(silentSwapSelection) + val swapCandidates = stackSelection + .containerWithMaterial(silentSwapSelection) + .map { it.matchingStacks(stackSelection) } + .flatten() if (swapCandidates.isEmpty()) { result(GenericResult.WrongItemSelection(pos, stackSelection, player.mainHandStack)) return null } - return swapCandidates - .map { it.matchingStacks(stackSelection) } - .asSequence() - .flatten() - .let { containerStacks -> - var bestStack = ItemStack.EMPTY - var bestBreakDelta = -1f - containerStacks.forEach { stack -> - val breakDelta = state.calcItemBlockBreakingDelta(pos, stack) - if (breakDelta > bestBreakDelta || - (stack == player.mainHandStack && breakDelta >= bestBreakDelta) - ) { - bestBreakDelta = breakDelta - bestStack = stack - } - } - bestStack + var bestStack = ItemStack.EMPTY + var bestBreakDelta = -1f + swapCandidates.forEach { stack -> + val breakDelta = state.calcItemBlockBreakingDelta(pos, stack) + if (breakDelta > bestBreakDelta || + (stack == player.mainHandStack && breakDelta >= bestBreakDelta) + ) { + bestBreakDelta = breakDelta + bestStack = stack } + } + return if (bestBreakDelta == -1f) null + else Pair(bestStack, stackSelection) } private suspend fun AutomatedSafeContext.affectsFluids(): Boolean { diff --git a/src/main/kotlin/com/lambda/interaction/material/StackSelection.kt b/src/main/kotlin/com/lambda/interaction/material/StackSelection.kt index 3090dc00e..9c7845015 100644 --- a/src/main/kotlin/com/lambda/interaction/material/StackSelection.kt +++ b/src/main/kotlin/com/lambda/interaction/material/StackSelection.kt @@ -20,6 +20,7 @@ package com.lambda.interaction.material import com.lambda.interaction.material.StackSelection.Companion.StackSelectionDsl import com.lambda.util.EnchantmentUtils.getEnchantment import com.lambda.util.item.ItemStackUtils.shulkerBoxContents +import com.lambda.util.item.ItemUtils import net.minecraft.block.Block import net.minecraft.block.BlockState import net.minecraft.component.ComponentType @@ -31,6 +32,7 @@ import net.minecraft.item.consume.UseAction import net.minecraft.registry.RegistryKey import net.minecraft.registry.tag.TagKey import net.minecraft.screen.slot.Slot +import java.util.* import kotlin.reflect.KClass /** @@ -159,6 +161,14 @@ class StackSelection { */ fun isOneOfStacks(stacks: Collection): (ItemStack) -> Boolean = stacks::contains + fun isEfficientForBreaking(blockState: BlockState): (ItemStack) -> Boolean = { itemStack -> + val hasEfficientTool = efficientToolCache.getOrPut(blockState) { + ItemUtils.tools.any { it.getMiningSpeed(it.defaultStack, blockState) > 1f } + } + if (hasEfficientTool) itemStack.item.getMiningSpeed(itemStack, blockState) > 1f + else false + } + fun isSuitableForBreaking(blockState: BlockState): (ItemStack) -> Boolean = { it.isSuitableFor(blockState) } fun hasTag(tag: TagKey): (ItemStack) -> Boolean = { it.isIn(tag) } @@ -275,6 +285,8 @@ class StackSelection { val NOTHING: (ItemStack) -> Boolean = { false } val NO_COMPARE: Comparator = Comparator { _, _ -> 0 } + val efficientToolCache: MutableMap = Collections.synchronizedMap(mutableMapOf()) + @StackSelectionDsl fun Item.select() = selectStack { isItem(this@select) } diff --git a/src/main/kotlin/com/lambda/interaction/request/breaking/BreakConfig.kt b/src/main/kotlin/com/lambda/interaction/request/breaking/BreakConfig.kt index 454a3b655..c46df9408 100644 --- a/src/main/kotlin/com/lambda/interaction/request/breaking/BreakConfig.kt +++ b/src/main/kotlin/com/lambda/interaction/request/breaking/BreakConfig.kt @@ -57,6 +57,7 @@ interface BreakConfig : RequestConfig { val avoidSupporting: Boolean val ignoredBlocks: Set + val efficientOnly: Boolean val suitableToolsOnly: Boolean val forceSilkTouch: Boolean val forceFortunePickaxe: Boolean diff --git a/src/main/kotlin/com/lambda/module/modules/player/FastBreak.kt b/src/main/kotlin/com/lambda/module/modules/player/FastBreak.kt index 7eb709d70..8081c0215 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/FastBreak.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/FastBreak.kt @@ -66,6 +66,7 @@ object FastBreak : Module( editTyped( ::avoidLiquids, ::avoidSupporting, + ::efficientOnly, ::suitableToolsOnly, ::rotateForBreak, ::doubleBreak diff --git a/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt b/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt index 8baa5fc49..1d0df02dc 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt @@ -79,7 +79,12 @@ object PacketMine : Module( private val endColor by setting("End Color", Color(255, 0, 0, 60).brighter(), "The color of the end (farthest from breaking) of the queue") { renderQueue && dynamicColor }.group(Group.Break, BreakSettings.Group.Cosmetic) override val breakConfig = BreakSettings(this, Group.Break).apply { - editTyped(::avoidLiquids, ::avoidSupporting, ::suitableToolsOnly) { defaultValue(false) } + editTyped( + ::avoidLiquids, + ::avoidSupporting, + ::efficientOnly, + ::suitableToolsOnly + ) { defaultValue(false) } ::swing.edit { defaultValue(BreakConfig.SwingMode.Start) } ::rebreak.insert(::rebreakMode, SettingGroup.InsertMode.Below) From 7d7cbc37a367790e5f2291013e25c2be5a5c66dc Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Sun, 26 Oct 2025 15:56:35 +0000 Subject: [PATCH 17/30] isEmpty check after fluid checks in break checker --- .../construction/simulation/checks/BreakChecker.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecker.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecker.kt index 3e9d4b209..de7541245 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecker.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecker.kt @@ -85,8 +85,6 @@ class BreakChecker @SimCheckerDsl private constructor(simInfo: SimInfo) } private suspend fun AutomatedSafeContext.checkBreaks(): Boolean { - if (state.isEmpty) return false - if (breakConfig.avoidSupporting) player.supportingBlockPos.getOrNull()?.let { support -> if (support != pos) return@let result(BreakResult.PlayerOnTop(pos, state)) @@ -98,6 +96,8 @@ class BreakChecker @SimCheckerDsl private constructor(simInfo: SimInfo) return simInfo(pos, state, TargetState.Solid(emptySet()))?.checkPlacements() ?: true } + if (state.isEmpty) return false + if (breakConfig.avoidLiquids && affectsFluids()) return true val (swapStack, stackSelection) = getSwapStack() ?: return true From ea3e64871987b825200c3d3242c783aa23d68bd9 Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Sun, 26 Oct 2025 19:08:18 +0000 Subject: [PATCH 18/30] isToolRequired check in isSuitableForBreaking predicate --- .../kotlin/com/lambda/interaction/material/StackSelection.kt | 5 ++--- src/main/kotlin/com/lambda/util/EnchantmentUtils.kt | 1 - 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/com/lambda/interaction/material/StackSelection.kt b/src/main/kotlin/com/lambda/interaction/material/StackSelection.kt index 9c7845015..869c7678b 100644 --- a/src/main/kotlin/com/lambda/interaction/material/StackSelection.kt +++ b/src/main/kotlin/com/lambda/interaction/material/StackSelection.kt @@ -58,8 +58,7 @@ class StackSelection { /** * Filters the given [stacks], sorts them with the [comparator] and returns the first value */ - fun bestItemMatch(stacks: List): ItemStack? = - filterStacks(stacks).firstOrNull() + fun bestItemMatch(stacks: List): ItemStack? = filterStacks(stacks).firstOrNull() fun matches(stack: ItemStack): Boolean = filterStack(stack) @@ -169,7 +168,7 @@ class StackSelection { else false } - fun isSuitableForBreaking(blockState: BlockState): (ItemStack) -> Boolean = { it.isSuitableFor(blockState) } + fun isSuitableForBreaking(blockState: BlockState): (ItemStack) -> Boolean = { !blockState.isToolRequired || it.isSuitableFor(blockState) } fun hasTag(tag: TagKey): (ItemStack) -> Boolean = { it.isIn(tag) } diff --git a/src/main/kotlin/com/lambda/util/EnchantmentUtils.kt b/src/main/kotlin/com/lambda/util/EnchantmentUtils.kt index f1af411aa..5853abde8 100644 --- a/src/main/kotlin/com/lambda/util/EnchantmentUtils.kt +++ b/src/main/kotlin/com/lambda/util/EnchantmentUtils.kt @@ -49,7 +49,6 @@ object EnchantmentUtils { ?.intValue ?: 0 - /** * Iterates over all the enchantments for the given [ItemStack] */ From ecbd48cdef30a61c7c504bdb73f18e588d576940 Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Tue, 28 Oct 2025 10:40:53 +0000 Subject: [PATCH 19/30] use supervisor scopes' feature to only return after all child jobs are done --- .../construction/simulation/BuildSimulator.kt | 57 ++++++++++--------- .../construction/simulation/SimChecker.kt | 10 ++-- .../simulation/checks/PlaceChecker.kt | 29 ++++------ .../checks/PostProcessingChecker.kt | 4 +- 4 files changed, 49 insertions(+), 51 deletions(-) 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 12d3e822b..011c3a1df 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt @@ -29,40 +29,45 @@ import com.lambda.interaction.construction.simulation.checks.RequirementChecks.c import com.lambda.util.BlockUtils.blockState import io.ktor.util.collections.* import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.joinAll import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.supervisorScope import net.minecraft.util.math.Vec3d object BuildSimulator : SimChecker() { context(automatedSafeContext: AutomatedSafeContext) - fun Blueprint.simulate(pov: Vec3d = automatedSafeContext.player.eyePos): Set = runBlocking(Dispatchers.Default) { - val concurrentSet = ConcurrentSet() - with(automatedSafeContext) { - structure.entries - .map { (pos, targetState) -> - launch { - val simInfo = simInfo( - pos, - blockState(pos), - targetState, - pov, - concurrentSet - ) ?: return@launch - with(simInfo) { - with(null) { - if (checkRequirements() || - checkPostProcessing() || - checkBreaks() || - checkPlacements()) return@launch - else result(PostSimResult.NoMatch(pos)) + fun Blueprint.simulate( + pov: Vec3d = automatedSafeContext.player.eyePos + ): Set = + runBlocking(Dispatchers.Default) { + supervisorScope { + val concurrentSet = ConcurrentSet() + + with(automatedSafeContext) { + structure.entries.forEach { (pos, targetState) -> + launch { + val simInfo = simInfo( + pos, + blockState(pos), + targetState, + pov, + concurrentSet + ) ?: return@launch + + with(simInfo) { + with(null) { + if (checkRequirements() || + checkPostProcessing() || + checkBreaks() || + checkPlacements()) return@launch + else result(PostSimResult.NoMatch(pos)) + } } } } - }.joinAll() - } + } - return@runBlocking concurrentSet - } + concurrentSet + } + } } - diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/SimChecker.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/SimChecker.kt index e1ab60fa0..69b043468 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/SimChecker.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/SimChecker.kt @@ -29,10 +29,8 @@ import com.lambda.util.math.distSq import com.lambda.util.math.vec3d import com.lambda.util.world.raycast.RayCastUtils.blockResult import io.ktor.util.collections.* -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.joinAll import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext +import kotlinx.coroutines.supervisorScope import net.minecraft.util.hit.BlockHitResult import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Direction @@ -82,8 +80,8 @@ abstract class SimChecker { val validHits = ConcurrentSet() val misses = ConcurrentSet() - withContext(Dispatchers.Default) { - boxes.map { box -> + supervisorScope { + boxes.forEach { box -> launch { val sides = if (buildConfig.checkSideVisibility || buildConfig.strictRayCast) { sides.intersect(box.getVisibleSurfaces(pov)) @@ -110,7 +108,7 @@ abstract class SimChecker { validHits.add(checked) } } - }.joinAll() + } } if (validHits.isEmpty()) { diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt index b50a28211..a54f54412 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt @@ -50,12 +50,9 @@ import com.lambda.util.player.MovementUtils.sneaking import com.lambda.util.player.copyPlayer import com.lambda.util.world.raycast.RayCastUtils.blockResult import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.cancel -import kotlinx.coroutines.joinAll import kotlinx.coroutines.launch import kotlinx.coroutines.supervisorScope -import kotlinx.coroutines.withContext import net.minecraft.block.BlockState import net.minecraft.block.pattern.CachedBlockPosition import net.minecraft.client.network.ClientPlayerEntity @@ -83,24 +80,20 @@ class PlaceChecker @SimCheckerDsl private constructor(simInfo: SimInfo) } } - private suspend fun AutomatedSafeContext.checkPlacements(): Boolean { + private suspend fun AutomatedSafeContext.checkPlacements(): Boolean = supervisorScope { - withContext(Dispatchers.Default) { - preProcessing.sides.map { side -> - launch { - val neighborPos = pos.offset(side) - val neighborSide = side.opposite - if (!placeConfig.airPlace.isEnabled) - testBlock(neighborPos, neighborSide, this@supervisorScope) - testBlock(pos, side, this@supervisorScope) - } - }.joinAll() + preProcessing.sides.forEach { side -> + launch { + val neighborPos = pos.offset(side) + val neighborSide = side.opposite + if (!placeConfig.airPlace.isEnabled) + testBlock(neighborPos, neighborSide, this@supervisorScope) + testBlock(pos, side, this@supervisorScope) + } } + true } - return true - } - private suspend fun AutomatedSafeContext.testBlock(pos: BlockPos, side: Direction, supervisorScope: CoroutineScope) { if (!world.worldBorder.contains(pos)) return @@ -240,7 +233,7 @@ class PlaceChecker @SimCheckerDsl private constructor(simInfo: SimInfo) private fun AutomatedSafeContext.testPlaceState(context: ItemPlacementContext): PlaceTest { val resultState = context.stack.blockItem.getPlacementState(context) ?: run { - result(PlaceResult.BlockedByEntity(pos)) + result(PlaceResult.BlockedByEntity(pos, emptyList())) return PlaceTest(state, PlaceTestResult.BlockedByEntity) } diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingChecker.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingChecker.kt index 663db0d11..61b2b95fb 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingChecker.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingChecker.kt @@ -24,9 +24,11 @@ import com.lambda.interaction.construction.result.Dependable import com.lambda.interaction.construction.result.results.GenericResult import com.lambda.interaction.construction.result.results.InteractResult import com.lambda.interaction.construction.simulation.ISimInfo +import com.lambda.interaction.construction.simulation.ISimInfo.Companion.simInfo import com.lambda.interaction.construction.simulation.SimChecker import com.lambda.interaction.construction.simulation.SimCheckerDsl import com.lambda.interaction.construction.simulation.SimInfo +import com.lambda.interaction.construction.simulation.checks.PlaceChecker.Companion.checkPlacements import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.material.ContainerSelection.Companion.selectContainer import com.lambda.interaction.material.StackSelection.Companion.select @@ -98,7 +100,7 @@ class PostProcessingChecker @SimCheckerDsl private constructor(simInfo: SimInfo) simInteraction(expectedState) } - Properties.SLAB_TYPE -> return false + Properties.SLAB_TYPE -> simInfo()?.checkPlacements() } } From a31c8df11489d9dd5d3c66ef46755f911a8a9cf8 Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Tue, 28 Oct 2025 13:32:17 +0000 Subject: [PATCH 20/30] improved entity spleef block position detection --- .../processing/ProcessorRegistry.kt | 5 +- .../simulation/checks/PlaceChecker.kt | 55 ++++++++++++++++--- .../kotlin/com/lambda/util/EntityUtils.kt | 41 ++++++++++++++ 3 files changed, 91 insertions(+), 10 deletions(-) create mode 100644 src/main/kotlin/com/lambda/util/EntityUtils.kt diff --git a/src/main/kotlin/com/lambda/interaction/construction/processing/ProcessorRegistry.kt b/src/main/kotlin/com/lambda/interaction/construction/processing/ProcessorRegistry.kt index 96138babb..1d219c4d6 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/processing/ProcessorRegistry.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/processing/ProcessorRegistry.kt @@ -106,7 +106,7 @@ object ProcessorRegistry : Loadable { override fun load() = "Loaded ${processors.size} pre processors" - fun TargetState.getProcessingInfo(pos: BlockPos) = + fun TargetState.getProcessingInfo(pos: BlockPos): PreProcessingInfo? = if (this !is TargetState.State) PreProcessingInfo.DEFAULT else { val get: () -> PreProcessingInfo? = get@{ @@ -115,9 +115,8 @@ object ProcessorRegistry : Loadable { processors.forEach { processor -> if (!processor.acceptsState(blockState)) return@forEach processor.preProcess(blockState, pos, infoAccumulator) - if (infoAccumulator.shouldBeOmitted) { + if (infoAccumulator.shouldBeOmitted) return@get null - } } infoAccumulator.complete() diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt index a54f54412..63f10f457 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt @@ -24,9 +24,11 @@ import com.lambda.interaction.construction.result.Dependable import com.lambda.interaction.construction.result.results.GenericResult import com.lambda.interaction.construction.result.results.PlaceResult import com.lambda.interaction.construction.simulation.ISimInfo +import com.lambda.interaction.construction.simulation.ISimInfo.Companion.simInfo import com.lambda.interaction.construction.simulation.SimChecker import com.lambda.interaction.construction.simulation.SimCheckerDsl import com.lambda.interaction.construction.simulation.SimInfo +import com.lambda.interaction.construction.simulation.checks.BreakChecker.Companion.checkBreaks import com.lambda.interaction.construction.simulation.checks.PlaceChecker.RotatePlaceTest.Companion.rotatePlaceTest import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.material.ContainerSelection.Companion.selectContainer @@ -43,8 +45,10 @@ import com.lambda.interaction.request.rotating.visibilty.lookAt import com.lambda.interaction.request.rotating.visibilty.lookInDirection import com.lambda.util.BlockUtils import com.lambda.util.BlockUtils.blockState +import com.lambda.util.EntityUtils.getPositionsWithinHitboxXZ import com.lambda.util.item.ItemStackUtils.inventoryIndex import com.lambda.util.item.ItemUtils.blockItem +import com.lambda.util.math.MathUtils.floorToInt import com.lambda.util.math.minus import com.lambda.util.player.MovementUtils.sneaking import com.lambda.util.player.copyPlayer @@ -54,8 +58,10 @@ import kotlinx.coroutines.cancel import kotlinx.coroutines.launch import kotlinx.coroutines.supervisorScope import net.minecraft.block.BlockState +import net.minecraft.block.ShapeContext import net.minecraft.block.pattern.CachedBlockPosition import net.minecraft.client.network.ClientPlayerEntity +import net.minecraft.entity.Entity import net.minecraft.item.ItemPlacementContext import net.minecraft.item.ItemStack import net.minecraft.util.Hand @@ -141,7 +147,7 @@ class PlaceChecker @SimCheckerDsl private constructor(simInfo: SimInfo) } } - private fun AutomatedSafeContext.selectHitPos( + private suspend fun AutomatedSafeContext.selectHitPos( validHits: Collection, fakePlayer: ClientPlayerEntity, swapStack: ItemStack @@ -203,7 +209,7 @@ class PlaceChecker @SimCheckerDsl private constructor(simInfo: SimInfo) return } - private fun AutomatedSafeContext.simRotatePlace( + private suspend fun AutomatedSafeContext.simRotatePlace( fakePlayer: ClientPlayerEntity, checkedHit: CheckedHit, context: ItemPlacementContext @@ -231,11 +237,13 @@ class PlaceChecker @SimCheckerDsl private constructor(simInfo: SimInfo) return null } - private fun AutomatedSafeContext.testPlaceState(context: ItemPlacementContext): PlaceTest { - val resultState = context.stack.blockItem.getPlacementState(context) ?: run { - result(PlaceResult.BlockedByEntity(pos, emptyList())) - return PlaceTest(state, PlaceTestResult.BlockedByEntity) - } + private suspend fun AutomatedSafeContext.testPlaceState(context: ItemPlacementContext): PlaceTest { + val resultState = context.stack.blockItem.getPlacementState(context) + ?: run { + val blockingEntities = handleEntityBlockage(context) + result(PlaceResult.BlockedByEntity(pos, blockingEntities)) + return PlaceTest(state, PlaceTestResult.BlockedByEntity) + } return if (!targetState.matches(resultState, pos, preProcessing.ignore)) { result(PlaceResult.NoIntegrity(pos, resultState, context, (targetState as? TargetState.State)?.blockState)) @@ -243,6 +251,39 @@ class PlaceChecker @SimCheckerDsl private constructor(simInfo: SimInfo) } else PlaceTest(resultState, PlaceTestResult.Success) } + private suspend fun AutomatedSafeContext.handleEntityBlockage(context: ItemPlacementContext): List { + val theoreticalState = context.stack.blockItem.block.getPlacementState(context) + ?: return emptyList() + + val collisionShape = theoreticalState.getCollisionShape( + world, + context.blockPos, + ShapeContext.ofPlacement(player) + ).offset(context.blockPos) + + val collidingEntities = collisionShape.boundingBoxes.flatMap { box -> + world.entities.filter { it.boundingBox.intersects(box) } + } + + if (collidingEntities.isNotEmpty()) { + collidingEntities + .mapNotNull { entity -> + val hitbox = entity.boundingBox + entity.getPositionsWithinHitboxXZ( + (pos.y - (hitbox.maxY - hitbox.minY)).floorToInt(), + pos.y + ) + } + .flatten() + .forEach { support -> + simInfo(support, blockState(support), TargetState.Empty)?.checkBreaks() + } + result(PlaceResult.BlockedByEntity(pos, collidingEntities)) + } + + return collidingEntities + } + private class RotatePlaceTest private constructor( val resultState: BlockState, val currentDirIsValid: Boolean, diff --git a/src/main/kotlin/com/lambda/util/EntityUtils.kt b/src/main/kotlin/com/lambda/util/EntityUtils.kt new file mode 100644 index 000000000..ebecce7da --- /dev/null +++ b/src/main/kotlin/com/lambda/util/EntityUtils.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.util + +import com.lambda.util.math.MathUtils.floorToInt +import net.minecraft.entity.Entity +import net.minecraft.util.math.BlockPos + +object EntityUtils { + fun Entity.getPositionsWithinHitboxXZ(minY: Int, maxY: Int): Set { + val hitbox = boundingBox + val minX = hitbox.minX.floorToInt() + val maxX = hitbox.maxX.floorToInt() + val minZ = hitbox.minZ.floorToInt() + val maxZ = hitbox.maxZ.floorToInt() + val positions = mutableSetOf() + (minX..maxX).forEach { x -> + (minY..maxY).forEach { y -> + (minZ..maxZ).forEach { z -> + positions.add(BlockPos(x, y, z)) + } + } + } + return positions + } +} \ No newline at end of file From 88961723c3de814d9eee4f430b4cd498a61e3fc4 Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Tue, 28 Oct 2025 13:34:41 +0000 Subject: [PATCH 21/30] remove BlockedBySelf build result --- .../construction/result/results/PlaceResult.kt | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/main/kotlin/com/lambda/interaction/construction/result/results/PlaceResult.kt b/src/main/kotlin/com/lambda/interaction/construction/result/results/PlaceResult.kt index 941ac599c..d8bc1b25d 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/result/results/PlaceResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/result/results/PlaceResult.kt @@ -97,23 +97,6 @@ sealed class PlaceResult : BuildResult() { } } - /** - * Represents a scenario where block placement is obstructed by the player itself. - * - * @property blockPos The position of the block that was attempted to be placed. - */ - data class BlockedBySelf( - override val pos: BlockPos - ) : Drawable, Navigable, PlaceResult() { - override val rank = Rank.PlaceBlockedByEntity - private val color = Color(252, 3, 3, 100) - override val goal = GoalInverted(GoalBlock(pos)) - - override fun ShapeBuilder.buildRenderer() { - box(pos, color, color) - } - } - /** * Represents a scenario where block placement is obstructed by an entity. * From 6d09f1ed6dec5868238d9a96e5c398ca968ec108 Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Thu, 30 Oct 2025 00:30:11 +0000 Subject: [PATCH 22/30] dsl oriented sim structure. Im not sure if i like how many objects are created but i guess this is a clean looking solution we can potentially improve performance on after the logic is smoothed --- .../construction/simulation/BuildSimulator.kt | 52 ++++++++----------- .../construction/simulation/ISimInfo.kt | 52 ++++++++++++++----- .../construction/simulation/SimChecker.kt | 1 - .../checks/{BreakChecker.kt => BreakSim.kt} | 42 +++++++-------- .../checks/{PlaceChecker.kt => PlaceSim.kt} | 27 +++++----- ...cessingChecker.kt => PostProcessingSim.kt} | 26 ++++------ ...irementChecks.kt => RequirementChecker.kt} | 22 ++++---- 7 files changed, 117 insertions(+), 105 deletions(-) rename src/main/kotlin/com/lambda/interaction/construction/simulation/checks/{BreakChecker.kt => BreakSim.kt} (92%) rename src/main/kotlin/com/lambda/interaction/construction/simulation/checks/{PlaceChecker.kt => PlaceSim.kt} (94%) rename src/main/kotlin/com/lambda/interaction/construction/simulation/checks/{PostProcessingChecker.kt => PostProcessingSim.kt} (91%) rename src/main/kotlin/com/lambda/interaction/construction/simulation/checks/{RequirementChecks.kt => RequirementChecker.kt} (88%) 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 011c3a1df..883d764d7 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt @@ -21,11 +21,11 @@ import com.lambda.context.AutomatedSafeContext import com.lambda.interaction.construction.blueprint.Blueprint import com.lambda.interaction.construction.result.BuildResult import com.lambda.interaction.construction.result.results.PostSimResult -import com.lambda.interaction.construction.simulation.ISimInfo.Companion.simInfo -import com.lambda.interaction.construction.simulation.checks.BreakChecker.Companion.checkBreaks -import com.lambda.interaction.construction.simulation.checks.PlaceChecker.Companion.checkPlacements -import com.lambda.interaction.construction.simulation.checks.PostProcessingChecker.Companion.checkPostProcessing -import com.lambda.interaction.construction.simulation.checks.RequirementChecks.checkRequirements +import com.lambda.interaction.construction.simulation.ISimInfo.Companion.sim +import com.lambda.interaction.construction.simulation.checks.BreakSim.Companion.simBreak +import com.lambda.interaction.construction.simulation.checks.PlaceSim.Companion.simPlacement +import com.lambda.interaction.construction.simulation.checks.PostProcessingSim.Companion.simPostProcessing +import com.lambda.interaction.construction.verify.TargetState import com.lambda.util.BlockUtils.blockState import io.ktor.util.collections.* import kotlinx.coroutines.Dispatchers @@ -38,36 +38,28 @@ object BuildSimulator : SimChecker() { context(automatedSafeContext: AutomatedSafeContext) fun Blueprint.simulate( pov: Vec3d = automatedSafeContext.player.eyePos - ): Set = - runBlocking(Dispatchers.Default) { - supervisorScope { - val concurrentSet = ConcurrentSet() + ): Set = runBlocking(Dispatchers.Default) { + supervisorScope { + val concurrentSet = ConcurrentSet() - with(automatedSafeContext) { - structure.entries.forEach { (pos, targetState) -> - launch { - val simInfo = simInfo( - pos, - blockState(pos), - targetState, - pov, - concurrentSet - ) ?: return@launch - - with(simInfo) { - with(null) { - if (checkRequirements() || - checkPostProcessing() || - checkBreaks() || - checkPlacements()) return@launch - else result(PostSimResult.NoMatch(pos)) - } + with(automatedSafeContext) { + structure.forEach { (pos, targetState) -> + launch { + sim(pos, blockState(pos), targetState, pov, concurrentSet) { + if (targetState is TargetState.State && + targetState.matches(state, pos, preProcessing.ignore) + ) { + simPostProcessing() + return@sim } + if (!targetState.isEmpty() && state.isReplaceable) simPlacement() + else simBreak() } } } - - concurrentSet } + + concurrentSet } + } } diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/ISimInfo.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/ISimInfo.kt index 3181733d5..99daeecaf 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/ISimInfo.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/ISimInfo.kt @@ -18,16 +18,21 @@ package com.lambda.interaction.construction.simulation import com.lambda.context.Automated +import com.lambda.context.AutomatedSafeContext import com.lambda.interaction.construction.processing.PreProcessingInfo import com.lambda.interaction.construction.processing.ProcessorRegistry.getProcessingInfo import com.lambda.interaction.construction.result.BuildResult import com.lambda.interaction.construction.result.Dependable +import com.lambda.interaction.construction.simulation.checks.RequirementChecker.checkRequirements import com.lambda.interaction.construction.verify.TargetState import net.minecraft.block.BlockState import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Vec3d import java.util.* +@DslMarker +annotation class SimBuilderDsl + interface ISimInfo : Automated { val pos: BlockPos val state: BlockState @@ -38,32 +43,53 @@ interface ISimInfo : Automated { val dependencyStack: Stack companion object { - @SimCheckerDsl - fun Automated.simInfo( + @SimBuilderDsl + private suspend fun ISimInfo.sim( + dependable: Dependable?, + simBuilder: suspend context(Dependable?) SimBuilder.() -> Unit + ) = SimBuilder(this).run { with(dependable) { simBuilder() } } + + @SimBuilderDsl + suspend fun AutomatedSafeContext.sim( pos: BlockPos, state: BlockState, targetState: TargetState, pov: Vec3d, concurrentResults: MutableSet, - ): SimInfo? { - val preProcessingInfo = targetState.getProcessingInfo(pos) ?: return null - return SimInfo(pos, state, targetState, preProcessingInfo, pov, concurrentResults, this) + simBuilder: suspend context(Dependable?) SimBuilder.() -> Unit + ) { + SimInfo( + pos, state, targetState, + targetState.getProcessingInfo(pos) ?: return, + pov, concurrentResults, this + ).takeIf { it.checkRequirements() }?.sim(null, simBuilder) } - @SimCheckerDsl - fun ISimInfo.simInfo( + @SimBuilderDsl + context(_: AutomatedSafeContext, dependable: Dependable) + suspend fun ISimInfo.sim( pos: BlockPos = this.pos, state: BlockState = this.state, targetState: TargetState = this.targetState, pov: Vec3d = this.pov, - concurrentResults: MutableSet = this.concurrentResults - ): SimInfo? { - val preProcessingInfo = targetState.getProcessingInfo(pos) ?: return null - return SimInfo(pos, state, targetState, preProcessingInfo, pov, concurrentResults, this) + simBuilder: suspend context(Dependable?) SimBuilder.() -> Unit + ) { + SimInfo( + pos, state, targetState, + targetState.getProcessingInfo(pos) ?: return, + pov, concurrentResults, this + ).takeIf { it.checkRequirements() }?.sim(dependable, simBuilder) } + + @SimBuilderDsl + context(dependable: Dependable) + suspend fun ISimInfo.sim( + simBuilder: suspend context(Dependable?) SimBuilder.() -> Unit + ) = sim(dependable, simBuilder) } } +@SimBuilderDsl data class SimInfo( override val pos: BlockPos, override val state: BlockState, @@ -74,4 +100,6 @@ data class SimInfo( val automated: Automated ) : ISimInfo, Automated by automated { override val dependencyStack = Stack() -} \ No newline at end of file +} + +class SimBuilder(simInfo: ISimInfo) : ISimInfo by simInfo \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/SimChecker.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/SimChecker.kt index 69b043468..0f26975b6 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/SimChecker.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/SimChecker.kt @@ -52,7 +52,6 @@ abstract class SimChecker { } fun ISimInfo.result(result: GenericResult) = addResult(result) - fun ISimInfo.result(result: T) = addResult(result) private fun ISimInfo.addResult(result: BuildResult) { diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecker.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt similarity index 92% rename from src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecker.kt rename to src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt index de7541245..148138006 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakChecker.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt @@ -24,11 +24,11 @@ import com.lambda.interaction.construction.result.Dependable import com.lambda.interaction.construction.result.results.BreakResult import com.lambda.interaction.construction.result.results.GenericResult import com.lambda.interaction.construction.simulation.ISimInfo -import com.lambda.interaction.construction.simulation.ISimInfo.Companion.simInfo +import com.lambda.interaction.construction.simulation.ISimInfo.Companion.sim +import com.lambda.interaction.construction.simulation.SimBuilder +import com.lambda.interaction.construction.simulation.SimBuilderDsl import com.lambda.interaction.construction.simulation.SimChecker -import com.lambda.interaction.construction.simulation.SimCheckerDsl -import com.lambda.interaction.construction.simulation.SimInfo -import com.lambda.interaction.construction.simulation.checks.PlaceChecker.Companion.checkPlacements +import com.lambda.interaction.construction.simulation.checks.PlaceSim.Companion.simPlacement import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.material.ContainerSelection.Companion.selectContainer import com.lambda.interaction.material.StackSelection @@ -44,7 +44,6 @@ import com.lambda.interaction.request.rotating.visibilty.lookAtBlock import com.lambda.util.BlockUtils.blockState import com.lambda.util.BlockUtils.calcItemBlockBreakingDelta import com.lambda.util.BlockUtils.instantBreakable -import com.lambda.util.BlockUtils.isEmpty import com.lambda.util.item.ItemStackUtils.inventoryIndex import com.lambda.util.item.ItemStackUtils.inventoryIndexOrSelected import com.lambda.util.world.raycast.RayCastUtils.blockResult @@ -67,7 +66,7 @@ import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Direction import kotlin.jvm.optionals.getOrNull -class BreakChecker @SimCheckerDsl private constructor(simInfo: SimInfo) +class BreakSim private constructor(simInfo: ISimInfo) : SimChecker(), Dependable, ISimInfo by simInfo { @@ -75,32 +74,31 @@ class BreakChecker @SimCheckerDsl private constructor(simInfo: SimInfo) BreakResult.Dependency(pos, buildResult) companion object { - @SimCheckerDsl + @SimBuilderDsl context(automatedSafeContext: AutomatedSafeContext, dependable: Dependable?) - suspend fun SimInfo.checkBreaks() = - BreakChecker(this).run { + suspend fun SimBuilder.simBreak() = + BreakSim(this).run { checkDependent(dependable) automatedSafeContext.checkBreaks() } } - private suspend fun AutomatedSafeContext.checkBreaks(): Boolean { + private suspend fun AutomatedSafeContext.checkBreaks() { if (breakConfig.avoidSupporting) player.supportingBlockPos.getOrNull()?.let { support -> if (support != pos) return@let result(BreakResult.PlayerOnTop(pos, state)) - return true + return } if (targetState.getState(pos).isAir && !state.fluidState.isEmpty && state.isReplaceable) { result(BreakResult.Submerge(pos, state)) - return simInfo(pos, state, TargetState.Solid(emptySet()))?.checkPlacements() ?: true + sim(pos, state, TargetState.Solid(emptySet())) { simPlacement() } + return } - if (state.isEmpty) return false - - if (breakConfig.avoidLiquids && affectsFluids()) return true + if (breakConfig.avoidLiquids && affectsFluids()) return - val (swapStack, stackSelection) = getSwapStack() ?: return true + val (swapStack, stackSelection) = getSwapStack() ?: return val instant = instantBreakable( state, pos, if (breakConfig.swapMode.isEnabled()) swapStack else player.mainHandStack, @@ -124,17 +122,17 @@ class BreakChecker @SimCheckerDsl private constructor(simInfo: SimInfo) ) result(BreakResult.Break(pos, breakContext)) } - return true + return } - val validHits = scanShape(pov, shape, pos, Direction.entries.toSet(), preProcessing) ?: return true + val validHits = scanShape(pov, shape, pos, Direction.entries.toSet(), preProcessing) ?: return - val bestHit = buildConfig.pointSelection.select(validHits) ?: return true + val bestHit = buildConfig.pointSelection.select(validHits) ?: return val target = lookAt(bestHit.targetRotation, 0.001) val rotationRequest = RotationRequest(target, this) val breakContext = BreakContext( - bestHit.hit.blockResult ?: return true, + bestHit.hit.blockResult ?: return, rotationRequest, swapStack.inventoryIndexOrSelected, stackSelection, @@ -144,7 +142,7 @@ class BreakChecker @SimCheckerDsl private constructor(simInfo: SimInfo) ) result(BreakResult.Break(pos, breakContext)) - return true + return } private fun AutomatedSafeContext.getSwapStack(): Pair? { @@ -276,7 +274,7 @@ class BreakChecker @SimCheckerDsl private constructor(simInfo: SimInfo) affectedFluids.forEach { (liquidPos, liquidState) -> result(BreakResult.Submerge(liquidPos, liquidState)) - simInfo(liquidPos, liquidState, TargetState.Solid(emptySet()))?.checkPlacements() + sim(liquidPos, liquidState, TargetState.Solid(emptySet())) { simPlacement() } } result(BreakResult.BlockedByFluid(pos, state)) return true diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceSim.kt similarity index 94% rename from src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt rename to src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceSim.kt index 63f10f457..a9e8c5264 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceChecker.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceSim.kt @@ -24,12 +24,12 @@ import com.lambda.interaction.construction.result.Dependable import com.lambda.interaction.construction.result.results.GenericResult import com.lambda.interaction.construction.result.results.PlaceResult import com.lambda.interaction.construction.simulation.ISimInfo -import com.lambda.interaction.construction.simulation.ISimInfo.Companion.simInfo +import com.lambda.interaction.construction.simulation.ISimInfo.Companion.sim +import com.lambda.interaction.construction.simulation.SimBuilder +import com.lambda.interaction.construction.simulation.SimBuilderDsl import com.lambda.interaction.construction.simulation.SimChecker -import com.lambda.interaction.construction.simulation.SimCheckerDsl -import com.lambda.interaction.construction.simulation.SimInfo -import com.lambda.interaction.construction.simulation.checks.BreakChecker.Companion.checkBreaks -import com.lambda.interaction.construction.simulation.checks.PlaceChecker.RotatePlaceTest.Companion.rotatePlaceTest +import com.lambda.interaction.construction.simulation.checks.BreakSim.Companion.simBreak +import com.lambda.interaction.construction.simulation.checks.PlaceSim.RotatePlaceTest.Companion.rotatePlaceTest import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.material.ContainerSelection.Companion.selectContainer import com.lambda.interaction.material.StackSelection.Companion.select @@ -69,7 +69,7 @@ import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Direction import net.minecraft.util.shape.VoxelShapes -class PlaceChecker @SimCheckerDsl private constructor(simInfo: SimInfo) +class PlaceSim private constructor(simInfo: ISimInfo) : SimChecker(), Dependable, ISimInfo by simInfo { @@ -77,16 +77,16 @@ class PlaceChecker @SimCheckerDsl private constructor(simInfo: SimInfo) PlaceResult.Dependency(pos, buildResult) companion object { - @SimCheckerDsl context(automatedSafeContext: AutomatedSafeContext, dependable: Dependable?) - suspend fun SimInfo.checkPlacements() = - PlaceChecker(this).run { + @SimBuilderDsl + suspend fun SimBuilder.simPlacement() = + PlaceSim(this).run { checkDependent(dependable) automatedSafeContext.checkPlacements() } } - private suspend fun AutomatedSafeContext.checkPlacements(): Boolean = + private suspend fun AutomatedSafeContext.checkPlacements() = supervisorScope { preProcessing.sides.forEach { side -> launch { @@ -97,7 +97,6 @@ class PlaceChecker @SimCheckerDsl private constructor(simInfo: SimInfo) testBlock(pos, side, this@supervisorScope) } } - true } private suspend fun AutomatedSafeContext.testBlock(pos: BlockPos, side: Direction, supervisorScope: CoroutineScope) { @@ -192,7 +191,7 @@ class PlaceChecker @SimCheckerDsl private constructor(simInfo: SimInfo) val placeContext = PlaceContext( hitResult, - RotationRequest(rotationRequest, this@PlaceChecker), + RotationRequest(rotationRequest, this@PlaceSim), swapStack.inventoryIndex, pos, state, @@ -200,7 +199,7 @@ class PlaceChecker @SimCheckerDsl private constructor(simInfo: SimInfo) fakePlayer.isSneaking, false, rotatePlaceTest.currentDirIsValid, - this@PlaceChecker + this@PlaceSim ) result(PlaceResult.Place(pos, placeContext)) @@ -276,7 +275,7 @@ class PlaceChecker @SimCheckerDsl private constructor(simInfo: SimInfo) } .flatten() .forEach { support -> - simInfo(support, blockState(support), TargetState.Empty)?.checkBreaks() + sim(support, blockState(support), TargetState.Empty) { simBreak() } } result(PlaceResult.BlockedByEntity(pos, collidingEntities)) } diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingChecker.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingSim.kt similarity index 91% rename from src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingChecker.kt rename to src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingSim.kt index 61b2b95fb..89efd5492 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingChecker.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingSim.kt @@ -24,11 +24,11 @@ import com.lambda.interaction.construction.result.Dependable import com.lambda.interaction.construction.result.results.GenericResult import com.lambda.interaction.construction.result.results.InteractResult import com.lambda.interaction.construction.simulation.ISimInfo -import com.lambda.interaction.construction.simulation.ISimInfo.Companion.simInfo +import com.lambda.interaction.construction.simulation.ISimInfo.Companion.sim +import com.lambda.interaction.construction.simulation.SimBuilder +import com.lambda.interaction.construction.simulation.SimBuilderDsl import com.lambda.interaction.construction.simulation.SimChecker -import com.lambda.interaction.construction.simulation.SimCheckerDsl -import com.lambda.interaction.construction.simulation.SimInfo -import com.lambda.interaction.construction.simulation.checks.PlaceChecker.Companion.checkPlacements +import com.lambda.interaction.construction.simulation.checks.PlaceSim.Companion.simPlacement import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.material.ContainerSelection.Companion.selectContainer import com.lambda.interaction.material.StackSelection.Companion.select @@ -45,7 +45,7 @@ import net.minecraft.item.ItemStack import net.minecraft.state.property.Properties import net.minecraft.util.math.Direction -class PostProcessingChecker @SimCheckerDsl private constructor(simInfo: SimInfo) +class PostProcessingSim private constructor(simInfo: ISimInfo) : SimChecker(), Dependable, ISimInfo by simInfo { @@ -53,19 +53,17 @@ class PostProcessingChecker @SimCheckerDsl private constructor(simInfo: SimInfo) InteractResult.Dependency(pos, buildResult) companion object { - @SimCheckerDsl context(automatedSafeContext: AutomatedSafeContext, dependable: Dependable?) - suspend fun SimInfo.checkPostProcessing() = - PostProcessingChecker(this).run { + @SimBuilderDsl + suspend fun SimBuilder.simPostProcessing() = + PostProcessingSim(this).run { checkDependent(dependable) automatedSafeContext.checkPostProcessing() } } - private suspend fun AutomatedSafeContext.checkPostProcessing(): Boolean { - val targetState = (targetState as? TargetState.State) ?: return false - - if (!targetState.matches(state, pos, preProcessing.ignore)) return false + private suspend fun AutomatedSafeContext.checkPostProcessing() { + val targetState = (targetState as? TargetState.State) ?: return val mismatchedProperties = state.properties.filter { state.get(it) != targetState.blockState.get(it) } mismatchedProperties.forEach { property -> @@ -100,11 +98,9 @@ class PostProcessingChecker @SimCheckerDsl private constructor(simInfo: SimInfo) simInteraction(expectedState) } - Properties.SLAB_TYPE -> simInfo()?.checkPlacements() + Properties.SLAB_TYPE -> sim { simPlacement() } } } - - return true } private suspend fun AutomatedSafeContext.simInteraction( diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/RequirementChecks.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/RequirementChecker.kt similarity index 88% rename from src/main/kotlin/com/lambda/interaction/construction/simulation/checks/RequirementChecks.kt rename to src/main/kotlin/com/lambda/interaction/construction/simulation/checks/RequirementChecker.kt index 95854a93e..b1c2d41d6 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/RequirementChecks.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/RequirementChecker.kt @@ -20,60 +20,60 @@ package com.lambda.interaction.construction.simulation.checks import com.lambda.context.AutomatedSafeContext import com.lambda.interaction.construction.result.results.GenericResult import com.lambda.interaction.construction.result.results.PreSimResult +import com.lambda.interaction.construction.simulation.SimBuilderDsl import com.lambda.interaction.construction.simulation.SimChecker -import com.lambda.interaction.construction.simulation.SimCheckerDsl import com.lambda.interaction.construction.simulation.SimInfo import com.lambda.interaction.construction.verify.TargetState import com.lambda.util.player.gamemode import com.lambda.util.world.WorldUtils.isLoaded import net.minecraft.block.OperatorBlock -object RequirementChecks : SimChecker() { - @SimCheckerDsl +object RequirementChecker : SimChecker() { + @SimBuilderDsl context(automatedSafeContext: AutomatedSafeContext) fun SimInfo.checkRequirements(): Boolean = with(automatedSafeContext) { // the chunk is not loaded if (!isLoaded(pos)) { result(PreSimResult.ChunkNotLoaded(pos)) - return true + return false } // block is already in the correct state if (targetState.matches(state, pos)) { result(PreSimResult.Done(pos)) - return true + return false } // block should be ignored if (state.block in breakConfig.ignoredBlocks && targetState.type == TargetState.Type.Air) { result(GenericResult.Ignored(pos)) - return true + return false } // the player is in the wrong game mode to alter the block state if (player.isBlockBreakingRestricted(world, pos, gamemode)) { result(PreSimResult.Restricted(pos)) - return true + return false } // the player has no permissions to alter the block state if (state.block is OperatorBlock && !player.isCreativeLevelTwoOp) { result(PreSimResult.NoPermission(pos, state)) - return true + return false } // block is outside the world so it cant be altered if (!world.worldBorder.contains(pos) || world.isOutOfHeightLimit(pos)) { result(PreSimResult.OutOfWorld(pos)) - return true + return false } // block is unbreakable, so it cant be broken or replaced if (state.getHardness(world, pos) < 0 && !gamemode.isCreative) { result(PreSimResult.Unbreakable(pos, state)) - return true + return false } - return false + return true } } \ No newline at end of file From ebd0d3eca72a8478b79dea98d614a15e6527e69b Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Thu, 30 Oct 2025 13:25:15 +0000 Subject: [PATCH 23/30] max dependency depth and reintroduce blocked by player build result --- .../com/lambda/context/AutomationConfig.kt | 1 + .../result/results/PlaceResult.kt | 20 ++++++++++++++++--- .../construction/simulation/ISimInfo.kt | 7 ++++--- .../construction/simulation/SimChecker.kt | 7 +++++-- .../simulation/checks/BreakSim.kt | 2 +- .../simulation/checks/PlaceSim.kt | 13 +++++++----- .../simulation/checks/PostProcessingSim.kt | 2 +- 7 files changed, 37 insertions(+), 15 deletions(-) diff --git a/src/main/kotlin/com/lambda/context/AutomationConfig.kt b/src/main/kotlin/com/lambda/context/AutomationConfig.kt index 03f4f20b2..369e175e4 100644 --- a/src/main/kotlin/com/lambda/context/AutomationConfig.kt +++ b/src/main/kotlin/com/lambda/context/AutomationConfig.kt @@ -62,6 +62,7 @@ object AutomationConfig : Configurable(LambdaConfig), Automated { val showAllEntries by setting("Show All Entries", false, "Show all entries in the task tree").group(Group.Debug) val shrinkFactor by setting("Shrink Factor", 0.001, 0.0..1.0, 0.001).group(Group.Debug) val ignoreItemDropWarnings by setting("Ignore Drop Warnings", false, "Hides the item drop warnings from the break manager").group(Group.Debug) + val maxSimDependencies by setting("Max Sim Dependencies", 10, 0..25, 1, "Maximum dependency build results").group(Group.Debug) @Volatile var drawables = listOf() diff --git a/src/main/kotlin/com/lambda/interaction/construction/result/results/PlaceResult.kt b/src/main/kotlin/com/lambda/interaction/construction/result/results/PlaceResult.kt index d8bc1b25d..fada08453 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/result/results/PlaceResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/result/results/PlaceResult.kt @@ -22,8 +22,6 @@ import baritone.api.pathing.goals.GoalInverted import com.lambda.context.Automated import com.lambda.graphics.renderer.esp.ShapeBuilder import com.lambda.interaction.construction.context.PlaceContext -import com.lambda.interaction.construction.verify.TargetState -import com.lambda.task.Task import com.lambda.interaction.construction.result.BuildResult import com.lambda.interaction.construction.result.ComparableResult import com.lambda.interaction.construction.result.Contextual @@ -33,7 +31,6 @@ import com.lambda.interaction.construction.result.Navigable import com.lambda.interaction.construction.result.Rank import com.lambda.interaction.construction.result.Resolvable import com.lambda.task.tasks.BuildTask.Companion.breakBlock -import com.lambda.task.tasks.BuildTask.Companion.build import net.minecraft.block.BlockState import net.minecraft.entity.Entity import net.minecraft.item.ItemPlacementContext @@ -97,6 +94,23 @@ sealed class PlaceResult : BuildResult() { } } + /** + * Represents a scenario where block placement is obstructed by the player itself. + * + * @property pos The position of the block that was attempted to be placed. + */ + data class BlockedBySelf( + override val pos: BlockPos + ) : Drawable, Navigable, PlaceResult() { + override val rank = Rank.PlaceBlockedByPlayer + private val color = Color(252, 3, 3, 100) + override val goal = GoalInverted(GoalBlock(pos)) + + override fun ShapeBuilder.buildRenderer() { + box(pos, color, color) + } + } + /** * Represents a scenario where block placement is obstructed by an entity. * diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/ISimInfo.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/ISimInfo.kt index 99daeecaf..91593208d 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/ISimInfo.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/ISimInfo.kt @@ -61,7 +61,7 @@ interface ISimInfo : Automated { SimInfo( pos, state, targetState, targetState.getProcessingInfo(pos) ?: return, - pov, concurrentResults, this + pov, concurrentResults, Stack(), this ).takeIf { it.checkRequirements() }?.sim(null, simBuilder) } @@ -77,7 +77,7 @@ interface ISimInfo : Automated { SimInfo( pos, state, targetState, targetState.getProcessingInfo(pos) ?: return, - pov, concurrentResults, this + pov, concurrentResults, dependencyStack, this ).takeIf { it.checkRequirements() }?.sim(dependable, simBuilder) } @@ -97,9 +97,10 @@ data class SimInfo( override val preProcessing: PreProcessingInfo, override val pov: Vec3d, override val concurrentResults: MutableSet, + override val dependencyStack: Stack, val automated: Automated ) : ISimInfo, Automated by automated { - override val dependencyStack = Stack() + } class SimBuilder(simInfo: ISimInfo) : ISimInfo by simInfo \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/SimChecker.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/SimChecker.kt index 0f26975b6..bdb87ac4e 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/SimChecker.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/SimChecker.kt @@ -17,6 +17,7 @@ package com.lambda.interaction.construction.simulation +import com.lambda.context.AutomationConfig.maxSimDependencies import com.lambda.interaction.construction.processing.PreProcessingInfo import com.lambda.interaction.construction.result.BuildResult import com.lambda.interaction.construction.result.Dependable @@ -43,12 +44,14 @@ annotation class SimCheckerDsl @SimCheckerDsl abstract class SimChecker { - protected fun ISimInfo.checkDependent(caller: Dependable?) { + protected fun ISimInfo.checkDependent(caller: Dependable?): Boolean { if (caller == null) { dependencyStack.clear() - return + return true } + if (dependencyStack.size >= maxSimDependencies) return false dependencyStack.push(caller) + return true } fun ISimInfo.result(result: GenericResult) = addResult(result) diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt index 148138006..5eaf18375 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt @@ -78,7 +78,7 @@ class BreakSim private constructor(simInfo: ISimInfo) context(automatedSafeContext: AutomatedSafeContext, dependable: Dependable?) suspend fun SimBuilder.simBreak() = BreakSim(this).run { - checkDependent(dependable) + if (!checkDependent(dependable)) return automatedSafeContext.checkBreaks() } } diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceSim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceSim.kt index a9e8c5264..deb3e8ee2 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceSim.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceSim.kt @@ -81,7 +81,7 @@ class PlaceSim private constructor(simInfo: ISimInfo) @SimBuilderDsl suspend fun SimBuilder.simPlacement() = PlaceSim(this).run { - checkDependent(dependable) + if (!checkDependent(dependable)) return automatedSafeContext.checkPlacements() } } @@ -251,14 +251,13 @@ class PlaceSim private constructor(simInfo: ISimInfo) } private suspend fun AutomatedSafeContext.handleEntityBlockage(context: ItemPlacementContext): List { + val pos = context.blockPos val theoreticalState = context.stack.blockItem.block.getPlacementState(context) ?: return emptyList() val collisionShape = theoreticalState.getCollisionShape( - world, - context.blockPos, - ShapeContext.ofPlacement(player) - ).offset(context.blockPos) + world, pos, ShapeContext.ofPlacement(player) + ).offset(pos) val collidingEntities = collisionShape.boundingBoxes.flatMap { box -> world.entities.filter { it.boundingBox.intersects(box) } @@ -267,6 +266,10 @@ class PlaceSim private constructor(simInfo: ISimInfo) if (collidingEntities.isNotEmpty()) { collidingEntities .mapNotNull { entity -> + if (entity === player) { + result(PlaceResult.BlockedBySelf(pos)) + return@mapNotNull null + } val hitbox = entity.boundingBox entity.getPositionsWithinHitboxXZ( (pos.y - (hitbox.maxY - hitbox.minY)).floorToInt(), diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingSim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingSim.kt index 89efd5492..f703dc0cb 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingSim.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingSim.kt @@ -57,7 +57,7 @@ class PostProcessingSim private constructor(simInfo: ISimInfo) @SimBuilderDsl suspend fun SimBuilder.simPostProcessing() = PostProcessingSim(this).run { - checkDependent(dependable) + if (!checkDependent(dependable)) return automatedSafeContext.checkPostProcessing() } } From 70d1b71267b4d57efb2dbb9e435e9f98f2f7099d Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Thu, 30 Oct 2025 14:43:50 +0000 Subject: [PATCH 24/30] tiny cleanup --- .../lambda/interaction/construction/simulation/ISimInfo.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/ISimInfo.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/ISimInfo.kt index 91593208d..e253feead 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/ISimInfo.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/ISimInfo.kt @@ -98,9 +98,7 @@ data class SimInfo( override val pov: Vec3d, override val concurrentResults: MutableSet, override val dependencyStack: Stack, - val automated: Automated -) : ISimInfo, Automated by automated { - -} + private val automated: Automated +) : ISimInfo, Automated by automated class SimBuilder(simInfo: ISimInfo) : ISimInfo by simInfo \ No newline at end of file From b59ffe86a676f511896ce06bdd8cdba1c5b3afb3 Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Thu, 30 Oct 2025 22:46:03 +0000 Subject: [PATCH 25/30] dependency tweaks --- .../construction/simulation/ISimInfo.kt | 6 +++--- .../construction/simulation/SimChecker.kt | 14 +++++++------- .../construction/simulation/checks/BreakSim.kt | 7 ++++--- .../construction/simulation/checks/PlaceSim.kt | 7 ++++--- .../simulation/checks/PostProcessingSim.kt | 7 ++++--- 5 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/ISimInfo.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/ISimInfo.kt index e253feead..9d35e30d6 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/ISimInfo.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/ISimInfo.kt @@ -61,7 +61,7 @@ interface ISimInfo : Automated { SimInfo( pos, state, targetState, targetState.getProcessingInfo(pos) ?: return, - pov, concurrentResults, Stack(), this + pov, Stack(), concurrentResults, this ).takeIf { it.checkRequirements() }?.sim(null, simBuilder) } @@ -77,7 +77,7 @@ interface ISimInfo : Automated { SimInfo( pos, state, targetState, targetState.getProcessingInfo(pos) ?: return, - pov, concurrentResults, dependencyStack, this + pov, dependencyStack, concurrentResults, this ).takeIf { it.checkRequirements() }?.sim(dependable, simBuilder) } @@ -96,8 +96,8 @@ data class SimInfo( override val targetState: TargetState, override val preProcessing: PreProcessingInfo, override val pov: Vec3d, - override val concurrentResults: MutableSet, override val dependencyStack: Stack, + override val concurrentResults: MutableSet, private val automated: Automated ) : ISimInfo, Automated by automated diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/SimChecker.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/SimChecker.kt index bdb87ac4e..ae08b2f2a 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/SimChecker.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/SimChecker.kt @@ -44,14 +44,14 @@ annotation class SimCheckerDsl @SimCheckerDsl abstract class SimChecker { - protected fun ISimInfo.checkDependent(caller: Dependable?): Boolean { - if (caller == null) { - dependencyStack.clear() - return true + protected suspend fun ISimInfo.withDependable(dependable: Dependable?, block: suspend () -> Unit) { + if (dependable == null) dependencyStack.clear() + else { + if (dependencyStack.size >= maxSimDependencies) return + dependencyStack.push(dependable) } - if (dependencyStack.size >= maxSimDependencies) return false - dependencyStack.push(caller) - return true + block() + if (dependable != null) dependencyStack.pop() } fun ISimInfo.result(result: GenericResult) = addResult(result) diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt index 5eaf18375..a5314284a 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt @@ -78,12 +78,13 @@ class BreakSim private constructor(simInfo: ISimInfo) context(automatedSafeContext: AutomatedSafeContext, dependable: Dependable?) suspend fun SimBuilder.simBreak() = BreakSim(this).run { - if (!checkDependent(dependable)) return - automatedSafeContext.checkBreaks() + withDependable(dependable) { + automatedSafeContext.simBreaks() + } } } - private suspend fun AutomatedSafeContext.checkBreaks() { + private suspend fun AutomatedSafeContext.simBreaks() { if (breakConfig.avoidSupporting) player.supportingBlockPos.getOrNull()?.let { support -> if (support != pos) return@let result(BreakResult.PlayerOnTop(pos, state)) diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceSim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceSim.kt index deb3e8ee2..133522be5 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceSim.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceSim.kt @@ -81,12 +81,13 @@ class PlaceSim private constructor(simInfo: ISimInfo) @SimBuilderDsl suspend fun SimBuilder.simPlacement() = PlaceSim(this).run { - if (!checkDependent(dependable)) return - automatedSafeContext.checkPlacements() + withDependable(dependable) { + automatedSafeContext.simPlacements() + } } } - private suspend fun AutomatedSafeContext.checkPlacements() = + private suspend fun AutomatedSafeContext.simPlacements() = supervisorScope { preProcessing.sides.forEach { side -> launch { diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingSim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingSim.kt index f703dc0cb..1978dbf78 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingSim.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingSim.kt @@ -57,12 +57,13 @@ class PostProcessingSim private constructor(simInfo: ISimInfo) @SimBuilderDsl suspend fun SimBuilder.simPostProcessing() = PostProcessingSim(this).run { - if (!checkDependent(dependable)) return - automatedSafeContext.checkPostProcessing() + withDependable(dependable) { + automatedSafeContext.simPostProcessing() + } } } - private suspend fun AutomatedSafeContext.checkPostProcessing() { + private suspend fun AutomatedSafeContext.simPostProcessing() { val targetState = (targetState as? TargetState.State) ?: return val mismatchedProperties = state.properties.filter { state.get(it) != targetState.blockState.get(it) } From 081c84f58c2cfee9e55a994381f9737763ab17f8 Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Thu, 30 Oct 2025 23:50:30 +0000 Subject: [PATCH 26/30] big refactor --- .../construction/simulation/BuildSimulator.kt | 2 +- .../construction/simulation/ISimInfo.kt | 42 ++++++------------- .../Dependable.kt => simulation/Results.kt} | 21 ++++++++-- .../simulation/{SimChecker.kt => Sim.kt} | 36 +++++----------- ...{RequirementChecker.kt => BasicChecker.kt} | 10 ++--- .../simulation/checks/BreakSim.kt | 18 ++++---- .../simulation/checks/PlaceSim.kt | 18 ++++---- .../simulation/checks/PostProcessingSim.kt | 21 ++++------ 8 files changed, 71 insertions(+), 97 deletions(-) rename src/main/kotlin/com/lambda/interaction/construction/{result/Dependable.kt => simulation/Results.kt} (51%) rename src/main/kotlin/com/lambda/interaction/construction/simulation/{SimChecker.kt => Sim.kt} (80%) rename src/main/kotlin/com/lambda/interaction/construction/simulation/checks/{RequirementChecker.kt => BasicChecker.kt} (90%) 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 883d764d7..78612b403 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt @@ -34,7 +34,7 @@ import kotlinx.coroutines.runBlocking import kotlinx.coroutines.supervisorScope import net.minecraft.util.math.Vec3d -object BuildSimulator : SimChecker() { +object BuildSimulator : Sim() { context(automatedSafeContext: AutomatedSafeContext) fun Blueprint.simulate( pov: Vec3d = automatedSafeContext.player.eyePos diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/ISimInfo.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/ISimInfo.kt index 9d35e30d6..00027e9e4 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/ISimInfo.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/ISimInfo.kt @@ -22,17 +22,13 @@ import com.lambda.context.AutomatedSafeContext import com.lambda.interaction.construction.processing.PreProcessingInfo import com.lambda.interaction.construction.processing.ProcessorRegistry.getProcessingInfo import com.lambda.interaction.construction.result.BuildResult -import com.lambda.interaction.construction.result.Dependable -import com.lambda.interaction.construction.simulation.checks.RequirementChecker.checkRequirements +import com.lambda.interaction.construction.simulation.checks.BasicChecker.hasBasicRequirements import com.lambda.interaction.construction.verify.TargetState import net.minecraft.block.BlockState import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Vec3d import java.util.* -@DslMarker -annotation class SimBuilderDsl - interface ISimInfo : Automated { val pos: BlockPos val state: BlockState @@ -40,65 +36,51 @@ interface ISimInfo : Automated { val preProcessing: PreProcessingInfo val pov: Vec3d val concurrentResults: MutableSet - val dependencyStack: Stack + val dependencyStack: Stack> companion object { - @SimBuilderDsl - private suspend fun ISimInfo.sim( - dependable: Dependable?, - simBuilder: suspend context(Dependable?) SimBuilder.() -> Unit - ) = SimBuilder(this).run { with(dependable) { simBuilder() } } - - @SimBuilderDsl + @SimDsl + context(_: BuildSimulator) suspend fun AutomatedSafeContext.sim( pos: BlockPos, state: BlockState, targetState: TargetState, pov: Vec3d, concurrentResults: MutableSet, - simBuilder: suspend context(Dependable?) SimBuilder.() -> Unit + simBuilder: suspend ISimInfo.() -> Unit ) { SimInfo( pos, state, targetState, targetState.getProcessingInfo(pos) ?: return, pov, Stack(), concurrentResults, this - ).takeIf { it.checkRequirements() }?.sim(null, simBuilder) + ).takeIf { it.hasBasicRequirements() }?.run { simBuilder() } } - @SimBuilderDsl - context(_: AutomatedSafeContext, dependable: Dependable) + @SimDsl + context(_: AutomatedSafeContext, dependent: Sim<*>) suspend fun ISimInfo.sim( pos: BlockPos = this.pos, state: BlockState = this.state, targetState: TargetState = this.targetState, pov: Vec3d = this.pov, - simBuilder: suspend context(Dependable?) SimBuilder.() -> Unit + simBuilder: suspend ISimInfo.() -> Unit ) { SimInfo( pos, state, targetState, targetState.getProcessingInfo(pos) ?: return, pov, dependencyStack, concurrentResults, this - ).takeIf { it.checkRequirements() }?.sim(dependable, simBuilder) + ).takeIf { it.hasBasicRequirements() }?.run { simBuilder() } } - - @SimBuilderDsl - context(dependable: Dependable) - suspend fun ISimInfo.sim( - simBuilder: suspend context(Dependable?) SimBuilder.() -> Unit - ) = sim(dependable, simBuilder) } } -@SimBuilderDsl data class SimInfo( override val pos: BlockPos, override val state: BlockState, override val targetState: TargetState, override val preProcessing: PreProcessingInfo, override val pov: Vec3d, - override val dependencyStack: Stack, + override val dependencyStack: Stack>, override val concurrentResults: MutableSet, private val automated: Automated -) : ISimInfo, Automated by automated - -class SimBuilder(simInfo: ISimInfo) : ISimInfo by simInfo \ No newline at end of file +) : ISimInfo, Automated by automated \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/construction/result/Dependable.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/Results.kt similarity index 51% rename from src/main/kotlin/com/lambda/interaction/construction/result/Dependable.kt rename to src/main/kotlin/com/lambda/interaction/construction/simulation/Results.kt index 45eafefd9..86c58bb39 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/result/Dependable.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/Results.kt @@ -15,10 +15,23 @@ * along with this program. If not, see . */ -package com.lambda.interaction.construction.result +package com.lambda.interaction.construction.simulation -import com.lambda.interaction.construction.simulation.ISimInfo +import com.lambda.interaction.construction.result.BuildResult +import com.lambda.interaction.construction.result.results.GenericResult -interface Dependable : ISimInfo { - fun asDependent(buildResult: BuildResult): BuildResult +@SimDsl +interface Results { + fun ISimInfo.result(result: GenericResult) = addResult(result) + fun ISimInfo.result(result: T) = addResult(result) + + private fun ISimInfo.addResult(result: BuildResult) { + concurrentResults.add( + dependencyStack + .asReversed() + .fold(result) { acc, dependable -> + with(dependable) { dependentUpon(acc) } + } + ) + } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/SimChecker.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/Sim.kt similarity index 80% rename from src/main/kotlin/com/lambda/interaction/construction/simulation/SimChecker.kt rename to src/main/kotlin/com/lambda/interaction/construction/simulation/Sim.kt index ae08b2f2a..1f47b9b8c 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/SimChecker.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/Sim.kt @@ -20,7 +20,6 @@ package com.lambda.interaction.construction.simulation import com.lambda.context.AutomationConfig.maxSimDependencies import com.lambda.interaction.construction.processing.PreProcessingInfo import com.lambda.interaction.construction.result.BuildResult -import com.lambda.interaction.construction.result.Dependable import com.lambda.interaction.construction.result.results.GenericResult import com.lambda.interaction.request.rotating.Rotation.Companion.rotationTo import com.lambda.interaction.request.rotating.visibilty.VisibilityChecker.CheckedHit @@ -40,32 +39,19 @@ import net.minecraft.util.shape.VoxelShape import kotlin.math.pow @DslMarker -annotation class SimCheckerDsl - -@SimCheckerDsl -abstract class SimChecker { - protected suspend fun ISimInfo.withDependable(dependable: Dependable?, block: suspend () -> Unit) { - if (dependable == null) dependencyStack.clear() - else { - if (dependencyStack.size >= maxSimDependencies) return - dependencyStack.push(dependable) - } - block() - if (dependable != null) dependencyStack.pop() - } +annotation class SimDsl - fun ISimInfo.result(result: GenericResult) = addResult(result) - fun ISimInfo.result(result: T) = addResult(result) +@SimDsl +abstract class Sim : Results { + @SimDsl + open fun dependentUpon(buildResult: BuildResult): BuildResult = buildResult - private fun ISimInfo.addResult(result: BuildResult) { - if (this@SimChecker is BuildSimulator) concurrentResults.add(result) - else concurrentResults.add( - dependencyStack - .asReversed() - .fold(result) { acc, dependable -> - with(dependable) { asDependent(acc) } - } - ) + protected suspend fun ISimInfo.withDependent(dependent: Sim<*>, block: suspend () -> Unit) { + // +1 because the build sim counts as a dependent + if (dependencyStack.size >= maxSimDependencies + 1) return + dependencyStack.push(dependent) + block() + dependencyStack.pop() } suspend fun ISimInfo.scanShape( diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/RequirementChecker.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BasicChecker.kt similarity index 90% rename from src/main/kotlin/com/lambda/interaction/construction/simulation/checks/RequirementChecker.kt rename to src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BasicChecker.kt index b1c2d41d6..ec61a518c 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/RequirementChecker.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BasicChecker.kt @@ -20,18 +20,18 @@ package com.lambda.interaction.construction.simulation.checks import com.lambda.context.AutomatedSafeContext import com.lambda.interaction.construction.result.results.GenericResult import com.lambda.interaction.construction.result.results.PreSimResult -import com.lambda.interaction.construction.simulation.SimBuilderDsl -import com.lambda.interaction.construction.simulation.SimChecker +import com.lambda.interaction.construction.simulation.Results +import com.lambda.interaction.construction.simulation.SimDsl import com.lambda.interaction.construction.simulation.SimInfo import com.lambda.interaction.construction.verify.TargetState import com.lambda.util.player.gamemode import com.lambda.util.world.WorldUtils.isLoaded import net.minecraft.block.OperatorBlock -object RequirementChecker : SimChecker() { - @SimBuilderDsl +object BasicChecker : Results { + @SimDsl context(automatedSafeContext: AutomatedSafeContext) - fun SimInfo.checkRequirements(): Boolean = with(automatedSafeContext) { + fun SimInfo.hasBasicRequirements(): Boolean = with(automatedSafeContext) { // the chunk is not loaded if (!isLoaded(pos)) { result(PreSimResult.ChunkNotLoaded(pos)) diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt index a5314284a..50a2f3459 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt @@ -20,14 +20,12 @@ package com.lambda.interaction.construction.simulation.checks import com.lambda.context.AutomatedSafeContext import com.lambda.interaction.construction.context.BreakContext import com.lambda.interaction.construction.result.BuildResult -import com.lambda.interaction.construction.result.Dependable import com.lambda.interaction.construction.result.results.BreakResult import com.lambda.interaction.construction.result.results.GenericResult import com.lambda.interaction.construction.simulation.ISimInfo import com.lambda.interaction.construction.simulation.ISimInfo.Companion.sim -import com.lambda.interaction.construction.simulation.SimBuilder -import com.lambda.interaction.construction.simulation.SimBuilderDsl -import com.lambda.interaction.construction.simulation.SimChecker +import com.lambda.interaction.construction.simulation.Sim +import com.lambda.interaction.construction.simulation.SimDsl import com.lambda.interaction.construction.simulation.checks.PlaceSim.Companion.simPlacement import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.material.ContainerSelection.Companion.selectContainer @@ -67,18 +65,18 @@ import net.minecraft.util.math.Direction import kotlin.jvm.optionals.getOrNull class BreakSim private constructor(simInfo: ISimInfo) - : SimChecker(), Dependable, + : Sim(), ISimInfo by simInfo { - override fun asDependent(buildResult: BuildResult) = + override fun dependentUpon(buildResult: BuildResult) = BreakResult.Dependency(pos, buildResult) companion object { - @SimBuilderDsl - context(automatedSafeContext: AutomatedSafeContext, dependable: Dependable?) - suspend fun SimBuilder.simBreak() = + @SimDsl + context(automatedSafeContext: AutomatedSafeContext, dependent: Sim<*>) + suspend fun ISimInfo.simBreak() = BreakSim(this).run { - withDependable(dependable) { + withDependent(dependent) { automatedSafeContext.simBreaks() } } diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceSim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceSim.kt index 133522be5..ed0cd6bb8 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceSim.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceSim.kt @@ -20,14 +20,12 @@ package com.lambda.interaction.construction.simulation.checks import com.lambda.context.AutomatedSafeContext import com.lambda.interaction.construction.context.PlaceContext import com.lambda.interaction.construction.result.BuildResult -import com.lambda.interaction.construction.result.Dependable import com.lambda.interaction.construction.result.results.GenericResult import com.lambda.interaction.construction.result.results.PlaceResult import com.lambda.interaction.construction.simulation.ISimInfo import com.lambda.interaction.construction.simulation.ISimInfo.Companion.sim -import com.lambda.interaction.construction.simulation.SimBuilder -import com.lambda.interaction.construction.simulation.SimBuilderDsl -import com.lambda.interaction.construction.simulation.SimChecker +import com.lambda.interaction.construction.simulation.Sim +import com.lambda.interaction.construction.simulation.SimDsl import com.lambda.interaction.construction.simulation.checks.BreakSim.Companion.simBreak import com.lambda.interaction.construction.simulation.checks.PlaceSim.RotatePlaceTest.Companion.rotatePlaceTest import com.lambda.interaction.construction.verify.TargetState @@ -70,18 +68,18 @@ import net.minecraft.util.math.Direction import net.minecraft.util.shape.VoxelShapes class PlaceSim private constructor(simInfo: ISimInfo) - : SimChecker(), Dependable, + : Sim(), ISimInfo by simInfo { - override fun asDependent(buildResult: BuildResult) = + override fun dependentUpon(buildResult: BuildResult) = PlaceResult.Dependency(pos, buildResult) companion object { - context(automatedSafeContext: AutomatedSafeContext, dependable: Dependable?) - @SimBuilderDsl - suspend fun SimBuilder.simPlacement() = + context(automatedSafeContext: AutomatedSafeContext, dependent: Sim<*>) + @SimDsl + suspend fun ISimInfo.simPlacement() = PlaceSim(this).run { - withDependable(dependable) { + withDependent(dependent) { automatedSafeContext.simPlacements() } } diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingSim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingSim.kt index 1978dbf78..a31f03dbf 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingSim.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingSim.kt @@ -20,14 +20,11 @@ package com.lambda.interaction.construction.simulation.checks import com.lambda.context.AutomatedSafeContext import com.lambda.interaction.construction.context.InteractionContext import com.lambda.interaction.construction.result.BuildResult -import com.lambda.interaction.construction.result.Dependable import com.lambda.interaction.construction.result.results.GenericResult import com.lambda.interaction.construction.result.results.InteractResult import com.lambda.interaction.construction.simulation.ISimInfo -import com.lambda.interaction.construction.simulation.ISimInfo.Companion.sim -import com.lambda.interaction.construction.simulation.SimBuilder -import com.lambda.interaction.construction.simulation.SimBuilderDsl -import com.lambda.interaction.construction.simulation.SimChecker +import com.lambda.interaction.construction.simulation.Sim +import com.lambda.interaction.construction.simulation.SimDsl import com.lambda.interaction.construction.simulation.checks.PlaceSim.Companion.simPlacement import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.material.ContainerSelection.Companion.selectContainer @@ -46,18 +43,18 @@ import net.minecraft.state.property.Properties import net.minecraft.util.math.Direction class PostProcessingSim private constructor(simInfo: ISimInfo) - : SimChecker(), Dependable, + : Sim(), ISimInfo by simInfo { - override fun asDependent(buildResult: BuildResult) = + override fun dependentUpon(buildResult: BuildResult) = InteractResult.Dependency(pos, buildResult) companion object { - context(automatedSafeContext: AutomatedSafeContext, dependable: Dependable?) - @SimBuilderDsl - suspend fun SimBuilder.simPostProcessing() = + context(automatedSafeContext: AutomatedSafeContext, dependent: Sim<*>) + @SimDsl + suspend fun ISimInfo.simPostProcessing() = PostProcessingSim(this).run { - withDependable(dependable) { + withDependent(dependent) { automatedSafeContext.simPostProcessing() } } @@ -99,7 +96,7 @@ class PostProcessingSim private constructor(simInfo: ISimInfo) simInteraction(expectedState) } - Properties.SLAB_TYPE -> sim { simPlacement() } + Properties.SLAB_TYPE -> simPlacement() } } } From 5cc445a7ecbebc0e78449e7b377c64b73a92ddb8 Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Fri, 31 Oct 2025 00:35:32 +0000 Subject: [PATCH 27/30] dependent over dependable --- .../com/lambda/interaction/construction/simulation/Results.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/Results.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/Results.kt index 86c58bb39..4111218ac 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/Results.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/Results.kt @@ -29,8 +29,8 @@ interface Results { concurrentResults.add( dependencyStack .asReversed() - .fold(result) { acc, dependable -> - with(dependable) { dependentUpon(acc) } + .fold(result) { acc, dependent -> + with(dependent) { dependentUpon(acc) } } ) } From 3d5d18ae9a15e5000e36ea22288c001d78780527 Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Fri, 31 Oct 2025 00:43:30 +0000 Subject: [PATCH 28/30] remove unused dependent sim context --- .../com/lambda/interaction/construction/simulation/ISimInfo.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/ISimInfo.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/ISimInfo.kt index 00027e9e4..fb67cd23d 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/ISimInfo.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/ISimInfo.kt @@ -57,7 +57,7 @@ interface ISimInfo : Automated { } @SimDsl - context(_: AutomatedSafeContext, dependent: Sim<*>) + context(_: AutomatedSafeContext) suspend fun ISimInfo.sim( pos: BlockPos = this.pos, state: BlockState = this.state, From 62ed3350ee4d35ac8b10e91a0c01323b060278cf Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Fri, 31 Oct 2025 15:31:22 +0000 Subject: [PATCH 29/30] copy dependency stack when nesting simulation to prevent concurrency issues --- src/main/kotlin/com/lambda/context/AutomationConfig.kt | 2 +- .../lambda/interaction/construction/simulation/ISimInfo.kt | 6 +++--- .../interaction/construction/simulation/checks/BreakSim.kt | 3 ++- .../interaction/construction/simulation/checks/PlaceSim.kt | 3 ++- .../construction/simulation/checks/PostProcessingSim.kt | 6 ++++-- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/com/lambda/context/AutomationConfig.kt b/src/main/kotlin/com/lambda/context/AutomationConfig.kt index 369e175e4..8925a5804 100644 --- a/src/main/kotlin/com/lambda/context/AutomationConfig.kt +++ b/src/main/kotlin/com/lambda/context/AutomationConfig.kt @@ -62,7 +62,7 @@ object AutomationConfig : Configurable(LambdaConfig), Automated { val showAllEntries by setting("Show All Entries", false, "Show all entries in the task tree").group(Group.Debug) val shrinkFactor by setting("Shrink Factor", 0.001, 0.0..1.0, 0.001).group(Group.Debug) val ignoreItemDropWarnings by setting("Ignore Drop Warnings", false, "Hides the item drop warnings from the break manager").group(Group.Debug) - val maxSimDependencies by setting("Max Sim Dependencies", 10, 0..25, 1, "Maximum dependency build results").group(Group.Debug) + val maxSimDependencies by setting("Max Sim Dependencies", 3, 0..10, 1, "Maximum dependency build results").group(Group.Debug) @Volatile var drawables = listOf() diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/ISimInfo.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/ISimInfo.kt index fb67cd23d..3a460ecd4 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/ISimInfo.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/ISimInfo.kt @@ -47,7 +47,7 @@ interface ISimInfo : Automated { targetState: TargetState, pov: Vec3d, concurrentResults: MutableSet, - simBuilder: suspend ISimInfo.() -> Unit + simBuilder: suspend SimInfo.() -> Unit ) { SimInfo( pos, state, targetState, @@ -63,12 +63,12 @@ interface ISimInfo : Automated { state: BlockState = this.state, targetState: TargetState = this.targetState, pov: Vec3d = this.pov, - simBuilder: suspend ISimInfo.() -> Unit + simBuilder: suspend SimInfo.() -> Unit ) { SimInfo( pos, state, targetState, targetState.getProcessingInfo(pos) ?: return, - pov, dependencyStack, concurrentResults, this + pov, Stack>().apply { addAll(dependencyStack) }, concurrentResults, this ).takeIf { it.hasBasicRequirements() }?.run { simBuilder() } } } diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt index 50a2f3459..b9179123d 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt @@ -26,6 +26,7 @@ import com.lambda.interaction.construction.simulation.ISimInfo import com.lambda.interaction.construction.simulation.ISimInfo.Companion.sim import com.lambda.interaction.construction.simulation.Sim import com.lambda.interaction.construction.simulation.SimDsl +import com.lambda.interaction.construction.simulation.SimInfo import com.lambda.interaction.construction.simulation.checks.PlaceSim.Companion.simPlacement import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.material.ContainerSelection.Companion.selectContainer @@ -74,7 +75,7 @@ class BreakSim private constructor(simInfo: ISimInfo) companion object { @SimDsl context(automatedSafeContext: AutomatedSafeContext, dependent: Sim<*>) - suspend fun ISimInfo.simBreak() = + suspend fun SimInfo.simBreak() = BreakSim(this).run { withDependent(dependent) { automatedSafeContext.simBreaks() diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceSim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceSim.kt index ed0cd6bb8..1de5c54d0 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceSim.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceSim.kt @@ -26,6 +26,7 @@ import com.lambda.interaction.construction.simulation.ISimInfo import com.lambda.interaction.construction.simulation.ISimInfo.Companion.sim import com.lambda.interaction.construction.simulation.Sim import com.lambda.interaction.construction.simulation.SimDsl +import com.lambda.interaction.construction.simulation.SimInfo import com.lambda.interaction.construction.simulation.checks.BreakSim.Companion.simBreak import com.lambda.interaction.construction.simulation.checks.PlaceSim.RotatePlaceTest.Companion.rotatePlaceTest import com.lambda.interaction.construction.verify.TargetState @@ -77,7 +78,7 @@ class PlaceSim private constructor(simInfo: ISimInfo) companion object { context(automatedSafeContext: AutomatedSafeContext, dependent: Sim<*>) @SimDsl - suspend fun ISimInfo.simPlacement() = + suspend fun SimInfo.simPlacement() = PlaceSim(this).run { withDependent(dependent) { automatedSafeContext.simPlacements() diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingSim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingSim.kt index a31f03dbf..b285f9bae 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingSim.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PostProcessingSim.kt @@ -23,8 +23,10 @@ import com.lambda.interaction.construction.result.BuildResult import com.lambda.interaction.construction.result.results.GenericResult import com.lambda.interaction.construction.result.results.InteractResult import com.lambda.interaction.construction.simulation.ISimInfo +import com.lambda.interaction.construction.simulation.ISimInfo.Companion.sim import com.lambda.interaction.construction.simulation.Sim import com.lambda.interaction.construction.simulation.SimDsl +import com.lambda.interaction.construction.simulation.SimInfo import com.lambda.interaction.construction.simulation.checks.PlaceSim.Companion.simPlacement import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.material.ContainerSelection.Companion.selectContainer @@ -52,7 +54,7 @@ class PostProcessingSim private constructor(simInfo: ISimInfo) companion object { context(automatedSafeContext: AutomatedSafeContext, dependent: Sim<*>) @SimDsl - suspend fun ISimInfo.simPostProcessing() = + suspend fun SimInfo.simPostProcessing() = PostProcessingSim(this).run { withDependent(dependent) { automatedSafeContext.simPostProcessing() @@ -96,7 +98,7 @@ class PostProcessingSim private constructor(simInfo: ISimInfo) simInteraction(expectedState) } - Properties.SLAB_TYPE -> simPlacement() + Properties.SLAB_TYPE -> sim { simPlacement() } } } } From 59cead9b3bf64f1cabd277ca6efd44290a39ee90 Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Sat, 1 Nov 2025 18:43:59 +0000 Subject: [PATCH 30/30] inline block item getPlacementContext --- .../result/results/PlaceResult.kt | 3 +-- .../simulation/checks/PlaceSim.kt | 24 +++++++++---------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/main/kotlin/com/lambda/interaction/construction/result/results/PlaceResult.kt b/src/main/kotlin/com/lambda/interaction/construction/result/results/PlaceResult.kt index fada08453..187e769f5 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/result/results/PlaceResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/result/results/PlaceResult.kt @@ -151,8 +151,7 @@ sealed class PlaceResult : BuildResult() { * @property simulated The context of the simulated item placement attempt. */ data class ScaffoldExceeded( - override val pos: BlockPos, - val simulated: ItemPlacementContext, + override val pos: BlockPos ) : PlaceResult() { override val rank = Rank.PlaceScaffoldExceeded } diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceSim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceSim.kt index 1de5c54d0..df5a0a644 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceSim.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/PlaceSim.kt @@ -154,13 +154,18 @@ class PlaceSim private constructor(simInfo: ISimInfo) buildConfig.pointSelection.select(validHits)?.let { checkedHit -> val hitResult = checkedHit.hit.blockResult ?: return - var context = ItemPlacementContext( - world, - fakePlayer, - Hand.MAIN_HAND, - swapStack, - hitResult, - ) + val context = swapStack.blockItem.getPlacementContext( + ItemPlacementContext( + world, + fakePlayer, + Hand.MAIN_HAND, + swapStack, + hitResult, + ) + ) ?: run { + result(PlaceResult.ScaffoldExceeded(pos)) + return + } if (context.blockPos != pos) { result(PlaceResult.UnexpectedPosition(pos, context.blockPos)) @@ -178,11 +183,6 @@ class PlaceSim private constructor(simInfo: ISimInfo) return } - context = swapStack.blockItem.getPlacementContext(context) ?: run { - result(PlaceResult.ScaffoldExceeded(pos, context)) - return - } - val rotatePlaceTest = simRotatePlace(fakePlayer, checkedHit, context) ?: return val rotationRequest = if (placeConfig.axisRotate) {