2019-08-11 07:42:56 +02:00
|
|
|
package fr.themode.minestom.instance;
|
|
|
|
|
2020-02-17 17:33:53 +01:00
|
|
|
import fr.themode.minestom.MinecraftServer;
|
2019-09-15 13:42:36 +02:00
|
|
|
import fr.themode.minestom.data.Data;
|
2020-02-09 15:34:09 +01:00
|
|
|
import fr.themode.minestom.data.DataContainer;
|
2019-08-31 07:54:53 +02:00
|
|
|
import fr.themode.minestom.entity.*;
|
2019-09-14 18:00:18 +02:00
|
|
|
import fr.themode.minestom.instance.batch.BlockBatch;
|
|
|
|
import fr.themode.minestom.instance.batch.ChunkBatch;
|
2020-04-11 17:21:53 +02:00
|
|
|
import fr.themode.minestom.instance.block.Block;
|
2019-09-14 19:27:25 +02:00
|
|
|
import fr.themode.minestom.instance.block.BlockManager;
|
2019-09-14 18:00:18 +02:00
|
|
|
import fr.themode.minestom.instance.block.CustomBlock;
|
2019-09-06 16:05:36 +02:00
|
|
|
import fr.themode.minestom.net.PacketWriterUtils;
|
|
|
|
import fr.themode.minestom.net.packet.server.play.ChunkDataPacket;
|
2019-08-21 16:50:52 +02:00
|
|
|
import fr.themode.minestom.utils.BlockPosition;
|
2019-08-25 20:03:43 +02:00
|
|
|
import fr.themode.minestom.utils.ChunkUtils;
|
2019-08-20 17:41:07 +02:00
|
|
|
import fr.themode.minestom.utils.Position;
|
2020-04-17 01:16:02 +02:00
|
|
|
import io.netty.buffer.ByteBuf;
|
2019-08-11 07:42:56 +02:00
|
|
|
|
2019-08-23 23:55:09 +02:00
|
|
|
import java.io.File;
|
2019-08-24 21:41:43 +02:00
|
|
|
import java.util.*;
|
|
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
|
|
import java.util.concurrent.CopyOnWriteArraySet;
|
2019-08-23 23:55:09 +02:00
|
|
|
import java.util.function.Consumer;
|
2019-08-11 07:42:56 +02:00
|
|
|
|
2020-02-09 15:34:09 +01:00
|
|
|
public abstract class Instance implements BlockModifier, DataContainer {
|
2019-08-11 07:42:56 +02:00
|
|
|
|
2019-08-24 21:41:43 +02:00
|
|
|
protected static final ChunkLoaderIO CHUNK_LOADER_IO = new ChunkLoaderIO();
|
2020-02-17 17:33:53 +01:00
|
|
|
protected static final BlockManager BLOCK_MANAGER = MinecraftServer.getBlockManager();
|
2019-09-14 19:27:25 +02:00
|
|
|
|
2019-08-24 21:41:43 +02:00
|
|
|
// Entities present in this instance
|
|
|
|
protected Set<Player> players = new CopyOnWriteArraySet<>();
|
2019-08-31 07:54:53 +02:00
|
|
|
protected Set<EntityCreature> creatures = new CopyOnWriteArraySet<>();
|
|
|
|
protected Set<ObjectEntity> objectEntities = new CopyOnWriteArraySet<>();
|
|
|
|
protected Set<ExperienceOrb> experienceOrbs = new CopyOnWriteArraySet<>();
|
2019-08-24 21:41:43 +02:00
|
|
|
// Entities per chunk
|
|
|
|
protected Map<Long, Set<Entity>> chunkEntities = new ConcurrentHashMap<>();
|
|
|
|
private UUID uniqueId;
|
2019-08-23 23:55:09 +02:00
|
|
|
|
2020-02-09 15:34:09 +01:00
|
|
|
private Data data;
|
|
|
|
|
2019-08-24 21:41:43 +02:00
|
|
|
protected Instance(UUID uniqueId) {
|
|
|
|
this.uniqueId = uniqueId;
|
|
|
|
}
|
2019-08-18 23:52:11 +02:00
|
|
|
|
2020-04-11 17:21:53 +02:00
|
|
|
public abstract void refreshBlockId(int x, int y, int z, short blockId);
|
|
|
|
|
2019-09-06 16:05:36 +02:00
|
|
|
// Used to call BlockBreakEvent and sending particle packet if true
|
2019-08-25 20:03:43 +02:00
|
|
|
public abstract void breakBlock(Player player, BlockPosition blockPosition);
|
2019-08-11 07:42:56 +02:00
|
|
|
|
2019-09-06 16:05:36 +02:00
|
|
|
// Force the generation of the chunk, even if no file and ChunkGenerator are defined
|
2019-08-24 21:41:43 +02:00
|
|
|
public abstract void loadChunk(int chunkX, int chunkZ, Consumer<Chunk> callback);
|
2019-08-20 22:40:57 +02:00
|
|
|
|
2019-08-25 20:03:43 +02:00
|
|
|
// Load only if auto chunk load is enabled
|
|
|
|
public abstract void loadOptionalChunk(int chunkX, int chunkZ, Consumer<Chunk> callback);
|
|
|
|
|
2019-08-24 21:41:43 +02:00
|
|
|
public abstract Chunk getChunk(int chunkX, int chunkZ);
|
2019-08-20 22:40:57 +02:00
|
|
|
|
2020-02-09 15:34:09 +01:00
|
|
|
public abstract void saveChunkToFolder(Chunk chunk, Runnable callback);
|
|
|
|
|
|
|
|
public abstract void saveChunksToFolder(Runnable callback);
|
2019-08-23 23:55:09 +02:00
|
|
|
|
2019-08-24 21:41:43 +02:00
|
|
|
public abstract BlockBatch createBlockBatch();
|
2019-08-23 23:55:09 +02:00
|
|
|
|
2019-08-24 21:41:43 +02:00
|
|
|
public abstract ChunkBatch createChunkBatch(Chunk chunk);
|
2019-08-23 23:55:09 +02:00
|
|
|
|
2019-08-24 21:41:43 +02:00
|
|
|
public abstract void setChunkGenerator(ChunkGenerator chunkGenerator);
|
2019-08-19 17:04:19 +02:00
|
|
|
|
2019-08-24 21:41:43 +02:00
|
|
|
public abstract Collection<Chunk> getChunks();
|
2019-08-18 23:52:11 +02:00
|
|
|
|
2019-08-24 21:41:43 +02:00
|
|
|
public abstract File getFolder();
|
2019-08-20 22:40:57 +02:00
|
|
|
|
2019-08-24 21:41:43 +02:00
|
|
|
public abstract void setFolder(File folder);
|
2019-08-18 23:52:11 +02:00
|
|
|
|
2019-08-24 21:41:43 +02:00
|
|
|
public abstract void sendChunkUpdate(Player player, Chunk chunk);
|
2019-08-12 13:27:24 +02:00
|
|
|
|
2019-08-25 20:03:43 +02:00
|
|
|
protected abstract void retrieveChunk(int chunkX, int chunkZ, Consumer<Chunk> callback);
|
2019-08-11 07:42:56 +02:00
|
|
|
|
2019-08-24 21:41:43 +02:00
|
|
|
public abstract void createChunk(int chunkX, int chunkZ, Consumer<Chunk> callback);
|
2019-08-11 07:42:56 +02:00
|
|
|
|
2019-08-24 21:41:43 +02:00
|
|
|
public abstract void sendChunks(Player player);
|
2019-08-21 16:50:52 +02:00
|
|
|
|
2019-08-25 20:03:43 +02:00
|
|
|
public abstract void sendChunk(Player player, Chunk chunk);
|
|
|
|
|
|
|
|
public abstract void enableAutoChunkLoad(boolean enable);
|
|
|
|
|
|
|
|
public abstract boolean hasEnabledAutoChunkLoad();
|
|
|
|
|
2019-08-24 20:34:01 +02:00
|
|
|
//
|
2019-08-24 21:41:43 +02:00
|
|
|
protected void sendChunkUpdate(Collection<Player> players, Chunk chunk) {
|
2020-04-17 01:16:02 +02:00
|
|
|
ByteBuf chunkData = chunk.getFullDataPacket();
|
2019-08-24 20:34:01 +02:00
|
|
|
players.forEach(player -> {
|
2019-09-02 06:02:12 +02:00
|
|
|
player.getPlayerConnection().sendPacket(chunkData);
|
2019-08-24 20:34:01 +02:00
|
|
|
});
|
2019-08-11 07:42:56 +02:00
|
|
|
}
|
2019-09-06 16:05:36 +02:00
|
|
|
|
|
|
|
protected void sendChunkSectionUpdate(Chunk chunk, int section, Collection<Player> 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;
|
|
|
|
}
|
2019-08-24 20:34:01 +02:00
|
|
|
//
|
2019-08-22 14:52:32 +02:00
|
|
|
|
2019-08-31 07:54:53 +02:00
|
|
|
public Set<Player> getPlayers() {
|
|
|
|
return Collections.unmodifiableSet(players);
|
2019-08-24 21:41:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public Set<EntityCreature> getCreatures() {
|
|
|
|
return Collections.unmodifiableSet(creatures);
|
|
|
|
}
|
|
|
|
|
2019-08-31 07:54:53 +02:00
|
|
|
public Set<ObjectEntity> getObjectEntities() {
|
|
|
|
return Collections.unmodifiableSet(objectEntities);
|
|
|
|
}
|
|
|
|
|
|
|
|
public Set<ExperienceOrb> getExperienceOrbs() {
|
|
|
|
return Collections.unmodifiableSet(experienceOrbs);
|
2019-08-24 21:41:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public Set<Entity> getChunkEntities(Chunk chunk) {
|
2019-08-25 20:03:43 +02:00
|
|
|
return Collections.unmodifiableSet(getEntitiesInChunk(ChunkUtils.getChunkIndex(chunk.getChunkX(), chunk.getChunkZ())));
|
2019-08-19 17:04:19 +02:00
|
|
|
}
|
|
|
|
|
2020-04-11 17:21:53 +02:00
|
|
|
public void refreshBlockId(int x, int y, int z, Block block) {
|
|
|
|
refreshBlockId(x, y, z, block.getBlockId());
|
|
|
|
}
|
|
|
|
|
2019-08-24 21:41:43 +02:00
|
|
|
public void loadChunk(int chunkX, int chunkZ) {
|
2019-08-24 20:34:01 +02:00
|
|
|
loadChunk(chunkX, chunkZ, null);
|
2019-08-11 07:42:56 +02:00
|
|
|
}
|
|
|
|
|
2019-08-24 21:41:43 +02:00
|
|
|
public void loadChunk(Position position, Consumer<Chunk> callback) {
|
2020-04-16 14:51:21 +02:00
|
|
|
int chunkX = ChunkUtils.getChunkCoordinate((int) position.getX());
|
|
|
|
int chunkZ = ChunkUtils.getChunkCoordinate((int) position.getZ());
|
2019-08-24 20:34:01 +02:00
|
|
|
loadChunk(chunkX, chunkZ, callback);
|
2019-08-11 07:42:56 +02:00
|
|
|
}
|
|
|
|
|
2020-04-16 14:51:21 +02:00
|
|
|
public void loadOptionalChunk(Position position, Consumer<Chunk> callback) {
|
|
|
|
int chunkX = ChunkUtils.getChunkCoordinate((int) position.getX());
|
|
|
|
int chunkZ = ChunkUtils.getChunkCoordinate((int) position.getZ());
|
|
|
|
loadOptionalChunk(chunkX, chunkZ, callback);
|
|
|
|
}
|
|
|
|
|
2019-08-24 21:41:43 +02:00
|
|
|
public short getBlockId(int x, int y, int z) {
|
2019-08-24 20:34:01 +02:00
|
|
|
Chunk chunk = getChunkAt(x, z);
|
|
|
|
return chunk.getBlockId((byte) (x % 16), (byte) y, (byte) (z % 16));
|
2019-08-11 07:42:56 +02:00
|
|
|
}
|
|
|
|
|
2020-04-09 14:25:42 +02:00
|
|
|
public short getBlockId(float x, float y, float z) {
|
|
|
|
return getBlockId(Math.round(x), Math.round(y), Math.round(z));
|
|
|
|
}
|
|
|
|
|
2019-08-24 21:41:43 +02:00
|
|
|
public short getBlockId(BlockPosition blockPosition) {
|
2019-08-24 20:34:01 +02:00
|
|
|
return getBlockId(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ());
|
2019-08-23 23:55:09 +02:00
|
|
|
}
|
|
|
|
|
2019-08-24 21:41:43 +02:00
|
|
|
public CustomBlock getCustomBlock(int x, int y, int z) {
|
2019-08-24 20:34:01 +02:00
|
|
|
Chunk chunk = getChunkAt(x, z);
|
|
|
|
return chunk.getCustomBlock((byte) (x % 16), (byte) y, (byte) (z % 16));
|
2019-08-23 23:55:09 +02:00
|
|
|
}
|
|
|
|
|
2020-03-30 19:48:25 +02:00
|
|
|
public CustomBlock getCustomBlock(BlockPosition blockPosition) {
|
|
|
|
return getCustomBlock(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ());
|
|
|
|
}
|
|
|
|
|
2019-09-15 13:42:36 +02:00
|
|
|
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));
|
|
|
|
}
|
|
|
|
|
2020-03-30 19:48:25 +02:00
|
|
|
public Data getBlockData(BlockPosition blockPosition) {
|
|
|
|
return getBlockData(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ());
|
|
|
|
}
|
|
|
|
|
2019-08-24 21:41:43 +02:00
|
|
|
public Chunk getChunkAt(double x, double z) {
|
2020-03-29 20:58:30 +02:00
|
|
|
int chunkX = ChunkUtils.getChunkCoordinate((int) x);
|
|
|
|
int chunkZ = ChunkUtils.getChunkCoordinate((int) z);
|
2019-08-24 20:34:01 +02:00
|
|
|
return getChunk(chunkX, chunkZ);
|
2019-08-23 23:55:09 +02:00
|
|
|
}
|
|
|
|
|
2019-08-24 21:41:43 +02:00
|
|
|
public boolean isChunkLoaded(int chunkX, int chunkZ) {
|
2019-08-24 20:34:01 +02:00
|
|
|
return getChunk(chunkX, chunkZ) != null;
|
2019-08-20 17:41:07 +02:00
|
|
|
}
|
|
|
|
|
2019-08-24 21:41:43 +02:00
|
|
|
public Chunk getChunkAt(BlockPosition blockPosition) {
|
2019-08-24 20:34:01 +02:00
|
|
|
return getChunkAt(blockPosition.getX(), blockPosition.getZ());
|
2019-08-23 23:55:09 +02:00
|
|
|
}
|
|
|
|
|
2019-08-24 21:41:43 +02:00
|
|
|
public Chunk getChunkAt(Position position) {
|
2019-08-24 20:34:01 +02:00
|
|
|
return getChunkAt(position.getX(), position.getZ());
|
2019-08-11 07:42:56 +02:00
|
|
|
}
|
2019-08-12 08:30:59 +02:00
|
|
|
|
2020-02-09 15:34:09 +01:00
|
|
|
public void saveChunkToFolder(Chunk chunk) {
|
|
|
|
saveChunkToFolder(chunk, null);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void saveChunksToFolder() {
|
|
|
|
saveChunksToFolder(null);
|
2019-08-19 17:04:19 +02:00
|
|
|
}
|
|
|
|
|
2019-08-24 21:41:43 +02:00
|
|
|
public UUID getUniqueId() {
|
|
|
|
return uniqueId;
|
|
|
|
}
|
|
|
|
|
2020-02-09 15:34:09 +01:00
|
|
|
@Override
|
|
|
|
public Data getData() {
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setData(Data data) {
|
|
|
|
this.data = data;
|
|
|
|
}
|
|
|
|
|
2019-08-24 21:41:43 +02:00
|
|
|
// UNSAFE METHODS (need most of time to be synchronized)
|
2019-08-19 17:04:19 +02:00
|
|
|
|
2019-08-24 21:41:43 +02:00
|
|
|
public void addEntity(Entity entity) {
|
|
|
|
Instance lastInstance = entity.getInstance();
|
|
|
|
if (lastInstance != null && lastInstance != this) {
|
2019-08-25 20:03:43 +02:00
|
|
|
lastInstance.removeEntity(entity); // If entity is in another instance, remove it from there and add it to this
|
2019-08-24 21:41:43 +02:00
|
|
|
}
|
|
|
|
|
2020-02-17 17:33:53 +01:00
|
|
|
long[] visibleChunksEntity = ChunkUtils.getChunksInRange(entity.getPosition(), MinecraftServer.ENTITY_VIEW_DISTANCE);
|
2019-08-27 20:49:11 +02:00
|
|
|
boolean isPlayer = entity instanceof Player;
|
|
|
|
|
|
|
|
if (isPlayer) {
|
|
|
|
sendChunks((Player) entity);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Send all visible entities
|
|
|
|
for (long chunkIndex : visibleChunksEntity) {
|
|
|
|
getEntitiesInChunk(chunkIndex).forEach(ent -> {
|
2019-09-07 11:42:33 +02:00
|
|
|
if (isPlayer) {
|
2019-08-27 20:49:11 +02:00
|
|
|
ent.addViewer((Player) entity);
|
2019-09-07 11:42:33 +02:00
|
|
|
}
|
|
|
|
|
2019-08-27 20:49:11 +02:00
|
|
|
if (ent instanceof Player) {
|
|
|
|
entity.addViewer((Player) ent);
|
|
|
|
}
|
|
|
|
});
|
2019-08-24 21:41:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
Chunk chunk = getChunkAt(entity.getPosition());
|
|
|
|
addEntityToChunk(entity, chunk);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void removeEntity(Entity entity) {
|
|
|
|
Instance entityInstance = entity.getInstance();
|
|
|
|
if (entityInstance == null || entityInstance != this)
|
|
|
|
return;
|
|
|
|
|
2019-08-25 20:03:43 +02:00
|
|
|
entity.getViewers().forEach(p -> entity.removeViewer(p)); // Remove this entity from players viewable list and send delete entities packet
|
2019-08-24 21:41:43 +02:00
|
|
|
|
|
|
|
Chunk chunk = getChunkAt(entity.getPosition());
|
|
|
|
removeEntityFromChunk(entity, chunk);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void addEntityToChunk(Entity entity, Chunk chunk) {
|
2019-08-25 20:03:43 +02:00
|
|
|
long chunkIndex = ChunkUtils.getChunkIndex(chunk.getChunkX(), chunk.getChunkZ());
|
2019-09-03 07:36:04 +02:00
|
|
|
synchronized (chunkEntities) {
|
|
|
|
Set<Entity> 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);
|
|
|
|
}
|
2019-08-24 21:41:43 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void removeEntityFromChunk(Entity entity, Chunk chunk) {
|
2019-08-25 20:03:43 +02:00
|
|
|
long chunkIndex = ChunkUtils.getChunkIndex(chunk.getChunkX(), chunk.getChunkZ());
|
2019-09-03 07:36:04 +02:00
|
|
|
synchronized (chunkEntities) {
|
|
|
|
Set<Entity> 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) {
|
2019-09-06 16:05:36 +02:00
|
|
|
this.experienceOrbs.remove(entity);
|
2019-09-03 07:36:04 +02:00
|
|
|
}
|
2019-08-24 21:41:43 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private Set<Entity> getEntitiesInChunk(long index) {
|
2019-09-07 11:42:33 +02:00
|
|
|
Set<Entity> entities = chunkEntities.get(index);
|
|
|
|
return entities != null ? entities : new CopyOnWriteArraySet<>();
|
2019-08-24 21:41:43 +02:00
|
|
|
}
|
2020-02-16 19:11:36 +01:00
|
|
|
}
|