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 e447aa85b51c7e28fcd89bf8aa7602c88d599239..0d4224c56bcdff54bbf6cc2d9d25e4d7b0ba187b 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayer.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java @@ -968,22 +968,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 - Restore vanilla drops behavior 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, false))); // Paper - Restore vanilla drops behavior; drop function taken from Inventory#dropAll (don't fire drop event) } } } if (this.shouldDropLoot() && this.level().getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { // Paper - fix player loottables running when mob loot gamerule is false // 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 - fix player loottables running when mob loot gamerule is false @@ -2481,8 +2479,8 @@ public class ServerPlayer extends Player { } @Override - public ItemEntity drop(ItemStack stack, boolean throwRandomly, boolean retainOwnership) { - ItemEntity entityitem = super.drop(stack, throwRandomly, retainOwnership); + public ItemEntity drop(ItemStack stack, boolean throwRandomly, boolean retainOwnership, boolean callDropEvent) { // Paper - Restore vanilla drops behavior; override method with most params + ItemEntity entityitem = super.drop(stack, throwRandomly, retainOwnership, callDropEvent); // Paper - Restore vanilla drops behavior; override method with most params if (entityitem == null) { return null; diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java index ee38f76e46c20e9fadfb5d4d602b7b82a8a22e51..4e96b6c05bf8b8235e91bdd26e5615c5299fd9aa 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java @@ -2524,6 +2524,25 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess @Nullable public ItemEntity spawnAtLocation(ItemStack stack, float yOffset) { + // Paper start - Restore vanilla drops behavior + 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.inventory.CraftItemType.bukkitToMinecraft(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 - Restore vanilla drops behavior if (stack.isEmpty()) { return null; } else if (this.level().isClientSide) { @@ -2531,14 +2550,21 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess } 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 - Restore vanilla drops behavior + ((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 - Restore vanilla drops behavior 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 - Call EntityDropItemEvent 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 6b156049b06b38afbbd7cf3155b06601e334a805..f024038d473e424aa08256040c1591d23871c20c 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java @@ -266,7 +266,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 - Restore vanilla drops behavior 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 aa22829c3d41118664a872540fdc8f716120c407..c23d4ee0a16d1ae7168b2496d97189a14256bdcc 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 @@ -537,10 +537,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 - Restore vanilla drops behavior; 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 b1b0b0554e0ad035463fd53aa440d9c079b7cfb4..2ed6845f16fab175e2e9e96e76391e63ab4a43e2 100644 --- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java @@ -614,7 +614,7 @@ public class ArmorStand extends LivingEntity { ItemStack itemstack = new ItemStack(Items.ARMOR_STAND); itemstack.set(DataComponents.CUSTOM_NAME, 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 - Restore vanilla drops behavior return this.brokenByAnything(damageSource); // Paper } @@ -628,7 +628,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 - Restore vanilla drops behavior; mirror so we can destroy it later - though this call site was safe & spawn drops correctly this.handItems.set(i, ItemStack.EMPTY); } } @@ -636,7 +636,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 - Restore vanilla drops behavior; 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 da89b73b08b27ec979545e825efe15aec5cf3af2..fe2fd48e8e8c7d5aed409605dda969d9017889d0 100644 --- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java @@ -964,17 +964,23 @@ 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 - Restore vanilla drops behavior } - 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 - Restore vanilla drops behavior // 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 final java.util.function.Function FROM_FUNCTION = stack -> { + if (stack == null) return null; + return new Entity.DefaultDrop(CraftItemType.bukkitToMinecraft(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 - Restore vanilla drops behavior populateFields(victim, event); // Paper - make cancellable CraftWorld world = (CraftWorld) entity.getWorld(); Bukkit.getServer().getPluginManager().callEvent(event); @@ -988,19 +994,23 @@ public class CraftEventFactory { victim.expToDrop = event.getDroppedExp(); lootCheck.run(); // Paper - advancement triggers before destroying items - for (org.bukkit.inventory.ItemStack stack : event.getDrops()) { + // Paper start - Restore vanilla drops behavior + for (Entity.DefaultDrop drop : drops) { + if (drop == null) continue; + final org.bukkit.inventory.ItemStack stack = drop.stack(); + // Paper end - Restore vanilla drops behavior if (stack == null || stack.getType() == Material.AIR || stack.getAmount() == 0) continue; - world.dropItem(entity.getLocation(), stack); // Paper - note: dropItem already clones due to this being bukkit -> NMS + drop.runConsumer(world, entity.getLocation()); // Paper - Restore vanilla drops behavior 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, boolean keepInventory) { // Paper - Adventure + public static PlayerDeathEvent callPlayerDeathEvent(ServerPlayer victim, List drops, net.kyori.adventure.text.Component deathMessage, boolean keepInventory) { // Paper - Adventure & Restore vanilla drops behavior CraftPlayer entity = victim.getBukkitEntity(); - PlayerDeathEvent event = new PlayerDeathEvent(entity, drops, victim.getExpReward(), 0, deathMessage); + PlayerDeathEvent event = new PlayerDeathEvent(entity, new io.papermc.paper.util.TransformingRandomAccessList<>(drops, Entity.DefaultDrop::stack, FROM_FUNCTION), victim.getExpReward(), 0, deathMessage); // Paper - Restore vanilla drops behavior event.setKeepInventory(keepInventory); event.setKeepLevel(victim.keepLevel); // SPIGOT-2222: pre-set keepLevel populateFields(victim, event); // Paper - make cancellable @@ -1019,10 +1029,14 @@ public class CraftEventFactory { victim.expToDrop = event.getDroppedExp(); victim.newExp = event.getNewExp(); - for (org.bukkit.inventory.ItemStack stack : event.getDrops()) { + // Paper start - Restore vanilla drops behavior + for (Entity.DefaultDrop drop : drops) { + if (drop == null) continue; + final org.bukkit.inventory.ItemStack stack = drop.stack(); + // Paper end - Restore vanilla drops behavior if (stack == null || stack.getType() == Material.AIR) continue; - world.dropItem(entity.getLocation(), stack); + drop.runConsumer(world, entity.getLocation()); // Paper - Restore vanilla drops behavior } return event;