mirror of https://github.com/Minestom/Minestom.git
Rewrite of the CustomBlock break delay system + support for multi player digging
This commit is contained in:
parent
b8c30d9b58
commit
5b394e5bf7
|
@ -30,13 +30,12 @@ import net.minestom.server.item.metadata.MapMeta;
|
||||||
import net.minestom.server.network.ConnectionManager;
|
import net.minestom.server.network.ConnectionManager;
|
||||||
import net.minestom.server.ping.ResponseDataConsumer;
|
import net.minestom.server.ping.ResponseDataConsumer;
|
||||||
import net.minestom.server.scoreboard.Sidebar;
|
import net.minestom.server.scoreboard.Sidebar;
|
||||||
import net.minestom.server.storage.StorageFolder;
|
|
||||||
import net.minestom.server.storage.StorageOptions;
|
|
||||||
import net.minestom.server.utils.BlockPosition;
|
import net.minestom.server.utils.BlockPosition;
|
||||||
import net.minestom.server.utils.MathUtils;
|
import net.minestom.server.utils.MathUtils;
|
||||||
import net.minestom.server.utils.Position;
|
import net.minestom.server.utils.Position;
|
||||||
import net.minestom.server.utils.Vector;
|
import net.minestom.server.utils.Vector;
|
||||||
import net.minestom.server.utils.time.TimeUnit;
|
import net.minestom.server.utils.time.TimeUnit;
|
||||||
|
import net.minestom.server.world.DimensionType;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
@ -48,11 +47,11 @@ public class PlayerInit {
|
||||||
private static volatile Inventory inventory;
|
private static volatile Inventory inventory;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
StorageFolder storageFolder = MinecraftServer.getStorageManager().getFolder("instance_data", new StorageOptions().setCompression(true));
|
//StorageFolder storageFolder = MinecraftServer.getStorageManager().getFolder("instance_data", new StorageOptions().setCompression(true));
|
||||||
ChunkGeneratorDemo chunkGeneratorDemo = new ChunkGeneratorDemo();
|
ChunkGeneratorDemo chunkGeneratorDemo = new ChunkGeneratorDemo();
|
||||||
NoiseTestGenerator noiseTestGenerator = new NoiseTestGenerator();
|
NoiseTestGenerator noiseTestGenerator = new NoiseTestGenerator();
|
||||||
instanceContainer = MinecraftServer.getInstanceManager().createInstanceContainer(storageFolder);
|
//instanceContainer = MinecraftServer.getInstanceManager().createInstanceContainer(storageFolder);
|
||||||
//instanceContainer = MinecraftServer.getInstanceManager().createInstanceContainer(DimensionType.OVERWORLD);
|
instanceContainer = MinecraftServer.getInstanceManager().createInstanceContainer(DimensionType.OVERWORLD);
|
||||||
instanceContainer.enableAutoChunkLoad(true);
|
instanceContainer.enableAutoChunkLoad(true);
|
||||||
//instanceContainer.setChunkDecider((x,y) -> (pos) -> pos.getY()>40?(short)0:(short)1);
|
//instanceContainer.setChunkDecider((x,y) -> (pos) -> pos.getY()>40?(short)0:(short)1);
|
||||||
instanceContainer.setChunkGenerator(noiseTestGenerator);
|
instanceContainer.setChunkGenerator(noiseTestGenerator);
|
||||||
|
@ -272,7 +271,7 @@ public class PlayerInit {
|
||||||
});
|
});
|
||||||
|
|
||||||
player.addEventCallback(PlayerSpawnEvent.class, event -> {
|
player.addEventCallback(PlayerSpawnEvent.class, event -> {
|
||||||
player.setGameMode(GameMode.CREATIVE);
|
player.setGameMode(GameMode.SURVIVAL);
|
||||||
player.teleport(new Position(0, 41f, 0));
|
player.teleport(new Position(0, 41f, 0));
|
||||||
|
|
||||||
//player.setHeldItemSlot((byte) 5);
|
//player.setHeldItemSlot((byte) 5);
|
||||||
|
@ -390,7 +389,7 @@ public class PlayerInit {
|
||||||
|
|
||||||
// Unload the chunk (save memory) if it has no remaining viewer
|
// Unload the chunk (save memory) if it has no remaining viewer
|
||||||
if (chunk.getViewers().isEmpty()) {
|
if (chunk.getViewers().isEmpty()) {
|
||||||
player.getInstance().unloadChunk(chunk);
|
//player.getInstance().unloadChunk(chunk);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -20,8 +20,8 @@ public class BurningTorchBlock extends CustomBlock {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleContact(Instance instance, BlockPosition position, Entity touching) {
|
public void handleContact(Instance instance, BlockPosition position, Entity touching) {
|
||||||
System.out.println("touching "+touching);
|
System.out.println("touching " + touching);
|
||||||
if(touching instanceof LivingEntity) {
|
if (touching instanceof LivingEntity) {
|
||||||
((LivingEntity) touching).damage(DamageType.GRAVITY, 0.1f);
|
((LivingEntity) touching).damage(DamageType.GRAVITY, 0.1f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,9 +45,4 @@ public class BurningTorchBlock extends CustomBlock {
|
||||||
public short getCustomBlockId() {
|
public short getCustomBlockId() {
|
||||||
return 3;
|
return 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getBreakDelay(Player player, BlockPosition position) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,8 @@ import net.minestom.server.instance.block.Block;
|
||||||
import net.minestom.server.instance.block.CustomBlock;
|
import net.minestom.server.instance.block.CustomBlock;
|
||||||
import net.minestom.server.utils.BlockPosition;
|
import net.minestom.server.utils.BlockPosition;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
public class StoneBlock extends CustomBlock {
|
public class StoneBlock extends CustomBlock {
|
||||||
|
|
||||||
public StoneBlock() {
|
public StoneBlock() {
|
||||||
|
@ -39,8 +41,18 @@ public class StoneBlock extends CustomBlock {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getBreakDelay(Player player, BlockPosition position) {
|
public int getBreakDelay(Player player, BlockPosition position, byte stage, Set<Player> breakers) {
|
||||||
return 750;
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean enableCustomBreakDelay() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean enableMultiPlayerBreaking() {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -9,6 +9,8 @@ import net.minestom.server.utils.BlockPosition;
|
||||||
import net.minestom.server.utils.time.TimeUnit;
|
import net.minestom.server.utils.time.TimeUnit;
|
||||||
import net.minestom.server.utils.time.UpdateOption;
|
import net.minestom.server.utils.time.UpdateOption;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
public class UpdatableBlockDemo extends CustomBlock {
|
public class UpdatableBlockDemo extends CustomBlock {
|
||||||
|
|
||||||
private static final UpdateOption UPDATE_OPTION = new UpdateOption(20, TimeUnit.TICK);
|
private static final UpdateOption UPDATE_OPTION = new UpdateOption(20, TimeUnit.TICK);
|
||||||
|
@ -43,8 +45,13 @@ public class UpdatableBlockDemo extends CustomBlock {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getBreakDelay(Player player, BlockPosition position) {
|
public int getBreakDelay(Player player, BlockPosition position, byte stage, Set<Player> breakers) {
|
||||||
return 500;
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean enableCustomBreakDelay() {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -67,7 +67,9 @@ public class SimpleCommand implements CommandProcessor {
|
||||||
NotificationCenter.send(notification, player);
|
NotificationCenter.send(notification, player);
|
||||||
NotificationCenter.send(notification, player);
|
NotificationCenter.send(notification, player);
|
||||||
|
|
||||||
player.getInstance().saveChunksToStorageFolder(() -> System.out.println("end save"));
|
System.gc();
|
||||||
|
|
||||||
|
//player.getInstance().saveChunksToStorageFolder(() -> System.out.println("end save"));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ import net.minestom.server.network.packet.server.login.JoinGamePacket;
|
||||||
import net.minestom.server.network.packet.server.play.*;
|
import net.minestom.server.network.packet.server.play.*;
|
||||||
import net.minestom.server.network.player.PlayerConnection;
|
import net.minestom.server.network.player.PlayerConnection;
|
||||||
import net.minestom.server.permission.Permission;
|
import net.minestom.server.permission.Permission;
|
||||||
|
import net.minestom.server.potion.PotionType;
|
||||||
import net.minestom.server.recipe.Recipe;
|
import net.minestom.server.recipe.Recipe;
|
||||||
import net.minestom.server.recipe.RecipeManager;
|
import net.minestom.server.recipe.RecipeManager;
|
||||||
import net.minestom.server.resourcepack.ResourcePack;
|
import net.minestom.server.resourcepack.ResourcePack;
|
||||||
|
@ -102,9 +103,10 @@ public class Player extends LivingEntity implements CommandSender {
|
||||||
// CustomBlock break delay
|
// CustomBlock break delay
|
||||||
private CustomBlock targetCustomBlock;
|
private CustomBlock targetCustomBlock;
|
||||||
private BlockPosition targetBlockPosition;
|
private BlockPosition targetBlockPosition;
|
||||||
private long targetBlockTime;
|
private long targetBreakDelay; // The last break delay requested
|
||||||
private byte targetLastStage;
|
private long targetBlockLastStageChangeTime; // Time at which the block stage last changed
|
||||||
private int blockBreakTime;
|
private byte targetStage; // The current stage of the target block, only if multi player breaking is disabled
|
||||||
|
private final Set<Player> targetBreakers = new HashSet<>(1); // Only used if multi player breaking is disabled, contains only this player
|
||||||
|
|
||||||
private BelowNameTag belowNameTag;
|
private BelowNameTag belowNameTag;
|
||||||
|
|
||||||
|
@ -159,6 +161,9 @@ public class Player extends LivingEntity implements CommandSender {
|
||||||
this.levelType = LevelType.FLAT;
|
this.levelType = LevelType.FLAT;
|
||||||
refreshPosition(0, 0, 0);
|
refreshPosition(0, 0, 0);
|
||||||
|
|
||||||
|
// Used to cache the breaker for single custom block breaking
|
||||||
|
this.targetBreakers.add(this);
|
||||||
|
|
||||||
// FakePlayer init its connection there
|
// FakePlayer init its connection there
|
||||||
playerConnectionInit();
|
playerConnectionInit();
|
||||||
|
|
||||||
|
@ -300,17 +305,39 @@ public class Player extends LivingEntity implements CommandSender {
|
||||||
|
|
||||||
// Target block stage
|
// Target block stage
|
||||||
if (targetCustomBlock != null) {
|
if (targetCustomBlock != null) {
|
||||||
final byte animationCount = 10;
|
final boolean processStage = (time - targetBlockLastStageChangeTime) >= targetBreakDelay;
|
||||||
final long since = time - targetBlockTime;
|
if (processStage) {
|
||||||
byte stage = (byte) (since / (blockBreakTime / animationCount));
|
// Should increment the target block stage
|
||||||
stage = MathUtils.setBetween(stage, (byte) -1, animationCount);
|
if (targetCustomBlock.enableMultiPlayerBreaking()) {
|
||||||
if (stage != targetLastStage) {
|
// Let the custom block object manages the breaking
|
||||||
sendBlockBreakAnimation(targetBlockPosition, stage);
|
final boolean canContinue = this.targetCustomBlock.processStage(instance, targetBlockPosition, this);
|
||||||
}
|
if (canContinue) {
|
||||||
this.targetLastStage = stage;
|
final Set<Player> breakers = targetCustomBlock.getBreakers(instance, targetBlockPosition);
|
||||||
if (stage > 9) {
|
refreshBreakDelay(breakers);
|
||||||
instance.breakBlock(this, targetBlockPosition);
|
this.targetBlockLastStageChangeTime = time;
|
||||||
resetTargetBlock();
|
} else {
|
||||||
|
resetTargetBlock();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Let the player object manages the breaking
|
||||||
|
// The custom block doesn't support multi player breaking
|
||||||
|
if (targetStage + 1 >= CustomBlock.MAX_STAGE) {
|
||||||
|
// Break the block
|
||||||
|
instance.breakBlock(this, targetBlockPosition);
|
||||||
|
resetTargetBlock();
|
||||||
|
} else {
|
||||||
|
// Send the new block break animation packet and refresh data
|
||||||
|
|
||||||
|
final Chunk chunk = instance.getChunkAt(targetBlockPosition);
|
||||||
|
final int entityId = targetCustomBlock.getBreakEntityId(this);
|
||||||
|
final BlockBreakAnimationPacket blockBreakAnimationPacket = new BlockBreakAnimationPacket(entityId, targetBlockPosition, targetStage);
|
||||||
|
chunk.sendPacketToViewers(blockBreakAnimationPacket);
|
||||||
|
|
||||||
|
refreshBreakDelay(targetBreakers);
|
||||||
|
this.targetBlockLastStageChangeTime = time;
|
||||||
|
this.targetStage++;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -630,24 +657,6 @@ public class Player extends LivingEntity implements CommandSender {
|
||||||
sendPluginMessage(channel, data);
|
sendPluginMessage(channel, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a {@link BlockBreakAnimationPacket} packet to the player and his viewers
|
|
||||||
* Setting {@code destroyStage} to -1 resets the break animation
|
|
||||||
*
|
|
||||||
* @param blockPosition the position of the block
|
|
||||||
* @param destroyStage the destroy stage
|
|
||||||
* @throws IllegalArgumentException if {@code destroyStage} is not between -1 and 10
|
|
||||||
*/
|
|
||||||
public void sendBlockBreakAnimation(BlockPosition blockPosition, byte destroyStage) {
|
|
||||||
Check.argCondition(!MathUtils.isBetween(destroyStage, -1, 10),
|
|
||||||
"The destroy stage has to be between -1 and 10");
|
|
||||||
BlockBreakAnimationPacket breakAnimationPacket = new BlockBreakAnimationPacket();
|
|
||||||
breakAnimationPacket.entityId = getEntityId() + 1;
|
|
||||||
breakAnimationPacket.blockPosition = blockPosition;
|
|
||||||
breakAnimationPacket.destroyStage = destroyStage;
|
|
||||||
sendPacketToViewersAndSelf(breakAnimationPacket);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendMessage(String message) {
|
public void sendMessage(String message) {
|
||||||
sendMessage(ColoredText.of(message));
|
sendMessage(ColoredText.of(message));
|
||||||
|
@ -1690,7 +1699,7 @@ public class Player extends LivingEntity implements CommandSender {
|
||||||
* Change the player ability "Creative Mode"
|
* Change the player ability "Creative Mode"
|
||||||
* <a href="https://wiki.vg/Protocol#Player_Abilities_.28clientbound.29">see</a>
|
* <a href="https://wiki.vg/Protocol#Player_Abilities_.28clientbound.29">see</a>
|
||||||
* <p>
|
* <p>
|
||||||
* WARNING: this has nothing to do with {@link CustomBlock#getBreakDelay(Player, BlockPosition)}
|
* WARNING: this has nothing to do with {@link CustomBlock#getBreakDelay(Player, BlockPosition, byte, Set)}
|
||||||
*
|
*
|
||||||
* @param instantBreak true to allow instant break
|
* @param instantBreak true to allow instant break
|
||||||
*/
|
*/
|
||||||
|
@ -1870,13 +1879,25 @@ public class Player extends LivingEntity implements CommandSender {
|
||||||
*
|
*
|
||||||
* @param targetCustomBlock the custom block to dig
|
* @param targetCustomBlock the custom block to dig
|
||||||
* @param targetBlockPosition the custom block position
|
* @param targetBlockPosition the custom block position
|
||||||
* @param breakTime the time it will take to break the block in milliseconds
|
* @param breakers the breakers of the block, can be null if {@code this} is the only breaker
|
||||||
*/
|
*/
|
||||||
public void setTargetBlock(CustomBlock targetCustomBlock, BlockPosition targetBlockPosition, int breakTime) {
|
public void setTargetBlock(CustomBlock targetCustomBlock, BlockPosition targetBlockPosition, Set<Player> breakers) {
|
||||||
this.targetCustomBlock = targetCustomBlock;
|
this.targetCustomBlock = targetCustomBlock;
|
||||||
this.targetBlockPosition = targetBlockPosition;
|
this.targetBlockPosition = targetBlockPosition;
|
||||||
this.targetBlockTime = targetBlockPosition == null ? 0 : System.currentTimeMillis();
|
|
||||||
this.blockBreakTime = breakTime;
|
refreshBreakDelay(breakers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh the break delay for the next block break stage
|
||||||
|
*
|
||||||
|
* @param breakers the list of breakers, can be null if {@code this} is the only breaker
|
||||||
|
*/
|
||||||
|
private void refreshBreakDelay(Set<Player> breakers) {
|
||||||
|
breakers = breakers == null ? targetBreakers : breakers;
|
||||||
|
final byte stage = targetCustomBlock.getBreakStage(instance, targetBlockPosition);
|
||||||
|
final int breakDelay = targetCustomBlock.getBreakDelay(this, targetBlockPosition, stage, breakers);
|
||||||
|
this.targetBreakDelay = breakDelay * MinecraftServer.TICK_MS;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1884,17 +1905,19 @@ public class Player extends LivingEntity implements CommandSender {
|
||||||
* If the currently mined block (or if there isn't any) is not a CustomBlock, nothing append
|
* If the currently mined block (or if there isn't any) is not a CustomBlock, nothing append
|
||||||
*/
|
*/
|
||||||
public void resetTargetBlock() {
|
public void resetTargetBlock() {
|
||||||
if (targetBlockPosition != null) {
|
if (targetCustomBlock != null) {
|
||||||
sendBlockBreakAnimation(targetBlockPosition, (byte) -1); // Clear the break animation
|
targetCustomBlock.stopDigging(instance, targetBlockPosition, this);
|
||||||
this.targetCustomBlock = null;
|
this.targetCustomBlock = null;
|
||||||
this.targetBlockPosition = null;
|
this.targetBlockPosition = null;
|
||||||
this.targetBlockTime = 0;
|
this.targetBreakDelay = 0;
|
||||||
|
this.targetBlockLastStageChangeTime = 0;
|
||||||
|
this.targetStage = 0;
|
||||||
|
|
||||||
// Remove effect
|
// Remove effect
|
||||||
RemoveEntityEffectPacket removeEntityEffectPacket = new RemoveEntityEffectPacket();
|
RemoveEntityEffectPacket removeEntityEffectPacket = new RemoveEntityEffectPacket();
|
||||||
removeEntityEffectPacket.entityId = getEntityId();
|
removeEntityEffectPacket.entityId = getEntityId();
|
||||||
removeEntityEffectPacket.effectId = 4;
|
removeEntityEffectPacket.effect = PotionType.AWKWARD;
|
||||||
getPlayerConnection().sendPacket(removeEntityEffectPacket);
|
playerConnection.sendPacket(removeEntityEffectPacket);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1927,7 +1950,7 @@ public class Player extends LivingEntity implements CommandSender {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the packet to add the player from tab-list
|
* Get the packet to add the player from the tab-list
|
||||||
*
|
*
|
||||||
* @return a {@link PlayerInfoPacket} to add the player
|
* @return a {@link PlayerInfoPacket} to add the player
|
||||||
*/
|
*/
|
||||||
|
@ -1950,9 +1973,9 @@ public class Player extends LivingEntity implements CommandSender {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the packet to remove the player from tab-list
|
* Get the packet to remove the player from the tab-list
|
||||||
*
|
*
|
||||||
* @return a {@link PlayerInfoPacket} to add the player
|
* @return a {@link PlayerInfoPacket} to remove the player
|
||||||
*/
|
*/
|
||||||
protected PlayerInfoPacket getRemovePlayerToList() {
|
protected PlayerInfoPacket getRemovePlayerToList() {
|
||||||
PlayerInfoPacket playerInfoPacket = new PlayerInfoPacket(PlayerInfoPacket.Action.REMOVE_PLAYER);
|
PlayerInfoPacket playerInfoPacket = new PlayerInfoPacket(PlayerInfoPacket.Action.REMOVE_PLAYER);
|
||||||
|
@ -1968,7 +1991,7 @@ public class Player extends LivingEntity implements CommandSender {
|
||||||
* Send all the related packet to have the player sent to another with related data
|
* Send all the related packet to have the player sent to another with related data
|
||||||
* (create player, spawn position, velocity, metadata, equipments, passengers, team)
|
* (create player, spawn position, velocity, metadata, equipments, passengers, team)
|
||||||
* <p>
|
* <p>
|
||||||
* WARNING: this does not sync the player, please use {@link #addViewer(Player)}
|
* WARNING: this alone does not sync the player, please use {@link #addViewer(Player)}
|
||||||
*
|
*
|
||||||
* @param connection the connection to show the player to
|
* @param connection the connection to show the player to
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -23,6 +23,14 @@ public class PlayerVehicleInformation {
|
||||||
return unmount;
|
return unmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh internal data
|
||||||
|
*
|
||||||
|
* @param sideways the new sideways value
|
||||||
|
* @param forward the new forward value
|
||||||
|
* @param jump the new jump value
|
||||||
|
* @param unmount the new unmount value
|
||||||
|
*/
|
||||||
public void refresh(float sideways, float forward, boolean jump, boolean unmount) {
|
public void refresh(float sideways, float forward, boolean jump, boolean unmount) {
|
||||||
this.sideways = sideways;
|
this.sideways = sideways;
|
||||||
this.forward = forward;
|
this.forward = forward;
|
||||||
|
|
|
@ -116,8 +116,17 @@ public class InstanceContainer extends Instance {
|
||||||
setAlreadyChanged(blockPosition, blockStateId);
|
setAlreadyChanged(blockPosition, blockStateId);
|
||||||
final int index = ChunkUtils.getBlockIndex(x, y, z);
|
final int index = ChunkUtils.getBlockIndex(x, y, z);
|
||||||
|
|
||||||
// Call the destroy listener if previous block was a custom block
|
final CustomBlock previousBlock = chunk.getCustomBlock(index);
|
||||||
callBlockDestroy(chunk, index, blockPosition);
|
if (previousBlock != null) {
|
||||||
|
// Previous block was a custom block
|
||||||
|
|
||||||
|
// Call the destroy listener
|
||||||
|
callBlockDestroy(chunk, index, previousBlock, blockPosition);
|
||||||
|
|
||||||
|
// Remove digging information for the previous custom block
|
||||||
|
previousBlock.removeDiggingInformation(this, blockPosition);
|
||||||
|
}
|
||||||
|
|
||||||
// Change id based on neighbors
|
// Change id based on neighbors
|
||||||
blockStateId = executeBlockPlacementRule(blockStateId, blockPosition);
|
blockStateId = executeBlockPlacementRule(blockStateId, blockPosition);
|
||||||
|
|
||||||
|
@ -170,13 +179,10 @@ public class InstanceContainer extends Instance {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void callBlockDestroy(Chunk chunk, int index, BlockPosition blockPosition) {
|
private void callBlockDestroy(Chunk chunk, int index, CustomBlock previousBlock, BlockPosition blockPosition) {
|
||||||
final CustomBlock previousBlock = chunk.getCustomBlock(index);
|
final Data previousData = chunk.getData(index);
|
||||||
if (previousBlock != null) {
|
previousBlock.onDestroy(this, blockPosition, previousData);
|
||||||
final Data previousData = chunk.getData(index);
|
chunk.UNSAFE_removeCustomBlock(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ());
|
||||||
previousBlock.onDestroy(this, blockPosition, previousData);
|
|
||||||
chunk.UNSAFE_removeCustomBlock(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void callBlockPlace(Chunk chunk, int index, BlockPosition blockPosition) {
|
private void callBlockPlace(Chunk chunk, int index, BlockPosition blockPosition) {
|
||||||
|
|
|
@ -23,13 +23,18 @@ public class MinestomBasicChunkLoader implements IChunkLoader {
|
||||||
LOGGER.warn("No folder to save chunk!");
|
LOGGER.warn("No folder to save chunk!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final int chunkX = chunk.getChunkX();
|
final int chunkX = chunk.getChunkX();
|
||||||
final int chunkZ = chunk.getChunkZ();
|
final int chunkZ = chunk.getChunkZ();
|
||||||
|
|
||||||
final String key = getChunkKey(chunkX, chunkZ);
|
final String key = getChunkKey(chunkX, chunkZ);
|
||||||
final byte[] data = chunk.getSerializedData();
|
final byte[] data = chunk.getSerializedData();
|
||||||
if (data == null)
|
if (data == null) {
|
||||||
|
if (callback != null)
|
||||||
|
callback.run();
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
storageFolder.set(key, data);
|
storageFolder.set(key, data);
|
||||||
|
|
||||||
if (callback != null)
|
if (callback != null)
|
||||||
|
|
|
@ -1,16 +1,28 @@
|
||||||
package net.minestom.server.instance.block;
|
package net.minestom.server.instance.block;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2ByteMap;
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2ByteOpenHashMap;
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||||
import net.minestom.server.data.Data;
|
import net.minestom.server.data.Data;
|
||||||
import net.minestom.server.entity.Entity;
|
import net.minestom.server.entity.Entity;
|
||||||
import net.minestom.server.entity.Player;
|
import net.minestom.server.entity.Player;
|
||||||
import net.minestom.server.gamedata.loottables.LootTable;
|
import net.minestom.server.gamedata.loottables.LootTable;
|
||||||
import net.minestom.server.gamedata.loottables.LootTableManager;
|
import net.minestom.server.gamedata.loottables.LootTableManager;
|
||||||
import net.minestom.server.instance.BlockModifier;
|
import net.minestom.server.instance.BlockModifier;
|
||||||
|
import net.minestom.server.instance.Chunk;
|
||||||
import net.minestom.server.instance.Instance;
|
import net.minestom.server.instance.Instance;
|
||||||
|
import net.minestom.server.network.packet.server.play.BlockBreakAnimationPacket;
|
||||||
import net.minestom.server.utils.BlockPosition;
|
import net.minestom.server.utils.BlockPosition;
|
||||||
import net.minestom.server.utils.time.UpdateOption;
|
import net.minestom.server.utils.time.UpdateOption;
|
||||||
|
import net.minestom.server.utils.validate.Check;
|
||||||
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
|
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represent the handler of a custom block type.
|
* Represent the handler of a custom block type.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -20,10 +32,17 @@ import org.jglrxavpok.hephaistos.nbt.NBTCompound;
|
||||||
*/
|
*/
|
||||||
public abstract class CustomBlock {
|
public abstract class CustomBlock {
|
||||||
|
|
||||||
|
public static final byte MAX_STAGE = 10;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO
|
* Instance -> break data
|
||||||
* - option to set the global as "global breaking" meaning that multiple players mining the same block will break it faster (accumulation)
|
* Used to store block break stage data when {@link #enableMultiPlayerBreaking()} is enabled
|
||||||
*/
|
*/
|
||||||
|
private final Map<Instance, InstanceBreakData> instanceBreakDataMap = new HashMap<>();
|
||||||
|
|
||||||
|
public int getBreakEntityId(Player firstBreaker) {
|
||||||
|
return firstBreaker.getEntityId() + 1;
|
||||||
|
}
|
||||||
|
|
||||||
private final short blockStateId;
|
private final short blockStateId;
|
||||||
private final String identifier;
|
private final String identifier;
|
||||||
|
@ -106,17 +125,45 @@ public abstract class CustomBlock {
|
||||||
public abstract short getCustomBlockId();
|
public abstract short getCustomBlockId();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called at digging start to check for custom breaking time
|
* Called when the player requests the next stage break delay
|
||||||
* Can be set to < 0 to be cancelled, in this case vanilla time will be used
|
|
||||||
*
|
*
|
||||||
* @param player the player who is trying to break the block
|
* @param player the player who is trying to break the block
|
||||||
* @param position the block position
|
* @param position the block position
|
||||||
* @return the time in ms to break it
|
* @param stage the current break stage of the block (0-10)
|
||||||
|
* @param breakers the list containing all the players currently digging this block
|
||||||
|
* @return the time in tick to pass to the next state, 0 to instant break it.
|
||||||
|
* @see #enableCustomBreakDelay() to enable/disable it
|
||||||
*/
|
*/
|
||||||
public abstract int getBreakDelay(Player player, BlockPosition position);
|
public int getBreakDelay(Player player, BlockPosition position, byte stage, Set<Player> breakers) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return true if {@link #getUpdateOption()} is not null, false otherwise
|
* Used to enable the custom break delay from {@link #getBreakDelay(Player, BlockPosition, byte, Set)}
|
||||||
|
* Disabling it would result in having vanilla time
|
||||||
|
*
|
||||||
|
* @return true to enable custom break delay
|
||||||
|
*/
|
||||||
|
public boolean enableCustomBreakDelay() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get if this block breaking time can be reduced by having multiple players
|
||||||
|
* digging it
|
||||||
|
* <p>
|
||||||
|
* WARNING: this should be constant, do not change this value halfway
|
||||||
|
*
|
||||||
|
* @return true to enable the multi-player breaking feature
|
||||||
|
*/
|
||||||
|
public boolean enableMultiPlayerBreaking() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get if this {@link CustomBlock} requires any tick update
|
||||||
|
*
|
||||||
|
* @return true if {@link #getUpdateOption()} is not null and the value is positive
|
||||||
*/
|
*/
|
||||||
public boolean hasUpdate() {
|
public boolean hasUpdate() {
|
||||||
final UpdateOption updateOption = getUpdateOption();
|
final UpdateOption updateOption = getUpdateOption();
|
||||||
|
@ -239,4 +286,189 @@ public abstract class CustomBlock {
|
||||||
public LootTable getLootTable(LootTableManager tableManager) {
|
public LootTable getLootTable(LootTableManager tableManager) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BLOCK BREAK METHODS
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a player start digging this custom block,
|
||||||
|
* process all necessary data if {@link #enableMultiPlayerBreaking()} is enabled
|
||||||
|
*
|
||||||
|
* @param instance the instance of the block
|
||||||
|
* @param blockPosition the position of the block
|
||||||
|
* @param player the player who started digging
|
||||||
|
*/
|
||||||
|
public void startDigging(Instance instance, BlockPosition blockPosition, Player player) {
|
||||||
|
// Stay null if multi player breaking is disabled
|
||||||
|
Set<Player> breakers = null;
|
||||||
|
|
||||||
|
if (enableMultiPlayerBreaking()) {
|
||||||
|
// Multi player breaking enabled, get the breakers and cache some values
|
||||||
|
InstanceBreakData instanceBreakData = instanceBreakDataMap.computeIfAbsent(instance, i -> new InstanceBreakData());
|
||||||
|
|
||||||
|
Map<BlockPosition, Set<Player>> breakersMap = instanceBreakData.breakersMap;
|
||||||
|
breakers = breakersMap.computeIfAbsent(blockPosition, pos -> new HashSet<>(1));
|
||||||
|
breakers.add(player);
|
||||||
|
|
||||||
|
Object2ByteMap<BlockPosition> breakStageMap = instanceBreakData.breakStageMap;
|
||||||
|
// Set the block stage to 0, use the previous one if any
|
||||||
|
if (!breakStageMap.containsKey(blockPosition)) {
|
||||||
|
breakStageMap.put(blockPosition, (byte) 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Object2IntMap<BlockPosition> breakIdMap = instanceBreakData.breakIdMap;
|
||||||
|
// Set the entity id used for the packet, otherwise use the previous one
|
||||||
|
if (!breakIdMap.containsKey(blockPosition)) {
|
||||||
|
breakIdMap.put(blockPosition, getBreakEntityId(player));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the player target block
|
||||||
|
player.setTargetBlock(this, blockPosition, breakers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a player stop digging a block,
|
||||||
|
* does remove the block break animation if he was the only breaker
|
||||||
|
*
|
||||||
|
* @param instance the instance of the block
|
||||||
|
* @param blockPosition the position of the block
|
||||||
|
* @param player the player who stopped digging
|
||||||
|
*/
|
||||||
|
public void stopDigging(Instance instance, BlockPosition blockPosition, Player player) {
|
||||||
|
if (enableMultiPlayerBreaking()) {
|
||||||
|
// Remove cache data
|
||||||
|
if (instanceBreakDataMap.containsKey(instance)) {
|
||||||
|
InstanceBreakData instanceBreakData = instanceBreakDataMap.get(instance);
|
||||||
|
|
||||||
|
Set<Player> breakers = instanceBreakData.breakersMap.get(blockPosition);
|
||||||
|
if (breakers != null) {
|
||||||
|
breakers.remove(player);
|
||||||
|
if (breakers.isEmpty()) {
|
||||||
|
// No remaining breakers
|
||||||
|
|
||||||
|
// Get the entity id assigned to the block break
|
||||||
|
final int entityId = instanceBreakData.breakIdMap.getInt(blockPosition);
|
||||||
|
|
||||||
|
final Chunk chunk = instance.getChunkAt(blockPosition);
|
||||||
|
chunk.sendPacketToViewers(new BlockBreakAnimationPacket(entityId, blockPosition, (byte) -1));
|
||||||
|
|
||||||
|
// Clear cache
|
||||||
|
removeDiggingInformation(instance, blockPosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Stop the breaking animation for the specific player id
|
||||||
|
final Chunk chunk = instance.getChunkAt(blockPosition);
|
||||||
|
final int entityId = getBreakEntityId(player);
|
||||||
|
chunk.sendPacketToViewers(new BlockBreakAnimationPacket(entityId, blockPosition, (byte) -1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process one stage on the block, break it if it excess {@link #MAX_STAGE},
|
||||||
|
* only if {@link #enableMultiPlayerBreaking()} is enabled
|
||||||
|
*
|
||||||
|
* @param instance the instance of the block
|
||||||
|
* @param blockPosition the position of the block
|
||||||
|
* @param player the player who processed one stage on the block
|
||||||
|
* @return true if the block can continue being digged
|
||||||
|
* @throws IllegalStateException if {@link #enableMultiPlayerBreaking()} is disabled
|
||||||
|
*/
|
||||||
|
public synchronized boolean processStage(Instance instance, BlockPosition blockPosition, Player player) {
|
||||||
|
Check.stateCondition(!enableMultiPlayerBreaking(),
|
||||||
|
"CustomBlock#processState requires having the multi player breaking feature enabled");
|
||||||
|
|
||||||
|
if (instanceBreakDataMap.containsKey(instance)) {
|
||||||
|
InstanceBreakData instanceBreakData = instanceBreakDataMap.get(instance);
|
||||||
|
Object2ByteMap<BlockPosition> breakStageMap = instanceBreakData.breakStageMap;
|
||||||
|
byte stage = breakStageMap.getByte(blockPosition);
|
||||||
|
if (stage + 1 >= MAX_STAGE) {
|
||||||
|
instance.breakBlock(player, blockPosition);
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Get the entity id assigned to the block break
|
||||||
|
final int entityId = instanceBreakData.breakIdMap.getInt(blockPosition);
|
||||||
|
|
||||||
|
// Send the block break animation
|
||||||
|
final Chunk chunk = instance.getChunkAt(blockPosition);
|
||||||
|
chunk.sendPacketToViewers(new BlockBreakAnimationPacket(entityId, blockPosition, stage));
|
||||||
|
|
||||||
|
// Refresh the stage
|
||||||
|
breakStageMap.put(blockPosition, ++stage);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeDiggingInformation(Instance instance, BlockPosition blockPosition) {
|
||||||
|
if (!enableMultiPlayerBreaking()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (instanceBreakDataMap.containsKey(instance)) {
|
||||||
|
InstanceBreakData instanceBreakData = instanceBreakDataMap.get(instance);
|
||||||
|
// Remove the block position from all maps
|
||||||
|
instanceBreakData.clear(blockPosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all the breakers of a block, only if {@link #enableMultiPlayerBreaking()} is enabled
|
||||||
|
*
|
||||||
|
* @param instance the instance of the block
|
||||||
|
* @param blockPosition the position of the block
|
||||||
|
* @return the {@link Set} of breakers of a block
|
||||||
|
* @throws IllegalStateException if {@link #enableMultiPlayerBreaking()} is disabled
|
||||||
|
*/
|
||||||
|
public Set<Player> getBreakers(Instance instance, BlockPosition blockPosition) {
|
||||||
|
Check.stateCondition(!enableMultiPlayerBreaking(),
|
||||||
|
"CustomBlock#getBreakers requires having the multi player breaking feature enabled");
|
||||||
|
|
||||||
|
if (instanceBreakDataMap.containsKey(instance)) {
|
||||||
|
InstanceBreakData instanceBreakData = instanceBreakDataMap.get(instance);
|
||||||
|
return instanceBreakData.breakersMap.get(blockPosition);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the block break stage at a position, only work if {@link #enableMultiPlayerBreaking()} is enabled
|
||||||
|
*
|
||||||
|
* @param instance the instance of the custom block
|
||||||
|
* @param blockPosition the position of the custom block
|
||||||
|
* @return the break stage at the position. Can also be 0 when nonexistent
|
||||||
|
*/
|
||||||
|
public byte getBreakStage(Instance instance, BlockPosition blockPosition) {
|
||||||
|
Check.stateCondition(!enableMultiPlayerBreaking(),
|
||||||
|
"CustomBlock#getBreakStage requires having the multi player breaking feature enabled");
|
||||||
|
|
||||||
|
if (!instanceBreakDataMap.containsKey(instance))
|
||||||
|
return 0;
|
||||||
|
final InstanceBreakData instanceBreakData = instanceBreakDataMap.get(instance);
|
||||||
|
return instanceBreakData.breakStageMap.getByte(blockPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class used to store block break stage
|
||||||
|
* Only used if multi player breaking is enabled
|
||||||
|
*/
|
||||||
|
private class InstanceBreakData {
|
||||||
|
// Contains all the breakers of a block
|
||||||
|
private final Map<BlockPosition, Set<Player>> breakersMap = new HashMap<>();
|
||||||
|
// Contains the current break stage of a block
|
||||||
|
private final Object2ByteMap<BlockPosition> breakStageMap = new Object2ByteOpenHashMap<>();
|
||||||
|
// Contains the entity id used by the block break packet
|
||||||
|
private final Object2IntMap<BlockPosition> breakIdMap = new Object2IntOpenHashMap<>();
|
||||||
|
|
||||||
|
private void clear(BlockPosition blockPosition) {
|
||||||
|
this.breakersMap.remove(blockPosition);
|
||||||
|
this.breakStageMap.removeByte(blockPosition);
|
||||||
|
this.breakIdMap.removeInt(blockPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import net.minestom.server.item.StackingRule;
|
||||||
import net.minestom.server.network.packet.client.play.ClientPlayerDiggingPacket;
|
import net.minestom.server.network.packet.client.play.ClientPlayerDiggingPacket;
|
||||||
import net.minestom.server.network.packet.server.play.AcknowledgePlayerDiggingPacket;
|
import net.minestom.server.network.packet.server.play.AcknowledgePlayerDiggingPacket;
|
||||||
import net.minestom.server.network.packet.server.play.EntityEffectPacket;
|
import net.minestom.server.network.packet.server.play.EntityEffectPacket;
|
||||||
|
import net.minestom.server.potion.PotionType;
|
||||||
import net.minestom.server.utils.BlockPosition;
|
import net.minestom.server.utils.BlockPosition;
|
||||||
|
|
||||||
public class PlayerDiggingListener {
|
public class PlayerDiggingListener {
|
||||||
|
@ -43,17 +44,17 @@ public class PlayerDiggingListener {
|
||||||
} else {
|
} else {
|
||||||
final CustomBlock customBlock = instance.getCustomBlock(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ());
|
final CustomBlock customBlock = instance.getCustomBlock(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ());
|
||||||
if (customBlock != null) {
|
if (customBlock != null) {
|
||||||
int breakTime = customBlock.getBreakDelay(player, blockPosition);
|
|
||||||
|
|
||||||
// Custom block has a custom break time, allow for digging event
|
// Custom block has a custom break time, allow for digging event
|
||||||
PlayerStartDiggingEvent playerStartDiggingEvent = new PlayerStartDiggingEvent(player, blockPosition, customBlock);
|
PlayerStartDiggingEvent playerStartDiggingEvent = new PlayerStartDiggingEvent(player, blockPosition, customBlock);
|
||||||
player.callEvent(PlayerStartDiggingEvent.class, playerStartDiggingEvent);
|
player.callEvent(PlayerStartDiggingEvent.class, playerStartDiggingEvent);
|
||||||
if (!playerStartDiggingEvent.isCancelled()) {
|
if (!playerStartDiggingEvent.isCancelled()) {
|
||||||
|
|
||||||
// Start digging the block
|
// Start digging the block
|
||||||
if (breakTime >= 0) {
|
if (customBlock.enableCustomBreakDelay()) {
|
||||||
player.setTargetBlock(customBlock, blockPosition, breakTime);
|
customBlock.startDigging(instance, blockPosition, player);
|
||||||
addEffect(player);
|
addEffect(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
sendAcknowledgePacket(player, blockPosition, customBlock.getBlockStateId(),
|
sendAcknowledgePacket(player, blockPosition, customBlock.getBlockStateId(),
|
||||||
ClientPlayerDiggingPacket.Status.STARTED_DIGGING, true);
|
ClientPlayerDiggingPacket.Status.STARTED_DIGGING, true);
|
||||||
} else {
|
} else {
|
||||||
|
@ -140,7 +141,7 @@ public class PlayerDiggingListener {
|
||||||
private static void addEffect(Player player) {
|
private static void addEffect(Player player) {
|
||||||
EntityEffectPacket entityEffectPacket = new EntityEffectPacket();
|
EntityEffectPacket entityEffectPacket = new EntityEffectPacket();
|
||||||
entityEffectPacket.entityId = player.getEntityId();
|
entityEffectPacket.entityId = player.getEntityId();
|
||||||
entityEffectPacket.effectId = 4;
|
entityEffectPacket.effect = PotionType.AWKWARD;
|
||||||
entityEffectPacket.amplifier = -1;
|
entityEffectPacket.amplifier = -1;
|
||||||
entityEffectPacket.duration = 0;
|
entityEffectPacket.duration = 0;
|
||||||
entityEffectPacket.flags = 0;
|
entityEffectPacket.flags = 0;
|
||||||
|
|
|
@ -11,6 +11,16 @@ public class BlockBreakAnimationPacket implements ServerPacket {
|
||||||
public BlockPosition blockPosition;
|
public BlockPosition blockPosition;
|
||||||
public byte destroyStage;
|
public byte destroyStage;
|
||||||
|
|
||||||
|
public BlockBreakAnimationPacket() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockBreakAnimationPacket(int entityId, BlockPosition blockPosition, byte destroyStage) {
|
||||||
|
this.entityId = entityId;
|
||||||
|
this.blockPosition = blockPosition;
|
||||||
|
this.destroyStage = destroyStage;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(BinaryWriter writer) {
|
public void write(BinaryWriter writer) {
|
||||||
writer.writeVarInt(entityId);
|
writer.writeVarInt(entityId);
|
||||||
|
|
|
@ -2,12 +2,13 @@ package net.minestom.server.network.packet.server.play;
|
||||||
|
|
||||||
import net.minestom.server.network.packet.server.ServerPacket;
|
import net.minestom.server.network.packet.server.ServerPacket;
|
||||||
import net.minestom.server.network.packet.server.ServerPacketIdentifier;
|
import net.minestom.server.network.packet.server.ServerPacketIdentifier;
|
||||||
|
import net.minestom.server.potion.PotionType;
|
||||||
import net.minestom.server.utils.binary.BinaryWriter;
|
import net.minestom.server.utils.binary.BinaryWriter;
|
||||||
|
|
||||||
public class EntityEffectPacket implements ServerPacket {
|
public class EntityEffectPacket implements ServerPacket {
|
||||||
|
|
||||||
public int entityId;
|
public int entityId;
|
||||||
public byte effectId;
|
public PotionType effect;
|
||||||
public byte amplifier;
|
public byte amplifier;
|
||||||
public int duration;
|
public int duration;
|
||||||
public byte flags;
|
public byte flags;
|
||||||
|
@ -15,7 +16,7 @@ public class EntityEffectPacket implements ServerPacket {
|
||||||
@Override
|
@Override
|
||||||
public void write(BinaryWriter writer) {
|
public void write(BinaryWriter writer) {
|
||||||
writer.writeVarInt(entityId);
|
writer.writeVarInt(entityId);
|
||||||
writer.writeByte(effectId);
|
writer.writeByte((byte) effect.getId());
|
||||||
writer.writeByte(amplifier);
|
writer.writeByte(amplifier);
|
||||||
writer.writeVarInt(duration);
|
writer.writeVarInt(duration);
|
||||||
writer.writeByte(flags);
|
writer.writeByte(flags);
|
||||||
|
|
|
@ -2,17 +2,18 @@ package net.minestom.server.network.packet.server.play;
|
||||||
|
|
||||||
import net.minestom.server.network.packet.server.ServerPacket;
|
import net.minestom.server.network.packet.server.ServerPacket;
|
||||||
import net.minestom.server.network.packet.server.ServerPacketIdentifier;
|
import net.minestom.server.network.packet.server.ServerPacketIdentifier;
|
||||||
|
import net.minestom.server.potion.PotionType;
|
||||||
import net.minestom.server.utils.binary.BinaryWriter;
|
import net.minestom.server.utils.binary.BinaryWriter;
|
||||||
|
|
||||||
public class RemoveEntityEffectPacket implements ServerPacket {
|
public class RemoveEntityEffectPacket implements ServerPacket {
|
||||||
|
|
||||||
public int entityId;
|
public int entityId;
|
||||||
public byte effectId;
|
public PotionType effect;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(BinaryWriter writer) {
|
public void write(BinaryWriter writer) {
|
||||||
writer.writeVarInt(entityId);
|
writer.writeVarInt(entityId);
|
||||||
writer.writeByte(effectId);
|
writer.writeByte((byte) effect.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -46,7 +46,6 @@ public class ChunkReader {
|
||||||
Data data = null;
|
Data data = null;
|
||||||
{
|
{
|
||||||
final boolean hasData = binaryReader.readBoolean();
|
final boolean hasData = binaryReader.readBoolean();
|
||||||
|
|
||||||
// Data deserializer
|
// Data deserializer
|
||||||
if (hasData) {
|
if (hasData) {
|
||||||
data = DataReader.readData(binaryReader);
|
data = DataReader.readData(binaryReader);
|
||||||
|
|
|
@ -33,19 +33,23 @@ public class DataReader {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
final String className;
|
// Get the class type
|
||||||
|
final Class type;
|
||||||
{
|
{
|
||||||
final byte[] typeCache = reader.readBytes(typeLength);
|
final byte[] typeCache = reader.readBytes(typeLength);
|
||||||
|
|
||||||
className = new String(typeCache);
|
final String className = new String(typeCache);
|
||||||
|
|
||||||
|
type = Class.forName(className);
|
||||||
}
|
}
|
||||||
|
|
||||||
final Class type = Class.forName(className);
|
// Get the key
|
||||||
|
|
||||||
final String name = reader.readSizedString();
|
final String name = reader.readSizedString();
|
||||||
|
|
||||||
|
// Get the data
|
||||||
final Object value = DATA_MANAGER.getDataType(type).decode(reader);
|
final Object value = DATA_MANAGER.getDataType(type).decode(reader);
|
||||||
|
|
||||||
|
// Set the data
|
||||||
data.set(name, value, type);
|
data.set(name, value, type);
|
||||||
}
|
}
|
||||||
} catch (ClassNotFoundException e) {
|
} catch (ClassNotFoundException e) {
|
||||||
|
|
|
@ -7,7 +7,7 @@ import java.util.Objects;
|
||||||
/**
|
/**
|
||||||
* Represents a namespaced ID
|
* Represents a namespaced ID
|
||||||
* https://minecraft.gamepedia.com/Namespaced_ID
|
* https://minecraft.gamepedia.com/Namespaced_ID
|
||||||
*
|
* <p>
|
||||||
* TODO: Implement validity conditions
|
* TODO: Implement validity conditions
|
||||||
*/
|
*/
|
||||||
public class NamespaceID implements CharSequence {
|
public class NamespaceID implements CharSequence {
|
||||||
|
@ -20,12 +20,13 @@ public class NamespaceID implements CharSequence {
|
||||||
/**
|
/**
|
||||||
* Extracts the domain from the namespace ID. "minecraft:stone" would return "minecraft".
|
* Extracts the domain from the namespace ID. "minecraft:stone" would return "minecraft".
|
||||||
* If no ':' character is found, "minecraft" is returned.
|
* If no ':' character is found, "minecraft" is returned.
|
||||||
|
*
|
||||||
* @param namespaceID
|
* @param namespaceID
|
||||||
* @return the domain of the namespace ID
|
* @return the domain of the namespace ID
|
||||||
*/
|
*/
|
||||||
public static String getDomain(String namespaceID) {
|
public static String getDomain(String namespaceID) {
|
||||||
int index = namespaceID.indexOf(':');
|
final int index = namespaceID.indexOf(':');
|
||||||
if(index < 0)
|
if (index < 0)
|
||||||
return "minecraft";
|
return "minecraft";
|
||||||
return namespaceID.substring(0, index);
|
return namespaceID.substring(0, index);
|
||||||
}
|
}
|
||||||
|
@ -33,14 +34,15 @@ public class NamespaceID implements CharSequence {
|
||||||
/**
|
/**
|
||||||
* Extracts the path from the namespace ID. "minecraft:blocks/stone" would return "blocks/stone".
|
* Extracts the path from the namespace ID. "minecraft:blocks/stone" would return "blocks/stone".
|
||||||
* If no ':' character is found, the <pre>namespaceID</pre> is returned.
|
* If no ':' character is found, the <pre>namespaceID</pre> is returned.
|
||||||
|
*
|
||||||
* @param namespaceID
|
* @param namespaceID
|
||||||
* @return the path of the namespace ID
|
* @return the path of the namespace ID
|
||||||
*/
|
*/
|
||||||
public static String getPath(String namespaceID) {
|
public static String getPath(String namespaceID) {
|
||||||
int index = namespaceID.indexOf(':');
|
final int index = namespaceID.indexOf(':');
|
||||||
if(index < 0)
|
if (index < 0)
|
||||||
return namespaceID;
|
return namespaceID;
|
||||||
return namespaceID.substring(index+1);
|
return namespaceID.substring(index + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int hash(String domain, String path) {
|
static int hash(String domain, String path) {
|
||||||
|
@ -48,7 +50,7 @@ public class NamespaceID implements CharSequence {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static NamespaceID from(String domain, String path) {
|
public static NamespaceID from(String domain, String path) {
|
||||||
int hash = hash(domain, path);
|
final int hash = hash(domain, path);
|
||||||
return cache.computeIfAbsent(hash, _unused -> new NamespaceID(domain, path));
|
return cache.computeIfAbsent(hash, _unused -> new NamespaceID(domain, path));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,13 +59,13 @@ public class NamespaceID implements CharSequence {
|
||||||
}
|
}
|
||||||
|
|
||||||
private NamespaceID(String path) {
|
private NamespaceID(String path) {
|
||||||
int index = path.indexOf(':');
|
final int index = path.indexOf(':');
|
||||||
if(index < 0) {
|
if (index < 0) {
|
||||||
this.domain = "minecraft";
|
this.domain = "minecraft";
|
||||||
this.path = path;
|
this.path = path;
|
||||||
} else {
|
} else {
|
||||||
this.domain = path.substring(0, index);
|
this.domain = path.substring(0, index);
|
||||||
this.path = path.substring(index+1);
|
this.path = path.substring(index + 1);
|
||||||
}
|
}
|
||||||
this.full = toString();
|
this.full = toString();
|
||||||
}
|
}
|
||||||
|
@ -113,7 +115,7 @@ public class NamespaceID implements CharSequence {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return domain+":"+path;
|
return domain + ":" + path;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,72 +11,76 @@ import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows servers to register custom dimensions. Also used during player joining to send the list of all existing dimensions.
|
* Allows servers to register custom dimensions. Also used during player joining to send the list of all existing dimensions.
|
||||||
*
|
* <p>
|
||||||
* Contains {@link Biome#PLAINS} by default but can be removed.
|
* Contains {@link Biome#PLAINS} by default but can be removed.
|
||||||
*/
|
*/
|
||||||
public class BiomeManager {
|
public class BiomeManager {
|
||||||
|
|
||||||
private final List<Biome> biomes = new LinkedList<>();
|
private final List<Biome> biomes = new LinkedList<>();
|
||||||
|
|
||||||
public BiomeManager() {
|
public BiomeManager() {
|
||||||
addBiome(Biome.PLAINS);
|
addBiome(Biome.PLAINS);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a new biome. This does NOT send the new list to players.
|
* Add a new biome. This does NOT send the new list to players.
|
||||||
* @param biome
|
*
|
||||||
*/
|
* @param biome the biome to add
|
||||||
public void addBiome(Biome biome) {
|
*/
|
||||||
biomes.add(biome);
|
public void addBiome(Biome biome) {
|
||||||
}
|
biomes.add(biome);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes a biome. This does NOT send the new list to players.
|
* Removes a biome. This does NOT send the new list to players.
|
||||||
* @param biome
|
*
|
||||||
* @return if the biome type was removed, false if it was not present before
|
* @param biome the biome to remove
|
||||||
*/
|
* @return true if the biome type was removed, false if it was not present before
|
||||||
public boolean removeBiome(Biome biome) {
|
*/
|
||||||
return biomes.remove(biome);
|
public boolean removeBiome(Biome biome) {
|
||||||
}
|
return biomes.remove(biome);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an immutable copy of the biomes already registered
|
* Returns an immutable copy of the biomes already registered
|
||||||
* @return
|
*
|
||||||
*/
|
* @return an immutable copy of the biomes already registered
|
||||||
public List<Biome> unmodifiableList() {
|
*/
|
||||||
return Collections.unmodifiableList(biomes);
|
public List<Biome> unmodifiableList() {
|
||||||
}
|
return Collections.unmodifiableList(biomes);
|
||||||
|
}
|
||||||
|
|
||||||
public Biome getById(int id) {
|
// TODO optimize for fast get
|
||||||
Biome biome = null;
|
public Biome getById(int id) {
|
||||||
for (final Biome biomeT : biomes) {
|
Biome biome = null;
|
||||||
if (biomeT.getId() == id) {
|
for (final Biome biomeT : biomes) {
|
||||||
biome = biomeT;
|
if (biomeT.getId() == id) {
|
||||||
break;
|
biome = biomeT;
|
||||||
}
|
break;
|
||||||
}
|
}
|
||||||
return biome;
|
}
|
||||||
}
|
return biome;
|
||||||
|
}
|
||||||
|
|
||||||
public Biome getByName(NamespaceID namespaceID) {
|
public Biome getByName(NamespaceID namespaceID) {
|
||||||
Biome biome = null;
|
Biome biome = null;
|
||||||
for (final Biome biomeT : biomes) {
|
for (final Biome biomeT : biomes) {
|
||||||
if (biomeT.getName().equals(namespaceID)) {
|
if (biomeT.getName().equals(namespaceID)) {
|
||||||
biome = biomeT;
|
biome = biomeT;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return biome;
|
return biome;
|
||||||
}
|
}
|
||||||
|
|
||||||
public NBTCompound toNBT() {
|
public NBTCompound toNBT() {
|
||||||
NBTCompound biomes = new NBTCompound();
|
NBTCompound biomes = new NBTCompound();
|
||||||
biomes.setString("type", "minecraft:worldgen/biome");
|
biomes.setString("type", "minecraft:worldgen/biome");
|
||||||
NBTList<NBTCompound> biomesList = new NBTList<>(NBTTypes.TAG_Compound);
|
NBTList<NBTCompound> biomesList = new NBTList<>(NBTTypes.TAG_Compound);
|
||||||
for (Biome biome : this.biomes) {
|
for (Biome biome : this.biomes) {
|
||||||
biomesList.add(biome.toNbt());
|
biomesList.add(biome.toNbt());
|
||||||
}
|
}
|
||||||
biomes.set("value", biomesList);
|
biomes.set("value", biomesList);
|
||||||
return biomes;
|
return biomes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue