From 0944e8dfa3e0b307796c3f9dc836949423b701a2 Mon Sep 17 00:00:00 2001 From: Constructor Date: Mon, 30 Sep 2024 04:51:04 +0200 Subject: [PATCH 01/39] Fix reach misconceptions and inventory crash --- .../interaction/construction/Blueprint.kt | 3 +- .../construction/result/BuildResult.kt | 21 ++++----- .../interaction/construction/result/Rank.kt | 2 +- .../construction/simulation/BuildSimulator.kt | 43 +++++++++++-------- .../construction/simulation/Simulation.kt | 18 +++----- .../construction/verify/TargetState.kt | 19 +++++--- .../module/modules/player/HighwayTools.kt | 14 +++++- .../kotlin/com/lambda/task/tasks/BuildTask.kt | 2 - .../com/lambda/task/tasks/InventoryTask.kt | 4 +- 9 files changed, 71 insertions(+), 55 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/Blueprint.kt b/common/src/main/kotlin/com/lambda/interaction/construction/Blueprint.kt index 5427cbde9..5dc8f41bb 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/Blueprint.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/Blueprint.kt @@ -18,8 +18,7 @@ abstract class Blueprint { open fun isDone(ctx: SafeContext) = structure.all { (pos, targetState) -> with(ctx) { - val state = pos.blockState(world) - targetState.matches(state, pos, world) || state.block in TaskFlow.ignoredBlocks + targetState.matches(pos.blockState(world), pos, world) } } diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/result/BuildResult.kt b/common/src/main/kotlin/com/lambda/interaction/construction/result/BuildResult.kt index f06f65eb3..e44516de8 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/result/BuildResult.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/result/BuildResult.kt @@ -4,6 +4,7 @@ import baritone.api.pathing.goals.GoalBlock import baritone.api.pathing.goals.GoalNear import baritone.process.BuilderProcess.GoalPlace import com.lambda.context.SafeContext +import com.lambda.interaction.construction.Blueprint import com.lambda.interaction.construction.context.BuildContext import com.lambda.interaction.material.ContainerManager.transfer import com.lambda.interaction.material.StackSelection.Companion.select @@ -28,7 +29,7 @@ abstract class BuildResult : ComparableResult, Task() { * The build action is done. */ data class Done( - override val blockPos: BlockPos, + override val blockPos: BlockPos ) : BuildResult() { override val rank = Rank.DONE } @@ -37,7 +38,7 @@ abstract class BuildResult : ComparableResult, Task() { * The build action is ignored. */ data class Ignored( - override val blockPos: BlockPos, + override val blockPos: BlockPos ) : BuildResult() { override val rank = Rank.IGNORED } @@ -236,26 +237,22 @@ abstract class BuildResult : ComparableResult, Task() { /** * Represents a break out of reach. * @param blockPos The position of the block that is out of reach. - * @param startVec The start vector of the reach. - * @param hitVec The hit vector of the reach. - * @param reach The maximum reach distance. - * @param side The side 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 startVec: Vec3d, - val hitVec: Vec3d, - val reach: Double, - val side: Direction, + val pov: Vec3d, + val misses: Set ) : Navigable, Drawable, BuildResult() { override val rank = Rank.OUT_OF_REACH private val color = Color(252, 3, 207, 100) val distance: Double by lazy { - startVec.distanceTo(hitVec) + misses.minOfOrNull { pov.distanceTo(it) } ?: 0.0 } - override val goal = GoalNear(blockPos, 2) + override val goal = GoalNear(blockPos, 3) override fun SafeContext.buildRenderer() { withPos(blockPos, color) diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/result/Rank.kt b/common/src/main/kotlin/com/lambda/interaction/construction/result/Rank.kt index 88fbd443b..c05a5d913 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/result/Rank.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/result/Rank.kt @@ -32,5 +32,5 @@ enum class Rank { IGNORED; val solvable: Boolean - get() = ordinal < OUT_OF_WORLD.ordinal + get() = ordinal < PLACE_NOT_ITEM_BLOCK.ordinal } \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt index 5e1cdc0b4..ae36ed71f 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt @@ -20,6 +20,8 @@ import com.lambda.util.BlockUtils import com.lambda.util.BlockUtils.blockState import com.lambda.util.BlockUtils.instantBreakable import com.lambda.util.BlockUtils.vecOf +import com.lambda.util.Communication.info +import com.lambda.util.Communication.warn import com.lambda.util.item.ItemStackUtils.equal import com.lambda.util.math.VecUtils.distSq import com.lambda.util.world.raycast.RayCastUtils.blockResult @@ -42,7 +44,7 @@ object BuildSimulator { fun Blueprint.simulate(eye: Vec3d, reach: Double = TaskFlow.interact.reach) = runSafe { structure.entries.flatMap { (pos, target) -> - checkRequirements(pos, target, reach)?.let { + checkRequirements(pos, target)?.let { return@flatMap setOf(it) } checkPlaceResults(pos, target, eye, reach).let { @@ -53,11 +55,12 @@ object BuildSimulator { if (it.isEmpty()) return@let return@flatMap it } + warn("Nothing matched $pos $target") emptySet() }.toSet() } ?: emptySet() - private fun SafeContext.checkRequirements(pos: BlockPos, target: TargetState, reach: Double): BuildResult? { + private fun SafeContext.checkRequirements(pos: BlockPos, target: TargetState): BuildResult? { /* the chunk is not loaded */ if (!world.isChunkLoaded(pos)) { return BuildResult.ChunkNotLoaded(pos) @@ -119,22 +122,18 @@ object BuildSimulator { if (voxelShape.isEmpty) return@forEach val boxes = voxelShape.boundingBoxes.map { it.offset(hitPos) } - - if (boxes.all { it.center.distanceTo(eye) > reach + 1 }) { - acc.add(BuildResult.OutOfReach(pos, eye, hitPos.vecOf(hitSide), reach, hitSide)) - return@forEach - } - val verify: HitResult.() -> Boolean = { blockResult?.blockPos == hitPos && blockResult?.side == hitSide } val validHits = mutableMapOf() + val misses = mutableSetOf() val reachSq = reach.pow(2) boxes.forEach { box -> val res = if (TaskFlow.interact.useRayCast) interact.resolution else 2 scanVisibleSurfaces(eye, box, setOf(hitSide), res) { side, vec -> if (eye distSq vec > reachSq) { + misses.add(vec) return@scanVisibleSurfaces } @@ -152,11 +151,15 @@ object BuildSimulator { false ) } - } } 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.vecOf(hitSide)))) return@forEach } @@ -258,7 +261,11 @@ object BuildSimulator { return acc } - private fun SafeContext.checkBreakResults(pos: BlockPos, eye: Vec3d, reach: Double): Set { + private fun SafeContext.checkBreakResults( + pos: BlockPos, + eye: Vec3d, + reach: Double + ): Set { val acc = mutableSetOf() val state = pos.blockState(world) @@ -315,15 +322,10 @@ object BuildSimulator { val voxelShape = state.getOutlineShape(world, pos) voxelShape.getClosestPointTo(eye).ifPresent { - // ToDo: Use closest point of shape + // ToDo: Use closest point of shape of only visible faces } val boxes = voxelShape.boundingBoxes.map { it.offset(pos) } - if (boxes.all { it.center.distanceTo(eye) > reach + 1 }) { - acc.add(BuildResult.OutOfReach(pos, eye, pos.toCenterPos(), reach, Direction.UP)) - return acc - } - val verify: HitResult.() -> Boolean = { blockResult?.blockPos == pos } /* the player is buried inside the block */ if (boxes.any { it.contains(eye) }) { @@ -343,12 +345,14 @@ object BuildSimulator { } val validHits = mutableMapOf() + val misses = mutableSetOf() val reachSq = reach.pow(2) boxes.forEach { box -> val res = if (TaskFlow.interact.useRayCast) interact.resolution else 2 scanVisibleSurfaces(eye, box, emptySet(), res) { side, vec -> if (eye distSq vec > reachSq) { + misses.add(vec) return@scanVisibleSurfaces } @@ -369,6 +373,12 @@ object BuildSimulator { } } + 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 + } + validHits.keys.optimum?.let { optimum -> validHits.minByOrNull { optimum distSq it.key }?.let { closest -> val optimumRotation = eye.rotationTo(closest.key) @@ -403,7 +413,6 @@ object BuildSimulator { acc.add(BreakResult.Break(pos, breakContext)) } - return acc } } \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt index f9c09c980..e002b712c 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt @@ -40,17 +40,13 @@ data class Simulation(val blueprint: Blueprint) { // }.keys fun simulate(pos: FastVector): Set { -// runSafe { -// if (!playerFitsIn(Vec3d.ofBottomCenter(pos.toBlockPos()))) return emptySet() -// } -// return blueprint.simulate(pos.toView()).also { cache[pos] = it } -// return cache.computeIfAbsent(pos) { -//// runSafe { -//// if (!playerFitsIn(Vec3d.ofBottomCenter(pos.toBlockPos()))) return@computeIfAbsent emptySet() -//// } -// blueprint.simulate(pos.toView()) -// } - return blueprint.simulate(pos.toView(), reach = 3.5) + return cache.computeIfAbsent(pos) { + runSafe { + if (!playerFitsIn(Vec3d.ofBottomCenter(pos.toBlockPos()))) return@computeIfAbsent emptySet() + } + blueprint.simulate(pos.toView(), reach = 3.5) + } +// return blueprint.simulate(pos.toView(), reach = 3.5) } private fun SafeContext.playerFitsIn(pos: Vec3d): Boolean { diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt b/common/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt index 950d6e175..25765f88b 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt @@ -11,15 +11,20 @@ import net.minecraft.item.Items import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Direction -sealed class TargetState : StateMatcher { - data object Air : TargetState() { +sealed class TargetState(val type: Type) : StateMatcher { + + enum class Type { + AIR, SOLID, SUPPORT, STATE, BLOCK, STACK + } + + data object Air : TargetState(Type.AIR) { override fun matches(state: BlockState, pos: BlockPos, world: ClientWorld) = state.isAir override fun getStack(world: ClientWorld, pos: BlockPos): ItemStack = ItemStack.EMPTY } - data object Solid : TargetState() { + data object Solid : TargetState(Type.SOLID) { override fun matches(state: BlockState, pos: BlockPos, world: ClientWorld) = state.isSolidBlock(world, pos) override fun getStack(world: ClientWorld, pos: BlockPos) = @@ -28,7 +33,7 @@ sealed class TargetState : StateMatcher { } ?: ItemStack(Items.NETHERRACK) } - data class Support(val direction: Direction) : TargetState() { + data class Support(val direction: Direction) : TargetState(Type.SUPPORT) { override fun matches(state: BlockState, pos: BlockPos, world: ClientWorld) = pos.offset(direction).blockState(world).isSolidBlock(world, pos.offset(direction)) || state.isSolidBlock(world, pos) @@ -39,21 +44,21 @@ sealed class TargetState : StateMatcher { } ?: ItemStack(Items.NETHERRACK) } - data class State(val blockState: BlockState) : TargetState() { + data class State(val blockState: BlockState) : TargetState(Type.STATE) { override fun matches(state: BlockState, pos: BlockPos, world: ClientWorld) = state == blockState override fun getStack(world: ClientWorld, pos: BlockPos): ItemStack = blockState.block.getPickStack(world, pos, blockState) } - data class Block(val block: net.minecraft.block.Block) : TargetState() { + data class Block(val block: net.minecraft.block.Block) : TargetState(Type.BLOCK) { override fun matches(state: BlockState, pos: BlockPos, world: ClientWorld) = state.block == block override fun getStack(world: ClientWorld, pos: BlockPos): ItemStack = block.getPickStack(world, pos, block.defaultState) } - data class Stack(val itemStack: ItemStack) : TargetState() { + data class Stack(val itemStack: ItemStack) : TargetState(Type.STACK) { private val block = itemStack.item.block override fun matches(state: BlockState, pos: BlockPos, world: ClientWorld) = diff --git a/common/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt b/common/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt index 3d83792cc..2cdf67c5d 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt @@ -27,9 +27,11 @@ object HighwayTools : Module( private val width by setting("Width", 6, 1..30, 1) private val rimHeight by setting("Rim Height", 1, 0..6, 1) private val cornerBlock by setting("Corner Block", false, description = "Include corner blocks in the highway") + private val ceiling by setting("Ceiling", false, description = "Smooth roof over the highway") + private val ceilingMaterial by setting("Ceiling Material", Blocks.OBSIDIAN, description = "Material to build the ceiling with") private val distance by setting("Distance", -1, -1..1000000, 1, description = "Distance to build the highway (negative for infinite)") private val sliceSize by setting("Slice Size", 3, 1..5, 1, description = "Number of slices to build at once") - private val material by setting("Material", Blocks.OBSIDIAN, description = "Material to build the highway with") + private val material by setting("Highway Material", Blocks.OBSIDIAN, description = "Material to build the highway with") private var octant = EightWayDirection.NORTH private var distanceMoved = 0 @@ -152,6 +154,16 @@ object HighwayTools : Module( -1, ).associateWith { TargetState.Support(Direction.UP) } + if (ceiling) { + structure += generateDirectionalTube( + orthogonal, + width, + 1, + -center, + height - 1, + ).associateWith { TargetState.Block(ceilingMaterial) } + } + return structure } } diff --git a/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt b/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt index 834a70fed..0a14b50db 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt @@ -84,8 +84,6 @@ class BuildTask @Ta5kBuilder constructor( val result = results.minOrNull() ?: return@listener when { !result.rank.solvable -> { -// info("Unsolvable: $result") - if (!blueprint.isDone(this)) return@listener success(Unit) } result is BuildResult.NotVisible -> { diff --git a/common/src/main/kotlin/com/lambda/task/tasks/InventoryTask.kt b/common/src/main/kotlin/com/lambda/task/tasks/InventoryTask.kt index 77bcb4386..589d0a15c 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/InventoryTask.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/InventoryTask.kt @@ -27,8 +27,8 @@ class InventoryTask( val selectedFrom = selector.filterSlots(from).filter { it.hasStack() } val selectedTo = to.filter { it.stack.isEmpty } + to.filter { it.stack.item.block in TaskFlow.disposables } selectedFrom.zip(selectedTo).forEach { (from, to) -> - transactions.add(SlotUtils.Transaction(to.id, 0, SlotActionType.SWAP)) - transactions.add(SlotUtils.Transaction(from.id, 0, SlotActionType.SWAP)) + transactions.add(SlotUtils.Transaction(to.index, 0, SlotActionType.SWAP)) + transactions.add(SlotUtils.Transaction(from.index, 0, SlotActionType.SWAP)) // ToDo: Handle overflow of cursor for PICKUP } From b0892a3d3bb4a880f107dfb97600a7375cfdf09e Mon Sep 17 00:00:00 2001 From: Constructor Date: Mon, 30 Sep 2024 05:31:09 +0200 Subject: [PATCH 02/39] Faster pathing --- .../interaction/construction/Blueprint.kt | 33 +++++++++++-------- .../construction/simulation/BuildGoal.kt | 4 +-- .../construction/simulation/Simulation.kt | 27 +++------------ .../kotlin/com/lambda/task/tasks/BuildTask.kt | 4 +-- 4 files changed, 27 insertions(+), 41 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/Blueprint.kt b/common/src/main/kotlin/com/lambda/interaction/construction/Blueprint.kt index 5dc8f41bb..b49f56acb 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/Blueprint.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/Blueprint.kt @@ -1,26 +1,33 @@ package com.lambda.interaction.construction -import com.lambda.context.SafeContext import com.lambda.interaction.construction.verify.TargetState -import com.lambda.module.modules.client.TaskFlow import com.lambda.util.BlockUtils.blockPos -import com.lambda.util.BlockUtils.blockState -import com.lambda.util.Communication.info import com.lambda.util.extension.Structure +import com.lambda.util.math.VecUtils.blockPos import net.minecraft.structure.StructureTemplate -import net.minecraft.util.math.BlockBox -import net.minecraft.util.math.BlockPos -import net.minecraft.util.math.Box +import net.minecraft.util.math.* abstract class Blueprint { abstract val structure: Structure - open fun isDone(ctx: SafeContext) = - structure.all { (pos, targetState) -> - with(ctx) { - targetState.matches(pos.blockState(world), pos, world) - } - } + private val bounds: BlockBox by lazy { + val maxX = structure.keys.maxOf { it.x } + val maxY = structure.keys.maxOf { it.y } + val maxZ = structure.keys.maxOf { it.z } + val minX = structure.keys.minOf { it.x } + val minY = structure.keys.minOf { it.y } + val minZ = structure.keys.minOf { it.z } + BlockBox(minX, minY, minZ, maxX, maxY, maxZ) + } + + fun getClosestPointTo(target: Vec3d): Vec3d { + val d = MathHelper.clamp(target.x, bounds.minX.toDouble(), bounds.maxX.toDouble()) + val e = MathHelper.clamp(target.y, bounds.minY.toDouble(), bounds.maxY.toDouble()) + val f = MathHelper.clamp(target.z, bounds.minZ.toDouble(), bounds.maxZ.toDouble()) + return Vec3d(d, e, f) + } + + fun isOutOfBounds(vec3d: Vec3d): Boolean = !bounds.contains(vec3d.blockPos) companion object { fun emptyStructure(): Structure = emptyMap() diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildGoal.kt b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildGoal.kt index 9259a6286..f6e5dee8c 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildGoal.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildGoal.kt @@ -1,19 +1,17 @@ package com.lambda.interaction.construction.simulation import baritone.api.pathing.goals.Goal -import com.lambda.util.Communication.info import com.lambda.util.world.fastVectorOf class BuildGoal(private val sim: Simulation) : Goal { override fun isInGoal(x: Int, y: Int, z: Int): Boolean { val vec = fastVectorOf(x, y, z) -// info("Checking goal at $x, $y, $z") val simu = sim.simulate(vec) return simu.any { it.rank.ordinal < 4 } } override fun heuristic(x: Int, y: Int, z: Int): Double { - val bestRank = sim.simulate(fastVectorOf(x, y, z)).minOrNull()?.rank?.ordinal ?: 10000 + val bestRank = sim.simulate(fastVectorOf(x, y, z)).minOrNull()?.rank?.ordinal ?: 100000 val score = 1 / (bestRank.toDouble() + 1) // info("Calculating heuristic at $x, $y, $z with score $score") return score diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt index e002b712c..de2d0b0bb 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt @@ -4,6 +4,8 @@ import com.lambda.context.SafeContext import com.lambda.interaction.construction.Blueprint import com.lambda.interaction.construction.result.BuildResult import com.lambda.interaction.construction.simulation.BuildSimulator.simulate +import com.lambda.module.modules.client.TaskFlow +import com.lambda.task.Task import com.lambda.threading.runSafe import com.lambda.util.Communication.info import com.lambda.util.world.FastVector @@ -18,35 +20,16 @@ import net.minecraft.util.math.Vec3d data class Simulation(val blueprint: Blueprint) { private val cache: MutableMap> = mutableMapOf() private fun FastVector.toView(): Vec3d = toVec3d().add(0.5, 0.62, 0.5) -// private lateinit var player: ClientPlayerEntity - -// init { -// runSafe { -// this@Simulation.player = player -// BlockPos.iterateOutwards(player.blockPos, 5, 5, 5).forEach { pos -> -// val vec = pos.toFastVec() -// info("Preloading simulation at $vec") -// simulate(vec) -// } -// } -// } - -// val best: FastVector? get() = cache.filter { (_, results) -> -// results.isNotEmpty() && results.any { it.rank.ordinal < 4 } -// }.keys.minByOrNull { it.toVec3d().distanceTo(player.pos) } - -// fun best() = cache.filter { (_, results) -> -// results.isNotEmpty() && results.any { it.rank.ordinal < 3 } -// }.keys fun simulate(pos: FastVector): Set { return cache.computeIfAbsent(pos) { + val view = pos.toView() runSafe { + if (blueprint.isOutOfBounds(view) && blueprint.getClosestPointTo(view).distanceTo(view) > 10.0) return@computeIfAbsent emptySet() if (!playerFitsIn(Vec3d.ofBottomCenter(pos.toBlockPos()))) return@computeIfAbsent emptySet() } - blueprint.simulate(pos.toView(), reach = 3.5) + blueprint.simulate(view, reach = TaskFlow.interact.reach - 1) } -// return blueprint.simulate(pos.toView(), reach = 3.5) } private fun SafeContext.playerFitsIn(pos: Vec3d): Boolean { diff --git a/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt b/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt index 0a14b50db..6935857fa 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt @@ -83,9 +83,7 @@ class BuildTask @Ta5kBuilder constructor( val result = results.minOrNull() ?: return@listener when { - !result.rank.solvable -> { - success(Unit) - } + !result.rank.solvable -> success(Unit) result is BuildResult.NotVisible -> { if (pathing) BaritoneUtils.setGoalAndPath( BuildGoal(blueprint.simulation()) From d15a3664d62cabf995397c84f7bdeefffcdf7430 Mon Sep 17 00:00:00 2001 From: Constructor Date: Mon, 30 Sep 2024 05:47:16 +0200 Subject: [PATCH 03/39] Only allow ignoring blocks when they are in airspace --- .../interaction/construction/simulation/BuildSimulator.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt index ae36ed71f..b863010fc 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt @@ -74,7 +74,7 @@ object BuildSimulator { } /* block should be ignored */ - if (state.block in TaskFlow.ignoredBlocks) { + if (state.block in TaskFlow.ignoredBlocks && target.type == TargetState.Type.AIR) { return BuildResult.Ignored(pos) } From 462f2e7881ca05c97480e663e6571d9014efae7b Mon Sep 17 00:00:00 2001 From: Constructor Date: Tue, 1 Oct 2024 03:40:01 +0200 Subject: [PATCH 04/39] Fix collecting items --- .../lambda/interaction/construction/result/BreakResult.kt | 1 + .../kotlin/com/lambda/module/modules/player/HighwayTools.kt | 2 +- common/src/main/kotlin/com/lambda/task/tasks/BreakBlock.kt | 5 ++++- .../src/main/kotlin/com/lambda/task/tasks/PlaceContainer.kt | 6 +----- common/src/main/kotlin/com/lambda/util/item/ItemUtils.kt | 1 + 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/result/BreakResult.kt b/common/src/main/kotlin/com/lambda/interaction/construction/result/BreakResult.kt index aeccc87e5..1474a41bf 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/result/BreakResult.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/result/BreakResult.kt @@ -30,6 +30,7 @@ sealed class BreakResult : BuildResult() { private val color = Color(222, 0, 0, 100) var collectDrop = false + override val pausesParent get() = collectDrop override fun SafeContext.onStart() { breakBlock(context, collectDrop = collectDrop).onSuccess { _, _ -> diff --git a/common/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt b/common/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt index 2cdf67c5d..f52f601bd 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt @@ -28,7 +28,7 @@ object HighwayTools : Module( private val rimHeight by setting("Rim Height", 1, 0..6, 1) private val cornerBlock by setting("Corner Block", false, description = "Include corner blocks in the highway") private val ceiling by setting("Ceiling", false, description = "Smooth roof over the highway") - private val ceilingMaterial by setting("Ceiling Material", Blocks.OBSIDIAN, description = "Material to build the ceiling with") + private val ceilingMaterial by setting("Ceiling Material", Blocks.OBSIDIAN, description = "Material to build the ceiling with") { ceiling } private val distance by setting("Distance", -1, -1..1000000, 1, description = "Distance to build the highway (negative for infinite)") private val sliceSize by setting("Slice Size", 3, 1..5, 1, description = "Number of slices to build at once") private val material by setting("Highway Material", Blocks.OBSIDIAN, description = "Material to build the highway with") diff --git a/common/src/main/kotlin/com/lambda/task/tasks/BreakBlock.kt b/common/src/main/kotlin/com/lambda/task/tasks/BreakBlock.kt index 43cf19c17..008130418 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/BreakBlock.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/BreakBlock.kt @@ -76,6 +76,7 @@ class BreakBlock @Ta5kBuilder constructor( listener { drop?.let { itemDrop -> if (!world.entities.contains(itemDrop)) { + BaritoneUtils.cancel() success(itemDrop) return@listener } @@ -86,6 +87,7 @@ class BreakBlock @Ta5kBuilder constructor( }?.let { clickSlot(it.index, 1, SlotActionType.THROW) } + return@listener } BaritoneUtils.setGoalAndPath(GoalBlock(itemDrop.blockPos)) @@ -98,16 +100,17 @@ class BreakBlock @Ta5kBuilder constructor( if (done()) { state = State.COLLECTING if (!collectDrop) { + BaritoneUtils.cancel() success(null) } } } + // ToDo: Find out when the stack entity is filled with the item listener { if (collectDrop && it.entity is ItemEntity && it.entity.pos.isInRange(blockPos.toCenterPos(), 0.5) - ) { drop = it.entity } diff --git a/common/src/main/kotlin/com/lambda/task/tasks/PlaceContainer.kt b/common/src/main/kotlin/com/lambda/task/tasks/PlaceContainer.kt index 669032f13..626cb35b8 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/PlaceContainer.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/PlaceContainer.kt @@ -32,15 +32,11 @@ class PlaceContainer @Ta5kBuilder constructor( .simulate(player.getCameraPosVec(mc.tickDelta)) } -// val res = results.sorted() -// res - val succeeds = results.filterIsInstance().filter { canBeOpened(stack, it.blockPos, it.context.result.side) } val wrongStacks = results.filterIsInstance().filter { - val result = (it.context as? PlaceContext)?.result ?: return@filter false - canBeOpened(stack, it.blockPos, result.side) + canBeOpened(stack, it.blockPos, it.context.result.side) } (succeeds + wrongStacks).minOrNull()?.let { result -> build { diff --git a/common/src/main/kotlin/com/lambda/util/item/ItemUtils.kt b/common/src/main/kotlin/com/lambda/util/item/ItemUtils.kt index 9d6541cb7..4001e20b0 100644 --- a/common/src/main/kotlin/com/lambda/util/item/ItemUtils.kt +++ b/common/src/main/kotlin/com/lambda/util/item/ItemUtils.kt @@ -53,6 +53,7 @@ object ItemUtils { val misc = setOf( Items.SHEARS, + Items.FLINT_AND_STEEL, ) val tools = pickaxes + shovels + axes + hoes + swords + misc From 720c1206ad4f95f1f3b72ed265d38aacacc44ad9 Mon Sep 17 00:00:00 2001 From: Constructor Date: Tue, 1 Oct 2024 05:37:12 +0200 Subject: [PATCH 05/39] Added WorldEater --- .../construction/simulation/Simulation.kt | 6 +- .../module/modules/player/WorldEater.kt | 68 +++++++++++++++++++ .../com/lambda/task/tasks/BreakBlock.kt | 3 +- .../kotlin/com/lambda/task/tasks/BuildTask.kt | 7 ++ 4 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 common/src/main/kotlin/com/lambda/module/modules/player/WorldEater.kt diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt index de2d0b0bb..385f5bfa7 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt @@ -7,6 +7,7 @@ import com.lambda.interaction.construction.simulation.BuildSimulator.simulate import com.lambda.module.modules.client.TaskFlow import com.lambda.task.Task import com.lambda.threading.runSafe +import com.lambda.util.BlockUtils.blockState import com.lambda.util.Communication.info import com.lambda.util.world.FastVector import com.lambda.util.world.toBlockPos @@ -15,6 +16,7 @@ import com.lambda.util.world.toVec3d import net.minecraft.client.network.ClientPlayerEntity import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Box +import net.minecraft.util.math.Direction import net.minecraft.util.math.Vec3d data class Simulation(val blueprint: Blueprint) { @@ -26,7 +28,9 @@ data class Simulation(val blueprint: Blueprint) { val view = pos.toView() runSafe { if (blueprint.isOutOfBounds(view) && blueprint.getClosestPointTo(view).distanceTo(view) > 10.0) return@computeIfAbsent emptySet() - if (!playerFitsIn(Vec3d.ofBottomCenter(pos.toBlockPos()))) return@computeIfAbsent emptySet() + val blockPos = pos.toBlockPos() + if (!playerFitsIn(Vec3d.ofBottomCenter(blockPos))) return@computeIfAbsent emptySet() + if (!blockPos.down().blockState(world).isSideSolidFullSquare(world, blockPos, Direction.UP)) return@computeIfAbsent emptySet() } blueprint.simulate(view, reach = TaskFlow.interact.reach - 1) } diff --git a/common/src/main/kotlin/com/lambda/module/modules/player/WorldEater.kt b/common/src/main/kotlin/com/lambda/module/modules/player/WorldEater.kt new file mode 100644 index 000000000..de965c043 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/module/modules/player/WorldEater.kt @@ -0,0 +1,68 @@ +package com.lambda.module.modules.player + +import com.lambda.event.events.RenderEvent +import com.lambda.event.listener.SafeListener.Companion.listener +import com.lambda.graphics.renderer.esp.builders.buildOutline +import com.lambda.interaction.construction.Blueprint.Companion.toStructure +import com.lambda.interaction.construction.StaticBlueprint.Companion.toBlueprint +import com.lambda.interaction.construction.verify.TargetState +import com.lambda.module.Module +import com.lambda.module.tag.ModuleTag +import com.lambda.task.Task +import com.lambda.task.tasks.BuildTask.Companion.build +import com.lambda.util.BaritoneUtils +import net.minecraft.util.math.BlockBox +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Box +import java.awt.Color + +object WorldEater : Module( + name = "WorldEater", + description = "Eats the world", + defaultTags = setOf(ModuleTag.PLAYER, ModuleTag.AUTOMATION) +) { +// private val height by setting("Height", 4, 1..10, 1) +// private val width by setting("Width", 6, 1..30, 1) + private val pos1 by setting("Position 1", BlockPos(351, 104, 103)) + private val pos2 by setting("Position 2", BlockPos(361, 70, 113)) + private val layerSize by setting("Layer Size", 1, 1..10, 1) + private var runningTask: Task<*>? = null + private var area = BlockBox.create(pos1, pos2) + private val work = mutableListOf() + + init { + onEnable { + area = BlockBox.create(pos1, pos2) + val layerRanges = (area.minY..area.maxY step layerSize).reversed() + work.addAll(layerRanges.mapNotNull { y -> + if (y == area.minY) return@mapNotNull null + BlockBox(area.minX, y - layerSize, area.minZ, area.maxX, y, area.maxZ) + }) + + buildLayer() + } + + onDisable { + runningTask?.cancel() + runningTask = null + work.clear() + BaritoneUtils.cancel() + } + + listener { + it.renderer.buildOutline(Box.enclosing(pos1, pos2), Color.BLUE) + } + } + + private fun buildLayer() { + work.firstOrNull()?.let { box -> + runningTask = build { + box.toStructure(TargetState.Air) + .toBlueprint() + }.onSuccess { _, _ -> + work.removeFirstOrNull() + buildLayer() + }.start(null) + } ?: disable() + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/task/tasks/BreakBlock.kt b/common/src/main/kotlin/com/lambda/task/tasks/BreakBlock.kt index 008130418..36321f2b8 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/BreakBlock.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/BreakBlock.kt @@ -91,6 +91,7 @@ class BreakBlock @Ta5kBuilder constructor( } BaritoneUtils.setGoalAndPath(GoalBlock(itemDrop.blockPos)) + return@listener } ?: BaritoneUtils.cancel() if (isValid || !rotate || ctx.instantBreak) { @@ -98,7 +99,6 @@ class BreakBlock @Ta5kBuilder constructor( } if (done()) { - state = State.COLLECTING if (!collectDrop) { BaritoneUtils.cancel() success(null) @@ -113,6 +113,7 @@ class BreakBlock @Ta5kBuilder constructor( && it.entity.pos.isInRange(blockPos.toCenterPos(), 0.5) ) { drop = it.entity + state = State.COLLECTING } } } diff --git a/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt b/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt index 6935857fa..32f190dad 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt @@ -1,5 +1,6 @@ package com.lambda.task.tasks +import baritone.api.pathing.goals.GoalNear import com.lambda.Lambda.LOG import com.lambda.context.SafeContext import com.lambda.event.events.RenderEvent @@ -99,6 +100,12 @@ class BuildTask @Ta5kBuilder constructor( result.collectDrop = collectDrops } + if (!(result is BreakResult.Break && result.collectDrop)) { + if (pathing) BaritoneUtils.setGoalAndPath( + GoalNear(result.blockPos, 4) + ) + } + pending.add(result) result.start(this@BuildTask, pauseParent = result.pausesParent) } From 87e30a32f7490006c6b3e0ae2a62ef38316c3ef0 Mon Sep 17 00:00:00 2001 From: Constructor Date: Tue, 1 Oct 2024 06:42:12 +0200 Subject: [PATCH 06/39] Allow mining below player if support is provided --- .../interaction/construction/simulation/BuildSimulator.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt index b863010fc..f95f8755e 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt @@ -280,6 +280,8 @@ object BuildSimulator { val aabb = Box(pBox.minX, pBox.minY - 1.0E-6, pBox.minZ, pBox.maxX, pBox.minY, pBox.maxZ) world.findSupportingBlockPos(player, aabb).orElse(null)?.let { support -> if (support != pos) return@let + val belowSupport = support.down().blockState(world) + if (belowSupport.isSolidSurface(world, support, player, Direction.UP)) return@let acc.add(BreakResult.PlayerOnTop(pos, state)) return acc } From 094c874ccbaeb87f5f49c8d8c8d02344458ce422 Mon Sep 17 00:00:00 2001 From: Constructor Date: Mon, 7 Oct 2024 06:38:59 +0200 Subject: [PATCH 07/39] More dynamic configuration of the highway --- .../module/modules/player/HighwayTools.kt | 182 +++++++++++------- .../com/lambda/util/extension/Aliases.kt | 12 ++ .../kotlin/com/lambda/util/math/VecUtils.kt | 8 +- 3 files changed, 131 insertions(+), 71 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt b/common/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt index f52f601bd..6db43eea6 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt @@ -10,6 +10,10 @@ import com.lambda.util.BaritoneUtils import com.lambda.util.Communication.info import com.lambda.util.player.MovementUtils.octant import com.lambda.util.extension.Structure +import com.lambda.util.extension.moveY +import com.lambda.util.math.MathUtils.ceilToInt +import com.lambda.util.math.MathUtils.floorToInt +import com.lambda.util.math.VecUtils.rotateClockwise import com.lambda.util.world.StructureUtils.generateDirectionalTube import net.minecraft.block.Blocks import net.minecraft.util.math.BlockPos @@ -23,15 +27,20 @@ object HighwayTools : Module( description = "Auto highway builder", defaultTags = setOf(ModuleTag.PLAYER, ModuleTag.AUTOMATION) ) { - private val height by setting("Height", 4, 1..10, 1) + private val height by setting("Height", 4, 2..10, 1) private val width by setting("Width", 6, 1..30, 1) - private val rimHeight by setting("Rim Height", 1, 0..6, 1) - private val cornerBlock by setting("Corner Block", false, description = "Include corner blocks in the highway") - private val ceiling by setting("Ceiling", false, description = "Smooth roof over the highway") - private val ceilingMaterial by setting("Ceiling Material", Blocks.OBSIDIAN, description = "Material to build the ceiling with") { ceiling } - private val distance by setting("Distance", -1, -1..1000000, 1, description = "Distance to build the highway (negative for infinite)") + private val pavement by setting("Pavement", Material.Block, description = "Material for the pavement") + private val rimHeight by setting("Pavement Rim Height", 1, 0..6, 1) { pavement != Material.Any } + private val cornerBlock by setting("Corner", Corner.None, description = "Include corner blocks in the highway") { pavement != Material.Any } + private val pavementMaterial by setting("Pavement Material", Blocks.OBSIDIAN, description = "Material to build the highway with") { pavement == Material.Block } + private val floor by setting("Floor", Material.Any, description = "Material for the floor") + private val floorMaterial by setting("Floor Material", Blocks.NETHERRACK, description = "Material to build the floor with") { floor == Material.Block } + private val walls by setting("Walls", Material.Any, description = "Material for the walls") + private val wallMaterial by setting("Wall Material", Blocks.NETHERRACK, description = "Material to build the walls with") { walls == Material.Block } + private val ceiling by setting("Ceiling", Material.Any, description = "Material for the ceiling") + private val ceilingMaterial by setting("Ceiling Material", Blocks.OBSIDIAN, description = "Material to build the ceiling with") { ceiling == Material.Block } + private val distance by setting("Distance", -1, -1..1000000, 1, description = "Distance to build the highway/tunnel (negative for infinite)") private val sliceSize by setting("Slice Size", 3, 1..5, 1, description = "Number of slices to build at once") - private val material by setting("Highway Material", Blocks.OBSIDIAN, description = "Material to build the highway with") private var octant = EightWayDirection.NORTH private var distanceMoved = 0 @@ -39,6 +48,17 @@ object HighwayTools : Module( private var currentPos = BlockPos.ORIGIN private var runningTask: Task<*>? = null + enum class Material { + Any, + Solid, + Block, + } + + enum class Corner { + None, + Solid, + } + init { onEnable { octant = player.octant @@ -58,7 +78,7 @@ object HighwayTools : Module( distanceMoved += sliceSize var structure: Structure = mutableMapOf() - val slice = highwaySlice() + val slice = generateSlice() repeat(sliceSize) { val vec = Vec3i(octant.offsetX, 0, octant.offsetZ) currentPos = currentPos.add(vec) @@ -77,93 +97,121 @@ object HighwayTools : Module( }.start(null) } - private fun highwaySlice(): Structure { + private fun generateSlice(): Structure { val structure = mutableMapOf() - val orthogonal = EightWayDirection.entries[(octant.ordinal + 2).mod(8)] - val center = (width / 2.0).roundToInt() + val orthogonal = octant.rotateClockwise(2) + val center = (width / 2.0).floorToInt() - // Area to clear + // Hole structure += generateDirectionalTube( orthogonal, width, height, -center, - -1, - ).associateWith { TargetState.Air } - - // Highway - structure += generateDirectionalTube( - orthogonal, - width, - 1, - -center, - -1, - ).associateWith { TargetState.Block(material) } - - // Left rim - structure += generateDirectionalTube( - orthogonal, - 1, - rimHeight, - -center + width - 1, - 0, - ).associateWith { TargetState.Block(material) } - - // Right rim - structure += generateDirectionalTube( - orthogonal, - 1, - rimHeight, - -center, 0, - ).associateWith { TargetState.Block(material) } + ).associateWith { TargetState.Air } - if (!cornerBlock) { - structure -= generateDirectionalTube( + if (pavement != Material.Any) { + structure += generateDirectionalTube( orthogonal, + width, 1, + -center, + 0, + ).associateWith { target(pavement, pavementMaterial) } + + // Left rim + structure += generateDirectionalTube( + orthogonal, 1, + rimHeight, -center + width - 1, - -1, - ) + 1, + ).associateWith { target(pavement, pavementMaterial) } - structure -= generateDirectionalTube( + // Right rim + structure += generateDirectionalTube( orthogonal, 1, + rimHeight, + -center, + 1, + ).associateWith { target(pavement, pavementMaterial) } + + if (cornerBlock == Corner.None) { + // Support for the left rim + structure += generateDirectionalTube( + orthogonal, + 1, + 1, + -center + width - 1, + 0, + ).associateWith { TargetState.Support(Direction.UP) } + + // Support for the right rim + structure += generateDirectionalTube( + orthogonal, + 1, + 1, + -center, + 0, + ).associateWith { TargetState.Support(Direction.UP) } + } + } + + if (ceiling != Material.Any) { + structure += generateDirectionalTube( + orthogonal, + width, 1, -center, - -1, - ) + height, + ).associateWith { target(ceiling, ceilingMaterial) } } - // Support for the left corner - structure += generateDirectionalTube( - orthogonal, - 1, - 1, - -center + width - 1, - -1, - ).associateWith { TargetState.Support(Direction.UP) } + if (walls != Material.Any) { + // Left wall + structure += generateDirectionalTube( + orthogonal, + 1, + height, + -center + width, + 0, + ).associateWith { target(walls, wallMaterial) } - // Support for the right corner - structure += generateDirectionalTube( - orthogonal, - 1, - 1, - -center, - -1, - ).associateWith { TargetState.Support(Direction.UP) } + // Right wall + structure += generateDirectionalTube( + orthogonal, + 1, + height, + -center - 1, + 0, + ).associateWith { target(walls, wallMaterial) } + } - if (ceiling) { + if (floor != Material.Any) { structure += generateDirectionalTube( orthogonal, width, 1, -center, - height - 1, - ).associateWith { TargetState.Block(ceilingMaterial) } + -1, + ).associateWith { target(floor, floorMaterial) } + } + + val transformed = when { + pavement != Material.Any -> structure.moveY(-1) + else -> structure } - return structure + return transformed + } + + private fun target(target: Material, material: net.minecraft.block.Block): TargetState { + return when (target) { + Material.Solid -> TargetState.Solid + Material.Block -> TargetState.Block(material) + else -> throw IllegalStateException("Invalid material") + } } } diff --git a/common/src/main/kotlin/com/lambda/util/extension/Aliases.kt b/common/src/main/kotlin/com/lambda/util/extension/Aliases.kt index db4b407a7..047b3b7a8 100644 --- a/common/src/main/kotlin/com/lambda/util/extension/Aliases.kt +++ b/common/src/main/kotlin/com/lambda/util/extension/Aliases.kt @@ -7,3 +7,15 @@ import net.minecraft.util.math.BlockPos typealias CommandBuilder = LiteralArgumentBuilder typealias Structure = Map + +fun Structure.move(offset: BlockPos): Structure = + mapKeys { (pos, _) -> pos.add(offset) } + +fun Structure.moveX(x: Int): Structure = + mapKeys { (pos, _) -> pos.east(x) } + +fun Structure.moveY(y: Int): Structure = + mapKeys { (pos, _) -> pos.up(y) } + +fun Structure.moveZ(z: Int): Structure = + mapKeys { (pos, _) -> pos.south(z) } diff --git a/common/src/main/kotlin/com/lambda/util/math/VecUtils.kt b/common/src/main/kotlin/com/lambda/util/math/VecUtils.kt index e77da651a..3fd76e8da 100644 --- a/common/src/main/kotlin/com/lambda/util/math/VecUtils.kt +++ b/common/src/main/kotlin/com/lambda/util/math/VecUtils.kt @@ -1,13 +1,10 @@ package com.lambda.util.math import com.lambda.util.math.MathUtils.sq -import com.lambda.util.world.FastVector -import com.lambda.util.world.x -import com.lambda.util.world.y -import com.lambda.util.world.z import net.minecraft.entity.Entity import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Direction +import net.minecraft.util.math.EightWayDirection import net.minecraft.util.math.Vec3d import net.minecraft.util.math.Vec3i import kotlin.math.pow @@ -25,6 +22,9 @@ object VecUtils { val Direction.hitVecOffset get() = CENTER + vector.vec3d * 0.5 + fun EightWayDirection.rotateClockwise(steps: Int) = + EightWayDirection.entries[(ordinal + steps) % 8] + infix fun Vec3d.dist(other: Vec3d): Double = this.distanceTo(other) infix fun Vec3d.distSq(other: Vec3d): Double = this.squaredDistanceTo(other) From 1a44be4c683b8a4777bde92094363d9b57ce19b1 Mon Sep 17 00:00:00 2001 From: Constructor Date: Mon, 7 Oct 2024 06:41:02 +0200 Subject: [PATCH 08/39] Less confusing material category --- .../module/modules/player/HighwayTools.kt | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt b/common/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt index 6db43eea6..2ad61dcd0 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt @@ -11,7 +11,6 @@ import com.lambda.util.Communication.info import com.lambda.util.player.MovementUtils.octant import com.lambda.util.extension.Structure import com.lambda.util.extension.moveY -import com.lambda.util.math.MathUtils.ceilToInt import com.lambda.util.math.MathUtils.floorToInt import com.lambda.util.math.VecUtils.rotateClockwise import com.lambda.util.world.StructureUtils.generateDirectionalTube @@ -20,7 +19,6 @@ import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Direction import net.minecraft.util.math.EightWayDirection import net.minecraft.util.math.Vec3i -import kotlin.math.roundToInt object HighwayTools : Module( name = "HighwayTools", @@ -30,14 +28,14 @@ object HighwayTools : Module( private val height by setting("Height", 4, 2..10, 1) private val width by setting("Width", 6, 1..30, 1) private val pavement by setting("Pavement", Material.Block, description = "Material for the pavement") - private val rimHeight by setting("Pavement Rim Height", 1, 0..6, 1) { pavement != Material.Any } - private val cornerBlock by setting("Corner", Corner.None, description = "Include corner blocks in the highway") { pavement != Material.Any } + private val rimHeight by setting("Pavement Rim Height", 1, 0..6, 1) { pavement != Material.None } + private val cornerBlock by setting("Corner", Corner.None, description = "Include corner blocks in the highway") { pavement != Material.None } private val pavementMaterial by setting("Pavement Material", Blocks.OBSIDIAN, description = "Material to build the highway with") { pavement == Material.Block } - private val floor by setting("Floor", Material.Any, description = "Material for the floor") + private val floor by setting("Floor", Material.None, description = "Material for the floor") private val floorMaterial by setting("Floor Material", Blocks.NETHERRACK, description = "Material to build the floor with") { floor == Material.Block } - private val walls by setting("Walls", Material.Any, description = "Material for the walls") + private val walls by setting("Walls", Material.None, description = "Material for the walls") private val wallMaterial by setting("Wall Material", Blocks.NETHERRACK, description = "Material to build the walls with") { walls == Material.Block } - private val ceiling by setting("Ceiling", Material.Any, description = "Material for the ceiling") + private val ceiling by setting("Ceiling", Material.None, description = "Material for the ceiling") private val ceilingMaterial by setting("Ceiling Material", Blocks.OBSIDIAN, description = "Material to build the ceiling with") { ceiling == Material.Block } private val distance by setting("Distance", -1, -1..1000000, 1, description = "Distance to build the highway/tunnel (negative for infinite)") private val sliceSize by setting("Slice Size", 3, 1..5, 1, description = "Number of slices to build at once") @@ -49,7 +47,7 @@ object HighwayTools : Module( private var runningTask: Task<*>? = null enum class Material { - Any, + None, Solid, Block, } @@ -111,7 +109,7 @@ object HighwayTools : Module( 0, ).associateWith { TargetState.Air } - if (pavement != Material.Any) { + if (pavement != Material.None) { structure += generateDirectionalTube( orthogonal, width, @@ -159,7 +157,7 @@ object HighwayTools : Module( } } - if (ceiling != Material.Any) { + if (ceiling != Material.None) { structure += generateDirectionalTube( orthogonal, width, @@ -169,7 +167,7 @@ object HighwayTools : Module( ).associateWith { target(ceiling, ceilingMaterial) } } - if (walls != Material.Any) { + if (walls != Material.None) { // Left wall structure += generateDirectionalTube( orthogonal, @@ -189,7 +187,7 @@ object HighwayTools : Module( ).associateWith { target(walls, wallMaterial) } } - if (floor != Material.Any) { + if (floor != Material.None) { structure += generateDirectionalTube( orthogonal, width, @@ -200,7 +198,7 @@ object HighwayTools : Module( } val transformed = when { - pavement != Material.Any -> structure.moveY(-1) + pavement != Material.None -> structure.moveY(-1) else -> structure } From ecb0418418fbef24e9a9a94fff26c217bdc2485f Mon Sep 17 00:00:00 2001 From: Constructor Date: Wed, 9 Oct 2024 03:32:47 +0200 Subject: [PATCH 09/39] Loading NBT structure files per command --- .../brigadier/argument/IdentifierArguments.kt | 117 ++++++++++++++++++ .../lambda/command/commands/BuildCommand.kt | 64 +++++++--- .../src/main/kotlin/com/lambda/core/Loader.kt | 4 +- .../construction/StructureManager.kt | 23 ++++ .../kotlin/com/lambda/task/tasks/BuildTask.kt | 2 +- .../kotlin/com/lambda/util/FolderRegister.kt | 1 + 6 files changed, 189 insertions(+), 22 deletions(-) create mode 100644 common/src/main/kotlin/com/lambda/brigadier/argument/IdentifierArguments.kt create mode 100644 common/src/main/kotlin/com/lambda/interaction/construction/StructureManager.kt diff --git a/common/src/main/kotlin/com/lambda/brigadier/argument/IdentifierArguments.kt b/common/src/main/kotlin/com/lambda/brigadier/argument/IdentifierArguments.kt new file mode 100644 index 000000000..96432cddb --- /dev/null +++ b/common/src/main/kotlin/com/lambda/brigadier/argument/IdentifierArguments.kt @@ -0,0 +1,117 @@ +package com.lambda.brigadier.argument + +/* + * Copyright 2024 The Quilt Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import com.lambda.brigadier.* +import com.lambda.brigadier.assumeSourceNotUsed +import net.minecraft.advancement.AdvancementEntry +import net.minecraft.command.argument.IdentifierArgumentType +import net.minecraft.loot.condition.LootCondition +import net.minecraft.loot.function.LootFunction +import net.minecraft.recipe.RecipeEntry +import net.minecraft.server.command.ServerCommandSource +import net.minecraft.util.Identifier + +/** + * Reads the [Identifier] value from the + * argument in the receiver [ArgumentReader]. + * + * @see IdentifierArgumentType.getIdentifier + */ +@JvmName("valueIdentifierArg") +@BrigadierDsl +fun DefaultArgumentReader.value(): Identifier { + return IdentifierArgumentType.getIdentifier(context.assumeSourceNotUsed(), name) +} + +/** + * Reads the [Identifier] value from the + * argument in the receiver [ArgumentReader] + * as an [AdvancementEntry]. + * + * @see IdentifierArgumentType.getAdvancementArgument + */ +@BrigadierDsl +fun ArgumentReader< + ServerCommandSource, + DefaultArgumentDescriptor< + IdentifierArgumentType + > + >.asAdvancement(): AdvancementEntry { + return IdentifierArgumentType.getAdvancementArgument(context, name) +} + +/** + * Reads the [Identifier] value from the + * argument in the receiver [ArgumentReader] + * as a [LootCondition]. + * + * @see IdentifierArgumentType.getPredicateArgument + */ +@BrigadierDsl +fun ArgumentReader< + ServerCommandSource, + DefaultArgumentDescriptor< + IdentifierArgumentType + > + >.asPredicate(): LootCondition { + return IdentifierArgumentType.getPredicateArgument(context, name) +} + +/** + * Reads the [Identifier] value from the + * argument in the receiver [ArgumentReader] + * as a [LootFunction]. + * + * @see IdentifierArgumentType.getItemModifierArgument + */ +@BrigadierDsl +fun ArgumentReader< + ServerCommandSource, + DefaultArgumentDescriptor< + IdentifierArgumentType + > + >.asItemModifier(): LootFunction { + return IdentifierArgumentType.getItemModifierArgument(context, name) +} + +/** + * Reads the [Identifier] value from the + * argument in the receiver [ArgumentReader] + * as a [RecipeEntry]. + * + * @see IdentifierArgumentType.getRecipeArgument + */ +@BrigadierDsl +fun ArgumentReader< + ServerCommandSource, + DefaultArgumentDescriptor< + IdentifierArgumentType + > + >.asRecipe(): RecipeEntry<*> { + return IdentifierArgumentType.getRecipeArgument(context, name) +} + +/** + * Creates an identifier argument with [name] as the parameter name. + */ +@BrigadierDsl +fun identifier( + name: String +): DefaultArgumentConstructor { + return argument(name, IdentifierArgumentType.identifier()) +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt b/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt index a065cf754..8783aeada 100644 --- a/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt +++ b/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt @@ -1,16 +1,24 @@ package com.lambda.command.commands +import com.lambda.brigadier.CommandResult +import com.lambda.brigadier.argument.identifier import com.lambda.brigadier.argument.literal +import com.lambda.brigadier.argument.value import com.lambda.brigadier.execute +import com.lambda.brigadier.executeWithResult import com.lambda.brigadier.required import com.lambda.command.LambdaCommand import com.lambda.interaction.construction.Blueprint.Companion.toStructure import com.lambda.interaction.construction.DynamicBlueprint.Companion.toBlueprint +import com.lambda.interaction.construction.StructureManager +import com.lambda.interaction.construction.StructureManager.templateManager import com.lambda.interaction.construction.verify.TargetState import com.lambda.task.tasks.BuildTask.Companion.build import com.lambda.threading.runSafe +import com.lambda.util.Communication.info import com.lambda.util.extension.CommandBuilder import net.minecraft.block.Blocks +import net.minecraft.util.Identifier import net.minecraft.util.math.BlockBox object BuildCommand : LambdaCommand( @@ -20,27 +28,43 @@ object BuildCommand : LambdaCommand( ) { override fun CommandBuilder.create() { required(literal("place")) { - execute { - runSafe { - val materials = setOf( - TargetState.Block(Blocks.NETHERRACK), - TargetState.Block(Blocks.AIR), - TargetState.Block(Blocks.COBBLESTONE), - TargetState.Block(Blocks.AIR), - ) - val facing = player.horizontalFacing - val pos = player.blockPos.add(facing.vector.multiply(2)) - - BlockBox.create(pos, pos.add(facing.rotateYClockwise().vector.multiply(3))) - .toStructure(TargetState.Block(Blocks.NETHERRACK)) - .toBlueprint { - it.mapValues { (_, _) -> - materials.elementAt((System.currentTimeMillis() / 5000).toInt() % materials.size) - } - } - .build(finishOnDone = false) - .start(null) + required(identifier("structure")) { structure -> + suggests { _, builder -> + templateManager.streamTemplates().forEach { + builder.suggest(it.path) + } + builder.buildFuture() + } + executeWithResult { + templateManager.getTemplate(structure().value()).ifPresent { template -> + info("Building structure: ${template.size} author: ${template.author}") + } + CommandResult.success() } + +// execute { +// runSafe { +// +//// val materials = setOf( +//// TargetState.Block(Blocks.NETHERRACK), +//// TargetState.Block(Blocks.AIR), +//// TargetState.Block(Blocks.COBBLESTONE), +//// TargetState.Block(Blocks.AIR), +//// ) +//// val facing = player.horizontalFacing +//// val pos = player.blockPos.add(facing.vector.multiply(2)) +//// +//// BlockBox.create(pos, pos.add(facing.rotateYClockwise().vector.multiply(3))) +//// .toStructure(TargetState.Block(Blocks.NETHERRACK)) +//// .toBlueprint { +//// it.mapValues { (_, _) -> +//// materials.elementAt((System.currentTimeMillis() / 5000).toInt() % materials.size) +//// } +//// } +//// .build(finishOnDone = false) +//// .start(null) +// } +// } } } } diff --git a/common/src/main/kotlin/com/lambda/core/Loader.kt b/common/src/main/kotlin/com/lambda/core/Loader.kt index f2bb399f4..9c321bad9 100644 --- a/common/src/main/kotlin/com/lambda/core/Loader.kt +++ b/common/src/main/kotlin/com/lambda/core/Loader.kt @@ -10,6 +10,7 @@ import com.lambda.gui.GuiConfigurable import com.lambda.gui.HudGuiConfigurable import com.lambda.interaction.PlayerPacketManager import com.lambda.interaction.RotationManager +import com.lambda.interaction.construction.StructureManager import com.lambda.interaction.material.ContainerManager import com.lambda.module.ModuleRegistry import com.lambda.sound.SoundRegistry @@ -37,7 +38,8 @@ object Loader { SoundRegistry, TimerManager, PingManager, - ContainerManager + ContainerManager, + StructureManager ) fun initialize() { diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/StructureManager.kt b/common/src/main/kotlin/com/lambda/interaction/construction/StructureManager.kt new file mode 100644 index 000000000..2e52b7c70 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/construction/StructureManager.kt @@ -0,0 +1,23 @@ +package com.lambda.interaction.construction + +import com.lambda.Lambda.mc +import com.lambda.core.Loadable +import com.lambda.util.FolderRegister +import net.minecraft.registry.Registries +import net.minecraft.structure.StructureTemplateManager + +object StructureManager : Loadable { + // ToDo: Rewrite the StructureTemplateManager: Remove clutter, clean file structure + lateinit var templateManager: StructureTemplateManager + + override fun load(): String { + templateManager = StructureTemplateManager( + mc.resourceManager, + mc.levelStorage.createSession(FolderRegister.structure.path), + mc.dataFixer, + Registries.BLOCK.readOnlyWrapper + ) + + return "StructureManager loaded ${templateManager.streamTemplates().count()} templates" + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt b/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt index 32f190dad..703ad171a 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt @@ -100,7 +100,7 @@ class BuildTask @Ta5kBuilder constructor( result.collectDrop = collectDrops } - if (!(result is BreakResult.Break && result.collectDrop)) { + if (result !is BreakResult.Break || !result.collectDrop) { if (pathing) BaritoneUtils.setGoalAndPath( GoalNear(result.blockPos, 4) ) diff --git a/common/src/main/kotlin/com/lambda/util/FolderRegister.kt b/common/src/main/kotlin/com/lambda/util/FolderRegister.kt index 86e698d57..c705f596c 100644 --- a/common/src/main/kotlin/com/lambda/util/FolderRegister.kt +++ b/common/src/main/kotlin/com/lambda/util/FolderRegister.kt @@ -32,6 +32,7 @@ object FolderRegister { val packetLogs: File = File(lambda, "packet-log") val replay: File = File(lambda, "replay") val cache: File = File(lambda, "cache") + val structure: File = File(lambda, "structure") fun File.createIfNotExists() { createFileIfNotExists(this.name, this.parentFile) From 7eacafcae2b792ba53b424e85d8df4e2493346db Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Thu, 10 Oct 2024 13:56:16 -0400 Subject: [PATCH 10/39] feat: StructureRegistry --- .../lambda/command/commands/BuildCommand.kt | 30 +++---- .../src/main/kotlin/com/lambda/core/Loader.kt | 2 - .../construction/StructureManager.kt | 23 ----- .../construction/StructureRegistry.kt | 85 +++++++++++++++++++ .../kotlin/com/lambda/util/extension/World.kt | 9 ++ 5 files changed, 106 insertions(+), 43 deletions(-) delete mode 100644 common/src/main/kotlin/com/lambda/interaction/construction/StructureManager.kt create mode 100644 common/src/main/kotlin/com/lambda/interaction/construction/StructureRegistry.kt diff --git a/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt b/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt index 8783aeada..24bb8c874 100644 --- a/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt +++ b/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt @@ -4,22 +4,12 @@ import com.lambda.brigadier.CommandResult import com.lambda.brigadier.argument.identifier import com.lambda.brigadier.argument.literal import com.lambda.brigadier.argument.value -import com.lambda.brigadier.execute import com.lambda.brigadier.executeWithResult import com.lambda.brigadier.required import com.lambda.command.LambdaCommand -import com.lambda.interaction.construction.Blueprint.Companion.toStructure -import com.lambda.interaction.construction.DynamicBlueprint.Companion.toBlueprint -import com.lambda.interaction.construction.StructureManager -import com.lambda.interaction.construction.StructureManager.templateManager -import com.lambda.interaction.construction.verify.TargetState -import com.lambda.task.tasks.BuildTask.Companion.build -import com.lambda.threading.runSafe +import com.lambda.interaction.construction.StructureRegistry import com.lambda.util.Communication.info import com.lambda.util.extension.CommandBuilder -import net.minecraft.block.Blocks -import net.minecraft.util.Identifier -import net.minecraft.util.math.BlockBox object BuildCommand : LambdaCommand( name = "Build", @@ -30,16 +20,20 @@ object BuildCommand : LambdaCommand( required(literal("place")) { required(identifier("structure")) { structure -> suggests { _, builder -> - templateManager.streamTemplates().forEach { - builder.suggest(it.path) - } + StructureRegistry.keys + .forEach { builder.suggest(it.path) } + builder.buildFuture() } + executeWithResult { - templateManager.getTemplate(structure().value()).ifPresent { template -> - info("Building structure: ${template.size} author: ${template.author}") + StructureRegistry.loadStructure(structure().value())?.let { template -> + info("Building structure: ${template.size}") + + return@executeWithResult CommandResult.success() } - CommandResult.success() + + CommandResult.failure("Structure not found") } // execute { @@ -68,4 +62,4 @@ object BuildCommand : LambdaCommand( } } } -} \ No newline at end of file +} diff --git a/common/src/main/kotlin/com/lambda/core/Loader.kt b/common/src/main/kotlin/com/lambda/core/Loader.kt index 9c321bad9..20eac33d5 100644 --- a/common/src/main/kotlin/com/lambda/core/Loader.kt +++ b/common/src/main/kotlin/com/lambda/core/Loader.kt @@ -10,7 +10,6 @@ import com.lambda.gui.GuiConfigurable import com.lambda.gui.HudGuiConfigurable import com.lambda.interaction.PlayerPacketManager import com.lambda.interaction.RotationManager -import com.lambda.interaction.construction.StructureManager import com.lambda.interaction.material.ContainerManager import com.lambda.module.ModuleRegistry import com.lambda.sound.SoundRegistry @@ -39,7 +38,6 @@ object Loader { TimerManager, PingManager, ContainerManager, - StructureManager ) fun initialize() { diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/StructureManager.kt b/common/src/main/kotlin/com/lambda/interaction/construction/StructureManager.kt deleted file mode 100644 index 2e52b7c70..000000000 --- a/common/src/main/kotlin/com/lambda/interaction/construction/StructureManager.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.lambda.interaction.construction - -import com.lambda.Lambda.mc -import com.lambda.core.Loadable -import com.lambda.util.FolderRegister -import net.minecraft.registry.Registries -import net.minecraft.structure.StructureTemplateManager - -object StructureManager : Loadable { - // ToDo: Rewrite the StructureTemplateManager: Remove clutter, clean file structure - lateinit var templateManager: StructureTemplateManager - - override fun load(): String { - templateManager = StructureTemplateManager( - mc.resourceManager, - mc.levelStorage.createSession(FolderRegister.structure.path), - mc.dataFixer, - Registries.BLOCK.readOnlyWrapper - ) - - return "StructureManager loaded ${templateManager.streamTemplates().count()} templates" - } -} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/StructureRegistry.kt b/common/src/main/kotlin/com/lambda/interaction/construction/StructureRegistry.kt new file mode 100644 index 000000000..08e3e3460 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/construction/StructureRegistry.kt @@ -0,0 +1,85 @@ +package com.lambda.interaction.construction + +import com.lambda.Lambda.mc +import com.lambda.util.Communication.logError +import com.lambda.util.FolderRegister +import com.lambda.util.extension.readNbtOrException +import net.minecraft.datafixer.DataFixTypes +import net.minecraft.nbt.NbtCompound +import net.minecraft.nbt.NbtHelper +import net.minecraft.nbt.NbtIo +import net.minecraft.nbt.NbtSizeTracker +import net.minecraft.registry.Registries +import net.minecraft.structure.StructureTemplate +import net.minecraft.util.Identifier +import net.minecraft.util.PathUtil +import net.minecraft.util.WorldSavePath +import java.nio.file.Files +import java.nio.file.LinkOption +import java.nio.file.StandardOpenOption +import java.util.concurrent.ConcurrentHashMap +import kotlin.io.path.inputStream +import kotlin.io.path.notExists + +@Suppress("JavaIoSerializableObjectMustHaveReadResolve") +object StructureRegistry : ConcurrentHashMap() { + private val levelSession = mc.levelStorage.createSession(FolderRegister.structure.path) + private val generatedPath = levelSession.getDirectory(WorldSavePath.ROOT).normalize() + + /** + * Loads a structure from disk based on the provided [id]. + * + * @param id The identifier of the structure to load. + * @return The loaded [StructureTemplate], or null if the structure is not found. + */ + fun loadStructure(id: Identifier): StructureTemplate? { + val path = PathUtil.getResourcePath(generatedPath, id.path, ".nbt") + + return if (!Files.isDirectory(generatedPath, LinkOption.NOFOLLOW_LINKS) || path.notExists()) null + else computeIfAbsent(id) { + val compound = path.inputStream(StandardOpenOption.READ) + .use { template -> + NbtIo.readCompressed( + template, + NbtSizeTracker.ofUnlimitedBytes() + ).also { template.close() } + } + + createStructure(nbt = compound) + } + } + + /** + * Creates a [StructureTemplate] from the provided NBT data. + * + * @param nbt The [NbtCompound] containing the structure's data. + * @return The created [StructureTemplate], or null if there was an error. + */ + private fun createStructure(nbt: NbtCompound): StructureTemplate? { + val template = StructureTemplate() + val version = NbtHelper.getDataVersion(nbt, 500) + + template.readNbtOrException( + Registries.BLOCK.readOnlyWrapper, + DataFixTypes.STRUCTURE.update(mc.dataFixer, nbt, version), + )?.let { err -> + this@StructureRegistry.logError("Could not create structure from file", err.message ?: "") + return null + } + + return template + } + + /** + * Saves the provided [structure] to disk under the specified [name]. + * + * @param name The name of the structure file (without the ".nbt" extension). + * @param structure The [StructureTemplate] to save. + */ + fun saveStructure(name: String, structure: StructureTemplate) { + val path = PathUtil.getResourcePath(generatedPath, name, ".nbt") + val compound = structure.writeNbt(NbtCompound()) + + NbtIo.writeCompressed(compound, path) + } +} diff --git a/common/src/main/kotlin/com/lambda/util/extension/World.kt b/common/src/main/kotlin/com/lambda/util/extension/World.kt index d3824b010..1e49cacdd 100644 --- a/common/src/main/kotlin/com/lambda/util/extension/World.kt +++ b/common/src/main/kotlin/com/lambda/util/extension/World.kt @@ -4,10 +4,14 @@ import com.lambda.util.world.FastVector import com.lambda.util.world.x import com.lambda.util.world.y import com.lambda.util.world.z +import net.minecraft.block.Block import net.minecraft.block.BlockState import net.minecraft.block.Blocks import net.minecraft.fluid.FluidState import net.minecraft.fluid.Fluids +import net.minecraft.nbt.NbtCompound +import net.minecraft.registry.RegistryEntryLookup +import net.minecraft.structure.StructureTemplate import net.minecraft.world.World fun World.getBlockState(x: Int, y: Int, z: Int): BlockState { @@ -34,3 +38,8 @@ fun World.getFluidState(x: Int, y: Int, z: Int): FluidState { fun World.getBlockState(vec: FastVector): BlockState = getBlockState(vec.x, vec.y, vec.z) fun World.getFluidState(vec: FastVector): FluidState = getFluidState(vec.x, vec.y, vec.z) + +fun StructureTemplate.readNbtOrException( + lookup: RegistryEntryLookup, + nbt: NbtCompound, +): Throwable? = runCatching { readNbt(lookup, nbt) }.exceptionOrNull() From 94efb7056d679343a986ea4b2f3e6eb4291f1ce0 Mon Sep 17 00:00:00 2001 From: Constructor Date: Fri, 11 Oct 2024 03:59:47 +0200 Subject: [PATCH 11/39] Proper structure template file discovery --- .../lambda/command/commands/BuildCommand.kt | 22 +++- .../construction/StructureRegistry.kt | 113 ++++++++++++++---- 2 files changed, 110 insertions(+), 25 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt b/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt index 24bb8c874..47f7e344c 100644 --- a/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt +++ b/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt @@ -7,9 +7,15 @@ import com.lambda.brigadier.argument.value import com.lambda.brigadier.executeWithResult import com.lambda.brigadier.required import com.lambda.command.LambdaCommand +import com.lambda.interaction.construction.Blueprint.Companion.toStructure +import com.lambda.interaction.construction.DynamicBlueprint.Companion.toBlueprint +import com.lambda.interaction.construction.StaticBlueprint.Companion.toBlueprint import com.lambda.interaction.construction.StructureRegistry +import com.lambda.task.tasks.BuildTask.Companion.build +import com.lambda.threading.runSafe import com.lambda.util.Communication.info import com.lambda.util.extension.CommandBuilder +import com.lambda.util.extension.move object BuildCommand : LambdaCommand( name = "Build", @@ -20,17 +26,25 @@ object BuildCommand : LambdaCommand( required(literal("place")) { required(identifier("structure")) { structure -> suggests { _, builder -> - StructureRegistry.keys + StructureRegistry.streamTemplates() .forEach { builder.suggest(it.path) } builder.buildFuture() } executeWithResult { - StructureRegistry.loadStructure(structure().value())?.let { template -> - info("Building structure: ${template.size}") + runSafe { + val id = structure().value() + StructureRegistry.loadStructure(id)?.let { template -> + info("Building structure ${id.path} with size ${template.size.toShortString()} by ${template.author}") + template.toStructure() + .move(player.blockPos) + .toBlueprint() + .build() + .start(null) - return@executeWithResult CommandResult.success() + return@executeWithResult CommandResult.success() + } } CommandResult.failure("Structure not found") diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/StructureRegistry.kt b/common/src/main/kotlin/com/lambda/interaction/construction/StructureRegistry.kt index 08e3e3460..bf262a3f0 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/StructureRegistry.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/StructureRegistry.kt @@ -14,17 +14,15 @@ import net.minecraft.structure.StructureTemplate import net.minecraft.util.Identifier import net.minecraft.util.PathUtil import net.minecraft.util.WorldSavePath -import java.nio.file.Files -import java.nio.file.LinkOption -import java.nio.file.StandardOpenOption +import java.nio.file.* +import kotlin.io.path.* import java.util.concurrent.ConcurrentHashMap -import kotlin.io.path.inputStream -import kotlin.io.path.notExists +import kotlin.streams.asSequence @Suppress("JavaIoSerializableObjectMustHaveReadResolve") object StructureRegistry : ConcurrentHashMap() { private val levelSession = mc.levelStorage.createSession(FolderRegister.structure.path) - private val generatedPath = levelSession.getDirectory(WorldSavePath.ROOT).normalize() + private val structurePath = levelSession.getDirectory(WorldSavePath.ROOT).normalize() /** * Loads a structure from disk based on the provided [id]. @@ -33,19 +31,19 @@ object StructureRegistry : ConcurrentHashMap() { * @return The loaded [StructureTemplate], or null if the structure is not found. */ fun loadStructure(id: Identifier): StructureTemplate? { - val path = PathUtil.getResourcePath(generatedPath, id.path, ".nbt") + val path = PathUtil.getResourcePath(structurePath, id.path, ".nbt") - return if (!Files.isDirectory(generatedPath, LinkOption.NOFOLLOW_LINKS) || path.notExists()) null + return if (!structurePath.isDirectory() || path.notExists()) null else computeIfAbsent(id) { - val compound = path.inputStream(StandardOpenOption.READ) - .use { template -> - NbtIo.readCompressed( - template, - NbtSizeTracker.ofUnlimitedBytes() - ).also { template.close() } + path.inputStream().use { templateStream -> + val compound = NbtIo.readCompressed(templateStream, NbtSizeTracker.ofUnlimitedBytes()) + if (compound.isValidStructureTemplate()) { + createStructure(compound) + } else { + logError("Invalid structure template: ${path.fileName}", "File does not match template format") + null } - - createStructure(nbt = compound) + } } } @@ -61,9 +59,9 @@ object StructureRegistry : ConcurrentHashMap() { template.readNbtOrException( Registries.BLOCK.readOnlyWrapper, - DataFixTypes.STRUCTURE.update(mc.dataFixer, nbt, version), - )?.let { err -> - this@StructureRegistry.logError("Could not create structure from file", err.message ?: "") + DataFixTypes.STRUCTURE.update(mc.dataFixer, nbt, version) + )?.let { error -> + logError("Could not create structure from file", error.message ?: "") return null } @@ -77,9 +75,82 @@ object StructureRegistry : ConcurrentHashMap() { * @param structure The [StructureTemplate] to save. */ fun saveStructure(name: String, structure: StructureTemplate) { - val path = PathUtil.getResourcePath(generatedPath, name, ".nbt") + val path = PathUtil.getResourcePath(structurePath, name, ".nbt") val compound = structure.writeNbt(NbtCompound()) - NbtIo.writeCompressed(compound, path) + Files.createDirectories(path.parent) // Ensure parent directories exist + path.outputStream().use { output -> + NbtIo.writeCompressed(compound, output) + } } + + /** + * Streams all available structure templates from the directory. + * + * @return A [Sequence] of [Identifier]s of the available templates. + */ + fun streamTemplates(): Sequence { + return if (!structurePath.isDirectory()) { + emptySequence() + } else { + try { + structurePath.walk().filter { it.isRegularFile() && it.extension == "nbt" } + .filter { it.isValidNbtStructure() } + .mapNotNull { it.toIdentifier() } + } catch (e: Exception) { + logError("Error streaming structure templates", e) + emptySequence() + } + } + } + + /** + * Converts a file [Path] to an [Identifier]. + * + * @param this@pathToIdentifier The file path to convert. + * @return The resulting [Identifier], or null if the path is invalid. + */ + private fun Path.toIdentifier(): Identifier? { + return try { + val relativePath = structurePath.relativize(this).invariantSeparatorsPathString + val namespace = "minecraft" + val pathWithoutExtension = relativePath.removeSuffix(".nbt") + Identifier(namespace, pathWithoutExtension) + } catch (e: Exception) { + this@StructureRegistry.logError("Invalid path for structure template", e.message ?: "") + null + } + } + + /** + * Walks through the [Path] hierarchy recursively and returns a [Sequence] of paths. + */ + private fun Path.walk(): Sequence = Files.walk(this).asSequence() + + /** + * Checks whether the NBT file at the given [this@isValidNbtStructure] is a valid Minecraft structure template. + * + * @param this@isValidNbtStructure The path to the NBT file. + * @return True if the NBT file is a valid structure template, false otherwise. + */ + private fun Path.isValidNbtStructure() = + runCatching { + inputStream().use { input -> + NbtIo.readCompressed(input, NbtSizeTracker.ofUnlimitedBytes()) + .isValidStructureTemplate() + } + }.getOrDefault(false) + + /** + * Verifies that the provided NBT data represents a valid Minecraft structure template. + * + * @param this@isValidStructureTemplate The [NbtCompound] to validate. + * @return True if the NBT contains valid structure template data, false otherwise. + */ + private fun NbtCompound.isValidStructureTemplate() = + contains("DataVersion") + && contains("blocks") + && contains("entities") + && contains("palette") + && contains("size") } From 6ff508391c502721fdb416a98f334fdfd2bd0add Mon Sep 17 00:00:00 2001 From: Constructor Date: Fri, 11 Oct 2024 04:01:03 +0200 Subject: [PATCH 12/39] Ignore unobtainable block properties --- .../lambda/interaction/construction/result/PlaceResult.kt | 3 ++- .../interaction/construction/simulation/BuildSimulator.kt | 6 ++++-- .../lambda/interaction/construction/verify/TargetState.kt | 5 ++++- .../kotlin/com/lambda/module/modules/client/TaskFlow.kt | 8 ++++++++ 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/result/PlaceResult.kt b/common/src/main/kotlin/com/lambda/interaction/construction/result/PlaceResult.kt index f1d281ddc..a8184ca22 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/result/PlaceResult.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/result/PlaceResult.kt @@ -62,7 +62,8 @@ sealed class PlaceResult : BuildResult() { data class NoIntegrity( override val blockPos: BlockPos, val expected: BlockState, - val simulated: ItemPlacementContext + val simulated: ItemPlacementContext, + val actual: BlockState? = null ) : Drawable, PlaceResult() { override val rank = Rank.PLACE_NO_INTEGRITY private val color = Color(252, 3, 3, 100) diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt index f95f8755e..38b13e449 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt @@ -94,7 +94,7 @@ object BuildSimulator { } /* block is unbreakable, so it cant be broken or replaced */ - if (state.getHardness(world, pos) < 0) { + if (state.getHardness(world, pos) < 0 && !player.isCreative) { return BuildResult.Unbreakable(pos, state) } @@ -222,7 +222,9 @@ object BuildSimulator { } if (!target.matches(resultState, pos, world)) { - acc.add(PlaceResult.NoIntegrity(pos, resultState, context)) + acc.add(PlaceResult.NoIntegrity( + pos, resultState, context, (target as? TargetState.State)?.blockState) + ) return@forEach } diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt b/common/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt index 25765f88b..9b87f217e 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt @@ -8,6 +8,7 @@ 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.Properties import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Direction @@ -46,7 +47,9 @@ sealed class TargetState(val type: Type) : StateMatcher { data class State(val blockState: BlockState) : TargetState(Type.STATE) { override fun matches(state: BlockState, pos: BlockPos, world: ClientWorld) = - state == blockState + state.block == blockState.block && state.properties.all { + it in TaskFlow.defaultIgnoreTags || state[it] == blockState[it] + } override fun getStack(world: ClientWorld, pos: BlockPos): ItemStack = blockState.block.getPickStack(world, pos, blockState) } diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/TaskFlow.kt b/common/src/main/kotlin/com/lambda/module/modules/client/TaskFlow.kt index 02aa12c66..52b7758e2 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/TaskFlow.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/TaskFlow.kt @@ -7,6 +7,7 @@ import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.BlockUtils.allSigns import com.lambda.util.item.ItemUtils +import net.minecraft.state.property.Properties object TaskFlow : Module( name = "TaskFlow", @@ -32,4 +33,11 @@ object TaskFlow : Module( } val disposables by setting("Disposables", ItemUtils.defaultDisposables) val ignoredBlocks by setting("Ignored Blocks", allSigns) + val defaultIgnoreTags = setOf( + Properties.DISTANCE_1_7, + Properties.PERSISTENT, + Properties.WATERLOGGED, + Properties.STAIR_SHAPE + ) +// val ignoredTags by setting("Ignored Tags", defaultIgnoreTags) } \ No newline at end of file From 7a77cc81797652d4345cf51881ea39d494ac3f6c Mon Sep 17 00:00:00 2001 From: Constructor Date: Fri, 11 Oct 2024 04:01:47 +0200 Subject: [PATCH 13/39] Sneak when placing onto interaction blocks --- common/src/main/kotlin/com/lambda/task/Task.kt | 2 +- .../main/kotlin/com/lambda/task/tasks/PlaceBlock.kt | 10 ++++++++++ common/src/main/kotlin/com/lambda/util/BlockUtils.kt | 2 ++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/common/src/main/kotlin/com/lambda/task/Task.kt b/common/src/main/kotlin/com/lambda/task/Task.kt index 9117bffb8..970c20648 100644 --- a/common/src/main/kotlin/com/lambda/task/Task.kt +++ b/common/src/main/kotlin/com/lambda/task/Task.kt @@ -198,7 +198,7 @@ abstract class Task : Nameable { if (state == State.CANCELLED) return cancelSubTasks() - state = State.CANCELLED + if (this !is RootTask) state = State.CANCELLED stopListening() runSafe { onCancel() } LOG.info("$identifier was cancelled") diff --git a/common/src/main/kotlin/com/lambda/task/tasks/PlaceBlock.kt b/common/src/main/kotlin/com/lambda/task/tasks/PlaceBlock.kt index 355f70e4b..df81719c6 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/PlaceBlock.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/PlaceBlock.kt @@ -3,6 +3,7 @@ package com.lambda.task.tasks import com.lambda.Lambda.LOG import com.lambda.config.groups.InteractionConfig import com.lambda.context.SafeContext +import com.lambda.event.events.MovementEvent import com.lambda.event.events.RotationEvent import com.lambda.event.events.TickEvent import com.lambda.event.events.WorldEvent @@ -10,6 +11,7 @@ import com.lambda.event.listener.SafeListener.Companion.listener import com.lambda.interaction.construction.context.PlaceContext import com.lambda.module.modules.client.TaskFlow import com.lambda.task.Task +import com.lambda.util.BlockUtils import com.lambda.util.BlockUtils.blockState import com.lambda.util.Communication.warn import net.minecraft.block.BlockState @@ -68,6 +70,14 @@ class PlaceBlock @Ta5kBuilder constructor( findOutIfNeeded = true } + listener { + if (state != State.PLACING) return@listener + val hitBlock = ctx.result.blockPos.blockState(world).block + if (hitBlock in BlockUtils.interactionBlacklist) { + it.input.sneaking = true + } + } + listener { if (it.pos != ctx.resultingPos) return@listener diff --git a/common/src/main/kotlin/com/lambda/util/BlockUtils.kt b/common/src/main/kotlin/com/lambda/util/BlockUtils.kt index 115a8d2a1..ebbdafede 100644 --- a/common/src/main/kotlin/com/lambda/util/BlockUtils.kt +++ b/common/src/main/kotlin/com/lambda/util/BlockUtils.kt @@ -41,6 +41,8 @@ object BlockUtils { Blocks.SOUL_CAMPFIRE, Blocks.JUKEBOX, Blocks.NOTE_BLOCK, + Blocks.STRUCTURE_BLOCK, + Blocks.COMMAND_BLOCK, ).apply { addAll(shulkerBlocks) } val signs = setOf( From 50bdef817ffb1e4366ea33e3dac1862d69edf0cb Mon Sep 17 00:00:00 2001 From: Constructor Date: Fri, 11 Oct 2024 04:04:44 +0200 Subject: [PATCH 14/39] ToDo reminder for HALF properties --- .../lambda/command/commands/BuildCommand.kt | 28 ++----------------- .../construction/simulation/BuildSimulator.kt | 1 + 2 files changed, 3 insertions(+), 26 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt b/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt index 47f7e344c..79848b8e5 100644 --- a/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt +++ b/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt @@ -33,8 +33,8 @@ object BuildCommand : LambdaCommand( } executeWithResult { + val id = structure().value() runSafe { - val id = structure().value() StructureRegistry.loadStructure(id)?.let { template -> info("Building structure ${id.path} with size ${template.size.toShortString()} by ${template.author}") template.toStructure() @@ -47,32 +47,8 @@ object BuildCommand : LambdaCommand( } } - CommandResult.failure("Structure not found") + CommandResult.failure("Structure ${id.path} not found") } - -// execute { -// runSafe { -// -//// val materials = setOf( -//// TargetState.Block(Blocks.NETHERRACK), -//// TargetState.Block(Blocks.AIR), -//// TargetState.Block(Blocks.COBBLESTONE), -//// TargetState.Block(Blocks.AIR), -//// ) -//// val facing = player.horizontalFacing -//// val pos = player.blockPos.add(facing.vector.multiply(2)) -//// -//// BlockBox.create(pos, pos.add(facing.rotateYClockwise().vector.multiply(3))) -//// .toStructure(TargetState.Block(Blocks.NETHERRACK)) -//// .toBlueprint { -//// it.mapValues { (_, _) -> -//// materials.elementAt((System.currentTimeMillis() / 5000).toInt() % materials.size) -//// } -//// } -//// .build(finishOnDone = false) -//// .start(null) -// } -// } } } } diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt index 38b13e449..010195a61 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt @@ -131,6 +131,7 @@ object BuildSimulator { boxes.forEach { box -> val res = if (TaskFlow.interact.useRayCast) interact.resolution else 2 + // ToDo: If state has HALF property we need to scan the correct half of the block surface scanVisibleSurfaces(eye, box, setOf(hitSide), res) { side, vec -> if (eye distSq vec > reachSq) { misses.add(vec) From 55ef7d3c0bfdd1f6e86e04e3e6be7eaf73ae3856 Mon Sep 17 00:00:00 2001 From: Constructor Date: Sat, 19 Oct 2024 06:43:46 +0200 Subject: [PATCH 15/39] Support for half slab building --- .../lambda/command/commands/BuildCommand.kt | 32 ++++++++++-------- .../construction/simulation/BuildSimulator.kt | 12 +++++-- .../construction/simulation/Simulation.kt | 5 ++- .../visibilty/VisibilityChecker.kt | 33 +++++++++++++++++-- .../main/resources/lambda.mixins.common.json | 4 +-- 5 files changed, 61 insertions(+), 25 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt b/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt index 79848b8e5..f1d71b9f7 100644 --- a/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt +++ b/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt @@ -1,10 +1,12 @@ package com.lambda.command.commands import com.lambda.brigadier.CommandResult +import com.lambda.brigadier.argument.boolean import com.lambda.brigadier.argument.identifier import com.lambda.brigadier.argument.literal import com.lambda.brigadier.argument.value import com.lambda.brigadier.executeWithResult +import com.lambda.brigadier.optional import com.lambda.brigadier.required import com.lambda.command.LambdaCommand import com.lambda.interaction.construction.Blueprint.Companion.toStructure @@ -31,23 +33,25 @@ object BuildCommand : LambdaCommand( builder.buildFuture() } + optional(boolean("pathing")) { pathing -> + executeWithResult { + val id = structure().value() + val path = if (pathing != null) pathing().value() else false + runSafe { + StructureRegistry.loadStructure(id)?.let { template -> + info("Building structure ${id.path} with dimensions ${template.size.toShortString()} by ${template.author}") + template.toStructure() + .move(player.blockPos) + .toBlueprint() + .build(pathing = path) + .start(null) - executeWithResult { - val id = structure().value() - runSafe { - StructureRegistry.loadStructure(id)?.let { template -> - info("Building structure ${id.path} with size ${template.size.toShortString()} by ${template.author}") - template.toStructure() - .move(player.blockPos) - .toBlueprint() - .build() - .start(null) - - return@executeWithResult CommandResult.success() + return@executeWithResult CommandResult.success() + } } - } - CommandResult.failure("Structure ${id.path} not found") + CommandResult.failure("Structure ${id.path} not found") + } } } } diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt index 010195a61..11eb0918f 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt @@ -12,6 +12,8 @@ import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.material.ContainerManager.findBestAvailableTool import com.lambda.interaction.rotation.Rotation.Companion.rotationTo import com.lambda.interaction.rotation.RotationContext +import com.lambda.interaction.visibilty.VisibilityChecker +import com.lambda.interaction.visibilty.VisibilityChecker.ScanMode.Companion.scanMode import com.lambda.interaction.visibilty.VisibilityChecker.optimum import com.lambda.interaction.visibilty.VisibilityChecker.scanVisibleSurfaces import com.lambda.module.modules.client.TaskFlow @@ -31,6 +33,7 @@ import net.minecraft.item.BlockItem import net.minecraft.item.ItemPlacementContext import net.minecraft.item.ItemUsageContext import net.minecraft.registry.RegistryKeys +import net.minecraft.state.property.Properties import net.minecraft.util.Hand import net.minecraft.util.hit.BlockHitResult import net.minecraft.util.hit.HitResult @@ -130,9 +133,12 @@ object BuildSimulator { val reachSq = reach.pow(2) boxes.forEach { box -> - val res = if (TaskFlow.interact.useRayCast) interact.resolution else 2 - // ToDo: If state has HALF property we need to scan the correct half of the block surface - scanVisibleSurfaces(eye, box, setOf(hitSide), res) { side, vec -> + val res = if (TaskFlow.interact.useRayCast) interact.resolution else 4 + val half = (target as? TargetState.State) + ?.blockState + ?.getOrEmpty(Properties.SLAB_TYPE) + ?.scanMode ?: VisibilityChecker.ScanMode.BOTH + scanVisibleSurfaces(eye, box, setOf(hitSide), res, half) { side, vec -> if (eye distSq vec > reachSq) { misses.add(vec) return@scanVisibleSurfaces diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt index 385f5bfa7..df50b5a89 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt @@ -23,8 +23,8 @@ data class Simulation(val blueprint: Blueprint) { private val cache: MutableMap> = mutableMapOf() private fun FastVector.toView(): Vec3d = toVec3d().add(0.5, 0.62, 0.5) - fun simulate(pos: FastVector): Set { - return cache.computeIfAbsent(pos) { + fun simulate(pos: FastVector) = + cache.computeIfAbsent(pos) { val view = pos.toView() runSafe { if (blueprint.isOutOfBounds(view) && blueprint.getClosestPointTo(view).distanceTo(view) > 10.0) return@computeIfAbsent emptySet() @@ -34,7 +34,6 @@ data class Simulation(val blueprint: Blueprint) { } blueprint.simulate(view, reach = TaskFlow.interact.reach - 1) } - } private fun SafeContext.playerFitsIn(pos: Vec3d): Boolean { val pBox = player.boundingBox diff --git a/common/src/main/kotlin/com/lambda/interaction/visibilty/VisibilityChecker.kt b/common/src/main/kotlin/com/lambda/interaction/visibilty/VisibilityChecker.kt index cbc5beacb..a9190e51e 100644 --- a/common/src/main/kotlin/com/lambda/interaction/visibilty/VisibilityChecker.kt +++ b/common/src/main/kotlin/com/lambda/interaction/visibilty/VisibilityChecker.kt @@ -13,6 +13,8 @@ import com.lambda.util.math.VecUtils.distSq import com.lambda.util.extension.component6 import com.lambda.util.world.raycast.RayCastUtils.blockResult import com.lambda.util.world.raycast.RayCastUtils.entityResult +import net.minecraft.block.enums.BlockHalf +import net.minecraft.block.enums.SlabType import net.minecraft.entity.Entity import net.minecraft.util.hit.HitResult import net.minecraft.util.math.BlockPos @@ -96,7 +98,8 @@ object VisibilityChecker { eyes: Vec3d, box: Box, sides: Set = emptySet(), - resolution: Int = 30, + resolution: Int = 5, + half: ScanMode = ScanMode.BOTH, check: (Direction, Vec3d) -> Unit, ) { box.getVisibleSurfaces(eyes) @@ -106,10 +109,20 @@ object VisibilityChecker { val stepX = (maxX - minX) / resolution val stepY = (maxY - minY) / resolution val stepZ = (maxZ - minZ) / resolution + + val centerY = (minY + maxY) / 2 + val (startY, endY) = if (stepY != 0.0) { + when (half) { + ScanMode.TOP -> centerY + 0.01 to maxY + ScanMode.BOTTOM -> minY to centerY - 0.01 + ScanMode.BOTH -> minY to maxY + } + } else minY to maxY (0..resolution).forEach { i -> val x = if (stepX != 0.0) minX + stepX * i else minX - (0..resolution).forEach { j -> - val y = if (stepY != 0.0) minY + stepY * j else minY + (0..resolution).forEach inner@ { j -> + val y = if (stepY != 0.0) startY + stepY * j else startY + if (y > endY) return@inner val z = if (stepZ != 0.0) minZ + stepZ * ((if (stepX != 0.0) j else i)) else minZ check(side, Vec3d(x, y, z)) } @@ -117,6 +130,20 @@ object VisibilityChecker { } } + enum class ScanMode { + BOTH, TOP, BOTTOM; + + companion object { + val Optional.scanMode: ScanMode + get() = when (orElse(null)) { + SlabType.TOP -> TOP + SlabType.BOTTOM -> BOTTOM + SlabType.DOUBLE -> BOTH + else -> BOTH + } + } + } + val Set.optimum: Vec3d? get() = reduceOrNull { acc, vec3d -> acc.add(vec3d) diff --git a/common/src/main/resources/lambda.mixins.common.json b/common/src/main/resources/lambda.mixins.common.json index 5c745c805..f7b5416e6 100644 --- a/common/src/main/resources/lambda.mixins.common.json +++ b/common/src/main/resources/lambda.mixins.common.json @@ -11,6 +11,7 @@ "entity.ClientPlayerEntityMixin", "entity.ClientPlayInteractionManagerMixin", "entity.EntityMixin", + "entity.FireworkRocketEntityMixin", "entity.LivingEntityMixin", "entity.PlayerEntityMixin", "input.KeyBindingMixin", @@ -43,8 +44,7 @@ "render.WorldRendererMixin", "world.BlockCollisionSpliteratorMixin", "world.ClientChunkManagerMixin", - "world.ClientWorldMixin", - "entity.FireworkRocketEntityMixin" + "world.ClientWorldMixin" ], "injectors": { "defaultRequire": 1 From 670324fbc587a06548650e2f0faa4a6752f657c6 Mon Sep 17 00:00:00 2001 From: Constructor Date: Sun, 20 Oct 2024 23:27:18 +0200 Subject: [PATCH 16/39] Restructure --- .../kotlin/com/lambda/command/commands/BuildCommand.kt | 6 +++--- .../construction/{ => blueprint}/Blueprint.kt | 2 +- .../construction/{ => blueprint}/DynamicBlueprint.kt | 2 +- .../construction/{ => blueprint}/StaticBlueprint.kt | 2 +- .../{ => construction}/processing/ProcessingStep.kt | 2 +- .../interaction/construction/processing/TaskPlanner.kt | 4 ++++ .../interaction/construction/result/BuildResult.kt | 3 --- .../construction/simulation/BuildSimulator.kt | 3 +-- .../interaction/construction/simulation/Simulation.kt | 7 +------ .../com/lambda/interaction/processing/TaskPlanner.kt | 4 ---- .../com/lambda/module/modules/player/HighwayTools.kt | 2 +- .../kotlin/com/lambda/module/modules/player/Nuker.kt | 4 ++-- .../com/lambda/module/modules/player/WorldEater.kt | 4 ++-- .../src/main/kotlin/com/lambda/task/tasks/BuildTask.kt | 9 ++++----- .../main/kotlin/com/lambda/task/tasks/PlaceContainer.kt | 5 ++--- 15 files changed, 24 insertions(+), 35 deletions(-) rename common/src/main/kotlin/com/lambda/interaction/construction/{ => blueprint}/Blueprint.kt (97%) rename common/src/main/kotlin/com/lambda/interaction/construction/{ => blueprint}/DynamicBlueprint.kt (94%) rename common/src/main/kotlin/com/lambda/interaction/construction/{ => blueprint}/StaticBlueprint.kt (80%) rename common/src/main/kotlin/com/lambda/interaction/{ => construction}/processing/ProcessingStep.kt (58%) create mode 100644 common/src/main/kotlin/com/lambda/interaction/construction/processing/TaskPlanner.kt delete mode 100644 common/src/main/kotlin/com/lambda/interaction/processing/TaskPlanner.kt diff --git a/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt b/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt index f1d71b9f7..98c1f1fe3 100644 --- a/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt +++ b/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt @@ -9,10 +9,10 @@ import com.lambda.brigadier.executeWithResult import com.lambda.brigadier.optional import com.lambda.brigadier.required import com.lambda.command.LambdaCommand -import com.lambda.interaction.construction.Blueprint.Companion.toStructure -import com.lambda.interaction.construction.DynamicBlueprint.Companion.toBlueprint -import com.lambda.interaction.construction.StaticBlueprint.Companion.toBlueprint +import com.lambda.interaction.construction.blueprint.DynamicBlueprint.Companion.toBlueprint +import com.lambda.interaction.construction.blueprint.StaticBlueprint.Companion.toBlueprint import com.lambda.interaction.construction.StructureRegistry +import com.lambda.interaction.construction.blueprint.Blueprint.Companion.toStructure import com.lambda.task.tasks.BuildTask.Companion.build import com.lambda.threading.runSafe import com.lambda.util.Communication.info diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/Blueprint.kt b/common/src/main/kotlin/com/lambda/interaction/construction/blueprint/Blueprint.kt similarity index 97% rename from common/src/main/kotlin/com/lambda/interaction/construction/Blueprint.kt rename to common/src/main/kotlin/com/lambda/interaction/construction/blueprint/Blueprint.kt index b49f56acb..2a24d3b62 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/Blueprint.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/blueprint/Blueprint.kt @@ -1,4 +1,4 @@ -package com.lambda.interaction.construction +package com.lambda.interaction.construction.blueprint import com.lambda.interaction.construction.verify.TargetState import com.lambda.util.BlockUtils.blockPos diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/DynamicBlueprint.kt b/common/src/main/kotlin/com/lambda/interaction/construction/blueprint/DynamicBlueprint.kt similarity index 94% rename from common/src/main/kotlin/com/lambda/interaction/construction/DynamicBlueprint.kt rename to common/src/main/kotlin/com/lambda/interaction/construction/blueprint/DynamicBlueprint.kt index 61686acce..4da052602 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/DynamicBlueprint.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/blueprint/DynamicBlueprint.kt @@ -1,4 +1,4 @@ -package com.lambda.interaction.construction +package com.lambda.interaction.construction.blueprint import com.lambda.context.SafeContext import com.lambda.util.extension.Structure diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/StaticBlueprint.kt b/common/src/main/kotlin/com/lambda/interaction/construction/blueprint/StaticBlueprint.kt similarity index 80% rename from common/src/main/kotlin/com/lambda/interaction/construction/StaticBlueprint.kt rename to common/src/main/kotlin/com/lambda/interaction/construction/blueprint/StaticBlueprint.kt index 87cdb9b24..f9181c854 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/StaticBlueprint.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/blueprint/StaticBlueprint.kt @@ -1,4 +1,4 @@ -package com.lambda.interaction.construction +package com.lambda.interaction.construction.blueprint import com.lambda.util.extension.Structure diff --git a/common/src/main/kotlin/com/lambda/interaction/processing/ProcessingStep.kt b/common/src/main/kotlin/com/lambda/interaction/construction/processing/ProcessingStep.kt similarity index 58% rename from common/src/main/kotlin/com/lambda/interaction/processing/ProcessingStep.kt rename to common/src/main/kotlin/com/lambda/interaction/construction/processing/ProcessingStep.kt index 17b5c7ff4..f4ff3f61c 100644 --- a/common/src/main/kotlin/com/lambda/interaction/processing/ProcessingStep.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/processing/ProcessingStep.kt @@ -1,4 +1,4 @@ -package com.lambda.interaction.processing +package com.lambda.interaction.construction.processing import com.lambda.task.Task diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/processing/TaskPlanner.kt b/common/src/main/kotlin/com/lambda/interaction/construction/processing/TaskPlanner.kt new file mode 100644 index 000000000..a051ae95c --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/construction/processing/TaskPlanner.kt @@ -0,0 +1,4 @@ +package com.lambda.interaction.construction.processing + +object TaskPlanner { +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/result/BuildResult.kt b/common/src/main/kotlin/com/lambda/interaction/construction/result/BuildResult.kt index e44516de8..a15f89df6 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/result/BuildResult.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/result/BuildResult.kt @@ -2,15 +2,12 @@ package com.lambda.interaction.construction.result import baritone.api.pathing.goals.GoalBlock import baritone.api.pathing.goals.GoalNear -import baritone.process.BuilderProcess.GoalPlace import com.lambda.context.SafeContext -import com.lambda.interaction.construction.Blueprint import com.lambda.interaction.construction.context.BuildContext import com.lambda.interaction.material.ContainerManager.transfer import com.lambda.interaction.material.StackSelection.Companion.select import com.lambda.interaction.material.container.MainHandContainer import com.lambda.task.Task -import com.lambda.task.Task.Companion.failTask import com.lambda.util.BlockUtils.blockState import net.minecraft.block.BlockState import net.minecraft.item.Item diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt index 11eb0918f..aece66cdd 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt @@ -2,7 +2,7 @@ package com.lambda.interaction.construction.simulation import com.lambda.context.SafeContext import com.lambda.interaction.RotationManager -import com.lambda.interaction.construction.Blueprint +import com.lambda.interaction.construction.blueprint.Blueprint import com.lambda.interaction.construction.context.BreakContext import com.lambda.interaction.construction.context.PlaceContext import com.lambda.interaction.construction.result.BreakResult @@ -22,7 +22,6 @@ import com.lambda.util.BlockUtils import com.lambda.util.BlockUtils.blockState import com.lambda.util.BlockUtils.instantBreakable import com.lambda.util.BlockUtils.vecOf -import com.lambda.util.Communication.info import com.lambda.util.Communication.warn import com.lambda.util.item.ItemStackUtils.equal import com.lambda.util.math.VecUtils.distSq diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt index df50b5a89..9791aebb6 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt @@ -1,20 +1,15 @@ package com.lambda.interaction.construction.simulation import com.lambda.context.SafeContext -import com.lambda.interaction.construction.Blueprint +import com.lambda.interaction.construction.blueprint.Blueprint import com.lambda.interaction.construction.result.BuildResult import com.lambda.interaction.construction.simulation.BuildSimulator.simulate import com.lambda.module.modules.client.TaskFlow -import com.lambda.task.Task import com.lambda.threading.runSafe import com.lambda.util.BlockUtils.blockState -import com.lambda.util.Communication.info import com.lambda.util.world.FastVector import com.lambda.util.world.toBlockPos -import com.lambda.util.world.toFastVec import com.lambda.util.world.toVec3d -import net.minecraft.client.network.ClientPlayerEntity -import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Box import net.minecraft.util.math.Direction import net.minecraft.util.math.Vec3d diff --git a/common/src/main/kotlin/com/lambda/interaction/processing/TaskPlanner.kt b/common/src/main/kotlin/com/lambda/interaction/processing/TaskPlanner.kt deleted file mode 100644 index 53d1a687a..000000000 --- a/common/src/main/kotlin/com/lambda/interaction/processing/TaskPlanner.kt +++ /dev/null @@ -1,4 +0,0 @@ -package com.lambda.interaction.processing - -object TaskPlanner { -} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt b/common/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt index 2ad61dcd0..4853bb03c 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt @@ -1,6 +1,6 @@ package com.lambda.module.modules.player -import com.lambda.interaction.construction.StaticBlueprint.Companion.toBlueprint +import com.lambda.interaction.construction.blueprint.StaticBlueprint.Companion.toBlueprint import com.lambda.interaction.construction.verify.TargetState import com.lambda.module.Module import com.lambda.module.tag.ModuleTag diff --git a/common/src/main/kotlin/com/lambda/module/modules/player/Nuker.kt b/common/src/main/kotlin/com/lambda/module/modules/player/Nuker.kt index c60ea9205..4ae289af3 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/player/Nuker.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/player/Nuker.kt @@ -1,7 +1,7 @@ package com.lambda.module.modules.player -import com.lambda.interaction.construction.Blueprint.Companion.emptyStructure -import com.lambda.interaction.construction.DynamicBlueprint.Companion.toBlueprint +import com.lambda.interaction.construction.blueprint.Blueprint.Companion.emptyStructure +import com.lambda.interaction.construction.blueprint.DynamicBlueprint.Companion.toBlueprint import com.lambda.interaction.construction.verify.TargetState import com.lambda.module.Module import com.lambda.module.tag.ModuleTag diff --git a/common/src/main/kotlin/com/lambda/module/modules/player/WorldEater.kt b/common/src/main/kotlin/com/lambda/module/modules/player/WorldEater.kt index de965c043..2962b6fb9 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/player/WorldEater.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/player/WorldEater.kt @@ -3,8 +3,8 @@ package com.lambda.module.modules.player import com.lambda.event.events.RenderEvent import com.lambda.event.listener.SafeListener.Companion.listener import com.lambda.graphics.renderer.esp.builders.buildOutline -import com.lambda.interaction.construction.Blueprint.Companion.toStructure -import com.lambda.interaction.construction.StaticBlueprint.Companion.toBlueprint +import com.lambda.interaction.construction.blueprint.Blueprint.Companion.toStructure +import com.lambda.interaction.construction.blueprint.StaticBlueprint.Companion.toBlueprint import com.lambda.interaction.construction.verify.TargetState import com.lambda.module.Module import com.lambda.module.tag.ModuleTag diff --git a/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt b/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt index 703ad171a..184532a68 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt @@ -6,10 +6,10 @@ import com.lambda.context.SafeContext import com.lambda.event.events.RenderEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listener -import com.lambda.interaction.construction.Blueprint -import com.lambda.interaction.construction.Blueprint.Companion.toStructure -import com.lambda.interaction.construction.DynamicBlueprint -import com.lambda.interaction.construction.StaticBlueprint.Companion.toBlueprint +import com.lambda.interaction.construction.blueprint.Blueprint +import com.lambda.interaction.construction.blueprint.Blueprint.Companion.toStructure +import com.lambda.interaction.construction.blueprint.DynamicBlueprint +import com.lambda.interaction.construction.blueprint.StaticBlueprint.Companion.toBlueprint import com.lambda.interaction.construction.result.* import com.lambda.interaction.construction.simulation.BuildGoal import com.lambda.interaction.construction.simulation.BuildSimulator.simulate @@ -18,7 +18,6 @@ import com.lambda.interaction.construction.verify.TargetState import com.lambda.module.modules.client.TaskFlow import com.lambda.task.Task import com.lambda.util.BaritoneUtils -import com.lambda.util.Communication.info import com.lambda.util.extension.Structure import net.minecraft.util.math.BlockPos diff --git a/common/src/main/kotlin/com/lambda/task/tasks/PlaceContainer.kt b/common/src/main/kotlin/com/lambda/task/tasks/PlaceContainer.kt index 626cb35b8..1a04c82ef 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/PlaceContainer.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/PlaceContainer.kt @@ -1,9 +1,8 @@ package com.lambda.task.tasks import com.lambda.context.SafeContext -import com.lambda.interaction.construction.Blueprint.Companion.toStructure -import com.lambda.interaction.construction.StaticBlueprint.Companion.toBlueprint -import com.lambda.interaction.construction.context.PlaceContext +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.simulation.BuildSimulator.simulate From 0317cde30248f83b4259fd53d58aba67d9f31fdf Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Sat, 9 Nov 2024 19:34:07 -0500 Subject: [PATCH 17/39] ref!: structure loader, management and conversion --- .../mixin/world/StructureTemplateMixin.java | 36 +++ .../lambda/command/commands/BuildCommand.kt | 33 +-- .../src/main/kotlin/com/lambda/core/Loader.kt | 2 + .../construction/StructureRegistry.kt | 231 +++++++++++------- .../kotlin/com/lambda/util/VarIntIterator.kt | 38 +++ .../kotlin/com/lambda/util/extension/Nbt.kt | 13 + .../kotlin/com/lambda/util/extension/World.kt | 133 +++++++++- .../main/resources/lambda.mixins.common.json | 3 +- 8 files changed, 373 insertions(+), 116 deletions(-) create mode 100644 common/src/main/java/com/lambda/mixin/world/StructureTemplateMixin.java create mode 100644 common/src/main/kotlin/com/lambda/util/VarIntIterator.kt create mode 100644 common/src/main/kotlin/com/lambda/util/extension/Nbt.kt diff --git a/common/src/main/java/com/lambda/mixin/world/StructureTemplateMixin.java b/common/src/main/java/com/lambda/mixin/world/StructureTemplateMixin.java new file mode 100644 index 000000000..5166f8ce0 --- /dev/null +++ b/common/src/main/java/com/lambda/mixin/world/StructureTemplateMixin.java @@ -0,0 +1,36 @@ +package com.lambda.mixin.world; + +import com.llamalad7.mixinextras.injector.ModifyReturnValue; +import net.minecraft.block.Block; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.registry.RegistryEntryLookup; +import net.minecraft.structure.StructureTemplate; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.Objects; + +@Mixin(StructureTemplate.class) +public class StructureTemplateMixin { + @Shadow + private String author; + + @ModifyReturnValue(method = "getAuthor()Ljava/lang/String;", at = @At("RETURN")) + public String getAuthor(String original) { + return Objects.equals(original, "?") || Objects.equals(original, "") ? "Unknown" : original; + } + + @Inject(method = "writeNbt(Lnet/minecraft/nbt/NbtCompound;)Lnet/minecraft/nbt/NbtCompound;", at = @At("TAIL")) + public void writeNbt(NbtCompound nbt, CallbackInfoReturnable cir) { + nbt.putString("author", author); + } + + @Inject(method = "readNbt(Lnet/minecraft/registry/RegistryEntryLookup;Lnet/minecraft/nbt/NbtCompound;)V", at = @At("TAIL")) + public void readNbt(RegistryEntryLookup blockLookup, NbtCompound nbt, CallbackInfo ci) { + author = nbt.getString("author"); + } +} diff --git a/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt b/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt index 98c1f1fe3..3811de860 100644 --- a/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt +++ b/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt @@ -2,17 +2,16 @@ package com.lambda.command.commands import com.lambda.brigadier.CommandResult import com.lambda.brigadier.argument.boolean -import com.lambda.brigadier.argument.identifier import com.lambda.brigadier.argument.literal +import com.lambda.brigadier.argument.string import com.lambda.brigadier.argument.value import com.lambda.brigadier.executeWithResult import com.lambda.brigadier.optional import com.lambda.brigadier.required import com.lambda.command.LambdaCommand -import com.lambda.interaction.construction.blueprint.DynamicBlueprint.Companion.toBlueprint -import com.lambda.interaction.construction.blueprint.StaticBlueprint.Companion.toBlueprint import com.lambda.interaction.construction.StructureRegistry import com.lambda.interaction.construction.blueprint.Blueprint.Companion.toStructure +import com.lambda.interaction.construction.blueprint.StaticBlueprint.Companion.toBlueprint import com.lambda.task.tasks.BuildTask.Companion.build import com.lambda.threading.runSafe import com.lambda.util.Communication.info @@ -26,10 +25,10 @@ object BuildCommand : LambdaCommand( ) { override fun CommandBuilder.create() { required(literal("place")) { - required(identifier("structure")) { structure -> + required(string("structure")) { structure -> suggests { _, builder -> - StructureRegistry.streamTemplates() - .forEach { builder.suggest(it.path) } + StructureRegistry + .forEach { key, _ -> builder.suggest(key) } builder.buildFuture() } @@ -38,19 +37,21 @@ object BuildCommand : LambdaCommand( val id = structure().value() val path = if (pathing != null) pathing().value() else false runSafe { - StructureRegistry.loadStructure(id)?.let { template -> - info("Building structure ${id.path} with dimensions ${template.size.toShortString()} by ${template.author}") - template.toStructure() - .move(player.blockPos) - .toBlueprint() - .build(pathing = path) - .start(null) + StructureRegistry + .loadStructureByName(id) + ?.let { template -> + info("Building structure $id with dimensions ${template.size.toShortString()} created by ${template.author}") + template.toStructure() + .move(player.blockPos) + .toBlueprint() + .build(pathing = path) + .start(null) - return@executeWithResult CommandResult.success() - } + return@executeWithResult CommandResult.success() + } } - CommandResult.failure("Structure ${id.path} not found") + CommandResult.failure("Structure $id not found") } } } diff --git a/common/src/main/kotlin/com/lambda/core/Loader.kt b/common/src/main/kotlin/com/lambda/core/Loader.kt index 20eac33d5..20fb4c8eb 100644 --- a/common/src/main/kotlin/com/lambda/core/Loader.kt +++ b/common/src/main/kotlin/com/lambda/core/Loader.kt @@ -10,6 +10,7 @@ import com.lambda.gui.GuiConfigurable import com.lambda.gui.HudGuiConfigurable import com.lambda.interaction.PlayerPacketManager import com.lambda.interaction.RotationManager +import com.lambda.interaction.construction.StructureRegistry import com.lambda.interaction.material.ContainerManager import com.lambda.module.ModuleRegistry import com.lambda.sound.SoundRegistry @@ -38,6 +39,7 @@ object Loader { TimerManager, PingManager, ContainerManager, + StructureRegistry ) fun initialize() { diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/StructureRegistry.kt b/common/src/main/kotlin/com/lambda/interaction/construction/StructureRegistry.kt index bf262a3f0..2dd4577e7 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/StructureRegistry.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/StructureRegistry.kt @@ -1,49 +1,140 @@ package com.lambda.interaction.construction import com.lambda.Lambda.mc +import com.lambda.core.Loadable import com.lambda.util.Communication.logError import com.lambda.util.FolderRegister +import com.lambda.util.extension.readLitematicaOrException import com.lambda.util.extension.readNbtOrException -import net.minecraft.datafixer.DataFixTypes +import com.lambda.util.extension.readSchematicOrException +import com.lambda.util.extension.readSpongeOrException import net.minecraft.nbt.NbtCompound -import net.minecraft.nbt.NbtHelper import net.minecraft.nbt.NbtIo import net.minecraft.nbt.NbtSizeTracker import net.minecraft.registry.Registries import net.minecraft.structure.StructureTemplate -import net.minecraft.util.Identifier -import net.minecraft.util.PathUtil import net.minecraft.util.WorldSavePath +import java.io.File import java.nio.file.* -import kotlin.io.path.* +import java.nio.file.StandardWatchEventKinds.ENTRY_CREATE +import java.nio.file.StandardWatchEventKinds.ENTRY_DELETE import java.util.concurrent.ConcurrentHashMap -import kotlin.streams.asSequence +import kotlin.io.inputStream +import kotlin.io.path.* +/** + * The `StructureRegistry` object is responsible for managing the loading, saving, and validating of Minecraft structure templates. + * It extends [ConcurrentHashMap] to allow concurrent access to structure templates by their names. + * This registry supports multiple structure formats and automatically monitors changes in the structure directory. + */ +@OptIn(ExperimentalPathApi::class) @Suppress("JavaIoSerializableObjectMustHaveReadResolve") -object StructureRegistry : ConcurrentHashMap() { +object StructureRegistry + : ConcurrentHashMap(), Loadable { private val levelSession = mc.levelStorage.createSession(FolderRegister.structure.path) private val structurePath = levelSession.getDirectory(WorldSavePath.ROOT).normalize() + private val pathWatcher = FileSystems.getDefault().newWatchService() + .apply { structurePath.register(this, ENTRY_CREATE, ENTRY_DELETE) } + + /** + * Map of file suffix to their respective read function + */ + val serializers = mapOf( + "nbt" to StructureTemplate::readNbtOrException, + "schem" to StructureTemplate::readSpongeOrException, + "litematica" to StructureTemplate::readLitematicaOrException, + + // Not supported, who could guess that converting a format from 14 years ago would be hard ? :clueless: + "schematic" to StructureTemplate::readSchematicOrException, + ) /** - * Loads a structure from disk based on the provided [id]. + * Searches for a structure file by name, trying all supported extensions. * - * @param id The identifier of the structure to load. + * @param name The name of the structure file without the extension. + * @return The corresponding [File] if found, or null otherwise. + */ + fun findStructureByName(name: String): File? = + serializers + .keys + .firstNotNullOfOrNull { extension -> + structurePath + .resolve("$name.$extension") + .toFile() + .takeIf { it.isFile && it.exists() } + } + + /** + * Loads a structure by name, will attempt a discovery sequence if the structure could not be found + * and performs format conversions if necessary + * + * @param name The name of the structure to load (without extension). + * @param convert Whether to replace the file after converting it. * @return The loaded [StructureTemplate], or null if the structure is not found. */ - fun loadStructure(id: Identifier): StructureTemplate? { - val path = PathUtil.getResourcePath(structurePath, id.path, ".nbt") - - return if (!structurePath.isDirectory() || path.notExists()) null - else computeIfAbsent(id) { - path.inputStream().use { templateStream -> - val compound = NbtIo.readCompressed(templateStream, NbtSizeTracker.ofUnlimitedBytes()) - if (compound.isValidStructureTemplate()) { - createStructure(compound) - } else { - logError("Invalid structure template: ${path.fileName}", "File does not match template format") - null + fun loadStructureByName( + name: String, + convert: Boolean = true, + ): StructureTemplate? { + if (!structurePath.isDirectory()) { + logError( + "Invalid structure template: $name", + "The structure folder is not a folder" + ) + } + + // Poll directory file events to load many structures at once + // They might not show up in the command suggestion, but they are + // present in the map + pathWatcher.poll() + ?.let { key -> + key.pollEvents()?.forEach { event -> + @Suppress("UNCHECKED_CAST") + event as WatchEvent + + val kind = event.kind() + val path = event.context() + val nameNoExt = path.nameWithoutExtension + + when (kind) { + ENTRY_DELETE -> remove(nameNoExt) + ENTRY_CREATE -> if (!contains(nameNoExt) && nameNoExt != name) loadStructureByName(path.nameWithoutExtension, convert = true) + } + + // Reset the key -- this step is critical if you want to + // receive further watch events. If the key is no longer valid, + // the directory is inaccessible so exit the loop. + if (!key.reset()) return@forEach } } + + return computeIfAbsent(name.lowercase()) { + findStructureByName(name) + ?.let { it to it.extension } + ?.let { (file, extension) -> + file.inputStream().use { templateStream -> + val compound = NbtIo.readCompressed(templateStream, NbtSizeTracker.ofUnlimitedBytes()) + val template = createStructure(compound, extension) + + // Only delete structure files that aren't NBT + if (convert && extension != "nbt") { + template + ?.let { saveStructure(name, it) } + ?.let { structurePath.resolve("$name.$extension").deleteIfExists() } + } + + // Verify the structure integrity after it had been + // converted to a regular structure template + if (compound.isValidStructureTemplate()) template + else { + logError( + "Invalid structure template: $it", + "File does not match template format, it might have been corrupted", + ) + null + } + } + } } } @@ -53,17 +144,19 @@ object StructureRegistry : ConcurrentHashMap() { * @param nbt The [NbtCompound] containing the structure's data. * @return The created [StructureTemplate], or null if there was an error. */ - private fun createStructure(nbt: NbtCompound): StructureTemplate? { + private fun createStructure(nbt: NbtCompound, suffix: String): StructureTemplate? { val template = StructureTemplate() - val version = NbtHelper.getDataVersion(nbt, 500) - - template.readNbtOrException( - Registries.BLOCK.readOnlyWrapper, - DataFixTypes.STRUCTURE.update(mc.dataFixer, nbt, version) - )?.let { error -> - logError("Could not create structure from file", error.message ?: "") - return null - } + + serializers[suffix] + ?.invoke( + template, + Registries.BLOCK.readOnlyWrapper, + nbt, + ) + ?.let { error -> + logError("Could not create structure from file", error.message ?: "") + return null + } return template } @@ -71,11 +164,11 @@ object StructureRegistry : ConcurrentHashMap() { /** * Saves the provided [structure] to disk under the specified [name]. * - * @param name The name of the structure file (without the ".nbt" extension). + * @param name The name of the structure file (without the extension). * @param structure The [StructureTemplate] to save. */ fun saveStructure(name: String, structure: StructureTemplate) { - val path = PathUtil.getResourcePath(structurePath, name, ".nbt") + val path = structurePath.resolve("$name.nbt") val compound = structure.writeNbt(NbtCompound()) Files.createDirectories(path.parent) // Ensure parent directories exist @@ -84,63 +177,6 @@ object StructureRegistry : ConcurrentHashMap() { } } - /** - * Streams all available structure templates from the directory. - * - * @return A [Sequence] of [Identifier]s of the available templates. - */ - fun streamTemplates(): Sequence { - return if (!structurePath.isDirectory()) { - emptySequence() - } else { - try { - structurePath.walk().filter { it.isRegularFile() && it.extension == "nbt" } - .filter { it.isValidNbtStructure() } - .mapNotNull { it.toIdentifier() } - } catch (e: Exception) { - logError("Error streaming structure templates", e) - emptySequence() - } - } - } - - /** - * Converts a file [Path] to an [Identifier]. - * - * @param this@pathToIdentifier The file path to convert. - * @return The resulting [Identifier], or null if the path is invalid. - */ - private fun Path.toIdentifier(): Identifier? { - return try { - val relativePath = structurePath.relativize(this).invariantSeparatorsPathString - val namespace = "minecraft" - val pathWithoutExtension = relativePath.removeSuffix(".nbt") - Identifier(namespace, pathWithoutExtension) - } catch (e: Exception) { - this@StructureRegistry.logError("Invalid path for structure template", e.message ?: "") - null - } - } - - /** - * Walks through the [Path] hierarchy recursively and returns a [Sequence] of paths. - */ - private fun Path.walk(): Sequence = Files.walk(this).asSequence() - - /** - * Checks whether the NBT file at the given [this@isValidNbtStructure] is a valid Minecraft structure template. - * - * @param this@isValidNbtStructure The path to the NBT file. - * @return True if the NBT file is a valid structure template, false otherwise. - */ - private fun Path.isValidNbtStructure() = - runCatching { - inputStream().use { input -> - NbtIo.readCompressed(input, NbtSizeTracker.ofUnlimitedBytes()) - .isValidStructureTemplate() - } - }.getOrDefault(false) - /** * Verifies that the provided NBT data represents a valid Minecraft structure template. * @@ -148,9 +184,14 @@ object StructureRegistry : ConcurrentHashMap() { * @return True if the NBT contains valid structure template data, false otherwise. */ private fun NbtCompound.isValidStructureTemplate() = - contains("DataVersion") - && contains("blocks") - && contains("entities") - && contains("palette") - && contains("size") + contains("DataVersion") && contains("blocks") && contains("palette") && contains("size") + + override fun load(): String { + structurePath.walk().forEach { path -> + if (path.extension in serializers.keys) + loadStructureByName(path.nameWithoutExtension) + } + + return "Loaded $size structure templates" + } } diff --git a/common/src/main/kotlin/com/lambda/util/VarIntIterator.kt b/common/src/main/kotlin/com/lambda/util/VarIntIterator.kt new file mode 100644 index 000000000..f00f60a6f --- /dev/null +++ b/common/src/main/kotlin/com/lambda/util/VarIntIterator.kt @@ -0,0 +1,38 @@ +package com.lambda.util + +import java.util.NoSuchElementException + +class VarIntIterator( + private val bytes: ByteArray, + private val bitsPerEntry: Int = 7, + private val maxGroups: Int = 5 +) : Iterator { + private var index: Int = 0 + + override fun hasNext(): Boolean = index < bytes.size + + override fun next(): Int { + if (!hasNext()) + throw NoSuchElementException("No more elements to read") + + var value = 0 + var bitsRead = 0 + + val groupMask = (1 shl bitsPerEntry) - 1 + val continuationBit = 1 shl bitsPerEntry + + var b: Byte + do { + if (index >= bytes.size) + throw NoSuchElementException("Unexpected end of byte array while reading VarInt") + + b = bytes[index++] + value = value or ((b.toInt() and groupMask) shl bitsRead) + bitsRead += bitsPerEntry + + require(bitsRead <= bitsPerEntry * maxGroups) { "VarInt size cannot exceed $maxGroups bytes" } + } while ((b.toInt() and continuationBit) != 0) + + return value + } +} diff --git a/common/src/main/kotlin/com/lambda/util/extension/Nbt.kt b/common/src/main/kotlin/com/lambda/util/extension/Nbt.kt new file mode 100644 index 000000000..baba51ce2 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/util/extension/Nbt.kt @@ -0,0 +1,13 @@ +package com.lambda.util.extension + +import net.minecraft.nbt.NbtCompound +import net.minecraft.nbt.NbtInt +import net.minecraft.nbt.NbtList + +/** + * Puts a list of integer into the component, this is not the same as an int array + */ +fun NbtCompound.putIntList(key: String, vararg values: Int) { + this.put(key, values + .fold(NbtList()) { list, value -> list.add(NbtInt.of(value)); list }) +} diff --git a/common/src/main/kotlin/com/lambda/util/extension/World.kt b/common/src/main/kotlin/com/lambda/util/extension/World.kt index 1e49cacdd..b816d79d1 100644 --- a/common/src/main/kotlin/com/lambda/util/extension/World.kt +++ b/common/src/main/kotlin/com/lambda/util/extension/World.kt @@ -1,18 +1,20 @@ package com.lambda.util.extension -import com.lambda.util.world.FastVector -import com.lambda.util.world.x -import com.lambda.util.world.y -import com.lambda.util.world.z +import com.lambda.Lambda.mc +import com.lambda.util.VarIntIterator +import com.lambda.util.world.* import net.minecraft.block.Block import net.minecraft.block.BlockState import net.minecraft.block.Blocks +import net.minecraft.datafixer.DataFixTypes import net.minecraft.fluid.FluidState import net.minecraft.fluid.Fluids import net.minecraft.nbt.NbtCompound +import net.minecraft.nbt.NbtList import net.minecraft.registry.RegistryEntryLookup import net.minecraft.structure.StructureTemplate import net.minecraft.world.World +import kotlin.experimental.and fun World.getBlockState(x: Int, y: Int, z: Int): BlockState { if (isOutOfHeightLimit(y)) return Blocks.VOID_AIR.defaultState @@ -39,7 +41,130 @@ fun World.getFluidState(x: Int, y: Int, z: Int): FluidState { fun World.getBlockState(vec: FastVector): BlockState = getBlockState(vec.x, vec.y, vec.z) fun World.getFluidState(vec: FastVector): FluidState = getFluidState(vec.x, vec.y, vec.z) +private fun positionFromIndex(width: Int, length: Int, index: Int): FastVector { + val y = index / (width * length) + val remainder = index - (y * width * length) + val z = remainder / width + val x = remainder - z * width + + return fastVectorOf(x, y, z) +} + fun StructureTemplate.readNbtOrException( lookup: RegistryEntryLookup, nbt: NbtCompound, ): Throwable? = runCatching { readNbt(lookup, nbt) }.exceptionOrNull() + +fun StructureTemplate.readSpongeOrException( + lookup: RegistryEntryLookup, + nbt: NbtCompound, +): Throwable? = when (nbt.getInt("Version")) { + 1, 2, 3 -> readSpongeV1OrException(lookup, nbt) + else -> IllegalStateException("Invalid sponge schematic version") +} + +fun StructureTemplate.readSchematicOrException( + lookup: RegistryEntryLookup, + nbt: NbtCompound, +): Throwable? = when (nbt.getString("Materials")) { + "Alpha" -> IllegalStateException("Not implemented, you can help us by contributing to the project") + "Classic" -> IllegalStateException("Method not implemented, you can help us by contributing to the Minecraft Wiki (https://minecraft.wiki/w/Data_values_(Classic))") + "Pocket" -> IllegalStateException("Pocket Edition schematics are not supported") + else -> IllegalStateException("Invalid MCEdit schematic version") +} + +private fun StructureTemplate.readSpongeV1OrException( + lookup: RegistryEntryLookup, + nbt: NbtCompound, +): Throwable? { + val version = nbt.getInt("DataVersion") + val width = (nbt.getShort("Width") and 0xFFFF.toShort()).toInt() + val height = (nbt.getShort("Height") and 0xFFFF.toShort()).toInt() + val length = (nbt.getShort("Length") and 0xFFFF.toShort()).toInt() + + val metadata = nbt.getCompound("Metadata") + + // If the offset is too far, we simply ignore it + // I think schematica at some point calculated + // the offset based on the current player position from the + // schematic, so it would fuck up everything when you tried + // to import it back and build it using Baritone + // val minimumPosition = nbt.getIntArray("Offset") + // .takeIf { it.isNotEmpty() } + // ?.let { fastVectorOf(it[0], it[1], it[2]) } + // ?.takeIf { 274945015809L times 16 < it } ?: 0L + + val paletteMax = nbt.getInt("PaletteMax") + val palette = nbt.getCompound("Palette") + + if (palette.size != paletteMax) + return IllegalStateException("Block palette size does not match the provided size (corrupted?)") + + val newPalette = NbtList() + val newBlocks = NbtList() + + palette.keys.forEach { key -> + val resource = key.substringBefore('[') + val paletteEntry = NbtCompound() + val blockState = NbtCompound() + + // Why ? + // I know it's supposed to be SNBT, but it cannot be parsed back + key + .substringAfter('[') + .substringBefore(']') + .takeIf { it != resource } + ?.split(',') + ?.associate { it.substringBefore('=') to it.substringAfter('=') } + ?.forEach { (key, value) -> blockState.putString(key, value) } + + paletteEntry.putString("Name", resource) + paletteEntry.put("Properties", blockState) + + newPalette.add(paletteEntry) + } + + var blockIndex = 0 + VarIntIterator(nbt.getByteArray("BlockData")) + .forEach { blockId -> + val compound = NbtCompound() + val blockpos = positionFromIndex(width, length, blockIndex++) + + compound.putIntList("pos", blockpos.x, blockpos.y, blockpos.z) + compound.putInt("state", blockId.toInt()) + + newBlocks.add(compound) + } + + // Construct a structure compatible nbt compound + nbt.putIntList("size", width, height, length) + nbt.put("palette", newPalette) + nbt.put("blocks", newBlocks) + nbt.putString("author", metadata.getString("Author")) + + // Fix the data for future versions + DataFixTypes.STRUCTURE.update(mc.dataFixer, nbt, version) + + // Use the StructureTemplate NBT read utils in order to construct the template + return readNbtOrException(lookup, nbt) +} + +private fun StructureTemplate.readSpongeV3OrException( + lookup: RegistryEntryLookup, + nbt: NbtCompound, +): Throwable? { + // Third revision + // - 3D Biome support + // - Rename Palette to BlockPalette + // - Wordsmithing varint and palette usages + nbt.put("Palette", nbt.getCompound("BlockPalette")) + + return readSpongeV1OrException(lookup, nbt) +} + +fun StructureTemplate.readLitematicaOrException( + lookup: RegistryEntryLookup, + nbt: NbtCompound, +): Throwable? { + return IllegalStateException("Litematica parsing is not implemented") +} diff --git a/common/src/main/resources/lambda.mixins.common.json b/common/src/main/resources/lambda.mixins.common.json index f7b5416e6..91975de5e 100644 --- a/common/src/main/resources/lambda.mixins.common.json +++ b/common/src/main/resources/lambda.mixins.common.json @@ -44,7 +44,8 @@ "render.WorldRendererMixin", "world.BlockCollisionSpliteratorMixin", "world.ClientChunkManagerMixin", - "world.ClientWorldMixin" + "world.ClientWorldMixin", + "world.StructureTemplateMixin" ], "injectors": { "defaultRequire": 1 From 437f9e9ff73e904f75d5f58ef71a14c9b0fd3b7e Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Sat, 9 Nov 2024 19:36:31 -0500 Subject: [PATCH 18/39] grammar --- common/src/main/kotlin/com/lambda/util/extension/World.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/util/extension/World.kt b/common/src/main/kotlin/com/lambda/util/extension/World.kt index b816d79d1..4858a3290 100644 --- a/common/src/main/kotlin/com/lambda/util/extension/World.kt +++ b/common/src/main/kotlin/com/lambda/util/extension/World.kt @@ -85,10 +85,10 @@ private fun StructureTemplate.readSpongeV1OrException( val metadata = nbt.getCompound("Metadata") // If the offset is too far, we simply ignore it - // I think schematica at some point calculated - // the offset based on the current player position from the - // schematic, so it would fuck up everything when you tried - // to import it back and build it using Baritone + // I think at some point schematica calculated + // the offset based on the distance between the player position + // and the schematic lower corner, so it would fuck up everything + // when you tried to import and build it using Baritone // val minimumPosition = nbt.getIntArray("Offset") // .takeIf { it.isNotEmpty() } // ?.let { fastVectorOf(it[0], it[1], it[2]) } From 466f3f3008f7a4deef76697bbf78e4c8abcf18e2 Mon Sep 17 00:00:00 2001 From: Constructor Date: Sun, 10 Nov 2024 03:35:15 +0100 Subject: [PATCH 19/39] Cleanup --- .../construction/StructureRegistry.kt | 125 +++++++++--------- .../kotlin/com/lambda/util/extension/Nbt.kt | 3 +- .../kotlin/com/lambda/util/extension/World.kt | 28 ++-- 3 files changed, 76 insertions(+), 80 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/StructureRegistry.kt b/common/src/main/kotlin/com/lambda/interaction/construction/StructureRegistry.kt index 2dd4577e7..705126b5a 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/StructureRegistry.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/StructureRegistry.kt @@ -29,8 +29,7 @@ import kotlin.io.path.* */ @OptIn(ExperimentalPathApi::class) @Suppress("JavaIoSerializableObjectMustHaveReadResolve") -object StructureRegistry - : ConcurrentHashMap(), Loadable { +object StructureRegistry : ConcurrentHashMap(), Loadable { private val levelSession = mc.levelStorage.createSession(FolderRegister.structure.path) private val structurePath = levelSession.getDirectory(WorldSavePath.ROOT).normalize() private val pathWatcher = FileSystems.getDefault().newWatchService() @@ -39,12 +38,12 @@ object StructureRegistry /** * Map of file suffix to their respective read function */ - val serializers = mapOf( + private val serializers = mapOf( "nbt" to StructureTemplate::readNbtOrException, "schem" to StructureTemplate::readSpongeOrException, "litematica" to StructureTemplate::readLitematicaOrException, - // Not supported, who could guess that converting a format from 14 years ago would be hard ? :clueless: + // Not supported, who could guess that converting a format from 14 years ago would be hard? :clueless: "schematic" to StructureTemplate::readSchematicOrException, ) @@ -54,7 +53,7 @@ object StructureRegistry * @param name The name of the structure file without the extension. * @return The corresponding [File] if found, or null otherwise. */ - fun findStructureByName(name: String): File? = + private fun findStructureByName(name: String): File? = serializers .keys .firstNotNullOfOrNull { extension -> @@ -81,24 +80,26 @@ object StructureRegistry "Invalid structure template: $name", "The structure folder is not a folder" ) + return null } // Poll directory file events to load many structures at once // They might not show up in the command suggestion, but they are // present in the map - pathWatcher.poll() - ?.let { key -> - key.pollEvents()?.forEach { event -> - @Suppress("UNCHECKED_CAST") - event as WatchEvent - - val kind = event.kind() + pathWatcher.poll()?.let { key -> + key.pollEvents() + ?.filterIsInstance>() + ?.forEach { event -> val path = event.context() val nameNoExt = path.nameWithoutExtension - when (kind) { + when (event.kind()) { ENTRY_DELETE -> remove(nameNoExt) - ENTRY_CREATE -> if (!contains(nameNoExt) && nameNoExt != name) loadStructureByName(path.nameWithoutExtension, convert = true) + ENTRY_CREATE -> { + if (!contains(nameNoExt) && nameNoExt != name) { + loadStructureByName(path.nameWithoutExtension, convert = true) + } + } } // Reset the key -- this step is critical if you want to @@ -106,37 +107,43 @@ object StructureRegistry // the directory is inaccessible so exit the loop. if (!key.reset()) return@forEach } - } + } - return computeIfAbsent(name.lowercase()) { - findStructureByName(name) - ?.let { it to it.extension } - ?.let { (file, extension) -> - file.inputStream().use { templateStream -> - val compound = NbtIo.readCompressed(templateStream, NbtSizeTracker.ofUnlimitedBytes()) - val template = createStructure(compound, extension) - - // Only delete structure files that aren't NBT - if (convert && extension != "nbt") { - template - ?.let { saveStructure(name, it) } - ?.let { structurePath.resolve("$name.$extension").deleteIfExists() } - } + return computeIfAbsent(name.lowercase()) { loadFileAndCreate(name, convert) } + } - // Verify the structure integrity after it had been - // converted to a regular structure template - if (compound.isValidStructureTemplate()) template - else { - logError( - "Invalid structure template: $it", - "File does not match template format, it might have been corrupted", - ) - null - } + /** + * Loads the structure file and creates a [StructureTemplate]. + * + * @param name The name of the structure file (without extension). + * @param convert Whether to replace the file after converting it. + * @return The created [StructureTemplate], or null if the structure is not found or invalid. + */ + private fun loadFileAndCreate(name: String, convert: Boolean) = + findStructureByName(name) + ?.let { it to it.extension } + ?.let { (file, extension) -> + file.inputStream().use { templateStream -> + val compound = NbtIo.readCompressed(templateStream, NbtSizeTracker.ofUnlimitedBytes()) + val template = createStructure(compound, extension) + + if (convert && extension != "nbt") { + template?.let { saveStructure(name, it) } + } + + // Verify the structure integrity after it had been + // converted to a regular structure template + if (compound.isValidStructureTemplate()) { + template + } else { + logError( + "Invalid structure template: $name", + "File does not match template format, it might have been corrupted", + ) + null } } - } - } + } /** * Creates a [StructureTemplate] from the provided NBT data. @@ -144,22 +151,15 @@ object StructureRegistry * @param nbt The [NbtCompound] containing the structure's data. * @return The created [StructureTemplate], or null if there was an error. */ - private fun createStructure(nbt: NbtCompound, suffix: String): StructureTemplate? { - val template = StructureTemplate() - - serializers[suffix] - ?.invoke( - template, - Registries.BLOCK.readOnlyWrapper, - nbt, - ) - ?.let { error -> - logError("Could not create structure from file", error.message ?: "") - return null - } - - return template - } + private fun createStructure(nbt: NbtCompound, suffix: String): StructureTemplate? = + StructureTemplate().apply { + serializers[suffix] + ?.invoke(this, Registries.BLOCK.readOnlyWrapper, nbt) + ?.let { error -> + logError("Could not create structure from file", error.message ?: "") + return null + } + } /** * Saves the provided [structure] to disk under the specified [name]. @@ -167,7 +167,7 @@ object StructureRegistry * @param name The name of the structure file (without the extension). * @param structure The [StructureTemplate] to save. */ - fun saveStructure(name: String, structure: StructureTemplate) { + private fun saveStructure(name: String, structure: StructureTemplate) { val path = structurePath.resolve("$name.nbt") val compound = structure.writeNbt(NbtCompound()) @@ -180,17 +180,16 @@ object StructureRegistry /** * Verifies that the provided NBT data represents a valid Minecraft structure template. * - * @param this@isValidStructureTemplate The [NbtCompound] to validate. + * @receiver The [NbtCompound] to validate. * @return True if the NBT contains valid structure template data, false otherwise. */ private fun NbtCompound.isValidStructureTemplate() = contains("DataVersion") && contains("blocks") && contains("palette") && contains("size") override fun load(): String { - structurePath.walk().forEach { path -> - if (path.extension in serializers.keys) - loadStructureByName(path.nameWithoutExtension) - } + structurePath.walk() + .filter { it.extension in serializers.keys } + .forEach { path -> loadStructureByName(path.nameWithoutExtension) } return "Loaded $size structure templates" } diff --git a/common/src/main/kotlin/com/lambda/util/extension/Nbt.kt b/common/src/main/kotlin/com/lambda/util/extension/Nbt.kt index baba51ce2..bc534df67 100644 --- a/common/src/main/kotlin/com/lambda/util/extension/Nbt.kt +++ b/common/src/main/kotlin/com/lambda/util/extension/Nbt.kt @@ -8,6 +8,5 @@ import net.minecraft.nbt.NbtList * Puts a list of integer into the component, this is not the same as an int array */ fun NbtCompound.putIntList(key: String, vararg values: Int) { - this.put(key, values - .fold(NbtList()) { list, value -> list.add(NbtInt.of(value)); list }) + put(key, values.fold(NbtList()) { list, value -> list.add(NbtInt.of(value)); list }) } diff --git a/common/src/main/kotlin/com/lambda/util/extension/World.kt b/common/src/main/kotlin/com/lambda/util/extension/World.kt index 4858a3290..f504130ef 100644 --- a/common/src/main/kotlin/com/lambda/util/extension/World.kt +++ b/common/src/main/kotlin/com/lambda/util/extension/World.kt @@ -97,43 +97,41 @@ private fun StructureTemplate.readSpongeV1OrException( val paletteMax = nbt.getInt("PaletteMax") val palette = nbt.getCompound("Palette") - if (palette.size != paletteMax) - return IllegalStateException("Block palette size does not match the provided size (corrupted?)") + check(palette.size == paletteMax) { + "Block palette size does not match the provided size (corrupted?)" + } val newPalette = NbtList() - val newBlocks = NbtList() palette.keys.forEach { key -> val resource = key.substringBefore('[') - val paletteEntry = NbtCompound() val blockState = NbtCompound() // Why ? // I know it's supposed to be SNBT, but it cannot be parsed back - key - .substringAfter('[') + key.substringAfter('[') .substringBefore(']') .takeIf { it != resource } ?.split(',') ?.associate { it.substringBefore('=') to it.substringAfter('=') } ?.forEach { (key, value) -> blockState.putString(key, value) } - paletteEntry.putString("Name", resource) - paletteEntry.put("Properties", blockState) - - newPalette.add(paletteEntry) + newPalette.add(NbtCompound().apply { + putString("Name", resource) + put("Properties", blockState) + }) } + val newBlocks = NbtList() var blockIndex = 0 VarIntIterator(nbt.getByteArray("BlockData")) .forEach { blockId -> - val compound = NbtCompound() val blockpos = positionFromIndex(width, length, blockIndex++) - compound.putIntList("pos", blockpos.x, blockpos.y, blockpos.z) - compound.putInt("state", blockId.toInt()) - - newBlocks.add(compound) + newBlocks.add(NbtCompound().apply { + putIntList("pos", blockpos.x, blockpos.y, blockpos.z) + putInt("state", blockId) + }) } // Construct a structure compatible nbt compound From a0ade28c5ba5bd181fcf854cd4876231411ae7f9 Mon Sep 17 00:00:00 2001 From: Constructor Date: Sun, 10 Nov 2024 05:18:56 +0100 Subject: [PATCH 20/39] Relative structure paths support --- .../lambda/command/commands/BuildCommand.kt | 49 ++++---- .../lambda/command/commands/ReplayCommand.kt | 5 +- .../config/configurations/FriendConfig.kt | 3 +- .../lambda/config/configurations/GuiConfig.kt | 3 +- .../config/configurations/LambdaConfig.kt | 3 +- .../config/configurations/ModuleConfig.kt | 3 +- .../src/main/kotlin/com/lambda/core/Loader.kt | 2 + .../main/kotlin/com/lambda/http/Request.kt | 2 +- .../construction/StructureRegistry.kt | 115 ++++++++---------- .../module/modules/network/PacketLogger.kt | 2 +- .../lambda/module/modules/player/Replay.kt | 2 +- .../kotlin/com/lambda/util/FolderRegister.kt | 35 ++++-- 12 files changed, 120 insertions(+), 104 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt b/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt index 3811de860..1ad5529f3 100644 --- a/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt +++ b/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt @@ -1,10 +1,7 @@ package com.lambda.command.commands import com.lambda.brigadier.CommandResult -import com.lambda.brigadier.argument.boolean -import com.lambda.brigadier.argument.literal -import com.lambda.brigadier.argument.string -import com.lambda.brigadier.argument.value +import com.lambda.brigadier.argument.* import com.lambda.brigadier.executeWithResult import com.lambda.brigadier.optional import com.lambda.brigadier.required @@ -17,6 +14,9 @@ import com.lambda.threading.runSafe import com.lambda.util.Communication.info import com.lambda.util.extension.CommandBuilder import com.lambda.util.extension.move +import java.nio.file.InvalidPathException +import java.nio.file.NoSuchFileException +import java.nio.file.Path object BuildCommand : LambdaCommand( name = "Build", @@ -25,33 +25,40 @@ object BuildCommand : LambdaCommand( ) { override fun CommandBuilder.create() { required(literal("place")) { - required(string("structure")) { structure -> + required(greedyString("structure")) { structure -> suggests { _, builder -> - StructureRegistry - .forEach { key, _ -> builder.suggest(key) } + StructureRegistry.forEach { key, _ -> builder.suggest(key) } builder.buildFuture() } optional(boolean("pathing")) { pathing -> executeWithResult { - val id = structure().value() - val path = if (pathing != null) pathing().value() else false + val pathString = structure().value() + val doPathing = if (pathing != null) pathing().value() else false runSafe { - StructureRegistry - .loadStructureByName(id) - ?.let { template -> - info("Building structure $id with dimensions ${template.size.toShortString()} created by ${template.author}") - template.toStructure() - .move(player.blockPos) - .toBlueprint() - .build(pathing = path) - .start(null) + try { + StructureRegistry + .loadStructureByRelativePath(Path.of(pathString)) + ?.let { template -> + info("Building structure $pathString with dimensions ${template.size.toShortString()} created by ${template.author}") + template.toStructure() + .move(player.blockPos) + .toBlueprint() + .build(pathing = doPathing) + .start(null) - return@executeWithResult CommandResult.success() - } + return@executeWithResult CommandResult.success() + } + } catch (e: InvalidPathException) { + return@executeWithResult CommandResult.failure("Invalid path $pathString") + } catch (e: NoSuchFileException) { + return@executeWithResult CommandResult.failure("Structure $pathString not found") + } catch (e: Exception) { + return@executeWithResult CommandResult.failure(e.message ?: "Failed to load structure $pathString") + } } - CommandResult.failure("Structure $id not found") + CommandResult.failure("Structure $pathString not found") } } } diff --git a/common/src/main/kotlin/com/lambda/command/commands/ReplayCommand.kt b/common/src/main/kotlin/com/lambda/command/commands/ReplayCommand.kt index e344f6313..e105861aa 100644 --- a/common/src/main/kotlin/com/lambda/command/commands/ReplayCommand.kt +++ b/common/src/main/kotlin/com/lambda/command/commands/ReplayCommand.kt @@ -13,6 +13,7 @@ import com.lambda.module.modules.player.Replay import com.lambda.util.FolderRegister import com.lambda.util.FolderRegister.listRecursive import com.lambda.util.extension.CommandBuilder +import kotlin.io.path.exists object ReplayCommand : LambdaCommand( name = "replay", @@ -31,7 +32,7 @@ object ReplayCommand : LambdaCommand( required(literal("load")) { required(greedyString("replay filepath")) { replayName -> suggests { _, builder -> - val dir = FolderRegister.replay + val dir = FolderRegister.replay.toFile() dir.listRecursive { it.isFile }.forEach { builder.suggest(it.relativeTo(dir).path) } @@ -46,7 +47,7 @@ object ReplayCommand : LambdaCommand( } try { - Replay.loadRecording(replayFile) + Replay.loadRecording(replayFile.toFile()) } catch (e: JsonSyntaxException) { return@executeWithResult CommandResult.failure("Failed to load replay file: ${e.message}") } diff --git a/common/src/main/kotlin/com/lambda/config/configurations/FriendConfig.kt b/common/src/main/kotlin/com/lambda/config/configurations/FriendConfig.kt index b860ce7f5..269a42719 100644 --- a/common/src/main/kotlin/com/lambda/config/configurations/FriendConfig.kt +++ b/common/src/main/kotlin/com/lambda/config/configurations/FriendConfig.kt @@ -2,8 +2,9 @@ package com.lambda.config.configurations import com.lambda.config.Configuration import com.lambda.util.FolderRegister +import java.io.File object FriendConfig : Configuration() { override val configName get() = "friends" - override val primary = FolderRegister.config.resolve("$configName.json") + override val primary: File = FolderRegister.config.resolve("$configName.json").toFile() } diff --git a/common/src/main/kotlin/com/lambda/config/configurations/GuiConfig.kt b/common/src/main/kotlin/com/lambda/config/configurations/GuiConfig.kt index 8165f3553..04b9cb7fa 100644 --- a/common/src/main/kotlin/com/lambda/config/configurations/GuiConfig.kt +++ b/common/src/main/kotlin/com/lambda/config/configurations/GuiConfig.kt @@ -2,8 +2,9 @@ package com.lambda.config.configurations import com.lambda.config.Configuration import com.lambda.util.FolderRegister +import java.io.File object GuiConfig : Configuration() { override val configName get() = "gui" - override val primary = FolderRegister.config.resolve("$configName.json") + override val primary: File = FolderRegister.config.resolve("$configName.json").toFile() } diff --git a/common/src/main/kotlin/com/lambda/config/configurations/LambdaConfig.kt b/common/src/main/kotlin/com/lambda/config/configurations/LambdaConfig.kt index 8d7690910..706a90a26 100644 --- a/common/src/main/kotlin/com/lambda/config/configurations/LambdaConfig.kt +++ b/common/src/main/kotlin/com/lambda/config/configurations/LambdaConfig.kt @@ -2,8 +2,9 @@ package com.lambda.config.configurations import com.lambda.config.Configuration import com.lambda.util.FolderRegister +import java.io.File object LambdaConfig : Configuration() { override val configName get() = "lambda" - override val primary = FolderRegister.config.resolve("$configName.json") + override val primary: File = FolderRegister.config.resolve("$configName.json").toFile() } diff --git a/common/src/main/kotlin/com/lambda/config/configurations/ModuleConfig.kt b/common/src/main/kotlin/com/lambda/config/configurations/ModuleConfig.kt index ee1b6d611..bf88cd23a 100644 --- a/common/src/main/kotlin/com/lambda/config/configurations/ModuleConfig.kt +++ b/common/src/main/kotlin/com/lambda/config/configurations/ModuleConfig.kt @@ -4,6 +4,7 @@ import com.lambda.config.Configuration import com.lambda.config.configurations.ModuleConfig.configName import com.lambda.config.configurations.ModuleConfig.primary import com.lambda.util.FolderRegister +import java.io.File /** @@ -16,5 +17,5 @@ import com.lambda.util.FolderRegister */ object ModuleConfig : Configuration() { override val configName get() = "modules" - override val primary = FolderRegister.config.resolve("$configName.json") + override val primary: File = FolderRegister.config.resolve("$configName.json").toFile() } diff --git a/common/src/main/kotlin/com/lambda/core/Loader.kt b/common/src/main/kotlin/com/lambda/core/Loader.kt index 20fb4c8eb..17b808c55 100644 --- a/common/src/main/kotlin/com/lambda/core/Loader.kt +++ b/common/src/main/kotlin/com/lambda/core/Loader.kt @@ -15,6 +15,7 @@ import com.lambda.interaction.material.ContainerManager import com.lambda.module.ModuleRegistry import com.lambda.sound.SoundRegistry import com.lambda.util.Communication.ascii +import com.lambda.util.FolderRegister import kotlin.system.measureTimeMillis import kotlin.time.DurationUnit import kotlin.time.toDuration @@ -26,6 +27,7 @@ object Loader { get() = "${(System.currentTimeMillis() - started).toDuration(DurationUnit.MILLISECONDS)}" private val loadables = listOf( + FolderRegister, ModuleRegistry, CommandRegistry, RotationManager, diff --git a/common/src/main/kotlin/com/lambda/http/Request.kt b/common/src/main/kotlin/com/lambda/http/Request.kt index b94e99701..304c04300 100644 --- a/common/src/main/kotlin/com/lambda/http/Request.kt +++ b/common/src/main/kotlin/com/lambda/http/Request.kt @@ -37,7 +37,7 @@ data class Request( * @param maxAge The maximum age of the cached resource. Default is 4 days. */ fun maybeDownload(name: String, maxAge: Duration = 7.days): File { - val file = cache.resolve(name).createIfNotExists() + val file = cache.resolve(name).toFile().createIfNotExists() if ( System.currentTimeMillis() - file.lastModified() < maxAge.inWholeMilliseconds diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/StructureRegistry.kt b/common/src/main/kotlin/com/lambda/interaction/construction/StructureRegistry.kt index 705126b5a..3e8e5c57d 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/StructureRegistry.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/StructureRegistry.kt @@ -1,5 +1,6 @@ package com.lambda.interaction.construction +import com.lambda.Lambda.LOG import com.lambda.Lambda.mc import com.lambda.core.Loadable import com.lambda.util.Communication.logError @@ -14,12 +15,10 @@ import net.minecraft.nbt.NbtSizeTracker import net.minecraft.registry.Registries import net.minecraft.structure.StructureTemplate import net.minecraft.util.WorldSavePath -import java.io.File import java.nio.file.* import java.nio.file.StandardWatchEventKinds.ENTRY_CREATE import java.nio.file.StandardWatchEventKinds.ENTRY_DELETE import java.util.concurrent.ConcurrentHashMap -import kotlin.io.inputStream import kotlin.io.path.* /** @@ -30,8 +29,7 @@ import kotlin.io.path.* @OptIn(ExperimentalPathApi::class) @Suppress("JavaIoSerializableObjectMustHaveReadResolve") object StructureRegistry : ConcurrentHashMap(), Loadable { - private val levelSession = mc.levelStorage.createSession(FolderRegister.structure.path) - private val structurePath = levelSession.getDirectory(WorldSavePath.ROOT).normalize() + private val structurePath = FolderRegister.structure private val pathWatcher = FileSystems.getDefault().newWatchService() .apply { structurePath.register(this, ENTRY_CREATE, ENTRY_DELETE) } @@ -48,56 +46,51 @@ object StructureRegistry : ConcurrentHashMap(), Load ) /** - * Searches for a structure file by name, trying all supported extensions. - * - * @param name The name of the structure file without the extension. - * @return The corresponding [File] if found, or null otherwise. - */ - private fun findStructureByName(name: String): File? = - serializers - .keys - .firstNotNullOfOrNull { extension -> - structurePath - .resolve("$name.$extension") - .toFile() - .takeIf { it.isFile && it.exists() } - } - - /** - * Loads a structure by name, will attempt a discovery sequence if the structure could not be found + * Loads a structure by relative path, will attempt a discovery sequence if the structure could not be found * and performs format conversions if necessary * - * @param name The name of the structure to load (without extension). + * @param relativePath The name of the structure to load (without extension). * @param convert Whether to replace the file after converting it. * @return The loaded [StructureTemplate], or null if the structure is not found. */ - fun loadStructureByName( - name: String, + fun loadStructureByRelativePath( + relativePath: Path, convert: Boolean = true, ): StructureTemplate? { if (!structurePath.isDirectory()) { logError( - "Invalid structure template: $name", + "Invalid structure template: $relativePath", "The structure folder is not a folder" ) return null } - // Poll directory file events to load many structures at once - // They might not show up in the command suggestion, but they are - // present in the map + updateFileWatcher() + + return computeIfAbsent(relativePath.pathString.lowercase()) { + loadFileAndCreate(relativePath, convert)?.also { + LOG.info("Loaded structure template $relativePath by ${it.author} with dimensions ${it.size.toShortString()}") + } + } + } + + /** + * Poll directory file events to load many structures at once. + * They might not show up in the command suggestion, but they are + * present in the map. + */ + private fun updateFileWatcher() { pathWatcher.poll()?.let { key -> key.pollEvents() ?.filterIsInstance>() ?.forEach { event -> - val path = event.context() - val nameNoExt = path.nameWithoutExtension + val newPath = event.context() when (event.kind()) { - ENTRY_DELETE -> remove(nameNoExt) + ENTRY_DELETE -> remove(newPath.pathString) ENTRY_CREATE -> { - if (!contains(nameNoExt) && nameNoExt != name) { - loadStructureByName(path.nameWithoutExtension, convert = true) + computeIfAbsent(newPath.pathString) { + loadStructureByRelativePath(newPath, convert = true) } } } @@ -108,42 +101,36 @@ object StructureRegistry : ConcurrentHashMap(), Load if (!key.reset()) return@forEach } } - - return computeIfAbsent(name.lowercase()) { loadFileAndCreate(name, convert) } } /** * Loads the structure file and creates a [StructureTemplate]. * - * @param name The name of the structure file (without extension). * @param convert Whether to replace the file after converting it. * @return The created [StructureTemplate], or null if the structure is not found or invalid. */ - private fun loadFileAndCreate(name: String, convert: Boolean) = - findStructureByName(name) - ?.let { it to it.extension } - ?.let { (file, extension) -> - file.inputStream().use { templateStream -> - val compound = NbtIo.readCompressed(templateStream, NbtSizeTracker.ofUnlimitedBytes()) - val template = createStructure(compound, extension) - - if (convert && extension != "nbt") { - template?.let { saveStructure(name, it) } - } + private fun loadFileAndCreate(path: Path, convert: Boolean) = + structurePath.resolve(path).inputStream().use { templateStream -> + val compound = NbtIo.readCompressed(templateStream, NbtSizeTracker.ofUnlimitedBytes()) + val extension = path.extension + val template = createStructure(compound, extension) + + if (convert && extension != "nbt") { + template?.let { saveStructure(path.pathString, it) } + } - // Verify the structure integrity after it had been - // converted to a regular structure template - if (compound.isValidStructureTemplate()) { - template - } else { - logError( - "Invalid structure template: $name", - "File does not match template format, it might have been corrupted", - ) - null - } - } + // Verify the structure integrity after it had been + // converted to a regular structure template + if (compound.isValidStructureTemplate()) { + template + } else { + logError( + "Invalid structure template: $path", + "File does not match template format, it might have been corrupted", + ) + null } + } /** * Creates a [StructureTemplate] from the provided NBT data. @@ -164,14 +151,14 @@ object StructureRegistry : ConcurrentHashMap(), Load /** * Saves the provided [structure] to disk under the specified [name]. * - * @param name The name of the structure file (without the extension). + * @param relativePath The relative path of the structure to save. * @param structure The [StructureTemplate] to save. */ - private fun saveStructure(name: String, structure: StructureTemplate) { - val path = structurePath.resolve("$name.nbt") + private fun saveStructure(relativePath: String, structure: StructureTemplate) { + val path = structurePath.resolve("$relativePath.nbt") val compound = structure.writeNbt(NbtCompound()) - Files.createDirectories(path.parent) // Ensure parent directories exist + Files.createDirectories(path.parent) path.outputStream().use { output -> NbtIo.writeCompressed(compound, output) } @@ -189,7 +176,7 @@ object StructureRegistry : ConcurrentHashMap(), Load override fun load(): String { structurePath.walk() .filter { it.extension in serializers.keys } - .forEach { path -> loadStructureByName(path.nameWithoutExtension) } + .forEach { loadStructureByRelativePath(structurePath.relativize(it)) } return "Loaded $size structure templates" } diff --git a/common/src/main/kotlin/com/lambda/module/modules/network/PacketLogger.kt b/common/src/main/kotlin/com/lambda/module/modules/network/PacketLogger.kt index 724de4fa7..e56150691 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/network/PacketLogger.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/network/PacketLogger.kt @@ -79,7 +79,7 @@ object PacketLogger : Module( val fileName = "packet-log-${getTime(fileFormatter)}.txt" // ToDo: Organize files with FolderRegister.worldBoundDirectory - file = FolderRegister.packetLogs.resolve(fileName).apply { + file = FolderRegister.packetLogs.resolve(fileName).toFile().apply { if (!parentFile.exists()) { parentFile.mkdirs() } diff --git a/common/src/main/kotlin/com/lambda/module/modules/player/Replay.kt b/common/src/main/kotlin/com/lambda/module/modules/player/Replay.kt index 19ad506af..6edbdec29 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/player/Replay.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/player/Replay.kt @@ -412,7 +412,7 @@ object Replay : Module( this@Replay.warn("Recording too short. Minimum length: 5 ticks.") return } - val file = FolderRegister.replay.locationBoundDirectory().resolve("$name.json") + val file = FolderRegister.replay.toFile().locationBoundDirectory().resolve("$name.json") lambdaScope.launch(Dispatchers.IO) { file.writeText(gsonCompact.toJson(recording)) diff --git a/common/src/main/kotlin/com/lambda/util/FolderRegister.kt b/common/src/main/kotlin/com/lambda/util/FolderRegister.kt index ddbccf187..2a6667844 100644 --- a/common/src/main/kotlin/com/lambda/util/FolderRegister.kt +++ b/common/src/main/kotlin/com/lambda/util/FolderRegister.kt @@ -1,29 +1,44 @@ package com.lambda.util import com.lambda.Lambda.mc +import com.lambda.core.Loadable import com.lambda.util.StringUtils.sanitizeForFilename import java.io.File import java.net.InetSocketAddress +import java.nio.file.Path +import kotlin.io.path.createDirectory +import kotlin.io.path.notExists /** * The [FolderRegister] object is responsible for managing the directory structure of the application. * * @property minecraft The root directory of the Minecraft client. * @property lambda The directory for the Lambda client, located within the Minecraft directory. - * @property mods The directory for storing mods, located within the Minecraft directory. * @property config The directory for storing configuration files, located within the Lambda directory. * @property packetLogs The directory for storing packet logs, located within the Lambda directory. * @property replay The directory for storing replay files, located within the Lambda directory. */ -object FolderRegister { - val minecraft: File = mc.runDirectory - val lambda: File = File(minecraft, "lambda") - val mods: File = File(minecraft, "mods") - val config: File = File(lambda, "config") - val packetLogs: File = File(lambda, "packet-log") - val replay: File = File(lambda, "replay") - val cache: File = File(lambda, "cache") - val structure: File = File(lambda, "structure") +object FolderRegister : Loadable { + val minecraft: Path = mc.runDirectory.toPath() + val lambda: Path = minecraft.resolve("lambda") + val config: Path = lambda.resolve("config") + val packetLogs: Path = lambda.resolve("packet-log") + val replay: Path = lambda.resolve("replay") + val cache: Path = lambda.resolve("cache") + val structure: Path = lambda.resolve("structure") + + override fun load(): String { + val folders = listOf(lambda, config, packetLogs, replay, cache, structure) + val createdFolders = folders.mapNotNull { + if (it.notExists()) { + it.createDirectory() + it + } else null + } + return if (createdFolders.isNotEmpty()) { + "\nCreated directories: ${createdFolders.joinToString { it.toString() }}" + } else "" + } /** * Ensures the current file exists by creating it if it does not. From 076e2c2a15d33acc2d3c40da3f1e018d13c3804b Mon Sep 17 00:00:00 2001 From: Constructor Date: Sun, 10 Nov 2024 05:29:42 +0100 Subject: [PATCH 21/39] Some fixes --- .../construction/StructureRegistry.kt | 23 ++++--------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/StructureRegistry.kt b/common/src/main/kotlin/com/lambda/interaction/construction/StructureRegistry.kt index 3e8e5c57d..415e91c29 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/StructureRegistry.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/StructureRegistry.kt @@ -1,7 +1,6 @@ package com.lambda.interaction.construction import com.lambda.Lambda.LOG -import com.lambda.Lambda.mc import com.lambda.core.Loadable import com.lambda.util.Communication.logError import com.lambda.util.FolderRegister @@ -14,7 +13,6 @@ import net.minecraft.nbt.NbtIo import net.minecraft.nbt.NbtSizeTracker import net.minecraft.registry.Registries import net.minecraft.structure.StructureTemplate -import net.minecraft.util.WorldSavePath import java.nio.file.* import java.nio.file.StandardWatchEventKinds.ENTRY_CREATE import java.nio.file.StandardWatchEventKinds.ENTRY_DELETE @@ -57,20 +55,10 @@ object StructureRegistry : ConcurrentHashMap(), Load relativePath: Path, convert: Boolean = true, ): StructureTemplate? { - if (!structurePath.isDirectory()) { - logError( - "Invalid structure template: $relativePath", - "The structure folder is not a folder" - ) - return null - } - updateFileWatcher() return computeIfAbsent(relativePath.pathString.lowercase()) { - loadFileAndCreate(relativePath, convert)?.also { - LOG.info("Loaded structure template $relativePath by ${it.author} with dimensions ${it.size.toShortString()}") - } + loadFileAndCreate(relativePath, convert) } } @@ -124,10 +112,7 @@ object StructureRegistry : ConcurrentHashMap(), Load if (compound.isValidStructureTemplate()) { template } else { - logError( - "Invalid structure template: $path", - "File does not match template format, it might have been corrupted", - ) + logError("Corrupted structure file: ${path.pathString}") null } } @@ -143,7 +128,7 @@ object StructureRegistry : ConcurrentHashMap(), Load serializers[suffix] ?.invoke(this, Registries.BLOCK.readOnlyWrapper, nbt) ?.let { error -> - logError("Could not create structure from file", error.message ?: "") + logError("Could not create structure from file: ${error.message}") return null } } @@ -155,7 +140,7 @@ object StructureRegistry : ConcurrentHashMap(), Load * @param structure The [StructureTemplate] to save. */ private fun saveStructure(relativePath: String, structure: StructureTemplate) { - val path = structurePath.resolve("$relativePath.nbt") + val path = structurePath.resolve(relativePath) val compound = structure.writeNbt(NbtCompound()) Files.createDirectories(path.parent) From a9c5e300cebcddaf0957ac8d9010702819b09242 Mon Sep 17 00:00:00 2001 From: Edouard127 <46357922+Edouard127@users.noreply.github.com> Date: Sun, 10 Nov 2024 14:29:48 -0500 Subject: [PATCH 22/39] fix: schem palette ordering --- .../main/kotlin/com/lambda/util/extension/World.kt | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/util/extension/World.kt b/common/src/main/kotlin/com/lambda/util/extension/World.kt index f504130ef..a90aef3be 100644 --- a/common/src/main/kotlin/com/lambda/util/extension/World.kt +++ b/common/src/main/kotlin/com/lambda/util/extension/World.kt @@ -94,16 +94,16 @@ private fun StructureTemplate.readSpongeV1OrException( // ?.let { fastVectorOf(it[0], it[1], it[2]) } // ?.takeIf { 274945015809L times 16 < it } ?: 0L - val paletteMax = nbt.getInt("PaletteMax") val palette = nbt.getCompound("Palette") - check(palette.size == paletteMax) { - "Block palette size does not match the provided size (corrupted?)" - } - + val paletteMax = nbt.getInt("PaletteMax") val newPalette = NbtList() - palette.keys.forEach { key -> + if (palette.size != paletteMax) return IllegalStateException("Block palette size does not match the provided size (corrupted?)") + + palette.keys + .sortedBy { palette.getInt(it) } + .forEach { key -> val resource = key.substringBefore('[') val blockState = NbtCompound() @@ -116,6 +116,7 @@ private fun StructureTemplate.readSpongeV1OrException( ?.associate { it.substringBefore('=') to it.substringAfter('=') } ?.forEach { (key, value) -> blockState.putString(key, value) } + // Populate the list using the correct indices newPalette.add(NbtCompound().apply { putString("Name", resource) put("Properties", blockState) From 468c35598a2e95ec5aa63cb7b4cc61aab29381e6 Mon Sep 17 00:00:00 2001 From: Constructor Date: Sun, 15 Dec 2024 21:06:14 +0100 Subject: [PATCH 23/39] Forcing horizontal rotation, placement preprocessor, lots of fixes --- .../lambda/config/groups/InteractionConfig.kt | 2 + .../config/groups/InteractionSettings.kt | 1 + .../com/lambda/event/listener/SafeListener.kt | 4 +- .../com/lambda/interaction/RotationManager.kt | 27 ++-- .../construction/context/PlaceContext.kt | 2 + .../processing/PlacementProcessor.kt | 26 ++++ .../processing/PreprocessingStep.kt | 28 ++++ .../processing/ProcessorRegistry.kt | 42 ++++++ .../processing/processors/FacingProcessor.kt | 35 +++++ .../processing/processors/SlabProcessor.kt | 50 +++++++ .../construction/simulation/BuildGoal.kt | 15 +-- .../construction/simulation/BuildSimulator.kt | 100 ++++++++++---- .../construction/simulation/Simulation.kt | 11 +- .../construction/verify/ScanMode.kt | 22 +++ .../construction/verify/SurfaceScan.kt | 30 +++++ .../construction/verify/TargetState.kt | 1 - .../lambda/interaction/rotation/Rotation.kt | 26 ++-- .../visibilty/VisibilityChecker.kt | 127 +++++++++++------- .../lambda/module/modules/client/TaskFlow.kt | 8 +- .../lambda/module/modules/combat/KillAura.kt | 15 ++- .../lambda/module/modules/player/Scaffold.kt | 18 ++- .../kotlin/com/lambda/task/tasks/BuildTask.kt | 19 +-- .../com/lambda/task/tasks/PlaceBlock.kt | 54 ++++++-- .../com/lambda/util/player/PlayerUtils.kt | 28 ++++ 24 files changed, 541 insertions(+), 150 deletions(-) create mode 100644 common/src/main/kotlin/com/lambda/interaction/construction/processing/PlacementProcessor.kt create mode 100644 common/src/main/kotlin/com/lambda/interaction/construction/processing/PreprocessingStep.kt create mode 100644 common/src/main/kotlin/com/lambda/interaction/construction/processing/ProcessorRegistry.kt create mode 100644 common/src/main/kotlin/com/lambda/interaction/construction/processing/processors/FacingProcessor.kt create mode 100644 common/src/main/kotlin/com/lambda/interaction/construction/processing/processors/SlabProcessor.kt create mode 100644 common/src/main/kotlin/com/lambda/interaction/construction/verify/ScanMode.kt create mode 100644 common/src/main/kotlin/com/lambda/interaction/construction/verify/SurfaceScan.kt create mode 100644 common/src/main/kotlin/com/lambda/util/player/PlayerUtils.kt diff --git a/common/src/main/kotlin/com/lambda/config/groups/InteractionConfig.kt b/common/src/main/kotlin/com/lambda/config/groups/InteractionConfig.kt index 63d45b4be..4b1a946fb 100644 --- a/common/src/main/kotlin/com/lambda/config/groups/InteractionConfig.kt +++ b/common/src/main/kotlin/com/lambda/config/groups/InteractionConfig.kt @@ -25,6 +25,8 @@ interface InteractionConfig { */ val reach: Double + val visibilityCheck: Boolean + /** * Will check `resolution squared` many points on a grid on each visible surface of the hit box. */ diff --git a/common/src/main/kotlin/com/lambda/config/groups/InteractionSettings.kt b/common/src/main/kotlin/com/lambda/config/groups/InteractionSettings.kt index 5d0691e83..e528607af 100644 --- a/common/src/main/kotlin/com/lambda/config/groups/InteractionSettings.kt +++ b/common/src/main/kotlin/com/lambda/config/groups/InteractionSettings.kt @@ -26,6 +26,7 @@ class InteractionSettings( ) : InteractionConfig { override val reach by c.setting("Reach", defaultReach, 0.1..10.0, 0.1, "Players reach / range", " blocks", vis) override val useRayCast by c.setting("Raycast", true, "Verify hit vector with ray casting (for very strict ACs)", vis) + override val visibilityCheck by c.setting("Visibility Check", true, "Check if target is visible", vis) override val resolution by c.setting("Resolution", 4, 1..40, 1, "How many raycast checks per surface (will be squared)") { vis() && useRayCast } override val swingHand by c.setting("Swing Hand", true, "Swing hand on interactions", vis) override val pingTimeout by c.setting("Ping Timeout", false, "Timeout on high ping", vis) diff --git a/common/src/main/kotlin/com/lambda/event/listener/SafeListener.kt b/common/src/main/kotlin/com/lambda/event/listener/SafeListener.kt index 49611d4cb..1e7d8fec3 100644 --- a/common/src/main/kotlin/com/lambda/event/listener/SafeListener.kt +++ b/common/src/main/kotlin/com/lambda/event/listener/SafeListener.kt @@ -46,9 +46,9 @@ import kotlin.reflect.KProperty * The [SafeListener] will keep a reference to the last signal processed by the listener. * Allowing use cases where the last signal is needed. * ```kotlin - * val lastPacketReceived by listener() + * val lastPacketReceived by listen() * - * listener { event -> + * listen { event -> * println("Last packet received: ${lastPacketReceived?.packet}") * // prints the last packet received * // prints null if no packet was received diff --git a/common/src/main/kotlin/com/lambda/interaction/RotationManager.kt b/common/src/main/kotlin/com/lambda/interaction/RotationManager.kt index f3c43d8ca..76caa68fa 100644 --- a/common/src/main/kotlin/com/lambda/interaction/RotationManager.kt +++ b/common/src/main/kotlin/com/lambda/interaction/RotationManager.kt @@ -123,19 +123,20 @@ object RotationManager : Loadable { currentRotation = currentContext?.let { context -> val rotationTo = if (keepTicks >= 0) context.rotation else player.rotation - var speedMultiplier = (context.config as? RotationSettings)?.speedMultiplier ?: 1.0 - if (keepTicks < 0) speedMultiplier = 1.0 - - val turnSpeed = context.config.turnSpeed * speedMultiplier - - currentRotation - .slerp(rotationTo, turnSpeed) - .fixSensitivity(prevRotation) - .apply { - if (context.config.rotationMode != RotationMode.LOCK) return@apply - player.yaw = this.yawF - player.pitch = this.pitchF - } + rotationTo +// var speedMultiplier = (context.config as? RotationSettings)?.speedMultiplier ?: 1.0 +// if (keepTicks < 0) speedMultiplier = 1.0 +// +// val turnSpeed = context.config.turnSpeed * speedMultiplier +// +// currentRotation +// .slerp(rotationTo, turnSpeed) +// .fixSensitivity(prevRotation) +// .apply { +// if (context.config.rotationMode != RotationMode.LOCK) return@apply +// player.yaw = this.yawF +// player.pitch = this.pitchF +// } } ?: player.rotation } diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/context/PlaceContext.kt b/common/src/main/kotlin/com/lambda/interaction/construction/context/PlaceContext.kt index e6804360a..3d23e0691 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/context/PlaceContext.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/context/PlaceContext.kt @@ -24,6 +24,7 @@ import net.minecraft.block.BlockState 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 data class PlaceContext( @@ -37,6 +38,7 @@ data class PlaceContext( val targetState: TargetState, val sneak: Boolean, val insideBlock: Boolean, + val primeDirection: Direction? ) : BuildContext { override val resultingPos: BlockPos get() = result.blockPos.offset(result.side) diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/processing/PlacementProcessor.kt b/common/src/main/kotlin/com/lambda/interaction/construction/processing/PlacementProcessor.kt new file mode 100644 index 000000000..31fde9586 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/construction/processing/PlacementProcessor.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2024 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.processing + +import net.minecraft.block.BlockState + +abstract class PlacementProcessor { + abstract fun acceptState(state: BlockState): Boolean + abstract fun preProcess(state: BlockState): PreprocessingStep +} + diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/processing/PreprocessingStep.kt b/common/src/main/kotlin/com/lambda/interaction/construction/processing/PreprocessingStep.kt new file mode 100644 index 000000000..bd1e566c0 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/construction/processing/PreprocessingStep.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2024 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.processing + +import com.lambda.interaction.construction.verify.SurfaceScan +import net.minecraft.state.property.Property +import net.minecraft.util.math.Direction + +data class PreprocessingStep( + val surfaceScan: SurfaceScan = SurfaceScan.DEFAULT, + val ignore: Set> = emptySet(), + val sides: Set = Direction.entries.toSet(), +) \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/processing/ProcessorRegistry.kt b/common/src/main/kotlin/com/lambda/interaction/construction/processing/ProcessorRegistry.kt new file mode 100644 index 000000000..8ddceec53 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/construction/processing/ProcessorRegistry.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2024 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.processing + +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.Blocks + +object ProcessorRegistry : Loadable { + private const val PROCESSOR_PACKAGE = "com.lambda.interaction.construction.processing.processors" + private val processors = getInstances { forPackages(PROCESSOR_PACKAGE) } + private val processorCache = mutableMapOf() + + fun findProcessorForState(target: TargetState): PreprocessingStep = + (target as? TargetState.State)?.let { state -> + processorCache.getOrPut(state.blockState) { + (processors.find { it.acceptState(state.blockState) } ?: DefaultProcessor).preProcess(state.blockState) + } + } ?: DefaultProcessor.preProcess(Blocks.AIR.defaultState) + + object DefaultProcessor : PlacementProcessor() { + override fun acceptState(state: BlockState) = true + override fun preProcess(state: BlockState) = PreprocessingStep() + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/processing/processors/FacingProcessor.kt b/common/src/main/kotlin/com/lambda/interaction/construction/processing/processors/FacingProcessor.kt new file mode 100644 index 000000000..62e791d4f --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/construction/processing/processors/FacingProcessor.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2024 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.processing.processors + +import com.lambda.interaction.construction.processing.PlacementProcessor +import com.lambda.interaction.construction.processing.PreprocessingStep +import net.minecraft.block.BlockState +import net.minecraft.state.property.Properties + +object FacingProcessor : PlacementProcessor() { + override fun acceptState(state: BlockState) = + state.getOrEmpty(Properties.FACING).isPresent + + override fun preProcess(state: BlockState): PreprocessingStep { + // Needs two sets of blocks: native and opposite! + return PreprocessingStep( + sides = setOf(state.getOrEmpty(Properties.FACING).get()) + ) + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/processing/processors/SlabProcessor.kt b/common/src/main/kotlin/com/lambda/interaction/construction/processing/processors/SlabProcessor.kt new file mode 100644 index 000000000..ac5794690 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/construction/processing/processors/SlabProcessor.kt @@ -0,0 +1,50 @@ +/* + * Copyright 2024 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.processing.processors + +import com.lambda.interaction.construction.processing.PlacementProcessor +import com.lambda.interaction.construction.processing.PreprocessingStep +import com.lambda.interaction.construction.verify.ScanMode +import com.lambda.interaction.construction.verify.SurfaceScan +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.Direction + +object SlabProcessor : PlacementProcessor() { + override fun acceptState(state: BlockState) = state.block is SlabBlock + + override fun preProcess(state: BlockState): PreprocessingStep { + val slab = state.getOrEmpty(Properties.SLAB_TYPE).get() + + val surfaceScan = when (slab) { + SlabType.BOTTOM -> SurfaceScan( + ScanMode.LESSER_HALF, Direction.Axis.Y + ) + SlabType.TOP -> SurfaceScan( + ScanMode.GREATER_HALF, Direction.Axis.Y + ) + SlabType.DOUBLE -> SurfaceScan( + ScanMode.FULL, Direction.Axis.Y + ) + } + + return PreprocessingStep(surfaceScan) + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildGoal.kt b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildGoal.kt index 8c7d5e099..b1259b228 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildGoal.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildGoal.kt @@ -21,16 +21,13 @@ import baritone.api.pathing.goals.Goal import com.lambda.util.world.fastVectorOf class BuildGoal(private val sim: Simulation) : Goal { - override fun isInGoal(x: Int, y: Int, z: Int): Boolean { - val vec = fastVectorOf(x, y, z) - val simu = sim.simulate(vec) - return simu.any { it.rank.ordinal < 4 } - } + override fun isInGoal(x: Int, y: Int, z: Int) = + sim.simulate(fastVectorOf(x, y, z)) + .any { it.rank.ordinal < 4 } override fun heuristic(x: Int, y: Int, z: Int): Double { - val bestRank = sim.simulate(fastVectorOf(x, y, z)).minOrNull()?.rank?.ordinal ?: 100000 - val score = 1 / (bestRank.toDouble() + 1) -// info("Calculating heuristic at $x, $y, $z with score $score") - return score + val bestRank = sim.simulate(fastVectorOf(x, y, z)) + .minOrNull()?.rank?.ordinal ?: 100000 + return 1 / (bestRank.toDouble() + 1) } } diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt index ccf0c2e92..9f65ab7d3 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt @@ -22,17 +22,20 @@ import com.lambda.interaction.RotationManager import com.lambda.interaction.construction.blueprint.Blueprint import com.lambda.interaction.construction.context.BreakContext import com.lambda.interaction.construction.context.PlaceContext +import com.lambda.interaction.construction.processing.ProcessorRegistry.findProcessorForState import com.lambda.interaction.construction.result.BreakResult import com.lambda.interaction.construction.result.BuildResult import com.lambda.interaction.construction.result.PlaceResult import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.material.ContainerManager.findBestAvailableTool +import com.lambda.interaction.rotation.Rotation.Companion.rotation import com.lambda.interaction.rotation.Rotation.Companion.rotationTo import com.lambda.interaction.rotation.RotationContext import com.lambda.interaction.visibilty.VisibilityChecker -import com.lambda.interaction.visibilty.VisibilityChecker.ScanMode.Companion.scanMode +import com.lambda.interaction.visibilty.VisibilityChecker.getVisibleSurfaces import com.lambda.interaction.visibilty.VisibilityChecker.optimum -import com.lambda.interaction.visibilty.VisibilityChecker.scanVisibleSurfaces +import com.lambda.interaction.visibilty.VisibilityChecker.scanSurfaces +import com.lambda.interaction.visibilty.VisibilityChecker.visibleSides import com.lambda.module.modules.client.TaskFlow import com.lambda.threading.runSafe import com.lambda.util.BlockUtils @@ -42,8 +45,10 @@ import com.lambda.util.BlockUtils.vecOf import com.lambda.util.Communication.warn import com.lambda.util.item.ItemStackUtils.equal import com.lambda.util.math.VecUtils.distSq +import com.lambda.util.player.copyPlayer import com.lambda.util.world.raycast.RayCastUtils.blockResult import net.minecraft.block.OperatorBlock +import net.minecraft.block.enums.BlockFace import net.minecraft.block.pattern.CachedBlockPosition import net.minecraft.item.BlockItem import net.minecraft.item.ItemPlacementContext @@ -57,6 +62,8 @@ import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Box import net.minecraft.util.math.Direction import net.minecraft.util.math.Vec3d +import java.util.stream.Collectors +import kotlin.jvm.optionals.getOrNull import kotlin.math.pow object BuildSimulator { @@ -133,7 +140,36 @@ object BuildSimulator { val interact = TaskFlow.interact val rotation = TaskFlow.rotation - Direction.entries.forEach { neighbor -> + val preprocessing = findProcessorForState(target) + +// var sidesToCheck = Direction.entries.toTypedArray() +// +// (target as? TargetState.State) +// ?.blockState +// ?.getOrEmpty(Properties.FACING) +// ?.ifPresent { +// sidesToCheck = arrayOf(it) +// } +// +// (target as? TargetState.State) +// ?.blockState +// ?.getOrEmpty(Properties.BLOCK_FACE) +// ?.ifPresent { +// sidesToCheck = when (it) { +// BlockFace.FLOOR -> arrayOf(Direction.DOWN) +// BlockFace.CEILING -> arrayOf(Direction.UP) +// BlockFace.WALL -> Direction.Type.HORIZONTAL.stream().collect(Collectors.toList()).toTypedArray() +// } +// } +// +// (target as? TargetState.State) +// ?.blockState +// ?.getOrEmpty(Properties.AXIS) +// ?.ifPresent { axis -> +// sidesToCheck = Direction.entries.filter { it.axis == axis }.toTypedArray() +// } + + preprocessing.sides.forEach { neighbor -> val hitPos = pos.offset(neighbor) val hitSide = neighbor.opposite @@ -150,20 +186,22 @@ object BuildSimulator { boxes.forEach { box -> val res = if (TaskFlow.interact.useRayCast) interact.resolution else 4 - val half = (target as? TargetState.State) - ?.blockState - ?.getOrEmpty(Properties.SLAB_TYPE) - ?.scanMode ?: VisibilityChecker.ScanMode.BOTH - scanVisibleSurfaces(eye, box, setOf(hitSide), res, half) { side, vec -> + val sides = if (TaskFlow.interact.visibilityCheck) { + box.getVisibleSurfaces(eye).intersect(setOf(hitSide)) + } else { + Direction.entries.toSet() + } + + scanSurfaces(box, sides, res, preprocessing.surfaceScan) { side, vec -> if (eye distSq vec > reachSq) { misses.add(vec) - return@scanVisibleSurfaces + return@scanSurfaces } - validHits[vec] = if (TaskFlow.interact.useRayCast) { + validHits[vec] = if (TaskFlow.interact.useRayCast && TaskFlow.interact.visibilityCheck) { val cast = eye.rotationTo(vec) - .rayCast(reach, eye) ?: return@scanVisibleSurfaces - if (!cast.verify()) return@scanVisibleSurfaces + .rayCast(reach, eye) ?: return@scanSurfaces + if (!cast.verify()) return@scanSurfaces cast } else { @@ -195,9 +233,15 @@ object BuildSimulator { }?.let { rotation -> val optimalStack = target.getStack(world, pos) + // ToDo: For each hand and sneak or not? + val fakePlayer = copyPlayer(player).apply { + setPos(eye.x, eye.y - standingEyeHeight, eye.z) + this.rotation = rotation.rotation + } + val usageContext = ItemUsageContext( - player, - Hand.MAIN_HAND, // ToDo: notice that the hand may have a different item stack and simulation will be wrong + fakePlayer, + Hand.MAIN_HAND, rotation.hitResult?.blockResult, ) val cachePos = CachedBlockPosition( @@ -240,7 +284,7 @@ object BuildSimulator { } val resultState = blockItem.getPlacementState(context) ?: run { - acc.add(PlaceResult.BlockedByPlayer(pos)) +// acc.add(PlaceResult.BlockedByPlayer(pos)) return@forEach } @@ -255,6 +299,11 @@ object BuildSimulator { val hitBlock = blockHit.blockPos.blockState(world).block val shouldSneak = hitBlock in BlockUtils.interactionBlacklist + val primeDirection = (target as? TargetState.State) + ?.blockState + ?.getOrEmpty(Properties.HORIZONTAL_FACING) + ?.getOrNull() + val placeContext = PlaceContext( eye, blockHit, @@ -265,7 +314,8 @@ object BuildSimulator { Hand.MAIN_HAND, target, shouldSneak, - false + false, + primeDirection ) val currentHandStack = player.getStackInHand(Hand.MAIN_HAND) @@ -377,25 +427,21 @@ object BuildSimulator { boxes.forEach { box -> val res = if (TaskFlow.interact.useRayCast) interact.resolution else 2 - scanVisibleSurfaces(eye, box, emptySet(), res) { side, vec -> + val sides = visibleSides(box, eye, TaskFlow.interact) + scanSurfaces(box, sides, res) { side, vec -> if (eye distSq vec > reachSq) { misses.add(vec) - return@scanVisibleSurfaces + return@scanSurfaces } - validHits[vec] = if (TaskFlow.interact.useRayCast) { + validHits[vec] = if (TaskFlow.interact.useRayCast && TaskFlow.interact.visibilityCheck) { val cast = eye.rotationTo(vec) - .rayCast(reach, eye) ?: return@scanVisibleSurfaces - if (!cast.verify()) return@scanVisibleSurfaces + .rayCast(reach, eye) ?: return@scanSurfaces + if (!cast.verify()) return@scanSurfaces cast } else { - BlockHitResult( - vec, - side, - pos, - false - ) + BlockHitResult(vec, side, pos, false) } } } diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt index f8339b354..6dc1c8696 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt @@ -27,22 +27,23 @@ import com.lambda.util.BlockUtils.blockState import com.lambda.util.world.FastVector import com.lambda.util.world.toBlockPos import com.lambda.util.world.toVec3d +import net.minecraft.client.network.ClientPlayerEntity import net.minecraft.util.math.Box import net.minecraft.util.math.Direction import net.minecraft.util.math.Vec3d data class Simulation(val blueprint: Blueprint) { private val cache: MutableMap> = mutableMapOf() - private fun FastVector.toView(): Vec3d = toVec3d().add(0.5, 0.62, 0.5) + private fun FastVector.toView(): Vec3d = toVec3d().add(0.5, ClientPlayerEntity.DEFAULT_EYE_HEIGHT.toDouble(), 0.5) fun simulate(pos: FastVector) = - cache.computeIfAbsent(pos) { + cache.getOrPut(pos) { val view = pos.toView() runSafe { - if (blueprint.isOutOfBounds(view) && blueprint.getClosestPointTo(view).distanceTo(view) > 10.0) return@computeIfAbsent emptySet() + if (blueprint.isOutOfBounds(view) && blueprint.getClosestPointTo(view).distanceTo(view) > 10.0) return@getOrPut emptySet() val blockPos = pos.toBlockPos() - if (!playerFitsIn(Vec3d.ofBottomCenter(blockPos))) return@computeIfAbsent emptySet() - if (!blockPos.down().blockState(world).isSideSolidFullSquare(world, blockPos, Direction.UP)) return@computeIfAbsent emptySet() + if (!playerFitsIn(Vec3d.ofBottomCenter(blockPos))) return@getOrPut emptySet() + if (!blockPos.down().blockState(world).isSideSolidFullSquare(world, blockPos, Direction.UP)) return@getOrPut emptySet() } blueprint.simulate(view, reach = TaskFlow.interact.reach - 1) } diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/verify/ScanMode.kt b/common/src/main/kotlin/com/lambda/interaction/construction/verify/ScanMode.kt new file mode 100644 index 000000000..44b190fdc --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/construction/verify/ScanMode.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2024 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.verify + +enum class ScanMode { + GREATER_HALF, LESSER_HALF, FULL +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/verify/SurfaceScan.kt b/common/src/main/kotlin/com/lambda/interaction/construction/verify/SurfaceScan.kt new file mode 100644 index 000000000..78586b7f7 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/construction/verify/SurfaceScan.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2024 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.verify + +import net.minecraft.util.math.Direction + +data class SurfaceScan( + val mode: ScanMode, + val axis: Direction.Axis, +) { + companion object { + val DEFAULT = SurfaceScan(ScanMode.FULL, Direction.Axis.Y) + } + +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt b/common/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt index 06a164ca9..dda6e3887 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt @@ -25,7 +25,6 @@ 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.Properties import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Direction diff --git a/common/src/main/kotlin/com/lambda/interaction/rotation/Rotation.kt b/common/src/main/kotlin/com/lambda/interaction/rotation/Rotation.kt index 520001059..39ed9948b 100644 --- a/common/src/main/kotlin/com/lambda/interaction/rotation/Rotation.kt +++ b/common/src/main/kotlin/com/lambda/interaction/rotation/Rotation.kt @@ -72,19 +72,25 @@ data class Rotation(val yaw: Double, val pitch: Double) { box.raycast(eyeVec, eyeVec + vector * reach).orElse(null) } - val Direction.yaw: Float - get() = when (this) { - Direction.NORTH -> -180.0f - Direction.SOUTH -> 0.0f - Direction.EAST -> -90.0f - Direction.WEST -> 90.0f - else -> 0.0f - } - companion object { + val Direction.yaw: Float + get() = when (this) { + Direction.NORTH -> -180.0f + Direction.SOUTH -> 0.0f + Direction.EAST -> -90.0f + Direction.WEST -> 90.0f + else -> 0.0f + } + val ZERO = Rotation(0.0, 0.0) val DOWN = Rotation(0.0, 90.0) - val Entity.rotation get() = Rotation(yaw, pitch) + val UP = Rotation(0.0, -90.0) + val Direction.rotation get() = Rotation(yaw.toDouble(), 0.0) + var Entity.rotation get() = Rotation(yaw, pitch) + set(value) { + yaw = value.yawF + pitch = value.pitchF + } fun wrap(deg: Double) = MathHelper.wrapDegrees(deg) diff --git a/common/src/main/kotlin/com/lambda/interaction/visibilty/VisibilityChecker.kt b/common/src/main/kotlin/com/lambda/interaction/visibilty/VisibilityChecker.kt index 2078cac1d..5c0ceed04 100644 --- a/common/src/main/kotlin/com/lambda/interaction/visibilty/VisibilityChecker.kt +++ b/common/src/main/kotlin/com/lambda/interaction/visibilty/VisibilityChecker.kt @@ -21,17 +21,16 @@ import com.lambda.config.groups.IRotationConfig import com.lambda.config.groups.InteractionConfig import com.lambda.context.SafeContext import com.lambda.interaction.RotationManager -import com.lambda.interaction.rotation.Rotation.Companion.dist +import com.lambda.interaction.construction.verify.ScanMode +import com.lambda.interaction.construction.verify.SurfaceScan import com.lambda.interaction.rotation.Rotation.Companion.rotationTo import com.lambda.interaction.rotation.RotationContext import com.lambda.module.modules.client.TaskFlow import com.lambda.util.BlockUtils.blockState -import com.lambda.util.math.VecUtils.distSq import com.lambda.util.extension.component6 +import com.lambda.util.math.VecUtils.distSq import com.lambda.util.world.raycast.RayCastUtils.blockResult import com.lambda.util.world.raycast.RayCastUtils.entityResult -import net.minecraft.block.enums.BlockHalf -import net.minecraft.block.enums.SlabType import net.minecraft.entity.Entity import net.minecraft.util.hit.HitResult import net.minecraft.util.math.BlockPos @@ -119,13 +118,15 @@ object VisibilityChecker { val reachSq = reach.pow(2) boxes.forEach { box -> - scanVisibleSurfaces(eye, box, sides, interact.resolution) { _, vec -> - if (eye distSq vec > reachSq) return@scanVisibleSurfaces + val visible = visibleSides(box, eye, interact) + + scanSurfaces(box, visible.intersect(sides), interact.resolution) { _, vec -> + if (eye distSq vec > reachSq) return@scanSurfaces val newRotation = eye.rotationTo(vec) - val cast = newRotation.rayCast(reach, eye) ?: return@scanVisibleSurfaces - if (!cast.verify()) return@scanVisibleSurfaces + val cast = newRotation.rayCast(reach, eye) ?: return@scanSurfaces + if (!cast.verify()) return@scanSurfaces validHits[vec] = cast } @@ -147,70 +148,92 @@ object VisibilityChecker { } /** - * Scans the visible surfaces of a given box, identifying points on each surface within a defined resolution. - * The surface is subdivided to hits the corners of the pixels + * Scans the surfaces of a given box, optionally excluding specific sides, + * and executes a callback for each point calculated based on the scanning parameters. * - * @param eyes The player's eye position. - * @param box The bounding box of the target. - * @param sides Set of block sides to consider for visibility. - * @param resolution The number of points to sample along each axis of the box. - * @param check A lambda to check each visible point for a hit. + * @param box The 3D box whose surfaces will be scanned. + * @param excludedSides A set of directions representing the sides of the box to exclude from the scan (default is an empty set). + * @param resolution The number of intervals into which each dimension is divided for scanning (default is 5). + * @param scan Configuration specifying the axis and mode of the scan (default is `SurfaceScan.DEFAULT`). + * @param check A callback function that performs an action for each surface point, receiving the direction of the surface and the current 3D vector. */ - inline fun scanVisibleSurfaces( - eyes: Vec3d, + inline fun scanSurfaces( box: Box, - sides: Set = emptySet(), + excludedSides: Set = emptySet(), resolution: Int = 5, - half: ScanMode = ScanMode.BOTH, + scan: SurfaceScan = SurfaceScan.DEFAULT, check: (Direction, Vec3d) -> Unit, ) { - box.getVisibleSurfaces(eyes) - .forEach { side -> - if (sides.isNotEmpty() && side !in sides) return@forEach - val (minX, minY, minZ, maxX, maxY, maxZ) = box.shrink(0.01, 0.01, 0.01).bounds(side) - val stepX = (maxX - minX) / resolution - val stepY = (maxY - minY) / resolution - val stepZ = (maxZ - minZ) / resolution + excludedSides.forEach { side -> + if (excludedSides.isNotEmpty() && side !in excludedSides) return@forEach + val (minX, minY, minZ, maxX, maxY, maxZ) = box.shrink(0.01, 0.01, 0.01).bounds(side) + val stepX = (maxX - minX) / resolution + val stepY = (maxY - minY) / resolution + val stepZ = (maxZ - minZ) / resolution + // Determine the bounds to scan based on the axis and mode + val (startX, endX) = if (scan.axis == Direction.Axis.X && stepX != 0.0) { + val centerX = (minX + maxX) / 2 + when (scan.mode) { + ScanMode.GREATER_HALF -> centerX + 0.01 to maxX + ScanMode.LESSER_HALF -> minX to centerX - 0.01 + ScanMode.FULL -> minX to maxX + } + } else minX to maxX + + val (startY, endY) = if (scan.axis == Direction.Axis.Y && stepY != 0.0) { val centerY = (minY + maxY) / 2 - val (startY, endY) = if (stepY != 0.0) { - when (half) { - ScanMode.TOP -> centerY + 0.01 to maxY - ScanMode.BOTTOM -> minY to centerY - 0.01 - ScanMode.BOTH -> minY to maxY - } - } else minY to maxY - (0..resolution).forEach { i -> - val x = if (stepX != 0.0) minX + stepX * i else minX - (0..resolution).forEach inner@ { j -> - val y = if (stepY != 0.0) startY + stepY * j else startY - if (y > endY) return@inner - val z = if (stepZ != 0.0) minZ + stepZ * ((if (stepX != 0.0) j else i)) else minZ - check(side, Vec3d(x, y, z)) - } + when (scan.mode) { + ScanMode.GREATER_HALF -> centerY + 0.01 to maxY + ScanMode.LESSER_HALF -> minY to centerY - 0.01 + ScanMode.FULL -> minY to maxY } - } - } + } else minY to maxY - enum class ScanMode { - BOTH, TOP, BOTTOM; + val (startZ, endZ) = if (scan.axis == Direction.Axis.Z && stepZ != 0.0) { + val centerZ = (minZ + maxZ) / 2 + when (scan.mode) { + ScanMode.GREATER_HALF -> centerZ + 0.01 to maxZ + ScanMode.LESSER_HALF -> minZ to centerZ - 0.01 + ScanMode.FULL -> minZ to maxZ + } + } else minZ to maxZ - companion object { - val Optional.scanMode: ScanMode - get() = when (orElse(null)) { - SlabType.TOP -> TOP - SlabType.BOTTOM -> BOTTOM - SlabType.DOUBLE -> BOTH - else -> BOTH + (0..resolution).forEach outer@ { i -> + val x = if (stepX != 0.0) startX + stepX * i else startX + if (x > endX) return@outer + (0..resolution).forEach inner@ { j -> + val y = if (stepY != 0.0) startY + stepY * j else startY + if (y > endY) return@inner + val z = if (stepZ != 0.0) startZ + stepZ * ((if (stepX != 0.0) j else i)) else startZ + if (z > endZ) return@inner + check(side, Vec3d(x, y, z)) } + } } } + /** + * Determines the approximate central point (optimum) of a set of 3D vectors. + */ val Set.optimum: Vec3d? get() = reduceOrNull { acc, vec3d -> acc.add(vec3d) }?.multiply(1.0 / size.toDouble()) + /** + * Determines the sides of a box that are visible from a given position, based on interaction settings. + * + * @param box The box whose visible sides are to be determined. + * @param eye The position (e.g., the player's eyes) to determine visibility from. + * @param interactionSettings The settings that define how visibility checks are handled. + * @return A set of directions corresponding to the visible sides of the box. + */ + fun visibleSides(box: Box, eye: Vec3d, interactionSettings: InteractionConfig) = + if (interactionSettings.visibilityCheck) { + box.getVisibleSurfaces(eye) + } else Direction.entries.toSet() + /** * Gets the bounding coordinates of a box's side, specifying min and max values for each axis. * diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/TaskFlow.kt b/common/src/main/kotlin/com/lambda/module/modules/client/TaskFlow.kt index 1366b4dd7..a8278ba2e 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/TaskFlow.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/TaskFlow.kt @@ -54,7 +54,13 @@ object TaskFlow : Module( Properties.DISTANCE_1_7, Properties.PERSISTENT, Properties.WATERLOGGED, - Properties.STAIR_SHAPE + Properties.STAIR_SHAPE, + Properties.UP, + Properties.DOWN, + Properties.NORTH, + Properties.EAST, + Properties.SOUTH, + Properties.WEST ) // val ignoredTags by setting("Ignored Tags", defaultIgnoreTags) } diff --git a/common/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt b/common/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt index b09d0948f..9ecba1689 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt @@ -31,7 +31,9 @@ import com.lambda.interaction.rotation.Rotation import com.lambda.interaction.rotation.Rotation.Companion.dist import com.lambda.interaction.rotation.Rotation.Companion.rotationTo import com.lambda.interaction.rotation.RotationContext -import com.lambda.interaction.visibilty.VisibilityChecker.scanVisibleSurfaces +import com.lambda.interaction.visibilty.VisibilityChecker.getVisibleSurfaces +import com.lambda.interaction.visibilty.VisibilityChecker.scanSurfaces +import com.lambda.interaction.visibilty.VisibilityChecker.visibleSides import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.threading.runConcurrent @@ -53,6 +55,7 @@ import net.minecraft.network.packet.c2s.play.HandSwingC2SPacket import net.minecraft.network.packet.c2s.play.PlayerInteractEntityC2SPacket import net.minecraft.network.packet.c2s.play.UpdateSelectedSlotC2SPacket import net.minecraft.util.Hand +import net.minecraft.util.math.Direction import net.minecraft.util.math.Vec3d import kotlin.math.pow @@ -243,13 +246,15 @@ object KillAura : Module( // Get visible point set val validHits = mutableMapOf() - scanVisibleSurfaces(eye, box, resolution = interactionSettings.resolution) { _, vec -> - if (eye distSq vec > reachSq) return@scanVisibleSurfaces + val sides = visibleSides(box, eye, interactionSettings) + + scanSurfaces(box, sides, resolution = interactionSettings.resolution) { _, vec -> + if (eye distSq vec > reachSq) return@scanSurfaces val newRotation = eye.rotationTo(vec) - val cast = newRotation.rayCast(reach, eye) ?: return@scanVisibleSurfaces - if (cast.entityResult?.entity != target) return@scanVisibleSurfaces + val cast = newRotation.rayCast(reach, eye) ?: return@scanSurfaces + if (cast.entityResult?.entity != target) return@scanSurfaces validHits[vec] = newRotation } diff --git a/common/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt b/common/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt index 333295046..4db6b41b1 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt @@ -36,9 +36,11 @@ import com.lambda.interaction.rotation.Rotation.Companion.dist import com.lambda.interaction.rotation.Rotation.Companion.rotationTo import com.lambda.interaction.rotation.Rotation.Companion.wrap import com.lambda.interaction.rotation.RotationContext -import com.lambda.interaction.visibilty.VisibilityChecker.scanVisibleSurfaces +import com.lambda.interaction.visibilty.VisibilityChecker.getVisibleSurfaces +import com.lambda.interaction.visibilty.VisibilityChecker.scanSurfaces import com.lambda.module.Module import com.lambda.module.modules.client.GuiSettings +import com.lambda.module.modules.client.TaskFlow import com.lambda.module.tag.ModuleTag import com.lambda.util.math.MathUtils.floorToInt import com.lambda.util.math.VecUtils.dist @@ -212,15 +214,19 @@ object Scaffold : Module( // Dividing the surface by segments and iterating through them val pointScan = mutableSetOf().apply { - scanVisibleSurfaces( - eyes = eye, - box = Box(info.clickPos), + val box = Box(info.clickPos) + val sides = if (TaskFlow.interact.visibilityCheck) { + box.getVisibleSurfaces(eye) + } else Direction.entries.toSet() + scanSurfaces( + box, + sides, resolution = interactionConfig.resolution ) { _, vec -> - if (eye distSq vec > reachSq) return@scanVisibleSurfaces + if (eye distSq vec > reachSq) return@scanSurfaces val rotation = eye.rotationTo(vec) - castRotation(rotation, info) ?: return@scanVisibleSurfaces + castRotation(rotation, info) ?: return@scanSurfaces add(rotation) } diff --git a/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt b/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt index b41e8dcd0..acc64ad5c 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt @@ -47,7 +47,7 @@ class BuildTask @Ta5kBuilder constructor( val collectDrops: Boolean = TaskFlow.build.collectDrops, ) : Task() { private var previousResults = setOf() - private val placeTimeout = 15 + private val placeTimeout = 5 private val pending = mutableListOf() override fun SafeContext.onStart() { @@ -66,9 +66,7 @@ class BuildTask @Ta5kBuilder constructor( if (it.age > placeTimeout) { it.cancel() true - } else { - it.isCompleted - } + } else it.isCompleted } (blueprint as? DynamicBlueprint)?.update(this) @@ -78,6 +76,7 @@ class BuildTask @Ta5kBuilder constructor( return@listen } + // ToDo: Simulate for each pair player positions that work val results = blueprint.simulate(player.getCameraPosVec(mc.tickDelta)) previousResults = results @@ -99,15 +98,17 @@ class BuildTask @Ta5kBuilder constructor( } val result = results.minOrNull() ?: return@listen - when { - !result.rank.solvable -> success(Unit) - result is BuildResult.NotVisible -> { + when (result) { + is BuildResult.Done -> { + if (finishOnDone) success(Unit) + } +// !result.rank.solvable -> failure("Result is not solvable: $result") + is BuildResult.NotVisible, is PlaceResult.NoIntegrity -> { if (pathing) BaritoneUtils.setGoalAndPath( BuildGoal(blueprint.simulation()) ) } - - result is Navigable -> { + is Navigable -> { if (pathing) BaritoneUtils.setGoalAndPath(result.goal) } diff --git a/common/src/main/kotlin/com/lambda/task/tasks/PlaceBlock.kt b/common/src/main/kotlin/com/lambda/task/tasks/PlaceBlock.kt index 4f6fb92d8..4becf35bf 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/PlaceBlock.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/PlaceBlock.kt @@ -26,12 +26,19 @@ import com.lambda.event.events.TickEvent import com.lambda.event.events.WorldEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.construction.context.PlaceContext +import com.lambda.interaction.rotation.Rotation +import com.lambda.interaction.rotation.Rotation.Companion.rotation +import com.lambda.interaction.rotation.RotationContext import com.lambda.module.modules.client.TaskFlow import com.lambda.task.Task import com.lambda.util.BlockUtils import com.lambda.util.BlockUtils.blockState +import com.lambda.util.Communication.info import com.lambda.util.Communication.warn +import com.lambda.util.extension.partialTicks import net.minecraft.block.BlockState +import net.minecraft.client.gui.screen.ingame.SignEditScreen +import net.minecraft.text.Text class PlaceBlock @Ta5kBuilder constructor( private val ctx: PlaceContext, @@ -40,8 +47,10 @@ class PlaceBlock @Ta5kBuilder constructor( private val waitForConfirmation: Boolean, ) : Task() { private var beginState: BlockState? = null - private var state = State.ROTATING + private var state = State.PRIME_ROTATION private var findOutIfNeeded = false + private var primeContext: RotationContext? = null + private var waited = 0 private val SafeContext.resultingState: BlockState get() = ctx.resultingPos.blockState(world) @@ -50,10 +59,14 @@ class PlaceBlock @Ta5kBuilder constructor( get() = ctx.targetState.matches(ctx.resultingPos.blockState(world), ctx.resultingPos, world) enum class State { - ROTATING, PLACING, CONFIRMING + PRIME_ROTATION, ROTATING, PLACING, CONFIRMING } override fun SafeContext.onStart() { + if (ctx.primeDirection == null) { + state = State.ROTATING + } + if (matches) { finish() return @@ -67,25 +80,46 @@ class PlaceBlock @Ta5kBuilder constructor( init { listen { event -> - if (state != State.ROTATING) return@listen if (!rotate) return@listen - event.context = ctx.rotation + when (state) { + State.PRIME_ROTATION -> { + ctx.primeDirection?.let { direction -> + primeContext = RotationContext( + direction.rotation, + ctx.rotation.config, + ) + event.context = primeContext + } + } + else -> event.context = ctx.rotation + } } listen { event -> - if (state != State.ROTATING) return@listen if (!rotate) return@listen - if (event.context != ctx.rotation) return@listen if (!event.context.isValid) return@listen - - state = State.PLACING + Text.of("Rotation: ${event.context.rotation}") + when (state) { + State.PRIME_ROTATION -> { + if (event.context != primeContext) return@listen + state = State.ROTATING + } + State.ROTATING -> { + if (event.context != ctx.rotation) return@listen + if (!event.context.isValid) return@listen + + state = State.PLACING + } + else -> return@listen + } } listen { if (state != State.PLACING) return@listen - if (findOutIfNeeded) placeBlock() - findOutIfNeeded = true + /*if (findOutIfNeeded) placeBlock() + findOutIfNeeded = true*/ + placeBlock() } listen { diff --git a/common/src/main/kotlin/com/lambda/util/player/PlayerUtils.kt b/common/src/main/kotlin/com/lambda/util/player/PlayerUtils.kt new file mode 100644 index 000000000..f32b5cb34 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/util/player/PlayerUtils.kt @@ -0,0 +1,28 @@ +package com.lambda.util.player + +import com.lambda.context.SafeContext +import net.minecraft.client.network.ClientPlayerEntity + +fun SafeContext.copyPlayer(entity: ClientPlayerEntity) = + ClientPlayerEntity( + mc, + world, + mc.networkHandler, + null, + null, + entity.isSneaking, + entity.isSprinting + ).apply { + setPos(entity.x, entity.y, entity.z) + setExperience(entity.experienceProgress, entity.totalExperience, entity.experienceLevel) + pitch = entity.pitch + yaw = entity.yaw + headYaw = entity.headYaw + bodyYaw = entity.bodyYaw + velocity = entity.velocity + movementSpeed = entity.movementSpeed + isSneaking = entity.isSneaking + isSprinting = entity.isSprinting + isSwimming = entity.isSwimming + isOnGround = entity.isOnGround + } \ No newline at end of file From d91811f394140d92542dc771104a61e88a17aca4 Mon Sep 17 00:00:00 2001 From: Constructor Date: Tue, 17 Dec 2024 00:41:05 +0100 Subject: [PATCH 24/39] Fix format --- .../lambda/interaction/construction/result/BreakResult.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/result/BreakResult.kt b/common/src/main/kotlin/com/lambda/interaction/construction/result/BreakResult.kt index cdb7a350d..87d8c648b 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/result/BreakResult.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/result/BreakResult.kt @@ -117,10 +117,10 @@ sealed class BreakResult : BuildResult() { ?.onSuccess { _, _ -> success(Unit) }?.start(this@ItemCantMine) ?: run { - selectStack { - isItem(badItem).not() - }.transfer(MainHandContainer)?.start(this@ItemCantMine) ?: failure("No item found or space") - } + selectStack { + isItem(badItem).not() + }.transfer(MainHandContainer)?.start(this@ItemCantMine) ?: failure("No item found or space") + } } override fun SafeContext.buildRenderer() { From a5ea17c79d1649e470ad3639559b0dcd8075f706 Mon Sep 17 00:00:00 2001 From: Constructor Date: Mon, 23 Dec 2024 04:01:10 +0100 Subject: [PATCH 25/39] Refactor Task system for enhanced flexibility and clarity Replaced `TaskResult` with more explicit task patterns and streamlined result handling. Improved modularity by refactoring references to `TaskFlow` into `TaskFlowModule`. Added `name` properties to various tasks for better debugging and logging. --- .../lambda/mixin/render/DebugHudMixin.java | 4 +- .../lambda/mixin/world/ClientWorldMixin.java | 7 - .../com/lambda/mixin/world/WorldMixin.java | 43 ++ .../lambda/command/commands/BuildCommand.kt | 3 +- .../lambda/command/commands/TaskCommand.kt | 14 +- .../command/commands/TransferCommand.kt | 13 +- .../com/lambda/event/events/WorldEvent.kt | 11 +- .../graphics/renderer/esp/ChunkedESP.kt | 2 +- .../construction/blueprint/Blueprint.kt | 2 + .../blueprint/DynamicBlueprint.kt | 2 + .../construction/blueprint/StaticBlueprint.kt | 2 + .../construction/result/BreakResult.kt | 16 +- .../construction/result/BuildResult.kt | 29 +- .../construction/result/PlaceResult.kt | 14 +- .../construction/simulation/BuildSimulator.kt | 35 +- .../construction/simulation/Simulation.kt | 4 +- .../construction/verify/TargetState.kt | 22 +- .../interaction/material/ContainerManager.kt | 4 +- .../interaction/material/ContainerTask.kt | 40 ++ .../interaction/material/MaterialContainer.kt | 12 +- .../material/container/ChestContainer.kt | 23 +- .../material/container/CreativeContainer.kt | 64 +- .../material/container/EnderChestContainer.kt | 29 +- .../material/container/HotbarContainer.kt | 18 +- .../material/container/InventoryContainer.kt | 9 +- .../material/container/MainHandContainer.kt | 38 +- .../material/container/OffHandContainer.kt | 34 +- .../material/container/ShulkerBoxContainer.kt | 40 +- .../material/container/StashContainer.kt | 8 - .../material/transfer/TransferResult.kt | 15 +- .../visibilty/VisibilityChecker.kt | 10 +- .../kotlin/com/lambda/module/HudModule.kt | 2 +- .../hud/{TaskFlow.kt => TaskFlowHUD.kt} | 11 +- .../client/{TaskFlow.kt => TaskFlowModule.kt} | 2 +- .../module/modules/debug/ContainerTest.kt | 3 +- .../module/modules/player/HighwayTools.kt | 7 +- .../module/modules/player/InventoryTweaks.kt | 13 +- .../com/lambda/module/modules/player/Nuker.kt | 9 +- .../module/modules/player/PacketMine.kt | 6 +- .../lambda/module/modules/player/Scaffold.kt | 4 +- .../module/modules/player/WorldEater.kt | 5 +- .../src/main/kotlin/com/lambda/task/Task.kt | 560 +++++------------- .../lambda/task/{RootTask.kt => TaskFlow.kt} | 21 +- .../main/kotlin/com/lambda/task/TaskResult.kt | 56 -- .../com/lambda/task/tasks/AcquireMaterial.kt | 9 +- .../com/lambda/task/tasks/BreakBlock.kt | 45 +- .../kotlin/com/lambda/task/tasks/BuildTask.kt | 33 +- .../com/lambda/task/tasks/InventoryTask.kt | 29 +- .../com/lambda/task/tasks/OpenContainer.kt | 34 +- .../com/lambda/task/tasks/PlaceBlock.kt | 58 +- .../com/lambda/task/tasks/PlaceContainer.kt | 16 +- .../main/resources/lambda.mixins.common.json | 3 +- 52 files changed, 667 insertions(+), 826 deletions(-) create mode 100644 common/src/main/java/com/lambda/mixin/world/WorldMixin.java create mode 100644 common/src/main/kotlin/com/lambda/interaction/material/ContainerTask.kt rename common/src/main/kotlin/com/lambda/module/hud/{TaskFlow.kt => TaskFlowHUD.kt} (73%) rename common/src/main/kotlin/com/lambda/module/modules/client/{TaskFlow.kt => TaskFlowModule.kt} (98%) rename common/src/main/kotlin/com/lambda/task/{RootTask.kt => TaskFlow.kt} (68%) delete mode 100644 common/src/main/kotlin/com/lambda/task/TaskResult.kt diff --git a/common/src/main/java/com/lambda/mixin/render/DebugHudMixin.java b/common/src/main/java/com/lambda/mixin/render/DebugHudMixin.java index 15d19aac3..c6027a33d 100644 --- a/common/src/main/java/com/lambda/mixin/render/DebugHudMixin.java +++ b/common/src/main/java/com/lambda/mixin/render/DebugHudMixin.java @@ -17,7 +17,7 @@ package com.lambda.mixin.render; -import com.lambda.task.RootTask; +import com.lambda.task.TaskFlow; import com.lambda.util.DebugInfoHud; import net.minecraft.client.gui.hud.DebugHud; import org.spongepowered.asm.mixin.Mixin; @@ -36,6 +36,6 @@ private void onGetRightText(CallbackInfoReturnable> cir) { @Inject(method = "getLeftText", at = @At(value = "TAIL")) private void onGetLeftText(CallbackInfoReturnable> cir) { - RootTask.INSTANCE.addInfo(cir.getReturnValue()); + cir.getReturnValue().addAll(List.of(TaskFlow.INSTANCE.toString().split("\n"))); } } diff --git a/common/src/main/java/com/lambda/mixin/world/ClientWorldMixin.java b/common/src/main/java/com/lambda/mixin/world/ClientWorldMixin.java index 40ecde9d4..6ca55a063 100644 --- a/common/src/main/java/com/lambda/mixin/world/ClientWorldMixin.java +++ b/common/src/main/java/com/lambda/mixin/world/ClientWorldMixin.java @@ -34,13 +34,6 @@ @Mixin(ClientWorld.class) public class ClientWorldMixin { - @Inject(method = "handleBlockUpdate", at = @At("HEAD"), cancellable = true) - private void handleBlockUpdateInject(BlockPos pos, BlockState state, int flags, CallbackInfo ci) { - if (EventFlow.post(new WorldEvent.BlockUpdate(pos, state, flags)).isCanceled()) { - ci.cancel(); - } - } - @Inject(method = "addEntity", at = @At("HEAD"), cancellable = true) private void addEntity(Entity entity, CallbackInfo ci) { if (EventFlow.post(new WorldEvent.EntitySpawn(entity)).isCanceled()) ci.cancel(); diff --git a/common/src/main/java/com/lambda/mixin/world/WorldMixin.java b/common/src/main/java/com/lambda/mixin/world/WorldMixin.java new file mode 100644 index 000000000..d9ae78a13 --- /dev/null +++ b/common/src/main/java/com/lambda/mixin/world/WorldMixin.java @@ -0,0 +1,43 @@ +/* + * Copyright 2024 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.mixin.world; + +import com.lambda.event.EventFlow; +import com.lambda.event.events.WorldEvent; +import net.minecraft.block.BlockState; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(World.class) +public abstract class WorldMixin { + @Inject(method = "setBlockState(Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;II)Z", at = @At("HEAD"), cancellable = true) + void setBlockStatePre(BlockPos pos, BlockState state, int flags, int maxUpdateDepth, CallbackInfoReturnable cir) { + if (EventFlow.post(new WorldEvent.BlockUpdate.Pre(pos, state, flags, maxUpdateDepth)).isCanceled()) { + cir.setReturnValue(false); + } + } + + @Inject(method = "setBlockState(Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;II)Z", at = @At("TAIL")) + void setBlockStatePost(BlockPos pos, BlockState state, int flags, int maxUpdateDepth, CallbackInfoReturnable cir) { + EventFlow.post(new WorldEvent.BlockUpdate.Post(pos, state, flags, maxUpdateDepth)); + } +} diff --git a/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt b/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt index 4fb2c2b08..e861f8b5d 100644 --- a/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt +++ b/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt @@ -26,6 +26,7 @@ import com.lambda.command.LambdaCommand import com.lambda.interaction.construction.StructureRegistry import com.lambda.interaction.construction.blueprint.Blueprint.Companion.toStructure import com.lambda.interaction.construction.blueprint.StaticBlueprint.Companion.toBlueprint +import com.lambda.task.TaskFlow.run import com.lambda.task.tasks.BuildTask.Companion.build import com.lambda.threading.runSafe import com.lambda.util.Communication.info @@ -62,7 +63,7 @@ object BuildCommand : LambdaCommand( .move(player.blockPos) .toBlueprint() .build(pathing = doPathing) - .start(null) + .run() return@executeWithResult CommandResult.success() } diff --git a/common/src/main/kotlin/com/lambda/command/commands/TaskCommand.kt b/common/src/main/kotlin/com/lambda/command/commands/TaskCommand.kt index 41dd5fb7e..97c33383a 100644 --- a/common/src/main/kotlin/com/lambda/command/commands/TaskCommand.kt +++ b/common/src/main/kotlin/com/lambda/command/commands/TaskCommand.kt @@ -21,20 +21,28 @@ import com.lambda.brigadier.argument.literal import com.lambda.brigadier.execute import com.lambda.brigadier.required import com.lambda.command.LambdaCommand -import com.lambda.task.RootTask +import com.lambda.task.TaskFlow import com.lambda.util.Communication.info import com.lambda.util.extension.CommandBuilder object TaskCommand : LambdaCommand( name = "task", - usage = "task ", + usage = "task ", description = "Control tasks" ) { override fun CommandBuilder.create() { required(literal("cancel")) { execute { this@TaskCommand.info("Cancelling all tasks") - RootTask.cancel() + TaskFlow.cancel() + } + } + + required(literal("clear")) { + execute { + this@TaskCommand.info("Clearing all tasks") + TaskFlow.cancel() + TaskFlow.clear() } } } diff --git a/common/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt b/common/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt index 4ab9ab51f..42b938b5b 100644 --- a/common/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt +++ b/common/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt @@ -27,6 +27,7 @@ import com.lambda.interaction.material.ContainerManager import com.lambda.interaction.material.ContainerManager.containerMatchSelection import com.lambda.interaction.material.StackSelection.Companion.selectStack import com.lambda.interaction.material.transfer.TransferResult +import com.lambda.task.TaskFlow.run import com.lambda.util.Communication.info import com.lambda.util.extension.CommandBuilder @@ -77,18 +78,18 @@ object TransferCommand : LambdaCommand( it.name == to().value().split(" with ").firstOrNull() } ?: return@executeWithResult failure("To container not found") - when (val result = fromContainer.transfer(selection, toContainer)) { + when (val transaction = fromContainer.transfer(selection, toContainer)) { is TransferResult.Transfer -> { - info("$result started.") - lastTransfer = result - result.onSuccess { _, _ -> + info("$transaction started.") + lastTransfer = transaction + transaction.finally { info("$lastTransfer completed.") - }.start(null) + }.run() return@executeWithResult success() } is TransferResult.MissingItems -> { - return@executeWithResult failure("Missing items: ${result.missing}") + return@executeWithResult failure("Missing items: ${transaction.missing}") } is TransferResult.NoSpace -> { diff --git a/common/src/main/kotlin/com/lambda/event/events/WorldEvent.kt b/common/src/main/kotlin/com/lambda/event/events/WorldEvent.kt index f7be6c13c..de0ea6cc1 100644 --- a/common/src/main/kotlin/com/lambda/event/events/WorldEvent.kt +++ b/common/src/main/kotlin/com/lambda/event/events/WorldEvent.kt @@ -21,7 +21,6 @@ import com.lambda.event.Event import com.lambda.event.callback.Cancellable import com.lambda.event.callback.ICancellable import net.minecraft.block.BlockState -import net.minecraft.client.world.ClientWorld import net.minecraft.entity.Entity import net.minecraft.entity.data.TrackedData import net.minecraft.util.math.BlockPos @@ -49,11 +48,15 @@ sealed class WorldEvent { /** * Represents a block update in the world */ - class BlockUpdate( + sealed class BlockUpdate( val pos: BlockPos, val state: BlockState, - val flags: Int - ) : ICancellable by Cancellable() + val flags: Int, + val maxUpdateDepth: Int, + ) { + class Pre(pos: BlockPos, state: BlockState, flags: Int, depth: Int) : BlockUpdate(pos, state, flags, depth), ICancellable by Cancellable() + class Post(pos: BlockPos, state: BlockState, flags: Int, depth: Int) : BlockUpdate(pos, state, flags, depth), Event + } /** * Represents an entity being added to the world diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/ChunkedESP.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/ChunkedESP.kt index 6210c599d..44ddc78d6 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/ChunkedESP.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/ChunkedESP.kt @@ -53,7 +53,7 @@ class ChunkedESP private constructor( } init { - listenConcurrently { event -> + listenConcurrently { event -> world.getWorldChunk(event.pos).renderer.notifyChunks() } diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/blueprint/Blueprint.kt b/common/src/main/kotlin/com/lambda/interaction/construction/blueprint/Blueprint.kt index 0b0978f45..136ba7aea 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/blueprint/Blueprint.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/blueprint/Blueprint.kt @@ -46,6 +46,8 @@ abstract class Blueprint { fun isOutOfBounds(vec3d: Vec3d): Boolean = !bounds.contains(vec3d.blockPos) + val center get() = bounds.center.blockPos + companion object { fun emptyStructure(): Structure = emptyMap() diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/blueprint/DynamicBlueprint.kt b/common/src/main/kotlin/com/lambda/interaction/construction/blueprint/DynamicBlueprint.kt index cfa86b5aa..ca21e401d 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/blueprint/DynamicBlueprint.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/blueprint/DynamicBlueprint.kt @@ -36,6 +36,8 @@ data class DynamicBlueprint( override var structure: Structure = emptyMap() private set + override fun toString() = "Dynamic Blueprint at ${center.toShortString()}" + companion object { fun offset(offset: Vec3i): SafeContext.(Structure) -> Structure = { it.map { (pos, state) -> diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/blueprint/StaticBlueprint.kt b/common/src/main/kotlin/com/lambda/interaction/construction/blueprint/StaticBlueprint.kt index 690ccb67d..b41f806d0 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/blueprint/StaticBlueprint.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/blueprint/StaticBlueprint.kt @@ -22,6 +22,8 @@ import com.lambda.util.extension.Structure data class StaticBlueprint( override val structure: Structure ) : Blueprint() { + override fun toString() = "Static Blueprint at ${center.toShortString()}" + companion object { fun Structure.toBlueprint() = StaticBlueprint(this) } diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/result/BreakResult.kt b/common/src/main/kotlin/com/lambda/interaction/construction/result/BreakResult.kt index 87d8c648b..ff9523adf 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/result/BreakResult.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/result/BreakResult.kt @@ -26,7 +26,7 @@ import com.lambda.interaction.material.ContainerManager.transfer import com.lambda.interaction.material.StackSelection.Companion.select import com.lambda.interaction.material.StackSelection.Companion.selectStack import com.lambda.interaction.material.container.MainHandContainer -import com.lambda.task.tasks.BreakBlock.Companion.breakBlock +import com.lambda.task.tasks.BreakBlock import net.minecraft.block.BlockState import net.minecraft.item.Item import net.minecraft.util.math.BlockPos @@ -50,9 +50,9 @@ sealed class BreakResult : BuildResult() { override val pausesParent get() = collectDrop override fun SafeContext.onStart() { - breakBlock(context, collectDrop = collectDrop).onSuccess { _, _ -> - success(Unit) - }.start(this@Break) + BreakBlock(context, collectDrop).finally { + success() + }.execute(this@Break) } override fun SafeContext.buildRenderer() { @@ -114,12 +114,12 @@ sealed class BreakResult : BuildResult() { findBestAvailableTool(blockState) ?.select() ?.transfer(MainHandContainer) - ?.onSuccess { _, _ -> - success(Unit) - }?.start(this@ItemCantMine) ?: run { + ?.finally { + success() + }?.execute(this@ItemCantMine) ?: run { selectStack { isItem(badItem).not() - }.transfer(MainHandContainer)?.start(this@ItemCantMine) ?: failure("No item found or space") + }.transfer(MainHandContainer)?.execute(this@ItemCantMine) ?: failure("No item found or space") } } diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/result/BuildResult.kt b/common/src/main/kotlin/com/lambda/interaction/construction/result/BuildResult.kt index 632d4acef..e44af123f 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/result/BuildResult.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/result/BuildResult.kt @@ -38,6 +38,7 @@ import java.awt.Color abstract class BuildResult : ComparableResult, Task() { abstract val blockPos: BlockPos open val pausesParent = false + override val name: String get() = "${this::class.simpleName} at ${blockPos.toShortString()}" /** * The build action is done. @@ -45,6 +46,8 @@ abstract class BuildResult : ComparableResult, Task() { data class Done( override val blockPos: BlockPos ) : BuildResult() { + override val name: String + get() = "Build at $blockPos is done." override val rank = Rank.DONE } @@ -54,6 +57,8 @@ abstract class BuildResult : ComparableResult, Task() { data class Ignored( override val blockPos: BlockPos ) : BuildResult() { + override val name: String + get() = "Build at $blockPos is ignored." override val rank = Rank.IGNORED } @@ -64,6 +69,7 @@ abstract class BuildResult : ComparableResult, Task() { 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) @@ -88,6 +94,7 @@ abstract class BuildResult : ComparableResult, Task() { 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) @@ -105,6 +112,7 @@ abstract class BuildResult : ComparableResult, Task() { 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) @@ -120,6 +128,7 @@ abstract class BuildResult : ComparableResult, Task() { 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) @@ -137,6 +146,7 @@ abstract class BuildResult : ComparableResult, Task() { 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) @@ -156,6 +166,7 @@ abstract class BuildResult : ComparableResult, Task() { 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) @@ -178,8 +189,10 @@ abstract class BuildResult : ComparableResult, Task() { data class WrongItem( override val blockPos: BlockPos, val context: BuildContext, - val neededItem: Item + val neededItem: Item, + val currentItem: ItemStack, ) : Drawable, BuildResult() { + override val name: String get() = "Wrong item ($currentItem) for ${blockPos.toShortString()} need ${neededItem.name.string}" override val rank = Rank.WRONG_ITEM private val color = Color(3, 252, 169, 100) @@ -188,9 +201,9 @@ abstract class BuildResult : ComparableResult, Task() { override fun SafeContext.onStart() { neededItem.select() .transfer(MainHandContainer) - ?.onSuccess { _, _ -> - success(Unit) - }?.start(this@WrongItem) ?: failure("Item ${neededItem.name.string} not found") + ?.finally { + success() + }?.execute(this@WrongItem) ?: failure("Item ${neededItem.name.string} not found") } override fun SafeContext.buildRenderer() { @@ -219,6 +232,7 @@ abstract class BuildResult : ComparableResult, Task() { val context: BuildContext, val neededStack: ItemStack ) : Drawable, BuildResult() { + override val name: String get() = "Wrong stack for $blockPos need $neededStack." override val rank = Rank.WRONG_ITEM private val color = Color(3, 252, 169, 100) @@ -227,9 +241,9 @@ abstract class BuildResult : ComparableResult, Task() { override fun SafeContext.onStart() { neededStack.select() .transfer(MainHandContainer) - ?.onSuccess { _, _ -> - success(Unit) - }?.start(this@WrongStack) ?: failTask("Stack ${neededStack.name.string} not found") + ?.finally { + success() + }?.execute(this@WrongStack) ?: failure("Stack ${neededStack.name.string} not found") } override fun SafeContext.buildRenderer() { @@ -259,6 +273,7 @@ abstract class BuildResult : ComparableResult, Task() { 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, 100) diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/result/PlaceResult.kt b/common/src/main/kotlin/com/lambda/interaction/construction/result/PlaceResult.kt index 7a54d6c7f..b2d516417 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/result/PlaceResult.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/result/PlaceResult.kt @@ -22,7 +22,7 @@ import baritone.api.pathing.goals.GoalInverted import com.lambda.context.SafeContext import com.lambda.interaction.construction.context.PlaceContext import com.lambda.task.tasks.BuildTask.Companion.breakBlock -import com.lambda.task.tasks.PlaceBlock.Companion.placeBlock +import com.lambda.task.tasks.PlaceBlock import net.minecraft.block.BlockState import net.minecraft.item.ItemPlacementContext import net.minecraft.item.ItemStack @@ -49,9 +49,9 @@ sealed class PlaceResult : BuildResult() { private val color = Color(35, 188, 254, 100) override fun SafeContext.onStart() { - placeBlock(context).onSuccess { _, _ -> - success(Unit) - }.start(this@Place) + PlaceBlock(context).finally { + success() + }.execute(this@Place) } override fun SafeContext.buildRenderer() { @@ -109,9 +109,9 @@ sealed class PlaceResult : BuildResult() { override val rank = Rank.PLACE_CANT_REPLACE override fun SafeContext.onStart() { - breakBlock(blockPos).onSuccess { _, _ -> - success(Unit) - }.start(this@CantReplace) + breakBlock(blockPos).finally { + success() + }.execute(this@CantReplace) } } diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt index 9f65ab7d3..8a173dab6 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt @@ -31,12 +31,11 @@ import com.lambda.interaction.material.ContainerManager.findBestAvailableTool import com.lambda.interaction.rotation.Rotation.Companion.rotation import com.lambda.interaction.rotation.Rotation.Companion.rotationTo import com.lambda.interaction.rotation.RotationContext -import com.lambda.interaction.visibilty.VisibilityChecker import com.lambda.interaction.visibilty.VisibilityChecker.getVisibleSurfaces import com.lambda.interaction.visibilty.VisibilityChecker.optimum import com.lambda.interaction.visibilty.VisibilityChecker.scanSurfaces import com.lambda.interaction.visibilty.VisibilityChecker.visibleSides -import com.lambda.module.modules.client.TaskFlow +import com.lambda.module.modules.client.TaskFlowModule import com.lambda.threading.runSafe import com.lambda.util.BlockUtils import com.lambda.util.BlockUtils.blockState @@ -48,7 +47,6 @@ import com.lambda.util.math.VecUtils.distSq import com.lambda.util.player.copyPlayer import com.lambda.util.world.raycast.RayCastUtils.blockResult import net.minecraft.block.OperatorBlock -import net.minecraft.block.enums.BlockFace import net.minecraft.block.pattern.CachedBlockPosition import net.minecraft.item.BlockItem import net.minecraft.item.ItemPlacementContext @@ -62,12 +60,11 @@ import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Box import net.minecraft.util.math.Direction import net.minecraft.util.math.Vec3d -import java.util.stream.Collectors import kotlin.jvm.optionals.getOrNull import kotlin.math.pow object BuildSimulator { - fun Blueprint.simulate(eye: Vec3d, reach: Double = TaskFlow.interact.reach) = + fun Blueprint.simulate(eye: Vec3d, reach: Double = TaskFlowModule.interact.reach) = runSafe { structure.entries.flatMap { (pos, target) -> checkRequirements(pos, target)?.let { @@ -100,7 +97,7 @@ object BuildSimulator { } /* block should be ignored */ - if (state.block in TaskFlow.ignoredBlocks && target.type == TargetState.Type.AIR) { + if (state.block in TaskFlowModule.ignoredBlocks && target.type == TargetState.Type.AIR) { return BuildResult.Ignored(pos) } @@ -137,8 +134,8 @@ object BuildSimulator { if (target is TargetState.Air || !pos.blockState(world).isReplaceable) return acc - val interact = TaskFlow.interact - val rotation = TaskFlow.rotation + val interact = TaskFlowModule.interact + val rotation = TaskFlowModule.rotation val preprocessing = findProcessorForState(target) @@ -185,8 +182,8 @@ object BuildSimulator { val reachSq = reach.pow(2) boxes.forEach { box -> - val res = if (TaskFlow.interact.useRayCast) interact.resolution else 4 - val sides = if (TaskFlow.interact.visibilityCheck) { + val res = if (TaskFlowModule.interact.useRayCast) interact.resolution else 4 + val sides = if (TaskFlowModule.interact.visibilityCheck) { box.getVisibleSurfaces(eye).intersect(setOf(hitSide)) } else { Direction.entries.toSet() @@ -198,7 +195,7 @@ object BuildSimulator { return@scanSurfaces } - validHits[vec] = if (TaskFlow.interact.useRayCast && TaskFlow.interact.visibilityCheck) { + validHits[vec] = if (TaskFlowModule.interact.useRayCast && TaskFlowModule.interact.visibilityCheck) { val cast = eye.rotationTo(vec) .rayCast(reach, eye) ?: return@scanSurfaces if (!cast.verify()) return@scanSurfaces @@ -325,7 +322,7 @@ object BuildSimulator { } if (optimalStack.item != currentHandStack.item) { - acc.add(BuildResult.WrongItem(pos, placeContext, optimalStack.item)) + acc.add(BuildResult.WrongItem(pos, placeContext, optimalStack.item, currentHandStack)) return@forEach } @@ -345,7 +342,7 @@ object BuildSimulator { val state = pos.blockState(world) /* is a block that will be destroyed by breaking adjacent blocks */ - if (TaskFlow.build.breakWeakBlocks && state.block.hardness == 0f && !state.isAir) { + if (TaskFlowModule.build.breakWeakBlocks && state.block.hardness == 0f && !state.isAir) { acc.add(BuildResult.Ignored(pos)) return acc } @@ -392,8 +389,8 @@ object BuildSimulator { return acc } - val interact = TaskFlow.interact - val rotation = TaskFlow.rotation + val interact = TaskFlowModule.interact + val rotation = TaskFlowModule.rotation val currentRotation = RotationManager.currentRotation val currentCast = currentRotation.rayCast(reach, eye) @@ -426,15 +423,15 @@ object BuildSimulator { val reachSq = reach.pow(2) boxes.forEach { box -> - val res = if (TaskFlow.interact.useRayCast) interact.resolution else 2 - val sides = visibleSides(box, eye, TaskFlow.interact) + val res = if (TaskFlowModule.interact.useRayCast) interact.resolution else 2 + val sides = visibleSides(box, eye, TaskFlowModule.interact) scanSurfaces(box, sides, res) { side, vec -> if (eye distSq vec > reachSq) { misses.add(vec) return@scanSurfaces } - validHits[vec] = if (TaskFlow.interact.useRayCast && TaskFlow.interact.visibilityCheck) { + validHits[vec] = if (TaskFlowModule.interact.useRayCast && TaskFlowModule.interact.visibilityCheck) { val cast = eye.rotationTo(vec) .rayCast(reach, eye) ?: return@scanSurfaces if (!cast.verify()) return@scanSurfaces @@ -479,7 +476,7 @@ object BuildSimulator { acc.add(BreakResult.Break(pos, breakContext)) return acc } ?: run { - acc.add(BuildResult.WrongItem(pos, breakContext, bestTool)) + acc.add(BuildResult.WrongItem(pos, breakContext, bestTool, player.activeItem)) return acc } } diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt index 6dc1c8696..f8c05bb27 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt @@ -21,7 +21,7 @@ import com.lambda.context.SafeContext import com.lambda.interaction.construction.blueprint.Blueprint import com.lambda.interaction.construction.result.BuildResult import com.lambda.interaction.construction.simulation.BuildSimulator.simulate -import com.lambda.module.modules.client.TaskFlow +import com.lambda.module.modules.client.TaskFlowModule import com.lambda.threading.runSafe import com.lambda.util.BlockUtils.blockState import com.lambda.util.world.FastVector @@ -45,7 +45,7 @@ data class Simulation(val blueprint: Blueprint) { if (!playerFitsIn(Vec3d.ofBottomCenter(blockPos))) return@getOrPut emptySet() if (!blockPos.down().blockState(world).isSideSolidFullSquare(world, blockPos, Direction.UP)) return@getOrPut emptySet() } - blueprint.simulate(view, reach = TaskFlow.interact.reach - 1) + blueprint.simulate(view, reach = TaskFlowModule.interact.reach - 1) } private fun SafeContext.playerFitsIn(pos: Vec3d): Boolean { diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt b/common/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt index dda6e3887..ceb77d74e 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt @@ -18,8 +18,9 @@ package com.lambda.interaction.construction.verify import com.lambda.interaction.material.ContainerManager.findDisposable -import com.lambda.module.modules.client.TaskFlow +import com.lambda.module.modules.client.TaskFlowModule import com.lambda.util.BlockUtils.blockState +import com.lambda.util.StringUtils.capitalize import com.lambda.util.item.ItemUtils.block import net.minecraft.block.BlockState import net.minecraft.client.world.ClientWorld @@ -35,6 +36,8 @@ sealed class TargetState(val type: Type) : StateMatcher { } data object Air : TargetState(Type.AIR) { + override fun toString() = "Air" + override fun matches(state: BlockState, pos: BlockPos, world: ClientWorld) = state.isAir @@ -43,36 +46,44 @@ sealed class TargetState(val type: Type) : StateMatcher { } data object Solid : TargetState(Type.SOLID) { + override fun toString() = "Solid" + override fun matches(state: BlockState, pos: BlockPos, world: ClientWorld) = state.isSolidBlock(world, pos) override fun getStack(world: ClientWorld, pos: BlockPos) = findDisposable()?.stacks?.firstOrNull { - it.item.block in TaskFlow.disposables + it.item.block in TaskFlowModule.disposables } ?: ItemStack(Items.NETHERRACK) } data class Support(val direction: Direction) : TargetState(Type.SUPPORT) { + override fun toString() = "Support for ${direction.name}" + override fun matches(state: BlockState, pos: BlockPos, world: ClientWorld) = pos.offset(direction).blockState(world).isSolidBlock(world, pos.offset(direction)) || state.isSolidBlock(world, pos) override fun getStack(world: ClientWorld, pos: BlockPos) = findDisposable()?.stacks?.firstOrNull { - it.item.block in TaskFlow.disposables + it.item.block in TaskFlowModule.disposables } ?: ItemStack(Items.NETHERRACK) } data class State(val blockState: BlockState) : TargetState(Type.STATE) { + override fun toString() = "State of $blockState" + override fun matches(state: BlockState, pos: BlockPos, world: ClientWorld) = state.block == blockState.block && state.properties.all { - it in TaskFlow.defaultIgnoreTags || state[it] == blockState[it] + it in TaskFlowModule.defaultIgnoreTags || state[it] == blockState[it] } override fun getStack(world: ClientWorld, pos: BlockPos): ItemStack = blockState.block.getPickStack(world, pos, blockState) } data class Block(val block: net.minecraft.block.Block) : TargetState(Type.BLOCK) { + override fun toString() = "Block of ${block.name.string.capitalize()}" + override fun matches(state: BlockState, pos: BlockPos, world: ClientWorld) = state.block == block @@ -81,6 +92,9 @@ sealed class TargetState(val type: Type) : StateMatcher { } 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()}" + private val block = itemStack.item.block override fun matches(state: BlockState, pos: BlockPos, world: ClientWorld) = diff --git a/common/src/main/kotlin/com/lambda/interaction/material/ContainerManager.kt b/common/src/main/kotlin/com/lambda/interaction/material/ContainerManager.kt index 12d98ef08..a1346d402 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/ContainerManager.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/ContainerManager.kt @@ -23,7 +23,7 @@ import com.lambda.event.events.ScreenHandlerEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.material.StackSelection.Companion.select import com.lambda.interaction.material.container.* -import com.lambda.module.modules.client.TaskFlow +import com.lambda.module.modules.client.TaskFlowModule import com.lambda.util.BlockUtils.blockEntity import com.lambda.util.BlockUtils.item import com.lambda.util.Communication.info @@ -133,7 +133,7 @@ object ContainerManager : Loadable { }?.first fun findDisposable() = container().find { container -> - TaskFlow.disposables.any { container.available(it.item.select()) >= 0 } + TaskFlowModule.disposables.any { container.available(it.item.select()) >= 0 } } class NoContainerFound(selection: StackSelection) : Exception("No container found matching $selection") diff --git a/common/src/main/kotlin/com/lambda/interaction/material/ContainerTask.kt b/common/src/main/kotlin/com/lambda/interaction/material/ContainerTask.kt new file mode 100644 index 000000000..c61589a24 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/material/ContainerTask.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2024 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.material + +import com.lambda.event.events.TickEvent +import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.task.Task + +abstract class ContainerTask : Task() { + private var finish = false + private val delay = 5 + private var currentDelay = 0 + + fun delayedFinish() { + finish = true + } + + init { + listen { + if (finish) { + if (currentDelay++ > delay) success() + } + } + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/interaction/material/MaterialContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/MaterialContainer.kt index c825e5a07..ddb94fa8b 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/MaterialContainer.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/MaterialContainer.kt @@ -17,6 +17,7 @@ package com.lambda.interaction.material +import com.lambda.context.SafeContext import com.lambda.interaction.material.StackSelection.Companion.select import com.lambda.interaction.material.container.ShulkerBoxContainer import com.lambda.interaction.material.transfer.TransferResult @@ -51,17 +52,24 @@ abstract class MaterialContainer( this.stacks = stacks } + class Nothing : Task() { + override val name = "Nothing" + override fun SafeContext.onStart() { + success() + } + } + /** * Withdraws items from the container to the player's inventory. */ @Task.Ta5kBuilder - abstract fun withdraw(selection: StackSelection): Task<*> + open fun withdraw(selection: StackSelection): Task<*> = Nothing() /** * Deposits items from the player's inventory into the container. */ @Task.Ta5kBuilder - abstract fun deposit(selection: StackSelection): Task<*> + open fun deposit(selection: StackSelection): Task<*> = Nothing() open fun matchingStacks(selection: StackSelection) = selection.filterStacks(stacks) diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/ChestContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/ChestContainer.kt index 92a8a469b..1dd6995d2 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/container/ChestContainer.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/container/ChestContainer.kt @@ -21,10 +21,9 @@ import com.lambda.interaction.material.MaterialContainer import com.lambda.interaction.material.StackSelection import com.lambda.task.tasks.InventoryTask.Companion.deposit import com.lambda.task.tasks.InventoryTask.Companion.withdraw -import com.lambda.task.tasks.OpenContainer.Companion.openContainer +import com.lambda.task.tasks.OpenContainer import com.lambda.util.Communication.info import net.minecraft.item.ItemStack -import net.minecraft.screen.GenericContainerScreenHandler import net.minecraft.screen.ScreenHandler import net.minecraft.util.math.BlockPos @@ -47,21 +46,17 @@ data class ChestContainer( // } override fun withdraw(selection: StackSelection) = - openContainer(blockPos) -// .withMaxAttempts(3) -// .withTimeout(20) - .onSuccess { open, screen -> - info("Withdrawing $selection from ${screen.type}") - withdraw(screen, selection).start(open) + OpenContainer(blockPos) + .then { + info("Withdrawing $selection from ${it.type}") + withdraw(it, selection) } override fun deposit(selection: StackSelection) = - openContainer(blockPos) -// .withMaxAttempts(3) -// .withTimeout(20) - .onSuccess { open, screen -> - info("Depositing $selection to ${screen.type}") - deposit(screen, selection).start(open) + OpenContainer(blockPos) + .then { + info("Depositing $selection to ${it.type}") + deposit(it, selection) } class ChestBlockedException : Exception("The chest is blocked by another block or a cat") diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/CreativeContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/CreativeContainer.kt index 126822684..380380239 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/container/CreativeContainer.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/container/CreativeContainer.kt @@ -18,10 +18,10 @@ package com.lambda.interaction.material.container import com.lambda.Lambda.mc -import com.lambda.interaction.construction.result.ComparableResult +import com.lambda.context.SafeContext import com.lambda.interaction.material.MaterialContainer import com.lambda.interaction.material.StackSelection -import com.lambda.task.Task.Companion.buildTask +import com.lambda.task.Task import com.lambda.util.item.ItemStackUtils.equal import net.minecraft.item.ItemStack @@ -34,38 +34,54 @@ data object CreativeContainer : MaterialContainer(Rank.CREATIVE) { override fun spaceLeft(selection: StackSelection) = Int.MAX_VALUE - override fun deposit(selection: StackSelection) = buildTask("CreativeDeposit") { - if (!player.isCreative) { - // ToDo: Maybe switch gamemode? - throw NotInCreativeModeException() - } - - interaction.clickCreativeStack( - ItemStack.EMPTY, - 36 + player.inventory.selectedSlot - ) - } - - // Withdraws items from the creative menu to the player's main hand - override fun withdraw(selection: StackSelection) = buildTask("CreativeWithdraw") { - selection.optimalStack?.let { optimalStack -> - if (player.mainHandStack.equal(optimalStack)) return@buildTask + class CreativeDeposit @Ta5kBuilder constructor(val selection: StackSelection) : Task() { + override val name: String get() = "Removing $selection from creative inventory" + override fun SafeContext.onStart() { if (!player.isCreative) { // ToDo: Maybe switch gamemode? throw NotInCreativeModeException() } - interaction.clickCreativeStack( - optimalStack, - 36 + player.inventory.selectedSlot - ) - return@buildTask + player.currentScreenHandler?.slots?.let { slots -> + selection.filterSlots(slots).forEach { + interaction.clickCreativeStack(ItemStack.EMPTY, it.id) + } + } + + success() } + } + + override fun deposit(selection: StackSelection) = CreativeDeposit(selection) - throw NoOptimalStackException() + class CreativeWithdrawal @Ta5kBuilder constructor(val selection: StackSelection) : Task() { + override val name: String get() = "Withdrawing $selection from creative inventory" + + override fun SafeContext.onStart() { + selection.optimalStack?.let { optimalStack -> + if (player.mainHandStack.equal(optimalStack)) return + + if (!player.isCreative) { + // ToDo: Maybe switch gamemode? + throw NotInCreativeModeException() + } + + interaction.clickCreativeStack( + optimalStack, + 36 + player.inventory.selectedSlot + ) + success() + return + } + + throw NoOptimalStackException() + } } + // Withdraws items from the creative menu to the player's main hand + override fun withdraw(selection: StackSelection) = CreativeWithdrawal(selection) + class NotInCreativeModeException : IllegalStateException("Insufficient permission: not in creative mode") class NoOptimalStackException : IllegalStateException("Cannot move item: no optimal stack") } diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/EnderChestContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/EnderChestContainer.kt index 1500596a8..894e1625f 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/container/EnderChestContainer.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/container/EnderChestContainer.kt @@ -17,15 +17,12 @@ package com.lambda.interaction.material.container +import com.lambda.context.SafeContext import com.lambda.interaction.material.MaterialContainer import com.lambda.interaction.material.StackSelection import com.lambda.task.Task -import com.lambda.task.Task.Companion.emptyTask -import com.lambda.task.Task.Companion.failTask -import com.lambda.task.tasks.OpenContainer.Companion.openContainer import com.lambda.util.Communication.info import net.minecraft.item.ItemStack -import net.minecraft.screen.GenericContainerScreenHandler import net.minecraft.util.math.BlockPos object EnderChestContainer : MaterialContainer(Rank.ENDER_CHEST) { @@ -47,13 +44,25 @@ object EnderChestContainer : MaterialContainer(Rank.ENDER_CHEST) { // } // } - override fun withdraw(selection: StackSelection): Task<*> { - info("Not yet implemented") - return emptyTask() + class EnderchestWithdrawal @Ta5kBuilder constructor(selection: StackSelection) : Task() { + override val name = "Withdrawing $selection from ender chest" + + override fun SafeContext.onStart() { + info("Not yet implemented") + success() + } } - override fun deposit(selection: StackSelection): Task<*> { - info("Not yet implemented") - return emptyTask() + override fun withdraw(selection: StackSelection) = EnderchestWithdrawal(selection) + + class EnderchestDeposit @Ta5kBuilder constructor(selection: StackSelection) : Task() { + override val name = "Depositing $selection into ender chest" + + override fun SafeContext.onStart() { + info("Not yet implemented") + success() + } } + + override fun deposit(selection: StackSelection) = EnderchestDeposit(selection) } diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/HotbarContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/HotbarContainer.kt index 55b75ae47..f731a574a 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/container/HotbarContainer.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/container/HotbarContainer.kt @@ -18,10 +18,10 @@ package com.lambda.interaction.material.container import com.lambda.Lambda.mc +import com.lambda.context.SafeContext +import com.lambda.interaction.material.ContainerTask import com.lambda.interaction.material.MaterialContainer import com.lambda.interaction.material.StackSelection -import com.lambda.task.Task -import com.lambda.task.Task.Companion.emptyTask import com.lambda.task.tasks.InventoryTask.Companion.deposit import com.lambda.util.player.SlotUtils.hotbar import net.minecraft.item.ItemStack @@ -32,10 +32,16 @@ object HotbarContainer : MaterialContainer(Rank.HOTBAR) { set(_) {} override val name = "Hotbar" - override fun withdraw(selection: StackSelection) = emptyTask("WithdrawFromHotbar") + class HotbarDeposit @Ta5kBuilder constructor(val selection: StackSelection) : ContainerTask() { + override val name: String get() = "Depositing $selection into hotbar" - override fun deposit(selection: StackSelection): Task<*> { - val handler = mc.player?.currentScreenHandler ?: return emptyTask("NoScreenHandler") - return deposit(handler, selection) + override fun SafeContext.onStart() { + val handler = player.currentScreenHandler + deposit(handler, selection).finally { + delayedFinish() + }.execute(this@HotbarDeposit) + } } + + override fun deposit(selection: StackSelection) = HotbarDeposit(selection) } diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/InventoryContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/InventoryContainer.kt index 2f8ca8c4e..6b0331e55 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/container/InventoryContainer.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/container/InventoryContainer.kt @@ -20,8 +20,7 @@ package com.lambda.interaction.material.container import com.lambda.Lambda.mc import com.lambda.interaction.material.MaterialContainer import com.lambda.interaction.material.StackSelection -import com.lambda.task.Task -import com.lambda.task.Task.Companion.emptyTask +import com.lambda.task.tasks.InventoryTask import com.lambda.util.player.SlotUtils.combined import net.minecraft.item.ItemStack @@ -31,7 +30,9 @@ object InventoryContainer : MaterialContainer(Rank.INVENTORY) { set(_) {} override val name = "Inventory" - override fun withdraw(selection: StackSelection) = emptyTask("WithdrawFromInventory") + private val handler get() = mc.player?.currentScreenHandler - override fun deposit(selection: StackSelection) = emptyTask("DepositToInventory") + override fun withdraw(selection: StackSelection) = InventoryTask.withdraw(handler!!, selection) + + override fun deposit(selection: StackSelection) = InventoryTask.deposit(handler!!, selection) } diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/MainHandContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/MainHandContainer.kt index b2955d691..1b84b220c 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/container/MainHandContainer.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/container/MainHandContainer.kt @@ -18,14 +18,15 @@ package com.lambda.interaction.material.container import com.lambda.Lambda.mc +import com.lambda.context.SafeContext +import com.lambda.interaction.material.ContainerTask import com.lambda.interaction.material.MaterialContainer import com.lambda.interaction.material.StackSelection -import com.lambda.task.Task.Companion.buildTask -import com.lambda.task.Task.Companion.emptyTask import com.lambda.util.player.SlotUtils.combined import com.lambda.util.player.SlotUtils.hotbar import net.minecraft.item.ItemStack import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket +import net.minecraft.util.Hand import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Direction @@ -35,15 +36,21 @@ object MainHandContainer : MaterialContainer(Rank.MAIN_HAND) { set(_) {} override val name = "MainHand" - override fun withdraw(selection: StackSelection) = emptyTask("WithdrawFromMainHand") + class MainHandDeposit @Ta5kBuilder constructor(val selection: StackSelection, val hand: Hand) : ContainerTask() { + override val name: String get() = "Depositing $selection to main hand" - override fun deposit(selection: StackSelection) = buildTask("DepositToMainHand") { - InventoryContainer.matchingStacks(selection).firstOrNull()?.let { stack -> - if (ItemStack.areEqual(stack, player.mainHandStack)) { - return@buildTask + override fun SafeContext.onStart() { + val moveStack = InventoryContainer.matchingStacks(selection).firstOrNull() ?: return + + val otherHand = if (hand == Hand.MAIN_HAND) Hand.OFF_HAND else Hand.MAIN_HAND + val handStack = player.getStackInHand(hand) + val otherStack = player.getStackInHand(otherHand) + if (ItemStack.areEqual(moveStack, handStack)) { + delayedFinish() + return } - if (ItemStack.areEqual(stack, player.offHandStack)) { + if (ItemStack.areEqual(moveStack, otherStack)) { connection.sendPacket( PlayerActionC2SPacket( PlayerActionC2SPacket.Action.SWAP_ITEM_WITH_OFFHAND, @@ -51,15 +58,20 @@ object MainHandContainer : MaterialContainer(Rank.MAIN_HAND) { Direction.DOWN, ), ) - return@buildTask + delayedFinish() + return } - if (stack in player.hotbar) { - player.inventory.selectedSlot = player.hotbar.indexOf(stack) - return@buildTask + if (moveStack in player.hotbar) { + player.inventory.selectedSlot = player.hotbar.indexOf(moveStack) + delayedFinish() + return } - interaction.pickFromInventory(player.combined.indexOf(stack)) + interaction.pickFromInventory(player.combined.indexOf(moveStack)) + delayedFinish() } } + + override fun deposit(selection: StackSelection) = MainHandDeposit(selection, Hand.MAIN_HAND) } diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/OffHandContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/OffHandContainer.kt index e2a86fae7..be0d2a2db 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/container/OffHandContainer.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/container/OffHandContainer.kt @@ -20,14 +20,8 @@ package com.lambda.interaction.material.container import com.lambda.Lambda.mc import com.lambda.interaction.material.MaterialContainer import com.lambda.interaction.material.StackSelection -import com.lambda.task.Task.Companion.buildTask -import com.lambda.task.Task.Companion.emptyTask -import com.lambda.util.player.SlotUtils.combined -import com.lambda.util.player.SlotUtils.hotbar import net.minecraft.item.ItemStack -import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket -import net.minecraft.util.math.BlockPos -import net.minecraft.util.math.Direction +import net.minecraft.util.Hand object OffHandContainer : MaterialContainer(Rank.OFF_HAND) { override var stacks: List @@ -35,29 +29,5 @@ object OffHandContainer : MaterialContainer(Rank.OFF_HAND) { set(_) {} override val name = "OffHand" - override fun withdraw(selection: StackSelection) = emptyTask("WithdrawFromOffHand") - - override fun deposit(selection: StackSelection) = buildTask("DepositToOffHand") { - InventoryContainer.matchingStacks(selection).firstOrNull()?.let { stack -> - if (ItemStack.areEqual(stack, player.offHandStack)) { - return@buildTask - } - - if (stack in player.hotbar) { - player.inventory.selectedSlot = player.hotbar.indexOf(stack) - } else { - interaction.pickFromInventory(player.combined.indexOf(stack)) - } - - if (ItemStack.areEqual(stack, player.mainHandStack)) { - connection.sendPacket( - PlayerActionC2SPacket( - PlayerActionC2SPacket.Action.SWAP_ITEM_WITH_OFFHAND, - BlockPos.ORIGIN, - Direction.DOWN, - ), - ) - } - } - } + override fun deposit(selection: StackSelection) = MainHandContainer.MainHandDeposit(selection, Hand.OFF_HAND) } diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/ShulkerBoxContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/ShulkerBoxContainer.kt index 63565b0b6..0a3ca08d6 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/container/ShulkerBoxContainer.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/container/ShulkerBoxContainer.kt @@ -24,8 +24,8 @@ import com.lambda.task.Task import com.lambda.task.tasks.BuildTask.Companion.breakAndCollectBlock import com.lambda.task.tasks.InventoryTask.Companion.deposit import com.lambda.task.tasks.InventoryTask.Companion.withdraw -import com.lambda.task.tasks.OpenContainer.Companion.openContainer -import com.lambda.task.tasks.PlaceContainer.Companion.placeContainer +import com.lambda.task.tasks.OpenContainer +import com.lambda.task.tasks.PlaceContainer import net.minecraft.item.ItemStack data class ShulkerBoxContainer( @@ -37,41 +37,45 @@ data class ShulkerBoxContainer( private val slotInContainer: Int get() = containedIn.stacks.indexOf(shulkerStack) - class Withdraw( + class ShulkerWithdraw( private val selection: StackSelection, private val shulkerStack: ItemStack ) : Task() { + override val name = "Withdraw $selection from ${shulkerStack.name.string}" + override fun SafeContext.onStart() { - placeContainer(shulkerStack).thenRun(this@Withdraw) { _, placePos -> - openContainer(placePos).thenRun(this@Withdraw) { _, screen -> - withdraw(screen, selection).thenRun(this@Withdraw) { _, _ -> - breakAndCollectBlock(placePos).onSuccess { _, _ -> - success(Unit) + PlaceContainer(shulkerStack).then { placePos -> + OpenContainer(placePos).then { screen -> + withdraw(screen, selection).then { + breakAndCollectBlock(placePos).finally { + success() } } } - }.start(this@Withdraw) + }.execute(this@ShulkerWithdraw) } } - override fun withdraw(selection: StackSelection) = Withdraw(selection, shulkerStack) + override fun withdraw(selection: StackSelection) = ShulkerWithdraw(selection, shulkerStack) - class Deposit( + class ShulkerDeposit( private val selection: StackSelection, private val shulkerStack: ItemStack ) : Task() { + override val name = "Deposit $selection into ${shulkerStack.name.string}" + override fun SafeContext.onStart() { - placeContainer(shulkerStack).thenRun(this@Deposit) { _, placePos -> - openContainer(placePos).thenRun(this@Deposit) { _, screen -> - deposit(screen, selection).thenRun(this@Deposit) { _, _ -> - breakAndCollectBlock(placePos).onSuccess { _, _ -> - success(Unit) + PlaceContainer(shulkerStack).then { placePos -> + OpenContainer(placePos).then { screen -> + deposit(screen, selection).then { + breakAndCollectBlock(placePos).finally { + success() } } } - }.start(this@Deposit) + }.execute(this@ShulkerDeposit) } } - override fun deposit(selection: StackSelection) = Deposit(selection, shulkerStack) + override fun deposit(selection: StackSelection) = ShulkerDeposit(selection, shulkerStack) } diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/StashContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/StashContainer.kt index fd078fae2..91e25e761 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/container/StashContainer.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/container/StashContainer.kt @@ -33,14 +33,6 @@ data class StashContainer( set(_) {} override val name = "Stash at ${pos.center.blockPos.toShortString()}" - override fun withdraw(selection: StackSelection): Task<*> { - TODO("Not yet implemented") - } - - override fun deposit(selection: StackSelection): Task<*> { - TODO("Not yet implemented") - } - override fun available(selection: StackSelection): Int = chests.sumOf { it.available(selection) diff --git a/common/src/main/kotlin/com/lambda/interaction/material/transfer/TransferResult.kt b/common/src/main/kotlin/com/lambda/interaction/material/transfer/TransferResult.kt index 7e0f1cea4..c395c2e62 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/transfer/TransferResult.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/transfer/TransferResult.kt @@ -21,6 +21,7 @@ import com.lambda.context.SafeContext import com.lambda.interaction.material.MaterialContainer import com.lambda.interaction.material.StackSelection import com.lambda.task.Task +import com.lambda.util.Communication.info abstract class TransferResult : Task() { data class Transfer( @@ -28,18 +29,22 @@ abstract class TransferResult : Task() { val from: MaterialContainer, val to: MaterialContainer ) : TransferResult() { + override val name = "Transfer of [$selection] from [${from.name}] to [${to.name}]" + override fun SafeContext.onStart() { - from.withdraw(selection).thenRun(this@Transfer) { _, _ -> - to.deposit(selection).onSuccess { _, _ -> - success(Unit) + from.withdraw(selection).then { + to.deposit(selection).finally { + success() } - }.start(this@Transfer) + }.execute(this@Transfer) } override fun toString() = "Transfer of [$selection] from [${from.name}] to [${to.name}]" } data object NoSpace : TransferResult() { + override val name = "No space left in the target container" + // ToDo: Needs inventory space resolver. compressing or disposing override fun SafeContext.onStart() { failure("No space left in the target container") @@ -47,6 +52,8 @@ abstract class TransferResult : Task() { } data class MissingItems(val missing: Int) : TransferResult() { + override val name = "Missing $missing items" + // ToDo: Find other satisfying permutations override fun SafeContext.onStart() { failure("Missing $missing items") diff --git a/common/src/main/kotlin/com/lambda/interaction/visibilty/VisibilityChecker.kt b/common/src/main/kotlin/com/lambda/interaction/visibilty/VisibilityChecker.kt index 5c0ceed04..f2035df83 100644 --- a/common/src/main/kotlin/com/lambda/interaction/visibilty/VisibilityChecker.kt +++ b/common/src/main/kotlin/com/lambda/interaction/visibilty/VisibilityChecker.kt @@ -25,7 +25,7 @@ import com.lambda.interaction.construction.verify.ScanMode import com.lambda.interaction.construction.verify.SurfaceScan import com.lambda.interaction.rotation.Rotation.Companion.rotationTo import com.lambda.interaction.rotation.RotationContext -import com.lambda.module.modules.client.TaskFlow +import com.lambda.module.modules.client.TaskFlowModule import com.lambda.util.BlockUtils.blockState import com.lambda.util.extension.component6 import com.lambda.util.math.VecUtils.distSq @@ -74,9 +74,9 @@ object VisibilityChecker { */ fun SafeContext.lookAtBlock( blockPos: BlockPos, - rotationConfig: IRotationConfig = TaskFlow.rotation, - interactionConfig: InteractionConfig = TaskFlow.interact, - sides: Set = emptySet() + rotationConfig: IRotationConfig = TaskFlowModule.rotation, + interactionConfig: InteractionConfig = TaskFlowModule.interact, + sides: Set = Direction.entries.toSet() ): RotationContext? { val state = blockPos.blockState(world) val voxelShape = state.getOutlineShape(world, blockPos) @@ -102,7 +102,7 @@ object VisibilityChecker { boxes: List, rotationConfig: IRotationConfig, interact: InteractionConfig, - sides: Set = emptySet(), + sides: Set = Direction.entries.toSet(), reach: Double = interact.reach, eye: Vec3d = player.getCameraPosVec(1f), verify: HitResult.() -> Boolean, diff --git a/common/src/main/kotlin/com/lambda/module/HudModule.kt b/common/src/main/kotlin/com/lambda/module/HudModule.kt index c9c68db0d..d51202658 100644 --- a/common/src/main/kotlin/com/lambda/module/HudModule.kt +++ b/common/src/main/kotlin/com/lambda/module/HudModule.kt @@ -87,7 +87,7 @@ abstract class HudModule( rectHandler.screenSize = event.screenSize renderCallables.forEach { function -> - function.invoke(renderer) + function(renderer) } renderer.render() diff --git a/common/src/main/kotlin/com/lambda/module/hud/TaskFlow.kt b/common/src/main/kotlin/com/lambda/module/hud/TaskFlowHUD.kt similarity index 73% rename from common/src/main/kotlin/com/lambda/module/hud/TaskFlow.kt rename to common/src/main/kotlin/com/lambda/module/hud/TaskFlowHUD.kt index 62416bf6e..1e741fea7 100644 --- a/common/src/main/kotlin/com/lambda/module/hud/TaskFlow.kt +++ b/common/src/main/kotlin/com/lambda/module/hud/TaskFlowHUD.kt @@ -19,18 +19,21 @@ package com.lambda.module.hud import com.lambda.module.HudModule import com.lambda.module.tag.ModuleTag +import com.lambda.task.TaskFlow import com.lambda.util.math.Vec2d -object TaskFlow : HudModule( +object TaskFlowHUD : HudModule( name = "TaskFlowHud", defaultTags = setOf(ModuleTag.CLIENT), ) { - override val width = 50.0 - override val height = 50.0 + override val width = 200.0 + override val height = 200.0 init { onRender { - font.build("TaskFlow", Vec2d.ZERO) + TaskFlow.toString().lines().forEachIndexed { index, line -> + font.build(line, Vec2d(position.x, position.y + index * (font.getHeight(font.scaleMultiplier) + 2.0))) + } } } } diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/TaskFlow.kt b/common/src/main/kotlin/com/lambda/module/modules/client/TaskFlowModule.kt similarity index 98% rename from common/src/main/kotlin/com/lambda/module/modules/client/TaskFlow.kt rename to common/src/main/kotlin/com/lambda/module/modules/client/TaskFlowModule.kt index a8278ba2e..e579bec6f 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/TaskFlow.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/TaskFlowModule.kt @@ -26,7 +26,7 @@ import com.lambda.util.BlockUtils.allSigns import com.lambda.util.item.ItemUtils import net.minecraft.state.property.Properties -object TaskFlow : Module( +object TaskFlowModule : Module( name = "TaskFlow", description = "Settings for task automation", defaultTags = setOf(ModuleTag.CLIENT, ModuleTag.AUTOMATION) diff --git a/common/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt b/common/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt index c541a6fdb..719724949 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt @@ -22,6 +22,7 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.material.StackSelection.Companion.select import com.lambda.module.Module import com.lambda.module.tag.ModuleTag +import com.lambda.task.TaskFlow.run import com.lambda.task.tasks.AcquireMaterial.Companion.acquire import net.minecraft.item.Items @@ -38,7 +39,7 @@ object ContainerTest : Module( onEnable { acquire { Items.OBSIDIAN.select() - }.start(null) + }.run() } } } diff --git a/common/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt b/common/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt index bcd0bae33..809331367 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt @@ -22,6 +22,7 @@ import com.lambda.interaction.construction.verify.TargetState import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.task.Task +import com.lambda.task.TaskFlow.run import com.lambda.task.tasks.BuildTask.Companion.build import com.lambda.util.BaritoneUtils import com.lambda.util.Communication.info @@ -100,16 +101,14 @@ object HighwayTools : Module( structure = structure.plus(slice.map { it.key.add(currentPos) to it.value }) } - runningTask = structure.toBlueprint().build().onSuccess { _, _ -> + runningTask = structure.toBlueprint().build().finally { if (distanceMoved < distance || distance < 0) { buildSlice() } else { this@HighwayTools.info("Highway built") disable() } - }.onFailure { _, _ -> - disable() - }.start(null) + }.run() } private fun generateSlice(): Structure { diff --git a/common/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt b/common/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt index 609305212..99316b871 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt @@ -24,9 +24,10 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.task.Task +import com.lambda.task.TaskFlow.run import com.lambda.task.tasks.BuildTask.Companion.breakAndCollectBlock -import com.lambda.task.tasks.OpenContainer.Companion.openContainer -import com.lambda.task.tasks.PlaceContainer.Companion.placeContainer +import com.lambda.task.tasks.OpenContainer +import com.lambda.task.tasks.PlaceContainer import com.lambda.util.item.ItemUtils.shulkerBoxes import com.lambda.util.player.SlotUtils.clickSlot import com.lambda.util.player.SlotUtils.hotbar @@ -59,19 +60,19 @@ object InventoryTweaks : Module( player.closeScreen() - lastPlace = placeContainer(stack).thenRun(null) { _, placePos -> + lastPlace = PlaceContainer(stack).then { placePos -> placedPos = placePos - openContainer(placePos).onSuccess { _, screenHandler -> + OpenContainer(placePos).finally { screenHandler -> lastOpenScreen = screenHandler } - }.start(null) + }.run() } listen { event -> if (event.screenHandler != lastOpenScreen) return@listen lastOpenScreen = null placedPos?.let { - lastBreak = breakAndCollectBlock(it).start(null) + lastBreak = breakAndCollectBlock(it).run() placedPos = null } } diff --git a/common/src/main/kotlin/com/lambda/module/modules/player/Nuker.kt b/common/src/main/kotlin/com/lambda/module/modules/player/Nuker.kt index b1d0c1039..2ac4284d6 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/player/Nuker.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/player/Nuker.kt @@ -22,7 +22,8 @@ import com.lambda.interaction.construction.blueprint.DynamicBlueprint.Companion. import com.lambda.interaction.construction.verify.TargetState import com.lambda.module.Module import com.lambda.module.tag.ModuleTag -import com.lambda.task.Task.Companion.emptyTask +import com.lambda.task.Task +import com.lambda.task.TaskFlow.run import com.lambda.task.tasks.BuildTask.Companion.build import com.lambda.util.BlockUtils.blockPos import com.lambda.util.BlockUtils.blockState @@ -39,7 +40,7 @@ object Nuker : Module( private val onlyBreakInstant by setting("Only Break Instant", true) private val fillFloor by setting("Fill Floor", false) - private var task = emptyTask() + private var task: Task<*>? = null init { onEnable { @@ -68,11 +69,11 @@ object Nuker : Module( finishOnDone = false, cancelOnUnsolvable = false ) - task.start(null) + task?.run() } onDisable { - task.cancel() + task?.cancel() } // listener { diff --git a/common/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt b/common/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt index 6e2049e11..9ec45ba51 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt @@ -29,7 +29,7 @@ import com.lambda.interaction.RotationManager import com.lambda.interaction.rotation.RotationContext import com.lambda.interaction.visibilty.VisibilityChecker.findRotation import com.lambda.module.Module -import com.lambda.module.modules.client.TaskFlow +import com.lambda.module.modules.client.TaskFlowModule import com.lambda.module.tag.ModuleTag import com.lambda.util.BlockUtils.blockState import com.lambda.util.math.lerp @@ -514,7 +514,7 @@ object PacketMine : Module( } } - listen { + listen { currentMiningBlock.forEach { ctx -> ctx?.apply { if (it.pos != pos || !isStateBroken(pos.blockState(world), it.state)) return@forEach @@ -539,7 +539,7 @@ object PacketMine : Module( lastNonEmptyState?.let { state -> val boxList = state.getOutlineShape(world, pos).boundingBoxes.map { it.offset(pos) } val rotationContext = - findRotation(boxList, TaskFlow.rotation, TaskFlow.interact, emptySet()) { true } + findRotation(boxList, TaskFlowModule.rotation, TaskFlowModule.interact, emptySet()) { true } rotationContext?.let { context -> it.context = context expectedRotation = context diff --git a/common/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt b/common/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt index 4db6b41b1..3f144f979 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt @@ -40,7 +40,7 @@ import com.lambda.interaction.visibilty.VisibilityChecker.getVisibleSurfaces import com.lambda.interaction.visibilty.VisibilityChecker.scanSurfaces import com.lambda.module.Module import com.lambda.module.modules.client.GuiSettings -import com.lambda.module.modules.client.TaskFlow +import com.lambda.module.modules.client.TaskFlowModule import com.lambda.module.tag.ModuleTag import com.lambda.util.math.MathUtils.floorToInt import com.lambda.util.math.VecUtils.dist @@ -215,7 +215,7 @@ object Scaffold : Module( // Dividing the surface by segments and iterating through them val pointScan = mutableSetOf().apply { val box = Box(info.clickPos) - val sides = if (TaskFlow.interact.visibilityCheck) { + val sides = if (TaskFlowModule.interact.visibilityCheck) { box.getVisibleSurfaces(eye) } else Direction.entries.toSet() scanSurfaces( diff --git a/common/src/main/kotlin/com/lambda/module/modules/player/WorldEater.kt b/common/src/main/kotlin/com/lambda/module/modules/player/WorldEater.kt index 9f761b3a6..17aaf0a28 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/player/WorldEater.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/player/WorldEater.kt @@ -26,6 +26,7 @@ import com.lambda.interaction.construction.verify.TargetState import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.task.Task +import com.lambda.task.TaskFlow.run import com.lambda.task.tasks.BuildTask.Companion.build import com.lambda.util.BaritoneUtils import net.minecraft.util.math.BlockBox @@ -76,10 +77,10 @@ object WorldEater : Module( runningTask = build { box.toStructure(TargetState.Air) .toBlueprint() - }.onSuccess { _, _ -> + }.finally { work.removeFirstOrNull() buildLayer() - }.start(null) + }.run() } ?: disable() } } \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/task/Task.kt b/common/src/main/kotlin/com/lambda/task/Task.kt index 83c6ed224..820d0a5bc 100644 --- a/common/src/main/kotlin/com/lambda/task/Task.kt +++ b/common/src/main/kotlin/com/lambda/task/Task.kt @@ -19,112 +19,49 @@ package com.lambda.task import com.lambda.Lambda.LOG import com.lambda.context.SafeContext -import com.lambda.event.Event import com.lambda.event.EventFlow import com.lambda.event.Subscriber import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.module.modules.client.TaskFlow -import com.lambda.threading.runConcurrent -import com.lambda.threading.runGameScheduled import com.lambda.threading.runSafe import com.lambda.util.Communication.logError -import com.lambda.util.Communication.warn import com.lambda.util.Nameable -import com.lambda.util.text.buildText -import com.lambda.util.text.color -import com.lambda.util.text.literal -import com.lambda.util.text.text -import kotlinx.coroutines.delay -import net.minecraft.text.Text -import org.apache.commons.lang3.time.DurationFormatUtils -import java.awt.Color - -/** - * A [Task] represents a time-critical activity. - * It automates in-game activities through event-based programming, leveraging game events to control task flow. - * - * [Result] is the type of the result that the task will return when it completes successfully. - * In case the task should not return any result, [Unit] can be used as the type. - * - * A [Task] can have event listeners that are active while the task is running. - * The task will attempt to execute its subtasks sequentially, and can either keep listening - * or stop receiving events while the subtasks are running. - * - * It supports a builder pattern, allowing you to chain configuration methods like [withDelay], - * [withTimeout], [withMaxAttempts], [withRepeats], [onSuccess], [onRetry], [onTimeout], [onFailure], and [onRepeat] - * to construct a [Task] instance. - * This makes it easy to build complex flows of nested tasks. - * - * @property delay The delay before the task starts, in milliseconds. - * @property timeout The maximum time that the task is allowed to run, in ticks. - * @property tries The maximum number of attempts to execute the task before it is considered failed. - * @property executions The number of times the task should be repeated. - * @property onSuccess The action to be performed when the task completes successfully. - * @property onRetry The action to be performed when the task is retried after a failure or timeout. - * @property onTimeout The action to be performed when the task times out. - * @property onRepeat The action to be performed each time the task is repeated. - * @property onException The action to be performed when the task encounters an exception. - */ -abstract class Task : Nameable { - open var delay: Int = 0 - open var timeout: Int = Int.MAX_VALUE - open var tries: Int = 0 - open var repeats: Int = 0 - open var cooldown: Int = TaskFlow.taskCooldown - open var onStart: SafeContext.(Task) -> Unit = {} - open var onSuccess: SafeContext.(Task, Result) -> Unit = { _, _ -> } - open var onRetry: SafeContext.(Task) -> Unit = {} - open var onTimeout: SafeContext.(Task) -> Unit = {} - open var onRepeat: SafeContext.(Task, Result, Int) -> Unit = { _, _, _ -> } - open var onException: SafeContext.(Task, Throwable) -> Unit = { _, _ -> } - - open var pausable = true +import com.lambda.util.StringUtils.capitalize - private var parent: Task<*>? = null - private val root: Task<*> get() = parent?.root ?: this - private val depth: Int get() = parent?.depth?.plus(1) ?: 0 +typealias TaskGenerator = SafeContext.(R) -> Task<*> +typealias TaskGeneratorOrNull = SafeContext.(R) -> Task<*>? +typealias TaskGeneratorUnit = SafeContext.(R) -> Unit - private var executions = 0 - private var attempted = 0 +abstract class Task : Nameable { + private var parent: Task<*>? = null private val subTasks = mutableListOf>() - private var state = State.IDLE + private var state = State.RUNNING var age = 0 + private val depth: Int get() = parent?.depth?.plus(1) ?: 0 + val isCompleted get() = state == State.COMPLETED + val size: Int get() = subTasks.sumOf { it.size } + 1 - private val isWaiting get() = state == State.WAITING - private val isRunning get() = state == State.RUNNING - val isFailed get() = state == State.FAILED - val isCompleted get() = state == State.COMPLETED || state == State.COOLDOWN - private val isRoot get() = parent == null - override var name = this::class.simpleName ?: "Task" - val identifier get() = "$name@${hashCode()}" + open var alwaysListening = false - // ToDo: Better color management - private val primaryColor = Color(0, 255, 0, 100) + private var nextTask: TaskGenerator? = null + private var nextTaskOrNull: TaskGeneratorOrNull? = null + private var onFinish: TaskGeneratorUnit? = null val syncListeners = Subscriber() private val concurrentListeners = Subscriber() enum class State { - IDLE, RUNNING, - WAITING, + PAUSED, CANCELLED, FAILED, - COOLDOWN, - COMPLETED, + COMPLETED; + + val display get() = name.lowercase().capitalize() } init { - listen { - parent?.let { - it.age++ - } - if (++age >= timeout) { - onTimeout(this@Task) - failure(TimeoutException(age, attempted)) - } - } + listen { age++ } } /** @@ -134,98 +71,118 @@ abstract class Task : Nameable { @DslMarker annotation class Ta5kBuilder + /** + * Invoked when the task starts execution. + * + * This method serves as a lifecycle hook and can be overridden to define + * custom behavior or initialization steps necessary before the task begins. + * It provides a `SafeContext` to access relevant context-sensitive properties + * or actions safely. + * + * By default, this method does not contain any logic. Subclasses may override + * it to implement specific functionality, such as logging, resource allocation, + * or preparing preconditions for task execution. + */ @Ta5kBuilder open fun SafeContext.onStart() {} + /** + * This function is called when the task is canceled. + * It can be overridden for tasks that need to perform cleanup operations, + * such as cancelling a block breaking progress, releasing resources, + * or stopping any ongoing operations that were started by the task. + */ + @Ta5kBuilder + open fun SafeContext.onCancel() {} + + /** + * Executes the current task as a subtask of the specified owner task. + * + * This method adds the current task to the owner's subtasks, sets the parent relationship, + * logs the execution details, and invokes the necessary lifecycle hooks. Additionally, + * it manages the state of the parent task and starts any required listeners for execution. + * + * @param owner The parent task that will execute this task as a child. Must not be the same as this task. + * @param pauseParent Defines whether the parent task should be paused during the execution of this task. Defaults to `true`. + * @return The current task instance as a `Task` to support chaining or further configuration. + * @throws IllegalArgumentException if the owner task is the same as the task being executed. + */ @Ta5kBuilder - fun start(parent: Task<*>?, pauseParent: Boolean = true): Task { - executions++ - val owner = parent ?: RootTask + fun execute(owner: Task<*>, pauseParent: Boolean = true): Task { + require(owner != this) { "Cannot execute a task as a child of itself" } owner.subTasks.add(this) - - LOG.info("${owner.identifier} started $identifier") - this.parent = owner - if (pauseParent && owner.isRunning && !owner.isRoot) { - LOG.info("$identifier deactivating parent ${owner.identifier}") + parent = owner + LOG.info("${owner.name} started $name") + if (!alwaysListening || pauseParent) { + LOG.info("$name deactivating parent ${owner.name}") owner.deactivate() } + runSafe { runCatching { onStart() }.onFailure { failure(it) } } + startListening() + return this + } - activate() + @Ta5kBuilder + fun success(result: Result) { + stopListening() + state = State.COMPLETED runSafe { - onStart(this@Task) - onStart() + executeNextTask(result) } - return this + } + + @Ta5kBuilder + fun Task.success() { + success(Unit) } @Ta5kBuilder fun activate() { - if (isRunning) return + if (state != State.PAUSED) return state = State.RUNNING startListening() } @Ta5kBuilder fun deactivate() { - if (isWaiting) return - state = State.WAITING - stopListening() - } - - @Ta5kBuilder - fun SafeContext.success(result: Result) { - if (executions < repeats) { - executions++ - LOG.info("Repeating $identifier $executions/$repeats...") - onRepeat(this@Task, result, executions) - reset() - return - } - + if (this is TaskFlow) return + if (state != State.RUNNING) return + state = State.PAUSED stopListening() - if (cooldown > 0) { - state = State.COOLDOWN - runConcurrent { - delay(cooldown.toLong()) - runGameScheduled { - if (state == State.COOLDOWN) finish(result) - } - } - } else finish(result) } - private fun SafeContext.finish(result: Result) { - state = State.COMPLETED - try { - onSuccess(this@Task, result) - } catch (e: ClassCastException) { - result?.let { - LOG.error("Failed to cast result of $identifier to ${it::class.simpleName}") - } - failure(e) + private fun SafeContext.executeNextTask(result: Result) { + nextTask?.let { taskGen -> + val task = taskGen(this, result) + nextTask = null + parent?.let { owner -> task.execute(owner) } + } ?: nextTaskOrNull?.let { taskGen -> + val task = taskGen(this, result) + nextTaskOrNull = null + parent?.let { owner -> task?.execute(owner) } + } ?: run { + onFinish?.invoke(this, result) ?: println("No more tasks to run") + onFinish = null } - - notifyParent() - LOG.info("$identifier completed successfully after $attempted retries and $executions executions.") } @Ta5kBuilder fun cancel() { - if (state == State.COMPLETED) return - if (state == State.CANCELLED) return - cancelSubTasks() - if (this !is RootTask) state = State.CANCELLED + if (this is TaskFlow) return + if (state == State.COMPLETED || state == State.CANCELLED) return + state = State.CANCELLED stopListening() - runSafe { onCancel() } - LOG.info("$identifier was cancelled") } @Ta5kBuilder fun cancelSubTasks() { - subTasks.forEach { - it.cancel() - } + subTasks.forEach { it.cancel() } + } + + fun clear() { + subTasks.forEach { it.clear() } + subTasks.clear() } @Ta5kBuilder @@ -238,26 +195,15 @@ abstract class Task : Nameable { e: Throwable, stacktrace: MutableList> = mutableListOf() ) { - if (attempted < tries) { - attempted++ - warn("Failed task with error: ${e.message}, retrying ($attempted/$tries) ...") - runSafe { - onRetry(this@Task) - } - reset() - return - } - state = State.FAILED stopListening() - runSafe { onException(this@Task, e) } stacktrace.add(this) parent?.failure(e, stacktrace) ?: run { val message = buildString { stacktrace.firstOrNull()?.let { first -> - append("${first.identifier} failed: ${e.message}\n") + append("${first.name} failed: ${e.message}\n") stacktrace.drop(1).forEach { - append(" -> ${it.identifier}\n") + append(" -> ${it.name}\n") } } } @@ -266,36 +212,6 @@ abstract class Task : Nameable { } } - /** - * This function is called when the task is canceled. - * It should be overridden for tasks that need to perform cleanup operations, - * such as cancelling a block breaking progress, releasing resources, - * or stopping any ongoing operations that were started by the task. - */ - @Ta5kBuilder - open fun SafeContext.onCancel() {} - - private fun notifyParent() { - parent?.let { par -> - when { - par.isCompleted -> { - LOG.info("$identifier completed parent ${par.identifier}") - par.notifyParent() - } - - !par.isRunning -> { - LOG.info("$identifier reactivated parent ${par.identifier}") - par.activate() - } - } - } - } - - @Ta5kBuilder - private fun reset() { - age = 0 - } - private fun startListening() { EventFlow.syncListeners.subscribe(syncListeners) EventFlow.concurrentListeners.subscribe(concurrentListeners) @@ -307,261 +223,109 @@ abstract class Task : Nameable { } /** - * Sets the delay before the task starts. - * - * @param delay The delay in ticks. - * @return This task instance with the updated delay. - */ - @Ta5kBuilder - fun withDelay(delay: Int): Task { - this.delay = delay - return this - } - - /** - * Sets the timeout for a single attempt of the task - * - * @param timeout The timeout in ticks. - * @return This task instance with the updated timeout. - */ - @Ta5kBuilder - fun withTimeout(timeout: Int): Task { - this.timeout = timeout - return this - } - - /** - * Sets the cooldown period for the task. - * - * The cooldown period is the time that the task will wait before it the next task can be executed. + * Specifies the next task to execute after the current task completes successfully. * - * @param cooldown The cooldown period in milliseconds. - * @return This task instance with the updated cooldown period. - */ - @Ta5kBuilder - fun withCooldown(cooldown: Int): Task { - this.cooldown = cooldown - return this - } - - /** - * Sets the maximum number of attempts to execute the task before it is considered failed. + * This method links the current task to the specified `task`, creating a sequential + * execution flow where the `task` will be executed immediately after the current task. * - * @param maxAttempts The maximum number of attempts. - * @return This task instance with the updated maximum attempts. + * @param task The task that should be executed following the successful completion + * of the current task. + * @return The current task instance (`Task`) to allow method chaining. */ @Ta5kBuilder - fun withMaxAttempts(maxAttempts: Int): Task { - this.tries = maxAttempts + infix fun then(task: Task): Task { + require(task != this) { "Cannot link a task to itself" } + nextTask = { task } return this } /** - * Sets the number of times the task should be repeated. + * Chains multiple tasks to be executed sequentially after the current task. * - * @param repeats The number of repeats. - * @return This task instance with the updated number of repeats. - */ - @Ta5kBuilder - fun withRepeats(repeats: Int): Task { - this.repeats = repeats - return this - } - - /** - * Sets the action to be performed when the task starts. + * This method establishes an ordered execution flow between tasks, where each task + * in the provided list will trigger the execution of the next task upon completion. + * It effectively links the current task to the first task in the given array + * and ensures that the sequence is executed in order. * - * @param action The action to be performed. - * @return The task instance with the updated start action. + * @param task A vararg list of tasks to be linked sequentially after the current task. + * These tasks are executed in the order they are provided. + * @return The current task instance (`Task`) to allow method chaining. */ @Ta5kBuilder - fun onStart(action: SafeContext.(Task) -> Unit): Task { - this.onStart = action + fun then(vararg task: Task): Task { + (listOf(this) + task).zipWithNext { current, next -> + current then next + } return this } /** - * Sets the action to be performed when the task completes successfully. + * Adds a subsequent task to the current task's execution sequence. * - * @param action The action to be performed. - * @return The task instance with the updated success action. - */ - @Ta5kBuilder - fun onSuccess(action: SafeContext.(Task, Result) -> Unit): Task { - this.onSuccess = action - return this - } - - /** - * This method allows you to chain another task that will be started upon the successful completion of the current task. - * The action provided as a parameter will be used to create the next task. - * The context of the action, the current task, and the result of the current task are passed as parameters to the action. + * This method specifies the next task to be executed after the current task + * completes successfully. The task is generated dynamically using the provided + * `TaskGenerator`. This allows chaining tasks together in a flexible manner. * - * @param action A lambda function that takes the current task and its result as parameters and returns the next task to be started. - * @return The current task instance with the updated success action. + * @param taskGenerator A function that generates the next task based on the result + * of the current task. It takes a `SafeContext` and the result + * of type `R` as input and returns a new `Task`. + * @return The current task instance (`Task`) to allow method chaining. */ @Ta5kBuilder - fun thenRun(owner: Task<*>?, action: SafeContext.(Task, Result) -> Task<*>): Task { - this.onSuccess = { task, result -> - action(this, task, result).start(owner) - } + fun then(taskGenerator: TaskGenerator): Task { + require(nextTask == null) { "Cannot link multiple tasks to a single task" } + nextTask = taskGenerator return this } /** - * Sets the action to be performed when the task is retried after a failure or timeout. + * Adds a subsequent task to the current task's execution sequence conditionally. * - * @param action The action to be performed. - * @return The task instance with the updated retry action. - */ - @Ta5kBuilder - fun onRetry(action: SafeContext.(Task) -> Unit): Task { - this.onRetry = action - return this - } - - /** - * Sets the action to be performed when the task times out. + * This method specifies the next task to be executed after the current task + * completes successfully. The next task is generated dynamically using the provided + * [TaskGeneratorOrNull]. This allows chaining tasks together flexibly, + * where the next task is conditionally determined or null. * - * @param action The action to be performed. - * @return The task instance with the updated timeout action. + * @param taskGenerator A function that generates the next task based on the + * result of the current task. It takes a `SafeContext` + * and the result of type `R` as input and returns a new + * `Task` or null if no next task is required. + * @return The current task instance (`Task`) to allow method chaining. */ @Ta5kBuilder - fun onTimeout(action: SafeContext.(Task) -> Unit): Task { - this.onTimeout = action + fun thenOrNull(taskGenerator: TaskGeneratorOrNull): Task { + require(nextTask == null) { "Cannot link multiple tasks to a single task" } + nextTaskOrNull = taskGenerator return this } /** - * Sets the action to be performed when the task encounters an exception. + * Registers a finalization action to be executed after the current task completes. * - * @param action The action to be performed. - * @return The task instance with the updated exception action. - */ - @Ta5kBuilder - fun onFailure(action: SafeContext.(Task, Throwable) -> Unit): Task { - this.onException = action - return this - } - - /** - * Sets the action to be performed each time the task is repeated. + * This method allows specifying a finalization function that runs upon completion + * of the task, regardless of its outcome (success or failure). It is typically used + * for cleanup operations or logging after a task finishes execution. * - * @param action The action to be performed. - * @return The task instance with the updated repeat action. + * @param onFinish The finalization action to be executed. This function receives + * the task's result of type `R` within a `SafeContext`. + * @return The current task instance (`Task`) to allow method chaining. */ @Ta5kBuilder - fun onRepeat(action: SafeContext.(Task, Result, Int) -> Unit): Task { - this.onRepeat = action - return this - } - - @Ta5kBuilder - inline fun withListener( - crossinline action: SafeContext.(Task) -> Unit - ): Task { - listen { - action(this@Task) - } - return this - } - - @Ta5kBuilder - fun withName(name: String): Task { - this.name = name + fun finally(onFinish: TaskGeneratorUnit): Task { + require(this.onFinish == null) { "Cannot link multiple finally blocks to a single task" } + this.onFinish = onFinish return this } - class TimeoutException(age: Int, attempts: Int) : Exception("Task timed out after $age ticks and $attempts attempts") - - companion object { - val MAX_DEPTH = 20 - const val MAX_DEBUG_ENTRIES = 15 - - @Ta5kBuilder - fun emptyTask( - name: String = "EmptyTask", - ): Task = object : Task() { - init { this.name = name } - override fun SafeContext.onStart() { success(Unit) } - } - - @Ta5kBuilder - fun failTask( - message: String - ): Task = object : Task() { - init { this.name = "FailTask" } - override fun SafeContext.onStart() { - failure(message) - } - } - - @Ta5kBuilder - fun failTask( - e: Throwable - ): Task = object : Task() { - init { this.name = "FailTask" } - override fun SafeContext.onStart() { - failure(e) - } - } - - @Ta5kBuilder - fun buildTask( - name: String = "Task", - block: SafeContext.() -> Unit - ) = object : Task() { - init { this.name = name } - override fun SafeContext.onStart() { - try { - success(block()) - } catch (e: Throwable) { - failure(e) - } - } - } + override fun toString() = + buildString { appendTaskTree(this@Task) } - @Ta5kBuilder - inline fun buildTaskWithReturn( - crossinline block: SafeContext.() -> R - ) = object : Task() { - override fun SafeContext.onStart() { - try { - success(block()) - } catch (e: Throwable) { - failure(e) - } - } + private fun StringBuilder.appendTaskTree(task: Task<*>, level: Int = 0) { + appendLine("${" ".repeat(level * 4)}${task.name}" + if (task !is TaskFlow) " [${task.state.display}]" else "") +// if (task.state == State.COMPLETED || task.state == State.CANCELLED) return + task.subTasks.forEach { +// if (task is TaskFlow && (it.state == State.COMPLETED || it.state == State.CANCELLED)) return@forEach + appendTaskTree(it, level + 1) } } - - val info: Text - get() = buildText { - literal("Name ") - color(primaryColor) { literal(name) } - - literal(" State ") - color(primaryColor) { literal(state.name) } - - literal(" Runtime ") - color(primaryColor) { - literal(DurationFormatUtils.formatDuration(age * 50L, "HH:mm:ss,SSS").dropLast(1)) - } - - val display = subTasks.reversed().take(MAX_DEBUG_ENTRIES) - display.forEach { - literal("\n${" ".repeat(depth + 1)}") - text(it.info) - } - - val left = subTasks.size - display.size - if (left > 0) { - literal("\n${" ".repeat(depth + 1)}And ") - color(primaryColor) { - literal("$left") - } - literal(" more...") - } - - } } diff --git a/common/src/main/kotlin/com/lambda/task/RootTask.kt b/common/src/main/kotlin/com/lambda/task/TaskFlow.kt similarity index 68% rename from common/src/main/kotlin/com/lambda/task/RootTask.kt rename to common/src/main/kotlin/com/lambda/task/TaskFlow.kt index 9f1b7b9c8..dc1da6432 100644 --- a/common/src/main/kotlin/com/lambda/task/RootTask.kt +++ b/common/src/main/kotlin/com/lambda/task/TaskFlow.kt @@ -17,13 +17,18 @@ package com.lambda.task -object RootTask : Task() { - init { - name = "RootTask" - } +import com.lambda.threading.runSafe + +object TaskFlow : Task() { + override val name get() = "TaskFlow ($size)" + + @Ta5kBuilder + fun Task<*>.run() = this.execute(this@TaskFlow) - fun addInfo(debugText: MutableList) { - debugText.add("") - debugText.addAll(info.string.split("\n")) + @Ta5kBuilder + fun Task<*>.run(task: TaskGenerator) { + runSafe { + task(Unit).execute(this@run) + } } -} +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/task/TaskResult.kt b/common/src/main/kotlin/com/lambda/task/TaskResult.kt deleted file mode 100644 index a20aa21b5..000000000 --- a/common/src/main/kotlin/com/lambda/task/TaskResult.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2024 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.task - -/** - * Represents the result of a task. - * - * A task result can be successful, failed, timed out, or canceled. - * - * @param Result The type of the result value for successful tasks. Covariant type parameter. - */ -sealed class TaskResult { - - /** - * Represents a successful task result. - * - * @param T The type of the result value. - * @property value The result value. - */ - data class Success(val value: T) : TaskResult() - - /** - * Represents a failed task result. - * - * @property throwable The exception that caused the task to fail. - */ - data class Failure(val throwable: Throwable) : TaskResult() - - /** - * Represents a task result that timed out. - * - * @property timeout The timeout duration in milliseconds. - * @property triesUsed The number of attempts made before the task timed out. - */ - data class Timeout(val timeout: Long, val triesUsed: Int) : TaskResult() - - /** - * Represents a cancelled task result. - */ - data object Cancelled : TaskResult() -} diff --git a/common/src/main/kotlin/com/lambda/task/tasks/AcquireMaterial.kt b/common/src/main/kotlin/com/lambda/task/tasks/AcquireMaterial.kt index 242b67bf1..92e25100a 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/AcquireMaterial.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/AcquireMaterial.kt @@ -23,15 +23,18 @@ import com.lambda.interaction.material.ContainerManager.findContainerWithSelecti import com.lambda.interaction.material.StackSelection import com.lambda.task.Task -class AcquireMaterial( +class AcquireMaterial @Ta5kBuilder constructor( val selection: StackSelection ) : Task() { + override val name: String + get() = "Acquiring $selection" + override fun SafeContext.onStart() { findContainerWithSelection(selection) ?.withdraw(selection) - ?.onSuccess { _, _ -> + ?.finally { success(selection) - }?.start(this@AcquireMaterial) + }?.execute(this@AcquireMaterial) ?: failure(ContainerManager.NoContainerFound(selection)) // ToDo: Create crafting path } diff --git a/common/src/main/kotlin/com/lambda/task/tasks/BreakBlock.kt b/common/src/main/kotlin/com/lambda/task/tasks/BreakBlock.kt index 163fe4253..01a32b04a 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/BreakBlock.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/BreakBlock.kt @@ -27,7 +27,7 @@ import com.lambda.event.events.WorldEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.construction.context.BreakContext import com.lambda.interaction.visibilty.VisibilityChecker.lookAtBlock -import com.lambda.module.modules.client.TaskFlow +import com.lambda.module.modules.client.TaskFlowModule import com.lambda.task.Task import com.lambda.util.BaritoneUtils import com.lambda.util.BlockUtils.blockState @@ -43,13 +43,15 @@ import net.minecraft.util.math.Direction class BreakBlock @Ta5kBuilder constructor( private val ctx: BreakContext, - private val rotation: IRotationConfig, - private val interact: InteractionConfig, - private val sides: Set, - private val collectDrop: Boolean, - private val rotate: Boolean, - private val swingHand: Boolean, + private val collectDrop: Boolean = false, + private val rotation: IRotationConfig = TaskFlowModule.rotation, + private val interact: InteractionConfig = TaskFlowModule.interact, + private val sides: Set = Direction.entries.toSet(), + private val rotate: Boolean = TaskFlowModule.build.rotateForBreak, + private val swingHand: Boolean = TaskFlowModule.interact.swingHand, ) : Task() { + override val name get() = "Breaking ${ctx.result.blockPos.toShortString()}" + val blockPos: BlockPos get() = ctx.result.blockPos private var beginState: BlockState? = null @@ -72,7 +74,7 @@ class BreakBlock @Ta5kBuilder constructor( beginState = blockState if (!rotate || ctx.instantBreak) { - breakBlock(ctx.result.side) + hitBlock(ctx.result.side) } } @@ -100,7 +102,7 @@ class BreakBlock @Ta5kBuilder constructor( if (player.hotbarAndStorage.none { it.isEmpty }) { player.currentScreenHandler.inventorySlots.firstOrNull { - it.stack.item.block in TaskFlow.disposables + it.stack.item.block in TaskFlowModule.disposables }?.let { clickSlot(it.index, 1, SlotActionType.THROW) } @@ -112,7 +114,7 @@ class BreakBlock @Ta5kBuilder constructor( } ?: BaritoneUtils.cancel() if (isValid || !rotate || ctx.instantBreak) { - breakBlock(ctx.result.side) + hitBlock(ctx.result.side) } if (done()) { @@ -137,31 +139,10 @@ class BreakBlock @Ta5kBuilder constructor( private fun SafeContext.done() = blockState.isAir && !collectDrop - private fun SafeContext.breakBlock(side: Direction) { + private fun SafeContext.hitBlock(side: Direction) { if (interaction.updateBlockBreakingProgress(blockPos, side)) { if (player.isCreative) interaction.blockBreakingCooldown = 0 if (swingHand) player.swingHand(ctx.hand) } } - - companion object { - @Ta5kBuilder - fun breakBlock( - ctx: BreakContext, - rotationConfig: IRotationConfig = TaskFlow.rotation, - interactionConfig: InteractionConfig = TaskFlow.interact, - sides: Set = emptySet(), - collectDrop: Boolean = TaskFlow.build.collectDrops, - rotate: Boolean = TaskFlow.build.rotateForBreak, - swingHand: Boolean = TaskFlow.interact.swingHand, - ) = BreakBlock( - ctx, - rotationConfig, - interactionConfig, - sides, - collectDrop, - rotate, - swingHand - ) - } } diff --git a/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt b/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt index acc64ad5c..141f5386a 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt @@ -23,6 +23,7 @@ import com.lambda.context.SafeContext import com.lambda.event.events.RenderEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.http.urlEncoded import com.lambda.interaction.construction.blueprint.Blueprint import com.lambda.interaction.construction.blueprint.Blueprint.Companion.toStructure import com.lambda.interaction.construction.blueprint.DynamicBlueprint @@ -32,7 +33,7 @@ import com.lambda.interaction.construction.simulation.BuildGoal import com.lambda.interaction.construction.simulation.BuildSimulator.simulate import com.lambda.interaction.construction.simulation.Simulation.Companion.simulation import com.lambda.interaction.construction.verify.TargetState -import com.lambda.module.modules.client.TaskFlow +import com.lambda.module.modules.client.TaskFlowModule import com.lambda.task.Task import com.lambda.util.BaritoneUtils import com.lambda.util.extension.Structure @@ -41,11 +42,13 @@ import net.minecraft.util.math.BlockPos class BuildTask @Ta5kBuilder constructor( private val blueprint: Blueprint, private val finishOnDone: Boolean = true, - private val pathing: Boolean = TaskFlow.build.pathing, + private val pathing: Boolean = TaskFlowModule.build.pathing, private val stayInRange: Boolean = true, private val forceSilkTouch: Boolean = false, - val collectDrops: Boolean = TaskFlow.build.collectDrops, + val collectDrops: Boolean = TaskFlowModule.build.collectDrops, ) : Task() { + override val name: String get() = "Building $blueprint" + private var previousResults = setOf() private val placeTimeout = 5 private val pending = mutableListOf() @@ -83,12 +86,12 @@ class BuildTask @Ta5kBuilder constructor( val instantResults = results.filterIsInstance() .filter { it.context.instantBreak } .sorted() - .take(TaskFlow.build.breaksPerTick) + .take(TaskFlowModule.build.breaksPerTick) - if (TaskFlow.build.breaksPerTick > 1 && instantResults.isNotEmpty()) { + if (TaskFlowModule.build.breaksPerTick > 1 && instantResults.isNotEmpty()) { instantResults.forEach { pending.add(it) - it.start(this@BuildTask, pauseParent = false) + it.execute(this@BuildTask, pauseParent = false) } return@listen } @@ -100,7 +103,7 @@ class BuildTask @Ta5kBuilder constructor( val result = results.minOrNull() ?: return@listen when (result) { is BuildResult.Done -> { - if (finishOnDone) success(Unit) + if (finishOnDone) success() } // !result.rank.solvable -> failure("Result is not solvable: $result") is BuildResult.NotVisible, is PlaceResult.NoIntegrity -> { @@ -126,7 +129,9 @@ class BuildTask @Ta5kBuilder constructor( } pending.add(result) - result.start(this@BuildTask, pauseParent = result.pausesParent) + result.finally { + this@BuildTask.activate() + }.execute(this@BuildTask, pauseParent = result.pausesParent) } } } @@ -136,10 +141,10 @@ class BuildTask @Ta5kBuilder constructor( @Ta5kBuilder fun build( finishOnDone: Boolean = true, - pathing: Boolean = TaskFlow.build.pathing, + pathing: Boolean = TaskFlowModule.build.pathing, stayInRange: Boolean = true, forceSilkTouch: Boolean = false, - collectDrops: Boolean = TaskFlow.build.collectDrops, + collectDrops: Boolean = TaskFlowModule.build.collectDrops, cancelOnUnsolvable: Boolean = true, blueprint: () -> Blueprint, ) = BuildTask( @@ -154,10 +159,10 @@ class BuildTask @Ta5kBuilder constructor( @Ta5kBuilder fun Structure.build( finishOnDone: Boolean = true, - pathing: Boolean = TaskFlow.build.pathing, + pathing: Boolean = TaskFlowModule.build.pathing, stayInRange: Boolean = true, forceSilkTouch: Boolean = false, - collectDrops: Boolean = TaskFlow.build.collectDrops, + collectDrops: Boolean = TaskFlowModule.build.collectDrops, cancelOnUnsolvable: Boolean = true, ) = BuildTask( toBlueprint(), @@ -171,10 +176,10 @@ class BuildTask @Ta5kBuilder constructor( @Ta5kBuilder fun Blueprint.build( finishOnDone: Boolean = true, - pathing: Boolean = TaskFlow.build.pathing, + pathing: Boolean = TaskFlowModule.build.pathing, stayInRange: Boolean = true, forceSilkTouch: Boolean = false, - collectDrops: Boolean = TaskFlow.build.collectDrops, + collectDrops: Boolean = TaskFlowModule.build.collectDrops, cancelOnUnsolvable: Boolean = true, ) = BuildTask( this, diff --git a/common/src/main/kotlin/com/lambda/task/tasks/InventoryTask.kt b/common/src/main/kotlin/com/lambda/task/tasks/InventoryTask.kt index a860e9f88..00727fc6a 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/InventoryTask.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/InventoryTask.kt @@ -21,7 +21,7 @@ import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.material.StackSelection -import com.lambda.module.modules.client.TaskFlow +import com.lambda.module.modules.client.TaskFlowModule import com.lambda.task.Task import com.lambda.util.extension.containerSlots import com.lambda.util.extension.inventorySlots @@ -31,18 +31,21 @@ import net.minecraft.screen.ScreenHandler import net.minecraft.screen.slot.Slot import net.minecraft.screen.slot.SlotActionType -class InventoryTask( +class InventoryTask @Ta5kBuilder constructor( val screen: ScreenHandler, - private val selector: StackSelection, + private val selection: StackSelection, val from: List, val to: List, private val closeScreen: Boolean = true ) : Task() { + override val name: String + get() = "Moving $selection from [${from.joinToString { "${it.id}" }}] to [${from.joinToString { "${it.id}" }}] in ${runCatching { screen.type::class.simpleName }.getOrNull() ?: screen::class.simpleName}" + private val transactions = mutableListOf() override fun SafeContext.onStart() { - val selectedFrom = selector.filterSlots(from).filter { it.hasStack() } - val selectedTo = to.filter { it.stack.isEmpty } + to.filter { it.stack.item.block in TaskFlow.disposables } + val selectedFrom = selection.filterSlots(from).filter { it.hasStack() } + val selectedTo = to.filter { it.stack.isEmpty } + to.filter { it.stack.item.block in TaskFlowModule.disposables } selectedFrom.zip(selectedTo).forEach { (from, to) -> transactions.add(SlotUtils.Transaction(to.index, 0, SlotActionType.SWAP)) transactions.add(SlotUtils.Transaction(from.index, 0, SlotActionType.SWAP)) @@ -55,16 +58,16 @@ class InventoryTask( // ToDo: Needs smart code to move as efficient as possible. // Also should handle overflow etc. Should be more generic listen { - val moved = selector.filterSlots(to) + val moved = selection.filterSlots(to) .filter { it.hasStack() } - .sumOf { it.stack.count } >= selector.count + .sumOf { it.stack.count } >= selection.count if (transactions.isEmpty() || moved) { if (closeScreen) player.closeHandledScreen() - success(Unit) + success() } - transactions.removeFirstOrNull()?.click() ?: success(Unit) + transactions.removeFirstOrNull()?.click() ?: success() } } @@ -79,11 +82,11 @@ class InventoryTask( ) = InventoryTask(screen, selection, from, to, closeScreen) @Ta5kBuilder - fun withdraw(screen: ScreenHandler, selection: StackSelection) = - moveItems(screen, selection, screen.containerSlots, screen.inventorySlots) + fun withdraw(screen: ScreenHandler, selection: StackSelection, closeScreen: Boolean = true) = + moveItems(screen, selection, screen.containerSlots, screen.inventorySlots, closeScreen) @Ta5kBuilder - fun deposit(screen: ScreenHandler, selection: StackSelection) = - moveItems(screen, selection, screen.inventorySlots, screen.containerSlots) + fun deposit(screen: ScreenHandler, selection: StackSelection, closeScreen: Boolean = true) = + moveItems(screen, selection, screen.inventorySlots, screen.containerSlots, closeScreen) } } diff --git a/common/src/main/kotlin/com/lambda/task/tasks/OpenContainer.kt b/common/src/main/kotlin/com/lambda/task/tasks/OpenContainer.kt index eba1a36d1..2b50d62dd 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/OpenContainer.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/OpenContainer.kt @@ -23,30 +23,37 @@ import com.lambda.event.events.RotationEvent import com.lambda.event.events.ScreenHandlerEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.visibilty.VisibilityChecker.lookAtBlock -import com.lambda.module.modules.client.TaskFlow +import com.lambda.module.modules.client.TaskFlowModule import com.lambda.task.Task +import com.lambda.util.Communication.info import com.lambda.util.world.raycast.RayCastUtils.blockResult import net.minecraft.screen.ScreenHandler import net.minecraft.util.Hand import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Direction -class OpenContainer( +class OpenContainer @Ta5kBuilder constructor( private val blockPos: BlockPos, private val waitForSlotLoad: Boolean = true, - private val rotate: Boolean, - private val rotation: IRotationConfig = TaskFlow.rotation, - private val interact: InteractionConfig = TaskFlow.interact, - private val sides: Set = emptySet(), + private val rotate: Boolean = true, + private val rotation: IRotationConfig = TaskFlowModule.rotation, + private val interact: InteractionConfig = TaskFlowModule.interact, + private val sides: Set = Direction.entries.toSet(), ) : Task() { + override val name get() = "${state.description(inScope)} at ${blockPos.toShortString()}" + private var screenHandler: ScreenHandler? = null private var state = State.SCOPING private var inScope = 0 - override var timeout = 50 - enum class State { - SCOPING, OPENING, SLOT_LOADING + SCOPING, OPENING, SLOT_LOADING; + + fun description(inScope: Int) = when (this) { + SCOPING -> "Waiting for scope ($inScope)" + OPENING -> "Opening container" + SLOT_LOADING -> "Waiting for slots to load" + } } init { @@ -92,13 +99,4 @@ class OpenContainer( } } } - - companion object { - @Ta5kBuilder - fun openContainer( - blockPos: BlockPos, - waitForSlotLoad: Boolean = true, - rotate: Boolean = true, - ) = OpenContainer(blockPos, waitForSlotLoad, rotate) - } } diff --git a/common/src/main/kotlin/com/lambda/task/tasks/PlaceBlock.kt b/common/src/main/kotlin/com/lambda/task/tasks/PlaceBlock.kt index 4becf35bf..92c13e57d 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/PlaceBlock.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/PlaceBlock.kt @@ -20,35 +20,31 @@ package com.lambda.task.tasks import com.lambda.Lambda.LOG import com.lambda.config.groups.InteractionConfig import com.lambda.context.SafeContext -import com.lambda.event.events.MovementEvent -import com.lambda.event.events.RotationEvent -import com.lambda.event.events.TickEvent -import com.lambda.event.events.WorldEvent +import com.lambda.event.events.* import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.construction.context.PlaceContext -import com.lambda.interaction.rotation.Rotation import com.lambda.interaction.rotation.Rotation.Companion.rotation import com.lambda.interaction.rotation.RotationContext -import com.lambda.module.modules.client.TaskFlow +import com.lambda.module.modules.client.TaskFlowModule import com.lambda.task.Task import com.lambda.util.BlockUtils import com.lambda.util.BlockUtils.blockState import com.lambda.util.Communication.info import com.lambda.util.Communication.warn -import com.lambda.util.extension.partialTicks import net.minecraft.block.BlockState -import net.minecraft.client.gui.screen.ingame.SignEditScreen +import net.minecraft.network.packet.s2c.play.BlockUpdateS2CPacket import net.minecraft.text.Text class PlaceBlock @Ta5kBuilder constructor( private val ctx: PlaceContext, - private val rotate: Boolean, - private val interact: InteractionConfig = TaskFlow.interact, - private val waitForConfirmation: Boolean, + private val rotate: Boolean = TaskFlowModule.build.rotateForPlace, + private val interact: InteractionConfig = TaskFlowModule.interact, + private val waitForConfirmation: Boolean = TaskFlowModule.build.placeConfirmation, ) : Task() { + override val name get() = "${state.description(waited)} ${ctx.targetState} at ${ctx.result.blockPos.toShortString()}" + private var beginState: BlockState? = null private var state = State.PRIME_ROTATION - private var findOutIfNeeded = false private var primeContext: RotationContext? = null private var waited = 0 @@ -59,7 +55,14 @@ class PlaceBlock @Ta5kBuilder constructor( get() = ctx.targetState.matches(ctx.resultingPos.blockState(world), ctx.resultingPos, world) enum class State { - PRIME_ROTATION, ROTATING, PLACING, CONFIRMING + PRIME_ROTATION, ROTATING, PLACING, CONFIRMING; + + fun description(waited: Int) = when (this) { + PRIME_ROTATION -> "Priming rotation" + ROTATING -> "Rotating" + PLACING -> "Placing" + CONFIRMING -> "Waiting for confirmation (${waited})" + } } override fun SafeContext.onStart() { @@ -116,9 +119,6 @@ class PlaceBlock @Ta5kBuilder constructor( listen { if (state != State.PLACING) return@listen - - /*if (findOutIfNeeded) placeBlock() - findOutIfNeeded = true*/ placeBlock() } @@ -130,11 +130,17 @@ class PlaceBlock @Ta5kBuilder constructor( } } - listen { - if (it.pos != ctx.resultingPos) return@listen + listen { + val packet = it.packet + if (packet !is BlockUpdateS2CPacket) return@listen + if (state != State.CONFIRMING) return@listen + if (packet.pos != ctx.resultingPos) return@listen - if (ctx.targetState.matches(it.state, it.pos, world)) { + if (ctx.targetState.matches(packet.state, packet.pos, world)) { finish() + } else { + info("State: ${packet.state.block} at ${packet.pos.toShortString()} doesn't match ${ctx.targetState} at ${ctx.resultingPos.toShortString()} (expected ${ctx.expectedState})") + warn("Waiting for confirmation...") } } } @@ -165,7 +171,7 @@ class PlaceBlock @Ta5kBuilder constructor( } } - private fun SafeContext.finish() { + private fun finish() { LOG.info( "Placed at ${ ctx.result.blockPos.toShortString() @@ -173,16 +179,6 @@ class PlaceBlock @Ta5kBuilder constructor( ctx.expectedState } and expecting position at ${ctx.resultingPos.toShortString()}" ) - success(Unit) - } - - companion object { - @Ta5kBuilder - fun placeBlock( - ctx: PlaceContext, - rotate: Boolean = TaskFlow.build.rotateForPlace, - waitForConfirmation: Boolean = TaskFlow.build.placeConfirmation, - interact: InteractionConfig = TaskFlow.interact, - ) = PlaceBlock(ctx, rotate, interact, waitForConfirmation) + success() } } diff --git a/common/src/main/kotlin/com/lambda/task/tasks/PlaceContainer.kt b/common/src/main/kotlin/com/lambda/task/tasks/PlaceContainer.kt index b4c1acb4d..200a5ec16 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/PlaceContainer.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/PlaceContainer.kt @@ -38,6 +38,9 @@ import net.minecraft.util.math.Direction class PlaceContainer @Ta5kBuilder constructor( val stack: ItemStack, ) : Task() { + private val startStack: ItemStack = stack.copy() + override val name: String get() = "Placing container ${startStack.name.string}" + override fun SafeContext.onStart() { val results = BlockPos.iterateOutwards(player.blockPos, 4, 3, 4) .map { it.blockPos } @@ -59,9 +62,9 @@ class PlaceContainer @Ta5kBuilder constructor( result.blockPos .toStructure(TargetState.Stack(stack)) .toBlueprint() - }.onSuccess { _, _ -> + }.finally { success(result.blockPos) - }.start(this@PlaceContainer) + }.execute(this@PlaceContainer) } ?: { failure("No valid placement found") } @@ -75,7 +78,6 @@ class PlaceContainer @Ta5kBuilder constructor( Items.ENDER_CHEST -> { !ChestBlock.isChestBlocked(world, blockPos) } - in shulkerBoxes -> { val box = ShulkerEntity .calculateBoundingBox(direction, 0.0f, 0.5f) @@ -83,14 +85,6 @@ class PlaceContainer @Ta5kBuilder constructor( .contract(1.0E-6) world.isSpaceEmpty(box) } - else -> false } - - companion object { - @Ta5kBuilder - fun placeContainer( - stack: ItemStack, - ) = PlaceContainer(stack) - } } diff --git a/common/src/main/resources/lambda.mixins.common.json b/common/src/main/resources/lambda.mixins.common.json index 55faf1b84..c27544122 100644 --- a/common/src/main/resources/lambda.mixins.common.json +++ b/common/src/main/resources/lambda.mixins.common.json @@ -46,7 +46,8 @@ "world.BlockCollisionSpliteratorMixin", "world.ClientChunkManagerMixin", "world.ClientWorldMixin", - "world.StructureTemplateMixin" + "world.StructureTemplateMixin", + "world.WorldMixin" ], "injectors": { "defaultRequire": 1 From 5cfdbac5f5b15c17753d657c1dfe57913f973b7c Mon Sep 17 00:00:00 2001 From: Constructor Date: Sat, 28 Dec 2024 03:47:48 +0100 Subject: [PATCH 26/39] Refactor block interaction logic to use expected positions Replaced `resultingPos` with `expectedPos` across the codebase for improved clarity and consistency in block placement and interaction handling. Introduced enhanced logic for detecting unexpected positions and added a corresponding result type `UnexpectedPosition`. Updated related event systems and modules to align with the new `BlockChange` event structure. --- .../com/lambda/mixin/world/WorldMixin.java | 15 ++---- .../com/lambda/event/events/WorldEvent.kt | 15 ++---- .../graphics/renderer/esp/ChunkedESP.kt | 2 +- .../construction/context/BreakContext.kt | 2 +- .../construction/context/BuildContext.kt | 2 +- .../construction/context/PlaceContext.kt | 4 +- .../construction/result/BreakResult.kt | 2 +- .../construction/result/PlaceResult.kt | 9 +++- .../interaction/construction/result/Rank.kt | 1 + .../construction/simulation/BuildSimulator.kt | 7 +++ .../module/modules/debug/InventoryDebug.kt | 5 ++ .../module/modules/player/PacketMine.kt | 4 +- .../com/lambda/task/tasks/PlaceBlock.kt | 53 ++++++++----------- 13 files changed, 58 insertions(+), 63 deletions(-) diff --git a/common/src/main/java/com/lambda/mixin/world/WorldMixin.java b/common/src/main/java/com/lambda/mixin/world/WorldMixin.java index d9ae78a13..bc809a5e8 100644 --- a/common/src/main/java/com/lambda/mixin/world/WorldMixin.java +++ b/common/src/main/java/com/lambda/mixin/world/WorldMixin.java @@ -25,19 +25,12 @@ import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(World.class) public abstract class WorldMixin { - @Inject(method = "setBlockState(Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;II)Z", at = @At("HEAD"), cancellable = true) - void setBlockStatePre(BlockPos pos, BlockState state, int flags, int maxUpdateDepth, CallbackInfoReturnable cir) { - if (EventFlow.post(new WorldEvent.BlockUpdate.Pre(pos, state, flags, maxUpdateDepth)).isCanceled()) { - cir.setReturnValue(false); - } - } - - @Inject(method = "setBlockState(Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;II)Z", at = @At("TAIL")) - void setBlockStatePost(BlockPos pos, BlockState state, int flags, int maxUpdateDepth, CallbackInfoReturnable cir) { - EventFlow.post(new WorldEvent.BlockUpdate.Post(pos, state, flags, maxUpdateDepth)); + @Inject(method = "onBlockChanged", at = @At("TAIL")) + void onBlockChanged(BlockPos pos, BlockState oldBlock, BlockState newBlock, CallbackInfo ci) { + EventFlow.post(new WorldEvent.BlockChange(pos, oldBlock, newBlock)); } } diff --git a/common/src/main/kotlin/com/lambda/event/events/WorldEvent.kt b/common/src/main/kotlin/com/lambda/event/events/WorldEvent.kt index de0ea6cc1..3679a973c 100644 --- a/common/src/main/kotlin/com/lambda/event/events/WorldEvent.kt +++ b/common/src/main/kotlin/com/lambda/event/events/WorldEvent.kt @@ -45,18 +45,11 @@ sealed class WorldEvent { ) : Event } - /** - * Represents a block update in the world - */ - sealed class BlockUpdate( + class BlockChange( val pos: BlockPos, - val state: BlockState, - val flags: Int, - val maxUpdateDepth: Int, - ) { - class Pre(pos: BlockPos, state: BlockState, flags: Int, depth: Int) : BlockUpdate(pos, state, flags, depth), ICancellable by Cancellable() - class Post(pos: BlockPos, state: BlockState, flags: Int, depth: Int) : BlockUpdate(pos, state, flags, depth), Event - } + val oldState: BlockState, + val newState: BlockState, + ) : Event /** * Represents an entity being added to the world diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/ChunkedESP.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/ChunkedESP.kt index 44ddc78d6..8e5919078 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/ChunkedESP.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/ChunkedESP.kt @@ -53,7 +53,7 @@ class ChunkedESP private constructor( } init { - listenConcurrently { event -> + listenConcurrently { event -> world.getWorldChunk(event.pos).renderer.notifyChunks() } diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/context/BreakContext.kt b/common/src/main/kotlin/com/lambda/interaction/construction/context/BreakContext.kt index 8cc5b1887..b56108521 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/context/BreakContext.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/context/BreakContext.kt @@ -35,7 +35,7 @@ data class BreakContext( override var hand: Hand, val instantBreak: Boolean, ) : BuildContext { - override val resultingPos: BlockPos + override val expectedPos: BlockPos get() = result.blockPos override val distance: Double by lazy { diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/context/BuildContext.kt b/common/src/main/kotlin/com/lambda/interaction/construction/context/BuildContext.kt index 8483777a2..ee9a58d9a 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/context/BuildContext.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/context/BuildContext.kt @@ -29,9 +29,9 @@ interface BuildContext : Comparable { val result: BlockHitResult val distance: Double val expectedState: BlockState + val expectedPos: BlockPos val checkedState: BlockState val hand: Hand - val resultingPos: BlockPos val rotation: RotationContext override fun compareTo(other: BuildContext): Int { diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/context/PlaceContext.kt b/common/src/main/kotlin/com/lambda/interaction/construction/context/PlaceContext.kt index 3d23e0691..4ab986d46 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/context/PlaceContext.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/context/PlaceContext.kt @@ -35,14 +35,12 @@ data class PlaceContext( override val expectedState: BlockState, override val checkedState: BlockState, override val hand: Hand, + override val expectedPos: BlockPos, val targetState: TargetState, val sneak: Boolean, val insideBlock: Boolean, val primeDirection: Direction? ) : BuildContext { - override val resultingPos: BlockPos - get() = result.blockPos.offset(result.side) - override fun compareTo(other: BuildContext): Int { return when (other) { is PlaceContext -> compareBy { diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/result/BreakResult.kt b/common/src/main/kotlin/com/lambda/interaction/construction/result/BreakResult.kt index ff9523adf..0b6c73634 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/result/BreakResult.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/result/BreakResult.kt @@ -56,7 +56,7 @@ sealed class BreakResult : BuildResult() { } override fun SafeContext.buildRenderer() { - withPos(context.resultingPos, color, context.result.side) + withPos(context.expectedPos, color, context.result.side) } override fun compareTo(other: ComparableResult): Int { diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/result/PlaceResult.kt b/common/src/main/kotlin/com/lambda/interaction/construction/result/PlaceResult.kt index b2d516417..6f74900e0 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/result/PlaceResult.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/result/PlaceResult.kt @@ -59,7 +59,7 @@ sealed class PlaceResult : BuildResult() { withPos(hitPos, color, context.result.side) val light = Color(35, 188, 254, 20) - withState(context.expectedState, context.resultingPos, light) + withState(context.expectedState, context.expectedPos, light) } override fun compareTo(other: ComparableResult): Int { @@ -136,6 +136,13 @@ sealed class PlaceResult : BuildResult() { override val rank = Rank.PLACE_BLOCK_FEATURE_DISABLED } + data class UnexpectedPosition( + override val blockPos: BlockPos, + val actualPos: BlockPos + ) : PlaceResult() { + override val rank = Rank.UNEXPECTED_POSITION + } + /** * The player has no permission to interact with the block. Or the stack cannot be used on the block. */ diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/result/Rank.kt b/common/src/main/kotlin/com/lambda/interaction/construction/result/Rank.kt index 766e27059..da6b5a599 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/result/Rank.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/result/Rank.kt @@ -42,6 +42,7 @@ enum class Rank { BREAK_NO_PERMISSION, PLACE_SCAFFOLD_EXCEEDED, PLACE_BLOCK_FEATURE_DISABLED, + UNEXPECTED_POSITION, PLACE_ILLEGAL_USAGE, // not an issue diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt index 8a173dab6..bb4080574 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt @@ -257,6 +257,12 @@ object BuildSimulator { var context = ItemPlacementContext(usageContext) + // ToDo: Actually find these result positions as well and use them smartly + 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 @@ -309,6 +315,7 @@ object BuildSimulator { resultState, blockHit.blockPos.blockState(world), Hand.MAIN_HAND, + context.blockPos, target, shouldSneak, false, diff --git a/common/src/main/kotlin/com/lambda/module/modules/debug/InventoryDebug.kt b/common/src/main/kotlin/com/lambda/module/modules/debug/InventoryDebug.kt index 034e6380a..dbcf5ad0d 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/debug/InventoryDebug.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/debug/InventoryDebug.kt @@ -20,6 +20,7 @@ package com.lambda.module.modules.debug import com.lambda.Lambda.LOG import com.lambda.event.events.PacketEvent import com.lambda.event.events.ScreenHandlerEvent +import com.lambda.event.events.WorldEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module import com.lambda.module.tag.ModuleTag @@ -35,6 +36,10 @@ object InventoryDebug : Module( defaultTags = setOf(ModuleTag.DEBUG) ) { init { + listen { + info("Block change at ${it.pos.toShortString()}: ${it.oldState} -> ${it.newState}") + } + listen { info("Opened screen handler: ${it.screenHandler::class.simpleName}") } diff --git a/common/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt b/common/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt index 9ec45ba51..5b6555ea0 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt @@ -514,10 +514,10 @@ object PacketMine : Module( } } - listen { + listen { currentMiningBlock.forEach { ctx -> ctx?.apply { - if (it.pos != pos || !isStateBroken(pos.blockState(world), it.state)) return@forEach + if (it.pos != pos || !isStateBroken(pos.blockState(world), it.newState)) return@forEach if (breakType.isPrimary()) { runHandlers(ProgressStage.PacketReceiveBreak, pos, lastValidBestTool) diff --git a/common/src/main/kotlin/com/lambda/task/tasks/PlaceBlock.kt b/common/src/main/kotlin/com/lambda/task/tasks/PlaceBlock.kt index 92c13e57d..8f876eea2 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/PlaceBlock.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/PlaceBlock.kt @@ -33,7 +33,8 @@ import com.lambda.util.Communication.info import com.lambda.util.Communication.warn import net.minecraft.block.BlockState import net.minecraft.network.packet.s2c.play.BlockUpdateS2CPacket -import net.minecraft.text.Text +import kotlin.time.DurationUnit +import kotlin.time.toDuration class PlaceBlock @Ta5kBuilder constructor( private val ctx: PlaceContext, @@ -44,20 +45,18 @@ class PlaceBlock @Ta5kBuilder constructor( override val name get() = "${state.description(waited)} ${ctx.targetState} at ${ctx.result.blockPos.toShortString()}" private var beginState: BlockState? = null - private var state = State.PRIME_ROTATION + private var state = State.INIT private var primeContext: RotationContext? = null private var waited = 0 - private val SafeContext.resultingState: BlockState - get() = ctx.resultingPos.blockState(world) - private val SafeContext.matches - get() = ctx.targetState.matches(ctx.resultingPos.blockState(world), ctx.resultingPos, world) + get() = ctx.targetState.matches(ctx.expectedPos.blockState(world), ctx.expectedPos, world) enum class State { - PRIME_ROTATION, ROTATING, PLACING, CONFIRMING; + INIT, PRIME_ROTATION, ROTATING, PLACING, CONFIRMING; fun description(waited: Int) = when (this) { + INIT -> "Placing block" PRIME_ROTATION -> "Priming rotation" ROTATING -> "Rotating" PLACING -> "Placing" @@ -68,40 +67,34 @@ class PlaceBlock @Ta5kBuilder constructor( override fun SafeContext.onStart() { if (ctx.primeDirection == null) { state = State.ROTATING + } else { + state = State.PRIME_ROTATION + primeContext = RotationContext( + ctx.primeDirection.rotation, + ctx.rotation.config, + ) } if (matches) { finish() return } - beginState = resultingState + beginState = ctx.expectedPos.blockState(world) - if (!rotate) { - placeBlock() - } + if (!rotate) placeBlock() } init { listen { event -> if (!rotate) return@listen - when (state) { - State.PRIME_ROTATION -> { - ctx.primeDirection?.let { direction -> - primeContext = RotationContext( - direction.rotation, - ctx.rotation.config, - ) - event.context = primeContext - } - } - else -> event.context = ctx.rotation - } + event.context = if (state == State.PRIME_ROTATION) { + primeContext + } else ctx.rotation } listen { event -> if (!rotate) return@listen if (!event.context.isValid) return@listen - Text.of("Rotation: ${event.context.rotation}") when (state) { State.PRIME_ROTATION -> { if (event.context != primeContext) return@listen @@ -134,13 +127,13 @@ class PlaceBlock @Ta5kBuilder constructor( val packet = it.packet if (packet !is BlockUpdateS2CPacket) return@listen if (state != State.CONFIRMING) return@listen - if (packet.pos != ctx.resultingPos) return@listen + if (packet.pos != ctx.expectedPos) return@listen if (ctx.targetState.matches(packet.state, packet.pos, world)) { finish() } else { - info("State: ${packet.state.block} at ${packet.pos.toShortString()} doesn't match ${ctx.targetState} at ${ctx.resultingPos.toShortString()} (expected ${ctx.expectedState})") - warn("Waiting for confirmation...") + info("Packet: State: ${packet.state.block} at ${packet.pos.toShortString()} doesn't match ${ctx.targetState} at ${ctx.expectedPos.toShortString()} (expected ${ctx.expectedState})") + warn("Packet: Waiting for confirmation...") } } } @@ -163,9 +156,7 @@ class PlaceBlock @Ta5kBuilder constructor( state = State.CONFIRMING - if (matches) { - if (!waitForConfirmation) finish() - } + if (!waitForConfirmation && matches) finish() } else { warn("Internal interaction failed with $actionResult") } @@ -177,7 +168,7 @@ class PlaceBlock @Ta5kBuilder constructor( ctx.result.blockPos.toShortString() } (${ctx.result.side}) with expecting state ${ ctx.expectedState - } and expecting position at ${ctx.resultingPos.toShortString()}" + } and expecting position at ${ctx.expectedPos.toShortString()}" ) success() } From e68b215d8b483d296e91ebbf2f6415127cfaf312 Mon Sep 17 00:00:00 2001 From: Constructor Date: Sat, 28 Dec 2024 04:59:46 +0100 Subject: [PATCH 27/39] Refactor task resolution and task nesting complexity Simplifies the task execution flow by introducing a `Resolvable` interface to standardize task resolution. Removes redundant task nesting and pending logic, streamlining parent task management and improving readability and maintainability. --- .../construction/result/BreakResult.kt | 30 ++++------- .../construction/result/BuildResult.kt | 26 ++++------ .../construction/result/PlaceResult.kt | 17 ++---- .../construction/result/Resolvable.kt | 24 +++++++++ .../module/modules/client/TaskFlowModule.kt | 14 +++++ .../src/main/kotlin/com/lambda/task/Task.kt | 13 ++--- .../kotlin/com/lambda/task/tasks/BuildTask.kt | 52 +++++-------------- .../com/lambda/task/tasks/PlaceBlock.kt | 13 ++--- 8 files changed, 88 insertions(+), 101 deletions(-) create mode 100644 common/src/main/kotlin/com/lambda/interaction/construction/result/Resolvable.kt diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/result/BreakResult.kt b/common/src/main/kotlin/com/lambda/interaction/construction/result/BreakResult.kt index 0b6c73634..f5b20fd1d 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/result/BreakResult.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/result/BreakResult.kt @@ -23,9 +23,11 @@ import com.lambda.context.SafeContext import com.lambda.interaction.construction.context.BreakContext import com.lambda.interaction.material.ContainerManager.findBestAvailableTool import com.lambda.interaction.material.ContainerManager.transfer +import com.lambda.interaction.material.MaterialContainer import com.lambda.interaction.material.StackSelection.Companion.select import com.lambda.interaction.material.StackSelection.Companion.selectStack import com.lambda.interaction.material.container.MainHandContainer +import com.lambda.task.Task import com.lambda.task.tasks.BreakBlock import net.minecraft.block.BlockState import net.minecraft.item.Item @@ -42,18 +44,14 @@ sealed class BreakResult : BuildResult() { data class Break( override val blockPos: BlockPos, val context: BreakContext - ) : Drawable, BreakResult() { + ) : Drawable, Resolvable, BreakResult() { override val rank = Rank.BREAK_SUCCESS private val color = Color(222, 0, 0, 100) var collectDrop = false override val pausesParent get() = collectDrop - override fun SafeContext.onStart() { - BreakBlock(context, collectDrop).finally { - success() - }.execute(this@Break) - } + override fun resolve() = BreakBlock(context, collectDrop) override fun SafeContext.buildRenderer() { withPos(context.expectedPos, color, context.result.side) @@ -79,10 +77,6 @@ sealed class BreakResult : BuildResult() { override val rank = Rank.BREAK_NOT_EXPOSED private val color = Color(46, 0, 0, 30) - override fun SafeContext.onStart() { - failure("Block is not exposed to air.") - } - override fun SafeContext.buildRenderer() { withPos(blockPos, color, side) } @@ -104,24 +98,20 @@ sealed class BreakResult : BuildResult() { override val blockPos: BlockPos, val blockState: BlockState, val badItem: Item - ) : Drawable, BreakResult() { + ) : Drawable, Resolvable, BreakResult() { override val rank = Rank.BREAK_ITEM_CANT_MINE private val color = Color(255, 0, 0, 100) override val pausesParent get() = true - override fun SafeContext.onStart() { + override fun resolve() = findBestAvailableTool(blockState) ?.select() ?.transfer(MainHandContainer) - ?.finally { - success() - }?.execute(this@ItemCantMine) ?: run { - selectStack { - isItem(badItem).not() - }.transfer(MainHandContainer)?.execute(this@ItemCantMine) ?: failure("No item found or space") - } - } + ?: selectStack { + isItem(badItem).not() + }.transfer(MainHandContainer) + ?: MaterialContainer.Nothing() override fun SafeContext.buildRenderer() { withPos(blockPos, color) diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/result/BuildResult.kt b/common/src/main/kotlin/com/lambda/interaction/construction/result/BuildResult.kt index e44af123f..4eecc096b 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/result/BuildResult.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/result/BuildResult.kt @@ -22,10 +22,12 @@ import baritone.api.pathing.goals.GoalNear import com.lambda.context.SafeContext import com.lambda.interaction.construction.context.BuildContext import com.lambda.interaction.material.ContainerManager.transfer +import com.lambda.interaction.material.MaterialContainer import com.lambda.interaction.material.StackSelection.Companion.select import com.lambda.interaction.material.container.MainHandContainer import com.lambda.task.Task import com.lambda.util.BlockUtils.blockState +import com.lambda.util.Nameable import net.minecraft.block.BlockState import net.minecraft.item.Item import net.minecraft.item.ItemStack @@ -35,7 +37,7 @@ import net.minecraft.util.math.Direction import net.minecraft.util.math.Vec3d import java.awt.Color -abstract class BuildResult : ComparableResult, Task() { +abstract class BuildResult : ComparableResult, Nameable { abstract val blockPos: BlockPos open val pausesParent = false override val name: String get() = "${this::class.simpleName} at ${blockPos.toShortString()}" @@ -191,20 +193,15 @@ abstract class BuildResult : ComparableResult, Task() { val context: BuildContext, val neededItem: Item, val currentItem: ItemStack, - ) : Drawable, BuildResult() { + ) : Drawable, Resolvable, BuildResult() { override val name: String get() = "Wrong item ($currentItem) for ${blockPos.toShortString()} need ${neededItem.name.string}" override val rank = Rank.WRONG_ITEM private val color = Color(3, 252, 169, 100) override val pausesParent get() = true - override fun SafeContext.onStart() { - neededItem.select() - .transfer(MainHandContainer) - ?.finally { - success() - }?.execute(this@WrongItem) ?: failure("Item ${neededItem.name.string} not found") - } + override fun resolve() = neededItem.select() + .transfer(MainHandContainer) ?: MaterialContainer.Nothing() override fun SafeContext.buildRenderer() { if (blockPos.blockState(world).isAir) { @@ -231,20 +228,15 @@ abstract class BuildResult : ComparableResult, Task() { override val blockPos: BlockPos, val context: BuildContext, val neededStack: ItemStack - ) : Drawable, BuildResult() { + ) : Drawable, Resolvable, BuildResult() { override val name: String get() = "Wrong stack for $blockPos need $neededStack." override val rank = Rank.WRONG_ITEM private val color = Color(3, 252, 169, 100) override val pausesParent get() = true - override fun SafeContext.onStart() { - neededStack.select() - .transfer(MainHandContainer) - ?.finally { - success() - }?.execute(this@WrongStack) ?: failure("Stack ${neededStack.name.string} not found") - } + override fun resolve() = + neededStack.select().transfer(MainHandContainer) ?: MaterialContainer.Nothing() override fun SafeContext.buildRenderer() { if (blockPos.blockState(world).isAir) { diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/result/PlaceResult.kt b/common/src/main/kotlin/com/lambda/interaction/construction/result/PlaceResult.kt index 6f74900e0..66ae0eaca 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/result/PlaceResult.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/result/PlaceResult.kt @@ -21,6 +21,7 @@ import baritone.api.pathing.goals.GoalBlock import baritone.api.pathing.goals.GoalInverted import com.lambda.context.SafeContext import com.lambda.interaction.construction.context.PlaceContext +import com.lambda.task.Task import com.lambda.task.tasks.BuildTask.Companion.breakBlock import com.lambda.task.tasks.PlaceBlock import net.minecraft.block.BlockState @@ -44,15 +45,11 @@ sealed class PlaceResult : BuildResult() { data class Place( override val blockPos: BlockPos, val context: PlaceContext - ) : Drawable, PlaceResult() { + ) : Drawable, Resolvable, PlaceResult() { override val rank = Rank.PLACE_SUCCESS private val color = Color(35, 188, 254, 100) - override fun SafeContext.onStart() { - PlaceBlock(context).finally { - success() - }.execute(this@Place) - } + override fun resolve() = PlaceBlock(context) override fun SafeContext.buildRenderer() { val hitPos = context.result.blockPos @@ -105,14 +102,10 @@ sealed class PlaceResult : BuildResult() { data class CantReplace( override val blockPos: BlockPos, val simulated: ItemPlacementContext - ) : PlaceResult() { + ) : Resolvable, PlaceResult() { override val rank = Rank.PLACE_CANT_REPLACE - override fun SafeContext.onStart() { - breakBlock(blockPos).finally { - success() - }.execute(this@CantReplace) - } + override fun resolve() = breakBlock(blockPos) } /** diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/result/Resolvable.kt b/common/src/main/kotlin/com/lambda/interaction/construction/result/Resolvable.kt new file mode 100644 index 000000000..ac28db61c --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/construction/result/Resolvable.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2024 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.task.Task + +interface Resolvable { + fun resolve(): Task<*> +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/TaskFlowModule.kt b/common/src/main/kotlin/com/lambda/module/modules/client/TaskFlowModule.kt index e579bec6f..4bc999ec8 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/TaskFlowModule.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/TaskFlowModule.kt @@ -20,6 +20,9 @@ package com.lambda.module.modules.client import com.lambda.config.groups.BuildSettings import com.lambda.config.groups.InteractionSettings import com.lambda.config.groups.RotationSettings +import com.lambda.event.events.RenderEvent +import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.construction.result.Drawable import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.BlockUtils.allSigns @@ -63,4 +66,15 @@ object TaskFlowModule : Module( Properties.WEST ) // val ignoredTags by setting("Ignored Tags", defaultIgnoreTags) + + @Volatile + var drawables = listOf() + + init { + listen { + drawables.toList().forEach { res -> + with(res) { buildRenderer() } + } + } + } } diff --git a/common/src/main/kotlin/com/lambda/task/Task.kt b/common/src/main/kotlin/com/lambda/task/Task.kt index 820d0a5bc..647dfe1ac 100644 --- a/common/src/main/kotlin/com/lambda/task/Task.kt +++ b/common/src/main/kotlin/com/lambda/task/Task.kt @@ -41,7 +41,7 @@ abstract class Task : Nameable { val isCompleted get() = state == State.COMPLETED val size: Int get() = subTasks.sumOf { it.size } + 1 - open var alwaysListening = false + open var unpausable = false private var nextTask: TaskGenerator? = null private var nextTaskOrNull: TaskGeneratorOrNull? = null @@ -113,9 +113,9 @@ abstract class Task : Nameable { owner.subTasks.add(this) parent = owner LOG.info("${owner.name} started $name") - if (!alwaysListening || pauseParent) { + if (!unpausable || pauseParent) { LOG.info("$name deactivating parent ${owner.name}") - owner.deactivate() + if (owner !is TaskFlow) owner.deactivate() } runSafe { runCatching { onStart() }.onFailure { failure(it) } } startListening() @@ -145,8 +145,8 @@ abstract class Task : Nameable { @Ta5kBuilder fun deactivate() { - if (this is TaskFlow) return if (state != State.RUNNING) return + if (unpausable) return state = State.PAUSED stopListening() } @@ -161,7 +161,8 @@ abstract class Task : Nameable { nextTaskOrNull = null parent?.let { owner -> task?.execute(owner) } } ?: run { - onFinish?.invoke(this, result) ?: println("No more tasks to run") + onFinish?.invoke(this, result) + parent?.activate() onFinish = null } } @@ -324,7 +325,7 @@ abstract class Task : Nameable { appendLine("${" ".repeat(level * 4)}${task.name}" + if (task !is TaskFlow) " [${task.state.display}]" else "") // if (task.state == State.COMPLETED || task.state == State.CANCELLED) return task.subTasks.forEach { -// if (task is TaskFlow && (it.state == State.COMPLETED || it.state == State.CANCELLED)) return@forEach + if (task is TaskFlow && (it.state == State.COMPLETED || it.state == State.CANCELLED)) return@forEach appendTaskTree(it, level + 1) } } diff --git a/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt b/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt index 141f5386a..de8017034 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt @@ -17,13 +17,10 @@ package com.lambda.task.tasks -import baritone.api.pathing.goals.GoalNear import com.lambda.Lambda.LOG import com.lambda.context.SafeContext -import com.lambda.event.events.RenderEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.http.urlEncoded import com.lambda.interaction.construction.blueprint.Blueprint import com.lambda.interaction.construction.blueprint.Blueprint.Companion.toStructure import com.lambda.interaction.construction.blueprint.DynamicBlueprint @@ -35,6 +32,7 @@ import com.lambda.interaction.construction.simulation.Simulation.Companion.simul import com.lambda.interaction.construction.verify.TargetState import com.lambda.module.modules.client.TaskFlowModule import com.lambda.task.Task +import com.lambda.task.TaskFlow import com.lambda.util.BaritoneUtils import com.lambda.util.extension.Structure import net.minecraft.util.math.BlockPos @@ -50,28 +48,13 @@ class BuildTask @Ta5kBuilder constructor( override val name: String get() = "Building $blueprint" private var previousResults = setOf() - private val placeTimeout = 5 - private val pending = mutableListOf() override fun SafeContext.onStart() { (blueprint as? DynamicBlueprint)?.create(this) } init { - listen { - previousResults.filterIsInstance().forEach { res -> - with(res) { buildRenderer() } - } - } - listen { - pending.removeIf { - if (it.age > placeTimeout) { - it.cancel() - true - } else it.isCompleted - } - (blueprint as? DynamicBlueprint)?.update(this) if (finishOnDone && blueprint.structure.isEmpty()) { @@ -81,7 +64,7 @@ class BuildTask @Ta5kBuilder constructor( // ToDo: Simulate for each pair player positions that work val results = blueprint.simulate(player.getCameraPosVec(mc.tickDelta)) - previousResults = results + TaskFlowModule.drawables = results.filterIsInstance() val instantResults = results.filterIsInstance() .filter { it.context.instantBreak } @@ -90,16 +73,11 @@ class BuildTask @Ta5kBuilder constructor( if (TaskFlowModule.build.breaksPerTick > 1 && instantResults.isNotEmpty()) { instantResults.forEach { - pending.add(it) - it.execute(this@BuildTask, pauseParent = false) + it.resolve().execute(this@BuildTask, pauseParent = false) } return@listen } - if (pending.isNotEmpty()) { - return@listen - } - val result = results.minOrNull() ?: return@listen when (result) { is BuildResult.Done -> { @@ -107,31 +85,25 @@ class BuildTask @Ta5kBuilder constructor( } // !result.rank.solvable -> failure("Result is not solvable: $result") is BuildResult.NotVisible, is PlaceResult.NoIntegrity -> { - if (pathing) BaritoneUtils.setGoalAndPath( - BuildGoal(blueprint.simulation()) - ) + if (pathing) BaritoneUtils.setGoalAndPath(BuildGoal(blueprint.simulation())) } is Navigable -> { if (pathing) BaritoneUtils.setGoalAndPath(result.goal) } - - else -> { - LOG.info("Resolving: $result") + is Resolvable -> { + LOG.info("Resolving: ${result.name}") if (result is BreakResult.Break) { result.collectDrop = collectDrops } - if (result !is BreakResult.Break || !result.collectDrop) { - if (pathing) BaritoneUtils.setGoalAndPath( - GoalNear(result.blockPos, 4) - ) - } + result.resolve().execute(this@BuildTask, pauseParent = result.pausesParent) - pending.add(result) - result.finally { - this@BuildTask.activate() - }.execute(this@BuildTask, pauseParent = result.pausesParent) +// if (result !is BreakResult.Break || !result.collectDrop) { +// if (pathing) BaritoneUtils.setGoalAndPath( +// GoalNear(result.blockPos, 4) +// ) +// } } } } diff --git a/common/src/main/kotlin/com/lambda/task/tasks/PlaceBlock.kt b/common/src/main/kotlin/com/lambda/task/tasks/PlaceBlock.kt index 8f876eea2..e81a7ba89 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/PlaceBlock.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/PlaceBlock.kt @@ -42,12 +42,11 @@ class PlaceBlock @Ta5kBuilder constructor( private val interact: InteractionConfig = TaskFlowModule.interact, private val waitForConfirmation: Boolean = TaskFlowModule.build.placeConfirmation, ) : Task() { - override val name get() = "${state.description(waited)} ${ctx.targetState} at ${ctx.result.blockPos.toShortString()}" + override val name get() = "${state.description()} ${ctx.targetState} at ${ctx.result.blockPos.toShortString()}" private var beginState: BlockState? = null private var state = State.INIT private var primeContext: RotationContext? = null - private var waited = 0 private val SafeContext.matches get() = ctx.targetState.matches(ctx.expectedPos.blockState(world), ctx.expectedPos, world) @@ -55,12 +54,12 @@ class PlaceBlock @Ta5kBuilder constructor( enum class State { INIT, PRIME_ROTATION, ROTATING, PLACING, CONFIRMING; - fun description(waited: Int) = when (this) { - INIT -> "Placing block" + fun description() = when (this) { + INIT -> "Placing" PRIME_ROTATION -> "Priming rotation" ROTATING -> "Rotating" PLACING -> "Placing" - CONFIRMING -> "Waiting for confirmation (${waited})" + CONFIRMING -> "Waiting for confirmation" } } @@ -112,7 +111,9 @@ class PlaceBlock @Ta5kBuilder constructor( listen { if (state != State.PLACING) return@listen - placeBlock() + if (!matches) placeBlock() else { + if (!waitForConfirmation) finish() + } } listen { From d0a1902bd2b6e13c63a79aa87fe6e2b97dd439bb Mon Sep 17 00:00:00 2001 From: Constructor Date: Mon, 30 Dec 2024 03:59:40 +0100 Subject: [PATCH 28/39] Refactor processor API and add Axis/BlockFace processors Refactored `findProcessorForState` to an extension function on `TargetState` for improved readability and consistency. Introduced `AxisPreprocessor` and `BlockFaceProcessor` to handle preprocessing based on block axis and block face properties, enhancing placement flexibility and processing logic. --- .../processing/ProcessorRegistry.kt | 4 +- .../processing/processors/AxisPreprocessor.kt | 36 ++++++++++++++++ .../processors/BlockFaceProcessor.kt | 41 +++++++++++++++++++ 3 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 common/src/main/kotlin/com/lambda/interaction/construction/processing/processors/AxisPreprocessor.kt create mode 100644 common/src/main/kotlin/com/lambda/interaction/construction/processing/processors/BlockFaceProcessor.kt diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/processing/ProcessorRegistry.kt b/common/src/main/kotlin/com/lambda/interaction/construction/processing/ProcessorRegistry.kt index 8ddceec53..7fd861802 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/processing/ProcessorRegistry.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/processing/ProcessorRegistry.kt @@ -28,8 +28,8 @@ object ProcessorRegistry : Loadable { private val processors = getInstances { forPackages(PROCESSOR_PACKAGE) } private val processorCache = mutableMapOf() - fun findProcessorForState(target: TargetState): PreprocessingStep = - (target as? TargetState.State)?.let { state -> + fun TargetState.findProcessorForState(): PreprocessingStep = + (this as? TargetState.State)?.let { state -> processorCache.getOrPut(state.blockState) { (processors.find { it.acceptState(state.blockState) } ?: DefaultProcessor).preProcess(state.blockState) } diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/processing/processors/AxisPreprocessor.kt b/common/src/main/kotlin/com/lambda/interaction/construction/processing/processors/AxisPreprocessor.kt new file mode 100644 index 000000000..0ff14d216 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/construction/processing/processors/AxisPreprocessor.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2024 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.processing.processors + +import com.lambda.interaction.construction.processing.PlacementProcessor +import com.lambda.interaction.construction.processing.PreprocessingStep +import net.minecraft.block.BlockState +import net.minecraft.state.property.Properties +import net.minecraft.util.math.Direction + +object AxisPreprocessor : PlacementProcessor() { + override fun acceptState(state: BlockState) = + state.getOrEmpty(Properties.AXIS).isPresent + + override fun preProcess(state: BlockState): PreprocessingStep { + val axis = state.getOrEmpty(Properties.AXIS).get() + return PreprocessingStep( + sides = Direction.entries.filter { it.axis == axis }.toSet() + ) + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/processing/processors/BlockFaceProcessor.kt b/common/src/main/kotlin/com/lambda/interaction/construction/processing/processors/BlockFaceProcessor.kt new file mode 100644 index 000000000..359709d5e --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/construction/processing/processors/BlockFaceProcessor.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2024 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.processing.processors + +import com.lambda.interaction.construction.processing.PlacementProcessor +import com.lambda.interaction.construction.processing.PreprocessingStep +import net.minecraft.block.BlockState +import net.minecraft.block.enums.BlockFace +import net.minecraft.state.property.Properties +import net.minecraft.util.math.Direction +import kotlin.jvm.optionals.getOrNull + +object BlockFaceProcessor : PlacementProcessor() { + override fun acceptState(state: BlockState) = + state.getOrEmpty(Properties.BLOCK_FACE).isPresent + + override fun preProcess(state: BlockState): PreprocessingStep { + val property = state.getOrEmpty(Properties.BLOCK_FACE).getOrNull() ?: return PreprocessingStep() + val sides = when (property) { + BlockFace.FLOOR -> setOf(Direction.DOWN) + BlockFace.CEILING -> setOf(Direction.UP) + BlockFace.WALL -> Direction.Type.HORIZONTAL.stream().toList().toSet() + } + return PreprocessingStep(sides = sides) + } +} \ No newline at end of file From 9e0ba5af571c34ba5e53e9b99414dda0d0c186da Mon Sep 17 00:00:00 2001 From: Constructor Date: Mon, 30 Dec 2024 04:07:34 +0100 Subject: [PATCH 29/39] Refactor placement handling in BuildTask implementation Lifted placement-related logic into the BuildTask to simplify task nesting and enhance data flow. This change improves maintainability and ensures more accessible context sharing across the build process. --- .../lambda/command/commands/BuildCommand.kt | 50 +++-- .../com/lambda/config/groups/BuildConfig.kt | 21 ++- .../com/lambda/config/groups/BuildSettings.kt | 19 +- .../{IRotationConfig.kt => RotationConfig.kt} | 4 +- .../lambda/config/groups/RotationSettings.kt | 2 +- .../com/lambda/interaction/RotationManager.kt | 4 +- .../blueprint/DynamicBlueprint.kt | 13 +- .../construction/context/BreakContext.kt | 4 + .../construction/context/BuildContext.kt | 3 +- .../construction/context/PlaceContext.kt | 41 +++- .../construction/result/PlaceResult.kt | 67 ++++--- .../construction/simulation/BuildSimulator.kt | 42 +---- .../construction/verify/StateMatcher.kt | 1 + .../construction/verify/TargetState.kt | 12 ++ .../interaction/rotation/RotationContext.kt | 4 +- .../visibilty/VisibilityChecker.kt | 8 +- .../lambda/module/modules/movement/Speed.kt | 4 +- .../lambda/module/modules/player/Freecam.kt | 4 +- .../com/lambda/module/modules/player/Nuker.kt | 7 +- .../lambda/module/modules/player/Replay.kt | 4 +- .../com/lambda/task/tasks/BreakBlock.kt | 4 +- .../kotlin/com/lambda/task/tasks/BuildTask.kt | 175 +++++++++++------- .../com/lambda/task/tasks/OpenContainer.kt | 5 +- 23 files changed, 297 insertions(+), 201 deletions(-) rename common/src/main/kotlin/com/lambda/config/groups/{IRotationConfig.kt => RotationConfig.kt} (95%) diff --git a/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt b/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt index e861f8b5d..cb2d81763 100644 --- a/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt +++ b/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt @@ -46,38 +46,34 @@ object BuildCommand : LambdaCommand( required(greedyString("structure")) { structure -> suggests { _, builder -> StructureRegistry.forEach { key, _ -> builder.suggest(key) } - builder.buildFuture() } - optional(boolean("pathing")) { pathing -> - executeWithResult { - val pathString = structure().value() - val doPathing = if (pathing != null) pathing().value() else false - runSafe { - try { - StructureRegistry - .loadStructureByRelativePath(Path.of(pathString)) - ?.let { template -> - info("Building structure $pathString with dimensions ${template.size.toShortString()} created by ${template.author}") - template.toStructure() - .move(player.blockPos) - .toBlueprint() - .build(pathing = doPathing) - .run() + executeWithResult { + val pathString = structure().value() + runSafe { + try { + StructureRegistry + .loadStructureByRelativePath(Path.of(pathString)) + ?.let { template -> + info("Building structure $pathString with dimensions ${template.size.toShortString()} created by ${template.author}") + template.toStructure() + .move(player.blockPos) + .toBlueprint() + .build() + .run() - return@executeWithResult CommandResult.success() - } - } catch (e: InvalidPathException) { - return@executeWithResult CommandResult.failure("Invalid path $pathString") - } catch (e: NoSuchFileException) { - return@executeWithResult CommandResult.failure("Structure $pathString not found") - } catch (e: Exception) { - return@executeWithResult CommandResult.failure(e.message ?: "Failed to load structure $pathString") - } + return@executeWithResult CommandResult.success() + } + } catch (e: InvalidPathException) { + return@executeWithResult CommandResult.failure("Invalid path $pathString") + } catch (e: NoSuchFileException) { + return@executeWithResult CommandResult.failure("Structure $pathString not found") + } catch (e: Exception) { + return@executeWithResult CommandResult.failure(e.message ?: "Failed to load structure $pathString") } - - CommandResult.failure("Structure $pathString not found") } + + CommandResult.failure("Structure $pathString not found") } } } diff --git a/common/src/main/kotlin/com/lambda/config/groups/BuildConfig.kt b/common/src/main/kotlin/com/lambda/config/groups/BuildConfig.kt index db143825a..17fc121f3 100644 --- a/common/src/main/kotlin/com/lambda/config/groups/BuildConfig.kt +++ b/common/src/main/kotlin/com/lambda/config/groups/BuildConfig.kt @@ -18,12 +18,23 @@ package com.lambda.config.groups interface BuildConfig { - val breakConfirmation: Boolean - val placeConfirmation: Boolean - val collectDrops: Boolean - val breakWeakBlocks: Boolean + // General val pathing: Boolean - val breaksPerTick: Int + val stayInRange: Boolean + val collectDrops: Boolean + val forceSilkTouch: Boolean + + // Breaking val rotateForBreak: Boolean + val breakConfirmation: Boolean + val maxPendingBreaks: Int + val breaksPerTick: Int + val breakWeakBlocks: Boolean + + // Placing val rotateForPlace: Boolean + val placeConfirmation: Boolean + val placeTimeout: Int + val maxPendingPlacements: Int + val placementsPerTick: Int } diff --git a/common/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt b/common/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt index f9e70ae7b..c584c1474 100644 --- a/common/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt +++ b/common/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt @@ -29,16 +29,23 @@ class BuildSettings( private val page by c.setting("Build Page", Page.GENERAL, "Current page", vis) + // General override val pathing by c.setting("Pathing", true, "Path to blocks") { vis() && page == Page.GENERAL } + override val stayInRange by c.setting("Stay In Range", true, "Stay in range of blocks") { vis() && page == Page.GENERAL && pathing } + override val collectDrops by c.setting("Collect All Drops", false, "Collect all drops when breaking blocks") { vis() && page == Page.GENERAL } + // Breaking + override val rotateForBreak by c.setting("Rotate For Break", true, "Rotate towards block while breaking") { vis() && page == Page.BREAK } override val breakConfirmation by c.setting("Break Confirmation", false, "Wait for block break confirmation") { vis() && page == Page.BREAK } + override val maxPendingBreaks by c.setting("Max Pending Breaks", 1, 1..10, 1, "Maximum pending block breaks") { vis() && page == Page.BREAK } + override val breaksPerTick by c.setting("Instant Breaks Per Tick", 5, 1..30, 1, "Maximum instant block breaks per tick") { vis() && page == Page.BREAK } override val breakWeakBlocks by c.setting("Break Weak Blocks", false, "Break blocks that dont have structural integrity (e.g: grass)") { vis() && page == Page.BREAK } - override val breaksPerTick by c.setting("Instant Breaks Per Tick", 10, 1..30, 1, "Maximum instant block breaks per tick") { vis() && page == Page.BREAK } - override val rotateForBreak by c.setting("Rotate For Break", true, "Rotate towards block while breaking") { vis() && page == Page.BREAK } - - override val collectDrops by c.setting("Collect All Drops", false, "Collect all drops when breaking blocks") { vis() && page == Page.BREAK } - - override val placeConfirmation by c.setting("Place Confirmation", true, "Wait for block placement confirmation") { vis() && page == Page.PLACE } + override val forceSilkTouch by c.setting("Force Silk Touch", false, "Force silk touch when breaking blocks") { vis() && page == Page.BREAK } + // Placing override val rotateForPlace by c.setting("Rotate For Place", true, "Rotate towards block while placing") { vis() && page == Page.PLACE } + override val placeConfirmation by c.setting("Place Confirmation", true, "Wait for block placement confirmation") { vis() && page == Page.PLACE } + override val placeTimeout by c.setting("Place Timeout", 10, 1..30, 1, "Timeout for block placement in ticks", unit = " ticks") { vis() && page == Page.PLACE && placeConfirmation } + override val maxPendingPlacements by c.setting("Max Pending Places", 1, 1..10, 1, "Maximum pending block places") { vis() && page == Page.PLACE } + override val placementsPerTick by c.setting("Instant Places Per Tick", 1, 1..30, 1, "Maximum instant block places per tick") { vis() && page == Page.PLACE } } diff --git a/common/src/main/kotlin/com/lambda/config/groups/IRotationConfig.kt b/common/src/main/kotlin/com/lambda/config/groups/RotationConfig.kt similarity index 95% rename from common/src/main/kotlin/com/lambda/config/groups/IRotationConfig.kt rename to common/src/main/kotlin/com/lambda/config/groups/RotationConfig.kt index fc83f9eed..a0e4d4b81 100644 --- a/common/src/main/kotlin/com/lambda/config/groups/IRotationConfig.kt +++ b/common/src/main/kotlin/com/lambda/config/groups/RotationConfig.kt @@ -19,7 +19,7 @@ package com.lambda.config.groups import com.lambda.interaction.rotation.RotationMode -interface IRotationConfig { +interface RotationConfig { /** * - [RotationMode.SILENT] Spoofing server-side rotation. * - [RotationMode.SYNC] Spoofing server-side rotation and adjusting client-side movement based on reported rotation (for Grim). @@ -42,7 +42,7 @@ interface IRotationConfig { */ val resetTicks: Int - interface Instant : IRotationConfig { + interface Instant : RotationConfig { override val turnSpeed get() = 360.0 override val keepTicks get() = 1 override val resetTicks get() = 1 diff --git a/common/src/main/kotlin/com/lambda/config/groups/RotationSettings.kt b/common/src/main/kotlin/com/lambda/config/groups/RotationSettings.kt index ae112bfe3..8981c468c 100644 --- a/common/src/main/kotlin/com/lambda/config/groups/RotationSettings.kt +++ b/common/src/main/kotlin/com/lambda/config/groups/RotationSettings.kt @@ -25,7 +25,7 @@ import kotlin.random.Random class RotationSettings( c: Configurable, vis: () -> Boolean = { true }, -) : IRotationConfig { +) : RotationConfig { /** * The rotation mode */ diff --git a/common/src/main/kotlin/com/lambda/interaction/RotationManager.kt b/common/src/main/kotlin/com/lambda/interaction/RotationManager.kt index 76caa68fa..d0de80d66 100644 --- a/common/src/main/kotlin/com/lambda/interaction/RotationManager.kt +++ b/common/src/main/kotlin/com/lambda/interaction/RotationManager.kt @@ -60,7 +60,7 @@ object RotationManager : Loadable { ) { var lastCtx: RotationContext? = null - this.listen(priority, alwaysListen) { event -> + listen(priority, alwaysListen) { event -> val rotationContext = onUpdate(event.context) rotationContext?.let { @@ -70,7 +70,7 @@ object RotationManager : Loadable { lastCtx = rotationContext } - this.listen { event -> + listen { event -> if (event.context == lastCtx && event.context.isValid) { onReceive() } diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/blueprint/DynamicBlueprint.kt b/common/src/main/kotlin/com/lambda/interaction/construction/blueprint/DynamicBlueprint.kt index ca21e401d..27908f5ff 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/blueprint/DynamicBlueprint.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/blueprint/DynamicBlueprint.kt @@ -18,6 +18,7 @@ package com.lambda.interaction.construction.blueprint import com.lambda.context.SafeContext +import com.lambda.threading.runSafe import com.lambda.util.extension.Structure import net.minecraft.util.math.Vec3i @@ -25,12 +26,16 @@ data class DynamicBlueprint( val init: SafeContext.(Structure) -> Structure = { emptyMap() }, val update: SafeContext.(Structure) -> Structure = { it }, ) : Blueprint() { - fun update(ctx: SafeContext) { - structure = ctx.update(structure) + fun update() { + runSafe { + structure = update(structure) + } } - fun create(ctx: SafeContext) { - structure = ctx.init(structure) + fun create() { + runSafe { + structure = init(structure) + } } override var structure: Structure = emptyMap() diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/context/BreakContext.kt b/common/src/main/kotlin/com/lambda/interaction/construction/context/BreakContext.kt index b56108521..8f99ffe26 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/context/BreakContext.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/context/BreakContext.kt @@ -58,4 +58,8 @@ data class BreakContext( else -> 1 } } + + override fun SafeContext.buildRenderer() { + + } } diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/context/BuildContext.kt b/common/src/main/kotlin/com/lambda/interaction/construction/context/BuildContext.kt index ee9a58d9a..316ea1b84 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/context/BuildContext.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/context/BuildContext.kt @@ -17,6 +17,7 @@ package com.lambda.interaction.construction.context +import com.lambda.interaction.construction.result.Drawable import com.lambda.interaction.rotation.RotationContext import net.minecraft.block.BlockState import net.minecraft.util.Hand @@ -24,7 +25,7 @@ import net.minecraft.util.hit.BlockHitResult import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Vec3d -interface BuildContext : Comparable { +interface BuildContext : Comparable, Drawable { val pov: Vec3d val result: BlockHitResult val distance: Double diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/context/PlaceContext.kt b/common/src/main/kotlin/com/lambda/interaction/construction/context/PlaceContext.kt index 4ab986d46..83481b88d 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/context/PlaceContext.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/context/PlaceContext.kt @@ -17,15 +17,21 @@ package com.lambda.interaction.construction.context +import com.lambda.context.SafeContext +import com.lambda.graphics.renderer.esp.DirectionMask +import com.lambda.graphics.renderer.esp.DirectionMask.exclude import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.rotation.RotationContext +import com.lambda.threading.runSafe import com.lambda.util.BlockUtils +import com.lambda.util.Communication.warn import net.minecraft.block.BlockState 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 java.awt.Color data class PlaceContext( override val pov: Vec3d, @@ -39,10 +45,35 @@ data class PlaceContext( val targetState: TargetState, val sneak: Boolean, val insideBlock: Boolean, - val primeDirection: Direction? + val primeDirection: Direction?, ) : BuildContext { - override fun compareTo(other: BuildContext): Int { - return when (other) { + var placeTick = 0L + private val baseColor = Color(35, 188, 254, 25) + private val sideColor = Color(35, 188, 254, 100) + + fun place(swingHand: Boolean) { + runSafe { + val actionResult = interaction.interactBlock( + player, hand, result + ) + + if (actionResult.isAccepted) { + if (actionResult.shouldSwingHand() && swingHand) { + player.swingHand(hand) + } + + if (!player.getStackInHand(hand).isEmpty && interaction.hasCreativeInventory()) { + mc.gameRenderer.firstPersonRenderer.resetEquipProgress(hand) + } + placeTick = mc.uptimeInTicks + } else { + warn("Internal interaction failed with $actionResult") + } + } + } + + override fun compareTo(other: BuildContext) = + when (other) { is PlaceContext -> compareBy { BlockUtils.fluids.indexOf(it.checkedState.fluidState.fluid) }.thenByDescending { @@ -59,5 +90,9 @@ data class PlaceContext( else -> 1 } + + override fun SafeContext.buildRenderer() { + withState(expectedState, expectedPos, baseColor, DirectionMask.ALL.exclude(result.side.opposite)) + withState(expectedState, expectedPos, sideColor, result.side.opposite) } } diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/result/PlaceResult.kt b/common/src/main/kotlin/com/lambda/interaction/construction/result/PlaceResult.kt index 66ae0eaca..5cd46bfe0 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/result/PlaceResult.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/result/PlaceResult.kt @@ -45,19 +45,8 @@ sealed class PlaceResult : BuildResult() { data class Place( override val blockPos: BlockPos, val context: PlaceContext - ) : Drawable, Resolvable, PlaceResult() { + ) : PlaceResult() { override val rank = Rank.PLACE_SUCCESS - private val color = Color(35, 188, 254, 100) - - override fun resolve() = PlaceBlock(context) - - override fun SafeContext.buildRenderer() { - val hitPos = context.result.blockPos - withPos(hitPos, color, context.result.side) - - val light = Color(35, 188, 254, 20) - withState(context.expectedState, context.expectedPos, light) - } override fun compareTo(other: ComparableResult): Int { return when (other) { @@ -68,10 +57,15 @@ sealed class PlaceResult : BuildResult() { } /** - * The calculated placement configuration does not match the simulated outcome. - * @param blockPos The position of the block that is not placed correctly. - * @param expected The expected placement configuration. - * @param simulated The simulated placement configuration. + * Represents a placement result where the block placement does not meet integrity expectations. + * + * 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 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, @@ -87,6 +81,11 @@ sealed class PlaceResult : BuildResult() { } } + /** + * Represents a scenario where block placement is obstructed by a player. + * + * @property blockPos The position of the block that was attempted to be placed. + */ data class BlockedByPlayer( override val blockPos: BlockPos ) : Navigable, PlaceResult() { @@ -96,8 +95,10 @@ sealed class PlaceResult : BuildResult() { } /** - * The placement configuration cannot replace the block at the target position. - * @param simulated The simulated placement configuration. + * 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 simulated The context of the item placement simulation. */ data class CantReplace( override val blockPos: BlockPos, @@ -109,8 +110,10 @@ sealed class PlaceResult : BuildResult() { } /** - * The placement configuration exceeds the maximum scaffold distance. - * @param simulated The simulated placement configuration. + * 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 simulated The context of the simulated item placement attempt. */ data class ScaffoldExceeded( override val blockPos: BlockPos, @@ -120,7 +123,11 @@ sealed class PlaceResult : BuildResult() { } /** - * The interaction with the block is restricted due to the feature being disabled. + * 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 itemStack The item stack associated with the attempted placement. */ data class BlockFeatureDisabled( override val blockPos: BlockPos, @@ -129,6 +136,12 @@ sealed class PlaceResult : BuildResult() { override val rank = Rank.PLACE_BLOCK_FEATURE_DISABLED } + /** + * 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 actualPos The actual position of the block, which differs from the intended position. + */ data class UnexpectedPosition( override val blockPos: BlockPos, val actualPos: BlockPos @@ -137,7 +150,11 @@ sealed class PlaceResult : BuildResult() { } /** - * The player has no permission to interact with the block. Or the stack cannot be used on the block. + * 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 rank The ranking of this result, which is always `PLACE_ILLEGAL_USAGE`. */ data class IllegalUsage( override val blockPos: BlockPos @@ -146,8 +163,10 @@ sealed class PlaceResult : BuildResult() { } /** - * The item stack is not a block item. - * This will be resolved by analyzing interaction with non-block items. + * 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 itemStack The item stack that was checked during the place operation. */ data class NotItemBlock( override val blockPos: BlockPos, diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt index bb4080574..8e0445f9b 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt @@ -131,43 +131,17 @@ object BuildSimulator { reach: Double ): Set { val acc = mutableSetOf() + val targetPosState = pos.blockState(world) - if (target is TargetState.Air || !pos.blockState(world).isReplaceable) return acc + if (target.isAir() || !targetPosState.isReplaceable) return acc val interact = TaskFlowModule.interact val rotation = TaskFlowModule.rotation - val preprocessing = findProcessorForState(target) - -// var sidesToCheck = Direction.entries.toTypedArray() -// -// (target as? TargetState.State) -// ?.blockState -// ?.getOrEmpty(Properties.FACING) -// ?.ifPresent { -// sidesToCheck = arrayOf(it) -// } -// -// (target as? TargetState.State) -// ?.blockState -// ?.getOrEmpty(Properties.BLOCK_FACE) -// ?.ifPresent { -// sidesToCheck = when (it) { -// BlockFace.FLOOR -> arrayOf(Direction.DOWN) -// BlockFace.CEILING -> arrayOf(Direction.UP) -// BlockFace.WALL -> Direction.Type.HORIZONTAL.stream().collect(Collectors.toList()).toTypedArray() -// } -// } -// -// (target as? TargetState.State) -// ?.blockState -// ?.getOrEmpty(Properties.AXIS) -// ?.ifPresent { axis -> -// sidesToCheck = Direction.entries.filter { it.axis == axis }.toTypedArray() -// } + val preprocessing = target.findProcessorForState() preprocessing.sides.forEach { neighbor -> - val hitPos = pos.offset(neighbor) + val hitPos = if (targetPosState.isAir) pos.offset(neighbor) else pos val hitSide = neighbor.opposite val voxelShape = hitPos.blockState(world).getOutlineShape(world, hitPos) @@ -202,12 +176,7 @@ object BuildSimulator { cast } else { - BlockHitResult( - vec, - side, - hitPos, - false - ) + BlockHitResult(vec, side, hitPos, false) } } } @@ -257,7 +226,6 @@ object BuildSimulator { var context = ItemPlacementContext(usageContext) - // ToDo: Actually find these result positions as well and use them smartly if (context.blockPos != pos) { acc.add(PlaceResult.UnexpectedPosition(pos, context.blockPos)) return@forEach diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/verify/StateMatcher.kt b/common/src/main/kotlin/com/lambda/interaction/construction/verify/StateMatcher.kt index 6f5e416d8..af2aaec11 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/verify/StateMatcher.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/verify/StateMatcher.kt @@ -25,4 +25,5 @@ import net.minecraft.util.math.BlockPos interface StateMatcher { fun matches(state: BlockState, pos: BlockPos, world: ClientWorld): Boolean fun getStack(world: ClientWorld, pos: BlockPos): ItemStack + fun isAir(): Boolean } diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt b/common/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt index ceb77d74e..f78a16d8d 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt @@ -43,6 +43,8 @@ sealed class TargetState(val type: Type) : StateMatcher { override fun getStack(world: ClientWorld, pos: BlockPos): ItemStack = ItemStack.EMPTY + + override fun isAir() = true } data object Solid : TargetState(Type.SOLID) { @@ -55,6 +57,8 @@ sealed class TargetState(val type: Type) : StateMatcher { findDisposable()?.stacks?.firstOrNull { it.item.block in TaskFlowModule.disposables } ?: ItemStack(Items.NETHERRACK) + + override fun isAir() = false } data class Support(val direction: Direction) : TargetState(Type.SUPPORT) { @@ -68,6 +72,8 @@ sealed class TargetState(val type: Type) : StateMatcher { findDisposable()?.stacks?.firstOrNull { it.item.block in TaskFlowModule.disposables } ?: ItemStack(Items.NETHERRACK) + + override fun isAir() = false } data class State(val blockState: BlockState) : TargetState(Type.STATE) { @@ -79,6 +85,8 @@ sealed class TargetState(val type: Type) : StateMatcher { } override fun getStack(world: ClientWorld, pos: BlockPos): ItemStack = blockState.block.getPickStack(world, pos, blockState) + + override fun isAir() = blockState.isAir } data class Block(val block: net.minecraft.block.Block) : TargetState(Type.BLOCK) { @@ -89,6 +97,8 @@ sealed class TargetState(val type: Type) : StateMatcher { override fun getStack(world: ClientWorld, pos: BlockPos): ItemStack = block.getPickStack(world, pos, block.defaultState) + + override fun isAir() = block.defaultState.isAir } data class Stack(val itemStack: ItemStack) : TargetState(Type.STACK) { @@ -102,5 +112,7 @@ sealed class TargetState(val type: Type) : StateMatcher { override fun getStack(world: ClientWorld, pos: BlockPos): ItemStack = itemStack + + override fun isAir() = false } } diff --git a/common/src/main/kotlin/com/lambda/interaction/rotation/RotationContext.kt b/common/src/main/kotlin/com/lambda/interaction/rotation/RotationContext.kt index 409491cfd..84dd5fbfd 100644 --- a/common/src/main/kotlin/com/lambda/interaction/rotation/RotationContext.kt +++ b/common/src/main/kotlin/com/lambda/interaction/rotation/RotationContext.kt @@ -17,13 +17,13 @@ package com.lambda.interaction.rotation -import com.lambda.config.groups.IRotationConfig +import com.lambda.config.groups.RotationConfig import com.lambda.util.world.raycast.RayCastUtils.orMiss import net.minecraft.util.hit.HitResult data class RotationContext( val rotation: Rotation, - val config: IRotationConfig, + val config: RotationConfig, val hitResult: HitResult? = null, val verify: HitResult.() -> Boolean = { true }, ) { diff --git a/common/src/main/kotlin/com/lambda/interaction/visibilty/VisibilityChecker.kt b/common/src/main/kotlin/com/lambda/interaction/visibilty/VisibilityChecker.kt index f2035df83..9d1b14339 100644 --- a/common/src/main/kotlin/com/lambda/interaction/visibilty/VisibilityChecker.kt +++ b/common/src/main/kotlin/com/lambda/interaction/visibilty/VisibilityChecker.kt @@ -17,7 +17,7 @@ package com.lambda.interaction.visibilty -import com.lambda.config.groups.IRotationConfig +import com.lambda.config.groups.RotationConfig import com.lambda.config.groups.InteractionConfig import com.lambda.context.SafeContext import com.lambda.interaction.RotationManager @@ -55,7 +55,7 @@ object VisibilityChecker { * @return A [RotationContext] if a valid rotation was found; otherwise, null. */ fun SafeContext.lookAtEntity( - rotationConfig: IRotationConfig, + rotationConfig: RotationConfig, interactionConfig: InteractionConfig, entity: Entity ) = findRotation(listOf(entity.boundingBox), rotationConfig, interactionConfig) { @@ -74,7 +74,7 @@ object VisibilityChecker { */ fun SafeContext.lookAtBlock( blockPos: BlockPos, - rotationConfig: IRotationConfig = TaskFlowModule.rotation, + rotationConfig: RotationConfig = TaskFlowModule.rotation, interactionConfig: InteractionConfig = TaskFlowModule.interact, sides: Set = Direction.entries.toSet() ): RotationContext? { @@ -100,7 +100,7 @@ object VisibilityChecker { */ fun SafeContext.findRotation( boxes: List, - rotationConfig: IRotationConfig, + rotationConfig: RotationConfig, interact: InteractionConfig, sides: Set = Direction.entries.toSet(), reach: Double = interact.reach, diff --git a/common/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt b/common/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt index 20b7ab900..2e61796fb 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt @@ -17,7 +17,7 @@ package com.lambda.module.modules.movement -import com.lambda.config.groups.IRotationConfig +import com.lambda.config.groups.RotationConfig import com.lambda.context.SafeContext import com.lambda.event.events.ClientEvent import com.lambda.event.events.MovementEvent @@ -71,7 +71,7 @@ object Speed : Module( private val ncpTimerBoost by setting("Timer Boost", 1.08, 1.0..1.1, 0.01) { mode == Mode.NCP_STRAFE } // Grim - private val rotationConfig = object : IRotationConfig.Instant { + private val rotationConfig = object : RotationConfig.Instant { override val rotationMode = RotationMode.SYNC } diff --git a/common/src/main/kotlin/com/lambda/module/modules/player/Freecam.kt b/common/src/main/kotlin/com/lambda/module/modules/player/Freecam.kt index fbb08ceaa..adb76c4a3 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/player/Freecam.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/player/Freecam.kt @@ -18,7 +18,7 @@ package com.lambda.module.modules.player import com.lambda.Lambda.mc -import com.lambda.config.groups.IRotationConfig +import com.lambda.config.groups.RotationConfig import com.lambda.event.events.* import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.rotation.Rotation @@ -56,7 +56,7 @@ object Freecam : Module( private val reach by setting("Reach", 10.0, 1.0..100.0, 1.0, "Freecam reach distance") private val rotateToTarget by setting("Rotate to target", true) - private val rotationConfig = object : IRotationConfig.Instant { + private val rotationConfig = object : RotationConfig.Instant { override val rotationMode = RotationMode.LOCK } diff --git a/common/src/main/kotlin/com/lambda/module/modules/player/Nuker.kt b/common/src/main/kotlin/com/lambda/module/modules/player/Nuker.kt index 2ac4284d6..606d3618f 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/player/Nuker.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/player/Nuker.kt @@ -64,11 +64,8 @@ object Nuker : Module( selection } - .build( - pathing = false, - finishOnDone = false, - cancelOnUnsolvable = false - ) + // ToDo: Add build setting delegates + .build() task?.run() } diff --git a/common/src/main/kotlin/com/lambda/module/modules/player/Replay.kt b/common/src/main/kotlin/com/lambda/module/modules/player/Replay.kt index b1f935d6f..e41daa91b 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/player/Replay.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/player/Replay.kt @@ -19,7 +19,7 @@ package com.lambda.module.modules.player import com.google.gson.* import com.lambda.brigadier.CommandResult -import com.lambda.config.groups.IRotationConfig +import com.lambda.config.groups.RotationConfig import com.lambda.context.SafeContext import com.lambda.core.TimerManager import com.lambda.event.EventFlow.lambdaScope @@ -79,7 +79,7 @@ object Replay : Module( private val deviationThreshold by setting("Deviation threshold", 0.1, 0.1..5.0, 0.1, description = "The threshold for the deviation to cancel the replay.") { cancelOnDeviation } private val lockCamera by setting("Lock Camera", true) - private val rotationConfig = object : IRotationConfig.Instant { + private val rotationConfig = object : RotationConfig.Instant { override val rotationMode = if (lockCamera) RotationMode.LOCK else RotationMode.SYNC } diff --git a/common/src/main/kotlin/com/lambda/task/tasks/BreakBlock.kt b/common/src/main/kotlin/com/lambda/task/tasks/BreakBlock.kt index 01a32b04a..acb26d565 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/BreakBlock.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/BreakBlock.kt @@ -18,7 +18,7 @@ package com.lambda.task.tasks import baritone.api.pathing.goals.GoalBlock -import com.lambda.config.groups.IRotationConfig +import com.lambda.config.groups.RotationConfig import com.lambda.config.groups.InteractionConfig import com.lambda.context.SafeContext import com.lambda.event.events.RotationEvent @@ -44,7 +44,7 @@ import net.minecraft.util.math.Direction class BreakBlock @Ta5kBuilder constructor( private val ctx: BreakContext, private val collectDrop: Boolean = false, - private val rotation: IRotationConfig = TaskFlowModule.rotation, + private val rotation: RotationConfig = TaskFlowModule.rotation, private val interact: InteractionConfig = TaskFlowModule.interact, private val sides: Set = Direction.entries.toSet(), private val rotate: Boolean = TaskFlowModule.build.rotateForBreak, diff --git a/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt b/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt index de8017034..42581b80e 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt @@ -18,13 +18,19 @@ package com.lambda.task.tasks import com.lambda.Lambda.LOG +import com.lambda.config.groups.* import com.lambda.context.SafeContext +import com.lambda.event.events.MovementEvent +import com.lambda.event.events.PacketEvent +import com.lambda.event.events.RotationEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.construction.blueprint.Blueprint import com.lambda.interaction.construction.blueprint.Blueprint.Companion.toStructure import com.lambda.interaction.construction.blueprint.DynamicBlueprint import com.lambda.interaction.construction.blueprint.StaticBlueprint.Companion.toBlueprint +import com.lambda.interaction.construction.context.BreakContext +import com.lambda.interaction.construction.context.PlaceContext import com.lambda.interaction.construction.result.* import com.lambda.interaction.construction.simulation.BuildGoal import com.lambda.interaction.construction.simulation.BuildSimulator.simulate @@ -32,30 +38,54 @@ import com.lambda.interaction.construction.simulation.Simulation.Companion.simul import com.lambda.interaction.construction.verify.TargetState import com.lambda.module.modules.client.TaskFlowModule import com.lambda.task.Task -import com.lambda.task.TaskFlow +import com.lambda.task.tasks.PlaceBlock.State import com.lambda.util.BaritoneUtils +import com.lambda.util.BlockUtils +import com.lambda.util.BlockUtils.blockState +import com.lambda.util.Communication.info +import com.lambda.util.Communication.warn import com.lambda.util.extension.Structure +import net.minecraft.network.packet.s2c.play.BlockUpdateS2CPacket import net.minecraft.util.math.BlockPos +import java.util.concurrent.ConcurrentLinkedQueue class BuildTask @Ta5kBuilder constructor( private val blueprint: Blueprint, private val finishOnDone: Boolean = true, - private val pathing: Boolean = TaskFlowModule.build.pathing, - private val stayInRange: Boolean = true, - private val forceSilkTouch: Boolean = false, - val collectDrops: Boolean = TaskFlowModule.build.collectDrops, + private val build: BuildConfig = TaskFlowModule.build, + private val rotation: RotationConfig = TaskFlowModule.rotation, + private val interact: InteractionConfig = TaskFlowModule.interact, ) : Task() { - override val name: String get() = "Building $blueprint" + override val name: String get() = "Building $blueprint with ${"%.2f".format(placements / (age / 20.0 + 0.001))} p/s" - private var previousResults = setOf() + private val pendingPlacements = ConcurrentLinkedQueue() + private val pendingBreaks = ConcurrentLinkedQueue() + + private var currentPlacement: PlaceContext? = null + private var placements = 0 + private var breaks = 0 override fun SafeContext.onStart() { - (blueprint as? DynamicBlueprint)?.create(this) + (blueprint as? DynamicBlueprint)?.create() } init { listen { - (blueprint as? DynamicBlueprint)?.update(this) + pendingPlacements.removeIf { + val timeout = (mc.uptimeInTicks - it.placeTick) > build.placeTimeout + if (timeout) { + info("Placement Timeout of ${it.expectedPos.toShortString()}") + } + timeout + } + + currentPlacement?.let { + it.place(interact.swingHand) + pendingPlacements.add(it) + currentPlacement = null + } + + (blueprint as? DynamicBlueprint)?.update() if (finishOnDone && blueprint.structure.isEmpty()) { failure("Structure is empty") @@ -64,46 +94,79 @@ class BuildTask @Ta5kBuilder constructor( // ToDo: Simulate for each pair player positions that work val results = blueprint.simulate(player.getCameraPosVec(mc.tickDelta)) - TaskFlowModule.drawables = results.filterIsInstance() + TaskFlowModule.drawables = results.filterIsInstance().plus(pendingPlacements.toList()) val instantResults = results.filterIsInstance() .filter { it.context.instantBreak } .sorted() - .take(TaskFlowModule.build.breaksPerTick) + .take(build.breaksPerTick) - if (TaskFlowModule.build.breaksPerTick > 1 && instantResults.isNotEmpty()) { + if (build.breaksPerTick > 1 && instantResults.isNotEmpty()) { instantResults.forEach { it.resolve().execute(this@BuildTask, pauseParent = false) } return@listen } - val result = results.minOrNull() ?: return@listen + val resultsWithoutPending = results.filterNot { res -> + val blockedPositions = pendingPlacements.map { it.expectedPos } + res is PlaceResult.Place && res.context.expectedPos in blockedPositions + } + val result = resultsWithoutPending.minOrNull() ?: return@listen when (result) { is BuildResult.Done -> { if (finishOnDone) success() } -// !result.rank.solvable -> failure("Result is not solvable: $result") is BuildResult.NotVisible, is PlaceResult.NoIntegrity -> { - if (pathing) BaritoneUtils.setGoalAndPath(BuildGoal(blueprint.simulation())) + if (build.pathing) BaritoneUtils.setGoalAndPath(BuildGoal(blueprint.simulation())) } is Navigable -> { - if (pathing) BaritoneUtils.setGoalAndPath(result.goal) + if (build.pathing) BaritoneUtils.setGoalAndPath(result.goal) + } + is PlaceResult.Place -> { + if (pendingPlacements.size >= build.maxPendingPlacements) return@listen + +// if (!result.context.rotation.isValid) { +// currentPlacement = result.context +// return@listen +// } + + currentPlacement = result.context } is Resolvable -> { LOG.info("Resolving: ${result.name}") if (result is BreakResult.Break) { - result.collectDrop = collectDrops + result.collectDrop = build.collectDrops } result.resolve().execute(this@BuildTask, pauseParent = result.pausesParent) + } + } + } -// if (result !is BreakResult.Break || !result.collectDrop) { -// if (pathing) BaritoneUtils.setGoalAndPath( -// GoalNear(result.blockPos, 4) -// ) -// } + listen { event -> + if (currentPlacement == null) return@listen + if (!build.rotateForPlace) return@listen + event.context = currentPlacement?.rotation + } + + listen { + val context = currentPlacement ?: return@listen + val hitBlock = context.result.blockPos.blockState(world).block + if (hitBlock in BlockUtils.interactionBlacklist) { + it.input.sneaking = true + } + } + + listen { event -> + val packet = event.packet + if (packet !is BlockUpdateS2CPacket) return@listen + + pendingPlacements.firstOrNull { it.expectedPos == packet.pos }?.let { + if (it.targetState.matches(packet.state, packet.pos, world)) { + pendingPlacements.remove(it) + placements++ } } } @@ -113,72 +176,50 @@ class BuildTask @Ta5kBuilder constructor( @Ta5kBuilder fun build( finishOnDone: Boolean = true, - pathing: Boolean = TaskFlowModule.build.pathing, - stayInRange: Boolean = true, - forceSilkTouch: Boolean = false, - collectDrops: Boolean = TaskFlowModule.build.collectDrops, - cancelOnUnsolvable: Boolean = true, + build: BuildConfig = TaskFlowModule.build, + rotation: RotationConfig = TaskFlowModule.rotation, + interact: InteractionConfig = TaskFlowModule.interact, blueprint: () -> Blueprint, - ) = BuildTask( - blueprint(), - finishOnDone, - pathing, - stayInRange, - forceSilkTouch, - collectDrops - ) + ) = BuildTask(blueprint(), finishOnDone, build, rotation, interact) @Ta5kBuilder fun Structure.build( finishOnDone: Boolean = true, - pathing: Boolean = TaskFlowModule.build.pathing, - stayInRange: Boolean = true, - forceSilkTouch: Boolean = false, - collectDrops: Boolean = TaskFlowModule.build.collectDrops, - cancelOnUnsolvable: Boolean = true, - ) = BuildTask( - toBlueprint(), - finishOnDone, - pathing, - stayInRange, - forceSilkTouch, - collectDrops - ) + build: BuildConfig = TaskFlowModule.build, + rotation: RotationConfig = TaskFlowModule.rotation, + interact: InteractionConfig = TaskFlowModule.interact + ) = BuildTask(toBlueprint(), finishOnDone, build, rotation, interact) @Ta5kBuilder fun Blueprint.build( finishOnDone: Boolean = true, - pathing: Boolean = TaskFlowModule.build.pathing, - stayInRange: Boolean = true, - forceSilkTouch: Boolean = false, - collectDrops: Boolean = TaskFlowModule.build.collectDrops, - cancelOnUnsolvable: Boolean = true, - ) = BuildTask( - this, - finishOnDone, - pathing, - stayInRange, - forceSilkTouch, - collectDrops - ) + build: BuildConfig = TaskFlowModule.build, + rotation: RotationConfig = TaskFlowModule.rotation, + interact: InteractionConfig = TaskFlowModule.interact + ) = BuildTask(this, finishOnDone, build, rotation, interact) @Ta5kBuilder fun breakAndCollectBlock( blockPos: BlockPos, - withSilkTouch: Boolean = false, - stayInRange: Boolean = false, + finishOnDone: Boolean = true, + build: BuildConfig = TaskFlowModule.build, + rotation: RotationConfig = TaskFlowModule.rotation, + interact: InteractionConfig = TaskFlowModule.interact ) = BuildTask( blockPos.toStructure(TargetState.Air).toBlueprint(), - forceSilkTouch = withSilkTouch, - stayInRange = stayInRange, - collectDrops = true + finishOnDone, build, rotation, interact ) @Ta5kBuilder fun breakBlock( blockPos: BlockPos, + finishOnDone: Boolean = true, + build: BuildConfig = TaskFlowModule.build, + rotation: RotationConfig = TaskFlowModule.rotation, + interact: InteractionConfig = TaskFlowModule.interact ) = BuildTask( - blockPos.toStructure(TargetState.Air).toBlueprint() + blockPos.toStructure(TargetState.Air).toBlueprint(), + finishOnDone, build, rotation, interact ) } } diff --git a/common/src/main/kotlin/com/lambda/task/tasks/OpenContainer.kt b/common/src/main/kotlin/com/lambda/task/tasks/OpenContainer.kt index 2b50d62dd..e8a397d7e 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/OpenContainer.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/OpenContainer.kt @@ -17,7 +17,7 @@ package com.lambda.task.tasks -import com.lambda.config.groups.IRotationConfig +import com.lambda.config.groups.RotationConfig import com.lambda.config.groups.InteractionConfig import com.lambda.event.events.RotationEvent import com.lambda.event.events.ScreenHandlerEvent @@ -25,7 +25,6 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.visibilty.VisibilityChecker.lookAtBlock import com.lambda.module.modules.client.TaskFlowModule import com.lambda.task.Task -import com.lambda.util.Communication.info import com.lambda.util.world.raycast.RayCastUtils.blockResult import net.minecraft.screen.ScreenHandler import net.minecraft.util.Hand @@ -36,7 +35,7 @@ class OpenContainer @Ta5kBuilder constructor( private val blockPos: BlockPos, private val waitForSlotLoad: Boolean = true, private val rotate: Boolean = true, - private val rotation: IRotationConfig = TaskFlowModule.rotation, + private val rotation: RotationConfig = TaskFlowModule.rotation, private val interact: InteractionConfig = TaskFlowModule.interact, private val sides: Set = Direction.entries.toSet(), ) : Task() { From 7d8e3848b7c94df84b92039984f24e2808814214 Mon Sep 17 00:00:00 2001 From: Constructor Date: Mon, 30 Dec 2024 04:08:52 +0100 Subject: [PATCH 30/39] Refactor inventory system configuration to enhance material handling. Implemented prioritized material and space comparators in inventory. Replaced `name` property with `description` for detailed container info. Simplified transfer and debug logic for better readability and usability. --- .../command/commands/TransferCommand.kt | 25 ++++----- .../lambda/config/groups/InventoryConfig.kt | 51 +++++++++++++++++++ .../lambda/config/groups/InventorySettings.kt | 28 ++++++++++ .../interaction/material/ContainerManager.kt | 46 ++++++++--------- .../interaction/material/MaterialContainer.kt | 48 +++++++++++++---- .../material/container/ChestContainer.kt | 15 +++++- .../material/container/CreativeContainer.kt | 15 ++++-- .../material/container/EnderChestContainer.kt | 5 +- .../material/container/HotbarContainer.kt | 5 +- .../material/container/InventoryContainer.kt | 5 +- .../material/container/MainHandContainer.kt | 18 ++++--- .../material/container/OffHandContainer.kt | 5 +- .../material/container/ShulkerBoxContainer.kt | 12 ++++- .../material/container/StashContainer.kt | 14 +++-- .../material/transfer/TransferResult.kt | 4 +- .../module/modules/client/RenderSettings.kt | 3 ++ .../module/modules/client/TaskFlowModule.kt | 20 +++----- .../module/modules/debug/InventoryDebug.kt | 4 -- .../com/lambda/task/tasks/AcquireMaterial.kt | 4 +- .../com/lambda/task/tasks/PlaceBlock.kt | 6 +-- .../com/lambda/task/tasks/PlaceContainer.kt | 8 +-- .../kotlin/com/lambda/util/text/TextDsl.kt | 8 +++ .../src/main/resources/lambda.accesswidener | 1 + 23 files changed, 254 insertions(+), 96 deletions(-) create mode 100644 common/src/main/kotlin/com/lambda/config/groups/InventoryConfig.kt create mode 100644 common/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt diff --git a/common/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt b/common/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt index 42b938b5b..5e6307df2 100644 --- a/common/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt +++ b/common/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt @@ -24,7 +24,8 @@ import com.lambda.brigadier.executeWithResult import com.lambda.brigadier.required import com.lambda.command.LambdaCommand import com.lambda.interaction.material.ContainerManager -import com.lambda.interaction.material.ContainerManager.containerMatchSelection +import com.lambda.interaction.material.ContainerManager.containerWithMaterial +import com.lambda.interaction.material.ContainerManager.containerWithSpace import com.lambda.interaction.material.StackSelection.Companion.selectStack import com.lambda.interaction.material.transfer.TransferResult import com.lambda.task.TaskFlow.run @@ -40,17 +41,15 @@ object TransferCommand : LambdaCommand( override fun CommandBuilder.create() { required(itemStack("stack", registry)) { stack -> - required(integer("amount")) { amount -> + required(integer("amount", 1)) { amount -> required(string("from")) { from -> suggests { ctx, builder -> val count = amount(ctx).value() val selection = selectStack(count) { isItem(stack(ctx).value().item) } - containerMatchSelection(selection).forEach { - val available = it.available(selection) - val availableMsg = if (available == Int.MAX_VALUE) "∞" else available.toString() - builder.suggest("\"${it.name} with $availableMsg\"") + containerWithMaterial(selection).forEachIndexed { i, container -> + builder.suggest("\"${i + 1}. ${container.name}\"", container.description(selection)) } builder.buildFuture() } @@ -59,10 +58,8 @@ object TransferCommand : LambdaCommand( val selection = selectStack(amount(ctx).value()) { isItem(stack(ctx).value().item) } - ContainerManager.container().forEach { - val space = it.spaceLeft(selection) - val spaceMsg = if (space == Int.MAX_VALUE) "∞" else space.toString() - if (space > 0) builder.suggest("\"${it.name} with $spaceMsg space left\"") + containerWithSpace(selection).forEachIndexed { i, container -> + builder.suggest("\"${i + 1}. ${container.name}\"", container.description(selection)) } builder.buildFuture() } @@ -71,19 +68,19 @@ object TransferCommand : LambdaCommand( isItem(stack().value().item) } val fromContainer = ContainerManager.container().find { - it.name == from().value().split(" with ").firstOrNull() + it.name == from().value().split(".").last().trim() } ?: return@executeWithResult failure("From container not found") val toContainer = ContainerManager.container().find { - it.name == to().value().split(" with ").firstOrNull() + it.name == to().value().split(".").last().trim() } ?: return@executeWithResult failure("To container not found") when (val transaction = fromContainer.transfer(selection, toContainer)) { is TransferResult.Transfer -> { - info("$transaction started.") + info("${transaction.name} started.") lastTransfer = transaction transaction.finally { - info("$lastTransfer completed.") + info("${transaction.name} completed.") }.run() return@executeWithResult success() } diff --git a/common/src/main/kotlin/com/lambda/config/groups/InventoryConfig.kt b/common/src/main/kotlin/com/lambda/config/groups/InventoryConfig.kt new file mode 100644 index 000000000..5fdd2eab1 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/config/groups/InventoryConfig.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2024 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.config.groups + +import com.lambda.interaction.material.MaterialContainer +import com.lambda.interaction.material.StackSelection + +interface InventoryConfig { + val providerPriority: Priority + val storePriority: Priority + + enum class Priority { + WITH_MIN_ITEMS, + WITH_MAX_ITEMS; + + fun materialComparator(selection: StackSelection) = + when (this) { + WITH_MAX_ITEMS -> compareBy { it.rank } + .thenByDescending { it.materialAvailable(selection) } + .thenBy { it.name } + WITH_MIN_ITEMS -> compareBy { it.rank } + .thenBy { it.materialAvailable(selection) } + .thenBy { it.name } + } + + fun spaceComparator(selection: StackSelection) = + when (this) { + WITH_MAX_ITEMS -> compareBy { it.rank } + .thenByDescending { it.spaceAvailable(selection) } + .thenBy { it.name } + WITH_MIN_ITEMS -> compareBy { it.rank } + .thenBy { it.spaceAvailable(selection) } + .thenBy { it.name } + } + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt b/common/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt new file mode 100644 index 000000000..1a22ed52c --- /dev/null +++ b/common/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2024 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.config.groups + +import com.lambda.config.Configurable + +class InventorySettings( + c: Configurable, + vis: () -> Boolean = { true }, +) : InventoryConfig { + override val providerPriority by c.setting("Provider Priority", InventoryConfig.Priority.WITH_MIN_ITEMS, "What container to prefer when retrieving the item from", vis) + override val storePriority by c.setting("Store Priority", InventoryConfig.Priority.WITH_MIN_ITEMS, "What container to prefer when storing the item to", vis) +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/interaction/material/ContainerManager.kt b/common/src/main/kotlin/com/lambda/interaction/material/ContainerManager.kt index a1346d402..4d04e4334 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/ContainerManager.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/ContainerManager.kt @@ -35,7 +35,6 @@ import net.minecraft.block.entity.BlockEntity import net.minecraft.block.entity.ChestBlockEntity import net.minecraft.block.entity.EnderChestBlockEntity import net.minecraft.item.Item -import net.minecraft.item.ItemStack import net.minecraft.screen.GenericContainerScreenHandler import net.minecraft.screen.ScreenHandlerType @@ -85,39 +84,38 @@ object ContainerManager : Loadable { } } - fun container() = container.flatMap { - setOf(it) + it.shulkerContainer - }.sorted() + fun container() = container.flatMap { setOf(it) + it.shulkerContainer }.sorted() fun StackSelection.transfer(destination: MaterialContainer) = - findContainerWithSelection(this)?.transfer(this, destination) + findContainerWithMaterial(this)?.transfer(this, destination) fun findContainer( block: (MaterialContainer) -> Boolean ): MaterialContainer? = container().find(block) - fun findContainerWithSelection( + fun findContainerWithMaterial( selection: StackSelection ): MaterialContainer? = - container().find { it.available(selection) >= selection.count } + containerWithMaterial(selection).firstOrNull() - fun containerMatchSelection( + fun findContainerWithSpace( selection: StackSelection - ): Set = - container().filter { it.available(selection) >= selection.count }.toSet() - - fun findContainerWithSelection( - selectionBuilder: StackSelection.() -> Unit - ): MaterialContainer? { - val selection = StackSelection().apply(selectionBuilder) - return container().find { it.available(selection) >= selection.count } - } - - fun findContainerWithStacks( - count: Int = StackSelection.DEFAULT_AMOUNT, - selection: (ItemStack) -> Boolean, ): MaterialContainer? = - findContainerWithSelection(selection.select()) + containerWithSpace(selection).firstOrNull() + + fun containerWithMaterial( + selection: StackSelection + ): List = + container() + .sortedWith(TaskFlowModule.inventory.providerPriority.materialComparator(selection)) + .filter { it.materialAvailable(selection) >= selection.count } + + fun containerWithSpace( + selection: StackSelection + ): List = + container() + .sortedWith(TaskFlowModule.inventory.providerPriority.spaceComparator(selection)) + .filter { it.spaceAvailable(selection) >= selection.count } fun findBestAvailableTool( blockState: BlockState, @@ -127,13 +125,13 @@ object ContainerManager : Loadable { }.filter { (item, speed) -> speed > 1.0 && item.isSuitableFor(blockState) - && findContainerWithSelection(item.select()) != null + && containerWithMaterial(item.select()).isNotEmpty() }.maxByOrNull { it.second }?.first fun findDisposable() = container().find { container -> - TaskFlowModule.disposables.any { container.available(it.item.select()) >= 0 } + TaskFlowModule.disposables.any { container.materialAvailable(it.item.select()) >= 0 } } class NoContainerFound(selection: StackSelection) : Exception("No container found matching $selection") diff --git a/common/src/main/kotlin/com/lambda/interaction/material/MaterialContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/MaterialContainer.kt index ddb94fa8b..51886997d 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/MaterialContainer.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/MaterialContainer.kt @@ -28,13 +28,41 @@ import com.lambda.util.item.ItemStackUtils.empty import com.lambda.util.item.ItemStackUtils.shulkerBoxContents import com.lambda.util.item.ItemStackUtils.spaceLeft import com.lambda.util.item.ItemUtils +import com.lambda.util.text.* import net.minecraft.item.ItemStack +import net.minecraft.text.Text // ToDo: Make jsonable to persistently store them abstract class MaterialContainer( - private val rank: Rank + val rank: Rank ) : Nameable, Comparable { abstract var stacks: List + abstract val description: Text + + @TextDsl + fun TextBuilder.stock(selection: StackSelection) { + literal("\n") + literal("Contains ") + val available = materialAvailable(selection) + highlighted(if (available == Int.MAX_VALUE) "∞" else available.toString()) + literal(" of ") + highlighted("${selection.optimalStack?.name?.string}") + literal("\n") + literal("Could store ") + val left = spaceAvailable(selection) + highlighted(if (left == Int.MAX_VALUE) "∞" else left.toString()) + literal(" of ") + highlighted("${selection.optimalStack?.name?.string}") + } + + fun description(selection: StackSelection) = + buildText { + text(description) + stock(selection) + } + + override val name: String + get() = buildText { text(description) }.string val shulkerContainer get() = @@ -53,7 +81,7 @@ abstract class MaterialContainer( } class Nothing : Task() { - override val name = "Nothing" + override val name = this::class.simpleName ?: "Nothing" override fun SafeContext.onStart() { success() } @@ -77,23 +105,23 @@ abstract class MaterialContainer( open fun matchingStacks(selection: (ItemStack) -> Boolean) = matchingStacks(selection.select()) - open fun available(selection: StackSelection) = + open fun materialAvailable(selection: StackSelection) = matchingStacks(selection).count - open fun spaceLeft(selection: StackSelection) = + open fun spaceAvailable(selection: StackSelection) = matchingStacks(selection).spaceLeft + stacks.empty * selection.stackSize fun transfer(selection: StackSelection, destination: MaterialContainer): TransferResult { - val amount = available(selection) + val amount = materialAvailable(selection) if (amount < selection.count) { return TransferResult.MissingItems(selection.count - amount) } -// val space = destination.spaceLeft(selection) -// if (space == 0) { -// return TransferResult.NoSpace -// } -// + val space = destination.spaceAvailable(selection) + if (space == 0) { + return TransferResult.NoSpace + } + // val transferAmount = minOf(amount, space) // selection.selector = { true } // selection.count = transferAmount diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/ChestContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/ChestContainer.kt index 1dd6995d2..21f65c341 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/container/ChestContainer.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/container/ChestContainer.kt @@ -23,6 +23,10 @@ import com.lambda.task.tasks.InventoryTask.Companion.deposit import com.lambda.task.tasks.InventoryTask.Companion.withdraw import com.lambda.task.tasks.OpenContainer import com.lambda.util.Communication.info +import com.lambda.util.text.TextBuilder +import com.lambda.util.text.buildText +import com.lambda.util.text.highlighted +import com.lambda.util.text.literal import net.minecraft.item.ItemStack import net.minecraft.screen.ScreenHandler import net.minecraft.util.math.BlockPos @@ -32,7 +36,16 @@ data class ChestContainer( val blockPos: BlockPos, val containedInStash: StashContainer? = null, ) : MaterialContainer(Rank.CHEST) { - override val name = "Chest at ${blockPos.toShortString()}" + override val description = + buildText { + literal("Chest at ") + highlighted(blockPos.toShortString()) + containedInStash?.let { stash -> + literal(" (contained in ") + highlighted(stash.name) + literal(")") + } + } // override fun prepare() = // moveIntoEntityRange(blockPos).onSuccess { _, _ -> diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/CreativeContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/CreativeContainer.kt index 380380239..11bcb0dd7 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/container/CreativeContainer.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/container/CreativeContainer.kt @@ -18,21 +18,30 @@ package com.lambda.interaction.material.container import com.lambda.Lambda.mc +import com.lambda.brigadier.argument.literal import com.lambda.context.SafeContext import com.lambda.interaction.material.MaterialContainer import com.lambda.interaction.material.StackSelection import com.lambda.task.Task import com.lambda.util.item.ItemStackUtils.equal +import com.lambda.util.text.buildText +import com.lambda.util.text.highlighted +import com.lambda.util.text.literal import net.minecraft.item.ItemStack data object CreativeContainer : MaterialContainer(Rank.CREATIVE) { override var stacks = emptyList() - override val name = "Creative" - override fun available(selection: StackSelection): Int = + override val description = + buildText { + literal("Creative") + } + + override fun materialAvailable(selection: StackSelection): Int = if (mc.player?.isCreative == true && selection.optimalStack != null) Int.MAX_VALUE else 0 - override fun spaceLeft(selection: StackSelection) = Int.MAX_VALUE + override fun spaceAvailable(selection: StackSelection): Int = + if (mc.player?.isCreative == true && selection.optimalStack != null) Int.MAX_VALUE else 0 class CreativeDeposit @Ta5kBuilder constructor(val selection: StackSelection) : Task() { override val name: String get() = "Removing $selection from creative inventory" diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/EnderChestContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/EnderChestContainer.kt index 894e1625f..f0b34ce3d 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/container/EnderChestContainer.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/container/EnderChestContainer.kt @@ -22,14 +22,17 @@ import com.lambda.interaction.material.MaterialContainer import com.lambda.interaction.material.StackSelection import com.lambda.task.Task import com.lambda.util.Communication.info +import com.lambda.util.text.buildText +import com.lambda.util.text.literal import net.minecraft.item.ItemStack import net.minecraft.util.math.BlockPos object EnderChestContainer : MaterialContainer(Rank.ENDER_CHEST) { override var stacks = emptyList() - override val name = "EnderChest" private var placePos: BlockPos? = null + override val description = buildText { literal("Ender Chest") } + // override fun prepare(): Task<*> { // TODO("Not yet implemented") // } diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/HotbarContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/HotbarContainer.kt index f731a574a..b68e65902 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/container/HotbarContainer.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/container/HotbarContainer.kt @@ -24,13 +24,16 @@ import com.lambda.interaction.material.MaterialContainer import com.lambda.interaction.material.StackSelection import com.lambda.task.tasks.InventoryTask.Companion.deposit import com.lambda.util.player.SlotUtils.hotbar +import com.lambda.util.text.buildText +import com.lambda.util.text.literal import net.minecraft.item.ItemStack object HotbarContainer : MaterialContainer(Rank.HOTBAR) { override var stacks: List get() = mc.player?.hotbar ?: emptyList() set(_) {} - override val name = "Hotbar" + + override val description = buildText { literal("Hotbar") } class HotbarDeposit @Ta5kBuilder constructor(val selection: StackSelection) : ContainerTask() { override val name: String get() = "Depositing $selection into hotbar" diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/InventoryContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/InventoryContainer.kt index 6b0331e55..690f1f548 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/container/InventoryContainer.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/container/InventoryContainer.kt @@ -22,13 +22,16 @@ import com.lambda.interaction.material.MaterialContainer import com.lambda.interaction.material.StackSelection import com.lambda.task.tasks.InventoryTask import com.lambda.util.player.SlotUtils.combined +import com.lambda.util.text.buildText +import com.lambda.util.text.literal import net.minecraft.item.ItemStack object InventoryContainer : MaterialContainer(Rank.INVENTORY) { override var stacks: List get() = mc.player?.combined ?: emptyList() set(_) {} - override val name = "Inventory" + + override val description = buildText { literal("Inventory") } private val handler get() = mc.player?.currentScreenHandler diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/MainHandContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/MainHandContainer.kt index 1b84b220c..f46506430 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/container/MainHandContainer.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/container/MainHandContainer.kt @@ -24,6 +24,8 @@ import com.lambda.interaction.material.MaterialContainer import com.lambda.interaction.material.StackSelection import com.lambda.util.player.SlotUtils.combined import com.lambda.util.player.SlotUtils.hotbar +import com.lambda.util.text.buildText +import com.lambda.util.text.literal import net.minecraft.item.ItemStack import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket import net.minecraft.util.Hand @@ -34,19 +36,23 @@ object MainHandContainer : MaterialContainer(Rank.MAIN_HAND) { override var stacks: List get() = mc.player?.mainHandStack?.let { listOf(it) } ?: emptyList() set(_) {} - override val name = "MainHand" + + override val description = buildText { literal("MainHand") } class MainHandDeposit @Ta5kBuilder constructor(val selection: StackSelection, val hand: Hand) : ContainerTask() { override val name: String get() = "Depositing $selection to main hand" override fun SafeContext.onStart() { - val moveStack = InventoryContainer.matchingStacks(selection).firstOrNull() ?: return + val moveStack = InventoryContainer.matchingStacks(selection).firstOrNull() ?: run { + success() + return + } val otherHand = if (hand == Hand.MAIN_HAND) Hand.OFF_HAND else Hand.MAIN_HAND val handStack = player.getStackInHand(hand) val otherStack = player.getStackInHand(otherHand) if (ItemStack.areEqual(moveStack, handStack)) { - delayedFinish() + success() return } @@ -58,18 +64,18 @@ object MainHandContainer : MaterialContainer(Rank.MAIN_HAND) { Direction.DOWN, ), ) - delayedFinish() + success() return } if (moveStack in player.hotbar) { player.inventory.selectedSlot = player.hotbar.indexOf(moveStack) - delayedFinish() + success() return } interaction.pickFromInventory(player.combined.indexOf(moveStack)) - delayedFinish() + success() } } diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/OffHandContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/OffHandContainer.kt index be0d2a2db..63915c53c 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/container/OffHandContainer.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/container/OffHandContainer.kt @@ -20,6 +20,8 @@ package com.lambda.interaction.material.container import com.lambda.Lambda.mc import com.lambda.interaction.material.MaterialContainer import com.lambda.interaction.material.StackSelection +import com.lambda.util.text.buildText +import com.lambda.util.text.literal import net.minecraft.item.ItemStack import net.minecraft.util.Hand @@ -27,7 +29,8 @@ object OffHandContainer : MaterialContainer(Rank.OFF_HAND) { override var stacks: List get() = mc.player?.offHandStack?.let { listOf(it) } ?: emptyList() set(_) {} - override val name = "OffHand" + + override val description = buildText { literal("OffHand") } override fun deposit(selection: StackSelection) = MainHandContainer.MainHandDeposit(selection, Hand.OFF_HAND) } diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/ShulkerBoxContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/ShulkerBoxContainer.kt index 0a3ca08d6..7cce8cdcb 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/container/ShulkerBoxContainer.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/container/ShulkerBoxContainer.kt @@ -26,6 +26,9 @@ import com.lambda.task.tasks.InventoryTask.Companion.deposit import com.lambda.task.tasks.InventoryTask.Companion.withdraw import com.lambda.task.tasks.OpenContainer import com.lambda.task.tasks.PlaceContainer +import com.lambda.util.text.buildText +import com.lambda.util.text.highlighted +import com.lambda.util.text.literal import net.minecraft.item.ItemStack data class ShulkerBoxContainer( @@ -33,7 +36,14 @@ data class ShulkerBoxContainer( val containedIn: MaterialContainer, val shulkerStack: ItemStack, ) : MaterialContainer(Rank.SHULKER_BOX) { - override val name = "${shulkerStack.name.string} in slot $slotInContainer in ${containedIn.name}" + override val description = + buildText { + highlighted(shulkerStack.name.string) + literal(" in ") + highlighted(containedIn.name) + literal(" in slot ") + highlighted("$slotInContainer") + } private val slotInContainer: Int get() = containedIn.stacks.indexOf(shulkerStack) diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/StashContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/StashContainer.kt index 91e25e761..75b9de76e 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/container/StashContainer.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/container/StashContainer.kt @@ -19,8 +19,10 @@ package com.lambda.interaction.material.container import com.lambda.interaction.material.MaterialContainer import com.lambda.interaction.material.StackSelection -import com.lambda.task.Task import com.lambda.util.math.VecUtils.blockPos +import com.lambda.util.text.buildText +import com.lambda.util.text.highlighted +import com.lambda.util.text.literal import net.minecraft.item.ItemStack import net.minecraft.util.math.Box @@ -31,10 +33,14 @@ data class StashContainer( override var stacks: List get() = chests.flatMap { it.stacks } set(_) {} - override val name = "Stash at ${pos.center.blockPos.toShortString()}" - override fun available(selection: StackSelection): Int = + override val description = buildText { + literal("Stash at ") + highlighted(pos.center.blockPos.toShortString()) + } + + override fun materialAvailable(selection: StackSelection): Int = chests.sumOf { - it.available(selection) + it.materialAvailable(selection) } } diff --git a/common/src/main/kotlin/com/lambda/interaction/material/transfer/TransferResult.kt b/common/src/main/kotlin/com/lambda/interaction/material/transfer/TransferResult.kt index c395c2e62..32e0637ab 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/transfer/TransferResult.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/transfer/TransferResult.kt @@ -30,7 +30,7 @@ abstract class TransferResult : Task() { val to: MaterialContainer ) : TransferResult() { override val name = "Transfer of [$selection] from [${from.name}] to [${to.name}]" - + override fun SafeContext.onStart() { from.withdraw(selection).then { to.deposit(selection).finally { @@ -38,8 +38,6 @@ abstract class TransferResult : Task() { } }.execute(this@Transfer) } - - override fun toString() = "Transfer of [$selection] from [${from.name}] to [${to.name}]" } data object NoSpace : TransferResult() { diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/RenderSettings.kt b/common/src/main/kotlin/com/lambda/module/modules/client/RenderSettings.kt index 18403baf5..64e1424c2 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/RenderSettings.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/RenderSettings.kt @@ -18,7 +18,9 @@ package com.lambda.module.modules.client import com.lambda.module.Module +import com.lambda.module.modules.client.GuiSettings.Page import com.lambda.module.tag.ModuleTag +import java.awt.Color object RenderSettings : Module( name = "RenderSettings", @@ -34,6 +36,7 @@ object RenderSettings : Module( val gap by setting("Gap", 1.5, -10.0..10.0, 0.5) { page == Page.Font } val baselineOffset by setting("Vertical Offset", 0.0, -10.0..10.0, 0.5) { page == Page.Font } private val lodBiasSetting by setting("Smoothing", 0.0, -10.0..10.0, 0.5) { page == Page.Font } + val highlightColor by setting("Text Highlight Color", Color(100, 100, 100, 150), visibility = { page == Page.Font }) // ESP val uploadsPerTick by setting("Uploads", 16, 1..256, 1, unit = " chunk/tick") { page == Page.ESP } diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/TaskFlowModule.kt b/common/src/main/kotlin/com/lambda/module/modules/client/TaskFlowModule.kt index 4bc999ec8..c8be28557 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/TaskFlowModule.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/TaskFlowModule.kt @@ -19,6 +19,7 @@ package com.lambda.module.modules.client import com.lambda.config.groups.BuildSettings import com.lambda.config.groups.InteractionSettings +import com.lambda.config.groups.InventorySettings import com.lambda.config.groups.RotationSettings import com.lambda.event.events.RenderEvent import com.lambda.event.listener.SafeListener.Companion.listen @@ -35,22 +36,15 @@ object TaskFlowModule : Module( defaultTags = setOf(ModuleTag.CLIENT, ModuleTag.AUTOMATION) ) { enum class Page { - BUILD, ROTATION, INTERACTION, TASKS + BUILD, ROTATION, INTERACTION, INVENTORY } private val page by setting("Page", Page.BUILD) - val build = BuildSettings(this) { - page == Page.BUILD - } - val rotation = RotationSettings(this) { - page == Page.ROTATION - } - val interact = InteractionSettings(this) { - page == Page.INTERACTION - } - val taskCooldown by setting("Task Cooldown", 0, 0..10000, 10, unit = " ms") { - page == Page.TASKS - } + val build = BuildSettings(this) { page == Page.BUILD } + val rotation = RotationSettings(this) { page == Page.ROTATION } + val interact = InteractionSettings(this) { page == Page.INTERACTION } + val inventory = InventorySettings(this) { page == Page.INVENTORY } + val disposables by setting("Disposables", ItemUtils.defaultDisposables) val ignoredBlocks by setting("Ignored Blocks", allSigns) val defaultIgnoreTags = setOf( diff --git a/common/src/main/kotlin/com/lambda/module/modules/debug/InventoryDebug.kt b/common/src/main/kotlin/com/lambda/module/modules/debug/InventoryDebug.kt index dbcf5ad0d..996365570 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/debug/InventoryDebug.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/debug/InventoryDebug.kt @@ -36,10 +36,6 @@ object InventoryDebug : Module( defaultTags = setOf(ModuleTag.DEBUG) ) { init { - listen { - info("Block change at ${it.pos.toShortString()}: ${it.oldState} -> ${it.newState}") - } - listen { info("Opened screen handler: ${it.screenHandler::class.simpleName}") } diff --git a/common/src/main/kotlin/com/lambda/task/tasks/AcquireMaterial.kt b/common/src/main/kotlin/com/lambda/task/tasks/AcquireMaterial.kt index 92e25100a..48d0c721c 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/AcquireMaterial.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/AcquireMaterial.kt @@ -19,7 +19,7 @@ package com.lambda.task.tasks import com.lambda.context.SafeContext import com.lambda.interaction.material.ContainerManager -import com.lambda.interaction.material.ContainerManager.findContainerWithSelection +import com.lambda.interaction.material.ContainerManager.findContainerWithMaterial import com.lambda.interaction.material.StackSelection import com.lambda.task.Task @@ -30,7 +30,7 @@ class AcquireMaterial @Ta5kBuilder constructor( get() = "Acquiring $selection" override fun SafeContext.onStart() { - findContainerWithSelection(selection) + findContainerWithMaterial(selection) ?.withdraw(selection) ?.finally { success(selection) diff --git a/common/src/main/kotlin/com/lambda/task/tasks/PlaceBlock.kt b/common/src/main/kotlin/com/lambda/task/tasks/PlaceBlock.kt index e81a7ba89..fc51f44e2 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/PlaceBlock.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/PlaceBlock.kt @@ -55,7 +55,7 @@ class PlaceBlock @Ta5kBuilder constructor( INIT, PRIME_ROTATION, ROTATING, PLACING, CONFIRMING; fun description() = when (this) { - INIT -> "Placing" + INIT -> "Prepare placing" PRIME_ROTATION -> "Priming rotation" ROTATING -> "Rotating" PLACING -> "Placing" @@ -96,11 +96,11 @@ class PlaceBlock @Ta5kBuilder constructor( if (!event.context.isValid) return@listen when (state) { State.PRIME_ROTATION -> { - if (event.context != primeContext) return@listen + if (event.context.rotation != primeContext?.rotation) return@listen state = State.ROTATING } State.ROTATING -> { - if (event.context != ctx.rotation) return@listen + if (event.context.rotation != ctx.rotation.rotation) return@listen if (!event.context.isValid) return@listen state = State.PLACING diff --git a/common/src/main/kotlin/com/lambda/task/tasks/PlaceContainer.kt b/common/src/main/kotlin/com/lambda/task/tasks/PlaceContainer.kt index 200a5ec16..ce35ec009 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/PlaceContainer.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/PlaceContainer.kt @@ -46,21 +46,21 @@ class PlaceContainer @Ta5kBuilder constructor( .map { it.blockPos } .flatMap { it.blockPos - .toStructure(TargetState.Stack(stack)) + .toStructure(TargetState.Stack(startStack)) .toBlueprint() .simulate(player.getCameraPosVec(mc.tickDelta)) } val succeeds = results.filterIsInstance().filter { - canBeOpened(stack, it.blockPos, it.context.result.side) + canBeOpened(startStack, it.blockPos, it.context.result.side) } val wrongStacks = results.filterIsInstance().filter { - canBeOpened(stack, it.blockPos, it.context.result.side) + canBeOpened(startStack, it.blockPos, it.context.result.side) } (succeeds + wrongStacks).minOrNull()?.let { result -> build { result.blockPos - .toStructure(TargetState.Stack(stack)) + .toStructure(TargetState.Stack(startStack)) .toBlueprint() }.finally { success(result.blockPos) diff --git a/common/src/main/kotlin/com/lambda/util/text/TextDsl.kt b/common/src/main/kotlin/com/lambda/util/text/TextDsl.kt index b611d7dfc..2f7365a3b 100644 --- a/common/src/main/kotlin/com/lambda/util/text/TextDsl.kt +++ b/common/src/main/kotlin/com/lambda/util/text/TextDsl.kt @@ -16,6 +16,7 @@ package com.lambda.util.text +import com.lambda.module.modules.client.RenderSettings import net.minecraft.text.* import net.minecraft.util.Identifier import java.awt.Color @@ -208,6 +209,13 @@ inline fun TextBuilder.color(color: Color?, action: TextBuilder.() -> Unit) { withProp(color, { this.color }, { this.color = it }, action) } +@TextDsl +fun TextBuilder.highlighted(value: String) { + color(RenderSettings.highlightColor) { + literal(value) + } +} + /** * Applies the [TextBuilder] [action] with [bold] set * to the provided value (or enabled if no value given). diff --git a/common/src/main/resources/lambda.accesswidener b/common/src/main/resources/lambda.accesswidener index ec7219d67..0adfa8136 100644 --- a/common/src/main/resources/lambda.accesswidener +++ b/common/src/main/resources/lambda.accesswidener @@ -5,6 +5,7 @@ accessible field net/minecraft/client/MinecraftClient attackCooldown I accessible field net/minecraft/client/MinecraftClient paused Z accessible field net/minecraft/client/MinecraftClient pausedTickDelta F accessible field net/minecraft/client/MinecraftClient thread Ljava/lang/Thread; +accessible field net/minecraft/client/MinecraftClient uptimeInTicks J # World accessible field net/minecraft/client/world/ClientWorld entityManager Lnet/minecraft/client/world/ClientEntityManager; From ecd23e4830814cf7cd1ff05ed759e2c853a2df0b Mon Sep 17 00:00:00 2001 From: Constructor Date: Mon, 30 Dec 2024 20:57:33 +0100 Subject: [PATCH 31/39] Refactor inventory and container structure Reorganize container classes into a dedicated `containers` package for improved modularity and clarity. Introduced new inventory transaction utilities, including `PickFromInventoryTransaction`, `DropItemInHandTransaction`, and `SwapHandsTransaction`, to streamline and enhance inventory management. Updated configurations, task flows, and dependencies to align with this new structure. --- .../ClientPlayNetworkHandlerMixin.java | 41 +++++ .../command/commands/TransferCommand.kt | 18 +-- .../lambda/config/groups/InventoryConfig.kt | 5 +- .../lambda/config/groups/InventorySettings.kt | 3 + .../events/InventoryEvent.kt} | 16 +- .../construction/result/BreakResult.kt | 9 +- .../construction/result/BuildResult.kt | 7 +- .../construction/simulation/BuildSimulator.kt | 2 +- .../construction/verify/TargetState.kt | 2 +- .../{ => container}/ContainerManager.kt | 6 +- .../{ => container}/MaterialContainer.kt | 26 ++-- .../{ => containers}/ChestContainer.kt | 9 +- .../{ => containers}/CreativeContainer.kt | 11 +- .../{ => containers}/EnderChestContainer.kt | 4 +- .../{ => containers}/HotbarContainer.kt | 6 +- .../{ => containers}/InventoryContainer.kt | 12 +- .../{ => containers}/MainHandContainer.kt | 54 +++---- .../{ => containers}/OffHandContainer.kt | 6 +- .../{ => containers}/ShulkerBoxContainer.kt | 8 +- .../{ => containers}/StashContainer.kt | 4 +- .../material/transfer/InventoryChanges.kt | 55 +++++++ .../material/transfer/InventoryTransaction.kt | 42 ++++++ .../material/transfer/InventoryTransfer.kt | 140 ++++++++++++++++++ .../transfer/InventoryTransferTask.kt} | 68 ++++++--- .../material/transfer/TransferResult.kt | 9 +- .../transaction/ClickSlotTransaction.kt | 42 ++++++ .../transaction/DropItemInHandTransaction.kt | 41 +++++ .../PickFromInventoryTransaction.kt | 43 ++++++ .../transaction/SwapHandsTransaction.kt | 47 ++++++ .../transaction/SwapHotbarSlotTransaction.kt | 38 +++++ .../module/modules/client/TaskFlowModule.kt | 1 - .../module/modules/debug/InventoryDebug.kt | 9 +- .../src/main/kotlin/com/lambda/task/Task.kt | 6 +- .../main/kotlin/com/lambda/task/TaskFlow.kt | 2 +- .../com/lambda/task/tasks/AcquireMaterial.kt | 4 +- .../com/lambda/task/tasks/BreakBlock.kt | 2 +- .../com/lambda/util/extension/Screen.kt | 5 +- .../com/lambda/util/player/SlotUtils.kt | 12 -- .../main/resources/lambda.mixins.common.json | 1 + 39 files changed, 650 insertions(+), 166 deletions(-) create mode 100644 common/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java rename common/src/main/kotlin/com/lambda/{interaction/material/transfer/TransferStep.kt => event/events/InventoryEvent.kt} (70%) rename common/src/main/kotlin/com/lambda/interaction/material/{ => container}/ContainerManager.kt (95%) rename common/src/main/kotlin/com/lambda/interaction/material/{ => container}/MaterialContainer.kt (87%) rename common/src/main/kotlin/com/lambda/interaction/material/container/{ => containers}/ChestContainer.kt (89%) rename common/src/main/kotlin/com/lambda/interaction/material/container/{ => containers}/CreativeContainer.kt (92%) rename common/src/main/kotlin/com/lambda/interaction/material/container/{ => containers}/EnderChestContainer.kt (95%) rename common/src/main/kotlin/com/lambda/interaction/material/container/{ => containers}/HotbarContainer.kt (88%) rename common/src/main/kotlin/com/lambda/interaction/material/container/{ => containers}/InventoryContainer.kt (70%) rename common/src/main/kotlin/com/lambda/interaction/material/container/{ => containers}/MainHandContainer.kt (53%) rename common/src/main/kotlin/com/lambda/interaction/material/container/{ => containers}/OffHandContainer.kt (87%) rename common/src/main/kotlin/com/lambda/interaction/material/container/{ => containers}/ShulkerBoxContainer.kt (91%) rename common/src/main/kotlin/com/lambda/interaction/material/container/{ => containers}/StashContainer.kt (92%) create mode 100644 common/src/main/kotlin/com/lambda/interaction/material/transfer/InventoryChanges.kt create mode 100644 common/src/main/kotlin/com/lambda/interaction/material/transfer/InventoryTransaction.kt create mode 100644 common/src/main/kotlin/com/lambda/interaction/material/transfer/InventoryTransfer.kt rename common/src/main/kotlin/com/lambda/{task/tasks/InventoryTask.kt => interaction/material/transfer/InventoryTransferTask.kt} (50%) create mode 100644 common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/ClickSlotTransaction.kt create mode 100644 common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/DropItemInHandTransaction.kt create mode 100644 common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/PickFromInventoryTransaction.kt create mode 100644 common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/SwapHandsTransaction.kt create mode 100644 common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/SwapHotbarSlotTransaction.kt diff --git a/common/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java b/common/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java new file mode 100644 index 000000000..b95b7b8dd --- /dev/null +++ b/common/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java @@ -0,0 +1,41 @@ +/* + * Copyright 2024 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.mixin.network; + +import com.lambda.event.EventFlow; +import com.lambda.event.events.InventoryEvent; +import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.network.packet.s2c.play.ScreenHandlerSlotUpdateS2CPacket; +import net.minecraft.network.packet.s2c.play.UpdateSelectedSlotS2CPacket; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(ClientPlayNetworkHandler.class) +public class ClientPlayNetworkHandlerMixin { + @Inject(method = "onUpdateSelectedSlot", at = @At(value = "TAIL")) + private void onUpdateSelectedSlot(UpdateSelectedSlotS2CPacket packet, CallbackInfo ci) { + EventFlow.post(new InventoryEvent.SelectedSlotUpdate(packet.getSlot())); + } + + @Inject(method = "onScreenHandlerSlotUpdate", at = @At(value = "TAIL")) + private void onScreenHandlerSlotUpdate(ScreenHandlerSlotUpdateS2CPacket packet, CallbackInfo ci) { + EventFlow.post(new InventoryEvent.SlotUpdate(packet.getSyncId(), packet.getRevision(), packet.getSlot(), packet.getStack())); + } +} diff --git a/common/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt b/common/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt index 5e6307df2..35ca83ec6 100644 --- a/common/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt +++ b/common/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt @@ -23,9 +23,9 @@ import com.lambda.brigadier.argument.* import com.lambda.brigadier.executeWithResult import com.lambda.brigadier.required import com.lambda.command.LambdaCommand -import com.lambda.interaction.material.ContainerManager -import com.lambda.interaction.material.ContainerManager.containerWithMaterial -import com.lambda.interaction.material.ContainerManager.containerWithSpace +import com.lambda.interaction.material.container.ContainerManager +import com.lambda.interaction.material.container.ContainerManager.containerWithMaterial +import com.lambda.interaction.material.container.ContainerManager.containerWithSpace import com.lambda.interaction.material.StackSelection.Companion.selectStack import com.lambda.interaction.material.transfer.TransferResult import com.lambda.task.TaskFlow.run @@ -37,7 +37,7 @@ object TransferCommand : LambdaCommand( usage = "transfer ", description = "Transfer items from anywhere to anywhere", ) { - private var lastTransfer: TransferResult.Transfer? = null + private var lastContainerTransfer: TransferResult.ContainerTransfer? = null override fun CommandBuilder.create() { required(itemStack("stack", registry)) { stack -> @@ -76,9 +76,9 @@ object TransferCommand : LambdaCommand( } ?: return@executeWithResult failure("To container not found") when (val transaction = fromContainer.transfer(selection, toContainer)) { - is TransferResult.Transfer -> { + is TransferResult.ContainerTransfer -> { info("${transaction.name} started.") - lastTransfer = transaction + lastContainerTransfer = transaction transaction.finally { info("${transaction.name} completed.") }.run() @@ -103,11 +103,11 @@ object TransferCommand : LambdaCommand( required(literal("cancel")) { executeWithResult { - lastTransfer?.cancel() ?: run { + lastContainerTransfer?.cancel() ?: run { return@executeWithResult failure("No transfer to cancel") } - info("$lastTransfer cancelled") - lastTransfer = null + info("$lastContainerTransfer cancelled") + lastContainerTransfer = null success() } } diff --git a/common/src/main/kotlin/com/lambda/config/groups/InventoryConfig.kt b/common/src/main/kotlin/com/lambda/config/groups/InventoryConfig.kt index 5fdd2eab1..8c12935e0 100644 --- a/common/src/main/kotlin/com/lambda/config/groups/InventoryConfig.kt +++ b/common/src/main/kotlin/com/lambda/config/groups/InventoryConfig.kt @@ -17,10 +17,13 @@ package com.lambda.config.groups -import com.lambda.interaction.material.MaterialContainer +import com.lambda.interaction.material.container.MaterialContainer import com.lambda.interaction.material.StackSelection interface InventoryConfig { + val actionTimout: Int + val swapWithDisposables: Boolean + val providerPriority: Priority val storePriority: Priority diff --git a/common/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt b/common/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt index 1a22ed52c..4e3cf5fdb 100644 --- a/common/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt +++ b/common/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt @@ -23,6 +23,9 @@ class InventorySettings( c: Configurable, vis: () -> Boolean = { true }, ) : InventoryConfig { + override val actionTimout by c.setting("Action Timeout", 10, 0..100, 1, "How long to wait for after each inventory action", " ticks") + override val swapWithDisposables by c.setting("Swap With Disposables", true, "Swap items with disposable ones", vis) + override val providerPriority by c.setting("Provider Priority", InventoryConfig.Priority.WITH_MIN_ITEMS, "What container to prefer when retrieving the item from", vis) override val storePriority by c.setting("Store Priority", InventoryConfig.Priority.WITH_MIN_ITEMS, "What container to prefer when storing the item to", vis) } \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/interaction/material/transfer/TransferStep.kt b/common/src/main/kotlin/com/lambda/event/events/InventoryEvent.kt similarity index 70% rename from common/src/main/kotlin/com/lambda/interaction/material/transfer/TransferStep.kt rename to common/src/main/kotlin/com/lambda/event/events/InventoryEvent.kt index 88d867c58..e9bd8e84e 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/transfer/TransferStep.kt +++ b/common/src/main/kotlin/com/lambda/event/events/InventoryEvent.kt @@ -15,14 +15,12 @@ * along with this program. If not, see . */ -package com.lambda.interaction.material.transfer +package com.lambda.event.events -import com.lambda.task.Task +import com.lambda.event.Event +import net.minecraft.item.ItemStack -abstract class TransferStep : Task() { - - - companion object { - - } -} +sealed class InventoryEvent { + class SelectedSlotUpdate(val slot: Int) : Event + class SlotUpdate(val syncId: Int, val revision: Int, val slot: Int, val stack: ItemStack) : Event +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/result/BreakResult.kt b/common/src/main/kotlin/com/lambda/interaction/construction/result/BreakResult.kt index f5b20fd1d..39c42481c 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/result/BreakResult.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/result/BreakResult.kt @@ -21,13 +21,12 @@ import baritone.api.pathing.goals.GoalBlock import baritone.api.pathing.goals.GoalInverted import com.lambda.context.SafeContext import com.lambda.interaction.construction.context.BreakContext -import com.lambda.interaction.material.ContainerManager.findBestAvailableTool -import com.lambda.interaction.material.ContainerManager.transfer -import com.lambda.interaction.material.MaterialContainer +import com.lambda.interaction.material.container.ContainerManager.findBestAvailableTool +import com.lambda.interaction.material.container.ContainerManager.transfer +import com.lambda.interaction.material.container.MaterialContainer import com.lambda.interaction.material.StackSelection.Companion.select import com.lambda.interaction.material.StackSelection.Companion.selectStack -import com.lambda.interaction.material.container.MainHandContainer -import com.lambda.task.Task +import com.lambda.interaction.material.container.containers.MainHandContainer import com.lambda.task.tasks.BreakBlock import net.minecraft.block.BlockState import net.minecraft.item.Item diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/result/BuildResult.kt b/common/src/main/kotlin/com/lambda/interaction/construction/result/BuildResult.kt index 4eecc096b..8954f1699 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/result/BuildResult.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/result/BuildResult.kt @@ -21,11 +21,10 @@ import baritone.api.pathing.goals.GoalBlock import baritone.api.pathing.goals.GoalNear import com.lambda.context.SafeContext import com.lambda.interaction.construction.context.BuildContext -import com.lambda.interaction.material.ContainerManager.transfer -import com.lambda.interaction.material.MaterialContainer +import com.lambda.interaction.material.container.ContainerManager.transfer +import com.lambda.interaction.material.container.MaterialContainer import com.lambda.interaction.material.StackSelection.Companion.select -import com.lambda.interaction.material.container.MainHandContainer -import com.lambda.task.Task +import com.lambda.interaction.material.container.containers.MainHandContainer import com.lambda.util.BlockUtils.blockState import com.lambda.util.Nameable import net.minecraft.block.BlockState diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt index 8e0445f9b..397068ab1 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt @@ -27,7 +27,7 @@ import com.lambda.interaction.construction.result.BreakResult import com.lambda.interaction.construction.result.BuildResult import com.lambda.interaction.construction.result.PlaceResult import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.material.ContainerManager.findBestAvailableTool +import com.lambda.interaction.material.container.ContainerManager.findBestAvailableTool import com.lambda.interaction.rotation.Rotation.Companion.rotation import com.lambda.interaction.rotation.Rotation.Companion.rotationTo import com.lambda.interaction.rotation.RotationContext diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt b/common/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt index f78a16d8d..c2f21b93a 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt @@ -17,7 +17,7 @@ package com.lambda.interaction.construction.verify -import com.lambda.interaction.material.ContainerManager.findDisposable +import com.lambda.interaction.material.container.ContainerManager.findDisposable import com.lambda.module.modules.client.TaskFlowModule import com.lambda.util.BlockUtils.blockState import com.lambda.util.StringUtils.capitalize diff --git a/common/src/main/kotlin/com/lambda/interaction/material/ContainerManager.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/ContainerManager.kt similarity index 95% rename from common/src/main/kotlin/com/lambda/interaction/material/ContainerManager.kt rename to common/src/main/kotlin/com/lambda/interaction/material/container/ContainerManager.kt index 4d04e4334..161010c41 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/ContainerManager.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/container/ContainerManager.kt @@ -15,14 +15,16 @@ * along with this program. If not, see . */ -package com.lambda.interaction.material +package com.lambda.interaction.material.container import com.lambda.core.Loadable import com.lambda.event.events.PlayerEvent import com.lambda.event.events.ScreenHandlerEvent import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.material.StackSelection import com.lambda.interaction.material.StackSelection.Companion.select -import com.lambda.interaction.material.container.* +import com.lambda.interaction.material.container.containers.ChestContainer +import com.lambda.interaction.material.container.containers.EnderChestContainer import com.lambda.module.modules.client.TaskFlowModule import com.lambda.util.BlockUtils.blockEntity import com.lambda.util.BlockUtils.item diff --git a/common/src/main/kotlin/com/lambda/interaction/material/MaterialContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/MaterialContainer.kt similarity index 87% rename from common/src/main/kotlin/com/lambda/interaction/material/MaterialContainer.kt rename to common/src/main/kotlin/com/lambda/interaction/material/container/MaterialContainer.kt index 51886997d..312b19ef3 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/MaterialContainer.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/container/MaterialContainer.kt @@ -15,11 +15,11 @@ * along with this program. If not, see . */ -package com.lambda.interaction.material +package com.lambda.interaction.material.container import com.lambda.context.SafeContext -import com.lambda.interaction.material.StackSelection.Companion.select -import com.lambda.interaction.material.container.ShulkerBoxContainer +import com.lambda.interaction.material.StackSelection +import com.lambda.interaction.material.container.containers.ShulkerBoxContainer import com.lambda.interaction.material.transfer.TransferResult import com.lambda.task.Task import com.lambda.util.Nameable @@ -80,8 +80,7 @@ abstract class MaterialContainer( this.stacks = stacks } - class Nothing : Task() { - override val name = this::class.simpleName ?: "Nothing" + class Nothing(override val name: String = "Nothing") : Task() { override fun SafeContext.onStart() { success() } @@ -91,20 +90,17 @@ abstract class MaterialContainer( * Withdraws items from the container to the player's inventory. */ @Task.Ta5kBuilder - open fun withdraw(selection: StackSelection): Task<*> = Nothing() + open fun withdraw(selection: StackSelection): Task<*> = Nothing(name) /** * Deposits items from the player's inventory into the container. */ @Task.Ta5kBuilder - open fun deposit(selection: StackSelection): Task<*> = Nothing() + open fun deposit(selection: StackSelection): Task<*> = Nothing(name) open fun matchingStacks(selection: StackSelection) = selection.filterStacks(stacks) - open fun matchingStacks(selection: (ItemStack) -> Boolean) = - matchingStacks(selection.select()) - open fun materialAvailable(selection: StackSelection) = matchingStacks(selection).count @@ -117,16 +113,16 @@ abstract class MaterialContainer( return TransferResult.MissingItems(selection.count - amount) } - val space = destination.spaceAvailable(selection) - if (space == 0) { - return TransferResult.NoSpace - } +// val space = destination.spaceAvailable(selection) +// if (space == 0) { +// return TransferResult.NoSpace +// } // val transferAmount = minOf(amount, space) // selection.selector = { true } // selection.count = transferAmount - return TransferResult.Transfer(selection, from = this, to = destination) + return TransferResult.ContainerTransfer(selection, from = this, to = destination) } enum class Rank { diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/ChestContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/containers/ChestContainer.kt similarity index 89% rename from common/src/main/kotlin/com/lambda/interaction/material/container/ChestContainer.kt rename to common/src/main/kotlin/com/lambda/interaction/material/container/containers/ChestContainer.kt index 21f65c341..fee5061ba 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/container/ChestContainer.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/container/containers/ChestContainer.kt @@ -15,15 +15,14 @@ * along with this program. If not, see . */ -package com.lambda.interaction.material.container +package com.lambda.interaction.material.container.containers -import com.lambda.interaction.material.MaterialContainer import com.lambda.interaction.material.StackSelection -import com.lambda.task.tasks.InventoryTask.Companion.deposit -import com.lambda.task.tasks.InventoryTask.Companion.withdraw +import com.lambda.interaction.material.transfer.InventoryTransferTask.Companion.deposit +import com.lambda.interaction.material.transfer.InventoryTransferTask.Companion.withdraw +import com.lambda.interaction.material.container.MaterialContainer import com.lambda.task.tasks.OpenContainer import com.lambda.util.Communication.info -import com.lambda.util.text.TextBuilder import com.lambda.util.text.buildText import com.lambda.util.text.highlighted import com.lambda.util.text.literal diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/CreativeContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/containers/CreativeContainer.kt similarity index 92% rename from common/src/main/kotlin/com/lambda/interaction/material/container/CreativeContainer.kt rename to common/src/main/kotlin/com/lambda/interaction/material/container/containers/CreativeContainer.kt index 11bcb0dd7..52683117a 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/container/CreativeContainer.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/container/containers/CreativeContainer.kt @@ -15,27 +15,22 @@ * along with this program. If not, see . */ -package com.lambda.interaction.material.container +package com.lambda.interaction.material.container.containers import com.lambda.Lambda.mc -import com.lambda.brigadier.argument.literal import com.lambda.context.SafeContext -import com.lambda.interaction.material.MaterialContainer import com.lambda.interaction.material.StackSelection +import com.lambda.interaction.material.container.MaterialContainer import com.lambda.task.Task import com.lambda.util.item.ItemStackUtils.equal import com.lambda.util.text.buildText -import com.lambda.util.text.highlighted import com.lambda.util.text.literal import net.minecraft.item.ItemStack data object CreativeContainer : MaterialContainer(Rank.CREATIVE) { override var stacks = emptyList() - override val description = - buildText { - literal("Creative") - } + override val description = buildText { literal("Creative") } override fun materialAvailable(selection: StackSelection): Int = if (mc.player?.isCreative == true && selection.optimalStack != null) Int.MAX_VALUE else 0 diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/EnderChestContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/containers/EnderChestContainer.kt similarity index 95% rename from common/src/main/kotlin/com/lambda/interaction/material/container/EnderChestContainer.kt rename to common/src/main/kotlin/com/lambda/interaction/material/container/containers/EnderChestContainer.kt index f0b34ce3d..398549eac 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/container/EnderChestContainer.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/container/containers/EnderChestContainer.kt @@ -15,11 +15,11 @@ * along with this program. If not, see . */ -package com.lambda.interaction.material.container +package com.lambda.interaction.material.container.containers import com.lambda.context.SafeContext -import com.lambda.interaction.material.MaterialContainer import com.lambda.interaction.material.StackSelection +import com.lambda.interaction.material.container.MaterialContainer import com.lambda.task.Task import com.lambda.util.Communication.info import com.lambda.util.text.buildText diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/HotbarContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/containers/HotbarContainer.kt similarity index 88% rename from common/src/main/kotlin/com/lambda/interaction/material/container/HotbarContainer.kt rename to common/src/main/kotlin/com/lambda/interaction/material/container/containers/HotbarContainer.kt index b68e65902..b3db7074e 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/container/HotbarContainer.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/container/containers/HotbarContainer.kt @@ -15,14 +15,14 @@ * along with this program. If not, see . */ -package com.lambda.interaction.material.container +package com.lambda.interaction.material.container.containers import com.lambda.Lambda.mc import com.lambda.context.SafeContext import com.lambda.interaction.material.ContainerTask -import com.lambda.interaction.material.MaterialContainer import com.lambda.interaction.material.StackSelection -import com.lambda.task.tasks.InventoryTask.Companion.deposit +import com.lambda.interaction.material.transfer.InventoryTransferTask.Companion.deposit +import com.lambda.interaction.material.container.MaterialContainer import com.lambda.util.player.SlotUtils.hotbar import com.lambda.util.text.buildText import com.lambda.util.text.literal diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/InventoryContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/containers/InventoryContainer.kt similarity index 70% rename from common/src/main/kotlin/com/lambda/interaction/material/container/InventoryContainer.kt rename to common/src/main/kotlin/com/lambda/interaction/material/container/containers/InventoryContainer.kt index 690f1f548..928bbd113 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/container/InventoryContainer.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/container/containers/InventoryContainer.kt @@ -15,12 +15,10 @@ * along with this program. If not, see . */ -package com.lambda.interaction.material.container +package com.lambda.interaction.material.container.containers import com.lambda.Lambda.mc -import com.lambda.interaction.material.MaterialContainer -import com.lambda.interaction.material.StackSelection -import com.lambda.task.tasks.InventoryTask +import com.lambda.interaction.material.container.MaterialContainer import com.lambda.util.player.SlotUtils.combined import com.lambda.util.text.buildText import com.lambda.util.text.literal @@ -32,10 +30,4 @@ object InventoryContainer : MaterialContainer(Rank.INVENTORY) { set(_) {} override val description = buildText { literal("Inventory") } - - private val handler get() = mc.player?.currentScreenHandler - - override fun withdraw(selection: StackSelection) = InventoryTask.withdraw(handler!!, selection) - - override fun deposit(selection: StackSelection) = InventoryTask.deposit(handler!!, selection) } diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/MainHandContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/containers/MainHandContainer.kt similarity index 53% rename from common/src/main/kotlin/com/lambda/interaction/material/container/MainHandContainer.kt rename to common/src/main/kotlin/com/lambda/interaction/material/container/containers/MainHandContainer.kt index f46506430..d093b9024 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/container/MainHandContainer.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/container/containers/MainHandContainer.kt @@ -15,22 +15,22 @@ * along with this program. If not, see . */ -package com.lambda.interaction.material.container +package com.lambda.interaction.material.container.containers import com.lambda.Lambda.mc import com.lambda.context.SafeContext import com.lambda.interaction.material.ContainerTask -import com.lambda.interaction.material.MaterialContainer +import com.lambda.interaction.material.transfer.InventoryTransfer.Companion.transfer import com.lambda.interaction.material.StackSelection +import com.lambda.interaction.material.container.MaterialContainer +import com.lambda.util.item.ItemStackUtils.equal import com.lambda.util.player.SlotUtils.combined import com.lambda.util.player.SlotUtils.hotbar +import com.lambda.util.player.SlotUtils.storage import com.lambda.util.text.buildText import com.lambda.util.text.literal import net.minecraft.item.ItemStack -import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket import net.minecraft.util.Hand -import net.minecraft.util.math.BlockPos -import net.minecraft.util.math.Direction object MainHandContainer : MaterialContainer(Rank.MAIN_HAND) { override var stacks: List @@ -39,45 +39,39 @@ object MainHandContainer : MaterialContainer(Rank.MAIN_HAND) { override val description = buildText { literal("MainHand") } - class MainHandDeposit @Ta5kBuilder constructor(val selection: StackSelection, val hand: Hand) : ContainerTask() { - override val name: String get() = "Depositing $selection to main hand" + class HandDeposit @Ta5kBuilder constructor(val selection: StackSelection, val hand: Hand) : ContainerTask() { + override val name: String get() = "Depositing [$selection] to ${hand.name.lowercase().replace("_", " ")}" override fun SafeContext.onStart() { val moveStack = InventoryContainer.matchingStacks(selection).firstOrNull() ?: run { - success() + failure("No matching stacks found in inventory") return } - val otherHand = if (hand == Hand.MAIN_HAND) Hand.OFF_HAND else Hand.MAIN_HAND val handStack = player.getStackInHand(hand) - val otherStack = player.getStackInHand(otherHand) - if (ItemStack.areEqual(moveStack, handStack)) { + if (moveStack.equal(handStack)) { success() return } - if (ItemStack.areEqual(moveStack, otherStack)) { - connection.sendPacket( - PlayerActionC2SPacket( - PlayerActionC2SPacket.Action.SWAP_ITEM_WITH_OFFHAND, - BlockPos.ORIGIN, - Direction.DOWN, - ), - ) - success() - return - } + transfer { + val stackInOffHand = moveStack.equal(player.offHandStack) + if (hand == Hand.MAIN_HAND && stackInOffHand) { + swapHands() + return@transfer + } - if (moveStack in player.hotbar) { - player.inventory.selectedSlot = player.hotbar.indexOf(moveStack) - success() - return - } + when (moveStack) { + in player.hotbar -> swapToHotbarSlot(player.hotbar.indexOf(moveStack)) + in player.storage -> pickFromInventory(player.combined.indexOf(moveStack)) + } - interaction.pickFromInventory(player.combined.indexOf(moveStack)) - success() + if (hand == Hand.OFF_HAND) swapHands() + }.finally { + success() + }.execute(this@HandDeposit) } } - override fun deposit(selection: StackSelection) = MainHandDeposit(selection, Hand.MAIN_HAND) + override fun deposit(selection: StackSelection) = HandDeposit(selection, Hand.MAIN_HAND) } diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/OffHandContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/containers/OffHandContainer.kt similarity index 87% rename from common/src/main/kotlin/com/lambda/interaction/material/container/OffHandContainer.kt rename to common/src/main/kotlin/com/lambda/interaction/material/container/containers/OffHandContainer.kt index 63915c53c..d19a7a0a6 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/container/OffHandContainer.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/container/containers/OffHandContainer.kt @@ -15,11 +15,11 @@ * along with this program. If not, see . */ -package com.lambda.interaction.material.container +package com.lambda.interaction.material.container.containers import com.lambda.Lambda.mc -import com.lambda.interaction.material.MaterialContainer import com.lambda.interaction.material.StackSelection +import com.lambda.interaction.material.container.MaterialContainer import com.lambda.util.text.buildText import com.lambda.util.text.literal import net.minecraft.item.ItemStack @@ -32,5 +32,5 @@ object OffHandContainer : MaterialContainer(Rank.OFF_HAND) { override val description = buildText { literal("OffHand") } - override fun deposit(selection: StackSelection) = MainHandContainer.MainHandDeposit(selection, Hand.OFF_HAND) + override fun deposit(selection: StackSelection) = MainHandContainer.HandDeposit(selection, Hand.OFF_HAND) } diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/ShulkerBoxContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/containers/ShulkerBoxContainer.kt similarity index 91% rename from common/src/main/kotlin/com/lambda/interaction/material/container/ShulkerBoxContainer.kt rename to common/src/main/kotlin/com/lambda/interaction/material/container/containers/ShulkerBoxContainer.kt index 7cce8cdcb..7382d60e3 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/container/ShulkerBoxContainer.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/container/containers/ShulkerBoxContainer.kt @@ -15,15 +15,15 @@ * along with this program. If not, see . */ -package com.lambda.interaction.material.container +package com.lambda.interaction.material.container.containers import com.lambda.context.SafeContext -import com.lambda.interaction.material.MaterialContainer import com.lambda.interaction.material.StackSelection import com.lambda.task.Task import com.lambda.task.tasks.BuildTask.Companion.breakAndCollectBlock -import com.lambda.task.tasks.InventoryTask.Companion.deposit -import com.lambda.task.tasks.InventoryTask.Companion.withdraw +import com.lambda.interaction.material.transfer.InventoryTransferTask.Companion.deposit +import com.lambda.interaction.material.transfer.InventoryTransferTask.Companion.withdraw +import com.lambda.interaction.material.container.MaterialContainer import com.lambda.task.tasks.OpenContainer import com.lambda.task.tasks.PlaceContainer import com.lambda.util.text.buildText diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/StashContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/containers/StashContainer.kt similarity index 92% rename from common/src/main/kotlin/com/lambda/interaction/material/container/StashContainer.kt rename to common/src/main/kotlin/com/lambda/interaction/material/container/containers/StashContainer.kt index 75b9de76e..647f2e7a5 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/container/StashContainer.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/container/containers/StashContainer.kt @@ -15,10 +15,10 @@ * along with this program. If not, see . */ -package com.lambda.interaction.material.container +package com.lambda.interaction.material.container.containers -import com.lambda.interaction.material.MaterialContainer import com.lambda.interaction.material.StackSelection +import com.lambda.interaction.material.container.MaterialContainer import com.lambda.util.math.VecUtils.blockPos import com.lambda.util.text.buildText import com.lambda.util.text.highlighted diff --git a/common/src/main/kotlin/com/lambda/interaction/material/transfer/InventoryChanges.kt b/common/src/main/kotlin/com/lambda/interaction/material/transfer/InventoryChanges.kt new file mode 100644 index 000000000..00c80199a --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/material/transfer/InventoryChanges.kt @@ -0,0 +1,55 @@ +package com.lambda.interaction.material.transfer + +import com.lambda.context.SafeContext +import com.lambda.interaction.material.StackSelection +import net.minecraft.item.ItemStack +import net.minecraft.screen.slot.Slot + +class InventoryChanges( + ctx: SafeContext +) : MutableMap>> by HashMap() { + + private var slots = ctx.player.currentScreenHandler.slots + private val originalStacks = slots.map { it.stack.copy() } // Snapshot of the initial state + + /** + * Detect and store changes directly in the map. + */ + fun detectChanges() { + slots.forEachIndexed { index, slot -> + val originalStack = originalStacks[index] + val updatedStack = slot.stack + if (!ItemStack.areEqual(originalStack, updatedStack)) { + getOrPut(index) { mutableListOf() }.add(originalStack to updatedStack.copy()) + } + } + } + + /** + * Create a new `InventoryChanges` object that merges this changes object with another one. + * + * @param other Another `InventoryChanges` instance to merge with. + * @return A new `InventoryChanges` instance containing merged entries. + */ + infix fun merge(other: InventoryChanges) { + other.forEach { (key, value) -> + getOrPut(key) { mutableListOf() }.addAll(value) + } + } + + fun fulfillsSelection(to: List, selection: StackSelection): Boolean { + val targetSlots = selection.filterSlots(to).map { it.id } + return filter { it.key in targetSlots }.entries.sumOf { (_, changes) -> + changes.lastOrNull()?.second?.count ?: 0 + } >= selection.count + } + + override fun toString() = + if (entries.isEmpty()) { + "No changes detected" + } else { + entries.joinToString("\n") { key -> + "#${key.key}: ${key.value.joinToString { "${it.first} -> ${it.second}" }}" + } + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/interaction/material/transfer/InventoryTransaction.kt b/common/src/main/kotlin/com/lambda/interaction/material/transfer/InventoryTransaction.kt new file mode 100644 index 000000000..f4954edb7 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/material/transfer/InventoryTransaction.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2024 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.material.transfer + +import com.lambda.context.SafeContext +import com.lambda.task.Task +import com.lambda.threading.runSafe +import com.lambda.util.Communication.info + +abstract class InventoryTransaction : Task() { + private var changes: InventoryChanges? = null + + override fun SafeContext.onStart() { + changes = InventoryChanges(this) + } + + fun finish() { + runSafe { + changes?.let { + it.detectChanges() + info("Changes: $it") + success(it) + } + } ?: failure("Failed to finish transaction") + } + +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/interaction/material/transfer/InventoryTransfer.kt b/common/src/main/kotlin/com/lambda/interaction/material/transfer/InventoryTransfer.kt new file mode 100644 index 000000000..f974c6302 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/material/transfer/InventoryTransfer.kt @@ -0,0 +1,140 @@ +/* + * Copyright 2024 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.material.transfer + +import com.lambda.context.SafeContext +import com.lambda.event.events.TickEvent +import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.material.transfer.transaction.* +import com.lambda.task.Task +import net.minecraft.screen.slot.SlotActionType + +class InventoryTransfer @Ta5kBuilder constructor() : Task() { + override val name: String get() = "Inventory Transfer" + + @DslMarker + annotation class InvTransfer + + private var transactions = mutableListOf() + private var changes: InventoryChanges? = null + + override fun SafeContext.onStart() { + changes = InventoryChanges(this) + } + + init { + listen { + if (transactions.isEmpty()) { + changes?.let { + success(it) + } + return@listen + } + + transactions.removeFirstOrNull()?.finally { change -> + changes?.merge(change) + }?.execute(this@InventoryTransfer) + } + } + + @InvTransfer + fun click(slotId: Int, button: Int, actionType: SlotActionType) { + transactions.add(ClickSlotTransaction(slotId, button, actionType)) + } + + @InvTransfer + fun pickFromInventory(slotId: Int) { + transactions.add(PickFromInventoryTransaction(slotId)) + } + + @InvTransfer + fun dropItemInHand(entireStack: Boolean = true) { + transactions.add(DropItemInHandTransaction(entireStack)) + } + + @InvTransfer + fun swapHands() { + transactions.add(SwapHandsTransaction()) + } + + @InvTransfer + fun swapToHotbarSlot(slotId: Int) { + transactions.add(SwapHotbarSlotTransaction(slotId)) + } + + @InvTransfer + fun pickup(slotId: Int, button: Int = 0) = click(slotId, button, SlotActionType.PICKUP) + + // Quick move action (Shift-click) + @InvTransfer + fun quickMove(slotId: Int) = click(slotId, 0, SlotActionType.QUICK_MOVE) + + @InvTransfer + fun swap(slotId: Int, hotbarSlot: Int) = click(slotId, hotbarSlot, SlotActionType.SWAP) + + // Clone action (Creative mode) + @InvTransfer + fun clone(slotId: Int) = click(slotId, 2, SlotActionType.CLONE) + + // Throw stack or single item + @InvTransfer + fun throwStack(slotId: Int) = click(slotId, 1, SlotActionType.THROW) + @InvTransfer + fun throwSingle(slotId: Int) = click(slotId, 0, SlotActionType.THROW) + + // Quick craft action + @InvTransfer + fun quickCraftStart(slotId: Int) = click(slotId, 0, SlotActionType.QUICK_CRAFT) + @InvTransfer + fun quickCraftDrag(slotId: Int) = click(slotId, 1, SlotActionType.QUICK_CRAFT) + @InvTransfer + fun quickCraftEnd(slotId: Int) = click(slotId, 2, SlotActionType.QUICK_CRAFT) + + // Pickup all items (double-click) + @InvTransfer + fun pickupAll(slotId: Int) = click(slotId, 0, SlotActionType.PICKUP_ALL) + + // Helper function: Move items from one slot to another + @InvTransfer + fun moveSlot(fromSlotId: Int, toSlotId: Int, button: Int = 0) { + pickup(fromSlotId, button) + pickup(toSlotId, button) + } + + // Helper function: Split a stack into two + @InvTransfer + fun splitStack(slotId: Int, targetSlotId: Int) { + pickup(slotId, 1) // Pickup half the stack + pickup(targetSlotId, 0) // Place it in the target slot + } + + // Helper function: Merge stacks + @InvTransfer + fun mergeStacks(sourceSlotId: Int, targetSlotId: Int) { + pickup(sourceSlotId, 0) + pickup(targetSlotId, 0) + } + + companion object { + @InvTransfer + fun transfer(block: InventoryTransfer.() -> Unit) = + InventoryTransfer().apply { + block(this) + } + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/task/tasks/InventoryTask.kt b/common/src/main/kotlin/com/lambda/interaction/material/transfer/InventoryTransferTask.kt similarity index 50% rename from common/src/main/kotlin/com/lambda/task/tasks/InventoryTask.kt rename to common/src/main/kotlin/com/lambda/interaction/material/transfer/InventoryTransferTask.kt index 00727fc6a..edc3951c5 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/InventoryTask.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/transfer/InventoryTransferTask.kt @@ -15,59 +15,79 @@ * along with this program. If not, see . */ -package com.lambda.task.tasks +package com.lambda.interaction.material.transfer +import com.lambda.Lambda.LOG +import com.lambda.config.groups.InventoryConfig import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.material.StackSelection +import com.lambda.interaction.material.transfer.InventoryTransfer.Companion.transfer import com.lambda.module.modules.client.TaskFlowModule import com.lambda.task.Task import com.lambda.util.extension.containerSlots import com.lambda.util.extension.inventorySlots -import com.lambda.util.item.ItemUtils.block -import com.lambda.util.player.SlotUtils import net.minecraft.screen.ScreenHandler import net.minecraft.screen.slot.Slot -import net.minecraft.screen.slot.SlotActionType -class InventoryTask @Ta5kBuilder constructor( +class InventoryTransferTask @Ta5kBuilder constructor( val screen: ScreenHandler, private val selection: StackSelection, val from: List, val to: List, - private val closeScreen: Boolean = true + private val closeScreen: Boolean = true, + private val settings: InventoryConfig = TaskFlowModule.inventory ) : Task() { + private var selectedFrom = selection.filterSlots(from) + private var selectedTo = to.filter { it.stack.isEmpty } // + to.filter { it.stack.item.block in TaskFlowModule.disposables } + private val screenName = runCatching { screen.type::class.simpleName }.getOrNull() ?: screen::class.simpleName override val name: String - get() = "Moving $selection from [${from.joinToString { "${it.id}" }}] to [${from.joinToString { "${it.id}" }}] in ${runCatching { screen.type::class.simpleName }.getOrNull() ?: screen::class.simpleName}" + get() = "Moving $selection from [${selectedFrom.joinToString { "${it.id}" }}] to [${selectedTo.joinToString { "${it.id}" }}] in $screenName" - private val transactions = mutableListOf() + private var delay = 0 + private var changes: InventoryChanges? = null override fun SafeContext.onStart() { - val selectedFrom = selection.filterSlots(from).filter { it.hasStack() } - val selectedTo = to.filter { it.stack.isEmpty } + to.filter { it.stack.item.block in TaskFlowModule.disposables } - selectedFrom.zip(selectedTo).forEach { (from, to) -> - transactions.add(SlotUtils.Transaction(to.index, 0, SlotActionType.SWAP)) - transactions.add(SlotUtils.Transaction(from.index, 0, SlotActionType.SWAP)) - - // ToDo: Handle overflow of cursor for PICKUP - } + changes = InventoryChanges(this) } init { - // ToDo: Needs smart code to move as efficient as possible. - // Also should handle overflow etc. Should be more generic listen { - val moved = selection.filterSlots(to) - .filter { it.hasStack() } - .sumOf { it.stack.count } >= selection.count + if (player.currentScreenHandler != screen) { + failure("Screen has changed") + return@listen + } - if (transactions.isEmpty() || moved) { + if (changes?.fulfillsSelection(to, selection) == true) { if (closeScreen) player.closeHandledScreen() success() + return@listen } - transactions.removeFirstOrNull()?.click() ?: success() + if (--delay >= 0) return@listen + delay = settings.actionTimout + + selectedFrom = selection.filterSlots(from) + selectedTo = to.filter { it.stack.isEmpty } // + to.filter { it.stack.item.block in TaskFlowModule.disposables } + + val nextFrom = selectedFrom.firstOrNull() ?: return@listen + val nextTo = selectedTo.firstOrNull() ?: return@listen + + LOG.info("Changes so far:\n$changes") + + transfer { + moveSlot(nextFrom.id, nextTo.id) + }.finally { change -> + changes?.merge(change) + }.execute(this@InventoryTransferTask) + +// if (transfer.fulfillsSelection(selection)) { +// info("Transfer complete") +//// success() +//// if (closeScreen) player.closeHandledScreen() +// return@listen +// } } } @@ -79,7 +99,7 @@ class InventoryTask @Ta5kBuilder constructor( from: List, to: List, closeScreen: Boolean = true - ) = InventoryTask(screen, selection, from, to, closeScreen) + ) = InventoryTransferTask(screen, selection, from, to, closeScreen) @Ta5kBuilder fun withdraw(screen: ScreenHandler, selection: StackSelection, closeScreen: Boolean = true) = diff --git a/common/src/main/kotlin/com/lambda/interaction/material/transfer/TransferResult.kt b/common/src/main/kotlin/com/lambda/interaction/material/transfer/TransferResult.kt index 32e0637ab..a64f961e9 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/transfer/TransferResult.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/transfer/TransferResult.kt @@ -18,25 +18,24 @@ package com.lambda.interaction.material.transfer import com.lambda.context.SafeContext -import com.lambda.interaction.material.MaterialContainer +import com.lambda.interaction.material.container.MaterialContainer import com.lambda.interaction.material.StackSelection import com.lambda.task.Task -import com.lambda.util.Communication.info abstract class TransferResult : Task() { - data class Transfer( + data class ContainerTransfer( val selection: StackSelection, val from: MaterialContainer, val to: MaterialContainer ) : TransferResult() { - override val name = "Transfer of [$selection] from [${from.name}] to [${to.name}]" + override val name = "Container Transfer of [$selection] from [${from.name}] to [${to.name}]" override fun SafeContext.onStart() { from.withdraw(selection).then { to.deposit(selection).finally { success() } - }.execute(this@Transfer) + }.execute(this@ContainerTransfer) } } diff --git a/common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/ClickSlotTransaction.kt b/common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/ClickSlotTransaction.kt new file mode 100644 index 000000000..5c4ff8af8 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/ClickSlotTransaction.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2024 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.material.transfer.transaction + +import com.lambda.event.events.TickEvent +import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.material.transfer.InventoryTransaction +import com.lambda.util.player.SlotUtils.clickSlot +import net.minecraft.screen.slot.SlotActionType + +class ClickSlotTransaction @Ta5kBuilder constructor( + private val slotId: Int, + private val button: Int, + private val actionType: SlotActionType +) : InventoryTransaction() { + override val name: String get() = "Click slot #$slotId with action $actionType and button $button" + + init { + listen { + clickSlot(slotId, button, actionType) + } + + listen { + finish() + } + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/DropItemInHandTransaction.kt b/common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/DropItemInHandTransaction.kt new file mode 100644 index 000000000..e480c6748 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/DropItemInHandTransaction.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2024 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.material.transfer.transaction + +import com.lambda.event.events.TickEvent +import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.material.transfer.InventoryTransaction +import net.minecraft.util.Hand + +class DropItemInHandTransaction @Ta5kBuilder constructor( + private val entireStack: Boolean = false +) : InventoryTransaction() { + override val name: String get() = "Dropping ${if (entireStack) "stack" else "item"} in hand" + + init { + listen { + if (!player.isSpectator && player.dropSelectedItem(entireStack)) { + player.swingHand(Hand.MAIN_HAND) + } + } + + listen { + finish() + } + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/PickFromInventoryTransaction.kt b/common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/PickFromInventoryTransaction.kt new file mode 100644 index 000000000..3b9cae831 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/PickFromInventoryTransaction.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2024 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.material.transfer.transaction + +import com.lambda.event.events.InventoryEvent +import com.lambda.event.events.TickEvent +import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.material.transfer.InventoryTransaction + +class PickFromInventoryTransaction @Ta5kBuilder constructor( + val slot: Int, +) : InventoryTransaction() { + override val name: String get() = "Picking from slot #$slot" + private var confirming = false + + init { + listen { + if (confirming) return@listen + + interaction.pickFromInventory(slot) + confirming = true + } + + listen { + finish() + } + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/SwapHandsTransaction.kt b/common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/SwapHandsTransaction.kt new file mode 100644 index 000000000..5c20afb80 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/SwapHandsTransaction.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2024 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.material.transfer.transaction + +import com.lambda.event.events.InventoryEvent +import com.lambda.event.events.TickEvent +import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.material.transfer.InventoryTransaction +import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Direction + +class SwapHandsTransaction @Ta5kBuilder constructor() : InventoryTransaction() { + override val name: String get() = "Swap Hand Stacks" + + init { + listen { + if (player.isSpectator) return@listen + connection.sendPacket( + PlayerActionC2SPacket( + PlayerActionC2SPacket.Action.SWAP_ITEM_WITH_OFFHAND, + BlockPos.ORIGIN, + Direction.DOWN + ) + ) + } + + listen { + finish() + } + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/SwapHotbarSlotTransaction.kt b/common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/SwapHotbarSlotTransaction.kt new file mode 100644 index 000000000..5b302af17 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/SwapHotbarSlotTransaction.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2024 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.material.transfer.transaction + +import com.lambda.event.events.TickEvent +import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.material.transfer.InventoryTransaction + +class SwapHotbarSlotTransaction @Ta5kBuilder constructor( + val slot: Int +) : InventoryTransaction() { + override val name: String get() = "Selecting slot #$slot" + + init { + listen { + player.inventory.selectedSlot = slot + } + + listen { + finish() + } + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/TaskFlowModule.kt b/common/src/main/kotlin/com/lambda/module/modules/client/TaskFlowModule.kt index c8be28557..3096a0b01 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/TaskFlowModule.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/TaskFlowModule.kt @@ -59,7 +59,6 @@ object TaskFlowModule : Module( Properties.SOUTH, Properties.WEST ) -// val ignoredTags by setting("Ignored Tags", defaultIgnoreTags) @Volatile var drawables = listOf() diff --git a/common/src/main/kotlin/com/lambda/module/modules/debug/InventoryDebug.kt b/common/src/main/kotlin/com/lambda/module/modules/debug/InventoryDebug.kt index 996365570..6fc8321c1 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/debug/InventoryDebug.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/debug/InventoryDebug.kt @@ -26,6 +26,7 @@ import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.Communication.info import com.lambda.util.DynamicReflectionSerializer.dynamicString +import net.minecraft.entity.player.PlayerInventory import net.minecraft.network.packet.c2s.play.* import net.minecraft.network.packet.s2c.play.InventoryS2CPacket import net.minecraft.network.packet.s2c.play.UpdateSelectedSlotS2CPacket @@ -36,8 +37,12 @@ object InventoryDebug : Module( defaultTags = setOf(ModuleTag.DEBUG) ) { init { - listen { - info("Opened screen handler: ${it.screenHandler::class.simpleName}") + listen { event -> + info("Opened screen handler: ${event.screenHandler::class.simpleName}") + + LOG.info(event.screenHandler.slots.joinToString("\n") { + "${it.inventory::class.simpleName} ${it.index} ${it.x} ${it.y}" + }) } listen { diff --git a/common/src/main/kotlin/com/lambda/task/Task.kt b/common/src/main/kotlin/com/lambda/task/Task.kt index 647dfe1ac..f86f68562 100644 --- a/common/src/main/kotlin/com/lambda/task/Task.kt +++ b/common/src/main/kotlin/com/lambda/task/Task.kt @@ -234,7 +234,7 @@ abstract class Task : Nameable { * @return The current task instance (`Task`) to allow method chaining. */ @Ta5kBuilder - infix fun then(task: Task): Task { + infix fun then(task: Task<*>): Task { require(task != this) { "Cannot link a task to itself" } nextTask = { task } return this @@ -253,7 +253,7 @@ abstract class Task : Nameable { * @return The current task instance (`Task`) to allow method chaining. */ @Ta5kBuilder - fun then(vararg task: Task): Task { + fun then(vararg task: Task<*>): Task { (listOf(this) + task).zipWithNext { current, next -> current then next } @@ -325,7 +325,7 @@ abstract class Task : Nameable { appendLine("${" ".repeat(level * 4)}${task.name}" + if (task !is TaskFlow) " [${task.state.display}]" else "") // if (task.state == State.COMPLETED || task.state == State.CANCELLED) return task.subTasks.forEach { - if (task is TaskFlow && (it.state == State.COMPLETED || it.state == State.CANCELLED)) return@forEach +// if (task is TaskFlow && (it.state == State.COMPLETED || it.state == State.CANCELLED)) return@forEach appendTaskTree(it, level + 1) } } diff --git a/common/src/main/kotlin/com/lambda/task/TaskFlow.kt b/common/src/main/kotlin/com/lambda/task/TaskFlow.kt index dc1da6432..5d647bb4c 100644 --- a/common/src/main/kotlin/com/lambda/task/TaskFlow.kt +++ b/common/src/main/kotlin/com/lambda/task/TaskFlow.kt @@ -20,7 +20,7 @@ package com.lambda.task import com.lambda.threading.runSafe object TaskFlow : Task() { - override val name get() = "TaskFlow ($size)" + override val name get() = "TaskFlow" @Ta5kBuilder fun Task<*>.run() = this.execute(this@TaskFlow) diff --git a/common/src/main/kotlin/com/lambda/task/tasks/AcquireMaterial.kt b/common/src/main/kotlin/com/lambda/task/tasks/AcquireMaterial.kt index 48d0c721c..0c7e744a3 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/AcquireMaterial.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/AcquireMaterial.kt @@ -18,8 +18,8 @@ package com.lambda.task.tasks import com.lambda.context.SafeContext -import com.lambda.interaction.material.ContainerManager -import com.lambda.interaction.material.ContainerManager.findContainerWithMaterial +import com.lambda.interaction.material.container.ContainerManager +import com.lambda.interaction.material.container.ContainerManager.findContainerWithMaterial import com.lambda.interaction.material.StackSelection import com.lambda.task.Task diff --git a/common/src/main/kotlin/com/lambda/task/tasks/BreakBlock.kt b/common/src/main/kotlin/com/lambda/task/tasks/BreakBlock.kt index acb26d565..89357a4dc 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/BreakBlock.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/BreakBlock.kt @@ -104,7 +104,7 @@ class BreakBlock @Ta5kBuilder constructor( player.currentScreenHandler.inventorySlots.firstOrNull { it.stack.item.block in TaskFlowModule.disposables }?.let { - clickSlot(it.index, 1, SlotActionType.THROW) + clickSlot(it.id, 1, SlotActionType.THROW) } return@listen } diff --git a/common/src/main/kotlin/com/lambda/util/extension/Screen.kt b/common/src/main/kotlin/com/lambda/util/extension/Screen.kt index f8a2e4edb..76cd17796 100644 --- a/common/src/main/kotlin/com/lambda/util/extension/Screen.kt +++ b/common/src/main/kotlin/com/lambda/util/extension/Screen.kt @@ -18,12 +18,15 @@ package com.lambda.util.extension import net.minecraft.entity.player.PlayerInventory +import net.minecraft.inventory.CraftingInventory +import net.minecraft.inventory.SimpleInventory import net.minecraft.item.ItemStack import net.minecraft.screen.ScreenHandler import net.minecraft.screen.slot.Slot -val ScreenHandler.containerSlots: List get() = slots.filter { it.inventory !is PlayerInventory } +val ScreenHandler.containerSlots: List get() = slots.filter { it.inventory is SimpleInventory } val ScreenHandler.inventorySlots: List get() = slots.filter { it.inventory is PlayerInventory } +val ScreenHandler.craftingSlots: List get() = slots.filter { it.inventory is CraftingInventory } val ScreenHandler.containerStacks: List get() = containerSlots.map { it.stack } val ScreenHandler.inventoryStacks: List get() = inventorySlots.map { it.stack } diff --git a/common/src/main/kotlin/com/lambda/util/player/SlotUtils.kt b/common/src/main/kotlin/com/lambda/util/player/SlotUtils.kt index 3288dc35c..d9017c807 100644 --- a/common/src/main/kotlin/com/lambda/util/player/SlotUtils.kt +++ b/common/src/main/kotlin/com/lambda/util/player/SlotUtils.kt @@ -18,7 +18,6 @@ package com.lambda.util.player import com.lambda.context.SafeContext -import com.lambda.threading.runSafe import net.minecraft.client.network.ClientPlayerEntity import net.minecraft.item.ItemStack import net.minecraft.screen.slot.SlotActionType @@ -46,15 +45,4 @@ object SlotUtils { ) } - data class Transaction( - val slotId: Int, - val button: Int, - val actionType: SlotActionType, - ) { - fun click() { - runSafe { - clickSlot(slotId, button, actionType) - } - } - } } diff --git a/common/src/main/resources/lambda.mixins.common.json b/common/src/main/resources/lambda.mixins.common.json index c27544122..b3861d6a2 100644 --- a/common/src/main/resources/lambda.mixins.common.json +++ b/common/src/main/resources/lambda.mixins.common.json @@ -21,6 +21,7 @@ "items.TridentMixin", "network.ClientConnectionMixin", "network.ClientLoginNetworkMixin", + "network.ClientPlayNetworkHandlerMixin", "network.HandshakeC2SPacketMixin", "network.LoginHelloC2SPacketMixin", "network.LoginKeyC2SPacketMixin", From c14bd8703642af80d22abc342d310e57ad3b3d92 Mon Sep 17 00:00:00 2001 From: Constructor Date: Mon, 30 Dec 2024 22:08:55 +0100 Subject: [PATCH 32/39] Refactor inventory and build configurations Standardize access to `disposables` and `ignoredBlocks` under specific config groups for modularity. Improved task handling by adding a new debug setting and validating rotation states in build tasks. Removed redundant logging and adjusted conditional checks for cleaner code execution. --- .../com/lambda/config/groups/BuildConfig.kt | 5 ++++- .../com/lambda/config/groups/BuildSettings.kt | 3 +++ .../lambda/config/groups/InventoryConfig.kt | 3 +++ .../lambda/config/groups/InventorySettings.kt | 5 +++-- .../construction/simulation/BuildSimulator.kt | 2 +- .../construction/verify/TargetState.kt | 6 +++--- .../material/container/ContainerManager.kt | 2 +- .../material/transfer/InventoryTransaction.kt | 2 -- .../module/modules/client/TaskFlowModule.kt | 18 +++--------------- common/src/main/kotlin/com/lambda/task/Task.kt | 5 +++-- .../kotlin/com/lambda/task/tasks/BreakBlock.kt | 2 +- .../kotlin/com/lambda/task/tasks/BuildTask.kt | 4 ++++ 12 files changed, 29 insertions(+), 28 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/config/groups/BuildConfig.kt b/common/src/main/kotlin/com/lambda/config/groups/BuildConfig.kt index 17fc121f3..5b3aa74b0 100644 --- a/common/src/main/kotlin/com/lambda/config/groups/BuildConfig.kt +++ b/common/src/main/kotlin/com/lambda/config/groups/BuildConfig.kt @@ -17,12 +17,13 @@ package com.lambda.config.groups +import net.minecraft.block.Block + interface BuildConfig { // General val pathing: Boolean val stayInRange: Boolean val collectDrops: Boolean - val forceSilkTouch: Boolean // Breaking val rotateForBreak: Boolean @@ -30,6 +31,8 @@ interface BuildConfig { val maxPendingBreaks: Int val breaksPerTick: Int val breakWeakBlocks: Boolean + val forceSilkTouch: Boolean + val ignoredBlocks: Set // Placing val rotateForPlace: Boolean diff --git a/common/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt b/common/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt index c584c1474..89a6deabf 100644 --- a/common/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt +++ b/common/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt @@ -18,6 +18,8 @@ package com.lambda.config.groups import com.lambda.config.Configurable +import com.lambda.module.modules.client.TaskFlowModule.setting +import com.lambda.util.BlockUtils.allSigns class BuildSettings( c: Configurable, @@ -41,6 +43,7 @@ class BuildSettings( override val breaksPerTick by c.setting("Instant Breaks Per Tick", 5, 1..30, 1, "Maximum instant block breaks per tick") { vis() && page == Page.BREAK } override val breakWeakBlocks by c.setting("Break Weak Blocks", false, "Break blocks that dont have structural integrity (e.g: grass)") { vis() && page == Page.BREAK } override val forceSilkTouch by c.setting("Force Silk Touch", false, "Force silk touch when breaking blocks") { vis() && page == Page.BREAK } + override val ignoredBlocks by setting("Ignored Blocks", allSigns, "Blocks that wont be broken") { vis() && page == Page.BREAK } // Placing override val rotateForPlace by c.setting("Rotate For Place", true, "Rotate towards block while placing") { vis() && page == Page.PLACE } diff --git a/common/src/main/kotlin/com/lambda/config/groups/InventoryConfig.kt b/common/src/main/kotlin/com/lambda/config/groups/InventoryConfig.kt index 8c12935e0..f25b16cda 100644 --- a/common/src/main/kotlin/com/lambda/config/groups/InventoryConfig.kt +++ b/common/src/main/kotlin/com/lambda/config/groups/InventoryConfig.kt @@ -19,8 +19,11 @@ package com.lambda.config.groups import com.lambda.interaction.material.container.MaterialContainer import com.lambda.interaction.material.StackSelection +import net.minecraft.block.Block interface InventoryConfig { + val disposables: Set + val actionTimout: Int val swapWithDisposables: Boolean diff --git a/common/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt b/common/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt index 4e3cf5fdb..bd2fe6823 100644 --- a/common/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt +++ b/common/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt @@ -18,14 +18,15 @@ package com.lambda.config.groups import com.lambda.config.Configurable +import com.lambda.util.item.ItemUtils class InventorySettings( c: Configurable, vis: () -> Boolean = { true }, ) : InventoryConfig { - override val actionTimout by c.setting("Action Timeout", 10, 0..100, 1, "How long to wait for after each inventory action", " ticks") + override val disposables by c.setting("Disposables", ItemUtils.defaultDisposables, "Items that will be ignored when checking for a free slot", vis) + override val actionTimout by c.setting("Action Timeout", 10, 0..100, 1, "How long to wait for after each inventory action", " ticks", vis) override val swapWithDisposables by c.setting("Swap With Disposables", true, "Swap items with disposable ones", vis) - override val providerPriority by c.setting("Provider Priority", InventoryConfig.Priority.WITH_MIN_ITEMS, "What container to prefer when retrieving the item from", vis) override val storePriority by c.setting("Store Priority", InventoryConfig.Priority.WITH_MIN_ITEMS, "What container to prefer when storing the item to", vis) } \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt index 397068ab1..03bed4002 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt @@ -97,7 +97,7 @@ object BuildSimulator { } /* block should be ignored */ - if (state.block in TaskFlowModule.ignoredBlocks && target.type == TargetState.Type.AIR) { + if (state.block in TaskFlowModule.build.ignoredBlocks && target.type == TargetState.Type.AIR) { return BuildResult.Ignored(pos) } diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt b/common/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt index c2f21b93a..22982d5e2 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt @@ -55,7 +55,7 @@ sealed class TargetState(val type: Type) : StateMatcher { override fun getStack(world: ClientWorld, pos: BlockPos) = findDisposable()?.stacks?.firstOrNull { - it.item.block in TaskFlowModule.disposables + it.item.block in TaskFlowModule.inventory.disposables } ?: ItemStack(Items.NETHERRACK) override fun isAir() = false @@ -70,7 +70,7 @@ sealed class TargetState(val type: Type) : StateMatcher { override fun getStack(world: ClientWorld, pos: BlockPos) = findDisposable()?.stacks?.firstOrNull { - it.item.block in TaskFlowModule.disposables + it.item.block in TaskFlowModule.inventory.disposables } ?: ItemStack(Items.NETHERRACK) override fun isAir() = false @@ -81,7 +81,7 @@ sealed class TargetState(val type: Type) : StateMatcher { override fun matches(state: BlockState, pos: BlockPos, world: ClientWorld) = state.block == blockState.block && state.properties.all { - it in TaskFlowModule.defaultIgnoreTags || state[it] == blockState[it] + /*it in TaskFlowModule.defaultIgnoreTags ||*/ state[it] == blockState[it] } override fun getStack(world: ClientWorld, pos: BlockPos): ItemStack = blockState.block.getPickStack(world, pos, blockState) diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/ContainerManager.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/ContainerManager.kt index 161010c41..c49179aca 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/container/ContainerManager.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/container/ContainerManager.kt @@ -133,7 +133,7 @@ object ContainerManager : Loadable { }?.first fun findDisposable() = container().find { container -> - TaskFlowModule.disposables.any { container.materialAvailable(it.item.select()) >= 0 } + TaskFlowModule.inventory.disposables.any { container.materialAvailable(it.item.select()) >= 0 } } class NoContainerFound(selection: StackSelection) : Exception("No container found matching $selection") diff --git a/common/src/main/kotlin/com/lambda/interaction/material/transfer/InventoryTransaction.kt b/common/src/main/kotlin/com/lambda/interaction/material/transfer/InventoryTransaction.kt index f4954edb7..67e4f9892 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/transfer/InventoryTransaction.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/transfer/InventoryTransaction.kt @@ -20,7 +20,6 @@ package com.lambda.interaction.material.transfer import com.lambda.context.SafeContext import com.lambda.task.Task import com.lambda.threading.runSafe -import com.lambda.util.Communication.info abstract class InventoryTransaction : Task() { private var changes: InventoryChanges? = null @@ -33,7 +32,6 @@ abstract class InventoryTransaction : Task() { runSafe { changes?.let { it.detectChanges() - info("Changes: $it") success(it) } } ?: failure("Failed to finish transaction") diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/TaskFlowModule.kt b/common/src/main/kotlin/com/lambda/module/modules/client/TaskFlowModule.kt index 3096a0b01..9106eb134 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/TaskFlowModule.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/TaskFlowModule.kt @@ -18,6 +18,7 @@ package com.lambda.module.modules.client import com.lambda.config.groups.BuildSettings +import com.lambda.config.groups.BuildSettings.Page import com.lambda.config.groups.InteractionSettings import com.lambda.config.groups.InventorySettings import com.lambda.config.groups.RotationSettings @@ -36,7 +37,7 @@ object TaskFlowModule : Module( defaultTags = setOf(ModuleTag.CLIENT, ModuleTag.AUTOMATION) ) { enum class Page { - BUILD, ROTATION, INTERACTION, INVENTORY + BUILD, ROTATION, INTERACTION, INVENTORY, DEBUG } private val page by setting("Page", Page.BUILD) @@ -45,20 +46,7 @@ object TaskFlowModule : Module( val interact = InteractionSettings(this) { page == Page.INTERACTION } val inventory = InventorySettings(this) { page == Page.INVENTORY } - val disposables by setting("Disposables", ItemUtils.defaultDisposables) - val ignoredBlocks by setting("Ignored Blocks", allSigns) - val defaultIgnoreTags = setOf( - Properties.DISTANCE_1_7, - Properties.PERSISTENT, - Properties.WATERLOGGED, - Properties.STAIR_SHAPE, - Properties.UP, - Properties.DOWN, - Properties.NORTH, - Properties.EAST, - Properties.SOUTH, - Properties.WEST - ) + val showAllEntries by setting("Show All Entries", false, "Show all entries in the task tree") { page == Page.DEBUG } @Volatile var drawables = listOf() diff --git a/common/src/main/kotlin/com/lambda/task/Task.kt b/common/src/main/kotlin/com/lambda/task/Task.kt index f86f68562..7c1f153ff 100644 --- a/common/src/main/kotlin/com/lambda/task/Task.kt +++ b/common/src/main/kotlin/com/lambda/task/Task.kt @@ -23,6 +23,7 @@ import com.lambda.event.EventFlow import com.lambda.event.Subscriber import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.module.modules.client.TaskFlowModule import com.lambda.threading.runSafe import com.lambda.util.Communication.logError import com.lambda.util.Nameable @@ -323,9 +324,9 @@ abstract class Task : Nameable { private fun StringBuilder.appendTaskTree(task: Task<*>, level: Int = 0) { appendLine("${" ".repeat(level * 4)}${task.name}" + if (task !is TaskFlow) " [${task.state.display}]" else "") -// if (task.state == State.COMPLETED || task.state == State.CANCELLED) return + if (!TaskFlowModule.showAllEntries && (task.state == State.COMPLETED || task.state == State.CANCELLED)) return task.subTasks.forEach { -// if (task is TaskFlow && (it.state == State.COMPLETED || it.state == State.CANCELLED)) return@forEach + if (!TaskFlowModule.showAllEntries && task is TaskFlow && (it.state == State.COMPLETED || it.state == State.CANCELLED)) return@forEach appendTaskTree(it, level + 1) } } diff --git a/common/src/main/kotlin/com/lambda/task/tasks/BreakBlock.kt b/common/src/main/kotlin/com/lambda/task/tasks/BreakBlock.kt index 89357a4dc..65a44c2c4 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/BreakBlock.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/BreakBlock.kt @@ -102,7 +102,7 @@ class BreakBlock @Ta5kBuilder constructor( if (player.hotbarAndStorage.none { it.isEmpty }) { player.currentScreenHandler.inventorySlots.firstOrNull { - it.stack.item.block in TaskFlowModule.disposables + it.stack.item.block in TaskFlowModule.inventory.disposables }?.let { clickSlot(it.id, 1, SlotActionType.THROW) } diff --git a/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt b/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt index 42581b80e..f5f649e99 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt @@ -64,6 +64,7 @@ class BuildTask @Ta5kBuilder constructor( private var currentPlacement: PlaceContext? = null private var placements = 0 private var breaks = 0 + private var inScope = 0 override fun SafeContext.onStart() { (blueprint as? DynamicBlueprint)?.create() @@ -80,9 +81,12 @@ class BuildTask @Ta5kBuilder constructor( } currentPlacement?.let { + if (currentPlacement?.rotation?.isValid != true) return@listen + if (inScope++ < 1) return@listen // ToDo: Should not be needed but timings are wrong it.place(interact.swingHand) pendingPlacements.add(it) currentPlacement = null + inScope = 0 } (blueprint as? DynamicBlueprint)?.update() From 653e71c8a0a23da5d3920ec6dbfe18edb7047fd6 Mon Sep 17 00:00:00 2001 From: Constructor Date: Mon, 30 Dec 2024 22:39:46 +0100 Subject: [PATCH 33/39] Proper drop collection --- .../kotlin/com/lambda/task/tasks/BuildTask.kt | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt b/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt index f5f649e99..9769ebac6 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt @@ -52,6 +52,7 @@ import java.util.concurrent.ConcurrentLinkedQueue class BuildTask @Ta5kBuilder constructor( private val blueprint: Blueprint, private val finishOnDone: Boolean = true, + private val collectDrops: Boolean = TaskFlowModule.build.collectDrops, private val build: BuildConfig = TaskFlowModule.build, private val rotation: RotationConfig = TaskFlowModule.rotation, private val interact: InteractionConfig = TaskFlowModule.interact, @@ -141,7 +142,7 @@ class BuildTask @Ta5kBuilder constructor( LOG.info("Resolving: ${result.name}") if (result is BreakResult.Break) { - result.collectDrop = build.collectDrops + result.collectDrop = collectDrops } result.resolve().execute(this@BuildTask, pauseParent = result.pausesParent) @@ -180,50 +181,55 @@ class BuildTask @Ta5kBuilder constructor( @Ta5kBuilder fun build( finishOnDone: Boolean = true, + collectDrops: Boolean = TaskFlowModule.build.collectDrops, build: BuildConfig = TaskFlowModule.build, rotation: RotationConfig = TaskFlowModule.rotation, interact: InteractionConfig = TaskFlowModule.interact, blueprint: () -> Blueprint, - ) = BuildTask(blueprint(), finishOnDone, build, rotation, interact) + ) = BuildTask(blueprint(), finishOnDone, collectDrops, build, rotation, interact) @Ta5kBuilder fun Structure.build( finishOnDone: Boolean = true, + collectDrops: Boolean = TaskFlowModule.build.collectDrops, build: BuildConfig = TaskFlowModule.build, rotation: RotationConfig = TaskFlowModule.rotation, interact: InteractionConfig = TaskFlowModule.interact - ) = BuildTask(toBlueprint(), finishOnDone, build, rotation, interact) + ) = BuildTask(toBlueprint(), finishOnDone, collectDrops, build, rotation, interact) @Ta5kBuilder fun Blueprint.build( finishOnDone: Boolean = true, + collectDrops: Boolean = TaskFlowModule.build.collectDrops, build: BuildConfig = TaskFlowModule.build, rotation: RotationConfig = TaskFlowModule.rotation, interact: InteractionConfig = TaskFlowModule.interact - ) = BuildTask(this, finishOnDone, build, rotation, interact) + ) = BuildTask(this, finishOnDone, collectDrops, build, rotation, interact) @Ta5kBuilder fun breakAndCollectBlock( blockPos: BlockPos, finishOnDone: Boolean = true, + collectDrops: Boolean = true, build: BuildConfig = TaskFlowModule.build, rotation: RotationConfig = TaskFlowModule.rotation, interact: InteractionConfig = TaskFlowModule.interact ) = BuildTask( blockPos.toStructure(TargetState.Air).toBlueprint(), - finishOnDone, build, rotation, interact + finishOnDone, collectDrops, build, rotation, interact ) @Ta5kBuilder fun breakBlock( blockPos: BlockPos, finishOnDone: Boolean = true, + collectDrops: Boolean = TaskFlowModule.build.collectDrops, build: BuildConfig = TaskFlowModule.build, rotation: RotationConfig = TaskFlowModule.rotation, interact: InteractionConfig = TaskFlowModule.interact ) = BuildTask( blockPos.toStructure(TargetState.Air).toBlueprint(), - finishOnDone, build, rotation, interact + finishOnDone, collectDrops, build, rotation, interact ) } } From 18c373b9b750184d45e1d5ed07b9cea5e909f698 Mon Sep 17 00:00:00 2001 From: Constructor Date: Tue, 31 Dec 2024 02:02:42 +0100 Subject: [PATCH 34/39] Refactor rotation handling and placement logic Simplified and streamlined rotation management by consolidating `onUpdate` and `onReceive` callbacks. Improved code readability and consistency in `BuildTask` and `BreakBlock` tasks by refining placement and rotation operations. Removed unused comments and ensured proper alignment with updated rotation conventions. --- .../com/lambda/interaction/RotationManager.kt | 33 +++++++++---------- .../com/lambda/task/tasks/BreakBlock.kt | 25 +++++++------- .../kotlin/com/lambda/task/tasks/BuildTask.kt | 8 ++--- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/interaction/RotationManager.kt b/common/src/main/kotlin/com/lambda/interaction/RotationManager.kt index d0de80d66..66fb26571 100644 --- a/common/src/main/kotlin/com/lambda/interaction/RotationManager.kt +++ b/common/src/main/kotlin/com/lambda/interaction/RotationManager.kt @@ -56,7 +56,7 @@ object RotationManager : Loadable { priority: Int = 0, alwaysListen: Boolean = false, onUpdate: SafeContext.(lastContext: RotationContext?) -> RotationContext?, - onReceive: SafeContext.() -> Unit = {} + onReceive: SafeContext.(context: RotationContext) -> Unit = {} ) { var lastCtx: RotationContext? = null @@ -71,8 +71,8 @@ object RotationManager : Loadable { } listen { event -> - if (event.context == lastCtx && event.context.isValid) { - onReceive() + if (event.context == lastCtx) { + onReceive(event.context) } } } @@ -123,20 +123,19 @@ object RotationManager : Loadable { currentRotation = currentContext?.let { context -> val rotationTo = if (keepTicks >= 0) context.rotation else player.rotation - rotationTo -// var speedMultiplier = (context.config as? RotationSettings)?.speedMultiplier ?: 1.0 -// if (keepTicks < 0) speedMultiplier = 1.0 -// -// val turnSpeed = context.config.turnSpeed * speedMultiplier -// -// currentRotation -// .slerp(rotationTo, turnSpeed) -// .fixSensitivity(prevRotation) -// .apply { -// if (context.config.rotationMode != RotationMode.LOCK) return@apply -// player.yaw = this.yawF -// player.pitch = this.pitchF -// } + var speedMultiplier = (context.config as? RotationSettings)?.speedMultiplier ?: 1.0 + if (keepTicks < 0) speedMultiplier = 1.0 + + val turnSpeed = context.config.turnSpeed * speedMultiplier + + currentRotation + .slerp(rotationTo, turnSpeed) + .fixSensitivity(prevRotation) + .apply { + if (context.config.rotationMode != RotationMode.LOCK) return@apply + player.yaw = this.yawF + player.pitch = this.pitchF + } } ?: player.rotation } diff --git a/common/src/main/kotlin/com/lambda/task/tasks/BreakBlock.kt b/common/src/main/kotlin/com/lambda/task/tasks/BreakBlock.kt index 65a44c2c4..2a743ff18 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/BreakBlock.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/BreakBlock.kt @@ -25,6 +25,7 @@ import com.lambda.event.events.RotationEvent import com.lambda.event.events.TickEvent import com.lambda.event.events.WorldEvent import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.RotationManager.requestRotation import com.lambda.interaction.construction.context.BreakContext import com.lambda.interaction.visibilty.VisibilityChecker.lookAtBlock import com.lambda.module.modules.client.TaskFlowModule @@ -71,6 +72,7 @@ class BreakBlock @Ta5kBuilder constructor( success(null) return } + beginState = blockState if (!rotate || ctx.instantBreak) { @@ -79,18 +81,17 @@ class BreakBlock @Ta5kBuilder constructor( } init { - listen { event -> - if (state != State.BREAKING) return@listen - if (!rotate || ctx.instantBreak) return@listen - event.context = lookAtBlock(blockPos, rotation, interact, sides) - } - - listen { - if (state != State.BREAKING) return@listen - if (!rotate || ctx.instantBreak) return@listen - - isValid = it.context.isValid - } + requestRotation( + onUpdate = { + if (state != State.BREAKING) return@requestRotation null + if (!rotate || ctx.instantBreak) return@requestRotation null + + lookAtBlock(blockPos, rotation, interact, sides) + }, + onReceive = { context -> + isValid = context.isValid + } + ) listen { drop?.let { itemDrop -> diff --git a/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt b/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt index 9769ebac6..dcde41077 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt @@ -81,11 +81,11 @@ class BuildTask @Ta5kBuilder constructor( timeout } - currentPlacement?.let { - if (currentPlacement?.rotation?.isValid != true) return@listen + currentPlacement?.let { context -> + if (!context.rotation.isValid) return@listen if (inScope++ < 1) return@listen // ToDo: Should not be needed but timings are wrong - it.place(interact.swingHand) - pendingPlacements.add(it) + context.place(interact.swingHand) + pendingPlacements.add(context) currentPlacement = null inScope = 0 } From 1de3f6f5a7e365af6dbc9d03643344d660b245bc Mon Sep 17 00:00:00 2001 From: Constructor Date: Wed, 1 Jan 2025 21:00:25 +0100 Subject: [PATCH 35/39] Rework initialization logging --- common/src/main/kotlin/com/lambda/Lambda.kt | 5 ++--- .../kotlin/com/lambda/command/CommandRegistry.kt | 2 +- .../kotlin/com/lambda/config/Configuration.kt | 5 ++++- common/src/main/kotlin/com/lambda/core/Loader.kt | 13 +++++++++---- .../main/kotlin/com/lambda/core/PingManager.kt | 2 ++ .../main/kotlin/com/lambda/core/TimerManager.kt | 2 ++ .../graphics/renderer/gui/font/LambdaFont.kt | 2 +- .../renderer/gui/font/glyph/FontGlyphs.kt | 2 +- .../kotlin/com/lambda/gui/GuiConfigurable.kt | 2 ++ .../kotlin/com/lambda/gui/HudGuiConfigurable.kt | 4 +++- .../lambda/interaction/PlayerPacketManager.kt | 4 ++-- .../com/lambda/interaction/RotationManager.kt | 4 +++- .../construction/StructureRegistry.kt | 15 ++++++++------- .../construction/processing/ProcessorRegistry.kt | 2 ++ .../material/container/ContainerManager.kt | 2 ++ .../kotlin/com/lambda/module/ModuleRegistry.kt | 2 +- .../module/modules/debug/InventoryDebug.kt | 16 +++++++++++----- .../main/kotlin/com/lambda/util/Communication.kt | 2 ++ .../kotlin/com/lambda/util/FolderRegister.kt | 11 +++++------ .../kotlin/com/lambda/fabric/LambdaFabric.kt | 2 +- .../main/kotlin/com/lambda/forge/LambdaForge.kt | 2 +- 21 files changed, 65 insertions(+), 36 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/Lambda.kt b/common/src/main/kotlin/com/lambda/Lambda.kt index 9150d7723..2d7fb6e31 100644 --- a/common/src/main/kotlin/com/lambda/Lambda.kt +++ b/common/src/main/kotlin/com/lambda/Lambda.kt @@ -63,10 +63,9 @@ object Lambda { .registerTypeAdapter(Optional::class.java, OptionalSerializer) .create() - fun initialize(block: () -> Unit) { + fun initialize(block: (Long) -> Unit) { recordRenderCall { - Loader.initialize() - block() + block(Loader.initialize()) } } } diff --git a/common/src/main/kotlin/com/lambda/command/CommandRegistry.kt b/common/src/main/kotlin/com/lambda/command/CommandRegistry.kt index f4e0c5b71..b201bca5d 100644 --- a/common/src/main/kotlin/com/lambda/command/CommandRegistry.kt +++ b/common/src/main/kotlin/com/lambda/command/CommandRegistry.kt @@ -32,6 +32,6 @@ object CommandRegistry : Configurable(LambdaConfig), Loadable { val commands = getInstances { forPackages("com.lambda.command.commands") }.toMutableList() override fun load(): String { - return "Registered ${commands.size} commands" + return "Loaded ${commands.size} commands" } } diff --git a/common/src/main/kotlin/com/lambda/config/Configuration.kt b/common/src/main/kotlin/com/lambda/config/Configuration.kt index 5d4910888..bb5a23a8b 100644 --- a/common/src/main/kotlin/com/lambda/config/Configuration.kt +++ b/common/src/main/kotlin/com/lambda/config/Configuration.kt @@ -104,7 +104,10 @@ abstract class Configuration : Jsonable { } private fun load(file: File) { - check(file.exists()) { "No configuration file found for ${configName.capitalize()}" } + if (!file.exists()) { + LOG.warn("No configuration file found for ${configName.capitalize()}. Creating new file when saving.") + return + } loadFromJson(JsonParser.parseReader(file.reader()).asJsonObject) } diff --git a/common/src/main/kotlin/com/lambda/core/Loader.kt b/common/src/main/kotlin/com/lambda/core/Loader.kt index a259b44e2..52b282a23 100644 --- a/common/src/main/kotlin/com/lambda/core/Loader.kt +++ b/common/src/main/kotlin/com/lambda/core/Loader.kt @@ -20,6 +20,7 @@ package com.lambda.core import com.lambda.Lambda import com.lambda.Lambda.LOG import com.lambda.util.Communication.ascii +import com.lambda.util.FolderRegister import com.lambda.util.reflections.getInstances import kotlin.system.measureTimeMillis import kotlin.time.DurationUnit @@ -33,14 +34,18 @@ object Loader { private val loadables = getInstances { forPackages("com.lambda") } - fun initialize() { + fun initialize(): Long { ascii.split("\n").forEach { LOG.info(it) } - LOG.info("Initializing ${Lambda.MOD_NAME} ${Lambda.VERSION}") + LOG.info("Initializing ${Lambda.MOD_NAME} ${Lambda.VERSION} (${loadables.size} loaders)...") val initTime = measureTimeMillis { - loadables.forEach { LOG.info(it.load()) } + loadables.forEach { + var response: String + val time = measureTimeMillis { response = it.load() } + LOG.info("$response ($time ms)") + } } - LOG.info("${Lambda.MOD_NAME} ${Lambda.VERSION} was successfully initialized (${initTime}ms)") + return initTime } } diff --git a/common/src/main/kotlin/com/lambda/core/PingManager.kt b/common/src/main/kotlin/com/lambda/core/PingManager.kt index 6c8d9bd0b..b717c2bc9 100644 --- a/common/src/main/kotlin/com/lambda/core/PingManager.kt +++ b/common/src/main/kotlin/com/lambda/core/PingManager.kt @@ -29,6 +29,8 @@ object PingManager : Loadable { private val pings: LimitedOrderedSet = LimitedOrderedSet(100) private const val INTERVAL = 1 + override fun load() = "Loaded Ping Manager" + val lastPing: Long get() = pings.lastOrNull() ?: 0 diff --git a/common/src/main/kotlin/com/lambda/core/TimerManager.kt b/common/src/main/kotlin/com/lambda/core/TimerManager.kt index 06223fa73..d370f0f79 100644 --- a/common/src/main/kotlin/com/lambda/core/TimerManager.kt +++ b/common/src/main/kotlin/com/lambda/core/TimerManager.kt @@ -23,6 +23,8 @@ import com.lambda.event.events.ClientEvent object TimerManager : Loadable { var lastTickLength: Float = 50f + override fun load() = "Loaded Timer Manager" + fun getLength(): Float { var length = 50f diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/LambdaFont.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/LambdaFont.kt index 59eab9ffc..5b3c2c219 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/LambdaFont.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/LambdaFont.kt @@ -37,7 +37,7 @@ enum class LambdaFont(private val fontName: String) { glyphs = FontGlyphs(font) } - object Loader : Loadable { + object FontLoader : Loadable { override fun load(): String { entries.forEach(LambdaFont::loadGlyphs) return "Loaded ${entries.size} fonts" diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/glyph/FontGlyphs.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/glyph/FontGlyphs.kt index 79ad1e40b..72471e733 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/glyph/FontGlyphs.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/glyph/FontGlyphs.kt @@ -40,7 +40,7 @@ class FontGlyphs( init { runCatching { processGlyphs() - LOG.info("Font ${font.fontName} loaded with ${charMap.size} characters") + LOG.info("Loaded ${font.fontName} with ${charMap.size} characters") }.onFailure { LOG.error("Failed to load font glyphs: ${it.message}", it) fontTexture = MipmapTexture(BufferedImage(1024, 1024, BufferedImage.TYPE_INT_ARGB)) diff --git a/common/src/main/kotlin/com/lambda/gui/GuiConfigurable.kt b/common/src/main/kotlin/com/lambda/gui/GuiConfigurable.kt index 7753183d4..3d32800ea 100644 --- a/common/src/main/kotlin/com/lambda/gui/GuiConfigurable.kt +++ b/common/src/main/kotlin/com/lambda/gui/GuiConfigurable.kt @@ -25,4 +25,6 @@ object GuiConfigurable : AbstractGuiConfigurable( LambdaClickGui, ModuleTag.defaults, "gui" ) { var customWindows by setting("custom windows", listOf()) + + override fun load() = "Loaded GUI Configurable" } diff --git a/common/src/main/kotlin/com/lambda/gui/HudGuiConfigurable.kt b/common/src/main/kotlin/com/lambda/gui/HudGuiConfigurable.kt index 5db2d6725..4e2c33e1b 100644 --- a/common/src/main/kotlin/com/lambda/gui/HudGuiConfigurable.kt +++ b/common/src/main/kotlin/com/lambda/gui/HudGuiConfigurable.kt @@ -22,4 +22,6 @@ import com.lambda.module.tag.ModuleTag object HudGuiConfigurable : AbstractGuiConfigurable( LambdaHudGui, ModuleTag.hudDefaults, "hudgui" -) +) { + override fun load() = "Loaded HUD GUI Configurable" +} diff --git a/common/src/main/kotlin/com/lambda/interaction/PlayerPacketManager.kt b/common/src/main/kotlin/com/lambda/interaction/PlayerPacketManager.kt index 47d358e46..f55cec01e 100644 --- a/common/src/main/kotlin/com/lambda/interaction/PlayerPacketManager.kt +++ b/common/src/main/kotlin/com/lambda/interaction/PlayerPacketManager.kt @@ -35,10 +35,10 @@ import net.minecraft.network.packet.c2s.play.ClientCommandC2SPacket import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket.* import net.minecraft.util.math.Vec3d -object PlayerPacketManager : Loadable { +object PlayerPacketManager { val configurations = LimitedOrderedSet(100) - var lastPosition = Vec3d.ZERO + var lastPosition: Vec3d = Vec3d.ZERO var lastRotation = Rotation.ZERO var lastSprint = false var lastSneak = false diff --git a/common/src/main/kotlin/com/lambda/interaction/RotationManager.kt b/common/src/main/kotlin/com/lambda/interaction/RotationManager.kt index 66fb26571..2491841e6 100644 --- a/common/src/main/kotlin/com/lambda/interaction/RotationManager.kt +++ b/common/src/main/kotlin/com/lambda/interaction/RotationManager.kt @@ -45,13 +45,15 @@ import kotlin.math.* object RotationManager : Loadable { var currentRotation = Rotation.ZERO - var prevRotation = Rotation.ZERO + private var prevRotation = Rotation.ZERO var currentContext: RotationContext? = null private var keepTicks = 0 private var pauseTicks = 0 + override fun load() = "Loaded Rotation Manager" + fun Any.requestRotation( priority: Int = 0, alwaysListen: Boolean = false, diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/StructureRegistry.kt b/common/src/main/kotlin/com/lambda/interaction/construction/StructureRegistry.kt index 50512c0ad..4d4003f5b 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/StructureRegistry.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/StructureRegistry.kt @@ -44,9 +44,10 @@ import kotlin.io.path.* @OptIn(ExperimentalPathApi::class) @Suppress("JavaIoSerializableObjectMustHaveReadResolve") object StructureRegistry : ConcurrentHashMap(), Loadable { - private val structurePath = FolderRegister.structure - private val pathWatcher = FileSystems.getDefault().newWatchService() - .apply { structurePath.register(this, ENTRY_CREATE, ENTRY_DELETE) } + private val pathWatcher by lazy { + FileSystems.getDefault().newWatchService() + .apply { FolderRegister.structure.register(this, ENTRY_CREATE, ENTRY_DELETE) } + } /** * Map of file suffix to their respective read function @@ -115,7 +116,7 @@ object StructureRegistry : ConcurrentHashMap(), Load * @return The created [StructureTemplate], or null if the structure is not found or invalid. */ private fun loadFileAndCreate(path: Path, convert: Boolean) = - structurePath.resolve(path).inputStream().use { templateStream -> + FolderRegister.structure.resolve(path).inputStream().use { templateStream -> val compound = NbtIo.readCompressed(templateStream, NbtSizeTracker.ofUnlimitedBytes()) val extension = path.extension val template = createStructure(compound, extension) @@ -157,7 +158,7 @@ object StructureRegistry : ConcurrentHashMap(), Load * @param structure The [StructureTemplate] to save. */ private fun saveStructure(relativePath: String, structure: StructureTemplate) { - val path = structurePath.resolve(relativePath) + val path = FolderRegister.structure.resolve(relativePath) val compound = structure.writeNbt(NbtCompound()) Files.createDirectories(path.parent) @@ -176,9 +177,9 @@ object StructureRegistry : ConcurrentHashMap(), Load contains("DataVersion") && contains("blocks") && contains("palette") && contains("size") override fun load(): String { - structurePath.walk() + FolderRegister.structure.walk() .filter { it.extension in serializers.keys } - .forEach { loadStructureByRelativePath(structurePath.relativize(it)) } + .forEach { loadStructureByRelativePath(FolderRegister.structure.relativize(it)) } return "Loaded $size structure templates" } diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/processing/ProcessorRegistry.kt b/common/src/main/kotlin/com/lambda/interaction/construction/processing/ProcessorRegistry.kt index 7fd861802..dc70e7844 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/processing/ProcessorRegistry.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/processing/ProcessorRegistry.kt @@ -28,6 +28,8 @@ object ProcessorRegistry : Loadable { private val processors = getInstances { forPackages(PROCESSOR_PACKAGE) } private val processorCache = mutableMapOf() + override fun load() = "Loaded ${processors.size} pre processors" + fun TargetState.findProcessorForState(): PreprocessingStep = (this as? TargetState.State)?.let { state -> processorCache.getOrPut(state.blockState) { diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/ContainerManager.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/ContainerManager.kt index c49179aca..cd67cd7b0 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/container/ContainerManager.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/container/ContainerManager.kt @@ -51,6 +51,8 @@ object ContainerManager : Loadable { private var lastInteractedBlockEntity: BlockEntity? = null + override fun load() = "Loaded ${compileContainers.size} containers" + init { listen { lastInteractedBlockEntity = it.blockHitResult.blockPos.blockEntity(world) diff --git a/common/src/main/kotlin/com/lambda/module/ModuleRegistry.kt b/common/src/main/kotlin/com/lambda/module/ModuleRegistry.kt index 37c2af3d3..ff2a1e2de 100644 --- a/common/src/main/kotlin/com/lambda/module/ModuleRegistry.kt +++ b/common/src/main/kotlin/com/lambda/module/ModuleRegistry.kt @@ -33,6 +33,6 @@ object ModuleRegistry : Loadable { get() = modules.map { it.name }.toSet() override fun load(): String { - return "Registered ${modules.size} modules with ${modules.sumOf { it.settings.size }} settings" + return "Loaded ${modules.size} modules with ${modules.sumOf { it.settings.size }} settings" } } diff --git a/common/src/main/kotlin/com/lambda/module/modules/debug/InventoryDebug.kt b/common/src/main/kotlin/com/lambda/module/modules/debug/InventoryDebug.kt index 6fc8321c1..4513ee1f5 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/debug/InventoryDebug.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/debug/InventoryDebug.kt @@ -40,7 +40,7 @@ object InventoryDebug : Module( listen { event -> info("Opened screen handler: ${event.screenHandler::class.simpleName}") - LOG.info(event.screenHandler.slots.joinToString("\n") { + LOG.info("\n" + event.screenHandler.slots.joinToString("\n") { "${it.inventory::class.simpleName} ${it.index} ${it.x} ${it.y}" }) } @@ -54,11 +54,17 @@ object InventoryDebug : Module( } listen { - when (val packet = it.packet) { - is UpdateSelectedSlotS2CPacket, is InventoryS2CPacket -> { - this@InventoryDebug.info(packet.dynamicString()) + when (it.packet) { + is UpdateSelectedSlotS2CPacket, + is InventoryS2CPacket + -> { + LOG.info(it.packet.dynamicString()) } } + when (val packet = it.packet) { + is UpdateSelectedSlotS2CPacket -> this@InventoryDebug.info("Updated selected slot: ${packet.slot}") + is InventoryS2CPacket -> this@InventoryDebug.info("Received inventory update: syncId: ${packet.syncId} | revision: ${packet.revision} | cursorStack ${packet.cursorStack}") + } } listen { @@ -70,7 +76,7 @@ object InventoryDebug : Module( is CreativeInventoryActionC2SPacket, is PickFromInventoryC2SPacket, is UpdateSelectedSlotC2SPacket, - -> LOG.info(it.packet.dynamicString()) + -> LOG.info(System.currentTimeMillis().toString() + " " + it.packet.dynamicString()) } } } diff --git a/common/src/main/kotlin/com/lambda/util/Communication.kt b/common/src/main/kotlin/com/lambda/util/Communication.kt index 6e717ca62..21102318f 100644 --- a/common/src/main/kotlin/com/lambda/util/Communication.kt +++ b/common/src/main/kotlin/com/lambda/util/Communication.kt @@ -37,6 +37,7 @@ import java.awt.Color object Communication { val ascii = """ + ⣰⡛⠶⣄⠀⠀⠀⠀⠀⠀ ⠑⠭⣛⡜⣳⡀⠀⠀⠀⠀ ⠀⠀⠹⣾⣥⣛⡄⠀⠀⠀ @@ -44,6 +45,7 @@ object Communication { ⠀⢠⣿⣿⣿⢶⣏⡿⡄⠀ ⢠⣿⣿⡿⠃⠘⣿⣼⣻⣄ ⠻⢿⡿⠁⠀⠀⠘⢷⡽⠞ + """.trimIndent() fun Any.debug(message: String, source: String = "") = log(LogLevel.DEBUG.text(message), LogLevel.DEBUG, source) diff --git a/common/src/main/kotlin/com/lambda/util/FolderRegister.kt b/common/src/main/kotlin/com/lambda/util/FolderRegister.kt index 46deafb2a..90008b00c 100644 --- a/common/src/main/kotlin/com/lambda/util/FolderRegister.kt +++ b/common/src/main/kotlin/com/lambda/util/FolderRegister.kt @@ -23,8 +23,8 @@ import com.lambda.util.StringUtils.sanitizeForFilename import java.io.File import java.net.InetSocketAddress import java.nio.file.Path -import kotlin.io.path.createDirectory -import kotlin.io.path.notExists +import kotlin.io.path.* +import kotlin.math.min /** * The [FolderRegister] object is responsible for managing the directory structure of the application. @@ -48,13 +48,12 @@ object FolderRegister : Loadable { val folders = listOf(lambda, config, packetLogs, replay, cache, structure) val createdFolders = folders.mapNotNull { if (it.notExists()) { - it.createDirectory() - it + it.createDirectories() } else null } return if (createdFolders.isNotEmpty()) { - "\nCreated directories: ${createdFolders.joinToString { it.toString() }}" - } else "" + "Created directories: ${createdFolders.joinToString { minecraft.parent.relativize(it).toString() }}" + } else "Loaded ${folders.size} directories" } /** diff --git a/fabric/src/main/kotlin/com/lambda/fabric/LambdaFabric.kt b/fabric/src/main/kotlin/com/lambda/fabric/LambdaFabric.kt index 15c2aaa78..baf5aabe3 100644 --- a/fabric/src/main/kotlin/com/lambda/fabric/LambdaFabric.kt +++ b/fabric/src/main/kotlin/com/lambda/fabric/LambdaFabric.kt @@ -29,7 +29,7 @@ object LambdaFabric : ClientModInitializer { override fun onInitializeClient() { Lambda.initialize { Registries.REGISTRIES.forEach(AgnosticRegistries::dump) - LOG.info("$MOD_NAME Fabric $VERSION initialized.") + LOG.info("$MOD_NAME Fabric $VERSION was successfully initialized after $it ms\n") } } } diff --git a/forge/src/main/kotlin/com/lambda/forge/LambdaForge.kt b/forge/src/main/kotlin/com/lambda/forge/LambdaForge.kt index 585a77ba5..255a07288 100644 --- a/forge/src/main/kotlin/com/lambda/forge/LambdaForge.kt +++ b/forge/src/main/kotlin/com/lambda/forge/LambdaForge.kt @@ -40,7 +40,7 @@ object LambdaForge { @SubscribeEvent fun onClient(event: FMLClientSetupEvent) { Lambda.initialize { - LOG.info("$MOD_NAME Forge $VERSION initialized.") + LOG.info("$MOD_NAME Forge $VERSION was successfully initialized after $it ms\n") } } From 3442d57b7a1c221c7a3c6aec2d8ae119e25ed5d9 Mon Sep 17 00:00:00 2001 From: Constructor Date: Wed, 1 Jan 2025 21:28:08 +0100 Subject: [PATCH 36/39] Refactor event listener management in Task and EventFlow Removed redundant listener management methods and replaced them with a streamlined `unsubscribe` approach. Cleaned up code to improve maintainability and consistency across Task state transitions, ensuring proper cleanup of listeners tied to task instances. --- .../main/kotlin/com/lambda/event/EventFlow.kt | 5 +++ .../kotlin/com/lambda/event/Subscriber.kt | 12 +++++ .../com/lambda/event/listener/SafeListener.kt | 45 ------------------- .../src/main/kotlin/com/lambda/task/Task.kt | 33 +++++--------- 4 files changed, 27 insertions(+), 68 deletions(-) diff --git a/common/src/main/kotlin/com/lambda/event/EventFlow.kt b/common/src/main/kotlin/com/lambda/event/EventFlow.kt index 0b66b1478..9abf905a8 100644 --- a/common/src/main/kotlin/com/lambda/event/EventFlow.kt +++ b/common/src/main/kotlin/com/lambda/event/EventFlow.kt @@ -87,6 +87,11 @@ object EventFlow { */ val concurrentListeners = Subscriber() + fun Any.unsubscribe() { + syncListeners.unsubscribe(this) + concurrentListeners.unsubscribe(this) + } + init { // parallel event execution on dedicated threads runConcurrent { diff --git a/common/src/main/kotlin/com/lambda/event/Subscriber.kt b/common/src/main/kotlin/com/lambda/event/Subscriber.kt index 3a78dcf4a..6b9354a7c 100644 --- a/common/src/main/kotlin/com/lambda/event/Subscriber.kt +++ b/common/src/main/kotlin/com/lambda/event/Subscriber.kt @@ -54,6 +54,18 @@ class Subscriber : ConcurrentHashMap, ConcurrentSkipListSet
  • unsubscribe(listener: Listener) = getOrElse(T::class) { defaultListenerSet }.remove(listener) + /** + * Unsubscribes all listeners associated with the current instance (the caller object). + * This method iterates over all values in the `Subscriber`'s map and removes listeners + * whose `owner` property matches the caller object. + * + * Use this method when you want to clean up listeners that were registered with the + * current instance, preventing further event notifications. + */ + fun unsubscribe(owner: Any) { + values.forEach { it.removeAll { listener -> listener.owner == owner } } + } + /** Allows a [Subscriber] to stop receiving all [Event]s of another [Subscriber] */ infix fun unsubscribe(subscriber: Subscriber) { subscriber.forEach { (eventType, listeners) -> diff --git a/common/src/main/kotlin/com/lambda/event/listener/SafeListener.kt b/common/src/main/kotlin/com/lambda/event/listener/SafeListener.kt index 1e7d8fec3..1c7cd869b 100644 --- a/common/src/main/kotlin/com/lambda/event/listener/SafeListener.kt +++ b/common/src/main/kotlin/com/lambda/event/listener/SafeListener.kt @@ -130,51 +130,6 @@ class SafeListener( return listener } - /** - * Registers a new [SafeListener] for a generic [Event] type [T] within the context of a [Task]. - * The [function] is executed on the same thread where the [Event] was dispatched. - * The [function] will only be executed when the context satisfies certain safety conditions. - * These conditions are met when none of the following [SafeContext] properties are null: - * - [SafeContext.world] - * - [SafeContext.player] - * - [SafeContext.interaction] - * - [SafeContext.connection] - * - * Usage: - * ```kotlin - * myTask.listen { event -> - * player.sendMessage("Event received: $event") - * } - * - * myTask.listen(priority = 1) { event -> - * player.sendMessage("Event received before the previous listener: $event") - * } - * ``` - * - * @param T The type of the event to listen for. - * This should be a subclass of Event. - * @param priority The priority of the listener. - * Listeners with higher priority will be executed first. - * The Default value is 0. - * @param alwaysListen If true, the listener will be executed even if it is muted. The Default value is false. - * @param function The function to be executed when the event is posted. - * This function should take a SafeContext and an event of type T as parameters. - * @return The newly created and registered [SafeListener]. - */ - inline fun Task<*>.listen( - priority: Int = 0, - alwaysListen: Boolean = false, - noinline function: SafeContext.(T) -> Unit = {}, - ): SafeListener { - val listener = SafeListener(priority, this, alwaysListen) { event -> - function(event) // ToDo: run function always on game thread - } - - syncListeners.subscribe(listener) - - return listener - } - /** * This function registers a new [SafeListener] for a generic [Event] type [T]. * The [transform] is executed on the same thread where the [Event] was dispatched. diff --git a/common/src/main/kotlin/com/lambda/task/Task.kt b/common/src/main/kotlin/com/lambda/task/Task.kt index 7c1f153ff..182b92c93 100644 --- a/common/src/main/kotlin/com/lambda/task/Task.kt +++ b/common/src/main/kotlin/com/lambda/task/Task.kt @@ -19,8 +19,8 @@ package com.lambda.task import com.lambda.Lambda.LOG import com.lambda.context.SafeContext -import com.lambda.event.EventFlow -import com.lambda.event.Subscriber +import com.lambda.event.EventFlow.unsubscribe +import com.lambda.event.Muteable import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.modules.client.TaskFlowModule @@ -33,10 +33,11 @@ typealias TaskGenerator = SafeContext.(R) -> Task<*> typealias TaskGeneratorOrNull = SafeContext.(R) -> Task<*>? typealias TaskGeneratorUnit = SafeContext.(R) -> Unit -abstract class Task : Nameable { +abstract class Task : Nameable, Muteable { private var parent: Task<*>? = null private val subTasks = mutableListOf>() - private var state = State.RUNNING + private var state = State.INIT + override val isMuted: Boolean get() = state == State.PAUSED || state == State.INIT var age = 0 private val depth: Int get() = parent?.depth?.plus(1) ?: 0 val isCompleted get() = state == State.COMPLETED @@ -48,10 +49,8 @@ abstract class Task : Nameable { private var nextTaskOrNull: TaskGeneratorOrNull? = null private var onFinish: TaskGeneratorUnit? = null - val syncListeners = Subscriber() - private val concurrentListeners = Subscriber() - enum class State { + INIT, RUNNING, PAUSED, CANCELLED, @@ -118,14 +117,14 @@ abstract class Task : Nameable { LOG.info("$name deactivating parent ${owner.name}") if (owner !is TaskFlow) owner.deactivate() } + state = State.RUNNING runSafe { runCatching { onStart() }.onFailure { failure(it) } } - startListening() return this } @Ta5kBuilder fun success(result: Result) { - stopListening() + unsubscribe() state = State.COMPLETED runSafe { executeNextTask(result) @@ -141,7 +140,6 @@ abstract class Task : Nameable { fun activate() { if (state != State.PAUSED) return state = State.RUNNING - startListening() } @Ta5kBuilder @@ -149,7 +147,6 @@ abstract class Task : Nameable { if (state != State.RUNNING) return if (unpausable) return state = State.PAUSED - stopListening() } private fun SafeContext.executeNextTask(result: Result) { @@ -174,7 +171,7 @@ abstract class Task : Nameable { if (this is TaskFlow) return if (state == State.COMPLETED || state == State.CANCELLED) return state = State.CANCELLED - stopListening() + unsubscribe() } @Ta5kBuilder @@ -198,7 +195,7 @@ abstract class Task : Nameable { stacktrace: MutableList> = mutableListOf() ) { state = State.FAILED - stopListening() + unsubscribe() stacktrace.add(this) parent?.failure(e, stacktrace) ?: run { val message = buildString { @@ -214,16 +211,6 @@ abstract class Task : Nameable { } } - private fun startListening() { - EventFlow.syncListeners.subscribe(syncListeners) - EventFlow.concurrentListeners.subscribe(concurrentListeners) - } - - private fun stopListening() { - EventFlow.syncListeners.unsubscribe(syncListeners) - EventFlow.concurrentListeners.unsubscribe(concurrentListeners) - } - /** * Specifies the next task to execute after the current task completes successfully. * From cc8408a5edfe09de4dcc28497328f263cad46607 Mon Sep 17 00:00:00 2001 From: Constructor Date: Wed, 1 Jan 2025 23:30:37 +0100 Subject: [PATCH 37/39] Refactor inventory handling and transaction structure. Replaced `InventoryTransfer` with `TransactionExecutor` for improved naming and functionality. Renamed `InventoryTransferTask` to `SlotTransfer` and updated method calls accordingly. Introduced `QuickCraftTransaction` and `TransferSelection` for expanded transaction capabilities and added error handling in `InventoryChanges`. --- .../ClientPlayInteractionManagerMixin.java | 6 ++- .../construction/result/BreakResult.kt | 2 +- .../container/containers/ChestContainer.kt | 4 +- .../container/containers/HotbarContainer.kt | 2 +- .../container/containers/MainHandContainer.kt | 2 +- .../containers/ShulkerBoxContainer.kt | 4 +- .../material/transfer/InventoryChanges.kt | 39 ++++++++++++++++--- .../material/transfer/InventoryTransaction.kt | 11 ++---- ...ventoryTransferTask.kt => SlotTransfer.kt} | 24 ++++++------ ...toryTransfer.kt => TransactionExecutor.kt} | 29 +++++++------- .../material/transfer/TransferSelection.kt | 21 ++++++++++ .../transaction/QuickCraftTransaction.kt | 31 +++++++++++++++ 12 files changed, 126 insertions(+), 49 deletions(-) rename common/src/main/kotlin/com/lambda/interaction/material/transfer/{InventoryTransferTask.kt => SlotTransfer.kt} (79%) rename common/src/main/kotlin/com/lambda/interaction/material/transfer/{InventoryTransfer.kt => TransactionExecutor.kt} (86%) create mode 100644 common/src/main/kotlin/com/lambda/interaction/material/transfer/TransferSelection.kt create mode 100644 common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/QuickCraftTransaction.kt diff --git a/common/src/main/java/com/lambda/mixin/entity/ClientPlayInteractionManagerMixin.java b/common/src/main/java/com/lambda/mixin/entity/ClientPlayInteractionManagerMixin.java index 3f863fd52..43896514f 100644 --- a/common/src/main/java/com/lambda/mixin/entity/ClientPlayInteractionManagerMixin.java +++ b/common/src/main/java/com/lambda/mixin/entity/ClientPlayInteractionManagerMixin.java @@ -66,9 +66,11 @@ public void interactItemHead(PlayerEntity player, Hand hand, CallbackInfoReturna } } - @Inject(method = "attackBlock", at = @At("HEAD")) + @Inject(method = "attackBlock", at = @At("HEAD"), cancellable = true) public void onAttackBlock(BlockPos pos, Direction side, CallbackInfoReturnable cir) { - if (EventFlow.post(new PlayerEvent.Attack.Block(pos, side)).isCanceled()) cir.cancel(); + if (EventFlow.post(new PlayerEvent.Attack.Block(pos, side)).isCanceled()) { + cir.setReturnValue(false); + } } @Inject(method = "attackEntity", at = @At("HEAD"), cancellable = true) diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/result/BreakResult.kt b/common/src/main/kotlin/com/lambda/interaction/construction/result/BreakResult.kt index 39c42481c..182ec9623 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/result/BreakResult.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/result/BreakResult.kt @@ -110,7 +110,7 @@ sealed class BreakResult : BuildResult() { ?: selectStack { isItem(badItem).not() }.transfer(MainHandContainer) - ?: MaterialContainer.Nothing() + ?: MaterialContainer.Nothing("Couldn't find a tool for ${blockState.block.name.string} with $badItem in main hand.") override fun SafeContext.buildRenderer() { withPos(blockPos, color) diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/containers/ChestContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/containers/ChestContainer.kt index fee5061ba..8bb41286e 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/container/containers/ChestContainer.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/container/containers/ChestContainer.kt @@ -18,8 +18,8 @@ package com.lambda.interaction.material.container.containers import com.lambda.interaction.material.StackSelection -import com.lambda.interaction.material.transfer.InventoryTransferTask.Companion.deposit -import com.lambda.interaction.material.transfer.InventoryTransferTask.Companion.withdraw +import com.lambda.interaction.material.transfer.SlotTransfer.Companion.deposit +import com.lambda.interaction.material.transfer.SlotTransfer.Companion.withdraw import com.lambda.interaction.material.container.MaterialContainer import com.lambda.task.tasks.OpenContainer import com.lambda.util.Communication.info diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/containers/HotbarContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/containers/HotbarContainer.kt index b3db7074e..a9000639e 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/container/containers/HotbarContainer.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/container/containers/HotbarContainer.kt @@ -21,7 +21,7 @@ import com.lambda.Lambda.mc import com.lambda.context.SafeContext import com.lambda.interaction.material.ContainerTask import com.lambda.interaction.material.StackSelection -import com.lambda.interaction.material.transfer.InventoryTransferTask.Companion.deposit +import com.lambda.interaction.material.transfer.SlotTransfer.Companion.deposit import com.lambda.interaction.material.container.MaterialContainer import com.lambda.util.player.SlotUtils.hotbar import com.lambda.util.text.buildText diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/containers/MainHandContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/containers/MainHandContainer.kt index d093b9024..5e26d6644 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/container/containers/MainHandContainer.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/container/containers/MainHandContainer.kt @@ -20,7 +20,7 @@ package com.lambda.interaction.material.container.containers import com.lambda.Lambda.mc import com.lambda.context.SafeContext import com.lambda.interaction.material.ContainerTask -import com.lambda.interaction.material.transfer.InventoryTransfer.Companion.transfer +import com.lambda.interaction.material.transfer.TransactionExecutor.Companion.transfer import com.lambda.interaction.material.StackSelection import com.lambda.interaction.material.container.MaterialContainer import com.lambda.util.item.ItemStackUtils.equal diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/containers/ShulkerBoxContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/containers/ShulkerBoxContainer.kt index 7382d60e3..52cc0da78 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/container/containers/ShulkerBoxContainer.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/container/containers/ShulkerBoxContainer.kt @@ -21,8 +21,8 @@ import com.lambda.context.SafeContext import com.lambda.interaction.material.StackSelection import com.lambda.task.Task import com.lambda.task.tasks.BuildTask.Companion.breakAndCollectBlock -import com.lambda.interaction.material.transfer.InventoryTransferTask.Companion.deposit -import com.lambda.interaction.material.transfer.InventoryTransferTask.Companion.withdraw +import com.lambda.interaction.material.transfer.SlotTransfer.Companion.deposit +import com.lambda.interaction.material.transfer.SlotTransfer.Companion.withdraw import com.lambda.interaction.material.container.MaterialContainer import com.lambda.task.tasks.OpenContainer import com.lambda.task.tasks.PlaceContainer diff --git a/common/src/main/kotlin/com/lambda/interaction/material/transfer/InventoryChanges.kt b/common/src/main/kotlin/com/lambda/interaction/material/transfer/InventoryChanges.kt index 00c80199a..cffaed390 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/transfer/InventoryChanges.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/transfer/InventoryChanges.kt @@ -1,25 +1,41 @@ package com.lambda.interaction.material.transfer -import com.lambda.context.SafeContext import com.lambda.interaction.material.StackSelection +import com.lambda.util.item.ItemStackUtils.equal import net.minecraft.item.ItemStack import net.minecraft.screen.slot.Slot +/** + * A class representing changes in an inventory's state over time. It acts as a tracker for + * detecting, storing, and merging differences between the original and updated states of + * inventory slots. This class extends a map-like structure, where the key is the slot index + * and the value is a list of pairs representing the original and updated states of an inventory slot. + * + * Example: + * ``` + * #0: 64 obsidian -> 0 air, 0 air -> 64 obsidian + * #36: 12 observer -> 11 observer + * ``` + * - Where `#0` is the slot id, first it was emptied and then got `64 obsidian` again + * - Where `#36` is the slot id, that was reduced by `1 observer` + * + * @property slots A list of `Slot` objects representing the current inventory slots being tracked. + * Defaults to an empty list. + */ class InventoryChanges( - ctx: SafeContext + private var slots: List, ) : MutableMap>> by HashMap() { - - private var slots = ctx.player.currentScreenHandler.slots - private val originalStacks = slots.map { it.stack.copy() } // Snapshot of the initial state + private val originalStacks = slots.map { it.stack.copy() } /** * Detect and store changes directly in the map. */ fun detectChanges() { + require(slots.isNotEmpty()) { "Cannot detect changes on an empty slots list" } slots.forEachIndexed { index, slot -> val originalStack = originalStacks[index] val updatedStack = slot.stack - if (!ItemStack.areEqual(originalStack, updatedStack)) { + if (!originalStack.equal(updatedStack)) { getOrPut(index) { mutableListOf() }.add(originalStack to updatedStack.copy()) } } @@ -32,12 +48,23 @@ class InventoryChanges( * @return A new `InventoryChanges` instance containing merged entries. */ infix fun merge(other: InventoryChanges) { + require(slots.isNotEmpty()) { "Cannot merge changes to an empty slots list" } other.forEach { (key, value) -> getOrPut(key) { mutableListOf() }.addAll(value) } } + /** + * Evaluates if the current inventory changes fulfill the given selection requirement. + * + * @param to A list of `Slot` objects to evaluate for the selection criteria. + * @param selection A `StackSelection` object that specifies the selection criteria, including the + * target items and their required count. + * @return `true` if the total count of matching items across the filtered slots meets or exceeds + * the required count specified in the `StackSelection`; `false` otherwise. + */ fun fulfillsSelection(to: List, selection: StackSelection): Boolean { + require(slots.isNotEmpty()) { "Cannot evaluate selection on an empty slots list" } val targetSlots = selection.filterSlots(to).map { it.id } return filter { it.key in targetSlots }.entries.sumOf { (_, changes) -> changes.lastOrNull()?.second?.count ?: 0 diff --git a/common/src/main/kotlin/com/lambda/interaction/material/transfer/InventoryTransaction.kt b/common/src/main/kotlin/com/lambda/interaction/material/transfer/InventoryTransaction.kt index 67e4f9892..b656214bd 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/transfer/InventoryTransaction.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/transfer/InventoryTransaction.kt @@ -22,19 +22,16 @@ import com.lambda.task.Task import com.lambda.threading.runSafe abstract class InventoryTransaction : Task() { - private var changes: InventoryChanges? = null + private lateinit var changes: InventoryChanges override fun SafeContext.onStart() { - changes = InventoryChanges(this) + changes = InventoryChanges(player.currentScreenHandler.slots) } fun finish() { runSafe { - changes?.let { - it.detectChanges() - success(it) - } + changes.detectChanges() + success(changes) } ?: failure("Failed to finish transaction") } - } \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/interaction/material/transfer/InventoryTransferTask.kt b/common/src/main/kotlin/com/lambda/interaction/material/transfer/SlotTransfer.kt similarity index 79% rename from common/src/main/kotlin/com/lambda/interaction/material/transfer/InventoryTransferTask.kt rename to common/src/main/kotlin/com/lambda/interaction/material/transfer/SlotTransfer.kt index edc3951c5..f19cd1c8f 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/transfer/InventoryTransferTask.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/transfer/SlotTransfer.kt @@ -23,7 +23,7 @@ import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.material.StackSelection -import com.lambda.interaction.material.transfer.InventoryTransfer.Companion.transfer +import com.lambda.interaction.material.transfer.TransactionExecutor.Companion.transfer import com.lambda.module.modules.client.TaskFlowModule import com.lambda.task.Task import com.lambda.util.extension.containerSlots @@ -31,7 +31,7 @@ import com.lambda.util.extension.inventorySlots import net.minecraft.screen.ScreenHandler import net.minecraft.screen.slot.Slot -class InventoryTransferTask @Ta5kBuilder constructor( +class SlotTransfer @Ta5kBuilder constructor( val screen: ScreenHandler, private val selection: StackSelection, val from: List, @@ -41,25 +41,25 @@ class InventoryTransferTask @Ta5kBuilder constructor( ) : Task() { private var selectedFrom = selection.filterSlots(from) private var selectedTo = to.filter { it.stack.isEmpty } // + to.filter { it.stack.item.block in TaskFlowModule.disposables } - private val screenName = runCatching { screen.type::class.simpleName }.getOrNull() ?: screen::class.simpleName override val name: String - get() = "Moving $selection from [${selectedFrom.joinToString { "${it.id}" }}] to [${selectedTo.joinToString { "${it.id}" }}] in $screenName" + get() = "Moving $selection from slots [${selectedFrom.joinToString { "${it.id}" }}] to slots [${selectedTo.joinToString { "${it.id}" }}] in ${screen::class.simpleName}" private var delay = 0 - private var changes: InventoryChanges? = null + private lateinit var changes: InventoryChanges override fun SafeContext.onStart() { - changes = InventoryChanges(this) + changes = InventoryChanges(player.currentScreenHandler.slots) } init { listen { - if (player.currentScreenHandler != screen) { - failure("Screen has changed") + val current = player.currentScreenHandler + if (current != screen) { + failure("Screen has changed. Expected ${screen::class.simpleName} (revision ${screen.revision}, got ${current::class.simpleName} (revision ${current.revision})") return@listen } - if (changes?.fulfillsSelection(to, selection) == true) { + if (changes.fulfillsSelection(to, selection)) { if (closeScreen) player.closeHandledScreen() success() return@listen @@ -79,8 +79,8 @@ class InventoryTransferTask @Ta5kBuilder constructor( transfer { moveSlot(nextFrom.id, nextTo.id) }.finally { change -> - changes?.merge(change) - }.execute(this@InventoryTransferTask) + changes merge change + }.execute(this@SlotTransfer) // if (transfer.fulfillsSelection(selection)) { // info("Transfer complete") @@ -99,7 +99,7 @@ class InventoryTransferTask @Ta5kBuilder constructor( from: List, to: List, closeScreen: Boolean = true - ) = InventoryTransferTask(screen, selection, from, to, closeScreen) + ) = SlotTransfer(screen, selection, from, to, closeScreen) @Ta5kBuilder fun withdraw(screen: ScreenHandler, selection: StackSelection, closeScreen: Boolean = true) = diff --git a/common/src/main/kotlin/com/lambda/interaction/material/transfer/InventoryTransfer.kt b/common/src/main/kotlin/com/lambda/interaction/material/transfer/TransactionExecutor.kt similarity index 86% rename from common/src/main/kotlin/com/lambda/interaction/material/transfer/InventoryTransfer.kt rename to common/src/main/kotlin/com/lambda/interaction/material/transfer/TransactionExecutor.kt index f974c6302..048cb2291 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/transfer/InventoryTransfer.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/transfer/TransactionExecutor.kt @@ -24,34 +24,33 @@ import com.lambda.interaction.material.transfer.transaction.* import com.lambda.task.Task import net.minecraft.screen.slot.SlotActionType -class InventoryTransfer @Ta5kBuilder constructor() : Task() { - override val name: String get() = "Inventory Transfer" +class TransactionExecutor @Ta5kBuilder constructor( + private val transactions: MutableList = mutableListOf() +) : Task() { + override val name: String get() = "Execution of ${transactions.size} transactions left" - @DslMarker - annotation class InvTransfer - - private var transactions = mutableListOf() - private var changes: InventoryChanges? = null + private lateinit var changes: InventoryChanges override fun SafeContext.onStart() { - changes = InventoryChanges(this) + changes = InventoryChanges(player.currentScreenHandler.slots) } init { listen { if (transactions.isEmpty()) { - changes?.let { - success(it) - } + success(changes) return@listen } transactions.removeFirstOrNull()?.finally { change -> - changes?.merge(change) - }?.execute(this@InventoryTransfer) + changes merge change + }?.execute(this@TransactionExecutor) } } + @DslMarker + annotation class InvTransfer + @InvTransfer fun click(slotId: Int, button: Int, actionType: SlotActionType) { transactions.add(ClickSlotTransaction(slotId, button, actionType)) @@ -132,8 +131,8 @@ class InventoryTransfer @Ta5kBuilder constructor() : Task() { companion object { @InvTransfer - fun transfer(block: InventoryTransfer.() -> Unit) = - InventoryTransfer().apply { + fun transfer(block: TransactionExecutor.() -> Unit) = + TransactionExecutor().apply { block(this) } } diff --git a/common/src/main/kotlin/com/lambda/interaction/material/transfer/TransferSelection.kt b/common/src/main/kotlin/com/lambda/interaction/material/transfer/TransferSelection.kt new file mode 100644 index 000000000..ac8ed4c62 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/material/transfer/TransferSelection.kt @@ -0,0 +1,21 @@ +/* + * 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.material.transfer + +class TransferSelection { +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/QuickCraftTransaction.kt b/common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/QuickCraftTransaction.kt new file mode 100644 index 000000000..007f2d1d3 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/QuickCraftTransaction.kt @@ -0,0 +1,31 @@ +/* + * 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.material.transfer.transaction + +import com.lambda.interaction.material.transfer.InventoryTransaction + +class QuickCraftTransaction @Ta5kBuilder constructor( + private val slots: List, + private val mode: Mode = Mode.SINGLE +) : InventoryTransaction() { + override val name: String get() = "Drag and drop ${slots.size} slots" + + enum class Mode { + SINGLE, SPLIT + } +} \ No newline at end of file From 573b69c98cf92a0db108c63be14e22a277ee8290 Mon Sep 17 00:00:00 2001 From: Constructor Date: Thu, 2 Jan 2025 05:05:55 +0100 Subject: [PATCH 38/39] Clean up InventoryEvents --- .../lambda/mixin/MinecraftClientMixin.java | 4 +- .../ClientPlayNetworkHandlerMixin.java | 2 +- .../mixin/render/ScreenHandlerMixin.java | 4 +- .../com/lambda/event/events/InventoryEvent.kt | 67 ++++++++++++++++++- .../lambda/event/events/ScreenHandlerEvent.kt | 33 --------- .../material/container/ContainerManager.kt | 4 +- .../PickFromInventoryTransaction.kt | 2 +- .../module/modules/debug/InventoryDebug.kt | 10 ++- .../module/modules/player/InventoryTweaks.kt | 4 +- .../com/lambda/task/tasks/OpenContainer.kt | 8 +-- 10 files changed, 83 insertions(+), 55 deletions(-) delete mode 100644 common/src/main/kotlin/com/lambda/event/events/ScreenHandlerEvent.kt diff --git a/common/src/main/java/com/lambda/mixin/MinecraftClientMixin.java b/common/src/main/java/com/lambda/mixin/MinecraftClientMixin.java index 7f9dd2aa8..21e53caf5 100644 --- a/common/src/main/java/com/lambda/mixin/MinecraftClientMixin.java +++ b/common/src/main/java/com/lambda/mixin/MinecraftClientMixin.java @@ -20,7 +20,7 @@ import com.lambda.Lambda; import com.lambda.event.EventFlow; import com.lambda.event.events.ClientEvent; -import com.lambda.event.events.ScreenHandlerEvent; +import com.lambda.event.events.InventoryEvent; import com.lambda.event.events.TickEvent; import com.lambda.module.modules.player.Interact; import net.minecraft.client.MinecraftClient; @@ -78,7 +78,7 @@ private void onStartup(CallbackInfo ci) { private void onScreenOpen(@Nullable Screen screen, CallbackInfo ci) { if (screen == null) return; if (screen instanceof ScreenHandlerProvider handledScreen) { - EventFlow.post(new ScreenHandlerEvent.Open(handledScreen.getScreenHandler())); + EventFlow.post(new InventoryEvent.Open(handledScreen.getScreenHandler())); } } diff --git a/common/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java b/common/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java index b95b7b8dd..e756a9f96 100644 --- a/common/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java +++ b/common/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java @@ -31,7 +31,7 @@ public class ClientPlayNetworkHandlerMixin { @Inject(method = "onUpdateSelectedSlot", at = @At(value = "TAIL")) private void onUpdateSelectedSlot(UpdateSelectedSlotS2CPacket packet, CallbackInfo ci) { - EventFlow.post(new InventoryEvent.SelectedSlotUpdate(packet.getSlot())); + EventFlow.post(new InventoryEvent.SelectedHotbarSlotUpdate(packet.getSlot())); } @Inject(method = "onScreenHandlerSlotUpdate", at = @At(value = "TAIL")) diff --git a/common/src/main/java/com/lambda/mixin/render/ScreenHandlerMixin.java b/common/src/main/java/com/lambda/mixin/render/ScreenHandlerMixin.java index 803825bf5..42ce6dd53 100644 --- a/common/src/main/java/com/lambda/mixin/render/ScreenHandlerMixin.java +++ b/common/src/main/java/com/lambda/mixin/render/ScreenHandlerMixin.java @@ -18,7 +18,7 @@ package com.lambda.mixin.render; import com.lambda.event.EventFlow; -import com.lambda.event.events.ScreenHandlerEvent; +import com.lambda.event.events.InventoryEvent; import net.minecraft.item.ItemStack; import net.minecraft.screen.ScreenHandler; import org.spongepowered.asm.mixin.Mixin; @@ -32,6 +32,6 @@ public class ScreenHandlerMixin { @Inject(method = "updateSlotStacks", at = @At("TAIL")) private void onUpdateSlotStacksHead(int revision, List stacks, ItemStack cursorStack, CallbackInfo ci) { - EventFlow.post(new ScreenHandlerEvent.Update(revision, stacks, cursorStack)); + EventFlow.post(new InventoryEvent.FullUpdate(revision, stacks, cursorStack)); } } diff --git a/common/src/main/kotlin/com/lambda/event/events/InventoryEvent.kt b/common/src/main/kotlin/com/lambda/event/events/InventoryEvent.kt index e9bd8e84e..e9d3dc607 100644 --- a/common/src/main/kotlin/com/lambda/event/events/InventoryEvent.kt +++ b/common/src/main/kotlin/com/lambda/event/events/InventoryEvent.kt @@ -19,8 +19,71 @@ package com.lambda.event.events import com.lambda.event.Event import net.minecraft.item.ItemStack +import net.minecraft.screen.ScreenHandler +/** + * Represents various events related to inventory interactions, updates, and state changes. + * + * This sealed class encompasses different types of inventory events that occur during gameplay, + * such as opening or closing an inventory, updating inventory contents, or changing hotbar slots. + * Each event provides specific contextual data relevant to the inventory interaction. + */ sealed class InventoryEvent { - class SelectedSlotUpdate(val slot: Int) : Event - class SlotUpdate(val syncId: Int, val revision: Int, val slot: Int, val stack: ItemStack) : Event + /** + * Represents an event triggered when an inventory or screen is opened. + * + * This event provides access to the associated [ScreenHandler], which manages the interaction + * logic between the inventory and the player. It is typically used to initialize or handle + * logic when the screen linked to a player's inventory is opened. + * + * @property screenHandler The screen handler associated with the opened inventory. + */ + class Open(val screenHandler: ScreenHandler) : Event + + /** + * Represents an event triggered when an inventory or screen is closed. + * + * This event provides access to the associated [ScreenHandler], which manages the interaction + * logic between the inventory and the player. It is typically used to finalize or clean up logic + * when a player's inventory screen is closed. + * + * @property screenHandler The screen handler associated with the closed inventory. + */ + class Close(val screenHandler: ScreenHandler) : Event + + /** + * Represents an update event for an inventory, typically triggered when inventory contents or states + * change during gameplay. + * + * @property revision The revision number indicating the current state of the inventory. + * @property stacks A list of item stacks representing the contents of the inventory slots. + * @property cursorStack The item stack currently held by the cursor. + */ + data class FullUpdate( + val revision: Int, + val stacks: List, + val cursorStack: ItemStack, + ) : Event + + /** + * Represents an event triggered when a specific inventory slot is updated. + * + * @property syncId The synchronization ID related to the container or inventory. + * @property revision The revision number representing the state of the inventory. + * @property slot The index of the updated inventory slot. + * @property stack The new item stack in the updated slot. + */ + class SlotUpdate( + val syncId: Int, + val revision: Int, + val slot: Int, + val stack: ItemStack + ) : Event + + /** + * Represents an event triggered when a player switches their selected hotbar slot. + * + * @property slot The index of the newly selected hotbar slot. + */ + class SelectedHotbarSlotUpdate(val slot: Int) : Event } \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/event/events/ScreenHandlerEvent.kt b/common/src/main/kotlin/com/lambda/event/events/ScreenHandlerEvent.kt deleted file mode 100644 index df017b316..000000000 --- a/common/src/main/kotlin/com/lambda/event/events/ScreenHandlerEvent.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2024 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.event.events - -import com.lambda.event.Event -import net.minecraft.item.ItemStack -import net.minecraft.screen.ScreenHandler - -sealed class ScreenHandlerEvent { - class Open(val screenHandler: ScreenHandler) : Event - class Close(val screenHandler: ScreenHandler) : Event - - data class Update( - val revision: Int, - val stacks: List, - val cursorStack: ItemStack, - ) : Event -} diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/ContainerManager.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/ContainerManager.kt index cd67cd7b0..6bf2df4cf 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/container/ContainerManager.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/container/ContainerManager.kt @@ -18,8 +18,8 @@ package com.lambda.interaction.material.container import com.lambda.core.Loadable +import com.lambda.event.events.InventoryEvent import com.lambda.event.events.PlayerEvent -import com.lambda.event.events.ScreenHandlerEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.material.StackSelection import com.lambda.interaction.material.StackSelection.Companion.select @@ -58,7 +58,7 @@ object ContainerManager : Loadable { lastInteractedBlockEntity = it.blockHitResult.blockPos.blockEntity(world) } - listen { event -> + listen { event -> if (event.screenHandler !is GenericContainerScreenHandler) return@listen val handler = event.screenHandler diff --git a/common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/PickFromInventoryTransaction.kt b/common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/PickFromInventoryTransaction.kt index 3b9cae831..9197741fe 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/PickFromInventoryTransaction.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/PickFromInventoryTransaction.kt @@ -36,7 +36,7 @@ class PickFromInventoryTransaction @Ta5kBuilder constructor( confirming = true } - listen { + listen { finish() } } diff --git a/common/src/main/kotlin/com/lambda/module/modules/debug/InventoryDebug.kt b/common/src/main/kotlin/com/lambda/module/modules/debug/InventoryDebug.kt index 4513ee1f5..18e74be3a 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/debug/InventoryDebug.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/debug/InventoryDebug.kt @@ -18,15 +18,13 @@ package com.lambda.module.modules.debug import com.lambda.Lambda.LOG +import com.lambda.event.events.InventoryEvent import com.lambda.event.events.PacketEvent -import com.lambda.event.events.ScreenHandlerEvent -import com.lambda.event.events.WorldEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.Communication.info import com.lambda.util.DynamicReflectionSerializer.dynamicString -import net.minecraft.entity.player.PlayerInventory import net.minecraft.network.packet.c2s.play.* import net.minecraft.network.packet.s2c.play.InventoryS2CPacket import net.minecraft.network.packet.s2c.play.UpdateSelectedSlotS2CPacket @@ -37,7 +35,7 @@ object InventoryDebug : Module( defaultTags = setOf(ModuleTag.DEBUG) ) { init { - listen { event -> + listen { event -> info("Opened screen handler: ${event.screenHandler::class.simpleName}") LOG.info("\n" + event.screenHandler.slots.joinToString("\n") { @@ -45,11 +43,11 @@ object InventoryDebug : Module( }) } - listen { + listen { info("Closed screen handler: ${it.screenHandler::class.simpleName}") } - listen { + listen { info("Updated screen handler: ${it.revision}, ${it.stacks}, ${it.cursorStack}") } diff --git a/common/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt b/common/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt index 99316b871..50ec1bd43 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt @@ -18,8 +18,8 @@ package com.lambda.module.modules.player import com.lambda.context.SafeContext +import com.lambda.event.events.InventoryEvent import com.lambda.event.events.PlayerEvent -import com.lambda.event.events.ScreenHandlerEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module import com.lambda.module.tag.ModuleTag @@ -68,7 +68,7 @@ object InventoryTweaks : Module( }.run() } - listen { event -> + listen { event -> if (event.screenHandler != lastOpenScreen) return@listen lastOpenScreen = null placedPos?.let { diff --git a/common/src/main/kotlin/com/lambda/task/tasks/OpenContainer.kt b/common/src/main/kotlin/com/lambda/task/tasks/OpenContainer.kt index e8a397d7e..8cd6ff14a 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/OpenContainer.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/OpenContainer.kt @@ -19,8 +19,8 @@ package com.lambda.task.tasks import com.lambda.config.groups.RotationConfig import com.lambda.config.groups.InteractionConfig +import com.lambda.event.events.InventoryEvent import com.lambda.event.events.RotationEvent -import com.lambda.event.events.ScreenHandlerEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.visibilty.VisibilityChecker.lookAtBlock import com.lambda.module.modules.client.TaskFlowModule @@ -56,7 +56,7 @@ class OpenContainer @Ta5kBuilder constructor( } init { - listen { + listen { if (state != State.OPENING) return@listen screenHandler = it.screenHandler @@ -65,14 +65,14 @@ class OpenContainer @Ta5kBuilder constructor( if (!waitForSlotLoad) success(it.screenHandler) } - listen { + listen { if (screenHandler != it.screenHandler) return@listen state = State.SCOPING screenHandler = null } - listen { + listen { if (state != State.SLOT_LOADING) return@listen screenHandler?.let { From 7a10323ac1621b7a89d4b62f75d1fd4b05fcc4b3 Mon Sep 17 00:00:00 2001 From: Constructor Date: Thu, 2 Jan 2025 05:09:50 +0100 Subject: [PATCH 39/39] Refactor rotation management and add Ender Chest access config. Replaced `requestRotation` with a more flexible `rotate` DSL across modules for cleaner code and enhanced rotation handling. Introduced a new `accessEnderChest` configuration in `InventoryConfig` and applied it to filter container usage. Improved `PacketMine` behavior for better break handling. --- .../lambda/mixin/MinecraftClientMixin.java | 2 +- .../lambda/config/groups/InventoryConfig.kt | 1 + .../lambda/config/groups/InventorySettings.kt | 1 + .../com/lambda/interaction/RotationManager.kt | 44 ++++++++++++++----- .../material/container/ContainerManager.kt | 3 +- .../lambda/module/modules/combat/KillAura.kt | 12 +++-- .../lambda/module/modules/movement/Speed.kt | 16 +++---- .../module/modules/player/PacketMine.kt | 6 ++- .../lambda/module/modules/player/Scaffold.kt | 12 ++--- .../com/lambda/task/tasks/BreakBlock.kt | 17 ++++--- .../kotlin/com/lambda/task/tasks/BuildTask.kt | 19 +++++--- 11 files changed, 82 insertions(+), 51 deletions(-) diff --git a/common/src/main/java/com/lambda/mixin/MinecraftClientMixin.java b/common/src/main/java/com/lambda/mixin/MinecraftClientMixin.java index 21e53caf5..ebc46893d 100644 --- a/common/src/main/java/com/lambda/mixin/MinecraftClientMixin.java +++ b/common/src/main/java/com/lambda/mixin/MinecraftClientMixin.java @@ -86,7 +86,7 @@ private void onScreenOpen(@Nullable Screen screen, CallbackInfo ci) { private void onScreenRemove(@Nullable Screen screen, CallbackInfo ci) { if (currentScreen == null) return; if (currentScreen instanceof ScreenHandlerProvider handledScreen) { - EventFlow.post(new ScreenHandlerEvent.Close(handledScreen.getScreenHandler())); + EventFlow.post(new InventoryEvent.Close(handledScreen.getScreenHandler())); } } diff --git a/common/src/main/kotlin/com/lambda/config/groups/InventoryConfig.kt b/common/src/main/kotlin/com/lambda/config/groups/InventoryConfig.kt index f25b16cda..4f068b8df 100644 --- a/common/src/main/kotlin/com/lambda/config/groups/InventoryConfig.kt +++ b/common/src/main/kotlin/com/lambda/config/groups/InventoryConfig.kt @@ -23,6 +23,7 @@ import net.minecraft.block.Block interface InventoryConfig { val disposables: Set + val accessEnderChest: Boolean val actionTimout: Int val swapWithDisposables: Boolean diff --git a/common/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt b/common/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt index bd2fe6823..74f962802 100644 --- a/common/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt +++ b/common/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt @@ -25,6 +25,7 @@ class InventorySettings( vis: () -> Boolean = { true }, ) : InventoryConfig { override val disposables by c.setting("Disposables", ItemUtils.defaultDisposables, "Items that will be ignored when checking for a free slot", vis) + override val accessEnderChest by c.setting("Access Ender Chest", false, "Allow access to the player's ender chest", vis) override val actionTimout by c.setting("Action Timeout", 10, 0..100, 1, "How long to wait for after each inventory action", " ticks", vis) override val swapWithDisposables by c.setting("Swap With Disposables", true, "Swap items with disposable ones", vis) override val providerPriority by c.setting("Provider Priority", InventoryConfig.Priority.WITH_MIN_ITEMS, "What container to prefer when retrieving the item from", vis) diff --git a/common/src/main/kotlin/com/lambda/interaction/RotationManager.kt b/common/src/main/kotlin/com/lambda/interaction/RotationManager.kt index 2491841e6..3e27cedf0 100644 --- a/common/src/main/kotlin/com/lambda/interaction/RotationManager.kt +++ b/common/src/main/kotlin/com/lambda/interaction/RotationManager.kt @@ -22,7 +22,9 @@ import com.lambda.config.groups.RotationSettings import com.lambda.context.SafeContext import com.lambda.core.Loadable import com.lambda.event.EventFlow.post -import com.lambda.event.events.* +import com.lambda.event.events.ConnectionEvent +import com.lambda.event.events.PacketEvent +import com.lambda.event.events.RotationEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.event.listener.UnsafeListener.Companion.listenUnsafe import com.lambda.interaction.rotation.Rotation @@ -34,14 +36,17 @@ import com.lambda.interaction.rotation.RotationMode import com.lambda.module.modules.client.Baritone import com.lambda.threading.runGameScheduled import com.lambda.threading.runSafe -import com.lambda.util.math.lerp -import com.lambda.util.math.MathUtils.toRadian -import com.lambda.util.math.Vec2d import com.lambda.util.extension.partialTicks import com.lambda.util.extension.rotation +import com.lambda.util.math.MathUtils.toRadian +import com.lambda.util.math.Vec2d +import com.lambda.util.math.lerp import net.minecraft.client.input.Input import net.minecraft.network.packet.s2c.play.PlayerPositionLookS2CPacket -import kotlin.math.* +import kotlin.math.cos +import kotlin.math.round +import kotlin.math.sign +import kotlin.math.sin object RotationManager : Loadable { var currentRotation = Rotation.ZERO @@ -54,16 +59,17 @@ object RotationManager : Loadable { override fun load() = "Loaded Rotation Manager" - fun Any.requestRotation( + @RotationDsl + fun Any.rotate( priority: Int = 0, alwaysListen: Boolean = false, - onUpdate: SafeContext.(lastContext: RotationContext?) -> RotationContext?, - onReceive: SafeContext.(context: RotationContext) -> Unit = {} + block: RequestRotationBuilder.() -> Unit, ) { + val builder = RequestRotationBuilder().apply(block) var lastCtx: RotationContext? = null listen(priority, alwaysListen) { event -> - val rotationContext = onUpdate(event.context) + val rotationContext = builder.onUpdate?.invoke(this, event.context) rotationContext?.let { event.context = it @@ -74,11 +80,29 @@ object RotationManager : Loadable { listen { event -> if (event.context == lastCtx) { - onReceive(event.context) + builder.onReceive?.invoke(this, event.context) } } } + @DslMarker + annotation class RotationDsl + + class RequestRotationBuilder { + var onUpdate: (SafeContext.(lastContext: RotationContext?) -> RotationContext?)? = null + var onReceive: (SafeContext.(context: RotationContext) -> Unit)? = null + + @RotationDsl + fun onUpdate(block: SafeContext.(lastContext: RotationContext?) -> RotationContext?) { + onUpdate = block + } + + @RotationDsl + fun onReceive(block: SafeContext.(context: RotationContext) -> Unit) { + onReceive = block + } + } + @JvmStatic fun update() = runSafe { RotationEvent.Update(BaritoneProcessor.poolContext()).post { diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/ContainerManager.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/ContainerManager.kt index 6bf2df4cf..714124825 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/container/ContainerManager.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/container/ContainerManager.kt @@ -43,7 +43,8 @@ import net.minecraft.screen.ScreenHandlerType // ToDo: Make this a Configurable to save container caches. Should use a cached region based storage system. object ContainerManager : Loadable { private val container: List - get() = compileContainers + runtimeContainers + // ToDo: Filter containers based on a filter setting TaskFlowModule.inventory.accessEnderChest etc + get() = compileContainers.filter { it !is EnderChestContainer } + runtimeContainers private val compileContainers = getInstances { forPackages("com.lambda.interaction.material.container") } diff --git a/common/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt b/common/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt index 9ecba1689..95311b4ab 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt @@ -26,12 +26,11 @@ import com.lambda.event.events.PlayerPacketEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.RotationManager -import com.lambda.interaction.RotationManager.requestRotation +import com.lambda.interaction.RotationManager.rotate import com.lambda.interaction.rotation.Rotation import com.lambda.interaction.rotation.Rotation.Companion.dist import com.lambda.interaction.rotation.Rotation.Companion.rotationTo import com.lambda.interaction.rotation.RotationContext -import com.lambda.interaction.visibilty.VisibilityChecker.getVisibleSurfaces import com.lambda.interaction.visibilty.VisibilityChecker.scanSurfaces import com.lambda.interaction.visibilty.VisibilityChecker.visibleSides import com.lambda.module.Module @@ -55,7 +54,6 @@ import net.minecraft.network.packet.c2s.play.HandSwingC2SPacket import net.minecraft.network.packet.c2s.play.PlayerInteractEntityC2SPacket import net.minecraft.network.packet.c2s.play.UpdateSelectedSlotC2SPacket import net.minecraft.util.Hand -import net.minecraft.util.math.Direction import net.minecraft.util.math.Vec3d import kotlin.math.pow @@ -116,15 +114,15 @@ object KillAura : Module( } init { - requestRotation( - onUpdate = { - if (!rotate) return@requestRotation null + rotate { + onUpdate { + if (!rotate) return@onUpdate null target?.let { target -> buildRotation(target) } } - ) + } listen(Int.MIN_VALUE) { event -> prevY = lastY diff --git a/common/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt b/common/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt index 2e61796fb..9982d0575 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt @@ -22,7 +22,7 @@ import com.lambda.context.SafeContext import com.lambda.event.events.ClientEvent import com.lambda.event.events.MovementEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.RotationManager.requestRotation +import com.lambda.interaction.RotationManager.rotate import com.lambda.interaction.rotation.Rotation import com.lambda.interaction.rotation.RotationContext import com.lambda.interaction.rotation.RotationMode @@ -134,15 +134,15 @@ object Speed : Module( } } - requestRotation(100, alwaysListen = false, - onUpdate = { lastContext -> - if (mode != Mode.GRIM_STRAFE) return@requestRotation null - if (!shouldWork()) return@requestRotation null + rotate(100, alwaysListen = false) { + onUpdate { lastContext -> + if (mode != Mode.GRIM_STRAFE) return@onUpdate null + if (!shouldWork()) return@onUpdate null var yaw = player.yaw val input = newMovementInput() - if (!input.isInputting) return@requestRotation null + if (!input.isInputting) return@onUpdate null run { if (!diagonal) return@run @@ -161,8 +161,8 @@ object Speed : Module( val rotation = Rotation(moveYaw, lastContext?.rotation?.pitch ?: player.pitch.toDouble()) RotationContext(rotation, rotationConfig) - }, {} - ) + } + } onEnable { reset() diff --git a/common/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt b/common/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt index 5b6555ea0..f3c6b92ce 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt @@ -1382,10 +1382,12 @@ object PacketMine : Module( } private fun SafeContext.packetStartBreak(pos: BlockPos) { - startBreak(pos) - if (packets != PacketMode.Vanilla || doubleBreak) { + if (packets == PacketMode.Grim) { abortBreak(pos) + stopBreak(pos) } + startBreak(pos) + if (packets == PacketMode.NCP) abortBreak(pos) if (packets == PacketMode.Grim || doubleBreak) { stopBreak(pos) } diff --git a/common/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt b/common/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt index 3f144f979..a08ea39e0 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt @@ -26,7 +26,7 @@ import com.lambda.graphics.renderer.esp.DirectionMask import com.lambda.graphics.renderer.esp.DirectionMask.buildSideMesh import com.lambda.graphics.renderer.esp.builders.build import com.lambda.interaction.RotationManager.currentRotation -import com.lambda.interaction.RotationManager.requestRotation +import com.lambda.interaction.RotationManager.rotate import com.lambda.interaction.blockplace.PlaceFinder.Companion.buildPlaceInfo import com.lambda.interaction.blockplace.PlaceInfo import com.lambda.interaction.blockplace.PlaceInteraction.placeBlock @@ -121,15 +121,15 @@ object Scaffold : Module( } init { - requestRotation( - onUpdate = { + rotate { + onUpdate { lastRotation = null - val info = updatePlaceInfo() ?: return@requestRotation null - val rotation = rotate(info) ?: return@requestRotation null + val info = updatePlaceInfo() ?: return@onUpdate null + val rotation = rotate(info) ?: return@onUpdate null RotationContext(rotation, rotationConfig) } - ) + } listen { if (sneakTicks > 0) it.sneak = true diff --git a/common/src/main/kotlin/com/lambda/task/tasks/BreakBlock.kt b/common/src/main/kotlin/com/lambda/task/tasks/BreakBlock.kt index 2a743ff18..66d010abb 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/BreakBlock.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/BreakBlock.kt @@ -21,11 +21,10 @@ import baritone.api.pathing.goals.GoalBlock import com.lambda.config.groups.RotationConfig import com.lambda.config.groups.InteractionConfig import com.lambda.context.SafeContext -import com.lambda.event.events.RotationEvent import com.lambda.event.events.TickEvent import com.lambda.event.events.WorldEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.RotationManager.requestRotation +import com.lambda.interaction.RotationManager.rotate import com.lambda.interaction.construction.context.BreakContext import com.lambda.interaction.visibilty.VisibilityChecker.lookAtBlock import com.lambda.module.modules.client.TaskFlowModule @@ -81,17 +80,17 @@ class BreakBlock @Ta5kBuilder constructor( } init { - requestRotation( - onUpdate = { - if (state != State.BREAKING) return@requestRotation null - if (!rotate || ctx.instantBreak) return@requestRotation null + rotate { + onUpdate { + if (state != State.BREAKING) return@onUpdate null + if (!rotate || ctx.instantBreak) return@onUpdate null lookAtBlock(blockPos, rotation, interact, sides) - }, - onReceive = { context -> + } + onReceive { context -> isValid = context.isValid } - ) + } listen { drop?.let { itemDrop -> diff --git a/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt b/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt index dcde41077..85ab56e2d 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt @@ -25,6 +25,7 @@ import com.lambda.event.events.PacketEvent import com.lambda.event.events.RotationEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.RotationManager.rotate import com.lambda.interaction.construction.blueprint.Blueprint import com.lambda.interaction.construction.blueprint.Blueprint.Companion.toStructure import com.lambda.interaction.construction.blueprint.DynamicBlueprint @@ -38,12 +39,10 @@ import com.lambda.interaction.construction.simulation.Simulation.Companion.simul import com.lambda.interaction.construction.verify.TargetState import com.lambda.module.modules.client.TaskFlowModule import com.lambda.task.Task -import com.lambda.task.tasks.PlaceBlock.State import com.lambda.util.BaritoneUtils import com.lambda.util.BlockUtils import com.lambda.util.BlockUtils.blockState import com.lambda.util.Communication.info -import com.lambda.util.Communication.warn import com.lambda.util.extension.Structure import net.minecraft.network.packet.s2c.play.BlockUpdateS2CPacket import net.minecraft.util.math.BlockPos @@ -119,7 +118,11 @@ class BuildTask @Ta5kBuilder constructor( } val result = resultsWithoutPending.minOrNull() ?: return@listen when (result) { - is BuildResult.Done -> { + is BuildResult.Done, + is BuildResult.Ignored, + is BuildResult.Unbreakable, + is BuildResult.Restricted, + is BuildResult.NoPermission -> { if (finishOnDone) success() } is BuildResult.NotVisible, is PlaceResult.NoIntegrity -> { @@ -150,10 +153,12 @@ class BuildTask @Ta5kBuilder constructor( } } - listen { event -> - if (currentPlacement == null) return@listen - if (!build.rotateForPlace) return@listen - event.context = currentPlacement?.rotation + rotate { + onUpdate { + if (currentPlacement == null) return@onUpdate null + if (!build.rotateForPlace) return@onUpdate null + currentPlacement?.rotation + } } listen {