hollow-cube/prevent-block-updates

(cherry picked from commit 55a1349049)
This commit is contained in:
mworzala 2023-07-16 07:48:05 -04:00 committed by Matt Worzala
parent 7a374a5079
commit bb6a93da89
8 changed files with 137 additions and 19 deletions

View File

@ -6,6 +6,7 @@ import net.kyori.adventure.text.format.Style;
import net.kyori.adventure.text.format.TextColor; import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.format.TextDecoration; import net.kyori.adventure.text.format.TextDecoration;
import net.minestom.demo.block.TestBlockHandler; import net.minestom.demo.block.TestBlockHandler;
import net.minestom.demo.block.placement.DripstonePlacementRule;
import net.minestom.demo.commands.*; import net.minestom.demo.commands.*;
import net.minestom.server.MinecraftServer; import net.minestom.server.MinecraftServer;
import net.minestom.server.command.CommandManager; import net.minestom.server.command.CommandManager;
@ -28,6 +29,7 @@ public class Main {
MinecraftServer minecraftServer = MinecraftServer.init(); MinecraftServer minecraftServer = MinecraftServer.init();
BlockManager blockManager = MinecraftServer.getBlockManager(); BlockManager blockManager = MinecraftServer.getBlockManager();
blockManager.registerBlockPlacementRule(new DripstonePlacementRule());
blockManager.registerHandler(TestBlockHandler.INSTANCE.getNamespaceId(), () -> TestBlockHandler.INSTANCE); blockManager.registerHandler(TestBlockHandler.INSTANCE.getNamespaceId(), () -> TestBlockHandler.INSTANCE);
CommandManager commandManager = MinecraftServer.getCommandManager(); CommandManager commandManager = MinecraftServer.getCommandManager();

View File

@ -133,6 +133,9 @@ public class PlayerInit {
event.getInstance().setBlock(event.getPosition(), block); event.getInstance().setBlock(event.getPosition(), block);
})
.addListener(PlayerBlockPlaceEvent.class, event -> {
event.setDoBlockUpdates(false);
}); });
static { static {

View File

@ -0,0 +1,62 @@
package net.minestom.demo.block.placement;
import net.minestom.server.instance.block.Block;
import net.minestom.server.instance.block.rule.BlockPlacementRule;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
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";
public DripstonePlacementRule() {
super(Block.POINTED_DRIPSTONE);
}
@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) {
case TOP -> "up";
case BOTTOM -> "down";
default -> y < 0.5 ? "up" : "down";
});
}
@Override
public @NotNull Block blockUpdate(@NotNull UpdateState updateState) {
return updateState.currentBlock()
.withProperty(PROP_THICKNESS, getThickness(updateState));
}
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);
// If there is no dripstone above, it is always a tip
if (aboveBlock.id() != Block.POINTED_DRIPSTONE.id())
return "tip";
// If there is an opposite facing dripstone above, it is always a merged tip
if ((direction ? "down" : "up").equals(aboveBlock.getProperty(PROP_VERTICAL_DIRECTION)))
return "tip_merge";
// If the dripstone above this is a tip, it is a frustum
var aboveThickness = aboveBlock.getProperty(PROP_THICKNESS);
if ("tip".equals(aboveThickness) || "tip_merge".equals(aboveThickness))
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);
// If there is no dripstone below, it is always a base
if (belowBlock.id() != Block.POINTED_DRIPSTONE.id())
return "base";
// Otherwise it is a middle
return "middle";
}
}

View File

@ -21,6 +21,7 @@ public class PlayerBlockPlaceEvent implements PlayerInstanceEvent, BlockEvent, C
private final Player.Hand hand; private final Player.Hand hand;
private boolean consumeBlock; private boolean consumeBlock;
private boolean doBlockUpdates;
private boolean cancelled; private boolean cancelled;
@ -33,6 +34,7 @@ public class PlayerBlockPlaceEvent implements PlayerInstanceEvent, BlockEvent, C
this.blockPosition = blockPosition; this.blockPosition = blockPosition;
this.hand = hand; this.hand = hand;
this.consumeBlock = true; this.consumeBlock = true;
this.doBlockUpdates = true;
} }
/** /**
@ -94,6 +96,22 @@ public class PlayerBlockPlaceEvent implements PlayerInstanceEvent, BlockEvent, C
return consumeBlock; return consumeBlock;
} }
/**
* Should the place trigger updates (on self and neighbors)
* @param doBlockUpdates true if this placement should do block updates
*/
public void setDoBlockUpdates(boolean doBlockUpdates) {
this.doBlockUpdates = doBlockUpdates;
}
/**
* Should the place trigger updates (on self and neighbors)
* @return true if this placement should do block updates
*/
public boolean shouldDoBlockUpdates() {
return doBlockUpdates;
}
@Override @Override
public boolean isCancelled() { public boolean isCancelled() {
return cancelled; return cancelled;

View File

@ -153,8 +153,24 @@ public abstract class Instance implements Block.Getter, Block.Setter,
this.scheduler.scheduleNextTick(() -> callback.accept(this)); this.scheduler.scheduleNextTick(() -> callback.accept(this));
} }
@Override
public void setBlock(int x, int y, int z, @NotNull Block block) {
setBlock(x, y, z, block, true);
}
public void setBlock(@NotNull Point blockPosition, @NotNull Block block, boolean doBlockUpdates) {
setBlock(blockPosition.blockX(), blockPosition.blockY(), blockPosition.blockZ(), block, doBlockUpdates);
}
public abstract void setBlock(int x, int y, int z, @NotNull Block block, boolean doBlockUpdates);
@ApiStatus.Internal @ApiStatus.Internal
public abstract boolean placeBlock(@NotNull BlockHandler.Placement placement); public boolean placeBlock(@NotNull BlockHandler.Placement placement) {
return placeBlock(placement, true);
}
@ApiStatus.Internal
public abstract boolean placeBlock(@NotNull BlockHandler.Placement placement, boolean doBlockUpdates);
/** /**
* Does call {@link net.minestom.server.event.player.PlayerBlockBreakEvent} * Does call {@link net.minestom.server.event.player.PlayerBlockBreakEvent}
@ -165,7 +181,21 @@ public abstract class Instance implements Block.Getter, Block.Setter,
* @return true if the block has been broken, false if it has been cancelled * @return true if the block has been broken, false if it has been cancelled
*/ */
@ApiStatus.Internal @ApiStatus.Internal
public abstract boolean breakBlock(@NotNull Player player, @NotNull Point blockPosition, @NotNull BlockFace blockFace); public boolean breakBlock(@NotNull Player player, @NotNull Point blockPosition, @NotNull BlockFace blockFace) {
return breakBlock(player, blockPosition, blockFace, true);
}
/**
* Does call {@link net.minestom.server.event.player.PlayerBlockBreakEvent}
* and send particle packets
*
* @param player the {@link Player} who break the block
* @param blockPosition the position of the broken block
* @param doBlockUpdates true to do block updates, false otherwise
* @return true if the block has been broken, false if it has been cancelled
*/
@ApiStatus.Internal
public abstract boolean breakBlock(@NotNull Player player, @NotNull Point blockPosition, @NotNull BlockFace blockFace, boolean doBlockUpdates);
/** /**
* Forces the generation of a {@link Chunk}, even if no file and {@link ChunkGenerator} are defined. * Forces the generation of a {@link Chunk}, even if no file and {@link ChunkGenerator} are defined.

View File

@ -100,14 +100,14 @@ public class InstanceContainer extends Instance {
} }
@Override @Override
public void setBlock(int x, int y, int z, @NotNull Block block) { public void setBlock(int x, int y, int z, @NotNull Block block, boolean doBlockUpdates) {
Chunk chunk = getChunkAt(x, z); Chunk chunk = getChunkAt(x, z);
if (chunk == null) { if (chunk == null) {
Check.stateCondition(!hasEnabledAutoChunkLoad(), Check.stateCondition(!hasEnabledAutoChunkLoad(),
"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); if (isLoaded(chunk)) UNSAFE_setBlock(chunk, x, y, z, block, null, null, doBlockUpdates);
} }
/** /**
@ -122,7 +122,8 @@ public class InstanceContainer extends Instance {
* @param block the block to place * @param block the block to place
*/ */
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) {
if (chunk.isReadOnly()) return; if (chunk.isReadOnly()) return;
synchronized (chunk) { synchronized (chunk) {
// Refresh the last block change time // Refresh the last block change time
@ -137,7 +138,7 @@ 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) { if (blockPlacementRule != null && doBlockUpdates) {
block = blockPlacementRule.blockUpdate(new BlockPlacementRule.UpdateState(this, blockPosition, block)); block = blockPlacementRule.blockUpdate(new BlockPlacementRule.UpdateState(this, blockPosition, block));
} }
@ -145,7 +146,9 @@ public class InstanceContainer extends Instance {
chunk.setBlock(x, y, z, block, placement, destroy); chunk.setBlock(x, y, z, block, placement, destroy);
// Refresh neighbors since a new block has been placed // Refresh neighbors since a new block has been placed
executeNeighboursBlockPlacementRule(blockPosition); if (doBlockUpdates) {
executeNeighboursBlockPlacementRule(blockPosition);
}
// Refresh player chunk block // Refresh player chunk block
{ {
@ -160,17 +163,17 @@ public class InstanceContainer extends Instance {
} }
@Override @Override
public boolean placeBlock(@NotNull BlockHandler.Placement placement) { public boolean placeBlock(@NotNull BlockHandler.Placement placement, boolean doBlockUpdates) {
final Point blockPosition = placement.getBlockPosition(); final Point blockPosition = placement.getBlockPosition();
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); placement.getBlock(), placement, null, doBlockUpdates);
return true; return true;
} }
@Override @Override
public boolean breakBlock(@NotNull Player player, @NotNull Point blockPosition, @NotNull BlockFace blockFace) { public boolean breakBlock(@NotNull Player player, @NotNull Point blockPosition, @NotNull BlockFace blockFace, boolean doBlockUpdates) {
final Chunk chunk = getChunkAt(blockPosition); final Chunk chunk = getChunkAt(blockPosition);
Check.notNull(chunk, "You cannot break blocks in a null chunk!"); Check.notNull(chunk, "You cannot break blocks in a null chunk!");
if (chunk.isReadOnly()) return false; if (chunk.isReadOnly()) return false;
@ -192,7 +195,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)); new BlockHandler.PlayerDestroy(block, this, blockPosition, player), doBlockUpdates);
// 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),

View File

@ -27,18 +27,18 @@ public class SharedInstance extends Instance {
} }
@Override @Override
public void setBlock(int x, int y, int z, @NotNull Block block) { public void setBlock(int x, int y, int z, @NotNull Block block, boolean doBlockUpdates) {
this.instanceContainer.setBlock(x, y, z, block); this.instanceContainer.setBlock(x, y, z, block, doBlockUpdates);
} }
@Override @Override
public boolean placeBlock(@NotNull BlockHandler.Placement placement) { public boolean placeBlock(@NotNull BlockHandler.Placement placement, boolean doBlockUpdates) {
return instanceContainer.placeBlock(placement); return instanceContainer.placeBlock(placement, doBlockUpdates);
} }
@Override @Override
public boolean breakBlock(@NotNull Player player, @NotNull Point blockPosition, @NotNull BlockFace blockFace) { public boolean breakBlock(@NotNull Player player, @NotNull Point blockPosition, @NotNull BlockFace blockFace, boolean doBlockUpdates) {
return instanceContainer.breakBlock(player, blockPosition, blockFace); return instanceContainer.breakBlock(player, blockPosition, blockFace, doBlockUpdates);
} }
@Override @Override

View File

@ -153,7 +153,7 @@ 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) { 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,
@ -168,7 +168,7 @@ public class BlockPlacementListener {
// 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,
packet.cursorPositionX(), packet.cursorPositionY(), packet.cursorPositionZ())); packet.cursorPositionX(), packet.cursorPositionY(), packet.cursorPositionZ()), playerBlockPlaceEvent.shouldDoBlockUpdates());
// Block consuming // Block consuming
if (playerBlockPlaceEvent.doesConsumeBlock()) { if (playerBlockPlaceEvent.doesConsumeBlock()) {
// Consume the block in the player's hand // Consume the block in the player's hand