diff --git a/common/src/main/java/dev/architectury/hooks/item/tool/AxeItemHooks.java b/common/src/main/java/dev/architectury/hooks/item/tool/AxeItemHooks.java
new file mode 100644
index 00000000..5a291e09
--- /dev/null
+++ b/common/src/main/java/dev/architectury/hooks/item/tool/AxeItemHooks.java
@@ -0,0 +1,37 @@
+package dev.architectury.hooks.item.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.STRIPPABLES instanceof ImmutableMap) {
+ AxeItem.STRIPPABLES = new HashMap<>(AxeItem.STRIPPABLES);
+ }
+ AxeItem.STRIPPABLES.put(input, result);
+ }
+}
diff --git a/common/src/main/java/dev/architectury/hooks/item/tool/HoeItemHooks.java b/common/src/main/java/dev/architectury/hooks/item/tool/HoeItemHooks.java
new file mode 100644
index 00000000..ef2f0468
--- /dev/null
+++ b/common/src/main/java/dev/architectury/hooks/item/tool/HoeItemHooks.java
@@ -0,0 +1,38 @@
+package dev.architectury.hooks.item.tool;
+
+import com.google.common.collect.ImmutableMap;
+import com.mojang.datafixers.util.Pair;
+import net.minecraft.world.item.HoeItem;
+import net.minecraft.world.item.context.UseOnContext;
+import net.minecraft.world.level.block.Block;
+
+import java.util.HashMap;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+public final class HoeItemHooks {
+ private HoeItemHooks() {
+ }
+
+ /**
+ * Adds a new tilling (interact with hoe) interaction to the game.
+ *
+ * Tilling uses a predicate/consumer pair system:
+ *
+ * - First, the game tests the context using the predicate.
+ * - Then, if the predicate returns {@code true}, the game will, on the server side only,
+ * invoke the action and then damage the hoe item by 1.
+ * - Otherwise, no action will be invoked.
+ *
+ *
+ * @param input input block
+ * @param predicate context predicate
+ * @param action action to run
+ */
+ public static void addTillable(Block input, Predicate predicate, Consumer action) {
+ if (HoeItem.TILLABLES instanceof ImmutableMap) {
+ HoeItem.TILLABLES = new HashMap<>(HoeItem.TILLABLES);
+ }
+ HoeItem.TILLABLES.put(input, new Pair<>(predicate, action));
+ }
+}
diff --git a/common/src/main/java/dev/architectury/hooks/item/tool/ShovelItemHooks.java b/common/src/main/java/dev/architectury/hooks/item/tool/ShovelItemHooks.java
new file mode 100644
index 00000000..b709b339
--- /dev/null
+++ b/common/src/main/java/dev/architectury/hooks/item/tool/ShovelItemHooks.java
@@ -0,0 +1,33 @@
+package dev.architectury.hooks.item.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 9376d749..c20dffd6 100644
--- a/common/src/main/resources/architectury.accessWidener
+++ b/common/src/main/resources/architectury.accessWidener
@@ -40,4 +40,10 @@ mutable field net/minecraft/world/level/biome/BiomeSpecialEffects ambientAdditio
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
-accessible class net/minecraft/world/level/block/entity/BlockEntityType$BlockEntitySupplier
\ No newline at end of file
+accessible class net/minecraft/world/level/block/entity/BlockEntityType$BlockEntitySupplier
+accessible field net/minecraft/world/item/AxeItem STRIPPABLES Ljava/util/Map;
+mutable field net/minecraft/world/item/AxeItem STRIPPABLES 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 5e44d25c..dd39e916 100644
--- a/fabric/src/main/resources/architectury.accessWidener
+++ b/fabric/src/main/resources/architectury.accessWidener
@@ -98,4 +98,10 @@ mutable field net/minecraft/world/level/biome/BiomeSpecialEffects ambientAdditio
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
-accessible class net/minecraft/world/level/block/entity/BlockEntityType$BlockEntitySupplier
\ No newline at end of file
+accessible class net/minecraft/world/level/block/entity/BlockEntityType$BlockEntitySupplier
+accessible field net/minecraft/world/item/AxeItem STRIPPABLES Ljava/util/Map;
+mutable field net/minecraft/world/item/AxeItem STRIPPABLES 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 2cbec00e..a18e70b5 100644
--- a/forge/src/main/resources/META-INF/accesstransformer.cfg
+++ b/forge/src/main/resources/META-INF/accesstransformer.cfg
@@ -45,3 +45,6 @@ public net.minecraft.world.level.GameRules$IntegerValue m_46294_(ILjava/util/fun
public net.minecraft.client.particle.ParticleEngine f_107296_ # textureAtlas
public net.minecraft.client.particle.ParticleEngine$MutableSpriteSet
public net.minecraft.client.particle.ParticleEngine$MutableSpriteSet f_107406_ # sprites
+public-f net.minecraft.world.item.AxeItem f_150683_ # STRIPPABLES
+public-f net.minecraft.world.item.ShovelItem f_43110_ # FLATTENABLES
+public-f net.minecraft.world.item.HoeItem f_41332_ # TILLABLES
diff --git a/gradle.properties b/gradle.properties
index ac1bf7d6..61be9db0 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -9,7 +9,7 @@ cf_type=release
archives_base_name=architectury
archives_base_name_snapshot=architectury-snapshot
-base_version=2.3
+base_version=2.4
maven_group=dev.architectury
fabric_loader_version=0.11.6
diff --git a/testmod-common/src/main/java/dev/architectury/test/TestMod.java b/testmod-common/src/main/java/dev/architectury/test/TestMod.java
index bdc91ad1..03ce539d 100644
--- a/testmod-common/src/main/java/dev/architectury/test/TestMod.java
+++ b/testmod-common/src/main/java/dev/architectury/test/TestMod.java
@@ -29,6 +29,7 @@ import dev.architectury.test.events.DebugEvents;
import dev.architectury.test.gamerule.TestGameRules;
import dev.architectury.test.networking.TestModNet;
import dev.architectury.test.particle.TestParticles;
+import dev.architectury.test.item.TestBlockInteractions;
import dev.architectury.test.registry.TestRegistries;
import dev.architectury.test.registry.client.TestKeybinds;
import dev.architectury.test.tags.TestTags;
@@ -52,6 +53,7 @@ public class TestMod {
TestTrades.init();
TestParticles.initialize();
TestModNet.initialize();
+ TestBlockInteractions.init();
if (Platform.getEnvironment() == Env.CLIENT) {
initializeClient();
}
diff --git a/testmod-common/src/main/java/dev/architectury/test/item/TestBlockInteractions.java b/testmod-common/src/main/java/dev/architectury/test/item/TestBlockInteractions.java
new file mode 100644
index 00000000..18150e79
--- /dev/null
+++ b/testmod-common/src/main/java/dev/architectury/test/item/TestBlockInteractions.java
@@ -0,0 +1,39 @@
+package dev.architectury.test.item;
+
+import dev.architectury.hooks.item.tool.AxeItemHooks;
+import dev.architectury.hooks.item.tool.HoeItemHooks;
+import dev.architectury.hooks.item.tool.ShovelItemHooks;
+import net.minecraft.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.network.chat.TextComponent;
+import net.minecraft.world.entity.player.Player;
+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, ctx -> {
+ if (!ctx.getLevel().isNight()) {
+ if (!ctx.getLevel().isClientSide) {
+ Player player = ctx.getPlayer();
+ if (player != null)
+ player.sendMessage(new TextComponent("These dark arts can only be done at night!"), Util.NIL_UUID);
+ }
+ return false;
+ }
+ return true;
+ }, ctx -> {
+ BlockPos pos = ctx.getClickedPos();
+ ctx.getLevel().setBlock(pos, Blocks.DIAMOND_BLOCK.defaultBlockState(), 3);
+ if (!ctx.getLevel().isClientSide) {
+ Player player = ctx.getPlayer();
+ if (player != null)
+ player.sendMessage(new TextComponent("Thou has successfully committed the dark arts of alchemy!!"), Util.NIL_UUID);
+ }
+ });
+ }
+}