diff --git a/src/main/java/net/minestom/server/instance/Instance.java b/src/main/java/net/minestom/server/instance/Instance.java index 89c64b9a0..07f128ed5 100644 --- a/src/main/java/net/minestom/server/instance/Instance.java +++ b/src/main/java/net/minestom/server/instance/Instance.java @@ -17,6 +17,7 @@ import net.minestom.server.event.EventCallback; import net.minestom.server.event.EventDispatcher; import net.minestom.server.event.EventFilter; import net.minestom.server.event.EventNode; +import net.minestom.server.event.handler.EventHandler; import net.minestom.server.event.instance.AddEntityToInstanceEvent; import net.minestom.server.event.instance.InstanceTickEvent; import net.minestom.server.event.instance.RemoveEntityFromInstanceEvent; @@ -44,8 +45,6 @@ import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import net.minestom.server.event.handler.EventHandler; - import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; @@ -146,6 +145,8 @@ public abstract class Instance implements BlockGetter, BlockSetter, Tickable, Ev this.nextTick.add(callback); } + public abstract boolean placeBlock(@NotNull Player player, @NotNull Block block, @NotNull BlockPosition blockPosition); + /** * Does call {@link net.minestom.server.event.player.PlayerBlockBreakEvent} * and send particle packets diff --git a/src/main/java/net/minestom/server/instance/InstanceContainer.java b/src/main/java/net/minestom/server/instance/InstanceContainer.java index d5aefe60d..07bcc192d 100644 --- a/src/main/java/net/minestom/server/instance/InstanceContainer.java +++ b/src/main/java/net/minestom/server/instance/InstanceContainer.java @@ -106,13 +106,13 @@ public class InstanceContainer extends Instance { public synchronized void setBlock(int x, int y, int z, @NotNull Block block) { final Chunk chunk = getChunkAt(x, z); if (ChunkUtils.isLoaded(chunk)) { - UNSAFE_setBlock(chunk, x, y, z, block); + UNSAFE_setBlock(chunk, x, y, z, block, null); } else { Check.stateCondition(!hasEnabledAutoChunkLoad(), "Tried to set a block to an unloaded chunk with auto chunk load disabled"); final int chunkX = ChunkUtils.getChunkCoordinate(x); final int chunkZ = ChunkUtils.getChunkCoordinate(z); - loadChunk(chunkX, chunkZ, c -> UNSAFE_setBlock(c, x, y, z, block)); + loadChunk(chunkX, chunkZ, c -> UNSAFE_setBlock(c, x, y, z, block, null)); } } @@ -127,20 +127,16 @@ public class InstanceContainer extends Instance { * @param z the block Z * @param block the block to place */ - private void UNSAFE_setBlock(@NotNull Chunk chunk, int x, int y, int z, @NotNull Block block) { - + private void UNSAFE_setBlock(@NotNull Chunk chunk, int x, int y, int z, @NotNull Block block, + @Nullable Player player) { // Cannot place block in a read-only chunk if (chunk.isReadOnly()) { return; } - synchronized (chunk) { - // Refresh the last block change time this.lastBlockChangeTime = System.currentTimeMillis(); - final BlockPosition blockPosition = new BlockPosition(x, y, z); - if (isAlreadyChanged(blockPosition, block)) { // do NOT change the block again. // Avoids StackOverflowExceptions when onDestroy tries to destroy the block itself // This can happen with nether portals which break the entire frame when a portal block is broken @@ -165,85 +161,29 @@ public class InstanceContainer extends Instance { if (previousHandler != null) { // Previous destroy - previousHandler.onDestroy(BlockHandler.Destroy.from(previousBlock, this, blockPosition)); + final var destroy = player != null ? + new BlockHandler.PlayerDestroy(block, this, blockPosition, player) : + BlockHandler.Destroy.from(previousBlock, this, blockPosition); + previousHandler.onDestroy(destroy); } final BlockHandler handler = block.handler(); if (handler != null) { // New placement - handler.onPlace(BlockHandler.Placement.from(block, this, blockPosition)); + final var placement = player != null ? + new BlockHandler.PlayerPlacement(block, this, blockPosition, player) : + BlockHandler.Placement.from(block, this, blockPosition); + handler.onPlace(placement); } } } - private void setAlreadyChanged(@NotNull BlockPosition blockPosition, Block block) { - currentlyChangingBlocks.put(blockPosition, block); - } - - /** - * Has this block already changed since last update? - * Prevents StackOverflow with blocks trying to modify their position in onDestroy or onPlace. - * - * @param blockPosition the block position - * @param block the block - * @return true if the block changed since the last update - */ - private boolean isAlreadyChanged(@NotNull BlockPosition blockPosition, @NotNull Block block) { - final Block changedBlock = currentlyChangingBlocks.get(blockPosition); - if (changedBlock == null) + @Override + public boolean placeBlock(@NotNull Player player, @NotNull Block block, @NotNull BlockPosition blockPosition) { + final Chunk chunk = getChunkAt(blockPosition); + if (!ChunkUtils.isLoaded(chunk)) return false; - return changedBlock.id() == block.id(); - } - - /** - * Calls the {@link BlockPlacementRule} for the specified block state id. - * - * @param block the block to modify - * @param blockPosition the block position - * @return the modified block state id - */ - private Block executeBlockPlacementRule(Block block, @NotNull BlockPosition blockPosition) { - final BlockPlacementRule blockPlacementRule = BLOCK_MANAGER.getBlockPlacementRule(block); - if (blockPlacementRule != null) { - return blockPlacementRule.blockUpdate(this, blockPosition, block); - } - return block; - } - - /** - * Executed when a block is modified, this is used to modify the states of neighbours blocks. - *

- * For example, this can be used for redstone wires which need an understanding of its neighborhoods to take the right shape. - * - * @param blockPosition the position of the modified block - */ - private void executeNeighboursBlockPlacementRule(@NotNull BlockPosition blockPosition) { - for (int offsetX = -1; offsetX < 2; offsetX++) { - for (int offsetY = -1; offsetY < 2; offsetY++) { - for (int offsetZ = -1; offsetZ < 2; offsetZ++) { - if (offsetX == 0 && offsetY == 0 && offsetZ == 0) - continue; - final int neighborX = blockPosition.getX() + offsetX; - final int neighborY = blockPosition.getY() + offsetY; - final int neighborZ = blockPosition.getZ() + offsetZ; - final Chunk chunk = getChunkAt(neighborX, neighborZ); - - // Do not try to get neighbour in an unloaded chunk - if (chunk == null) - continue; - - final Block neighborBlock = chunk.getBlock(neighborX, neighborY, neighborZ); - final BlockPlacementRule neighborBlockPlacementRule = BLOCK_MANAGER.getBlockPlacementRule(neighborBlock); - if (neighborBlockPlacementRule != null) { - final BlockPosition neighborPosition = new BlockPosition(neighborX, neighborY, neighborZ); - final Block newNeighborBlock = neighborBlockPlacementRule.blockUpdate(this, - neighborPosition, neighborBlock); - if (neighborBlock != newNeighborBlock) { - setBlock(neighborPosition, newNeighborBlock); - } - } - } - } - } + UNSAFE_setBlock(chunk, blockPosition.getX(), blockPosition.getY(), blockPosition.getZ(), block, player); + return true; } @Override @@ -700,6 +640,77 @@ public class InstanceContainer extends Instance { } } + private void setAlreadyChanged(@NotNull BlockPosition blockPosition, Block block) { + currentlyChangingBlocks.put(blockPosition, block); + } + + /** + * Has this block already changed since last update? + * Prevents StackOverflow with blocks trying to modify their position in onDestroy or onPlace. + * + * @param blockPosition the block position + * @param block the block + * @return true if the block changed since the last update + */ + private boolean isAlreadyChanged(@NotNull BlockPosition blockPosition, @NotNull Block block) { + final Block changedBlock = currentlyChangingBlocks.get(blockPosition); + if (changedBlock == null) + return false; + return changedBlock.id() == block.id(); + } + + /** + * Calls the {@link BlockPlacementRule} for the specified block state id. + * + * @param block the block to modify + * @param blockPosition the block position + * @return the modified block state id + */ + private Block executeBlockPlacementRule(Block block, @NotNull BlockPosition blockPosition) { + final BlockPlacementRule blockPlacementRule = BLOCK_MANAGER.getBlockPlacementRule(block); + if (blockPlacementRule != null) { + return blockPlacementRule.blockUpdate(this, blockPosition, block); + } + return block; + } + + /** + * Executed when a block is modified, this is used to modify the states of neighbours blocks. + *

+ * For example, this can be used for redstone wires which need an understanding of its neighborhoods to take the right shape. + * + * @param blockPosition the position of the modified block + */ + private void executeNeighboursBlockPlacementRule(@NotNull BlockPosition blockPosition) { + for (int offsetX = -1; offsetX < 2; offsetX++) { + for (int offsetY = -1; offsetY < 2; offsetY++) { + for (int offsetZ = -1; offsetZ < 2; offsetZ++) { + if (offsetX == 0 && offsetY == 0 && offsetZ == 0) + continue; + final int neighborX = blockPosition.getX() + offsetX; + final int neighborY = blockPosition.getY() + offsetY; + final int neighborZ = blockPosition.getZ() + offsetZ; + final Chunk chunk = getChunkAt(neighborX, neighborZ); + + // Do not try to get neighbour in an unloaded chunk + if (chunk == null) + continue; + + final Block neighborBlock = chunk.getBlock(neighborX, neighborY, neighborZ); + final BlockPlacementRule neighborBlockPlacementRule = BLOCK_MANAGER.getBlockPlacementRule(neighborBlock); + if (neighborBlockPlacementRule != null) { + final BlockPosition neighborPosition = new BlockPosition(neighborX, neighborY, neighborZ); + final Block newNeighborBlock = neighborBlockPlacementRule.blockUpdate(this, + neighborPosition, neighborBlock); + if (neighborBlock != newNeighborBlock) { + setBlock(neighborPosition, newNeighborBlock); + } + } + } + } + } + } + private void callChunkLoadEvent(int chunkX, int chunkZ) { InstanceChunkLoadEvent chunkLoadEvent = new InstanceChunkLoadEvent(this, chunkX, chunkZ); EventDispatcher.call(chunkLoadEvent); diff --git a/src/main/java/net/minestom/server/instance/SharedInstance.java b/src/main/java/net/minestom/server/instance/SharedInstance.java index ad5d5cb78..87b268901 100644 --- a/src/main/java/net/minestom/server/instance/SharedInstance.java +++ b/src/main/java/net/minestom/server/instance/SharedInstance.java @@ -30,6 +30,11 @@ public class SharedInstance extends Instance { this.instanceContainer.setBlock(x, y, z, block); } + @Override + public boolean placeBlock(@NotNull Player player, @NotNull Block block, @NotNull BlockPosition blockPosition) { + return instanceContainer.placeBlock(player, block, blockPosition); + } + @Override public boolean breakBlock(@NotNull Player player, @NotNull BlockPosition blockPosition) { return instanceContainer.breakBlock(player, blockPosition); diff --git a/src/main/java/net/minestom/server/instance/block/BlockHandler.java b/src/main/java/net/minestom/server/instance/block/BlockHandler.java index 0062fcb74..663848608 100644 --- a/src/main/java/net/minestom/server/instance/block/BlockHandler.java +++ b/src/main/java/net/minestom/server/instance/block/BlockHandler.java @@ -112,6 +112,39 @@ public interface BlockHandler { } } + final class PlayerPlacement implements Placement { + private final Block block; + private final Instance instance; + private final BlockPosition blockPosition; + private final Player player; + + public PlayerPlacement(Block block, Instance instance, BlockPosition blockPosition, Player player) { + this.block = block; + this.instance = instance; + this.blockPosition = blockPosition; + this.player = player; + } + + @Override + public @NotNull Block block() { + return block; + } + + @Override + public @NotNull Instance instance() { + return instance; + } + + @Override + public @NotNull BlockPosition blockPosition() { + return blockPosition; + } + + public @NotNull Player player() { + return player; + } + } + @ApiStatus.NonExtendable interface Destroy { @NotNull Block block(); @@ -140,6 +173,39 @@ public interface BlockHandler { } } + final class PlayerDestroy implements Destroy { + private final Block block; + private final Instance instance; + private final BlockPosition blockPosition; + private final Player player; + + public PlayerDestroy(Block block, Instance instance, BlockPosition blockPosition, Player player) { + this.block = block; + this.instance = instance; + this.blockPosition = blockPosition; + this.player = player; + } + + @Override + public @NotNull Block block() { + return block; + } + + @Override + public @NotNull Instance instance() { + return instance; + } + + @Override + public @NotNull BlockPosition blockPosition() { + return blockPosition; + } + + public @NotNull Player player() { + return player; + } + } + @ApiStatus.NonExtendable interface Interaction { @NotNull Block block(); diff --git a/src/main/java/net/minestom/server/listener/BlockPlacementListener.java b/src/main/java/net/minestom/server/listener/BlockPlacementListener.java index a77dcb04d..6e80ce26b 100644 --- a/src/main/java/net/minestom/server/listener/BlockPlacementListener.java +++ b/src/main/java/net/minestom/server/listener/BlockPlacementListener.java @@ -142,7 +142,7 @@ public class BlockPlacementListener { final boolean placementRuleCheck = resultBlock != null; if (placementRuleCheck) { // Place the block - instance.setBlock(blockPosition, resultBlock); + instance.placeBlock(player, resultBlock, blockPosition); // Block consuming if (playerBlockPlaceEvent.doesConsumeBlock()) { // Consume the block in the player's hand