From e75f0b67fe96c53b8667e1f0501ca75e6331b9d0 Mon Sep 17 00:00:00 2001 From: cech12 Date: Thu, 20 Nov 2025 02:39:39 +0100 Subject: [PATCH] Fixes shearing leads for custom shears (interacting with entity & leash knot) (#10706) --- .../minecraft/world/entity/Entity.java.patch | 7 +- .../LeashFenceKnotEntity.java.patch | 11 ++ .../custom_shears_unleash_entity.json | 7 ++ .../custom_shears_unleash_knot.json | 7 ++ .../gameplay/item/ShearsBehaviorTest.java | 110 ++++++++++++++++++ 5 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 patches/minecraft/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java.patch create mode 100644 src/test/generated/shears_behavior/data/forge/test_instance/shears_behavior/custom_shears_unleash_entity.json create mode 100644 src/test/generated/shears_behavior/data/forge/test_instance/shears_behavior/custom_shears_unleash_knot.json create mode 100644 src/test/java/net/minecraftforge/debug/gameplay/item/ShearsBehaviorTest.java diff --git a/patches/minecraft/net/minecraft/world/entity/Entity.java.patch b/patches/minecraft/net/minecraft/world/entity/Entity.java.patch index 0cc141b684..51e16a87ef 100644 --- a/patches/minecraft/net/minecraft/world/entity/Entity.java.patch +++ b/patches/minecraft/net/minecraft/world/entity/Entity.java.patch @@ -238,7 +238,12 @@ p_409579_.addFreshEntity(itementity); return itementity; } -@@ -2127,7 +_,7 @@ +@@ -2123,11 +_,11 @@ + } + + ItemStack itemstack = p_19978_.getItemInHand(p_19979_); +- if (itemstack.is(Items.SHEARS) && this.shearOffAllLeashConnections(p_19978_)) { ++ if (itemstack.canPerformAction(net.minecraftforge.common.ToolActions.SHEARS_HARVEST) && this.shearOffAllLeashConnections(p_19978_)) { itemstack.hurtAndBreak(1, p_19978_, p_19979_); return InteractionResult.SUCCESS; } else if (this instanceof Mob mob diff --git a/patches/minecraft/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java.patch b/patches/minecraft/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java.patch new file mode 100644 index 0000000000..ab085d010d --- /dev/null +++ b/patches/minecraft/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java ++++ b/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java +@@ -72,7 +_,7 @@ + if (this.level().isClientSide) { + return InteractionResult.SUCCESS; + } else { +- if (p_31842_.getItemInHand(p_31843_).is(Items.SHEARS)) { ++ if (p_31842_.getItemInHand(p_31843_).canPerformAction(net.minecraftforge.common.ToolActions.SHEARS_HARVEST)) { + InteractionResult interactionresult = super.interact(p_31842_, p_31843_); + if (interactionresult instanceof InteractionResult.Success interactionresult$success && interactionresult$success.wasItemInteraction()) { + return interactionresult; diff --git a/src/test/generated/shears_behavior/data/forge/test_instance/shears_behavior/custom_shears_unleash_entity.json b/src/test/generated/shears_behavior/data/forge/test_instance/shears_behavior/custom_shears_unleash_entity.json new file mode 100644 index 0000000000..0885f5d6e0 --- /dev/null +++ b/src/test/generated/shears_behavior/data/forge/test_instance/shears_behavior/custom_shears_unleash_entity.json @@ -0,0 +1,7 @@ +{ + "type": "minecraft:function", + "environment": "minecraft:default", + "function": "forge:shears_behavior/custom_shears_unleash_entity", + "max_ticks": 100, + "structure": "forge:empty3x3x3" +} \ No newline at end of file diff --git a/src/test/generated/shears_behavior/data/forge/test_instance/shears_behavior/custom_shears_unleash_knot.json b/src/test/generated/shears_behavior/data/forge/test_instance/shears_behavior/custom_shears_unleash_knot.json new file mode 100644 index 0000000000..4b23f99596 --- /dev/null +++ b/src/test/generated/shears_behavior/data/forge/test_instance/shears_behavior/custom_shears_unleash_knot.json @@ -0,0 +1,7 @@ +{ + "type": "minecraft:function", + "environment": "minecraft:default", + "function": "forge:shears_behavior/custom_shears_unleash_knot", + "max_ticks": 100, + "structure": "forge:empty3x3x3" +} \ No newline at end of file diff --git a/src/test/java/net/minecraftforge/debug/gameplay/item/ShearsBehaviorTest.java b/src/test/java/net/minecraftforge/debug/gameplay/item/ShearsBehaviorTest.java new file mode 100644 index 0000000000..e00ea237c2 --- /dev/null +++ b/src/test/java/net/minecraftforge/debug/gameplay/item/ShearsBehaviorTest.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.minecraftforge.debug.gameplay.item; + +import net.minecraft.core.BlockPos; +import net.minecraft.gametest.framework.GameTestHelper; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.decoration.LeashFenceKnotEntity; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.GameType; +import net.minecraft.world.level.block.Blocks; +import net.minecraftforge.common.ToolAction; +import net.minecraftforge.common.ToolActions; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import net.minecraftforge.gametest.GameTest; +import net.minecraftforge.gametest.GameTestNamespace; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.RegistryObject; +import net.minecraftforge.test.BaseTestMod; + +@Mod(ShearsBehaviorTest.MOD_ID) +@GameTestNamespace("forge") +public class ShearsBehaviorTest extends BaseTestMod { + public static final String MOD_ID = "shears_behavior"; + + private static final DeferredRegister ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, MOD_ID); + private static final RegistryObject CUSTOM_SHEARS_HARVEST_ITEM = ITEMS.register("custom_shears_harvest_item", () -> new ShearsHarvestItem( + new Item.Properties().stacksTo(1).setId(ITEMS.key("custom_shears_harvest_item")) + )); + + public ShearsBehaviorTest(FMLJavaModLoadingContext context) { + super(context, false, true); + this.testItem(lookup -> CUSTOM_SHEARS_HARVEST_ITEM.get().getDefaultInstance()); + } + + @GameTest + public static void custom_shears_unleash_entity(GameTestHelper helper) { + helper.makeFloor(); + + // setup: fence + knot + leash cow + var fencePos = new BlockPos(1, 1, 1); + var cowPos = new BlockPos(0, 1, 1); + helper.setBlock(fencePos, Blocks.OAK_FENCE); + var cow = helper.spawnWithNoFreeWill(EntityType.COW, cowPos); + var knot = LeashFenceKnotEntity.getOrCreateKnot(helper.getLevel(), helper.absolutePos(fencePos)); + cow.setLeashedTo(knot, true); + helper.assertTrue(cow.isLeashed(), "Cow should start leashed"); + + // act: interact with cow using custom shears + var player = helper.makeMockPlayer(GameType.SURVIVAL); + var shears = CUSTOM_SHEARS_HARVEST_ITEM.get().getDefaultInstance(); + player.setItemInHand(InteractionHand.MAIN_HAND, shears); + var result = player.interactOn(cow, InteractionHand.MAIN_HAND); + + // assert + helper.assertTrue(result.consumesAction(), "Using custom shears on leashed cow should result in consume action"); + helper.assertTrue(!cow.isLeashed(), "Cow should be unleashed by custom shears"); + helper.assertItemEntityPresent(Items.LEAD); + + helper.succeed(); + } + + + @GameTest + public static void custom_shears_unleash_knot(GameTestHelper helper) { + helper.makeFloor(); + + // setup: fence + knot + leash cow + var fencePos = new BlockPos(1, 1, 1); + var cowPos = new BlockPos(0, 1, 1); + helper.setBlock(fencePos, Blocks.OAK_FENCE); + var cow = helper.spawnWithNoFreeWill(EntityType.COW, cowPos); + var knot = LeashFenceKnotEntity.getOrCreateKnot(helper.getLevel(), helper.absolutePos(fencePos)); + cow.setLeashedTo(knot, true); + helper.assertTrue(cow.isLeashed(), "Cow should start leashed"); + + // act: interact with knot using custom shears + var player = helper.makeMockPlayer(GameType.SURVIVAL); + var shears = CUSTOM_SHEARS_HARVEST_ITEM.get().getDefaultInstance(); + player.setItemInHand(InteractionHand.MAIN_HAND, shears); + var result = player.interactOn(knot, InteractionHand.MAIN_HAND); + + // assert + helper.assertTrue(result.consumesAction(), "Using custom shears on leash knot should result in consume action"); + helper.assertTrue(!cow.isLeashed(), "Cow should be unleashed by sheared knot"); + helper.assertItemEntityPresent(Items.LEAD); + + helper.succeed(); + } + + private static final class ShearsHarvestItem extends Item { + ShearsHarvestItem(Item.Properties properties) { + super(properties); + } + + @Override + public boolean canPerformAction(ItemStack stack, ToolAction action) { + return action == ToolActions.SHEARS_HARVEST; + } + } + +}