mirror of
https://github.com/Minestom/Minestom.git
synced 2025-01-01 05:58:00 +01:00
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.ping.ResponseDataConsumer;
|
||||
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.MathUtils;
|
||||
import net.minestom.server.utils.Position;
|
||||
import net.minestom.server.utils.Vector;
|
||||
import net.minestom.server.utils.time.TimeUnit;
|
||||
import net.minestom.server.world.DimensionType;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
@ -48,11 +47,11 @@ public class PlayerInit {
|
||||
private static volatile Inventory inventory;
|
||||
|
||||
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();
|
||||
NoiseTestGenerator noiseTestGenerator = new NoiseTestGenerator();
|
||||
instanceContainer = MinecraftServer.getInstanceManager().createInstanceContainer(storageFolder);
|
||||
//instanceContainer = MinecraftServer.getInstanceManager().createInstanceContainer(DimensionType.OVERWORLD);
|
||||
//instanceContainer = MinecraftServer.getInstanceManager().createInstanceContainer(storageFolder);
|
||||
instanceContainer = MinecraftServer.getInstanceManager().createInstanceContainer(DimensionType.OVERWORLD);
|
||||
instanceContainer.enableAutoChunkLoad(true);
|
||||
//instanceContainer.setChunkDecider((x,y) -> (pos) -> pos.getY()>40?(short)0:(short)1);
|
||||
instanceContainer.setChunkGenerator(noiseTestGenerator);
|
||||
@ -272,7 +271,7 @@ public class PlayerInit {
|
||||
});
|
||||
|
||||
player.addEventCallback(PlayerSpawnEvent.class, event -> {
|
||||
player.setGameMode(GameMode.CREATIVE);
|
||||
player.setGameMode(GameMode.SURVIVAL);
|
||||
player.teleport(new Position(0, 41f, 0));
|
||||
|
||||
//player.setHeldItemSlot((byte) 5);
|
||||
@ -390,7 +389,7 @@ public class PlayerInit {
|
||||
|
||||
// Unload the chunk (save memory) if it has no remaining viewer
|
||||
if (chunk.getViewers().isEmpty()) {
|
||||
player.getInstance().unloadChunk(chunk);
|
||||
//player.getInstance().unloadChunk(chunk);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -20,8 +20,8 @@ public class BurningTorchBlock extends CustomBlock {
|
||||
|
||||
@Override
|
||||
public void handleContact(Instance instance, BlockPosition position, Entity touching) {
|
||||
System.out.println("touching "+touching);
|
||||
if(touching instanceof LivingEntity) {
|
||||
System.out.println("touching " + touching);
|
||||
if (touching instanceof LivingEntity) {
|
||||
((LivingEntity) touching).damage(DamageType.GRAVITY, 0.1f);
|
||||
}
|
||||
}
|
||||
@ -45,9 +45,4 @@ public class BurningTorchBlock extends CustomBlock {
|
||||
public short getCustomBlockId() {
|
||||
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.utils.BlockPosition;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class StoneBlock extends CustomBlock {
|
||||
|
||||
public StoneBlock() {
|
||||
@ -39,8 +41,18 @@ public class StoneBlock extends CustomBlock {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBreakDelay(Player player, BlockPosition position) {
|
||||
return 750;
|
||||
public int getBreakDelay(Player player, BlockPosition position, byte stage, Set<Player> breakers) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enableCustomBreakDelay() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enableMultiPlayerBreaking() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -9,6 +9,8 @@ import net.minestom.server.utils.BlockPosition;
|
||||
import net.minestom.server.utils.time.TimeUnit;
|
||||
import net.minestom.server.utils.time.UpdateOption;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class UpdatableBlockDemo extends CustomBlock {
|
||||
|
||||
private static final UpdateOption UPDATE_OPTION = new UpdateOption(20, TimeUnit.TICK);
|
||||
@ -43,8 +45,13 @@ public class UpdatableBlockDemo extends CustomBlock {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBreakDelay(Player player, BlockPosition position) {
|
||||
return 500;
|
||||
public int getBreakDelay(Player player, BlockPosition position, byte stage, Set<Player> breakers) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enableCustomBreakDelay() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -67,7 +67,9 @@ public class SimpleCommand implements CommandProcessor {
|
||||
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;
|
||||
}
|
||||
|
@ -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.player.PlayerConnection;
|
||||
import net.minestom.server.permission.Permission;
|
||||
import net.minestom.server.potion.PotionType;
|
||||
import net.minestom.server.recipe.Recipe;
|
||||
import net.minestom.server.recipe.RecipeManager;
|
||||
import net.minestom.server.resourcepack.ResourcePack;
|
||||
@ -102,9 +103,10 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
// CustomBlock break delay
|
||||
private CustomBlock targetCustomBlock;
|
||||
private BlockPosition targetBlockPosition;
|
||||
private long targetBlockTime;
|
||||
private byte targetLastStage;
|
||||
private int blockBreakTime;
|
||||
private long targetBreakDelay; // The last break delay requested
|
||||
private long targetBlockLastStageChangeTime; // Time at which the block stage last changed
|
||||
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;
|
||||
|
||||
@ -159,6 +161,9 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
this.levelType = LevelType.FLAT;
|
||||
refreshPosition(0, 0, 0);
|
||||
|
||||
// Used to cache the breaker for single custom block breaking
|
||||
this.targetBreakers.add(this);
|
||||
|
||||
// FakePlayer init its connection there
|
||||
playerConnectionInit();
|
||||
|
||||
@ -300,17 +305,39 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
|
||||
// Target block stage
|
||||
if (targetCustomBlock != null) {
|
||||
final byte animationCount = 10;
|
||||
final long since = time - targetBlockTime;
|
||||
byte stage = (byte) (since / (blockBreakTime / animationCount));
|
||||
stage = MathUtils.setBetween(stage, (byte) -1, animationCount);
|
||||
if (stage != targetLastStage) {
|
||||
sendBlockBreakAnimation(targetBlockPosition, stage);
|
||||
}
|
||||
this.targetLastStage = stage;
|
||||
if (stage > 9) {
|
||||
instance.breakBlock(this, targetBlockPosition);
|
||||
resetTargetBlock();
|
||||
final boolean processStage = (time - targetBlockLastStageChangeTime) >= targetBreakDelay;
|
||||
if (processStage) {
|
||||
// Should increment the target block stage
|
||||
if (targetCustomBlock.enableMultiPlayerBreaking()) {
|
||||
// Let the custom block object manages the breaking
|
||||
final boolean canContinue = this.targetCustomBlock.processStage(instance, targetBlockPosition, this);
|
||||
if (canContinue) {
|
||||
final Set<Player> breakers = targetCustomBlock.getBreakers(instance, targetBlockPosition);
|
||||
refreshBreakDelay(breakers);
|
||||
this.targetBlockLastStageChangeTime = time;
|
||||
} 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
public void sendMessage(String message) {
|
||||
sendMessage(ColoredText.of(message));
|
||||
@ -1690,7 +1699,7 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
* Change the player ability "Creative Mode"
|
||||
* <a href="https://wiki.vg/Protocol#Player_Abilities_.28clientbound.29">see</a>
|
||||
* <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
|
||||
*/
|
||||
@ -1870,13 +1879,25 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
*
|
||||
* @param targetCustomBlock the custom block to dig
|
||||
* @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.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
|
||||
*/
|
||||
public void resetTargetBlock() {
|
||||
if (targetBlockPosition != null) {
|
||||
sendBlockBreakAnimation(targetBlockPosition, (byte) -1); // Clear the break animation
|
||||
if (targetCustomBlock != null) {
|
||||
targetCustomBlock.stopDigging(instance, targetBlockPosition, this);
|
||||
this.targetCustomBlock = null;
|
||||
this.targetBlockPosition = null;
|
||||
this.targetBlockTime = 0;
|
||||
this.targetBreakDelay = 0;
|
||||
this.targetBlockLastStageChangeTime = 0;
|
||||
this.targetStage = 0;
|
||||
|
||||
// Remove effect
|
||||
RemoveEntityEffectPacket removeEntityEffectPacket = new RemoveEntityEffectPacket();
|
||||
removeEntityEffectPacket.entityId = getEntityId();
|
||||
removeEntityEffectPacket.effectId = 4;
|
||||
getPlayerConnection().sendPacket(removeEntityEffectPacket);
|
||||
removeEntityEffectPacket.effect = PotionType.AWKWARD;
|
||||
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
|
||||
*/
|
||||
@ -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() {
|
||||
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
|
||||
* (create player, spawn position, velocity, metadata, equipments, passengers, team)
|
||||
* <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
|
||||
*/
|
||||
|
@ -23,6 +23,14 @@ public class PlayerVehicleInformation {
|
||||
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) {
|
||||
this.sideways = sideways;
|
||||
this.forward = forward;
|
||||
|
@ -116,8 +116,17 @@ public class InstanceContainer extends Instance {
|
||||
setAlreadyChanged(blockPosition, blockStateId);
|
||||
final int index = ChunkUtils.getBlockIndex(x, y, z);
|
||||
|
||||
// Call the destroy listener if previous block was a custom block
|
||||
callBlockDestroy(chunk, index, blockPosition);
|
||||
final CustomBlock previousBlock = chunk.getCustomBlock(index);
|
||||
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
|
||||
blockStateId = executeBlockPlacementRule(blockStateId, blockPosition);
|
||||
|
||||
@ -170,13 +179,10 @@ public class InstanceContainer extends Instance {
|
||||
}
|
||||
}
|
||||
|
||||
private void callBlockDestroy(Chunk chunk, int index, BlockPosition blockPosition) {
|
||||
final CustomBlock previousBlock = chunk.getCustomBlock(index);
|
||||
if (previousBlock != null) {
|
||||
final Data previousData = chunk.getData(index);
|
||||
previousBlock.onDestroy(this, blockPosition, previousData);
|
||||
chunk.UNSAFE_removeCustomBlock(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ());
|
||||
}
|
||||
private void callBlockDestroy(Chunk chunk, int index, CustomBlock previousBlock, BlockPosition blockPosition) {
|
||||
final Data previousData = chunk.getData(index);
|
||||
previousBlock.onDestroy(this, blockPosition, previousData);
|
||||
chunk.UNSAFE_removeCustomBlock(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ());
|
||||
}
|
||||
|
||||
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!");
|
||||
return;
|
||||
}
|
||||
|
||||
final int chunkX = chunk.getChunkX();
|
||||
final int chunkZ = chunk.getChunkZ();
|
||||
|
||||
final String key = getChunkKey(chunkX, chunkZ);
|
||||
final byte[] data = chunk.getSerializedData();
|
||||
if (data == null)
|
||||
if (data == null) {
|
||||
if (callback != null)
|
||||
callback.run();
|
||||
return;
|
||||
}
|
||||
|
||||
storageFolder.set(key, data);
|
||||
|
||||
if (callback != null)
|
||||
|
@ -1,16 +1,28 @@
|
||||
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.entity.Entity;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.gamedata.loottables.LootTable;
|
||||
import net.minestom.server.gamedata.loottables.LootTableManager;
|
||||
import net.minestom.server.instance.BlockModifier;
|
||||
import net.minestom.server.instance.Chunk;
|
||||
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.time.UpdateOption;
|
||||
import net.minestom.server.utils.validate.Check;
|
||||
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.
|
||||
* <p>
|
||||
@ -20,10 +32,17 @@ import org.jglrxavpok.hephaistos.nbt.NBTCompound;
|
||||
*/
|
||||
public abstract class CustomBlock {
|
||||
|
||||
public static final byte MAX_STAGE = 10;
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* - option to set the global as "global breaking" meaning that multiple players mining the same block will break it faster (accumulation)
|
||||
* Instance -> break data
|
||||
* 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 String identifier;
|
||||
@ -106,17 +125,45 @@ public abstract class CustomBlock {
|
||||
public abstract short getCustomBlockId();
|
||||
|
||||
/**
|
||||
* Called at digging start to check for custom breaking time
|
||||
* Can be set to < 0 to be cancelled, in this case vanilla time will be used
|
||||
* Called when the player requests the next stage break delay
|
||||
*
|
||||
* @param player the player who is trying to break the block
|
||||
* @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() {
|
||||
final UpdateOption updateOption = getUpdateOption();
|
||||
@ -239,4 +286,189 @@ public abstract class CustomBlock {
|
||||
public LootTable getLootTable(LootTableManager tableManager) {
|
||||
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.server.play.AcknowledgePlayerDiggingPacket;
|
||||
import net.minestom.server.network.packet.server.play.EntityEffectPacket;
|
||||
import net.minestom.server.potion.PotionType;
|
||||
import net.minestom.server.utils.BlockPosition;
|
||||
|
||||
public class PlayerDiggingListener {
|
||||
@ -43,17 +44,17 @@ public class PlayerDiggingListener {
|
||||
} else {
|
||||
final CustomBlock customBlock = instance.getCustomBlock(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ());
|
||||
if (customBlock != null) {
|
||||
int breakTime = customBlock.getBreakDelay(player, blockPosition);
|
||||
|
||||
// Custom block has a custom break time, allow for digging event
|
||||
PlayerStartDiggingEvent playerStartDiggingEvent = new PlayerStartDiggingEvent(player, blockPosition, customBlock);
|
||||
player.callEvent(PlayerStartDiggingEvent.class, playerStartDiggingEvent);
|
||||
if (!playerStartDiggingEvent.isCancelled()) {
|
||||
|
||||
// Start digging the block
|
||||
if (breakTime >= 0) {
|
||||
player.setTargetBlock(customBlock, blockPosition, breakTime);
|
||||
if (customBlock.enableCustomBreakDelay()) {
|
||||
customBlock.startDigging(instance, blockPosition, player);
|
||||
addEffect(player);
|
||||
}
|
||||
|
||||
sendAcknowledgePacket(player, blockPosition, customBlock.getBlockStateId(),
|
||||
ClientPlayerDiggingPacket.Status.STARTED_DIGGING, true);
|
||||
} else {
|
||||
@ -140,7 +141,7 @@ public class PlayerDiggingListener {
|
||||
private static void addEffect(Player player) {
|
||||
EntityEffectPacket entityEffectPacket = new EntityEffectPacket();
|
||||
entityEffectPacket.entityId = player.getEntityId();
|
||||
entityEffectPacket.effectId = 4;
|
||||
entityEffectPacket.effect = PotionType.AWKWARD;
|
||||
entityEffectPacket.amplifier = -1;
|
||||
entityEffectPacket.duration = 0;
|
||||
entityEffectPacket.flags = 0;
|
||||
|
@ -11,6 +11,16 @@ public class BlockBreakAnimationPacket implements ServerPacket {
|
||||
public BlockPosition blockPosition;
|
||||
public byte destroyStage;
|
||||
|
||||
public BlockBreakAnimationPacket() {
|
||||
|
||||
}
|
||||
|
||||
public BlockBreakAnimationPacket(int entityId, BlockPosition blockPosition, byte destroyStage) {
|
||||
this.entityId = entityId;
|
||||
this.blockPosition = blockPosition;
|
||||
this.destroyStage = destroyStage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(BinaryWriter writer) {
|
||||
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.ServerPacketIdentifier;
|
||||
import net.minestom.server.potion.PotionType;
|
||||
import net.minestom.server.utils.binary.BinaryWriter;
|
||||
|
||||
public class EntityEffectPacket implements ServerPacket {
|
||||
|
||||
public int entityId;
|
||||
public byte effectId;
|
||||
public PotionType effect;
|
||||
public byte amplifier;
|
||||
public int duration;
|
||||
public byte flags;
|
||||
@ -15,7 +16,7 @@ public class EntityEffectPacket implements ServerPacket {
|
||||
@Override
|
||||
public void write(BinaryWriter writer) {
|
||||
writer.writeVarInt(entityId);
|
||||
writer.writeByte(effectId);
|
||||
writer.writeByte((byte) effect.getId());
|
||||
writer.writeByte(amplifier);
|
||||
writer.writeVarInt(duration);
|
||||
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.ServerPacketIdentifier;
|
||||
import net.minestom.server.potion.PotionType;
|
||||
import net.minestom.server.utils.binary.BinaryWriter;
|
||||
|
||||
public class RemoveEntityEffectPacket implements ServerPacket {
|
||||
|
||||
public int entityId;
|
||||
public byte effectId;
|
||||
public PotionType effect;
|
||||
|
||||
@Override
|
||||
public void write(BinaryWriter writer) {
|
||||
writer.writeVarInt(entityId);
|
||||
writer.writeByte(effectId);
|
||||
writer.writeByte((byte) effect.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -46,7 +46,6 @@ public class ChunkReader {
|
||||
Data data = null;
|
||||
{
|
||||
final boolean hasData = binaryReader.readBoolean();
|
||||
|
||||
// Data deserializer
|
||||
if (hasData) {
|
||||
data = DataReader.readData(binaryReader);
|
||||
|
@ -33,19 +33,23 @@ public class DataReader {
|
||||
break;
|
||||
}
|
||||
|
||||
final String className;
|
||||
// Get the class type
|
||||
final Class type;
|
||||
{
|
||||
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();
|
||||
|
||||
// Get the data
|
||||
final Object value = DATA_MANAGER.getDataType(type).decode(reader);
|
||||
|
||||
// Set the data
|
||||
data.set(name, value, type);
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
|
@ -7,7 +7,7 @@ import java.util.Objects;
|
||||
/**
|
||||
* Represents a namespaced ID
|
||||
* https://minecraft.gamepedia.com/Namespaced_ID
|
||||
*
|
||||
* <p>
|
||||
* TODO: Implement validity conditions
|
||||
*/
|
||||
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".
|
||||
* If no ':' character is found, "minecraft" is returned.
|
||||
*
|
||||
* @param namespaceID
|
||||
* @return the domain of the namespace ID
|
||||
*/
|
||||
public static String getDomain(String namespaceID) {
|
||||
int index = namespaceID.indexOf(':');
|
||||
if(index < 0)
|
||||
final int index = namespaceID.indexOf(':');
|
||||
if (index < 0)
|
||||
return "minecraft";
|
||||
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".
|
||||
* If no ':' character is found, the <pre>namespaceID</pre> is returned.
|
||||
*
|
||||
* @param namespaceID
|
||||
* @return the path of the namespace ID
|
||||
*/
|
||||
public static String getPath(String namespaceID) {
|
||||
int index = namespaceID.indexOf(':');
|
||||
if(index < 0)
|
||||
final int index = namespaceID.indexOf(':');
|
||||
if (index < 0)
|
||||
return namespaceID;
|
||||
return namespaceID.substring(index+1);
|
||||
return namespaceID.substring(index + 1);
|
||||
}
|
||||
|
||||
static int hash(String domain, String path) {
|
||||
@ -48,7 +50,7 @@ public class NamespaceID implements CharSequence {
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
@ -57,13 +59,13 @@ public class NamespaceID implements CharSequence {
|
||||
}
|
||||
|
||||
private NamespaceID(String path) {
|
||||
int index = path.indexOf(':');
|
||||
if(index < 0) {
|
||||
final int index = path.indexOf(':');
|
||||
if (index < 0) {
|
||||
this.domain = "minecraft";
|
||||
this.path = path;
|
||||
} else {
|
||||
this.domain = path.substring(0, index);
|
||||
this.path = path.substring(index+1);
|
||||
this.path = path.substring(index + 1);
|
||||
}
|
||||
this.full = toString();
|
||||
}
|
||||
@ -113,7 +115,7 @@ public class NamespaceID implements CharSequence {
|
||||
|
||||
@Override
|
||||
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.
|
||||
*
|
||||
* <p>
|
||||
* Contains {@link Biome#PLAINS} by default but can be removed.
|
||||
*/
|
||||
public class BiomeManager {
|
||||
|
||||
private final List<Biome> biomes = new LinkedList<>();
|
||||
private final List<Biome> biomes = new LinkedList<>();
|
||||
|
||||
public BiomeManager() {
|
||||
addBiome(Biome.PLAINS);
|
||||
}
|
||||
public BiomeManager() {
|
||||
addBiome(Biome.PLAINS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new biome. This does NOT send the new list to players.
|
||||
* @param biome
|
||||
*/
|
||||
public void addBiome(Biome biome) {
|
||||
biomes.add(biome);
|
||||
}
|
||||
/**
|
||||
* Add a new biome. This does NOT send the new list to players.
|
||||
*
|
||||
* @param biome the biome to add
|
||||
*/
|
||||
public void addBiome(Biome biome) {
|
||||
biomes.add(biome);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public boolean removeBiome(Biome biome) {
|
||||
return biomes.remove(biome);
|
||||
}
|
||||
/**
|
||||
* Removes a biome. This does NOT send the new list to players.
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an immutable copy of the biomes already registered
|
||||
* @return
|
||||
*/
|
||||
public List<Biome> unmodifiableList() {
|
||||
return Collections.unmodifiableList(biomes);
|
||||
}
|
||||
/**
|
||||
* Returns an immutable copy of the biomes already registered
|
||||
*
|
||||
* @return an immutable copy of the biomes already registered
|
||||
*/
|
||||
public List<Biome> unmodifiableList() {
|
||||
return Collections.unmodifiableList(biomes);
|
||||
}
|
||||
|
||||
public Biome getById(int id) {
|
||||
Biome biome = null;
|
||||
for (final Biome biomeT : biomes) {
|
||||
if (biomeT.getId() == id) {
|
||||
biome = biomeT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return biome;
|
||||
}
|
||||
// TODO optimize for fast get
|
||||
public Biome getById(int id) {
|
||||
Biome biome = null;
|
||||
for (final Biome biomeT : biomes) {
|
||||
if (biomeT.getId() == id) {
|
||||
biome = biomeT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return biome;
|
||||
}
|
||||
|
||||
public Biome getByName(NamespaceID namespaceID) {
|
||||
Biome biome = null;
|
||||
for (final Biome biomeT : biomes) {
|
||||
if (biomeT.getName().equals(namespaceID)) {
|
||||
biome = biomeT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return biome;
|
||||
}
|
||||
public Biome getByName(NamespaceID namespaceID) {
|
||||
Biome biome = null;
|
||||
for (final Biome biomeT : biomes) {
|
||||
if (biomeT.getName().equals(namespaceID)) {
|
||||
biome = biomeT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return biome;
|
||||
}
|
||||
|
||||
public NBTCompound toNBT() {
|
||||
NBTCompound biomes = new NBTCompound();
|
||||
biomes.setString("type", "minecraft:worldgen/biome");
|
||||
NBTList<NBTCompound> biomesList = new NBTList<>(NBTTypes.TAG_Compound);
|
||||
for (Biome biome : this.biomes) {
|
||||
biomesList.add(biome.toNbt());
|
||||
}
|
||||
biomes.set("value", biomesList);
|
||||
return biomes;
|
||||
}
|
||||
public NBTCompound toNBT() {
|
||||
NBTCompound biomes = new NBTCompound();
|
||||
biomes.setString("type", "minecraft:worldgen/biome");
|
||||
NBTList<NBTCompound> biomesList = new NBTList<>(NBTTypes.TAG_Compound);
|
||||
for (Biome biome : this.biomes) {
|
||||
biomesList.add(biome.toNbt());
|
||||
}
|
||||
biomes.set("value", biomesList);
|
||||
return biomes;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user