Paper/patches/server/0902-Restore-vanilla-entity-drops-behavior.patch
Jake Potrebic c6aa61ee18
Updated Upstream (Bukkit/CraftBukkit/Spigot) (#11561)
Updated Upstream (Bukkit/CraftBukkit/Spigot)

Upstream has released updates that appear to apply and compile correctly.
This update has not been tested by PaperMC and as with ANY update, please do your own testing

Bukkit Changes:
b9df8e9f SPIGOT-7933: Improve custom Minecart max speed
fc496179 Fix InstrumentTest
7c0ec598 PR-1075: Make Art an interface
c389f5a4 PR-1074: Make Sound an interface

CraftBukkit Changes:
df1efc0bb SPIGOT-7945: `Bukkit#dispatchCommand` throws `UnsupportedOperationException`
285df6e85 SPIGOT-7933: Improve custom Minecart max speed
a0f3d4e50 SPIGOT-7940: Recipe book errors after reload
9e0618ec2 SPIGOT-7937: Cannot spawn minecart during world generation with minecart_improvements enabled
1eb4d28da SPIGOT-7941: Fix resistance over 4 amplify causing issues in damage
52b99158a PR-1504: Make Art an interface
e18ae35f1 PR-1502: Make Sound an interface

Spigot Changes:
e65d67a7 SPIGOT-7934: Item entities start "bouncing" under certain conditions
2024-11-04 18:42:38 +01:00

270 lines
19 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
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 419fcb4cd97cf10a2601e02024b999a51a0ff952..df21cd1bd2a3dda7169edbea18bbfdf043db76f8 100644
--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
@@ -1235,20 +1235,20 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player {
if (this.isRemoved()) {
return;
}
- java.util.List<org.bukkit.inventory.ItemStack> loot = new java.util.ArrayList<>(this.getInventory().getContainerSize());
+ List<DefaultDrop> loot = new java.util.ArrayList<>(this.getInventory().getContainerSize()); // Paper - Restore vanilla drops behavior
boolean keepInventory = this.serverLevel().getGameRules().getBoolean(GameRules.RULE_KEEPINVENTORY) || this.isSpectator();
if (!keepInventory) {
for (ItemStack item : this.getInventory().getContents()) {
if (!item.isEmpty() && !EnchantmentHelper.has(item, EnchantmentEffectComponents.PREVENT_EQUIPMENT_DROP)) {
- loot.add(CraftItemStack.asCraftMirror(item).markForInventoryDrop());
+ 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.serverLevel().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(this.serverLevel(), damageSource, this.lastHurtByPlayerTime > 0);
- this.dropCustomDeathLoot(this.serverLevel(), damageSource, flag);
+ // Paper - Restore vanilla drops behaviour; custom death loot is a noop on server player, remove.
loot.addAll(this.drops);
this.drops.clear(); // SPIGOT-5188: make sure to clear
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
index 7657888bab53428fda9d3d10ac35bfddc5c16266..b7594a0fd6fe7e25d13202c95b2c39a04e84abde 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -2679,19 +2679,45 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@Nullable
public ItemEntity spawnAtLocation(ServerLevel world, ItemStack stack, float yOffset) {
+ // Paper start - Restore vanilla drops behavior
+ return this.spawnAtLocation(world, stack, yOffset, null);
+ }
+ public record DefaultDrop(Item item, org.bukkit.inventory.ItemStack stack, @Nullable java.util.function.Consumer<ItemStack> dropConsumer) {
+ public DefaultDrop(final ItemStack stack, final java.util.function.Consumer<ItemStack> dropConsumer) {
+ this(stack.getItem(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack), dropConsumer);
+ }
+
+ public void runConsumer(final java.util.function.Consumer<org.bukkit.inventory.ItemStack> fallback) {
+ if (this.dropConsumer == null || org.bukkit.craftbukkit.inventory.CraftItemType.bukkitToMinecraft(this.stack.getType()) != this.item) {
+ fallback.accept(this.stack);
+ } else {
+ this.dropConsumer.accept(org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(this.stack));
+ }
+ }
+ }
+ @Nullable
+ public ItemEntity spawnAtLocation(ServerLevel world, ItemStack stack, float yOffset, @Nullable java.util.function.Consumer<? super ItemEntity> delayedAddConsumer) {
+ // Paper end - Restore vanilla drops behavior
if (stack.isEmpty()) {
return null;
} 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(world, 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(world, entityitem);
}
diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java
index 22c565fd6b14ae018f4526b174388ed5247600f6..8c3ec957210a259e26253dbb3a4191702ce6699c 100644
--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java
+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java
@@ -289,7 +289,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
protected float appliedScale;
// CraftBukkit start
public int expToDrop;
- public ArrayList<org.bukkit.inventory.ItemStack> drops = new ArrayList<org.bukkit.inventory.ItemStack>();
+ public ArrayList<DefaultDrop> drops = new ArrayList<>(); // Paper - Restore vanilla drops behavior
public final org.bukkit.craftbukkit.attribute.CraftAttributeMap craftAttributes;
public boolean collides = true;
public Set<UUID> 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 e6b18d7f8922cb42acb9e40bef2f71a56aea8646..e1be143959fbaa1d54af2a1a2c27187d70e6a9e9 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
@@ -536,10 +536,10 @@ public class WitherBoss extends Monster implements RangedAttackMob {
@Override
protected void dropCustomDeathLoot(ServerLevel world, DamageSource source, boolean causedByPlayer) {
super.dropCustomDeathLoot(world, source, causedByPlayer);
- ItemEntity entityitem = this.spawnAtLocation(world, (ItemLike) Items.NETHER_STAR);
+ ItemEntity entityitem = this.spawnAtLocation(world, 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 2bb2b36f793d25b6e49d1a72bb665cfa9f212730..63f02cdc67d9e88cc6998d0ae9d139c83e85b447 100644
--- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java
+++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java
@@ -619,7 +619,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(world, damageSource); // Paper
}
@@ -633,7 +633,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);
}
}
@@ -641,7 +641,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 b6c30bb70fb4746da024bc4d80b71aeb3558f101..1c87019f5eb8e51accef3dc7ee949cdf2bec8f72 100644
--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
@@ -973,19 +973,25 @@ public class CraftEventFactory {
}
public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim, DamageSource damageSource) {
- return CraftEventFactory.callEntityDeathEvent(victim, damageSource, new ArrayList<org.bukkit.inventory.ItemStack>(0));
+ return CraftEventFactory.callEntityDeathEvent(victim, damageSource, new ArrayList<>(0)); // Paper - Restore vanilla drops behavior
}
- public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim, DamageSource damageSource, List<org.bukkit.inventory.ItemStack> drops) {
+ public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim, DamageSource damageSource, List<Entity.DefaultDrop> drops) { // Paper - Restore vanilla drops behavior
// Paper start
return CraftEventFactory.callEntityDeathEvent(victim, damageSource, drops, com.google.common.util.concurrent.Runnables.doNothing());
}
- public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim, DamageSource damageSource, List<org.bukkit.inventory.ItemStack> drops, Runnable lootCheck) {
+
+ private static final java.util.function.Function<org.bukkit.inventory.ItemStack, Entity.DefaultDrop> 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, DamageSource damageSource, List<Entity.DefaultDrop> drops, Runnable lootCheck) { // Paper - Restore vanilla drops behavior
// Paper end
CraftLivingEntity entity = (CraftLivingEntity) victim.getBukkitEntity();
CraftDamageSource bukkitDamageSource = new CraftDamageSource(damageSource);
CraftWorld world = (CraftWorld) entity.getWorld();
- EntityDeathEvent event = new EntityDeathEvent(entity, bukkitDamageSource, drops, victim.getExpReward(world.getHandle(), damageSource.getEntity()));
+ EntityDeathEvent event = new EntityDeathEvent(entity, bukkitDamageSource, new io.papermc.paper.util.TransformingRandomAccessList<>(drops, Entity.DefaultDrop::stack, FROM_FUNCTION), victim.getExpReward(world.getHandle(), damageSource.getEntity())); // Paper - Restore vanilla drops behavior
populateFields(victim, event); // Paper - make cancellable
Bukkit.getServer().getPluginManager().callEvent(event);
@@ -998,20 +1004,24 @@ 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(s -> world.dropItem(entity.getLocation(), s)); // 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, DamageSource damageSource, List<org.bukkit.inventory.ItemStack> drops, net.kyori.adventure.text.Component deathMessage, boolean keepInventory) { // Paper - Adventure
+ public static PlayerDeathEvent callPlayerDeathEvent(ServerPlayer victim, DamageSource damageSource, List<Entity.DefaultDrop> drops, net.kyori.adventure.text.Component deathMessage, boolean keepInventory) { // Paper - Adventure & Restore vanilla drops behavior
CraftPlayer entity = victim.getBukkitEntity();
CraftDamageSource bukkitDamageSource = new CraftDamageSource(damageSource);
- PlayerDeathEvent event = new PlayerDeathEvent(entity, bukkitDamageSource, drops, victim.getExpReward(victim.serverLevel(), damageSource.getEntity()), 0, deathMessage);
+ PlayerDeathEvent event = new PlayerDeathEvent(entity, bukkitDamageSource, new io.papermc.paper.util.TransformingRandomAccessList<>(drops, Entity.DefaultDrop::stack, FROM_FUNCTION), victim.getExpReward(victim.serverLevel(), damageSource.getEntity()), 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
@@ -1029,16 +1039,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;
- if (stack instanceof CraftItemStack craftItemStack && craftItemStack.isForInventoryDrop()) {
- victim.drop(CraftItemStack.asNMSCopy(stack), true, false, false); // SPIGOT-7800, SPIGOT-7801: Vanilla Behaviour for Player Inventory dropped items
- } else {
- victim.forceDrops = true;
- victim.spawnAtLocation(victim.serverLevel(), CraftItemStack.asNMSCopy(stack)); // SPIGOT-7806: Vanilla Behaviour for items not related to Player Inventory dropped items
- victim.forceDrops = false;
- }
+ drop.runConsumer(s -> victim.drop(CraftItemStack.unwrap(s), true, false, false)); // Paper - Restore vanilla drops behavior
}
return event;
diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java
index 30eba68435387daa3917fa2b3071892c350d9ddc..0b7bc5e83634a26ac6521694377b554c74c6bff0 100644
--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java
+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java
@@ -142,27 +142,6 @@ public final class CraftItemStack extends ItemStack {
this.setItemMeta(itemMeta);
}
- /**
- * Gets if the item is marked as an inventory drop in death events.
- *
- * @return true if the item is marked as an inventory drop
- */
- @ApiStatus.Internal
- public boolean isForInventoryDrop() {
- return this.isForInventoryDrop;
- }
-
- /**
- * Marks this item as an inventory drop in death events.
- *
- * @return the ItemStack marked as an inventory drop
- */
- @ApiStatus.Internal
- public ItemStack markForInventoryDrop() {
- this.isForInventoryDrop = true;
- return this;
- }
-
@Override
public MaterialData getData() {
return this.handle != null ? CraftMagicNumbers.getMaterialData(this.handle.getItem()) : super.getData();