diff --git a/patches/api/Add-drops-to-shear-events.patch b/patches/api/Add-drops-to-shear-events.patch new file mode 100644 index 0000000000..43e9d20c10 --- /dev/null +++ b/patches/api/Add-drops-to-shear-events.patch @@ -0,0 +1,103 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 18 May 2021 12:31:54 -0700 +Subject: [PATCH] Add drops to shear events + + +diff --git a/src/main/java/org/bukkit/event/block/BlockShearEntityEvent.java b/src/main/java/org/bukkit/event/block/BlockShearEntityEvent.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/bukkit/event/block/BlockShearEntityEvent.java ++++ b/src/main/java/org/bukkit/event/block/BlockShearEntityEvent.java +@@ -0,0 +0,0 @@ public class BlockShearEntityEvent extends BlockEvent implements Cancellable { + private final Entity sheared; + private final ItemStack tool; + private boolean cancelled; ++ private java.util.List drops; // Paper + +- public BlockShearEntityEvent(@NotNull Block dispenser, @NotNull Entity sheared, @NotNull ItemStack tool) { ++ @org.jetbrains.annotations.ApiStatus.Internal // Paper ++ public BlockShearEntityEvent(@NotNull Block dispenser, @NotNull Entity sheared, @NotNull ItemStack tool, final @NotNull java.util.List drops) { // Paper - custom shear drops + super(dispenser); + this.sheared = sheared; + this.tool = tool; ++ this.drops = drops; // Paper + } + + /** +@@ -0,0 +0,0 @@ public class BlockShearEntityEvent extends BlockEvent implements Cancellable { + public static HandlerList getHandlerList() { + return handlers; + } ++ // Paper start - custom shear drops ++ /** ++ * Get an immutable list of drops for this shearing. ++ * ++ * @return the shearing drops ++ * @see #setDrops(java.util.List) ++ */ ++ public java.util.@NotNull @org.jetbrains.annotations.Unmodifiable List getDrops() { ++ return java.util.Collections.unmodifiableList(this.drops); ++ } ++ ++ /** ++ * Sets the drops for the shearing. ++ * ++ * @param drops the shear drops ++ */ ++ public void setDrops(final java.util.@NotNull List drops) { ++ this.drops = java.util.List.copyOf(drops); ++ } ++ // Paper end - custom shear drops + } +diff --git a/src/main/java/org/bukkit/event/player/PlayerShearEntityEvent.java b/src/main/java/org/bukkit/event/player/PlayerShearEntityEvent.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/bukkit/event/player/PlayerShearEntityEvent.java ++++ b/src/main/java/org/bukkit/event/player/PlayerShearEntityEvent.java +@@ -0,0 +0,0 @@ public class PlayerShearEntityEvent extends PlayerEvent implements Cancellable { + private final Entity what; + private final ItemStack item; + private final EquipmentSlot hand; ++ private java.util.List drops; // Paper - custom shear drops + +- public PlayerShearEntityEvent(@NotNull Player who, @NotNull Entity what, @NotNull ItemStack item, @NotNull EquipmentSlot hand) { ++ @org.jetbrains.annotations.ApiStatus.Internal // Paper ++ public PlayerShearEntityEvent(@NotNull Player who, @NotNull Entity what, @NotNull ItemStack item, @NotNull EquipmentSlot hand, final java.util.@NotNull List drops) { // Paper - custom shear drops + super(who); + this.what = what; + this.item = item; + this.hand = hand; ++ this.drops = drops; // Paper - custom shear drops + } + + @Deprecated + public PlayerShearEntityEvent(@NotNull final Player who, @NotNull final Entity what) { +- this(who, what, new ItemStack(Material.SHEARS), EquipmentSlot.HAND); ++ this(who, what, new ItemStack(Material.SHEARS), EquipmentSlot.HAND, java.util.Collections.emptyList()); // Paper - custom shear drops + } + + @Override +@@ -0,0 +0,0 @@ public class PlayerShearEntityEvent extends PlayerEvent implements Cancellable { + return handlers; + } + ++ // Paper start - custom shear drops ++ /** ++ * Get an immutable list of drops for this shearing. ++ * ++ * @return the shearing drops ++ * @see #setDrops(java.util.List) ++ */ ++ public java.util.@NotNull @org.jetbrains.annotations.Unmodifiable List getDrops() { ++ return this.drops; ++ } ++ ++ /** ++ * Sets the drops for the shearing. ++ * ++ * @param drops the shear drops ++ */ ++ public void setDrops(final java.util.@NotNull List drops) { ++ this.drops = java.util.List.copyOf(drops); ++ } ++ // Paper end - custom shear drops + } diff --git a/patches/server/Add-drops-to-shear-events.patch b/patches/server/Add-drops-to-shear-events.patch new file mode 100644 index 0000000000..df5fdec999 --- /dev/null +++ b/patches/server/Add-drops-to-shear-events.patch @@ -0,0 +1,325 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 18 May 2021 12:32:02 -0700 +Subject: [PATCH] Add drops to shear events + + +diff --git a/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java ++++ b/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java +@@ -0,0 +0,0 @@ public class ShearsDispenseItemBehavior extends OptionalDispenseItemBehavior { + + if (ishearable.readyForShearing()) { + // CraftBukkit start +- if (CraftEventFactory.callBlockShearEntityEvent(entityliving, bukkitBlock, craftItem).isCancelled()) { ++ // Paper start ++ org.bukkit.event.block.BlockShearEntityEvent event = CraftEventFactory.callBlockShearEntityEvent(entityliving, bukkitBlock, craftItem, ishearable.generateDefaultDrops()); ++ if (event.isCancelled()) { ++ // Paper end + continue; + } + // CraftBukkit end +- ishearable.shear(SoundSource.BLOCKS); ++ ishearable.shear(SoundSource.BLOCKS, CraftItemStack.asNMSCopy(event.getDrops())); // Paper + worldserver.gameEvent((Entity) null, GameEvent.SHEAR, blockposition); + return true; + } +diff --git a/src/main/java/net/minecraft/world/entity/Shearable.java b/src/main/java/net/minecraft/world/entity/Shearable.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/entity/Shearable.java ++++ b/src/main/java/net/minecraft/world/entity/Shearable.java +@@ -0,0 +0,0 @@ package net.minecraft.world.entity; + import net.minecraft.sounds.SoundSource; + + public interface Shearable { ++ default void shear(SoundSource soundCategory, java.util.List drops) { this.shear(soundCategory); } // Paper + void shear(SoundSource shearedSoundCategory); + + boolean readyForShearing(); ++ // Paper start - ensure all implementing entities override this ++ default java.util.List generateDefaultDrops() { ++ return java.util.Collections.emptyList(); ++ } ++ // Paper end + } +diff --git a/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java b/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java ++++ b/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java +@@ -0,0 +0,0 @@ public class MushroomCow extends Cow implements Shearable, VariantHolder drops = this.generateDefaultDrops(); ++ org.bukkit.event.player.PlayerShearEntityEvent event = CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand, drops); ++ if (event != null) { ++ if (event.isCancelled()) { ++ return InteractionResult.PASS; ++ } ++ drops = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getDrops()); + } ++ // Paper end - custom shear drops + // CraftBukkit end +- this.shear(SoundSource.PLAYERS); ++ this.shear(SoundSource.PLAYERS, drops); // Paper + this.gameEvent(GameEvent.SHEAR, player); + if (!this.level().isClientSide) { + itemstack.hurtAndBreak(1, player, (entityhuman1) -> { +@@ -0,0 +0,0 @@ public class MushroomCow extends Cow implements Shearable, VariantHolder generateDefaultDrops() { ++ List dropEntities = new java.util.ArrayList<>(5); ++ for (int i = 0; i < 5; ++i) { ++ dropEntities.add(new ItemStack(this.getVariant().getBlockState().getBlock())); ++ } ++ return dropEntities; ++ } ++ ++ @Override ++ public void shear(SoundSource shearedSoundCategory, List drops) { // If drops is null, need to generate drops ++ // Paper end - custom shear drops + this.level().playSound((Player) null, (Entity) this, SoundEvents.MOOSHROOM_SHEAR, shearedSoundCategory, 1.0F, 1.0F); + if (!this.level().isClientSide()) { + Cow entitycow = (Cow) EntityType.COW.create(this.level()); +@@ -0,0 +0,0 @@ public class MushroomCow extends Cow implements Shearable, VariantHolder drops = this.generateDefaultDrops(); ++ org.bukkit.event.player.PlayerShearEntityEvent event = CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand, drops); ++ if (event != null) { ++ if (event.isCancelled()) { ++ return InteractionResult.PASS; ++ } ++ drops = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getDrops()); + } ++ // Paper end - custom shear drops + // CraftBukkit end +- this.shear(SoundSource.PLAYERS); ++ this.shear(SoundSource.PLAYERS, drops); // Paper + this.gameEvent(GameEvent.SHEAR, player); + itemstack.hurtAndBreak(1, player, (entityhuman1) -> { + entityhuman1.broadcastBreakEvent(hand); +@@ -0,0 +0,0 @@ public class Sheep extends Animal implements Shearable { + + @Override + public void shear(SoundSource shearedSoundCategory) { ++ // Paper start - custom shear drops ++ this.shear(shearedSoundCategory, this.generateDefaultDrops()); ++ } ++ ++ @Override ++ public java.util.List generateDefaultDrops() { ++ int count = 1 + this.random.nextInt(3); ++ java.util.List dropEntities = new java.util.ArrayList<>(count); ++ for (int j = 0; j < count; ++j) { ++ dropEntities.add(new ItemStack(Sheep.ITEM_BY_DYE.get(this.getColor()))); ++ } ++ return dropEntities; ++ } ++ ++ @Override ++ public void shear(SoundSource shearedSoundCategory, java.util.List drops) { ++ // Paper end - custom shear drops + this.level().playSound((Player) null, (Entity) this, SoundEvents.SHEEP_SHEAR, shearedSoundCategory, 1.0F, 1.0F); + this.setSheared(true); + int i = 1 + this.random.nextInt(3); + +- for (int j = 0; j < i; ++j) { ++ for (final ItemStack drop : drops) { // Paper - custom shear drops (moved drop generation to separate method) + this.forceDrops = true; // CraftBukkit +- ItemEntity entityitem = this.spawnAtLocation((ItemLike) Sheep.ITEM_BY_DYE.get(this.getColor()), 1); ++ ItemEntity entityitem = this.spawnAtLocation(drop, 1); // Paper - custom shear drops + this.forceDrops = false; // CraftBukkit + + if (entityitem != null) { +diff --git a/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java b/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java ++++ b/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java +@@ -0,0 +0,0 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM + + if (itemstack.is(Items.SHEARS) && this.readyForShearing()) { + // CraftBukkit start +- if (!CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand)) { +- return InteractionResult.PASS; ++ // Paper start - custom shear drops ++ java.util.List drops = this.generateDefaultDrops(); ++ org.bukkit.event.player.PlayerShearEntityEvent event = CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand, drops); ++ if (event != null) { ++ if (event.isCancelled()) { ++ return InteractionResult.PASS; ++ } ++ drops = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getDrops()); + } ++ // Paper end - custom shear drops + // CraftBukkit end +- this.shear(SoundSource.PLAYERS); ++ this.shear(SoundSource.PLAYERS, drops); // Paper + this.gameEvent(GameEvent.SHEAR, player); + if (!this.level().isClientSide) { + itemstack.hurtAndBreak(1, player, (entityhuman1) -> { +@@ -0,0 +0,0 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM + + @Override + public void shear(SoundSource shearedSoundCategory) { ++ // Paper start - custom shear drops ++ this.shear(shearedSoundCategory, this.generateDefaultDrops()); ++ } ++ ++ @Override ++ public java.util.List generateDefaultDrops() { ++ return java.util.Collections.singletonList(new ItemStack(Items.CARVED_PUMPKIN)); ++ } ++ ++ @Override ++ public void shear(SoundSource shearedSoundCategory, java.util.List drops) { ++ // Paper end - custom shear drops + this.level().playSound((Player) null, (Entity) this, SoundEvents.SNOW_GOLEM_SHEAR, shearedSoundCategory, 1.0F, 1.0F); + if (!this.level().isClientSide()) { + this.setPumpkin(false); +- this.forceDrops = true; // CraftBukkit +- this.spawnAtLocation(new ItemStack(Items.CARVED_PUMPKIN), 1.7F); +- this.forceDrops = false; // CraftBukkit ++ // Paper start - custom shear drops (moved drop generation to separate method) ++ for (final ItemStack drop : drops) { ++ this.forceDrops = true; ++ this.spawnAtLocation(drop, 1.7F); ++ this.forceDrops = false; ++ } ++ // Paper end - custom shear drops + } + + } +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -0,0 +0,0 @@ public class CraftEventFactory { + return event; + } + +- public static BlockShearEntityEvent callBlockShearEntityEvent(Entity animal, org.bukkit.block.Block dispenser, CraftItemStack is) { +- BlockShearEntityEvent bse = new BlockShearEntityEvent(dispenser, animal.getBukkitEntity(), is); ++ public static BlockShearEntityEvent callBlockShearEntityEvent(Entity animal, Block dispenser, CraftItemStack is, List drops) { // Paper - custom shear drops ++ BlockShearEntityEvent bse = new BlockShearEntityEvent(dispenser, animal.getBukkitEntity(), is, Lists.transform(drops, CraftItemStack::asCraftMirror)); // Paper - custom shear drops + Bukkit.getPluginManager().callEvent(bse); + return bse; + } + +- public static boolean handlePlayerShearEntityEvent(net.minecraft.world.entity.player.Player player, Entity sheared, ItemStack shears, InteractionHand hand) { ++ public static PlayerShearEntityEvent handlePlayerShearEntityEvent(net.minecraft.world.entity.player.Player player, Entity sheared, ItemStack shears, InteractionHand hand, List drops) { // Paper - custom shear drops + if (!(player instanceof ServerPlayer)) { +- return true; ++ return null; // Paper - custom shear drops + } + +- PlayerShearEntityEvent event = new PlayerShearEntityEvent((Player) player.getBukkitEntity(), sheared.getBukkitEntity(), CraftItemStack.asCraftMirror(shears), (hand == InteractionHand.OFF_HAND ? EquipmentSlot.OFF_HAND : EquipmentSlot.HAND)); ++ PlayerShearEntityEvent event = new PlayerShearEntityEvent((Player) player.getBukkitEntity(), sheared.getBukkitEntity(), CraftItemStack.asCraftMirror(shears), (hand == InteractionHand.OFF_HAND ? EquipmentSlot.OFF_HAND : EquipmentSlot.HAND), Lists.transform(drops, CraftItemStack::asCraftMirror)); // Paper - custom shear drops + Bukkit.getPluginManager().callEvent(event); +- return !event.isCancelled(); ++ return event; // Paper - custom shear drops + } + + public static Cancellable handleStatisticsIncrease(net.minecraft.world.entity.player.Player entityHuman, net.minecraft.stats.Stat statistic, int current, int newValue) { +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +@@ -0,0 +0,0 @@ public final class CraftItemStack extends ItemStack { + } + return stack; + } ++ // Paper start ++ public static java.util.List asNMSCopy(java.util.List originals) { ++ final java.util.List nms = new java.util.ArrayList<>(); ++ for (final org.bukkit.inventory.ItemStack original : originals) { ++ nms.add(asNMSCopy(original)); ++ } ++ return nms; ++ } ++ // Paper end + + public static net.minecraft.world.item.ItemStack copyNMSStack(net.minecraft.world.item.ItemStack original, int amount) { + net.minecraft.world.item.ItemStack stack = original.copy(); +diff --git a/src/test/java/io/papermc/paper/entity/ShearableDropsTest.java b/src/test/java/io/papermc/paper/entity/ShearableDropsTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/test/java/io/papermc/paper/entity/ShearableDropsTest.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.entity; ++ ++import io.github.classgraph.ClassGraph; ++import io.github.classgraph.ClassInfo; ++import io.github.classgraph.MethodInfoList; ++import io.github.classgraph.ScanResult; ++import java.util.ArrayList; ++import net.minecraft.world.entity.Shearable; ++import org.bukkit.support.AbstractTestingBase; ++import org.junit.jupiter.params.ParameterizedTest; ++import org.junit.jupiter.params.provider.MethodSource; ++ ++import static org.junit.jupiter.api.Assertions.assertEquals; ++ ++class ShearableDropsTest extends AbstractTestingBase { ++ ++ static Iterable parameters() { ++ try (ScanResult scanResult = new ClassGraph() ++ .enableClassInfo() ++ .enableMethodInfo() ++ .whitelistPackages("net.minecraft") ++ .scan() ++ ) { ++ return new ArrayList<>(scanResult.getClassesImplementing(Shearable.class.getName())); ++ } ++ } ++ ++ @ParameterizedTest ++ @MethodSource("parameters") ++ void checkShearableDropOverrides(final ClassInfo classInfo) { ++ final MethodInfoList generateDefaultDrops = classInfo.getDeclaredMethodInfo("generateDefaultDrops"); ++ assertEquals(1, generateDefaultDrops.size(), classInfo.getName() + " doesn't implement Shearable#generateDefaultDrops"); ++ } ++}