From 2a473745d071a014a83074348d621a93d60c5de1 Mon Sep 17 00:00:00 2001 From: JustAHuman-xD Date: Sat, 23 May 2026 12:18:05 -0500 Subject: [PATCH 1/5] add event --- .../event/entity/GolemConstructEvent.java | 62 +++++++++++++++++++ .../level/block/CarvedPumpkinBlock.java.patch | 12 +++- 2 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 paper-api/src/main/java/io/papermc/paper/event/entity/GolemConstructEvent.java diff --git a/paper-api/src/main/java/io/papermc/paper/event/entity/GolemConstructEvent.java b/paper-api/src/main/java/io/papermc/paper/event/entity/GolemConstructEvent.java new file mode 100644 index 000000000000..c0853ab96c20 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/event/entity/GolemConstructEvent.java @@ -0,0 +1,62 @@ +package io.papermc.paper.event.entity; + +import org.bukkit.block.Block; +import org.bukkit.entity.Golem; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Unmodifiable; +import org.jspecify.annotations.NullMarked; +import java.util.List; + +/** + * Called just before a {@link Golem} spawns due to a pattern of blocks being constructed. + */ +@NullMarked +public class GolemConstructEvent extends EntityEvent implements Cancellable { + + private static final HandlerList HANDLER_LIST = new HandlerList(); + + private final List blocks; + private boolean cancelled; + + @ApiStatus.Internal + public GolemConstructEvent(Golem golem, List blocks) { + super(golem); + this.blocks = List.copyOf(blocks); + } + + @Override + public Golem getEntity() { + return (Golem) super.getEntity(); + } + + /** + * Get an immutable list of the blocks for this golem + * + * @return the golem blocks + */ + public @Unmodifiable List getBlocks() { + return blocks; + } + + @Override + public void setCancelled(final boolean cancel) { + this.cancelled = cancel; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public HandlerList getHandlers() { + return HANDLER_LIST; + } + + public static HandlerList getHandlerList() { + return HANDLER_LIST; + } +} diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/CarvedPumpkinBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/CarvedPumpkinBlock.java.patch index 53f2111574c8..ce9a06013c66 100644 --- a/paper-server/patches/sources/net/minecraft/world/level/block/CarvedPumpkinBlock.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/level/block/CarvedPumpkinBlock.java.patch @@ -8,7 +8,7 @@ this.replaceCopperBlockWithChest(level, copperGolemMatch); copperGolem.spawn(this.getWeatherStateFromPattern(copperGolemMatch)); } -@@ -105,9 +_,22 @@ +@@ -105,9 +_,32 @@ } private static void spawnGolemInWorld(final Level level, final BlockPattern.BlockPatternMatch match, final Entity golem, final BlockPos spawnPos) { @@ -17,6 +17,16 @@ golem.snapTo(spawnPos.getX() + 0.5, spawnPos.getY() + 0.05, spawnPos.getZ() + 0.5, 0.0F, 0.0F); - level.addFreshEntity(golem); + // Paper start ++ java.util.List blocks = new java.util.ArrayList<>(); ++ for (int x = 0; x < match.getWidth(); x++) { ++ for (int y = 0; y < match.getHeight(); y++) { ++ BlockInWorld block = match.getBlock(x, y, 0); ++ blocks.add(org.bukkit.craftbukkit.block.CraftBlock.at(level, block.getPos())); ++ } ++ } ++ if (!new io.papermc.paper.event.entity.GolemConstructEvent((org.bukkit.entity.Golem) golem.getBukkitEntity(), blocks).callEvent()) { ++ return; ++ } + org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason; + if (golem.getType() == net.minecraft.world.entity.EntityType.SNOW_GOLEM) { + spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BUILD_SNOWMAN; From 9c510551261c512dad14833e3cde8e016703f28e Mon Sep 17 00:00:00 2001 From: JustAHuman-xD Date: Sat, 23 May 2026 12:29:36 -0500 Subject: [PATCH 2/5] better javadoc --- .../io/papermc/paper/event/entity/GolemConstructEvent.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/paper-api/src/main/java/io/papermc/paper/event/entity/GolemConstructEvent.java b/paper-api/src/main/java/io/papermc/paper/event/entity/GolemConstructEvent.java index c0853ab96c20..5f466ed97c29 100644 --- a/paper-api/src/main/java/io/papermc/paper/event/entity/GolemConstructEvent.java +++ b/paper-api/src/main/java/io/papermc/paper/event/entity/GolemConstructEvent.java @@ -5,6 +5,7 @@ import org.bukkit.event.Cancellable; import org.bukkit.event.HandlerList; import org.bukkit.event.entity.EntityEvent; +import org.bukkit.event.entity.EntitySpawnEvent; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Unmodifiable; import org.jspecify.annotations.NullMarked; @@ -12,6 +13,9 @@ /** * Called just before a {@link Golem} spawns due to a pattern of blocks being constructed. + *
+ * Note: This event is fired before {@link EntitySpawnEvent}, before the golem is added to the world, + * the success of this event does not guarantee the golem will actually spawn. */ @NullMarked public class GolemConstructEvent extends EntityEvent implements Cancellable { From fe20e5aa2c5ade5d1e4e8bc398e518e00e0fc8bd Mon Sep 17 00:00:00 2001 From: JustAHuman-xD Date: Fri, 5 Jun 2026 01:42:47 -0500 Subject: [PATCH 3/5] generic entity construct event --- ...ctEvent.java => EntityConstructEvent.java} | 25 ++++++++----------- .../level/block/CarvedPumpkinBlock.java.patch | 11 ++------ .../level/block/WitherSkullBlock.java.patch | 7 +++++- .../bukkit/craftbukkit/block/CraftBlock.java | 13 ++++++++++ 4 files changed, 32 insertions(+), 24 deletions(-) rename paper-api/src/main/java/io/papermc/paper/event/entity/{GolemConstructEvent.java => EntityConstructEvent.java} (66%) diff --git a/paper-api/src/main/java/io/papermc/paper/event/entity/GolemConstructEvent.java b/paper-api/src/main/java/io/papermc/paper/event/entity/EntityConstructEvent.java similarity index 66% rename from paper-api/src/main/java/io/papermc/paper/event/entity/GolemConstructEvent.java rename to paper-api/src/main/java/io/papermc/paper/event/entity/EntityConstructEvent.java index 5f466ed97c29..8d5ba35015f5 100644 --- a/paper-api/src/main/java/io/papermc/paper/event/entity/GolemConstructEvent.java +++ b/paper-api/src/main/java/io/papermc/paper/event/entity/EntityConstructEvent.java @@ -1,6 +1,7 @@ package io.papermc.paper.event.entity; import org.bukkit.block.Block; +import org.bukkit.entity.Entity; import org.bukkit.entity.Golem; import org.bukkit.event.Cancellable; import org.bukkit.event.HandlerList; @@ -12,13 +13,13 @@ import java.util.List; /** - * Called just before a {@link Golem} spawns due to a pattern of blocks being constructed. + * Called just before a {@link Entity} spawns due to a pattern of blocks being constructed (golems, the wither, etc.) *
- * Note: This event is fired before {@link EntitySpawnEvent}, before the golem is added to the world, - * the success of this event does not guarantee the golem will actually spawn. + * Note: This event is fired before {@link EntitySpawnEvent}, before the entity is added to the world, + * the success of this event does not guarantee the entity will actually spawn. */ @NullMarked -public class GolemConstructEvent extends EntityEvent implements Cancellable { +public class EntityConstructEvent extends EntityEvent implements Cancellable { private static final HandlerList HANDLER_LIST = new HandlerList(); @@ -26,20 +27,16 @@ public class GolemConstructEvent extends EntityEvent implements Cancellable { private boolean cancelled; @ApiStatus.Internal - public GolemConstructEvent(Golem golem, List blocks) { - super(golem); + public EntityConstructEvent(Entity entity, List blocks) { + super(entity); this.blocks = List.copyOf(blocks); } - @Override - public Golem getEntity() { - return (Golem) super.getEntity(); - } - /** - * Get an immutable list of the blocks for this golem - * - * @return the golem blocks + * Get an immutable list of the blocks used in this construction + *
+ * Note: This includes any required air blocks + * @return the blocks */ public @Unmodifiable List getBlocks() { return blocks; diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/CarvedPumpkinBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/CarvedPumpkinBlock.java.patch index ce9a06013c66..ba8fff223a58 100644 --- a/paper-server/patches/sources/net/minecraft/world/level/block/CarvedPumpkinBlock.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/level/block/CarvedPumpkinBlock.java.patch @@ -8,7 +8,7 @@ this.replaceCopperBlockWithChest(level, copperGolemMatch); copperGolem.spawn(this.getWeatherStateFromPattern(copperGolemMatch)); } -@@ -105,9 +_,32 @@ +@@ -105,9 +_,25 @@ } private static void spawnGolemInWorld(final Level level, final BlockPattern.BlockPatternMatch match, final Entity golem, final BlockPos spawnPos) { @@ -17,14 +17,7 @@ golem.snapTo(spawnPos.getX() + 0.5, spawnPos.getY() + 0.05, spawnPos.getZ() + 0.5, 0.0F, 0.0F); - level.addFreshEntity(golem); + // Paper start -+ java.util.List blocks = new java.util.ArrayList<>(); -+ for (int x = 0; x < match.getWidth(); x++) { -+ for (int y = 0; y < match.getHeight(); y++) { -+ BlockInWorld block = match.getBlock(x, y, 0); -+ blocks.add(org.bukkit.craftbukkit.block.CraftBlock.at(level, block.getPos())); -+ } -+ } -+ if (!new io.papermc.paper.event.entity.GolemConstructEvent((org.bukkit.entity.Golem) golem.getBukkitEntity(), blocks).callEvent()) { ++ if (!new io.papermc.paper.event.entity.EntityConstructEvent(golem.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.getMatchingBlocks(level, match)).callEvent()) { + return; + } + org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason; diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/WitherSkullBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/WitherSkullBlock.java.patch index 5aa9ac611b52..c36b192c8cbc 100644 --- a/paper-server/patches/sources/net/minecraft/world/level/block/WitherSkullBlock.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/level/block/WitherSkullBlock.java.patch @@ -17,10 +17,15 @@ BlockPos spawnPos = match.getBlock(1, 2, 0).getPos(); witherBoss.snapTo( spawnPos.getX() + 0.5, -@@ -68,12 +_,18 @@ +@@ -68,12 +_,23 @@ ); witherBoss.yBodyRot = match.getForwards().getAxis() == Direction.Axis.X ? 0.0F : 90.0F; witherBoss.makeInvulnerable(); ++ // Paper start ++ if (!new io.papermc.paper.event.entity.EntityConstructEvent(witherBoss.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.getMatchingBlocks(level, match)).callEvent()) { ++ return; ++ } ++ // Paper end + // CraftBukkit start + if (!level.addFreshEntity(witherBoss, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BUILD_WITHER)) { + return; diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java index 5d6116c55072..bf8654445b81 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java @@ -23,6 +23,8 @@ import net.minecraft.world.level.block.RedStoneWireBlock; import net.minecraft.world.level.block.SaplingBlock; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.pattern.BlockInWorld; +import net.minecraft.world.level.block.state.pattern.BlockPattern; import net.minecraft.world.level.redstone.Redstone; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.BlockHitResult; @@ -297,6 +299,17 @@ public static BlockFace notchToBlockFace(@Nullable Direction direction) { }; } + public static List getMatchingBlocks(LevelAccessor level, BlockPattern.BlockPatternMatch match) { + List blocks = new ArrayList<>(); + for (int x = 0; x < match.getWidth(); x++) { + for (int y = 0; y < match.getHeight(); y++) { + BlockInWorld block = match.getBlock(x, y, 0); + blocks.add(at(level, block.getPos())); + } + } + return blocks; + } + @Override public org.bukkit.block.BlockState getState() { return CraftBlockStates.getBlockState(this); From bcb3e6962deb66fa4590086774c2078b590be5f4 Mon Sep 17 00:00:00 2001 From: JustAHuman-xD <65748158+JustAHuman-xD@users.noreply.github.com> Date: Fri, 5 Jun 2026 11:06:41 -0500 Subject: [PATCH 4/5] an not a Co-authored-by: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> --- .../io/papermc/paper/event/entity/EntityConstructEvent.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paper-api/src/main/java/io/papermc/paper/event/entity/EntityConstructEvent.java b/paper-api/src/main/java/io/papermc/paper/event/entity/EntityConstructEvent.java index 8d5ba35015f5..638a144bb0c2 100644 --- a/paper-api/src/main/java/io/papermc/paper/event/entity/EntityConstructEvent.java +++ b/paper-api/src/main/java/io/papermc/paper/event/entity/EntityConstructEvent.java @@ -13,7 +13,7 @@ import java.util.List; /** - * Called just before a {@link Entity} spawns due to a pattern of blocks being constructed (golems, the wither, etc.) + * Called just before an {@link Entity} spawns due to a pattern of blocks being constructed (golems, the wither, etc.) *
* Note: This event is fired before {@link EntitySpawnEvent}, before the entity is added to the world, * the success of this event does not guarantee the entity will actually spawn. From 2c6329d41c0c2233f96ed357788f0befeeb0d047 Mon Sep 17 00:00:00 2001 From: JustAHuman-xD Date: Fri, 5 Jun 2026 20:59:09 -0500 Subject: [PATCH 5/5] pr comments --- .../io/papermc/paper/event/entity/EntityConstructEvent.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/paper-api/src/main/java/io/papermc/paper/event/entity/EntityConstructEvent.java b/paper-api/src/main/java/io/papermc/paper/event/entity/EntityConstructEvent.java index 638a144bb0c2..cab7cc9ddca1 100644 --- a/paper-api/src/main/java/io/papermc/paper/event/entity/EntityConstructEvent.java +++ b/paper-api/src/main/java/io/papermc/paper/event/entity/EntityConstructEvent.java @@ -33,9 +33,9 @@ public EntityConstructEvent(Entity entity, List blocks) { } /** - * Get an immutable list of the blocks used in this construction - *
- * Note: This includes any required air blocks + * Get an immutable list of the blocks required for this construction, including + * any required air blocks. + * * @return the blocks */ public @Unmodifiable List getBlocks() {