Skip to content

Commit c0299a1

Browse files
authored
Move some nuker and printer checks into PlayerBuildLayerUtils, allowing printer to make use of flatten modes and baritone selections (#283)
1 parent c400e1f commit c0299a1

File tree

5 files changed

+160
-87
lines changed

5 files changed

+160
-87
lines changed

src/main/kotlin/com/lambda/interaction/construction/blueprint/TickingBlueprint.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import com.lambda.util.extension.Structure
2323
import net.minecraft.util.math.Vec3i
2424

2525
data class TickingBlueprint(
26-
val onTick: SafeContext.(Structure) -> Structure? = { it },
26+
val onTick: SafeContext.(Structure) -> Structure? = { it }
2727
) : Blueprint() {
2828
fun tick() =
2929
runSafe {
@@ -48,7 +48,7 @@ data class TickingBlueprint(
4848
}
4949

5050
fun tickingBlueprint(
51-
onTick: SafeContext.(Structure) -> Structure?,
51+
onTick: SafeContext.(Structure) -> Structure?
5252
) = TickingBlueprint(onTick)
5353
}
5454
}

src/main/kotlin/com/lambda/module/modules/world/Nuker.kt

Lines changed: 6 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ package com.lambda.module.modules.world
2020
import com.lambda.config.AutomationConfig.Companion.setDefaultAutomationConfig
2121
import com.lambda.config.applyEdits
2222
import com.lambda.context.SafeContext
23-
import com.lambda.interaction.BaritoneManager
24-
import com.lambda.interaction.construction.blueprint.TickingBlueprint
2523
import com.lambda.interaction.construction.blueprint.TickingBlueprint.Companion.tickingBlueprint
2624
import com.lambda.interaction.construction.verify.TargetState
2725
import com.lambda.module.Module
@@ -30,12 +28,11 @@ import com.lambda.task.RootTask.run
3028
import com.lambda.task.Task
3129
import com.lambda.task.tasks.BuildTask.Companion.build
3230
import com.lambda.util.BlockUtils.blockPos
33-
import com.lambda.util.BlockUtils.blockState
34-
import com.lambda.util.BlockUtils.isNotEmpty
35-
import com.lambda.util.math.MathUtils.ceilToInt
3631
import net.minecraft.block.Blocks
3732
import net.minecraft.util.math.BlockPos
38-
import net.minecraft.util.math.Direction
33+
import com.lambda.util.PlayerBuildLayerUtils.FlattenMode
34+
import com.lambda.util.PlayerBuildLayerUtils.isInFlatten
35+
import com.lambda.util.PlayerBuildLayerUtils.isInBaritoneSelection
3936

4037
object Nuker : Module(
4138
name = "Nuker",
@@ -69,12 +66,12 @@ object Nuker : Module(
6966
if (onGround && !player.isOnGround) return@tickingBlueprint emptyMap()
7067

7168
val selection = BlockPos.iterateOutwards(player.blockPos, width, height, width)
72-
.asSequence()
7369
.map { it.blockPos }
70+
.asSequence()
7471
.filter { !world.isAir(it) }
75-
.filter { flattenMode == FlattenMode.None || isInFlatten(it) }
72+
.filter { !baritoneSelection || isInBaritoneSelection(it) != inverseSelection }
73+
.filter { isInFlatten(it, flattenMode, sneakLowersFlatten, baritoneSelection, inverseSelection) }
7674
.filter { isWithinDigDirection(it) }
77-
.filter { isInBaritoneSelection(it) == !inverseSelection }
7875
.associateWith { if (breakConfig.fillFluids) TargetState.Air else TargetState.Empty }
7976

8077
if (fillFloor) {
@@ -94,43 +91,6 @@ object Nuker : Module(
9491
}
9592
}
9693

97-
private fun SafeContext.isInFlatten(pos: BlockPos): Boolean {
98-
if (flattenMode == FlattenMode.Staircase) {
99-
val up = pos.up()
100-
if ((blockState(up).isNotEmpty && (!baritoneSelection || isInBaritoneSelection(up)))
101-
|| (blockState(up.east()).isNotEmpty && (!baritoneSelection || isInBaritoneSelection(up.east())))
102-
|| (blockState(up.south()).isNotEmpty && (!baritoneSelection || isInBaritoneSelection(up.south())))
103-
|| (blockState(up.west()).isNotEmpty && (!baritoneSelection || isInBaritoneSelection(up.west())))
104-
|| (blockState(up.north()).isNotEmpty && (!baritoneSelection || isInBaritoneSelection(up.north())))
105-
) { return false }
106-
}
107-
108-
val flattenY = player.y.ceilToInt()
109-
val playerPos = player.blockPos
110-
val flattenLevel =
111-
if (sneakLowersFlatten && player.isSneaking) flattenY - 1
112-
else flattenY
113-
114-
if (!flattenMode.isSmart && pos.y < flattenLevel)
115-
return false
116-
117-
if (pos == player.supportingBlockPos) return false
118-
119-
val playerLookDir = player.horizontalFacing
120-
val smartFlattenDir =
121-
if (flattenMode == FlattenMode.Smart) playerLookDir
122-
else playerLookDir?.opposite
123-
124-
if (pos.y >= flattenLevel) return true
125-
126-
val zeroedPos = pos.add(-playerPos.x, -flattenY, -playerPos.z)
127-
128-
return (zeroedPos.x < 0 && smartFlattenDir == Direction.EAST)
129-
|| (zeroedPos.z < 0 && smartFlattenDir == Direction.SOUTH)
130-
|| (zeroedPos.x > 0 && smartFlattenDir == Direction.WEST)
131-
|| (zeroedPos.z > 0 && smartFlattenDir == Direction.NORTH)
132-
}
133-
13494
private fun SafeContext.isWithinDigDirection(pos: BlockPos): Boolean {
13595
val playerPos = player.blockPos
13696
return when (directionalDig) {
@@ -142,27 +102,6 @@ object Nuker : Module(
142102
}
143103
}
144104

145-
private fun isInBaritoneSelection(pos: BlockPos) =
146-
if (!baritoneSelection) true
147-
else BaritoneManager.primary?.selectionManager?.selections?.any {
148-
val min = it.min()
149-
val max = it.max()
150-
pos.x >= min.x && pos.x <= max.x
151-
&& pos.y >= min.y && pos.y <= max.y
152-
&& pos.z >= min.z && pos.z <= max.z
153-
} ?: false
154-
155-
private enum class FlattenMode {
156-
None,
157-
Standard,
158-
Smart,
159-
ReverseSmart,
160-
Staircase;
161-
162-
val isSmart
163-
get() = this == Smart || this == ReverseSmart
164-
}
165-
166105
private enum class DigDirection {
167106
None,
168107
East,

src/main/kotlin/com/lambda/module/modules/world/Printer.kt

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,12 @@
1818
package com.lambda.module.modules.world
1919

2020
import com.lambda.config.AutomationConfig.Companion.setDefaultAutomationConfig
21-
import com.lambda.interaction.construction.blueprint.TickingBlueprint
21+
import com.lambda.context.SafeContext
22+
import com.lambda.interaction.construction.blueprint.TickingBlueprint.Companion.tickingBlueprint
23+
import com.lambda.interaction.construction.simulation.result.BuildResult
24+
import com.lambda.interaction.construction.simulation.result.Contextual
25+
import com.lambda.interaction.construction.simulation.result.results.BreakResult
26+
import com.lambda.interaction.construction.simulation.result.results.InteractResult
2227
import com.lambda.interaction.construction.verify.TargetState
2328
import com.lambda.module.Module
2429
import com.lambda.module.tag.ModuleTag
@@ -27,6 +32,12 @@ import com.lambda.task.Task
2732
import com.lambda.task.tasks.BuildTask.Companion.build
2833
import com.lambda.util.BlockUtils.blockPos
2934
import com.lambda.util.Communication.logError
35+
import com.lambda.util.Describable
36+
import com.lambda.util.NamedEnum
37+
import com.lambda.util.PlayerBuildLayerUtils.isInBaritoneSelection
38+
import com.lambda.util.PlayerBuildLayerUtils.isInFlatten
39+
import com.lambda.util.PlayerBuildLayerUtils.FlattenMode
40+
import com.lambda.util.PlayerBuildLayerUtils.inSchematic
3041
import fi.dy.masa.litematica.data.DataManager
3142
import fi.dy.masa.litematica.world.SchematicWorldHandler
3243
import net.minecraft.util.math.BlockPos
@@ -38,6 +49,11 @@ object Printer : Module(
3849
) {
3950
private val range by setting("Range", 5, 1..7, 1, description = "The range around the player to check for blocks to print")
4051
private val air by setting("Air", false, description = "Consider breaking blocks in the world that are air in the schematic.\nNote: Breaking can also be disabled in the Automation Config.")
52+
private val flattenMode by setting("Flatten Mode", FlattenMode.Standard, description = "Configures what blocks to break relative to the player's Y level")
53+
private val flattenModeApply by setting("Flatten Apply Mode", FlattenModeApply.BreakOnly, description = "Configures whether the flatten mode applies to breaking, placing, or both") { flattenMode != FlattenMode.None }
54+
private val baritoneSelection by setting("Baritone Selection", false, "Restricts block breaking and placing to your baritone selection")
55+
private val inverseSelection by setting("Inverse Selection", false, "Break and place blocks outside of the baritone selection and ignores blocks inside") { baritoneSelection }
56+
private val sneakLowersFlatten by setting("Sneak Lowers Flatten", false, "When enabled, sneaking will lower the flattening level by 1, allowing you to mine the block below you")
4157

4258
private var buildTask: Task<*>? = null
4359

@@ -50,29 +66,46 @@ object Printer : Module(
5066
disable()
5167
return@onEnable
5268
}
53-
buildTask = TickingBlueprint {
54-
val schematicWorld = SchematicWorldHandler.getSchematicWorld() ?: return@TickingBlueprint emptyMap()
69+
buildTask = tickingBlueprint {
70+
val schematicWorld = SchematicWorldHandler.getSchematicWorld() ?: return@tickingBlueprint emptyMap()
5571
BlockPos.iterateOutwards(player.blockPos, range, range, range)
5672
.map { it.blockPos }
5773
.asSequence()
74+
.filter { pos -> !baritoneSelection || isInBaritoneSelection(pos) != inverseSelection }
5875
.filter { DataManager.getRenderLayerRange().isPositionWithinRange(it) && inSchematic(it) }
5976
.associateWith { TargetState.State(schematicWorld.getBlockState(it)) }
6077
.filter { air || !it.value.blockState.isAir }
61-
}.build(finishOnDone = false).run()
78+
}.build(finishOnDone = false, buildResultFilter = { filterBuildResults(it) }).run()
6279
}
6380

6481
onDisable { buildTask?.cancel(); buildTask = null }
6582
}
6683

67-
private fun inSchematic(pos: BlockPos): Boolean {
68-
val placementManager = DataManager.getSchematicPlacementManager()
69-
return placementManager?.getAllPlacementsTouchingChunk(pos)?.any {
70-
it.placement.isEnabled && it.bb.containsPos(pos)
71-
} ?: false
84+
/**
85+
* Checks a block position against the current settings to determine whether a build result at that position should be considered for building.
86+
*
87+
* @return true if the build result should be built, false if it should be ignored
88+
*/
89+
private fun SafeContext.filterBuildResults(buildResult: BuildResult): Boolean {
90+
return if (buildResult !is Contextual ||
91+
buildResult is InteractResult && !flattenModeApply.placing ||
92+
buildResult is BreakResult && !flattenModeApply.breaking) true
93+
else isInFlatten(buildResult.pos, flattenMode, sneakLowersFlatten, baritoneSelection, inverseSelection)
7294
}
7395

7496
private fun litematicaAvailable(): Boolean = runCatching {
7597
Class.forName("fi.dy.masa.litematica.Litematica")
7698
true
7799
}.getOrDefault(false)
100+
101+
private enum class FlattenModeApply(
102+
override val displayName: String,
103+
val breaking: Boolean,
104+
val placing: Boolean,
105+
override val description: String
106+
) : NamedEnum, Describable {
107+
BreakOnly("Break Only", true, false, "Only applies flattening logic to blocks that are being broken"),
108+
PlaceOnly("Place Only", false, true, "Only applies flattening logic to blocks that are being placed"),
109+
Both("Both", true, true, "Applies flattening logic to all blocks, whether being placed or broken")
110+
}
78111
}

src/main/kotlin/com/lambda/task/tasks/BuildTask.kt

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ class BuildTask private constructor(
6666
private val finishOnDone: Boolean,
6767
private val collectDrops: Boolean,
6868
private val lifeMaintenance: Boolean,
69-
automated: Automated
69+
automated: Automated,
70+
private val buildResultFilter: SafeContext.(BuildResult) -> Boolean,
7071
) : Task<Structure>(), Automated by automated {
7172
override val name: String get() = "Building $blueprint with ${(breaks / (age / 20.0 + 0.001)).format(precision = 1)} b/s ${(placements / (age / 20.0 + 0.001)).format(precision = 1)} p/s"
7273

@@ -136,6 +137,7 @@ class BuildTask private constructor(
136137
it.blockPos == finalResult.pos
137138
} && (finalResult !is Contextual || finalResult.context.canUse())
138139
}
140+
.filter { buildResultFilter(it) }
139141
.sorted()
140142

141143
val bestResult = viableResults.firstOrNull() ?: return
@@ -240,33 +242,37 @@ class BuildTask private constructor(
240242
finishOnDone: Boolean = true,
241243
collectDrops: Boolean = buildConfig.collectDrops,
242244
lifeMaintenance: Boolean = false,
245+
buildResultFilter: SafeContext.(BuildResult) -> Boolean = { true },
243246
blueprint: () -> Blueprint
244-
) = BuildTask(blueprint(), finishOnDone, collectDrops, lifeMaintenance, this)
247+
) = BuildTask(blueprint(), finishOnDone, collectDrops, lifeMaintenance, this, buildResultFilter)
245248

246249
@Ta5kBuilder
247250
context(automated: Automated)
248251
fun Structure.build(
249252
finishOnDone: Boolean = true,
250253
collectDrops: Boolean = automated.buildConfig.collectDrops,
251-
lifeMaintenance: Boolean = false
252-
) = BuildTask(toBlueprint(), finishOnDone, collectDrops, lifeMaintenance, automated)
254+
lifeMaintenance: Boolean = false,
255+
buildResultFilter: SafeContext.(BuildResult) -> Boolean = { true },
256+
) = BuildTask(toBlueprint(), finishOnDone, collectDrops, lifeMaintenance, automated, buildResultFilter)
253257

254258
@Ta5kBuilder
255259
context(automated: Automated)
256260
fun Blueprint.build(
257261
finishOnDone: Boolean = true,
258262
collectDrops: Boolean = automated.buildConfig.collectDrops,
259-
lifeMaintenance: Boolean = false
260-
) = BuildTask(this, finishOnDone, collectDrops, lifeMaintenance, automated)
263+
lifeMaintenance: Boolean = false,
264+
buildResultFilter: SafeContext.(BuildResult) -> Boolean = { true },
265+
) = BuildTask(this, finishOnDone, collectDrops, lifeMaintenance, automated, buildResultFilter)
261266

262267
@Ta5kBuilder
263268
fun Automated.breakAndCollectBlock(
264269
blockPos: BlockPos,
265270
finishOnDone: Boolean = true,
266-
lifeMaintenance: Boolean = false
271+
lifeMaintenance: Boolean = false,
272+
buildResultFilter: SafeContext.(BuildResult) -> Boolean = { true },
267273
) = BuildTask(
268-
blockPos.toStructure(TargetState.Air).toBlueprint(),
269-
finishOnDone, true, lifeMaintenance, this
274+
blockPos.toStructure(TargetState.Empty).toBlueprint(),
275+
finishOnDone, true, lifeMaintenance, this, buildResultFilter
270276
)
271277
}
272278
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Copyright 2026 Lambda
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
18+
package com.lambda.util
19+
20+
import com.lambda.context.SafeContext
21+
import com.lambda.interaction.BaritoneManager
22+
import com.lambda.util.BlockUtils.blockState
23+
import com.lambda.util.BlockUtils.isNotEmpty
24+
import com.lambda.util.math.MathUtils.ceilToInt
25+
import fi.dy.masa.litematica.data.DataManager
26+
import net.minecraft.util.math.BlockPos
27+
import net.minecraft.util.math.Direction
28+
29+
object PlayerBuildLayerUtils {
30+
fun SafeContext.isInFlatten(pos: BlockPos, flattenMode: FlattenMode, sneakLowersFlatten: Boolean, baritoneSelection: Boolean, baritoneSelectionInverted: Boolean): Boolean {
31+
if (flattenMode == FlattenMode.None) return true
32+
33+
if (flattenMode == FlattenMode.Staircase) {
34+
val up = pos.up()
35+
if ((blockState(up).isNotEmpty && (!baritoneSelection || (isInBaritoneSelection(up) == !baritoneSelectionInverted)))
36+
|| (blockState(up.east()).isNotEmpty && (!baritoneSelection || (isInBaritoneSelection(up.east()) == !baritoneSelectionInverted)))
37+
|| (blockState(up.south()).isNotEmpty && (!baritoneSelection || (isInBaritoneSelection(up.south()) == !baritoneSelectionInverted)))
38+
|| (blockState(up.west()).isNotEmpty && (!baritoneSelection || (isInBaritoneSelection(up.west()) == !baritoneSelectionInverted)))
39+
|| (blockState(up.north()).isNotEmpty && (!baritoneSelection || (isInBaritoneSelection(up.north()) == !baritoneSelectionInverted)))
40+
) { return false }
41+
}
42+
43+
val flattenY = player.y.ceilToInt()
44+
val playerPos = player.blockPos
45+
val flattenLevel =
46+
if (sneakLowersFlatten && player.isSneaking) flattenY - 1
47+
else flattenY
48+
49+
if (!flattenMode.isSmart && pos.y < flattenLevel)
50+
return false
51+
52+
if (pos == player.supportingBlockPos) return false
53+
54+
val playerLookDir = player.horizontalFacing
55+
val smartFlattenDir =
56+
if (flattenMode == FlattenMode.Smart) playerLookDir
57+
else playerLookDir?.opposite
58+
59+
if (pos.y >= flattenLevel) return true
60+
61+
val zeroedPos = pos.add(-playerPos.x, -flattenY, -playerPos.z)
62+
63+
return (zeroedPos.x < 0 && smartFlattenDir == Direction.EAST)
64+
|| (zeroedPos.z < 0 && smartFlattenDir == Direction.SOUTH)
65+
|| (zeroedPos.x > 0 && smartFlattenDir == Direction.WEST)
66+
|| (zeroedPos.z > 0 && smartFlattenDir == Direction.NORTH)
67+
}
68+
69+
fun isInBaritoneSelection(pos: BlockPos) =
70+
BaritoneManager.primary?.selectionManager?.selections?.any {
71+
val min = it.min()
72+
val max = it.max()
73+
pos.x >= min.x && pos.x <= max.x
74+
&& pos.y >= min.y && pos.y <= max.y
75+
&& pos.z >= min.z && pos.z <= max.z
76+
} ?: false
77+
78+
fun inSchematic(pos: BlockPos): Boolean {
79+
val placementManager = DataManager.getSchematicPlacementManager()
80+
return placementManager?.getAllPlacementsTouchingChunk(pos)?.any {
81+
it.placement.isEnabled && it.bb.containsPos(pos)
82+
} ?: false
83+
}
84+
85+
enum class FlattenMode {
86+
None,
87+
Standard,
88+
Smart,
89+
ReverseSmart,
90+
Staircase;
91+
92+
val isSmart
93+
get() = this == Smart || this == ReverseSmart
94+
}
95+
}

0 commit comments

Comments
 (0)