package net.minestom.server.instance; import io.netty.buffer.ByteBuf; import net.minestom.server.MinecraftServer; import net.minestom.server.data.Data; import net.minestom.server.data.DataContainer; import net.minestom.server.entity.*; import net.minestom.server.instance.batch.BlockBatch; import net.minestom.server.instance.batch.ChunkBatch; import net.minestom.server.instance.block.Block; import net.minestom.server.instance.block.BlockManager; import net.minestom.server.instance.block.CustomBlock; import net.minestom.server.network.PacketWriterUtils; import net.minestom.server.network.packet.server.play.BlockActionPacket; import net.minestom.server.network.packet.server.play.ChunkDataPacket; import net.minestom.server.utils.BlockPosition; import net.minestom.server.utils.ChunkUtils; import net.minestom.server.utils.Position; import java.io.File; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArraySet; import java.util.function.Consumer; public abstract class Instance implements BlockModifier, DataContainer { protected static final ChunkLoaderIO CHUNK_LOADER_IO = new ChunkLoaderIO(); protected static final BlockManager BLOCK_MANAGER = MinecraftServer.getBlockManager(); // Entities present in this instance protected Set players = new CopyOnWriteArraySet<>(); protected Set creatures = new CopyOnWriteArraySet<>(); protected Set objectEntities = new CopyOnWriteArraySet<>(); protected Set experienceOrbs = new CopyOnWriteArraySet<>(); // Entities per chunk protected Map> chunkEntities = new ConcurrentHashMap<>(); private UUID uniqueId; private Data data; protected Instance(UUID uniqueId) { this.uniqueId = uniqueId; } public abstract void refreshBlockId(int x, int y, int z, short blockId); // Used to call BlockBreakEvent and sending particle packet if true public abstract void breakBlock(Player player, BlockPosition blockPosition); // Force the generation of the chunk, even if no file and ChunkGenerator are defined public abstract void loadChunk(int chunkX, int chunkZ, Consumer callback); // Load only if auto chunk load is enabled public abstract void loadOptionalChunk(int chunkX, int chunkZ, Consumer callback); public abstract void unloadChunk(int chunkX, int chunkZ); public abstract Chunk getChunk(int chunkX, int chunkZ); public abstract void saveChunkToFolder(Chunk chunk, Runnable callback); public abstract void saveChunksToFolder(Runnable callback); public abstract BlockBatch createBlockBatch(); public abstract ChunkBatch createChunkBatch(Chunk chunk); public abstract void setChunkGenerator(ChunkGenerator chunkGenerator); public abstract Collection getChunks(); public abstract File getFolder(); public abstract void setFolder(File folder); public abstract void sendChunkUpdate(Player player, Chunk chunk); protected abstract void retrieveChunk(int chunkX, int chunkZ, Consumer callback); public abstract void createChunk(int chunkX, int chunkZ, Consumer callback); public abstract void sendChunks(Player player); public abstract void sendChunk(Player player, Chunk chunk); public abstract void enableAutoChunkLoad(boolean enable); public abstract boolean hasEnabledAutoChunkLoad(); // protected void sendChunkUpdate(Collection players, Chunk chunk) { ByteBuf chunkData = chunk.getFullDataPacket(); players.forEach(player -> { player.getPlayerConnection().sendPacket(chunkData); }); } protected void sendChunkSectionUpdate(Chunk chunk, int section, Collection players) { PacketWriterUtils.writeAndSend(players, getChunkSectionUpdatePacket(chunk, section)); } public void sendChunkSectionUpdate(Chunk chunk, int section, Player player) { PacketWriterUtils.writeAndSend(player, getChunkSectionUpdatePacket(chunk, section)); } protected ChunkDataPacket getChunkSectionUpdatePacket(Chunk chunk, int section) { ChunkDataPacket chunkDataPacket = new ChunkDataPacket(); chunkDataPacket.fullChunk = false; chunkDataPacket.chunk = chunk; int[] sections = new int[16]; sections[section] = 1; chunkDataPacket.sections = sections; return chunkDataPacket; } // public Set getPlayers() { return Collections.unmodifiableSet(players); } public Set getCreatures() { return Collections.unmodifiableSet(creatures); } public Set getObjectEntities() { return Collections.unmodifiableSet(objectEntities); } public Set getExperienceOrbs() { return Collections.unmodifiableSet(experienceOrbs); } public Set getChunkEntities(Chunk chunk) { return Collections.unmodifiableSet(getEntitiesInChunk(ChunkUtils.getChunkIndex(chunk.getChunkX(), chunk.getChunkZ()))); } public void refreshBlockId(int x, int y, int z, Block block) { refreshBlockId(x, y, z, block.getBlockId()); } public void loadChunk(int chunkX, int chunkZ) { loadChunk(chunkX, chunkZ, null); } public void loadChunk(Position position, Consumer callback) { int chunkX = ChunkUtils.getChunkCoordinate((int) position.getX()); int chunkZ = ChunkUtils.getChunkCoordinate((int) position.getZ()); loadChunk(chunkX, chunkZ, callback); } public void loadOptionalChunk(Position position, Consumer callback) { int chunkX = ChunkUtils.getChunkCoordinate((int) position.getX()); int chunkZ = ChunkUtils.getChunkCoordinate((int) position.getZ()); loadOptionalChunk(chunkX, chunkZ, callback); } public short getBlockId(int x, int y, int z) { Chunk chunk = getChunkAt(x, z); return chunk.getBlockId((byte) (x % 16), (byte) y, (byte) (z % 16)); } public short getBlockId(float x, float y, float z) { return getBlockId(Math.round(x), Math.round(y), Math.round(z)); } public short getBlockId(BlockPosition blockPosition) { return getBlockId(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ()); } public CustomBlock getCustomBlock(int x, int y, int z) { Chunk chunk = getChunkAt(x, z); return chunk.getCustomBlock((byte) (x % 16), (byte) y, (byte) (z % 16)); } public CustomBlock getCustomBlock(BlockPosition blockPosition) { return getCustomBlock(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ()); } public void sendBlockAction(BlockPosition blockPosition, byte actionId, byte actionParam) { short blockId = getBlockId(blockPosition); BlockActionPacket blockActionPacket = new BlockActionPacket(); blockActionPacket.blockPosition = blockPosition; blockActionPacket.actionId = actionId; blockActionPacket.actionParam = actionParam; blockActionPacket.blockId = blockId; // FIXME: block id and not block state? Chunk chunk = getChunkAt(blockPosition); chunk.sendPacketToViewers(blockActionPacket); } public Data getBlockData(int x, int y, int z) { Chunk chunk = getChunkAt(x, z); return chunk.getData((byte) (x % 16), (byte) y, (byte) (z % 16)); } public Data getBlockData(BlockPosition blockPosition) { return getBlockData(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ()); } public Chunk getChunkAt(double x, double z) { int chunkX = ChunkUtils.getChunkCoordinate((int) x); int chunkZ = ChunkUtils.getChunkCoordinate((int) z); return getChunk(chunkX, chunkZ); } public boolean isChunkLoaded(int chunkX, int chunkZ) { return getChunk(chunkX, chunkZ) != null; } public Chunk getChunkAt(BlockPosition blockPosition) { return getChunkAt(blockPosition.getX(), blockPosition.getZ()); } public Chunk getChunkAt(Position position) { return getChunkAt(position.getX(), position.getZ()); } public void saveChunkToFolder(Chunk chunk) { saveChunkToFolder(chunk, null); } public void saveChunksToFolder() { saveChunksToFolder(null); } public UUID getUniqueId() { return uniqueId; } @Override public Data getData() { return data; } @Override public void setData(Data data) { this.data = data; } // UNSAFE METHODS (need most of time to be synchronized) public void addEntity(Entity entity) { Instance lastInstance = entity.getInstance(); if (lastInstance != null && lastInstance != this) { lastInstance.removeEntity(entity); // If entity is in another instance, remove it from there and add it to this } long[] visibleChunksEntity = ChunkUtils.getChunksInRange(entity.getPosition(), MinecraftServer.ENTITY_VIEW_DISTANCE); boolean isPlayer = entity instanceof Player; if (isPlayer) { sendChunks((Player) entity); } // Send all visible entities for (long chunkIndex : visibleChunksEntity) { getEntitiesInChunk(chunkIndex).forEach(ent -> { if (isPlayer) { ent.addViewer((Player) entity); } if (ent instanceof Player) { entity.addViewer((Player) ent); } }); } Chunk chunk = getChunkAt(entity.getPosition()); addEntityToChunk(entity, chunk); } public void removeEntity(Entity entity) { Instance entityInstance = entity.getInstance(); if (entityInstance == null || entityInstance != this) return; entity.getViewers().forEach(p -> entity.removeViewer(p)); // Remove this entity from players viewable list and send delete entities packet Chunk chunk = getChunkAt(entity.getPosition()); removeEntityFromChunk(entity, chunk); } public void addEntityToChunk(Entity entity, Chunk chunk) { long chunkIndex = ChunkUtils.getChunkIndex(chunk.getChunkX(), chunk.getChunkZ()); synchronized (chunkEntities) { Set entities = getEntitiesInChunk(chunkIndex); entities.add(entity); this.chunkEntities.put(chunkIndex, entities); if (entity instanceof Player) { this.players.add((Player) entity); } else if (entity instanceof EntityCreature) { this.creatures.add((EntityCreature) entity); } else if (entity instanceof ObjectEntity) { this.objectEntities.add((ObjectEntity) entity); } else if (entity instanceof ExperienceOrb) { this.experienceOrbs.add((ExperienceOrb) entity); } } } public void removeEntityFromChunk(Entity entity, Chunk chunk) { long chunkIndex = ChunkUtils.getChunkIndex(chunk.getChunkX(), chunk.getChunkZ()); synchronized (chunkEntities) { Set entities = getEntitiesInChunk(chunkIndex); entities.remove(entity); if (entities.isEmpty()) { this.chunkEntities.remove(chunkIndex); } else { this.chunkEntities.put(chunkIndex, entities); } if (entity instanceof Player) { this.players.remove(entity); } else if (entity instanceof EntityCreature) { this.creatures.remove(entity); } else if (entity instanceof ObjectEntity) { this.objectEntities.remove(entity); } else if (entity instanceof ExperienceOrb) { this.experienceOrbs.remove(entity); } } } private Set getEntitiesInChunk(long index) { Set entities = chunkEntities.get(index); return entities != null ? entities : new CopyOnWriteArraySet<>(); } }