aboutsummaryrefslogtreecommitdiffhomepage
path: root/patches/server/0917-Add-drops-to-shear-events.patch
diff options
context:
space:
mode:
Diffstat (limited to 'patches/server/0917-Add-drops-to-shear-events.patch')
-rw-r--r--patches/server/0917-Add-drops-to-shear-events.patch410
1 files changed, 410 insertions, 0 deletions
diff --git a/patches/server/0917-Add-drops-to-shear-events.patch b/patches/server/0917-Add-drops-to-shear-events.patch
new file mode 100644
index 0000000000..7e5645cd5f
--- /dev/null
+++ b/patches/server/0917-Add-drops-to-shear-events.patch
@@ -0,0 +1,410 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+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 f32f8d5cb22feb885a53d3b56c04ad4219d2bafa..44b79a7c2f8b95a484d1999fa2167ce588f7985b 100644
+--- a/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java
++++ b/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java
+@@ -103,11 +103,14 @@ public class ShearsDispenseItemBehavior extends OptionalDispenseItemBehavior {
+ if (entityliving instanceof Shearable ishearable) {
+ if (ishearable.readyForShearing()) {
+ // CraftBukkit start
+- if (CraftEventFactory.callBlockShearEntityEvent(entityliving, bukkitBlock, craftItem).isCancelled()) {
++ // Paper start - Add drops to shear events
++ org.bukkit.event.block.BlockShearEntityEvent event = CraftEventFactory.callBlockShearEntityEvent(entityliving, bukkitBlock, craftItem, ishearable.generateDefaultDrops());
++ if (event.isCancelled()) {
++ // Paper end - Add drops to shear events
+ continue;
+ }
+ // CraftBukkit end
+- ishearable.shear(SoundSource.BLOCKS);
++ ishearable.shear(SoundSource.BLOCKS, CraftItemStack.asNMSCopy(event.getDrops())); // Paper - Add drops to shear events
+ worldserver.gameEvent((Entity) null, (Holder) 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 5e8cc5cfac8888628c6d513148f41be09ca65a2c..2ee48ac3b665db2b02bcb1a30ec972d43a3725b0 100644
+--- a/src/main/java/net/minecraft/world/entity/Shearable.java
++++ b/src/main/java/net/minecraft/world/entity/Shearable.java
+@@ -3,7 +3,13 @@ package net.minecraft.world.entity;
+ import net.minecraft.sounds.SoundSource;
+
+ public interface Shearable {
++ default void shear(SoundSource soundCategory, java.util.List<net.minecraft.world.item.ItemStack> drops) { this.shear(soundCategory); } // Paper - Add drops to shear events
+ void shear(SoundSource shearedSoundCategory);
+
+ boolean readyForShearing();
++ // Paper start - custom shear drops; ensure all implementing entities override this
++ default java.util.List<net.minecraft.world.item.ItemStack> generateDefaultDrops() {
++ return java.util.Collections.emptyList();
++ }
++ // Paper end - custom shear drops
+ }
+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 aa125e3043b120935aaa015803f065f99eb8d050..0c21959f57ae88fcd0a4d6dc911c1ce347c96528 100644
+--- a/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java
++++ b/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java
+@@ -123,11 +123,18 @@ public class MushroomCow extends Cow implements Shearable, VariantHolder<Mushroo
+ return InteractionResult.sidedSuccess(this.level().isClientSide);
+ } else 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<ItemStack> 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 - custom shear drops
+ this.gameEvent(GameEvent.SHEAR, player);
+ if (!this.level().isClientSide) {
+ itemstack.hurtAndBreak(1, player, getSlotForHand(hand));
+@@ -164,6 +171,22 @@ public class MushroomCow extends Cow implements Shearable, VariantHolder<Mushroo
+
+ @Override
+ public void shear(SoundSource shearedSoundCategory) {
++ // Paper start - custom shear drops
++ this.shear(shearedSoundCategory, this.generateDefaultDrops());
++ }
++
++ @Override
++ public java.util.List<ItemStack> generateDefaultDrops() {
++ java.util.List<ItemStack> 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, java.util.List<ItemStack> 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());
+@@ -193,17 +216,12 @@ public class MushroomCow extends Cow implements Shearable, VariantHolder<Mushroo
+ this.discard(EntityRemoveEvent.Cause.TRANSFORMATION); // CraftBukkit - from above and add Bukkit remove cause
+ // CraftBukkit end
+
+- for (int i = 0; i < 5; ++i) {
+- // CraftBukkit start
+- ItemEntity entityitem = new ItemEntity(this.level(), this.getX(), this.getY(1.0D), this.getZ(), new ItemStack(this.getVariant().blockState.getBlock()));
+- EntityDropItemEvent event = new EntityDropItemEvent(this.getBukkitEntity(), (org.bukkit.entity.Item) entityitem.getBukkitEntity());
+- Bukkit.getPluginManager().callEvent(event);
+- if (event.isCancelled()) {
+- continue;
+- }
+- this.level().addFreshEntity(entityitem);
+- // CraftBukkit end
++ // Paper start - custom shear drops; moved drop generation to separate method
++ for (final ItemStack drop : drops) {
++ ItemEntity entityitem = new ItemEntity(this.level(), this.getX(), this.getY(1.0D), this.getZ(), drop);
++ this.spawnAtLocation(entityitem);
+ }
++ // Paper end - custom shear drops
+ }
+ }
+
+diff --git a/src/main/java/net/minecraft/world/entity/animal/Sheep.java b/src/main/java/net/minecraft/world/entity/animal/Sheep.java
+index 6b26af41423110bd982eb8c0eea0cba5e9fdc633..38ac2759894660be1ee7ba59b0bd1270158e9232 100644
+--- a/src/main/java/net/minecraft/world/entity/animal/Sheep.java
++++ b/src/main/java/net/minecraft/world/entity/animal/Sheep.java
+@@ -256,11 +256,18 @@ public class Sheep extends Animal implements Shearable {
+ if (itemstack.is(Items.SHEARS)) {
+ if (!this.level().isClientSide && this.readyForShearing()) {
+ // CraftBukkit start
+- if (!CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand)) {
+- return InteractionResult.PASS;
++ // Paper start - custom shear drops
++ java.util.List<ItemStack> 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, getSlotForHand(hand));
+ return InteractionResult.SUCCESS;
+@@ -274,13 +281,30 @@ 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<ItemStack> generateDefaultDrops() {
++ int count = 1 + this.random.nextInt(3);
++ java.util.List<ItemStack> 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<ItemStack> 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 2de1a2f666da9db1832907e1651dbff948e37252..5c2ed3c39c8eb850f3be1e2ea5b5a7ea266e16d1 100644
+--- a/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java
++++ b/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java
+@@ -146,11 +146,18 @@ 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<ItemStack> 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, getSlotForHand(hand));
+@@ -164,12 +171,28 @@ 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<ItemStack> generateDefaultDrops() {
++ return java.util.Collections.singletonList(new ItemStack(Items.CARVED_PUMPKIN));
++ }
++
++ @Override
++ public void shear(SoundSource shearedSoundCategory, java.util.List<ItemStack> 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), this.getEyeHeight());
+- 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, this.getEyeHeight());
++ this.forceDrops = false;
++ }
++ // Paper end - custom shear drops
+ }
+
+ }
+diff --git a/src/main/java/net/minecraft/world/entity/monster/Bogged.java b/src/main/java/net/minecraft/world/entity/monster/Bogged.java
+index dc6230458e09f7555eee7f6a567ff60ad454666b..9d50b9ac8084f3db1844cc7ad1ce9153614ff9d9 100644
+--- a/src/main/java/net/minecraft/world/entity/monster/Bogged.java
++++ b/src/main/java/net/minecraft/world/entity/monster/Bogged.java
+@@ -80,12 +80,19 @@ public class Bogged extends AbstractSkeleton implements Shearable {
+
+ if (itemstack.is(Items.SHEARS) && this.readyForShearing()) {
+ // CraftBukkit start
+- if (!org.bukkit.craftbukkit.event.CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand)) {
+- this.getEntityData().markDirty(Bogged.DATA_SHEARED); // CraftBukkit - mark dirty to restore sheared state to clients
+- return InteractionResult.PASS;
++ // Paper start - expose drops in event
++ java.util.List<net.minecraft.world.item.ItemStack> drops = generateDefaultDrops();
++ final org.bukkit.event.player.PlayerShearEntityEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand, drops);
++ if (event != null) {
++ if (event.isCancelled()) {
++ if (player instanceof final net.minecraft.server.level.ServerPlayer serverPlayer) this.resendPossiblyDesyncedDataValues(java.util.List.of(Bogged.DATA_SHEARED), serverPlayer);
++ return InteractionResult.PASS;
++ }
++ drops = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getDrops());
++ // Paper end - expose drops in event
+ }
+ // CraftBukkit end
+- this.shear(SoundSource.PLAYERS);
++ this.shear(SoundSource.PLAYERS, drops); // Paper - expose drops in event
+ this.gameEvent(GameEvent.SHEAR, player);
+ if (!this.level().isClientSide) {
+ itemstack.hurtAndBreak(1, player, getSlotForHand(hand));
+@@ -140,12 +147,31 @@ public class Bogged extends AbstractSkeleton implements Shearable {
+
+ @Override
+ public void shear(SoundSource shearedSoundCategory) {
++ // Paper start - shear drop API
++ this.shear(shearedSoundCategory, generateDefaultDrops());
++ }
++
++ @Override
++ public void shear(SoundSource shearedSoundCategory, java.util.List<net.minecraft.world.item.ItemStack> drops) {
++ // Paper end - shear drop API
+ this.level().playSound((Player) null, (Entity) this, SoundEvents.BOGGED_SHEAR, shearedSoundCategory, 1.0F, 1.0F);
+- this.spawnShearedMushrooms();
++ this.spawnDrops(drops); // Paper - shear drop API
+ this.setSheared(true);
+ }
+
+ private void spawnShearedMushrooms() {
++ // Paper start - shear drops API
++ this.spawnDrops(generateDefaultDrops()); // Only here for people calling spawnSheardMushrooms. Not used otherwise.
++ }
++ private void spawnDrops(java.util.List<net.minecraft.world.item.ItemStack> drops) {
++ drops.forEach(stack -> {
++ this.forceDrops = true;
++ this.spawnAtLocation(stack, this.getBbHeight());
++ this.forceDrops = false;
++ });
++ }
++ private void generateShearedMushrooms(java.util.function.Consumer<ItemStack> stackConsumer) {
++ // Paper end - shear drops API
+ Level world = this.level();
+
+ if (world instanceof ServerLevel worldserver) {
+@@ -156,12 +182,21 @@ public class Bogged extends AbstractSkeleton implements Shearable {
+ while (objectlistiterator.hasNext()) {
+ ItemStack itemstack = (ItemStack) objectlistiterator.next();
+
+- this.spawnAtLocation(itemstack, this.getBbHeight());
++ stackConsumer.accept(itemstack); // Paper
+ }
+ }
+
+ }
+
++ // Paper start - shear drops API
++ @Override
++ public java.util.List<ItemStack> generateDefaultDrops() {
++ final java.util.List<ItemStack> drops = new java.util.ArrayList<>();
++ this.generateShearedMushrooms(drops::add);
++ return drops;
++ }
++ // Paper end - shear drops API
++
+ @Override
+ public boolean readyForShearing() {
+ return !this.isSheared() && this.isAlive();
+diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
+index f529243852df8e193a604bb6d895333848b14a74..d407e9e8f1b530f00e632e0249514fb68d48316b 100644
+--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
+@@ -1691,20 +1691,20 @@ public class CraftEventFactory {
+ player.level().getCraftServer().getPluginManager().callEvent(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, org.bukkit.block.Block dispenser, CraftItemStack is, List<ItemStack> 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<ItemStack> 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 f33742ee06e8443a5f5adaaa987d7523dc193b5a..a1a32a77bda0560a7b7f30a5d1c1837ee96997d3 100644
+--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java
++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java
+@@ -69,6 +69,16 @@ public final class CraftItemStack extends ItemStack {
+ return stack;
+ }
+
++ // Paper start
++ public static java.util.List<net.minecraft.world.item.ItemStack> asNMSCopy(java.util.List<? extends ItemStack> originals) {
++ final java.util.List<net.minecraft.world.item.ItemStack> items = new java.util.ArrayList<>(originals.size());
++ for (final ItemStack original : originals) {
++ items.add(asNMSCopy(original));
++ }
++ return items;
++ }
++ // 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();
+ stack.setCount(amount);
+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..e612515f7709dbe5d1fa5751337cdc34fce10a98
+--- /dev/null
++++ b/src/test/java/io/papermc/paper/entity/ShearableDropsTest.java
+@@ -0,0 +1,34 @@
++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<ClassInfo> 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");
++ }
++}