Add Placement/Destroy implementation for player

This commit is contained in:
TheMode 2021-06-22 23:51:01 +02:00
parent 22ba15f390
commit 2b89fa1527
5 changed files with 164 additions and 81 deletions

View File

@ -17,6 +17,7 @@ import net.minestom.server.event.EventCallback;
import net.minestom.server.event.EventDispatcher;
import net.minestom.server.event.EventFilter;
import net.minestom.server.event.EventNode;
import net.minestom.server.event.handler.EventHandler;
import net.minestom.server.event.instance.AddEntityToInstanceEvent;
import net.minestom.server.event.instance.InstanceTickEvent;
import net.minestom.server.event.instance.RemoveEntityFromInstanceEvent;
@ -44,8 +45,6 @@ import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import net.minestom.server.event.handler.EventHandler;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
@ -146,6 +145,8 @@ public abstract class Instance implements BlockGetter, BlockSetter, Tickable, Ev
this.nextTick.add(callback);
}
public abstract boolean placeBlock(@NotNull Player player, @NotNull Block block, @NotNull BlockPosition blockPosition);
/**
* Does call {@link net.minestom.server.event.player.PlayerBlockBreakEvent}
* and send particle packets

View File

@ -106,13 +106,13 @@ public class InstanceContainer extends Instance {
public synchronized void setBlock(int x, int y, int z, @NotNull Block block) {
final Chunk chunk = getChunkAt(x, z);
if (ChunkUtils.isLoaded(chunk)) {
UNSAFE_setBlock(chunk, x, y, z, block);
UNSAFE_setBlock(chunk, x, y, z, block, null);
} else {
Check.stateCondition(!hasEnabledAutoChunkLoad(),
"Tried to set a block to an unloaded chunk with auto chunk load disabled");
final int chunkX = ChunkUtils.getChunkCoordinate(x);
final int chunkZ = ChunkUtils.getChunkCoordinate(z);
loadChunk(chunkX, chunkZ, c -> UNSAFE_setBlock(c, x, y, z, block));
loadChunk(chunkX, chunkZ, c -> UNSAFE_setBlock(c, x, y, z, block, null));
}
}
@ -127,20 +127,16 @@ public class InstanceContainer extends Instance {
* @param z the block Z
* @param block the block to place
*/
private void UNSAFE_setBlock(@NotNull Chunk chunk, int x, int y, int z, @NotNull Block block) {
private void UNSAFE_setBlock(@NotNull Chunk chunk, int x, int y, int z, @NotNull Block block,
@Nullable Player player) {
// Cannot place block in a read-only chunk
if (chunk.isReadOnly()) {
return;
}
synchronized (chunk) {
// Refresh the last block change time
this.lastBlockChangeTime = System.currentTimeMillis();
final BlockPosition blockPosition = new BlockPosition(x, y, z);
if (isAlreadyChanged(blockPosition, block)) { // do NOT change the block again.
// Avoids StackOverflowExceptions when onDestroy tries to destroy the block itself
// This can happen with nether portals which break the entire frame when a portal block is broken
@ -165,85 +161,29 @@ public class InstanceContainer extends Instance {
if (previousHandler != null) {
// Previous destroy
previousHandler.onDestroy(BlockHandler.Destroy.from(previousBlock, this, blockPosition));
final var destroy = player != null ?
new BlockHandler.PlayerDestroy(block, this, blockPosition, player) :
BlockHandler.Destroy.from(previousBlock, this, blockPosition);
previousHandler.onDestroy(destroy);
}
final BlockHandler handler = block.handler();
if (handler != null) {
// New placement
handler.onPlace(BlockHandler.Placement.from(block, this, blockPosition));
final var placement = player != null ?
new BlockHandler.PlayerPlacement(block, this, blockPosition, player) :
BlockHandler.Placement.from(block, this, blockPosition);
handler.onPlace(placement);
}
}
}
private void setAlreadyChanged(@NotNull BlockPosition blockPosition, Block block) {
currentlyChangingBlocks.put(blockPosition, block);
}
/**
* Has this block already changed since last update?
* Prevents StackOverflow with blocks trying to modify their position in onDestroy or onPlace.
*
* @param blockPosition the block position
* @param block the block
* @return true if the block changed since the last update
*/
private boolean isAlreadyChanged(@NotNull BlockPosition blockPosition, @NotNull Block block) {
final Block changedBlock = currentlyChangingBlocks.get(blockPosition);
if (changedBlock == null)
@Override
public boolean placeBlock(@NotNull Player player, @NotNull Block block, @NotNull BlockPosition blockPosition) {
final Chunk chunk = getChunkAt(blockPosition);
if (!ChunkUtils.isLoaded(chunk))
return false;
return changedBlock.id() == block.id();
}
/**
* Calls the {@link BlockPlacementRule} for the specified block state id.
*
* @param block the block to modify
* @param blockPosition the block position
* @return the modified block state id
*/
private Block executeBlockPlacementRule(Block block, @NotNull BlockPosition blockPosition) {
final BlockPlacementRule blockPlacementRule = BLOCK_MANAGER.getBlockPlacementRule(block);
if (blockPlacementRule != null) {
return blockPlacementRule.blockUpdate(this, blockPosition, block);
}
return block;
}
/**
* Executed when a block is modified, this is used to modify the states of neighbours blocks.
* <p>
* For example, this can be used for redstone wires which need an understanding of its neighborhoods to take the right shape.
*
* @param blockPosition the position of the modified block
*/
private void executeNeighboursBlockPlacementRule(@NotNull BlockPosition blockPosition) {
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.getX() + offsetX;
final int neighborY = blockPosition.getY() + offsetY;
final int neighborZ = blockPosition.getZ() + offsetZ;
final Chunk chunk = getChunkAt(neighborX, neighborZ);
// Do not try to get neighbour in an unloaded chunk
if (chunk == null)
continue;
final Block neighborBlock = chunk.getBlock(neighborX, neighborY, neighborZ);
final BlockPlacementRule neighborBlockPlacementRule = BLOCK_MANAGER.getBlockPlacementRule(neighborBlock);
if (neighborBlockPlacementRule != null) {
final BlockPosition neighborPosition = new BlockPosition(neighborX, neighborY, neighborZ);
final Block newNeighborBlock = neighborBlockPlacementRule.blockUpdate(this,
neighborPosition, neighborBlock);
if (neighborBlock != newNeighborBlock) {
setBlock(neighborPosition, newNeighborBlock);
}
}
}
}
}
UNSAFE_setBlock(chunk, blockPosition.getX(), blockPosition.getY(), blockPosition.getZ(), block, player);
return true;
}
@Override
@ -700,6 +640,77 @@ public class InstanceContainer extends Instance {
}
}
private void setAlreadyChanged(@NotNull BlockPosition blockPosition, Block block) {
currentlyChangingBlocks.put(blockPosition, block);
}
/**
* Has this block already changed since last update?
* Prevents StackOverflow with blocks trying to modify their position in onDestroy or onPlace.
*
* @param blockPosition the block position
* @param block the block
* @return true if the block changed since the last update
*/
private boolean isAlreadyChanged(@NotNull BlockPosition blockPosition, @NotNull Block block) {
final Block changedBlock = currentlyChangingBlocks.get(blockPosition);
if (changedBlock == null)
return false;
return changedBlock.id() == block.id();
}
/**
* Calls the {@link BlockPlacementRule} for the specified block state id.
*
* @param block the block to modify
* @param blockPosition the block position
* @return the modified block state id
*/
private Block executeBlockPlacementRule(Block block, @NotNull BlockPosition blockPosition) {
final BlockPlacementRule blockPlacementRule = BLOCK_MANAGER.getBlockPlacementRule(block);
if (blockPlacementRule != null) {
return blockPlacementRule.blockUpdate(this, blockPosition, block);
}
return block;
}
/**
* Executed when a block is modified, this is used to modify the states of neighbours blocks.
* <p>
* For example, this can be used for redstone wires which need an understanding of its neighborhoods to take the right shape.
*
* @param blockPosition the position of the modified block
*/
private void executeNeighboursBlockPlacementRule(@NotNull BlockPosition blockPosition) {
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.getX() + offsetX;
final int neighborY = blockPosition.getY() + offsetY;
final int neighborZ = blockPosition.getZ() + offsetZ;
final Chunk chunk = getChunkAt(neighborX, neighborZ);
// Do not try to get neighbour in an unloaded chunk
if (chunk == null)
continue;
final Block neighborBlock = chunk.getBlock(neighborX, neighborY, neighborZ);
final BlockPlacementRule neighborBlockPlacementRule = BLOCK_MANAGER.getBlockPlacementRule(neighborBlock);
if (neighborBlockPlacementRule != null) {
final BlockPosition neighborPosition = new BlockPosition(neighborX, neighborY, neighborZ);
final Block newNeighborBlock = neighborBlockPlacementRule.blockUpdate(this,
neighborPosition, neighborBlock);
if (neighborBlock != newNeighborBlock) {
setBlock(neighborPosition, newNeighborBlock);
}
}
}
}
}
}
private void callChunkLoadEvent(int chunkX, int chunkZ) {
InstanceChunkLoadEvent chunkLoadEvent = new InstanceChunkLoadEvent(this, chunkX, chunkZ);
EventDispatcher.call(chunkLoadEvent);

View File

@ -30,6 +30,11 @@ public class SharedInstance extends Instance {
this.instanceContainer.setBlock(x, y, z, block);
}
@Override
public boolean placeBlock(@NotNull Player player, @NotNull Block block, @NotNull BlockPosition blockPosition) {
return instanceContainer.placeBlock(player, block, blockPosition);
}
@Override
public boolean breakBlock(@NotNull Player player, @NotNull BlockPosition blockPosition) {
return instanceContainer.breakBlock(player, blockPosition);

View File

@ -112,6 +112,39 @@ public interface BlockHandler {
}
}
final class PlayerPlacement implements Placement {
private final Block block;
private final Instance instance;
private final BlockPosition blockPosition;
private final Player player;
public PlayerPlacement(Block block, Instance instance, BlockPosition blockPosition, Player player) {
this.block = block;
this.instance = instance;
this.blockPosition = blockPosition;
this.player = player;
}
@Override
public @NotNull Block block() {
return block;
}
@Override
public @NotNull Instance instance() {
return instance;
}
@Override
public @NotNull BlockPosition blockPosition() {
return blockPosition;
}
public @NotNull Player player() {
return player;
}
}
@ApiStatus.NonExtendable
interface Destroy {
@NotNull Block block();
@ -140,6 +173,39 @@ public interface BlockHandler {
}
}
final class PlayerDestroy implements Destroy {
private final Block block;
private final Instance instance;
private final BlockPosition blockPosition;
private final Player player;
public PlayerDestroy(Block block, Instance instance, BlockPosition blockPosition, Player player) {
this.block = block;
this.instance = instance;
this.blockPosition = blockPosition;
this.player = player;
}
@Override
public @NotNull Block block() {
return block;
}
@Override
public @NotNull Instance instance() {
return instance;
}
@Override
public @NotNull BlockPosition blockPosition() {
return blockPosition;
}
public @NotNull Player player() {
return player;
}
}
@ApiStatus.NonExtendable
interface Interaction {
@NotNull Block block();

View File

@ -142,7 +142,7 @@ public class BlockPlacementListener {
final boolean placementRuleCheck = resultBlock != null;
if (placementRuleCheck) {
// Place the block
instance.setBlock(blockPosition, resultBlock);
instance.placeBlock(player, resultBlock, blockPosition);
// Block consuming
if (playerBlockPlaceEvent.doesConsumeBlock()) {
// Consume the block in the player's hand