From 74ca1041f3ddd9f58f89583a611644fb23ed7d16 Mon Sep 17 00:00:00 2001 From: mworzala Date: Sun, 16 Jul 2023 08:33:09 -0400 Subject: [PATCH] hollow-cube/block-update-changes --- CHANGELOG.md | 6 ++ .../java/net/minestom/demo/PlayerInit.java | 2 +- .../placement/DripstonePlacementRule.java | 42 ++++++---- .../server/instance/InstanceContainer.java | 84 ++++++++++++------- .../block/rule/BlockPlacementRule.java | 21 +++-- .../listener/BlockPlacementListener.java | 24 +++--- 6 files changed, 117 insertions(+), 62 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bd2bddc2..916fd584d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,5 +14,11 @@ Some of these are pending, some deserve PRs, others are just minor tweaks * This is a breaking change because it changes the signature of `Argument#parse`, but most use cases should not be affected. Support has been maintained for the old argument map signature, so only completely custom arguments will be affected. * **breaking** [Placement rule api changes](https://github.com/hollow-cube/minestom-ce/pull/20) +* **breaking** Block update rework + * Block updates are optional, placements in instances can be done without triggering updates (will not call placement rule place or update events) + * Block updates are not always triggered by a block place (only if a neighbor update triggers one back) + * Block updates now only update adjacent blocks, not diagonals. This is inline with vanilla behvaior + * Block placement rules can dictate a max range where updates will be applied. Defaults to 10 to be more compatible with prior behavior. + * Block placement updates receive the block face that triggered the update * Optionally use reworked chunk sending algorithm (`minestom.use-new-chunk-sending` system property) * Add an API to check for swept entity collisions. Used in the new PlayerProjectile class diff --git a/demo/src/main/java/net/minestom/demo/PlayerInit.java b/demo/src/main/java/net/minestom/demo/PlayerInit.java index fd701b11e..29c62db77 100644 --- a/demo/src/main/java/net/minestom/demo/PlayerInit.java +++ b/demo/src/main/java/net/minestom/demo/PlayerInit.java @@ -135,7 +135,7 @@ public class PlayerInit { }) .addListener(PlayerBlockPlaceEvent.class, event -> { - event.setDoBlockUpdates(false); +// event.setDoBlockUpdates(false); }); static { diff --git a/demo/src/main/java/net/minestom/demo/block/placement/DripstonePlacementRule.java b/demo/src/main/java/net/minestom/demo/block/placement/DripstonePlacementRule.java index ed5cbb480..195d07873 100644 --- a/demo/src/main/java/net/minestom/demo/block/placement/DripstonePlacementRule.java +++ b/demo/src/main/java/net/minestom/demo/block/placement/DripstonePlacementRule.java @@ -1,10 +1,16 @@ package net.minestom.demo.block.placement; +import net.minestom.server.coordinate.Point; +import net.minestom.server.coordinate.Vec; import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.BlockFace; import net.minestom.server.instance.block.rule.BlockPlacementRule; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Map; +import java.util.Objects; + public class DripstonePlacementRule extends BlockPlacementRule { private static final String PROP_VERTICAL_DIRECTION = "vertical_direction"; // Tip, frustum, middle(0 or more), base private static final String PROP_THICKNESS = "thickness"; @@ -15,25 +21,29 @@ public class DripstonePlacementRule extends BlockPlacementRule { @Override public @Nullable Block blockPlace(@NotNull PlacementState placementState) { - var blockFace = placementState.blockFace(); - var y = placementState.cursorPosition().y(); - return block.withProperty(PROP_VERTICAL_DIRECTION, switch (blockFace) { + var blockFace = Objects.requireNonNullElse(placementState.blockFace(), BlockFace.TOP); + var direction = switch (blockFace) { case TOP -> "up"; case BOTTOM -> "down"; - default -> y < 0.5 ? "up" : "down"; - }); + default -> Objects.requireNonNullElse(placementState.cursorPosition(), Vec.ZERO).y() < 0.5 ? "up" : "down"; + }; + var thickness = getThickness(placementState.instance(), placementState.placePosition(), direction.equals("up")); + return block.withProperties(Map.of( + PROP_VERTICAL_DIRECTION, direction, + PROP_THICKNESS, thickness + )); } @Override public @NotNull Block blockUpdate(@NotNull UpdateState updateState) { - return updateState.currentBlock() - .withProperty(PROP_THICKNESS, getThickness(updateState)); + var direction = updateState.currentBlock().getProperty(PROP_VERTICAL_DIRECTION).equals("up"); + var newThickness = getThickness(updateState.instance(), updateState.blockPosition(), direction); + return updateState.currentBlock().withProperty(PROP_THICKNESS, newThickness); } - private @NotNull String getThickness(@NotNull UpdateState updateState) { - var direction = updateState.currentBlock().getProperty(PROP_VERTICAL_DIRECTION).equals("up"); - var abovePosition = updateState.blockPosition().add(0, direction ? 1 : -1, 0); - var aboveBlock = updateState.instance().getBlock(abovePosition, Block.Getter.Condition.TYPE); + private @NotNull String getThickness(@NotNull Block.Getter instance, @NotNull Point blockPosition, boolean direction) { + var abovePosition = blockPosition.add(0, direction ? 1 : -1, 0); + var aboveBlock = instance.getBlock(abovePosition, Block.Getter.Condition.TYPE); // If there is no dripstone above, it is always a tip if (aboveBlock.id() != Block.POINTED_DRIPSTONE.id()) @@ -48,8 +58,8 @@ public class DripstonePlacementRule extends BlockPlacementRule { return "frustum"; // At this point we know that there is a dripstone above, and that the dripstone is facing the same direction. - var belowPosition = updateState.blockPosition().add(0, direction ? -1 : 1, 0); - var belowBlock = updateState.instance().getBlock(belowPosition, Block.Getter.Condition.TYPE); + var belowPosition = blockPosition.add(0, direction ? -1 : 1, 0); + var belowBlock = instance.getBlock(belowPosition, Block.Getter.Condition.TYPE); // If there is no dripstone below, it is always a base if (belowBlock.id() != Block.POINTED_DRIPSTONE.id()) @@ -58,5 +68,9 @@ public class DripstonePlacementRule extends BlockPlacementRule { // Otherwise it is a middle return "middle"; } -} + @Override + public int maxUpdateDistance() { + return 2; + } +} diff --git a/src/main/java/net/minestom/server/instance/InstanceContainer.java b/src/main/java/net/minestom/server/instance/InstanceContainer.java index 68e14c6ad..db5f97daf 100644 --- a/src/main/java/net/minestom/server/instance/InstanceContainer.java +++ b/src/main/java/net/minestom/server/instance/InstanceContainer.java @@ -52,6 +52,10 @@ import static net.minestom.server.utils.chunk.ChunkUtils.*; public class InstanceContainer extends Instance { private static final AnvilLoader DEFAULT_LOADER = new AnvilLoader("world"); + private static final BlockFace[] BLOCK_UPDATE_FACES = new BlockFace[]{ + BlockFace.WEST, BlockFace.EAST, BlockFace.NORTH, BlockFace.SOUTH, BlockFace.BOTTOM, BlockFace.TOP + }; + // the shared instances assigned to this instance private final List sharedInstances = new CopyOnWriteArrayList<>(); @@ -107,7 +111,7 @@ public class InstanceContainer extends Instance { "Tried to set a block to an unloaded chunk with auto chunk load disabled"); chunk = loadChunk(getChunkCoordinate(x), getChunkCoordinate(z)).join(); } - if (isLoaded(chunk)) UNSAFE_setBlock(chunk, x, y, z, block, null, null, doBlockUpdates); + if (isLoaded(chunk)) UNSAFE_setBlock(chunk, x, y, z, block, null, null, doBlockUpdates, 0); } /** @@ -123,7 +127,7 @@ public class InstanceContainer extends Instance { */ private synchronized void UNSAFE_setBlock(@NotNull Chunk chunk, int x, int y, int z, @NotNull Block block, @Nullable BlockHandler.Placement placement, @Nullable BlockHandler.Destroy destroy, - boolean doBlockUpdates) { + boolean doBlockUpdates, int updateDistance) { if (chunk.isReadOnly()) return; synchronized (chunk) { // Refresh the last block change time @@ -138,8 +142,26 @@ public class InstanceContainer extends Instance { // Change id based on neighbors final BlockPlacementRule blockPlacementRule = MinecraftServer.getBlockManager().getBlockPlacementRule(block); - if (blockPlacementRule != null && doBlockUpdates) { - block = blockPlacementRule.blockUpdate(new BlockPlacementRule.UpdateState(this, blockPosition, block)); + if (placement != null && blockPlacementRule != null && doBlockUpdates) { + BlockPlacementRule.PlacementState rulePlacement; + if (placement instanceof BlockHandler.PlayerPlacement pp) { + rulePlacement = new BlockPlacementRule.PlacementState( + this, block, pp.getBlockFace(), blockPosition, + new Vec(pp.getCursorX(), pp.getCursorY(), pp.getCursorZ()), + pp.getPlayer().getPosition(), + pp.getPlayer().getItemInHand(pp.getHand()).meta(), + pp.getPlayer().isSneaking() + ); + } else { + rulePlacement = new BlockPlacementRule.PlacementState( + this, block, null, blockPosition, + null, null, null, + false + ); + } + + block = blockPlacementRule.blockPlace(rulePlacement); + if (block == null) block = Block.AIR; } // Set the block @@ -147,7 +169,7 @@ public class InstanceContainer extends Instance { // Refresh neighbors since a new block has been placed if (doBlockUpdates) { - executeNeighboursBlockPlacementRule(blockPosition); + executeNeighboursBlockPlacementRule(blockPosition, updateDistance); } // Refresh player chunk block @@ -168,7 +190,7 @@ public class InstanceContainer extends Instance { final Chunk chunk = getChunkAt(blockPosition); if (!isLoaded(chunk)) return false; UNSAFE_setBlock(chunk, blockPosition.blockX(), blockPosition.blockY(), blockPosition.blockZ(), - placement.getBlock(), placement, null, doBlockUpdates); + placement.getBlock(), placement, null, doBlockUpdates, 0); return true; } @@ -195,7 +217,7 @@ public class InstanceContainer extends Instance { // Break or change the broken block based on event result final Block resultBlock = blockBreakEvent.getResultBlock(); UNSAFE_setBlock(chunk, x, y, z, resultBlock, null, - new BlockHandler.PlayerDestroy(block, this, blockPosition, player), doBlockUpdates); + new BlockHandler.PlayerDestroy(block, this, blockPosition, player), doBlockUpdates, 0); // Send the block break effect packet PacketUtils.sendGroupedPacket(chunk.getViewers(), new EffectPacket(2001 /*Block break + block break sound*/, blockPosition, block.stateId(), false), @@ -594,31 +616,33 @@ public class InstanceContainer extends Instance { * * @param blockPosition the position of the modified block */ - private void executeNeighboursBlockPlacementRule(@NotNull Point blockPosition) { + private void executeNeighboursBlockPlacementRule(@NotNull Point blockPosition, int updateDistance) { ChunkCache cache = new ChunkCache(this, null, null); - 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.blockX() + offsetX; - final int neighborY = blockPosition.blockY() + offsetY; - final int neighborZ = blockPosition.blockZ() + offsetZ; - if (neighborY < getDimensionType().getMinY() || neighborY > getDimensionType().getTotalHeight()) - continue; - final Block neighborBlock = cache.getBlock(neighborX, neighborY, neighborZ, Condition.TYPE); - if (neighborBlock == null) - continue; - final BlockPlacementRule neighborBlockPlacementRule = MinecraftServer.getBlockManager().getBlockPlacementRule(neighborBlock); - if (neighborBlockPlacementRule == null) continue; + for (var updateFace : BLOCK_UPDATE_FACES) { + var direction = updateFace.toDirection(); + final int neighborX = blockPosition.blockX() + direction.normalX(); + final int neighborY = blockPosition.blockY() + direction.normalY(); + final int neighborZ = blockPosition.blockZ() + direction.normalZ(); + if (neighborY < getDimensionType().getMinY() || neighborY > getDimensionType().getTotalHeight()) + continue; + final Block neighborBlock = cache.getBlock(neighborX, neighborY, neighborZ, Condition.TYPE); + if (neighborBlock == null) + continue; + final BlockPlacementRule neighborBlockPlacementRule = MinecraftServer.getBlockManager().getBlockPlacementRule(neighborBlock); + if (neighborBlockPlacementRule == null || updateDistance >= neighborBlockPlacementRule.maxUpdateDistance()) continue; - final Vec neighborPosition = new Vec(neighborX, neighborY, neighborZ); - final Block newNeighborBlock = neighborBlockPlacementRule.blockUpdate(new BlockPlacementRule.UpdateState(this, - neighborPosition, neighborBlock)); - if (neighborBlock != newNeighborBlock) { - setBlock(neighborPosition, newNeighborBlock); - } - } + final Vec neighborPosition = new Vec(neighborX, neighborY, neighborZ); + final Block newNeighborBlock = neighborBlockPlacementRule.blockUpdate(new BlockPlacementRule.UpdateState( + this, + neighborPosition, + neighborBlock, + updateFace.getOppositeFace() + )); + if (neighborBlock != newNeighborBlock) { + final Chunk chunk = getChunkAt(neighborPosition); + if (!isLoaded(chunk)) continue; + UNSAFE_setBlock(chunk, neighborPosition.blockX(), neighborPosition.blockY(), neighborPosition.blockZ(), newNeighborBlock, + null, null, true, updateDistance + 1); } } } diff --git a/src/main/java/net/minestom/server/instance/block/rule/BlockPlacementRule.java b/src/main/java/net/minestom/server/instance/block/rule/BlockPlacementRule.java index 43f4c8db0..68d027514 100644 --- a/src/main/java/net/minestom/server/instance/block/rule/BlockPlacementRule.java +++ b/src/main/java/net/minestom/server/instance/block/rule/BlockPlacementRule.java @@ -10,6 +10,8 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public abstract class BlockPlacementRule { + public static final int DEFAULT_UPDATE_RANGE = 10; + protected final Block block; protected BlockPlacementRule(@NotNull Block block) { @@ -44,21 +46,30 @@ public abstract class BlockPlacementRule { return block; } + /** + * The max distance where a block update can be triggered. It is not based on block, so if the value is 3 and a completely + * different block updates 3 blocks away it could still trigger an update. + */ + public int maxUpdateDistance() { + return DEFAULT_UPDATE_RANGE; + } + public record PlacementState( @NotNull Block.Getter instance, @NotNull Block block, - @NotNull BlockFace blockFace, + @Nullable BlockFace blockFace, @NotNull Point placePosition, - @NotNull Point cursorPosition, - @NotNull Pos playerPosition, - @NotNull ItemMeta usedItemMeta, + @Nullable Point cursorPosition, + @Nullable Pos playerPosition, + @Nullable ItemMeta usedItemMeta, boolean isPlayerShifting ) { } public record UpdateState(@NotNull Block.Getter instance, @NotNull Point blockPosition, - @NotNull Block currentBlock) { + @NotNull Block currentBlock, + @NotNull BlockFace fromFace) { } public record Replacement( diff --git a/src/main/java/net/minestom/server/listener/BlockPlacementListener.java b/src/main/java/net/minestom/server/listener/BlockPlacementListener.java index 28e895ea3..a3869c6f5 100644 --- a/src/main/java/net/minestom/server/listener/BlockPlacementListener.java +++ b/src/main/java/net/minestom/server/listener/BlockPlacementListener.java @@ -152,19 +152,19 @@ public class BlockPlacementListener { // BlockPlacementRule check Block resultBlock = playerBlockPlaceEvent.getBlock(); - final BlockPlacementRule blockPlacementRule = BLOCK_MANAGER.getBlockPlacementRule(resultBlock); - if (blockPlacementRule != null && playerBlockPlaceEvent.shouldDoBlockUpdates()) { +// final BlockPlacementRule blockPlacementRule = BLOCK_MANAGER.getBlockPlacementRule(resultBlock); +// if (blockPlacementRule != null && playerBlockPlaceEvent.shouldDoBlockUpdates()) { // Get id from block placement rule instead of the event - resultBlock = blockPlacementRule.blockPlace(new BlockPlacementRule.PlacementState( - instance, resultBlock, blockFace, - placementPosition, cursorPosition, - player.getPosition(), usedItem.meta(), player.isSneaking()) - ); - } - if (resultBlock == null) { - refresh(player, chunk); - return; - } +// resultBlock = blockPlacementRule.blockPlace(new BlockPlacementRule.PlacementState( +// instance, resultBlock, blockFace, +// placementPosition, cursorPosition, +// player.getPosition(), usedItem.meta(), player.isSneaking()) +// ); +// } +// if (resultBlock == null) { +// refresh(player, chunk); +// return; +// } // Place the block player.sendPacket(new AcknowledgeBlockChangePacket(packet.sequence())); instance.placeBlock(new BlockHandler.PlayerPlacement(resultBlock, instance, placementPosition, player, hand, blockFace,