From 37bee09e3dc752327f87481bbdf28bcc511960fd Mon Sep 17 00:00:00 2001 From: Jake Potrebic Date: Sat, 2 Dec 2023 17:35:10 -0800 Subject: [PATCH] Restore vanilla entity drops behavior (#7650) --- ...estore-vanilla-entity-drops-behavior.patch | 230 ++++++++++++++++++ 1 file changed, 230 insertions(+) create mode 100644 patches/server/1056-Restore-vanilla-entity-drops-behavior.patch diff --git a/patches/server/1056-Restore-vanilla-entity-drops-behavior.patch b/patches/server/1056-Restore-vanilla-entity-drops-behavior.patch new file mode 100644 index 0000000000..4b72d8152a --- /dev/null +++ b/patches/server/1056-Restore-vanilla-entity-drops-behavior.patch @@ -0,0 +1,230 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 22 Mar 2022 09:34:41 -0700 +Subject: [PATCH] Restore vanilla entity drops behavior + +Instead of just tracking the itemstacks, this tracks with it, the +action to take with that itemstack to apply the correct logic +on dropping the item instead of generalizing it for all dropped +items like CB does. + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 08980763020a13ab49dc7d637625a4fba56da8c9..907c8f15f5247f9972c6677ff0f9e1aa22764a04 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -937,22 +937,20 @@ public class ServerPlayer extends Player { + if (this.isRemoved()) { + return; + } +- java.util.List loot = new java.util.ArrayList(this.getInventory().getContainerSize()); ++ List loot = new java.util.ArrayList<>(this.getInventory().getContainerSize()); // Paper + boolean keepInventory = this.level().getGameRules().getBoolean(GameRules.RULE_KEEPINVENTORY) || this.isSpectator(); + + if (!keepInventory) { + for (ItemStack item : this.getInventory().getContents()) { + if (!item.isEmpty() && !EnchantmentHelper.hasVanishingCurse(item)) { +- loot.add(CraftItemStack.asCraftMirror(item)); ++ loot.add(new DefaultDrop(item, stack -> this.drop(stack, true, false))); // Paper - drop function taken from Inventory#dropAll + } + } + } + if (this.shouldDropLoot() && this.level().getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { // Paper - preserve this check from vanilla + // SPIGOT-5071: manually add player loot tables (SPIGOT-5195 - ignores keepInventory rule) + this.dropFromLootTable(damageSource, this.lastHurtByPlayerTime > 0); +- for (org.bukkit.inventory.ItemStack item : this.drops) { +- loot.add(item); +- } ++ loot.addAll(this.drops); // Paper + this.drops.clear(); // SPIGOT-5188: make sure to clear + } // Paper + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index c655c6fee393c62ba79301f76baa72f9b1154a9a..fece91254b10b59474056aa730fd420f90cd7bec 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2673,6 +2673,25 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + + @Nullable + public ItemEntity spawnAtLocation(ItemStack stack, float yOffset) { ++ // Paper start ++ return this.spawnAtLocation(stack, yOffset, null); ++ } ++ public record DefaultDrop(Item item, org.bukkit.inventory.ItemStack stack, @Nullable java.util.function.Consumer dropConsumer) { ++ public DefaultDrop(final ItemStack stack, final java.util.function.Consumer dropConsumer) { ++ this(stack.getItem(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack), dropConsumer); ++ } ++ ++ public void runConsumer(final org.bukkit.World fallbackWorld, final Location fallbackLoc) { ++ if (this.dropConsumer == null || org.bukkit.craftbukkit.util.CraftMagicNumbers.getItem(this.stack.getType()) != this.item) { ++ fallbackWorld.dropItem(fallbackLoc, this.stack); ++ } else { ++ this.dropConsumer.accept(org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(this.stack)); ++ } ++ } ++ } ++ @Nullable ++ public ItemEntity spawnAtLocation(ItemStack stack, float yOffset, @Nullable java.util.function.Consumer delayedAddConsumer) { ++ // Paper end + if (stack.isEmpty()) { + return null; + } else if (this.level().isClientSide) { +@@ -2680,14 +2699,21 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + } else { + // CraftBukkit start - Capture drops for death event + if (this instanceof net.minecraft.world.entity.LivingEntity && !((net.minecraft.world.entity.LivingEntity) this).forceDrops) { +- ((net.minecraft.world.entity.LivingEntity) this).drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack)); // Paper - mirror so we can destroy it later ++ // Paper start ++ ((net.minecraft.world.entity.LivingEntity) this).drops.add(new net.minecraft.world.entity.Entity.DefaultDrop(stack, itemStack -> { ++ ItemEntity itemEntity = new ItemEntity(this.level, this.getX(), this.getY() + (double) yOffset, this.getZ(), itemStack); // stack is copied before consumer ++ itemEntity.setDefaultPickUpDelay(); ++ this.level.addFreshEntity(itemEntity); ++ if (delayedAddConsumer != null) delayedAddConsumer.accept(itemEntity); ++ })); ++ // Paper end + return null; + } + // CraftBukkit end + ItemEntity entityitem = new ItemEntity(this.level(), this.getX(), this.getY() + (double) yOffset, this.getZ(), stack.copy()); // Paper - copy so we can destroy original + stack.setCount(0); // Paper - destroy this item - if this ever leaks due to game bugs, ensure it doesn't dupe + +- entityitem.setDefaultPickUpDelay(); ++ entityitem.setDefaultPickUpDelay(); // Paper - diff on change (in dropConsumer) + // Paper start + return this.spawnAtLocation(entityitem); + } +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 87134e57a57df0fceda903e35d22f3f2de31adf3..15e1d8c09fad181406a6acb8b3f177cd5e6c0f52 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -255,7 +255,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + // CraftBukkit start + public int expToDrop; + public boolean forceDrops; +- public ArrayList drops = new ArrayList(); ++ public ArrayList drops = new ArrayList<>(); // Paper + public final org.bukkit.craftbukkit.attribute.CraftAttributeMap craftAttributes; + public boolean collides = true; + public Set collidableExemptions = new HashSet<>(); +diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java +index 1e07febcf7a3dfb281728cc5e3e4f15dd776d7e0..c9a4feb4a52c0eb621b120e5b8c18d0a74dae0cd 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java ++++ b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java +@@ -533,10 +533,10 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob + @Override + protected void dropCustomDeathLoot(DamageSource source, int lootingMultiplier, boolean allowDrops) { + super.dropCustomDeathLoot(source, lootingMultiplier, allowDrops); +- ItemEntity entityitem = this.spawnAtLocation((ItemLike) Items.NETHER_STAR); ++ ItemEntity entityitem = this.spawnAtLocation(new net.minecraft.world.item.ItemStack(Items.NETHER_STAR), 0, ItemEntity::setExtendedLifetime); // Paper - spawnAtLocation returns null so modify the item entity with a consumer + + if (entityitem != null) { +- entityitem.setExtendedLifetime(); ++ entityitem.setExtendedLifetime(); // Paper - diff on change + } + + } +diff --git a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +index 9dcf28bdcb5770a191e62353a60c953731671283..523f14916073fb137578da777a23ba8265fd8af6 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +@@ -623,7 +623,7 @@ public class ArmorStand extends LivingEntity { + itemstack.setHoverName(this.getCustomName()); + } + +- this.drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(itemstack)); // CraftBukkit - add to drops ++ this.drops.add(new DefaultDrop(itemstack, stack -> Block.popResource(this.level(), this.blockPosition(), stack))); // CraftBukkit - add to drops // Paper - spawn drops correctly + return this.brokenByAnything(damageSource); // Paper + } + +@@ -637,7 +637,7 @@ public class ArmorStand extends LivingEntity { + for (i = 0; i < this.handItems.size(); ++i) { + itemstack = (ItemStack) this.handItems.get(i); + if (!itemstack.isEmpty()) { +- this.drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack)); // CraftBukkit - add to drops // Paper - mirror so we can destroy it later - though this call site was safe ++ this.drops.add(new DefaultDrop(itemstack, stack -> Block.popResource(this.level(), this.blockPosition().above(), stack))); // CraftBukkit - add to drops // Paper - mirror so we can destroy it later - though this call site was safe & spawn drops correctly + this.handItems.set(i, ItemStack.EMPTY); + } + } +@@ -645,7 +645,7 @@ public class ArmorStand extends LivingEntity { + for (i = 0; i < this.armorItems.size(); ++i) { + itemstack = (ItemStack) this.armorItems.get(i); + if (!itemstack.isEmpty()) { +- this.drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack)); // CraftBukkit - add to drops // Paper - mirror so we can destroy it later - though this call site was safe ++ this.drops.add(new DefaultDrop(itemstack, stack -> Block.popResource(this.level(), this.blockPosition().above(), stack))); // CraftBukkit - add to drops // Paper - mirror so we can destroy it later - though this call site was safe & spawn drops correctly + this.armorItems.set(i, ItemStack.EMPTY); + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 5dc160b743534665c6b3efb10b10f7c36e2da5ab..64ae7cfe765ebe697a2cce1b71751e628d6f1662 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -942,17 +942,21 @@ public class CraftEventFactory { + } + + public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim) { +- return CraftEventFactory.callEntityDeathEvent(victim, new ArrayList(0)); ++ return CraftEventFactory.callEntityDeathEvent(victim, new ArrayList<>(0)); // Paper + } + +- public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim, List drops) { ++ public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim, List drops) { // Paper + // Paper start + return CraftEventFactory.callEntityDeathEvent(victim, drops, com.google.common.util.concurrent.Runnables.doNothing()); + } +- public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim, List drops, Runnable lootCheck) { ++ private static java.util.function.Function FROM_FUNCTION = stack -> { ++ if (stack == null) return null; ++ return new Entity.DefaultDrop(CraftMagicNumbers.getItem(stack.getType()), stack, null); ++ }; ++ public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim, List drops, Runnable lootCheck) { // Paper + // Paper end + CraftLivingEntity entity = (CraftLivingEntity) victim.getBukkitEntity(); +- EntityDeathEvent event = new EntityDeathEvent(entity, drops, victim.getExpReward()); ++ EntityDeathEvent event = new EntityDeathEvent(entity, new io.papermc.paper.util.TransformingRandomAccessList<>(drops, Entity.DefaultDrop::stack, FROM_FUNCTION), victim.getExpReward()); // Paper + populateFields(victim, event); // Paper - make cancellable + CraftWorld world = (CraftWorld) entity.getWorld(); + Bukkit.getServer().getPluginManager().callEvent(event); +@@ -966,19 +970,22 @@ public class CraftEventFactory { + victim.expToDrop = event.getDroppedExp(); + lootCheck.run(); // Paper - advancement triggers before destroying items + +- for (org.bukkit.inventory.ItemStack stack : event.getDrops()) { +- if (stack == null || stack.getType() == Material.AIR || stack.getAmount() == 0) continue; ++ // Paper start ++ for (Entity.DefaultDrop drop : drops) { ++ final org.bukkit.inventory.ItemStack stack = drop.stack(); ++ if (drop == null || stack == null || stack.getType() == Material.AIR || stack.getAmount() == 0) continue; ++ // Paper end + +- world.dropItem(entity.getLocation(), stack); // Paper - note: dropItem already clones due to this being bukkit -> NMS ++ drop.runConsumer(world, entity.getLocation()); // Paper + if (stack instanceof CraftItemStack) stack.setAmount(0); // Paper - destroy this item - if this ever leaks due to game bugs, ensure it doesn't dupe, but don't nuke bukkit stacks of manually added items + } + + return event; + } + +- public static PlayerDeathEvent callPlayerDeathEvent(ServerPlayer victim, List drops, net.kyori.adventure.text.Component deathMessage, String stringDeathMessage, boolean keepInventory) { // Paper - Adventure ++ public static PlayerDeathEvent callPlayerDeathEvent(ServerPlayer victim, List drops, net.kyori.adventure.text.Component deathMessage, String stringDeathMessage, boolean keepInventory) { // Paper - Adventure & improve drops + CraftPlayer entity = victim.getBukkitEntity(); +- PlayerDeathEvent event = new PlayerDeathEvent(entity, drops, victim.getExpReward(), 0, deathMessage, stringDeathMessage); // Paper - Adventure ++ PlayerDeathEvent event = new PlayerDeathEvent(entity, new io.papermc.paper.util.TransformingRandomAccessList<>(drops, Entity.DefaultDrop::stack, FROM_FUNCTION), victim.getExpReward(), 0, deathMessage, stringDeathMessage); // Paper - Adventure & improve drops + event.setKeepInventory(keepInventory); + event.setKeepLevel(victim.keepLevel); // SPIGOT-2222: pre-set keepLevel + populateFields(victim, event); // Paper - make cancellable +@@ -997,10 +1004,13 @@ public class CraftEventFactory { + victim.expToDrop = event.getDroppedExp(); + victim.newExp = event.getNewExp(); + +- for (org.bukkit.inventory.ItemStack stack : event.getDrops()) { +- if (stack == null || stack.getType() == Material.AIR) continue; ++ // Paper start ++ for (Entity.DefaultDrop drop : drops) { ++ final org.bukkit.inventory.ItemStack stack = drop.stack(); ++ if (drop == null || stack == null || stack.getType() == Material.AIR) continue; ++ // Paper end + +- world.dropItem(entity.getLocation(), stack); ++ drop.runConsumer(world, entity.getLocation()); // Paper + } + + return event;