mirror of
https://github.com/Minestom/Minestom.git
synced 2025-01-02 14:38:26 +01:00
hollow-cube/block-update-changes
This commit is contained in:
parent
55a1349049
commit
74ca1041f3
@ -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
|
||||
|
@ -135,7 +135,7 @@ public class PlayerInit {
|
||||
|
||||
})
|
||||
.addListener(PlayerBlockPlaceEvent.class, event -> {
|
||||
event.setDoBlockUpdates(false);
|
||||
// event.setDoBlockUpdates(false);
|
||||
});
|
||||
|
||||
static {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user