mirror of
https://github.com/Minestom/Minestom.git
synced 2025-01-06 16:37:38 +01:00
Add Placement/Destroy implementation for player
This commit is contained in:
parent
22ba15f390
commit
2b89fa1527
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user