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.
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

View File

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

View File

@ -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;
}
}

View File

@ -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<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");
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);
}
}
}

View File

@ -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(

View File

@ -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,