From ddbfcd44036df3193fc3d52db27258fc4acc6d06 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. --- .../server/level/ServerPlayer.java.patch | 6 +- .../minecraft/world/entity/Entity.java.patch | 161 +++++++++++------- .../world/entity/LivingEntity.java.patch | 2 +- .../entity/boss/wither/WitherBoss.java.patch | 13 ++ .../entity/decoration/ArmorStand.java.patch | 6 +- .../craftbukkit/event/CraftEventFactory.java | 40 +++-- .../craftbukkit/inventory/CraftItemStack.java | 21 --- 7 files changed, 141 insertions(+), 108 deletions(-) diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch index 31b8a1aa03..c8a12fa371 100644 --- a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch @@ -622,7 +622,7 @@ + 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.serverLevel().getGameRules().getBoolean(GameRules.RULE_KEEPINVENTORY) || this.isSpectator(); - if (flag) { @@ -630,14 +630,14 @@ + 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/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch index 61ffb2e55e..06cf6e51af 100644 --- a/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch @@ -18,7 +18,7 @@ import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.FenceGateBlock; -@@ -138,8 +138,152 @@ +@@ -138,9 +138,153 @@ import net.minecraft.world.scores.ScoreHolder; import net.minecraft.world.scores.Team; import org.slf4j.Logger; @@ -99,7 +99,7 @@ + public int next(int bits) { + return super.next(bits); + } -+ + + @Override + public int nextInt(int origin, int bound) { + return net.minecraft.world.level.levelgen.BitRandomSource.super.nextInt(origin, bound); @@ -168,9 +168,10 @@ + return Entity.TOTAL_AIR_SUPPLY; + } + // CraftBukkit end - ++ private static final Logger LOGGER = LogUtils.getLogger(); public static final String ID_TAG = "id"; + public static final String PASSENGERS_TAG = "Passengers"; @@ -224,7 +368,7 @@ private static final EntityDataAccessor DATA_CUSTOM_NAME_VISIBLE = SynchedEntityData.defineId(Entity.class, EntityDataSerializers.BOOLEAN); private static final EntityDataAccessor DATA_SILENT = SynchedEntityData.defineId(Entity.class, EntityDataSerializers.BOOLEAN); @@ -410,19 +411,21 @@ } protected final AABB makeBoundingBox() { -@@ -462,10 +734,20 @@ - this.baseTick(); - } +@@ -460,12 +732,22 @@ + public void tick() { + this.baseTick(); ++ } ++ + // CraftBukkit start + public void postTick() { + // No clean way to break out of ticking once the entity has been copied to a new world, so instead we move the portalling later in the tick cycle + if (!(this instanceof ServerPlayer) && this.isAlive()) { // Paper - don't attempt to teleport dead entities + this.handlePortal(); + } -+ } + } + // CraftBukkit end -+ + public void baseTick() { ProfilerFiller gameprofilerfiller = Profiler.get(); @@ -601,12 +604,10 @@ BlockPos blockposition = (BlockPos) this.mainSupportingBlockPos.get(); if (offset <= 1.0E-5F) { -@@ -1131,7 +1477,21 @@ - - protected SoundEvent getSwimHighSpeedSplashSound() { +@@ -1133,6 +1479,20 @@ return SoundEvents.GENERIC_SPLASH; -+ } -+ + } + + // CraftBukkit start - Add delegate methods + public SoundEvent getSwimSound0() { + return this.getSwimSound(); @@ -618,11 +619,12 @@ + + public SoundEvent getSwimHighSpeedSplashSound0() { + return this.getSwimHighSpeedSplashSound(); - } ++ } + // CraftBukkit end - ++ public void recordMovementThroughBlocks(Vec3 oldPos, Vec3 newPos) { this.movementThisTick.add(new Entity.Movement(oldPos, newPos)); + } @@ -1599,6 +1959,7 @@ this.setXRot(Mth.clamp(pitch, -90.0F, 90.0F) % 360.0F); this.yRotO = this.getYRot(); @@ -690,7 +692,7 @@ this.hasImpulse = true; } -@@ -1858,8 +2243,20 @@ +@@ -1858,9 +2243,21 @@ } public boolean isPushable() { @@ -702,15 +704,16 @@ + // Paper end - Climbing should not bypass cramming gamerule return false; } -+ + + // CraftBukkit start - collidable API + public boolean canCollideWithBukkit(Entity entity) { + return this.isPushable(); + } + // CraftBukkit end - ++ public void awardKillScore(Entity entityKilled, DamageSource damageSource) { if (entityKilled instanceof ServerPlayer) { + CriteriaTriggers.ENTITY_KILLED_PLAYER.trigger((ServerPlayer) entityKilled, this, damageSource); @@ -1889,74 +2286,133 @@ } @@ -1014,21 +1017,51 @@ protected abstract void readAdditionalSaveData(CompoundTag nbt); protected abstract void addAdditionalSaveData(CompoundTag nbt); -@@ -2153,9 +2707,31 @@ +@@ -2150,12 +2704,60 @@ + + @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 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 java.util.function.Consumer 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 delayedAddConsumer) { ++ // Paper end - Restore vanilla drops behavior if (stack.isEmpty()) { return null; } else { - ItemEntity entityitem = new ItemEntity(world, this.getX(), this.getY() + (double) yOffset, this.getZ(), stack); + // 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(); ++ entityitem.setDefaultPickUpDelay(); // Paper - diff on change (in dropConsumer) + // Paper start - Call EntityDropItemEvent + return this.spawnAtLocation(world, entityitem); + } @@ -1047,7 +1080,7 @@ world.addFreshEntity(entityitem); return entityitem; } -@@ -2184,7 +2760,16 @@ +@@ -2184,7 +2786,16 @@ if (this.isAlive() && this instanceof Leashable leashable) { if (leashable.getLeashHolder() == player) { if (!this.level().isClientSide()) { @@ -1065,7 +1098,7 @@ leashable.removeLeash(); } else { leashable.dropLeash(); -@@ -2200,6 +2785,14 @@ +@@ -2200,6 +2811,14 @@ if (itemstack.is(Items.LEAD) && leashable.canHaveALeashAttachedToIt()) { if (!this.level().isClientSide()) { @@ -1080,7 +1113,7 @@ leashable.setLeashedTo(player, true); } -@@ -2265,15 +2858,15 @@ +@@ -2265,15 +2884,15 @@ } public boolean showVehicleHealth() { @@ -1099,7 +1132,7 @@ return false; } else { for (Entity entity1 = entity; entity1.vehicle != null; entity1 = entity1.vehicle) { -@@ -2285,11 +2878,32 @@ +@@ -2285,11 +2904,32 @@ if (!force && (!this.canRide(entity) || !entity.canAddPassenger(this))) { return false; } else { @@ -1133,7 +1166,7 @@ this.vehicle = entity; this.vehicle.addPassenger(this); entity.getIndirectPassengersStream().filter((entity2) -> { -@@ -2314,19 +2928,30 @@ +@@ -2314,19 +2954,30 @@ } public void removeVehicle() { @@ -1166,7 +1199,7 @@ protected void addPassenger(Entity passenger) { if (passenger.getVehicle() != this) { throw new IllegalStateException("Use x.startRiding(y), not y.addPassenger(x)"); -@@ -2349,21 +2974,53 @@ +@@ -2349,21 +3000,53 @@ } } @@ -1226,7 +1259,7 @@ } protected boolean canAddPassenger(Entity passenger) { -@@ -2464,7 +3121,7 @@ +@@ -2464,7 +3147,7 @@ if (teleporttransition != null) { ServerLevel worldserver1 = teleporttransition.newLevel(); @@ -1235,7 +1268,7 @@ this.teleport(teleporttransition); } } -@@ -2547,7 +3204,7 @@ +@@ -2547,7 +3230,7 @@ } public boolean isCrouching() { @@ -1244,7 +1277,7 @@ } public boolean isSprinting() { -@@ -2563,7 +3220,7 @@ +@@ -2563,7 +3246,7 @@ } public boolean isVisuallySwimming() { @@ -1253,7 +1286,7 @@ } public boolean isVisuallyCrawling() { -@@ -2571,6 +3228,13 @@ +@@ -2571,6 +3254,13 @@ } public void setSwimming(boolean swimming) { @@ -1267,7 +1300,7 @@ this.setSharedFlag(4, swimming); } -@@ -2609,6 +3273,7 @@ +@@ -2609,6 +3299,7 @@ @Nullable public PlayerTeam getTeam() { @@ -1275,7 +1308,7 @@ return this.level().getScoreboard().getPlayersTeam(this.getScoreboardName()); } -@@ -2624,8 +3289,12 @@ +@@ -2624,8 +3315,12 @@ return this.getTeam() != null ? this.getTeam().isAlliedTo(team) : false; } @@ -1289,7 +1322,7 @@ } public boolean getSharedFlag(int index) { -@@ -2644,7 +3313,7 @@ +@@ -2644,7 +3339,7 @@ } public int getMaxAirSupply() { @@ -1298,7 +1331,7 @@ } public int getAirSupply() { -@@ -2652,7 +3321,18 @@ +@@ -2652,7 +3347,18 @@ } public void setAirSupply(int air) { @@ -1318,7 +1351,7 @@ } public int getTicksFrozen() { -@@ -2679,11 +3359,44 @@ +@@ -2679,11 +3385,44 @@ public void thunderHit(ServerLevel world, LightningBolt lightning) { this.setRemainingFireTicks(this.remainingFireTicks + 1); @@ -1365,7 +1398,7 @@ } public void onAboveBubbleCol(boolean drag) { -@@ -2713,7 +3426,7 @@ +@@ -2713,7 +3452,7 @@ this.resetFallDistance(); } @@ -1374,7 +1407,7 @@ return true; } -@@ -2818,7 +3531,7 @@ +@@ -2818,7 +3557,7 @@ public String toString() { String s = this.level() == null ? "~NULL~" : this.level().toString(); @@ -1383,7 +1416,7 @@ } public final boolean isInvulnerableToBase(DamageSource damageSource) { -@@ -2838,6 +3551,13 @@ +@@ -2838,6 +3577,13 @@ } public void restoreFrom(Entity original) { @@ -1397,7 +1430,7 @@ CompoundTag nbttagcompound = original.saveWithoutId(new CompoundTag()); nbttagcompound.remove("Dimension"); -@@ -2850,8 +3570,57 @@ +@@ -2850,8 +3596,57 @@ public Entity teleport(TeleportTransition teleportTarget) { Level world = this.level(); @@ -1455,7 +1488,7 @@ ServerLevel worldserver1 = teleportTarget.newLevel(); boolean flag = worldserver1.dimension() != worldserver.dimension(); -@@ -2918,10 +3687,19 @@ +@@ -2918,10 +3713,19 @@ gameprofilerfiller.pop(); return null; } else { @@ -1476,7 +1509,7 @@ Iterator iterator1 = list1.iterator(); while (iterator1.hasNext()) { -@@ -2947,7 +3725,7 @@ +@@ -2947,7 +3751,7 @@ } private void sendTeleportTransitionToRidingPlayers(TeleportTransition teleportTarget) { @@ -1485,7 +1518,7 @@ Iterator iterator = this.getIndirectPassengers().iterator(); while (iterator.hasNext()) { -@@ -2995,9 +3773,17 @@ +@@ -2995,9 +3799,17 @@ } protected void removeAfterChangingDimensions() { @@ -1506,7 +1539,7 @@ } } -@@ -3006,11 +3792,34 @@ +@@ -3006,11 +3818,34 @@ return PortalShape.getRelativePosition(portalRect, portalAxis, this.position(), this.getDimensions(this.getPose())); } @@ -1541,7 +1574,7 @@ if (from.dimension() == Level.END && to.dimension() == Level.OVERWORLD) { Iterator iterator = this.getPassengers().iterator(); -@@ -3134,10 +3943,16 @@ +@@ -3134,10 +3969,16 @@ return (Boolean) this.entityData.get(Entity.DATA_CUSTOM_NAME_VISIBLE); } @@ -1561,7 +1594,7 @@ return entity != null; } -@@ -3187,7 +4002,7 @@ +@@ -3187,7 +4028,7 @@ /** @deprecated */ @Deprecated protected void fixupDimensions() { @@ -1570,7 +1603,7 @@ EntityDimensions entitysize = this.getDimensions(entitypose); this.dimensions = entitysize; -@@ -3196,7 +4011,7 @@ +@@ -3196,7 +4037,7 @@ public void refreshDimensions() { EntityDimensions entitysize = this.dimensions; @@ -1579,7 +1612,7 @@ EntityDimensions entitysize1 = this.getDimensions(entitypose); this.dimensions = entitysize1; -@@ -3258,10 +4073,29 @@ +@@ -3258,10 +4099,29 @@ } public final void setBoundingBox(AABB boundingBox) { @@ -1611,7 +1644,7 @@ return this.getDimensions(pose).eyeHeight(); } -@@ -3300,7 +4134,14 @@ +@@ -3300,7 +4160,14 @@ public void startSeenByPlayer(ServerPlayer player) {} @@ -1627,7 +1660,7 @@ public float rotate(Rotation rotation) { float f = Mth.wrapDegrees(this.getYRot()); -@@ -3335,7 +4176,7 @@ +@@ -3335,7 +4202,7 @@ } @Nullable @@ -1636,7 +1669,7 @@ return null; } -@@ -3373,20 +4214,34 @@ +@@ -3373,20 +4240,34 @@ } private Stream getIndirectPassengersStream() { @@ -1671,7 +1704,7 @@ return () -> { return this.getIndirectPassengersStream().iterator(); }; -@@ -3399,6 +4254,7 @@ +@@ -3399,6 +4280,7 @@ } public boolean hasExactlyOnePlayerPassenger() { @@ -1679,7 +1712,7 @@ return this.countPlayerPassengers() == 1; } -@@ -3435,7 +4291,7 @@ +@@ -3435,7 +4317,7 @@ } public boolean isControlledByLocalInstance() { @@ -1688,7 +1721,7 @@ if (entityliving instanceof Player entityhuman) { return entityhuman.isLocalPlayer(); -@@ -3445,7 +4301,7 @@ +@@ -3445,7 +4327,7 @@ } public boolean isControlledByClient() { @@ -1697,7 +1730,7 @@ return entityliving != null && entityliving.isControlledByClient(); } -@@ -3463,7 +4319,7 @@ +@@ -3463,7 +4345,7 @@ return new Vec3((double) f1 * d2 / (double) f3, 0.0D, (double) f2 * d2 / (double) f3); } @@ -1706,7 +1739,7 @@ return new Vec3(this.getX(), this.getBoundingBox().maxY, this.getZ()); } -@@ -3489,8 +4345,37 @@ +@@ -3489,8 +4371,37 @@ return 1; } @@ -1745,7 +1778,7 @@ } public void lookAt(EntityAnchorArgument.Anchor anchorPoint, Vec3 target) { -@@ -3551,6 +4436,11 @@ +@@ -3551,6 +4462,11 @@ vec3d = vec3d.add(vec3d1); ++k1; } @@ -1757,7 +1790,7 @@ } } } -@@ -3613,7 +4503,7 @@ +@@ -3613,7 +4529,7 @@ return new ClientboundAddEntityPacket(this, entityTrackerEntry); } @@ -1766,7 +1799,7 @@ return this.type.getDimensions(); } -@@ -3714,7 +4604,39 @@ +@@ -3714,7 +4630,39 @@ return this.getZ((2.0D * this.random.nextDouble() - 1.0D) * widthScale); } @@ -1806,7 +1839,7 @@ if (this.position.x != x || this.position.y != y || this.position.z != z) { this.position = new Vec3(x, y, z); int i = Mth.floor(x); -@@ -3732,6 +4654,12 @@ +@@ -3732,6 +4680,12 @@ this.levelCallback.onMove(); } @@ -1819,7 +1852,7 @@ } public void checkDespawn() {} -@@ -3818,8 +4746,17 @@ +@@ -3818,8 +4772,17 @@ @Override public final void setRemoved(Entity.RemovalReason reason) { @@ -1838,7 +1871,7 @@ } if (this.removalReason.shouldDestroy()) { -@@ -3827,14 +4764,30 @@ +@@ -3827,14 +4790,30 @@ } this.getPassengers().forEach(Entity::stopRiding); @@ -1871,7 +1904,7 @@ @Override public void setLevelCallback(EntityInLevelCallback changeListener) { this.levelCallback = changeListener; -@@ -3887,7 +4840,7 @@ +@@ -3887,7 +4866,7 @@ } public Vec3 getKnownMovement() { @@ -1880,7 +1913,7 @@ if (entityliving instanceof Player entityhuman) { if (this.isAlive()) { -@@ -3962,4 +4915,14 @@ +@@ -3962,4 +4941,14 @@ void accept(Entity entity, double x, double y, double z); } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch index 4a71ab8db0..10e3db8f32 100644 --- a/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch @@ -72,7 +72,7 @@ protected float appliedScale; + // CraftBukkit start + public int expToDrop; -+ 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/paper-server/patches/sources/net/minecraft/world/entity/boss/wither/WitherBoss.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/boss/wither/WitherBoss.java.patch index 38a819cef3..bf9b1fb3ae 100644 --- a/paper-server/patches/sources/net/minecraft/world/entity/boss/wither/WitherBoss.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/entity/boss/wither/WitherBoss.java.patch @@ -126,6 +126,19 @@ } this.bossEvent.setProgress(this.getHealth() / this.getMaxHealth()); +@@ -488,10 +535,10 @@ + @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 + } + + } @@ -499,7 +546,7 @@ @Override public void checkDespawn() { diff --git a/paper-server/patches/sources/net/minecraft/world/entity/decoration/ArmorStand.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/decoration/ArmorStand.java.patch index 9f4be8d8f9..edb69ea66e 100644 --- a/paper-server/patches/sources/net/minecraft/world/entity/decoration/ArmorStand.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/entity/decoration/ArmorStand.java.patch @@ -347,7 +347,7 @@ itemstack.set(DataComponents.CUSTOM_NAME, this.getCustomName()); - Block.popResource(this.level(), this.blockPosition(), itemstack); - this.brokenByAnything(world, damageSource); -+ 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 } @@ -364,7 +364,7 @@ itemstack = (ItemStack) this.handItems.get(i); if (!itemstack.isEmpty()) { - Block.popResource(this.level(), this.blockPosition().above(), itemstack); -+ 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); } } @@ -373,7 +373,7 @@ itemstack = (ItemStack) this.armorItems.get(i); if (!itemstack.isEmpty()) { - Block.popResource(this.level(), this.blockPosition().above(), itemstack); -+ 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/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java index b6c30bb70f..1c87019f5e 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/paper-server/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(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 drops) { + public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim, DamageSource damageSource, List 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 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, DamageSource damageSource, List 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 drops, net.kyori.adventure.text.Component deathMessage, boolean keepInventory) { // Paper - Adventure + public static PlayerDeathEvent callPlayerDeathEvent(ServerPlayer victim, DamageSource damageSource, List 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/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java index 30eba68435..0b7bc5e836 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +++ b/paper-server/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();