From c16b696e414fe36743c9a4962e9eda921736b62d Mon Sep 17 00:00:00 2001 From: Doc Date: Wed, 15 Jan 2025 19:14:37 +1100 Subject: [PATCH] SPIGOT-7967, #887: Call explode events for EXPLODE effect (enchantments) and non block changes (mobGriefing false) --- .../enchantment/effects/ExplodeEffect.patch | 11 +++ .../world/level/ServerExplosion.patch | 73 +++++++------------ .../craftbukkit/event/CraftEventFactory.java | 35 +++++++++ 3 files changed, 73 insertions(+), 46 deletions(-) create mode 100644 nms-patches/net/minecraft/world/item/enchantment/effects/ExplodeEffect.patch diff --git a/nms-patches/net/minecraft/world/item/enchantment/effects/ExplodeEffect.patch b/nms-patches/net/minecraft/world/item/enchantment/effects/ExplodeEffect.patch new file mode 100644 index 000000000..918a60019 --- /dev/null +++ b/nms-patches/net/minecraft/world/item/enchantment/effects/ExplodeEffect.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/item/enchantment/effects/ExplodeEffect.java ++++ b/net/minecraft/world/item/enchantment/effects/ExplodeEffect.java +@@ -40,7 +40,7 @@ + + @Nullable + private DamageSource getDamageSource(Entity entity, Vec3D vec3d) { +- return this.damageType.isEmpty() ? null : (this.attributeToUser ? new DamageSource((Holder) this.damageType.get(), entity) : new DamageSource((Holder) this.damageType.get(), vec3d)); ++ return this.damageType.isEmpty() ? (entity == null ? null : entity.level().damageSources().explosion(null).customCausingEntityDamager(entity)) : (this.attributeToUser ? new DamageSource((Holder) this.damageType.get(), entity) : new DamageSource((Holder) this.damageType.get(), vec3d)); // CraftBukkit - copy from explosion default damagesource to allow tracking entity behind the effect + } + + @Override diff --git a/nms-patches/net/minecraft/world/level/ServerExplosion.patch b/nms-patches/net/minecraft/world/level/ServerExplosion.patch index 424ef5419..d118a865e 100644 --- a/nms-patches/net/minecraft/world/level/ServerExplosion.patch +++ b/nms-patches/net/minecraft/world/level/ServerExplosion.patch @@ -1,6 +1,6 @@ --- a/net/minecraft/world/level/ServerExplosion.java +++ b/net/minecraft/world/level/ServerExplosion.java -@@ -35,6 +35,17 @@ +@@ -35,6 +35,13 @@ import net.minecraft.world.phys.MovingObjectPosition; import net.minecraft.world.phys.Vec3D; @@ -9,16 +9,12 @@ +import net.minecraft.world.entity.boss.enderdragon.EntityEnderDragon; +import net.minecraft.world.level.block.Blocks; +import org.bukkit.craftbukkit.event.CraftEventFactory; -+import org.bukkit.craftbukkit.util.CraftLocation; -+import org.bukkit.event.entity.EntityExplodeEvent; -+import org.bukkit.Location; -+import org.bukkit.event.block.BlockExplodeEvent; +// CraftBukkit end + public class ServerExplosion implements Explosion { private static final ExplosionDamageCalculator EXPLOSION_DAMAGE_CALCULATOR = new ExplosionDamageCalculator(); -@@ -50,16 +61,21 @@ +@@ -50,16 +57,21 @@ private final DamageSource damageSource; private final ExplosionDamageCalculator damageCalculator; private final Map hitPlayers = new HashMap(); @@ -41,7 +37,7 @@ } private ExplosionDamageCalculator makeDamageCalculator(@Nullable Entity entity) { -@@ -195,7 +211,35 @@ +@@ -195,7 +207,35 @@ float f2 = !flag && f1 == 0.0F ? 0.0F : getSeenPercent(this.center, entity); if (flag) { @@ -78,7 +74,7 @@ } double d5 = (1.0D - d0) * (double) f2 * (double) f1; -@@ -214,6 +258,17 @@ +@@ -214,6 +254,17 @@ d3 *= d6; Vec3D vec3d = new Vec3D(d1, d2, d3); @@ -96,45 +92,15 @@ entity.push(vec3d); if (entity instanceof EntityHuman) { EntityHuman entityhuman = (EntityHuman) entity; -@@ -235,10 +290,62 @@ +@@ -235,10 +286,31 @@ List list1 = new ArrayList(); SystemUtils.shuffle(list, this.level.random); + // CraftBukkit start -+ org.bukkit.World bworld = this.level.getWorld(); -+ Location location = CraftLocation.toBukkit(this.center, bworld); -+ -+ List blockList = new ObjectArrayList<>(); -+ for (int i1 = list.size() - 1; i1 >= 0; i1--) { -+ BlockPosition cpos = list.get(i1); -+ org.bukkit.block.Block bblock = bworld.getBlockAt(cpos.getX(), cpos.getY(), cpos.getZ()); -+ if (!bblock.getType().isAir()) { -+ blockList.add(bblock); -+ } -+ } -+ -+ List bukkitBlocks; -+ -+ if (this.source != null) { -+ EntityExplodeEvent event = CraftEventFactory.callEntityExplodeEvent(this.source, blockList, this.yield, getBlockInteraction()); -+ this.wasCanceled = event.isCancelled(); -+ bukkitBlocks = event.blockList(); -+ this.yield = event.getYield(); -+ } else { -+ org.bukkit.block.Block block = location.getBlock(); -+ org.bukkit.block.BlockState blockState = (damageSource.getDirectBlockState() != null) ? damageSource.getDirectBlockState() : block.getState(); -+ BlockExplodeEvent event = CraftEventFactory.callBlockExplodeEvent(block, blockState, blockList, this.yield, getBlockInteraction()); -+ this.wasCanceled = event.isCancelled(); -+ bukkitBlocks = event.blockList(); -+ this.yield = event.getYield(); -+ } ++ List bukkitBlocks = CraftEventFactory.handleExplodeEvent(this, list); + + list.clear(); -+ -+ for (org.bukkit.block.Block bblock : bukkitBlocks) { -+ BlockPosition coords = new BlockPosition(bblock.getX(), bblock.getY(), bblock.getZ()); -+ list.add(coords); -+ } ++ list.addAll(bukkitBlocks.stream().map(bblock -> new BlockPosition(bblock.getX(), bblock.getY(), bblock.getZ())).toList()); + + if (this.wasCanceled) { + return; @@ -148,9 +114,8 @@ + IBlockData iblockdata = this.level.getBlockState(blockposition); + Block block = iblockdata.getBlock(); + if (block instanceof net.minecraft.world.level.block.BlockTNT) { -+ Entity sourceEntity = source == null ? null : source; -+ BlockPosition sourceBlock = sourceEntity == null ? BlockPosition.containing(this.center) : null; -+ if (!CraftEventFactory.callTNTPrimeEvent(this.level, blockposition, org.bukkit.event.block.TNTPrimeEvent.PrimeCause.EXPLOSION, sourceEntity, sourceBlock)) { ++ BlockPosition sourceBlock = source == null ? BlockPosition.containing(this.center) : null; ++ if (!CraftEventFactory.callTNTPrimeEvent(this.level, blockposition, org.bukkit.event.block.TNTPrimeEvent.PrimeCause.EXPLOSION, source, sourceBlock)) { + this.level.sendBlockUpdated(blockposition, Blocks.AIR.defaultBlockState(), iblockdata, 3); // Update the block on the client + continue; + } @@ -159,7 +124,7 @@ this.level.getBlockState(blockposition).onExplosionHit(this.level, blockposition, this, (itemstack, blockposition1) -> { addOrAppendStack(list1, itemstack, blockposition1); -@@ -262,13 +369,22 @@ +@@ -262,13 +334,22 @@ BlockPosition blockposition = (BlockPosition) iterator.next(); if (this.level.random.nextInt(3) == 0 && this.level.getBlockState(blockposition).isAir() && this.level.getBlockState(blockposition.below()).isSolidRender()) { @@ -183,7 +148,23 @@ this.level.gameEvent(this.source, (Holder) GameEvent.EXPLODE, this.center); List list = this.calculateExplodedPositions(); -@@ -288,6 +404,7 @@ +@@ -279,7 +360,15 @@ + gameprofilerfiller.push("explosion_blocks"); + this.interactWithBlocks(list); + gameprofilerfiller.pop(); ++ // CraftBukkit start - handle KEEP effect ++ } else { ++ SystemUtils.shuffle(list, this.level.random); // CraftBukkit - Copy from calculateExplodedPositions ++ List bukkitBlocks = CraftEventFactory.handleExplodeEvent(this, list); ++ ++ list.clear(); ++ list.addAll(bukkitBlocks.stream().map(bblock -> new BlockPosition(bblock.getX(), bblock.getY(), bblock.getZ())).toList()); + } ++ // CraftBukkit end + + if (this.fire) { + this.createFire(list); +@@ -288,6 +377,7 @@ } private static void addOrAppendStack(List list, ItemStack itemstack, BlockPosition blockposition) { diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java index 9ef6ac886..d6fec9ba3 100644 --- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java @@ -5,6 +5,7 @@ import com.google.common.base.Functions; import com.google.common.base.Preconditions; import com.google.common.collect.Lists; import com.mojang.datafixers.util.Either; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.ArrayList; @@ -66,6 +67,7 @@ import net.minecraft.world.item.crafting.RecipeHolder; import net.minecraft.world.level.ChunkCoordIntPair; import net.minecraft.world.level.Explosion; import net.minecraft.world.level.GeneratorAccess; +import net.minecraft.world.level.ServerExplosion; import net.minecraft.world.level.World; import net.minecraft.world.level.block.entity.TileEntitySign; import net.minecraft.world.level.block.state.IBlockData; @@ -108,6 +110,7 @@ import org.bukkit.craftbukkit.inventory.CraftInventoryCrafting; import org.bukkit.craftbukkit.inventory.CraftItemStack; import org.bukkit.craftbukkit.inventory.CraftItemType; import org.bukkit.craftbukkit.potion.CraftPotionUtil; +import org.bukkit.craftbukkit.util.CraftLocation; import org.bukkit.craftbukkit.util.CraftNamespacedKey; import org.bukkit.craftbukkit.util.CraftVector; import org.bukkit.entity.AbstractHorse; @@ -1877,6 +1880,38 @@ public class CraftEventFactory { return !event.isCancelled(); } + public static List handleExplodeEvent(ServerExplosion serverExplosion, List blockPositions) { + // First convert the blockPositionList related to the explosion to Bukkit objects + org.bukkit.World bworld = serverExplosion.level().getWorld(); + + List blockList = new ObjectArrayList<>(); + for (int i1 = blockPositions.size() - 1; i1 >= 0; i1--) { + BlockPosition cpos = blockPositions.get(i1); + org.bukkit.block.Block bblock = bworld.getBlockAt(cpos.getX(), cpos.getY(), cpos.getZ()); + if (!bblock.getType().isAir()) { + blockList.add(bblock); + } + } + + // Handle based on explosion or damage source whether we need to call EntityExplodeEvent + if (serverExplosion.getDirectSourceEntity() != null || serverExplosion.getDamageSource().getCausingDamager() != null) { + EntityExplodeEvent event = CraftEventFactory.callEntityExplodeEvent((serverExplosion.getDirectSourceEntity() != null) ? serverExplosion.getDirectSourceEntity() : serverExplosion.getDamageSource().getCausingDamager(), blockList, serverExplosion.yield, serverExplosion.getBlockInteraction()); + serverExplosion.wasCanceled = event.isCancelled(); + serverExplosion.yield = event.getYield(); + return event.blockList(); + } + + // Else BlockExplodeEvent when entity not found in previous if statement + Location location = CraftLocation.toBukkit(serverExplosion.center(), bworld); + org.bukkit.block.Block block = location.getBlock(); + org.bukkit.block.BlockState blockState = (serverExplosion.getDamageSource().getDirectBlockState() != null) ? serverExplosion.getDamageSource().getDirectBlockState() : block.getState(); + BlockExplodeEvent event = CraftEventFactory.callBlockExplodeEvent(block, blockState, blockList, serverExplosion.yield, serverExplosion.getBlockInteraction()); + serverExplosion.wasCanceled = event.isCancelled(); + serverExplosion.yield = event.getYield(); + + return event.blockList(); + } + public static EntityExplodeEvent callEntityExplodeEvent(Entity entity, List blocks, float yield, Explosion.Effect effect) { EntityExplodeEvent event = new EntityExplodeEvent(entity.getBukkitEntity(), entity.getBukkitEntity().getLocation(), blocks, yield, CraftExplosionResult.toBukkit(effect)); Bukkit.getPluginManager().callEvent(event);