diff --git a/common/src/main/java/me/shedaniel/architectury/hooks/tool/AxeItemHooks.java b/common/src/main/java/me/shedaniel/architectury/hooks/tool/AxeItemHooks.java
new file mode 100644
index 00000000..195f162b
--- /dev/null
+++ b/common/src/main/java/me/shedaniel/architectury/hooks/tool/AxeItemHooks.java
@@ -0,0 +1,37 @@
+package me.shedaniel.architectury.hooks.tool;
+
+import com.google.common.collect.ImmutableMap;
+import net.minecraft.world.item.AxeItem;
+import net.minecraft.world.level.block.Block;
+import net.minecraft.world.level.block.RotatedPillarBlock;
+
+import java.util.HashMap;
+
+public final class AxeItemHooks {
+ private AxeItemHooks() {
+ }
+
+ /**
+ * Adds a new stripping (interact with axe) interaction to the game.
+ *
+ * Note that both the input block and the result block must have the
+ * {@link net.minecraft.world.level.block.state.properties.BlockStateProperties#AXIS AXIS} property,
+ * and that the value of this property will be copied from the input block to the result block when the recipe
+ * is performed.
+ *
+ * @param input input block
+ * @param result result block
+ * @throws IllegalArgumentException if the input or result blocks do not have the
+ * {@link net.minecraft.world.level.block.state.properties.BlockStateProperties#AXIS AXIS} property.
+ */
+ public static void addStrippable(Block input, Block result) {
+ if (!input.defaultBlockState().hasProperty(RotatedPillarBlock.AXIS))
+ throw new IllegalArgumentException("Input block is missing required 'AXIS' property!");
+ if (!result.defaultBlockState().hasProperty(RotatedPillarBlock.AXIS))
+ throw new IllegalArgumentException("Result block is missing required 'AXIS' property!");
+ if (AxeItem.STRIPABLES instanceof ImmutableMap) {
+ AxeItem.STRIPABLES = new HashMap<>(AxeItem.STRIPABLES);
+ }
+ AxeItem.STRIPABLES.put(input, result);
+ }
+}
diff --git a/common/src/main/java/me/shedaniel/architectury/hooks/tool/HoeItemHooks.java b/common/src/main/java/me/shedaniel/architectury/hooks/tool/HoeItemHooks.java
new file mode 100644
index 00000000..361aae3f
--- /dev/null
+++ b/common/src/main/java/me/shedaniel/architectury/hooks/tool/HoeItemHooks.java
@@ -0,0 +1,31 @@
+package me.shedaniel.architectury.hooks.tool;
+
+import com.google.common.collect.ImmutableMap;
+import net.minecraft.world.item.HoeItem;
+import net.minecraft.world.level.block.Block;
+import net.minecraft.world.level.block.state.BlockState;
+
+import java.util.HashMap;
+
+public final class HoeItemHooks {
+ private HoeItemHooks() {
+ }
+
+ /**
+ * Adds a new tilling (interact with hoe) interaction to the game.
+ *
+ * Notes:
+ *
+ * - Blocks can only be tilled if they have no block above them.
+ *
+ *
+ * @param input input block
+ * @param result resulting state
+ */
+ public static void addTillable(Block input, BlockState result) {
+ if (HoeItem.TILLABLES instanceof ImmutableMap) {
+ HoeItem.TILLABLES = new HashMap<>(HoeItem.TILLABLES);
+ }
+ HoeItem.TILLABLES.put(input, result);
+ }
+}
diff --git a/common/src/main/java/me/shedaniel/architectury/hooks/tool/ShovelItemHooks.java b/common/src/main/java/me/shedaniel/architectury/hooks/tool/ShovelItemHooks.java
new file mode 100644
index 00000000..551d8ed5
--- /dev/null
+++ b/common/src/main/java/me/shedaniel/architectury/hooks/tool/ShovelItemHooks.java
@@ -0,0 +1,33 @@
+package me.shedaniel.architectury.hooks.tool;
+
+import com.google.common.collect.ImmutableMap;
+import net.minecraft.world.item.ShovelItem;
+import net.minecraft.world.level.block.Block;
+import net.minecraft.world.level.block.state.BlockState;
+
+import java.util.HashMap;
+
+public final class ShovelItemHooks {
+ private ShovelItemHooks() {
+ }
+
+ /**
+ * Adds a new flattening (interact with shovel) interaction to the game.
+ *
+ * Notes:
+ *
+ * - Blocks can only be flattened if they have no block above them.
+ * - {@linkplain net.minecraft.world.level.block.CampfireBlock Campfires} have a special case for being extinguished by a shovel,
+ * though you can override that using this method due to the check order.
+ *
+ *
+ * @param input input block
+ * @param result result block state
+ */
+ public static void addFlattenable(Block input, BlockState result) {
+ if (ShovelItem.FLATTENABLES instanceof ImmutableMap) {
+ ShovelItem.FLATTENABLES = new HashMap<>(ShovelItem.FLATTENABLES);
+ }
+ ShovelItem.FLATTENABLES.put(input, result);
+ }
+}
diff --git a/common/src/main/resources/architectury.accessWidener b/common/src/main/resources/architectury.accessWidener
index 80026d51..cbf6f035 100644
--- a/common/src/main/resources/architectury.accessWidener
+++ b/common/src/main/resources/architectury.accessWidener
@@ -39,4 +39,10 @@ accessible field net/minecraft/world/level/biome/BiomeSpecialEffects ambientAddi
mutable field net/minecraft/world/level/biome/BiomeSpecialEffects ambientAdditionsSettings Ljava/util/Optional;
accessible field net/minecraft/world/level/biome/BiomeSpecialEffects backgroundMusic Ljava/util/Optional;
mutable field net/minecraft/world/level/biome/BiomeSpecialEffects backgroundMusic Ljava/util/Optional;
-accessible method net/minecraft/world/level/storage/LevelResource (Ljava/lang/String;)V
\ No newline at end of file
+accessible method net/minecraft/world/level/storage/LevelResource (Ljava/lang/String;)V
+accessible field net/minecraft/world/item/AxeItem STRIPABLES Ljava/util/Map;
+mutable field net/minecraft/world/item/AxeItem STRIPABLES Ljava/util/Map;
+accessible field net/minecraft/world/item/ShovelItem FLATTENABLES Ljava/util/Map;
+mutable field net/minecraft/world/item/ShovelItem FLATTENABLES Ljava/util/Map;
+accessible field net/minecraft/world/item/HoeItem TILLABLES Ljava/util/Map;
+mutable field net/minecraft/world/item/HoeItem TILLABLES Ljava/util/Map;
\ No newline at end of file
diff --git a/fabric/src/main/resources/architectury.accessWidener b/fabric/src/main/resources/architectury.accessWidener
index 0d428916..e5df1e35 100644
--- a/fabric/src/main/resources/architectury.accessWidener
+++ b/fabric/src/main/resources/architectury.accessWidener
@@ -95,4 +95,10 @@ accessible field net/minecraft/world/level/biome/BiomeSpecialEffects ambientAddi
mutable field net/minecraft/world/level/biome/BiomeSpecialEffects ambientAdditionsSettings Ljava/util/Optional;
accessible field net/minecraft/world/level/biome/BiomeSpecialEffects backgroundMusic Ljava/util/Optional;
mutable field net/minecraft/world/level/biome/BiomeSpecialEffects backgroundMusic Ljava/util/Optional;
-accessible method net/minecraft/world/level/storage/LevelResource (Ljava/lang/String;)V
\ No newline at end of file
+accessible method net/minecraft/world/level/storage/LevelResource (Ljava/lang/String;)V
+accessible field net/minecraft/world/item/AxeItem STRIPABLES Ljava/util/Map;
+mutable field net/minecraft/world/item/AxeItem STRIPABLES Ljava/util/Map;
+accessible field net/minecraft/world/item/ShovelItem FLATTENABLES Ljava/util/Map;
+mutable field net/minecraft/world/item/ShovelItem FLATTENABLES Ljava/util/Map;
+accessible field net/minecraft/world/item/HoeItem TILLABLES Ljava/util/Map;
+mutable field net/minecraft/world/item/HoeItem TILLABLES Ljava/util/Map;
\ No newline at end of file
diff --git a/forge/src/main/resources/META-INF/accesstransformer.cfg b/forge/src/main/resources/META-INF/accesstransformer.cfg
index b4afaea4..efb3bcfa 100644
--- a/forge/src/main/resources/META-INF/accesstransformer.cfg
+++ b/forge/src/main/resources/META-INF/accesstransformer.cfg
@@ -33,3 +33,6 @@ public-f net.minecraft.world.biome.BiomeAmbience field_242524_f # foliageColor
public-f net.minecraft.world.biome.BiomeAmbience field_242525_g # grassColor
public-f net.minecraft.world.biome.BiomeAmbience field_242526_h # grassColorModifier
public net.minecraft.world.storage.FolderName (Ljava/lang/String;)V
+public-f net.minecraft.item.AxeItem field_203176_a # STRIPABLES
+public-f net.minecraft.item.ShovelItem field_195955_e # FLATTENABLES
+public-f net.minecraft.item.HoeItem field_195973_b # TILLABLES
diff --git a/gradle.properties b/gradle.properties
index 148596e2..365c7082 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -6,7 +6,7 @@ supported_version=1.16.4/5
archives_base_name=architectury
archives_base_name_snapshot=architectury-snapshot
-base_version=1.20
+base_version=1.21
maven_group=me.shedaniel
fabric_loader_version=0.11.1
diff --git a/testmod-common/src/main/java/me/shedaniel/architectury/test/TestMod.java b/testmod-common/src/main/java/me/shedaniel/architectury/test/TestMod.java
index aac11d48..b494e2bf 100644
--- a/testmod-common/src/main/java/me/shedaniel/architectury/test/TestMod.java
+++ b/testmod-common/src/main/java/me/shedaniel/architectury/test/TestMod.java
@@ -27,6 +27,7 @@ import me.shedaniel.architectury.test.debug.client.ClientOverlayMessageSink;
import me.shedaniel.architectury.test.entity.TestEntity;
import me.shedaniel.architectury.test.events.DebugEvents;
import me.shedaniel.architectury.test.gamerule.TestGameRules;
+import me.shedaniel.architectury.test.item.TestBlockInteractions;
import me.shedaniel.architectury.test.networking.TestModNet;
import me.shedaniel.architectury.test.particle.TestParticles;
import me.shedaniel.architectury.test.registry.TestRegistries;
@@ -49,6 +50,7 @@ public class TestMod {
TestTrades.init();
TestParticles.initialize();
TestModNet.initialize();
+ TestBlockInteractions.init();
if (Platform.getEnvironment() == Env.CLIENT) {
TestKeybinds.initialize();
EntityRenderers.register(TestEntity.TYPE, MinecartRenderer::new);
diff --git a/testmod-common/src/main/java/me/shedaniel/architectury/test/item/TestBlockInteractions.java b/testmod-common/src/main/java/me/shedaniel/architectury/test/item/TestBlockInteractions.java
new file mode 100644
index 00000000..dd18b40c
--- /dev/null
+++ b/testmod-common/src/main/java/me/shedaniel/architectury/test/item/TestBlockInteractions.java
@@ -0,0 +1,17 @@
+package me.shedaniel.architectury.test.item;
+
+import me.shedaniel.architectury.hooks.tool.AxeItemHooks;
+import me.shedaniel.architectury.hooks.tool.HoeItemHooks;
+import me.shedaniel.architectury.hooks.tool.ShovelItemHooks;
+import net.minecraft.world.level.block.Blocks;
+
+public final class TestBlockInteractions {
+ private TestBlockInteractions() {
+ }
+
+ public static void init() {
+ AxeItemHooks.addStrippable(Blocks.QUARTZ_PILLAR, Blocks.OAK_LOG);
+ ShovelItemHooks.addFlattenable(Blocks.IRON_ORE, Blocks.DIAMOND_BLOCK.defaultBlockState());
+ HoeItemHooks.addTillable(Blocks.COAL_BLOCK, Blocks.DIAMOND_BLOCK.defaultBlockState());
+ }
+}