Skip to content

Commit 9721c5f

Browse files
authored
Feature: Till farmland and leave growing plants alone (#348)
1 parent fae1aad commit 9721c5f

5 files changed

Lines changed: 212 additions & 10 deletions

File tree

‎src/main/kotlin/com/lambda/interaction/construction/simulation/processing/ProcessorRegistry.kt‎

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import com.lambda.util.BlockUtils.matches
2626
import com.lambda.util.ReflectionUtils.getInstances
2727
import net.minecraft.block.BlockState
2828
import net.minecraft.item.ItemStack
29+
import net.minecraft.registry.tag.BlockTags
2930
import net.minecraft.state.property.Properties
3031
import net.minecraft.util.math.BlockPos
3132
import java.util.*
@@ -64,14 +65,6 @@ object ProcessorRegistry : Loadable {
6465
Properties.FLOWER_AMOUNT,
6566
Properties.EAST_WALL_SHAPE, Properties.SOUTH_WALL_SHAPE, Properties.WEST_WALL_SHAPE, Properties.NORTH_WALL_SHAPE,
6667
Properties.EAST_WIRE_CONNECTION, Properties.SOUTH_WIRE_CONNECTION, Properties.WEST_WIRE_CONNECTION, Properties.NORTH_WIRE_CONNECTION,
67-
Properties.AGE_1,
68-
Properties.AGE_2,
69-
Properties.AGE_3,
70-
Properties.AGE_4,
71-
Properties.AGE_5,
72-
Properties.AGE_7,
73-
Properties.AGE_15,
74-
Properties.AGE_25,
7568
Properties.BITES,
7669
Properties.CANDLES,
7770
Properties.DELAY,
@@ -87,7 +80,6 @@ object ProcessorRegistry : Loadable {
8780
Properties.NOTE,
8881
Properties.PICKLES,
8982
Properties.POWER,
90-
Properties.STAGE,
9183
Properties.CHARGES,
9284
Properties.COMPARATOR_MODE,
9385
Properties.INSTRUMENT,
@@ -122,6 +114,18 @@ object ProcessorRegistry : Loadable {
122114
Properties.NOTE
123115
)
124116

117+
val growthProperties = setOf(
118+
Properties.AGE_1,
119+
Properties.AGE_2,
120+
Properties.AGE_3,
121+
Properties.AGE_4,
122+
Properties.AGE_5,
123+
Properties.AGE_7,
124+
Properties.AGE_15,
125+
Properties.AGE_25,
126+
Properties.STAGE
127+
)
128+
125129
override fun load() = "Loaded ${propertyPreProcessors.size} pre processors"
126130

127131
/**
@@ -156,7 +160,7 @@ object ProcessorRegistry : Loadable {
156160
}
157161
if (!omitInteraction) {
158162
if (state.block != expectedState.block) {
159-
if (state.isReplaceable) {
163+
if (state.canBeReplacedBy(expectedState)) {
160164
propertyPreProcessors.forEach { processor ->
161165
if (processor.acceptsState(state, expectedState)) {
162166
with(processor) { preProcess(state, expectedState, pos) }
@@ -174,6 +178,12 @@ object ProcessorRegistry : Loadable {
174178
}
175179
complete()
176180
}
181+
182+
// Hoes only till with air above, so things like short grass are broken before a crop goes in
183+
private fun BlockState.canBeReplacedBy(expectedState: BlockState) = when {
184+
expectedState.isIn(BlockTags.CROPS) -> isReplaceable && (isAir || !fluidState.isEmpty)
185+
else -> isReplaceable
186+
}
177187
}
178188

179189
data class PreProcessingData(val info: PreProcessingInfo, val pos: BlockPos)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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.interaction.construction.simulation.processing.preprocessors.property.placement.post
19+
20+
import com.lambda.context.SafeContext
21+
import com.lambda.interaction.construction.simulation.processing.PreProcessingInfoAccumulator
22+
import com.lambda.interaction.construction.simulation.processing.PropertyPostProcessor
23+
import net.minecraft.block.BlockState
24+
import net.minecraft.block.Blocks
25+
import net.minecraft.util.math.BlockPos
26+
27+
@Suppress("unused")
28+
object FarmlandPostProcessor : PropertyPostProcessor {
29+
override fun acceptsState(state: BlockState, targetState: BlockState) =
30+
state.block == Blocks.FARMLAND && targetState.block == Blocks.FARMLAND && state != targetState
31+
32+
context(safeContext: SafeContext)
33+
override fun PreProcessingInfoAccumulator.preProcess(state: BlockState, targetState: BlockState, pos: BlockPos) =
34+
omitInteraction()
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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.interaction.construction.simulation.processing.preprocessors.property.placement.post
19+
20+
import com.lambda.context.SafeContext
21+
import com.lambda.interaction.construction.simulation.processing.PreProcessingInfoAccumulator
22+
import com.lambda.interaction.construction.simulation.processing.ProcessorRegistry.growthProperties
23+
import com.lambda.interaction.construction.simulation.processing.PropertyPostProcessor
24+
import net.minecraft.block.BlockState
25+
import net.minecraft.util.math.BlockPos
26+
27+
@Suppress("unused")
28+
object GrowthPostProcessor : PropertyPostProcessor {
29+
override fun acceptsState(state: BlockState, targetState: BlockState) =
30+
state.block == targetState.block && state != targetState &&
31+
growthProperties.all { it !in state || state.get(it) <= targetState.get(it) } &&
32+
state.entries.all { (property, value) -> property in growthProperties || targetState.entries[property] == value }
33+
34+
context(safeContext: SafeContext)
35+
override fun PreProcessingInfoAccumulator.preProcess(state: BlockState, targetState: BlockState, pos: BlockPos) {
36+
addIgnores(*growthProperties.toTypedArray())
37+
omitInteraction()
38+
}
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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.interaction.construction.simulation.processing.preprocessors.property.placement.pre
19+
20+
import com.lambda.context.SafeContext
21+
import com.lambda.interaction.construction.simulation.processing.PreProcessingInfoAccumulator
22+
import com.lambda.interaction.construction.simulation.processing.ProcessorRegistry.growthProperties
23+
import com.lambda.interaction.construction.simulation.processing.PropertyPreProcessor
24+
import net.minecraft.block.BlockState
25+
import net.minecraft.util.math.BlockPos
26+
27+
@Suppress("unused")
28+
object GrowthPreProcessor : PropertyPreProcessor {
29+
override fun acceptsState(state: BlockState, targetState: BlockState) =
30+
growthProperties.any { it in targetState }
31+
32+
context(safeContext: SafeContext)
33+
override fun PreProcessingInfoAccumulator.preProcess(state: BlockState, targetState: BlockState, pos: BlockPos) =
34+
addIgnores(*growthProperties.toTypedArray())
35+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
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.interaction.construction.simulation.processing.preprocessors.state
19+
20+
import com.lambda.context.SafeContext
21+
import com.lambda.interaction.construction.simulation.processing.PreProcessingInfoAccumulator
22+
import com.lambda.interaction.construction.simulation.processing.StateProcessor
23+
import com.lambda.util.BlockUtils.blockState
24+
import com.lambda.util.item.ItemUtils
25+
import com.lambda.util.player.SlotUtils.hotbarAndInventoryStacks
26+
import net.minecraft.block.BlockState
27+
import net.minecraft.block.Blocks
28+
import net.minecraft.client.network.ClientPlayerEntity
29+
import net.minecraft.item.Item
30+
import net.minecraft.item.Items
31+
import net.minecraft.util.math.BlockPos
32+
import net.minecraft.util.math.Direction
33+
34+
@Suppress("unused")
35+
object FarmlandStateProcessor : StateProcessor {
36+
private val tillResults = mapOf(
37+
Blocks.GRASS_BLOCK to Blocks.FARMLAND,
38+
Blocks.DIRT to Blocks.FARMLAND,
39+
Blocks.DIRT_PATH to Blocks.FARMLAND,
40+
Blocks.COARSE_DIRT to Blocks.DIRT,
41+
Blocks.ROOTED_DIRT to Blocks.DIRT
42+
)
43+
44+
private val sourceBlocks = listOf(Blocks.DIRT, Blocks.GRASS_BLOCK)
45+
46+
override fun acceptsState(state: BlockState, targetState: BlockState) =
47+
targetState.block == Blocks.FARMLAND && state.block != targetState.block &&
48+
(state.isReplaceable || state.block in tillResults)
49+
50+
context(safeContext: SafeContext)
51+
override fun PreProcessingInfoAccumulator.preProcess(state: BlockState, targetState: BlockState, pos: BlockPos) {
52+
val player = safeContext.player
53+
noCaching()
54+
55+
if (state.isReplaceable) {
56+
if (player.carries(Items.FARMLAND)) return
57+
val sourceBlock = sourceBlocks.firstOrNull { player.carries(it.asItem()) } ?: Blocks.DIRT
58+
setExpectedState(sourceBlock.defaultState)
59+
setItem(sourceBlock.asItem())
60+
return
61+
}
62+
63+
val tilledBlock = tillResults[state.block] ?: return
64+
if (!safeContext.blockState(pos.up()).isAir) {
65+
omitInteraction()
66+
return
67+
}
68+
val hoe = player.findHoe() ?: Items.NETHERITE_HOE
69+
70+
setExpectedState(tilledBlock.defaultState)
71+
setItem(hoe)
72+
setPlacing(false)
73+
retainSides { it != Direction.DOWN }
74+
setSneak(false)
75+
}
76+
77+
private fun ClientPlayerEntity.findHoe(): Item? =
78+
mainHandStack.item.takeIf { it in ItemUtils.hoes }
79+
?: hotbarAndInventoryStacks.firstOrNull { it.item in ItemUtils.hoes }?.item
80+
81+
private fun ClientPlayerEntity.carries(item: Item) =
82+
hotbarAndInventoryStacks.any { it.item == item }
83+
}

0 commit comments

Comments
 (0)