hollow-cube/block-update-changes

This commit is contained in:
mworzala 2023-07-16 08:33:09 -04:00
parent 55a1349049
commit 74ca1041f3
No known key found for this signature in database
GPG Key ID: B148F922E64797C7
6 changed files with 117 additions and 62 deletions

View File

@ -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. * 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. 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** [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) * 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 * Add an API to check for swept entity collisions. Used in the new PlayerProjectile class

View File

@ -135,7 +135,7 @@ public class PlayerInit {
}) })
.addListener(PlayerBlockPlaceEvent.class, event -> { .addListener(PlayerBlockPlaceEvent.class, event -> {
event.setDoBlockUpdates(false); // event.setDoBlockUpdates(false);
}); });
static { static {

View File

@ -1,10 +1,16 @@
package net.minestom.demo.block.placement; 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.Block;
import net.minestom.server.instance.block.BlockFace;
import net.minestom.server.instance.block.rule.BlockPlacementRule; import net.minestom.server.instance.block.rule.BlockPlacementRule;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.Map;
import java.util.Objects;
public class DripstonePlacementRule extends BlockPlacementRule { 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_VERTICAL_DIRECTION = "vertical_direction"; // Tip, frustum, middle(0 or more), base
private static final String PROP_THICKNESS = "thickness"; private static final String PROP_THICKNESS = "thickness";
@ -15,25 +21,29 @@ public class DripstonePlacementRule extends BlockPlacementRule {
@Override @Override
public @Nullable Block blockPlace(@NotNull PlacementState placementState) { public @Nullable Block blockPlace(@NotNull PlacementState placementState) {
var blockFace = placementState.blockFace(); var blockFace = Objects.requireNonNullElse(placementState.blockFace(), BlockFace.TOP);
var y = placementState.cursorPosition().y(); var direction = switch (blockFace) {
return block.withProperty(PROP_VERTICAL_DIRECTION, switch (blockFace) {
case TOP -> "up"; case TOP -> "up";
case BOTTOM -> "down"; 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 @Override
public @NotNull Block blockUpdate(@NotNull UpdateState updateState) { public @NotNull Block blockUpdate(@NotNull UpdateState updateState) {
return updateState.currentBlock() var direction = updateState.currentBlock().getProperty(PROP_VERTICAL_DIRECTION).equals("up");
.withProperty(PROP_THICKNESS, getThickness(updateState)); var newThickness = getThickness(updateState.instance(), updateState.blockPosition(), direction);
return updateState.currentBlock().withProperty(PROP_THICKNESS, newThickness);
} }
private @NotNull String getThickness(@NotNull UpdateState updateState) { private @NotNull String getThickness(@NotNull Block.Getter instance, @NotNull Point blockPosition, boolean direction) {
var direction = updateState.currentBlock().getProperty(PROP_VERTICAL_DIRECTION).equals("up"); var abovePosition = blockPosition.add(0, direction ? 1 : -1, 0);
var abovePosition = updateState.blockPosition().add(0, direction ? 1 : -1, 0); var aboveBlock = instance.getBlock(abovePosition, Block.Getter.Condition.TYPE);
var aboveBlock = updateState.instance().getBlock(abovePosition, Block.Getter.Condition.TYPE);
// If there is no dripstone above, it is always a tip // If there is no dripstone above, it is always a tip
if (aboveBlock.id() != Block.POINTED_DRIPSTONE.id()) if (aboveBlock.id() != Block.POINTED_DRIPSTONE.id())
@ -48,8 +58,8 @@ public class DripstonePlacementRule extends BlockPlacementRule {
return "frustum"; return "frustum";
// At this point we know that there is a dripstone above, and that the dripstone is facing the same direction. // 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 belowPosition = blockPosition.add(0, direction ? -1 : 1, 0);
var belowBlock = updateState.instance().getBlock(belowPosition, Block.Getter.Condition.TYPE); var belowBlock = instance.getBlock(belowPosition, Block.Getter.Condition.TYPE);
// If there is no dripstone below, it is always a base // If there is no dripstone below, it is always a base
if (belowBlock.id() != Block.POINTED_DRIPSTONE.id()) if (belowBlock.id() != Block.POINTED_DRIPSTONE.id())
@ -58,5 +68,9 @@ public class DripstonePlacementRule extends BlockPlacementRule {
// Otherwise it is a middle // Otherwise it is a middle
return "middle"; return "middle";
} }
}
@Override
public int maxUpdateDistance() {
return 2;
}
}

View File

@ -52,6 +52,10 @@ import static net.minestom.server.utils.chunk.ChunkUtils.*;
public class InstanceContainer extends Instance { public class InstanceContainer extends Instance {
private static final AnvilLoader DEFAULT_LOADER = new AnvilLoader("world"); 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 // the shared instances assigned to this instance
private final List<SharedInstance> sharedInstances = new CopyOnWriteArrayList<>(); private final List<SharedInstance> 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"); "Tried to set a block to an unloaded chunk with auto chunk load disabled");
chunk = loadChunk(getChunkCoordinate(x), getChunkCoordinate(z)).join(); 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, 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, @Nullable BlockHandler.Placement placement, @Nullable BlockHandler.Destroy destroy,
boolean doBlockUpdates) { boolean doBlockUpdates, int updateDistance) {
if (chunk.isReadOnly()) return; if (chunk.isReadOnly()) return;
synchronized (chunk) { synchronized (chunk) {
// Refresh the last block change time // Refresh the last block change time
@ -138,8 +142,26 @@ public class InstanceContainer extends Instance {
// Change id based on neighbors // Change id based on neighbors
final BlockPlacementRule blockPlacementRule = MinecraftServer.getBlockManager().getBlockPlacementRule(block); final BlockPlacementRule blockPlacementRule = MinecraftServer.getBlockManager().getBlockPlacementRule(block);
if (blockPlacementRule != null && doBlockUpdates) { if (placement != null && blockPlacementRule != null && doBlockUpdates) {
block = blockPlacementRule.blockUpdate(new BlockPlacementRule.UpdateState(this, blockPosition, block)); 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 // Set the block
@ -147,7 +169,7 @@ public class InstanceContainer extends Instance {
// Refresh neighbors since a new block has been placed // Refresh neighbors since a new block has been placed
if (doBlockUpdates) { if (doBlockUpdates) {
executeNeighboursBlockPlacementRule(blockPosition); executeNeighboursBlockPlacementRule(blockPosition, updateDistance);
} }
// Refresh player chunk block // Refresh player chunk block
@ -168,7 +190,7 @@ public class InstanceContainer extends Instance {
final Chunk chunk = getChunkAt(blockPosition); final Chunk chunk = getChunkAt(blockPosition);
if (!isLoaded(chunk)) return false; if (!isLoaded(chunk)) return false;
UNSAFE_setBlock(chunk, blockPosition.blockX(), blockPosition.blockY(), blockPosition.blockZ(), UNSAFE_setBlock(chunk, blockPosition.blockX(), blockPosition.blockY(), blockPosition.blockZ(),
placement.getBlock(), placement, null, doBlockUpdates); placement.getBlock(), placement, null, doBlockUpdates, 0);
return true; return true;
} }
@ -195,7 +217,7 @@ public class InstanceContainer extends Instance {
// Break or change the broken block based on event result // Break or change the broken block based on event result
final Block resultBlock = blockBreakEvent.getResultBlock(); final Block resultBlock = blockBreakEvent.getResultBlock();
UNSAFE_setBlock(chunk, x, y, z, resultBlock, null, 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 // Send the block break effect packet
PacketUtils.sendGroupedPacket(chunk.getViewers(), PacketUtils.sendGroupedPacket(chunk.getViewers(),
new EffectPacket(2001 /*Block break + block break sound*/, blockPosition, block.stateId(), false), 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 * @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); ChunkCache cache = new ChunkCache(this, null, null);
for (int offsetX = -1; offsetX < 2; offsetX++) { for (var updateFace : BLOCK_UPDATE_FACES) {
for (int offsetY = -1; offsetY < 2; offsetY++) { var direction = updateFace.toDirection();
for (int offsetZ = -1; offsetZ < 2; offsetZ++) { final int neighborX = blockPosition.blockX() + direction.normalX();
if (offsetX == 0 && offsetY == 0 && offsetZ == 0) final int neighborY = blockPosition.blockY() + direction.normalY();
continue; final int neighborZ = blockPosition.blockZ() + direction.normalZ();
final int neighborX = blockPosition.blockX() + offsetX; if (neighborY < getDimensionType().getMinY() || neighborY > getDimensionType().getTotalHeight())
final int neighborY = blockPosition.blockY() + offsetY; continue;
final int neighborZ = blockPosition.blockZ() + offsetZ; final Block neighborBlock = cache.getBlock(neighborX, neighborY, neighborZ, Condition.TYPE);
if (neighborY < getDimensionType().getMinY() || neighborY > getDimensionType().getTotalHeight()) if (neighborBlock == null)
continue; continue;
final Block neighborBlock = cache.getBlock(neighborX, neighborY, neighborZ, Condition.TYPE); final BlockPlacementRule neighborBlockPlacementRule = MinecraftServer.getBlockManager().getBlockPlacementRule(neighborBlock);
if (neighborBlock == null) if (neighborBlockPlacementRule == null || updateDistance >= neighborBlockPlacementRule.maxUpdateDistance()) continue;
continue;
final BlockPlacementRule neighborBlockPlacementRule = MinecraftServer.getBlockManager().getBlockPlacementRule(neighborBlock);
if (neighborBlockPlacementRule == null) continue;
final Vec neighborPosition = new Vec(neighborX, neighborY, neighborZ); final Vec neighborPosition = new Vec(neighborX, neighborY, neighborZ);
final Block newNeighborBlock = neighborBlockPlacementRule.blockUpdate(new BlockPlacementRule.UpdateState(this, final Block newNeighborBlock = neighborBlockPlacementRule.blockUpdate(new BlockPlacementRule.UpdateState(
neighborPosition, neighborBlock)); this,
if (neighborBlock != newNeighborBlock) { neighborPosition,
setBlock(neighborPosition, newNeighborBlock); 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);
} }
} }
} }

View File

@ -10,6 +10,8 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
public abstract class BlockPlacementRule { public abstract class BlockPlacementRule {
public static final int DEFAULT_UPDATE_RANGE = 10;
protected final Block block; protected final Block block;
protected BlockPlacementRule(@NotNull Block block) { protected BlockPlacementRule(@NotNull Block block) {
@ -44,21 +46,30 @@ public abstract class BlockPlacementRule {
return block; 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( public record PlacementState(
@NotNull Block.Getter instance, @NotNull Block.Getter instance,
@NotNull Block block, @NotNull Block block,
@NotNull BlockFace blockFace, @Nullable BlockFace blockFace,
@NotNull Point placePosition, @NotNull Point placePosition,
@NotNull Point cursorPosition, @Nullable Point cursorPosition,
@NotNull Pos playerPosition, @Nullable Pos playerPosition,
@NotNull ItemMeta usedItemMeta, @Nullable ItemMeta usedItemMeta,
boolean isPlayerShifting boolean isPlayerShifting
) { ) {
} }
public record UpdateState(@NotNull Block.Getter instance, public record UpdateState(@NotNull Block.Getter instance,
@NotNull Point blockPosition, @NotNull Point blockPosition,
@NotNull Block currentBlock) { @NotNull Block currentBlock,
@NotNull BlockFace fromFace) {
} }
public record Replacement( public record Replacement(

View File

@ -152,19 +152,19 @@ public class BlockPlacementListener {
// BlockPlacementRule check // BlockPlacementRule check
Block resultBlock = playerBlockPlaceEvent.getBlock(); Block resultBlock = playerBlockPlaceEvent.getBlock();
final BlockPlacementRule blockPlacementRule = BLOCK_MANAGER.getBlockPlacementRule(resultBlock); // final BlockPlacementRule blockPlacementRule = BLOCK_MANAGER.getBlockPlacementRule(resultBlock);
if (blockPlacementRule != null && playerBlockPlaceEvent.shouldDoBlockUpdates()) { // if (blockPlacementRule != null && playerBlockPlaceEvent.shouldDoBlockUpdates()) {
// Get id from block placement rule instead of the event // Get id from block placement rule instead of the event
resultBlock = blockPlacementRule.blockPlace(new BlockPlacementRule.PlacementState( // resultBlock = blockPlacementRule.blockPlace(new BlockPlacementRule.PlacementState(
instance, resultBlock, blockFace, // instance, resultBlock, blockFace,
placementPosition, cursorPosition, // placementPosition, cursorPosition,
player.getPosition(), usedItem.meta(), player.isSneaking()) // player.getPosition(), usedItem.meta(), player.isSneaking())
); // );
} // }
if (resultBlock == null) { // if (resultBlock == null) {
refresh(player, chunk); // refresh(player, chunk);
return; // return;
} // }
// Place the block // Place the block
player.sendPacket(new AcknowledgeBlockChangePacket(packet.sequence())); player.sendPacket(new AcknowledgeBlockChangePacket(packet.sequence()));
instance.placeBlock(new BlockHandler.PlayerPlacement(resultBlock, instance, placementPosition, player, hand, blockFace, instance.placeBlock(new BlockHandler.PlayerPlacement(resultBlock, instance, placementPosition, player, hand, blockFace,