diff --git a/src/main/java/fr/themode/minestom/Main.java b/src/main/java/fr/themode/minestom/Main.java index fd194a76c..96f657c1a 100644 --- a/src/main/java/fr/themode/minestom/Main.java +++ b/src/main/java/fr/themode/minestom/Main.java @@ -7,12 +7,12 @@ import fr.adamaq01.ozao.net.server.ServerHandler; import fr.adamaq01.ozao.net.server.backend.tcp.TCPServer; import fr.themode.minestom.entity.EntityManager; import fr.themode.minestom.entity.Player; +import fr.themode.minestom.instance.InstanceManager; import fr.themode.minestom.net.ConnectionManager; import fr.themode.minestom.net.PacketProcessor; import fr.themode.minestom.net.packet.server.play.DestroyEntitiesPacket; import fr.themode.minestom.net.packet.server.play.KeepAlivePacket; import fr.themode.minestom.net.packet.server.play.PlayerInfoPacket; -import fr.themode.minestom.net.player.PlayerConnection; import fr.themode.minestom.net.protocol.MinecraftProtocol; import java.lang.reflect.InvocationTargetException; @@ -25,12 +25,14 @@ public class Main { private static Server server; // In-Game Manager + private static InstanceManager instanceManager; private static EntityManager entityManager; public static void main(String[] args) { connectionManager = new ConnectionManager(); packetProcessor = new PacketProcessor(); + instanceManager = new InstanceManager(); entityManager = new EntityManager(); server = new TCPServer(new MinecraftProtocol()).addHandler(new ServerHandler() { @@ -51,7 +53,7 @@ public class Main { PlayerInfoPacket playerInfoPacket = new PlayerInfoPacket(PlayerInfoPacket.Action.REMOVE_PLAYER); playerInfoPacket.playerInfos.add(new PlayerInfoPacket.RemovePlayer(player.getUuid())); DestroyEntitiesPacket destroyEntitiesPacket = new DestroyEntitiesPacket(); - destroyEntitiesPacket.entityIds = new int[] {player.getEntityId()}; + destroyEntitiesPacket.entityIds = new int[]{player.getEntityId()}; for (Player onlinePlayer : connectionManager.getOnlinePlayers()) { if (!onlinePlayer.equals(player)) { onlinePlayer.getPlayerConnection().sendPacket(destroyEntitiesPacket); @@ -117,6 +119,10 @@ public class Main { return server; } + public static InstanceManager getInstanceManager() { + return instanceManager; + } + public static EntityManager getEntityManager() { return entityManager; } diff --git a/src/main/java/fr/themode/minestom/entity/Entity.java b/src/main/java/fr/themode/minestom/entity/Entity.java index 2a524fbc6..38e7ac1d3 100644 --- a/src/main/java/fr/themode/minestom/entity/Entity.java +++ b/src/main/java/fr/themode/minestom/entity/Entity.java @@ -1,18 +1,23 @@ package fr.themode.minestom.entity; -import fr.themode.minestom.Main; +import fr.themode.minestom.instance.Chunk; +import fr.themode.minestom.instance.Instance; import java.util.UUID; public class Entity { private static volatile int lastEntityId; + protected Instance instance; + protected double lastX, lastY, lastZ; protected double x, y, z; private int id; protected UUID uuid; private boolean isActive; // False if entity has only been instanced without being added somewhere private boolean shouldRemove; + private Object monitor = new Object(); + public Entity() { this.id = generateId(); this.uuid = UUID.randomUUID(); @@ -34,18 +39,44 @@ public class Entity { return isActive; } - public void addToWorld() { - this.isActive = true; - EntityManager entityManager = Main.getEntityManager(); - if (this instanceof EntityCreature) { - entityManager.addCreature((EntityCreature) this); - } + public Instance getInstance() { + return instance; } - public void setPosition(double x, double y, double z) { + public void setInstance(Instance instance) { + if (instance == null) + throw new IllegalArgumentException("instance cannot be null!"); + + if (this.instance != null) { + this.instance.removeEntity(this); + } + + this.isActive = true; + this.instance = instance; + instance.addEntity(this); + } + + public void refreshPosition(double x, double y, double z) { + this.lastX = this.x; + this.lastY = this.y; + this.lastZ = this.z; this.x = x; this.y = y; this.z = z; + + Instance instance = getInstance(); + if (instance != null) { + Chunk lastChunk = instance.getChunkAt(lastX, lastZ); + Chunk newChunk = instance.getChunkAt(x, z); + if (newChunk != null && lastChunk != newChunk) { + synchronized (lastChunk) { + synchronized (newChunk) { + lastChunk.removeEntity(this); + newChunk.addEntity(this); + } + } + } + } } public double getX() { diff --git a/src/main/java/fr/themode/minestom/entity/EntityCreature.java b/src/main/java/fr/themode/minestom/entity/EntityCreature.java index f775ec732..ac6e777d7 100644 --- a/src/main/java/fr/themode/minestom/entity/EntityCreature.java +++ b/src/main/java/fr/themode/minestom/entity/EntityCreature.java @@ -40,9 +40,12 @@ public abstract class EntityCreature extends LivingEntity { } public void removeViewer(Player player) { - if (!viewers.contains(player)) - return; - // TODO send packet to remove entity + synchronized (viewers) { + if (!viewers.contains(player)) + return; + this.viewers.remove(player); + // TODO send packet to remove entity + } } public Set getViewers() { diff --git a/src/main/java/fr/themode/minestom/entity/EntityManager.java b/src/main/java/fr/themode/minestom/entity/EntityManager.java index 27f88bd30..487eb0b84 100644 --- a/src/main/java/fr/themode/minestom/entity/EntityManager.java +++ b/src/main/java/fr/themode/minestom/entity/EntityManager.java @@ -1,47 +1,45 @@ package fr.themode.minestom.entity; import fr.themode.minestom.Main; -import fr.themode.minestom.net.ConnectionManager; +import fr.themode.minestom.instance.Instance; +import fr.themode.minestom.instance.InstanceManager; -import java.util.*; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class EntityManager { - private Set creatures = Collections.synchronizedSet(new HashSet<>()); + private static InstanceManager instanceManager = Main.getInstanceManager(); private ExecutorService creaturesPool = Executors.newFixedThreadPool(3); private ExecutorService playersPool = Executors.newFixedThreadPool(2); - private ConnectionManager connectionManager = Main.getConnectionManager(); - public void update() { - creatures.removeIf(creature -> creature.shouldRemove()); - synchronized (creatures) { - Iterator iterator = creatures.iterator(); - while (iterator.hasNext()) { - EntityCreature creature = iterator.next(); - creaturesPool.submit(creature::update); + for (Instance instance : instanceManager.getInstances()) { + // Creatures + for (EntityCreature creature : instance.getCreatures()) { + creaturesPool.submit(() -> { + creature.update(); + boolean shouldRemove = creature.shouldRemove(); + if (shouldRemove) { + instance.removeEntity(creature); + } + }); + } + + // Players + for (Player player : instance.getPlayers()) { + playersPool.submit(() -> { + player.update(); + boolean shouldRemove = player.shouldRemove(); + if (shouldRemove) { + instance.removeEntity(player); + } + }); } } - Collection players = connectionManager.getOnlinePlayers(); - Iterator iterator = players.iterator(); - while (iterator.hasNext()) { - Player player = iterator.next(); - playersPool.submit(player::update); - } - - } - - public Set getCreatures() { - return Collections.unmodifiableSet(creatures); - } - - protected void addCreature(EntityCreature creature) { - this.creatures.add(creature); } } diff --git a/src/main/java/fr/themode/minestom/entity/Player.java b/src/main/java/fr/themode/minestom/entity/Player.java index 0a865a0bb..89cd90777 100644 --- a/src/main/java/fr/themode/minestom/entity/Player.java +++ b/src/main/java/fr/themode/minestom/entity/Player.java @@ -1,9 +1,7 @@ package fr.themode.minestom.entity; import fr.themode.minestom.Main; -import fr.themode.minestom.net.packet.server.play.EntityLookAndRelativeMovePacket; import fr.themode.minestom.net.packet.server.play.EntityTeleportPacket; -import fr.themode.minestom.net.packet.server.play.PlayerPositionAndLookPacket; import fr.themode.minestom.net.player.PlayerConnection; import java.util.UUID; @@ -50,12 +48,6 @@ public class Player extends LivingEntity { return playerConnection; } - public void refreshPosition(double x, double y, double z) { - this.x = x; - this.y = y; - this.z = z; - } - public void refreshView(float yaw, float pitch) { this.yaw = yaw; this.pitch = pitch; diff --git a/src/main/java/fr/themode/minestom/entity/demo/ChickenCreature.java b/src/main/java/fr/themode/minestom/entity/demo/ChickenCreature.java index aa46d7608..7a8019927 100644 --- a/src/main/java/fr/themode/minestom/entity/demo/ChickenCreature.java +++ b/src/main/java/fr/themode/minestom/entity/demo/ChickenCreature.java @@ -13,13 +13,13 @@ public class ChickenCreature extends EntityCreature { public void update() { onGround = true; - double speed = 0.05; + double speed = 0.075; double newPos = getZ() + speed; EntityRelativeMovePacket entityRelativeMovePacket = new EntityRelativeMovePacket(); entityRelativeMovePacket.entityId = getEntityId(); entityRelativeMovePacket.deltaZ = (short) ((newPos * 32 - getZ() * 32) * 128); - entityRelativeMovePacket.onGround = false; + entityRelativeMovePacket.onGround = true; getViewers().forEach(player -> player.getPlayerConnection().sendPacket(entityRelativeMovePacket)); setZ(newPos); } diff --git a/src/main/java/fr/themode/minestom/world/CustomBiome.java b/src/main/java/fr/themode/minestom/instance/Biome.java similarity index 67% rename from src/main/java/fr/themode/minestom/world/CustomBiome.java rename to src/main/java/fr/themode/minestom/instance/Biome.java index 516dc0f3b..6a0191659 100644 --- a/src/main/java/fr/themode/minestom/world/CustomBiome.java +++ b/src/main/java/fr/themode/minestom/instance/Biome.java @@ -1,8 +1,8 @@ -package fr.themode.minestom.world; +package fr.themode.minestom.instance; import java.util.Arrays; -public enum CustomBiome { +public enum Biome { OCEAN(0), PLAINS(1), @@ -10,11 +10,11 @@ public enum CustomBiome { private int id; - CustomBiome(int id) { + Biome(int id) { this.id = id; } - public static CustomBiome fromId(int id) { + public static Biome fromId(int id) { return Arrays.stream(values()).filter(customBiome -> customBiome.id == id).findFirst().get(); } diff --git a/src/main/java/fr/themode/minestom/world/CustomBlock.java b/src/main/java/fr/themode/minestom/instance/Block.java similarity index 83% rename from src/main/java/fr/themode/minestom/world/CustomBlock.java rename to src/main/java/fr/themode/minestom/instance/Block.java index 37ac580f4..7b9dc42d4 100644 --- a/src/main/java/fr/themode/minestom/world/CustomBlock.java +++ b/src/main/java/fr/themode/minestom/instance/Block.java @@ -1,15 +1,15 @@ -package fr.themode.minestom.world; +package fr.themode.minestom.instance; -public class CustomBlock { +public class Block { private short typeAndDamage; - public CustomBlock(int type) { + public Block(int type) { this.typeAndDamage = (short) (type & 0x0FFF); this.typeAndDamage |= (0 << 12) & 0xF000; } - public CustomBlock(int type, int damage) { + public Block(int type, int damage) { this.typeAndDamage = (short) (type & 0x0FFF); this.typeAndDamage |= (damage << 12) & 0xF000; } diff --git a/src/main/java/fr/themode/minestom/instance/Chunk.java b/src/main/java/fr/themode/minestom/instance/Chunk.java new file mode 100644 index 000000000..4cbebd09e --- /dev/null +++ b/src/main/java/fr/themode/minestom/instance/Chunk.java @@ -0,0 +1,104 @@ +package fr.themode.minestom.instance; + +import fr.themode.minestom.entity.Entity; +import fr.themode.minestom.entity.EntityCreature; +import fr.themode.minestom.entity.Player; +import fr.themode.minestom.net.packet.server.play.ChunkDataPacket; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; + +public class Chunk { + + protected Set creatures = Collections.synchronizedSet(new HashSet<>()); + protected Set players = Collections.synchronizedSet(new HashSet<>()); + private int chunkX, chunkZ; + private Biome biome; + private HashMap blocks = new HashMap<>(); + private ChunkDataPacket fullChunkPacket; + + public Chunk(Biome biome, int chunkX, int chunkZ) { + this.biome = biome; + this.chunkX = chunkX; + this.chunkZ = chunkZ; + } + + public void setBlock(int x, int y, int z, Block block) { + short index = (short) (x & 0x000F); + index |= (y << 4) & 0x0FF0; + index |= (z << 12) & 0xF000; + this.blocks.put(index, block); + } + + public Block getBlock(int x, int y, int z) { + short index = (short) (x & 0x000F); + index |= (y << 4) & 0x0FF0; + index |= (z << 12) & 0xF000; + return this.blocks.getOrDefault(index, new Block(0)); + } + + public void addEntity(Entity entity) { + if (entity instanceof Player) { + synchronized (players) { + if (this.players.contains(entity)) + return; + this.players.add((Player) entity); + } + } else if (entity instanceof EntityCreature) { + synchronized (creatures) { + if (this.creatures.contains(entity)) + return; + this.creatures.add((EntityCreature) entity); + } + } + } + + public void removeEntity(Entity entity) { + if (entity instanceof Player) { + synchronized (players) { + this.players.remove(entity); + } + } else if (entity instanceof EntityCreature) { + synchronized (creatures) { + this.creatures.remove(entity); + } + } + } + + public HashMap getBlocks() { + return blocks; + } + + public Biome getBiome() { + return biome; + } + + public int getChunkX() { + return chunkX; + } + + public int getChunkZ() { + return chunkZ; + } + + public Set getCreatures() { + return Collections.unmodifiableSet(creatures); + } + + public Set getPlayers() { + return Collections.unmodifiableSet(players); + } + + private void refreshFullChunkPacket() { + ChunkDataPacket chunkDataPacket = new ChunkDataPacket(); + chunkDataPacket.fullChunk = true; + chunkDataPacket.chunk = this; + // TODO fill buffer + } + + public ChunkDataPacket getFullChunkPacket() { + return fullChunkPacket; + } +} diff --git a/src/main/java/fr/themode/minestom/instance/Instance.java b/src/main/java/fr/themode/minestom/instance/Instance.java new file mode 100644 index 000000000..dc47c5ed4 --- /dev/null +++ b/src/main/java/fr/themode/minestom/instance/Instance.java @@ -0,0 +1,110 @@ +package fr.themode.minestom.instance; + +import fr.themode.minestom.entity.Entity; +import fr.themode.minestom.entity.EntityCreature; +import fr.themode.minestom.entity.Player; +import fr.themode.minestom.utils.GroupedCollections; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +public class Instance { + + private int id; + + private Set chunksSet = Collections.synchronizedSet(new HashSet<>()); + + public Instance(int id) { + this.id = id; + } + + // TODO BlockBatch with pool + public void setBlock(int x, int y, int z, Block block) { + Chunk chunk = getChunkAt(x, z); + if (chunk == null) { + int chunkX = x / 16; + int chunkZ = z / 16; + chunk = new Chunk(Biome.VOID, chunkX, chunkZ); + this.chunksSet.add(chunk); + } + synchronized (chunk) { + chunk.setBlock(x % 16, y, z % 16, block); + } + } + + public Chunk getChunk(int chunkX, int chunkZ) { + for (Chunk chunk : getChunks()) { + if (chunk.getChunkX() == chunkX && chunk.getChunkZ() == chunkZ) + return chunk; + } + return null; + } + + public Chunk getChunkAt(double x, double z) { + int chunkX = (int) (x / 16); + int chunkZ = (int) (z / 16); + return getChunk(chunkX, chunkZ); + } + + public Set getChunks() { + return Collections.unmodifiableSet(chunksSet); + } + + public void addEntity(Entity entity) { + Instance lastInstance = entity.getInstance(); + if (lastInstance != null && lastInstance != this) { + lastInstance.removeEntity(entity); + } + + if (entity instanceof EntityCreature) { + // TODO based on distance with player + getPlayers().forEach(p -> ((EntityCreature) entity).addViewer(p)); + } else if (entity instanceof Player) { + getCreatures().forEach(entityCreature -> entityCreature.addViewer((Player) entity)); + } + + Chunk chunk = getChunkAt(entity.getX(), entity.getZ()); + chunk.addEntity(entity); + } + + public void removeEntity(Entity entity) { + Instance entityInstance = entity.getInstance(); + if (entityInstance == null || entityInstance != this) + return; + + if (entity instanceof EntityCreature) { + EntityCreature creature = (EntityCreature) entity; + creature.getViewers().forEach(p -> creature.removeViewer(p)); + } + + Chunk chunk = getChunkAt(entity.getX(), entity.getZ()); + chunk.removeEntity(entity); + } + + public GroupedCollections getCreatures() { + GroupedCollections creatures = new GroupedCollections(); + for (Chunk chunk : getChunks()) { + creatures.addCollection(chunk.creatures); + } + return creatures; + } + + public GroupedCollections getPlayers() { + GroupedCollections players = new GroupedCollections(); + for (Chunk chunk : getChunks()) { + players.addCollection(chunk.players); + } + return players; + } + + public int getId() { + return id; + } + + private Chunk createChunk(int chunkX, int chunkZ) { + Chunk chunk = new Chunk(Biome.VOID, chunkX, chunkZ); + this.chunksSet.add(chunk); + return chunk; + } +} diff --git a/src/main/java/fr/themode/minestom/instance/InstanceManager.java b/src/main/java/fr/themode/minestom/instance/InstanceManager.java new file mode 100644 index 000000000..7343e81ec --- /dev/null +++ b/src/main/java/fr/themode/minestom/instance/InstanceManager.java @@ -0,0 +1,27 @@ +package fr.themode.minestom.instance; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +public class InstanceManager { + + private static volatile int lastInstanceId; + + private Set instances = Collections.synchronizedSet(new HashSet<>()); + + private static int generateId() { + return ++lastInstanceId; + } + + public Instance createInstance() { + Instance instance = new Instance(generateId()); + this.instances.add(instance); + return instance; + } + + public Set getInstances() { + return Collections.unmodifiableSet(instances); + } + +} diff --git a/src/main/java/fr/themode/minestom/net/packet/client/login/LoginStartPacket.java b/src/main/java/fr/themode/minestom/net/packet/client/login/LoginStartPacket.java index f9b426ff8..4ac181627 100644 --- a/src/main/java/fr/themode/minestom/net/packet/client/login/LoginStartPacket.java +++ b/src/main/java/fr/themode/minestom/net/packet/client/login/LoginStartPacket.java @@ -1,22 +1,22 @@ package fr.themode.minestom.net.packet.client.login; import fr.adamaq01.ozao.net.Buffer; -import fr.adamaq01.ozao.net.packet.Packet; +import fr.themode.minestom.Main; import fr.themode.minestom.entity.GameMode; import fr.themode.minestom.entity.Player; import fr.themode.minestom.entity.demo.ChickenCreature; +import fr.themode.minestom.instance.Block; +import fr.themode.minestom.instance.Chunk; +import fr.themode.minestom.instance.Instance; import fr.themode.minestom.net.ConnectionManager; import fr.themode.minestom.net.ConnectionState; import fr.themode.minestom.net.packet.client.ClientPreplayPacket; import fr.themode.minestom.net.packet.server.login.JoinGamePacket; import fr.themode.minestom.net.packet.server.login.LoginSuccessPacket; -import fr.themode.minestom.net.packet.server.play.PlayerInfoPacket; -import fr.themode.minestom.net.packet.server.play.PlayerPositionAndLookPacket; -import fr.themode.minestom.net.packet.server.play.SpawnPlayerPacket; -import fr.themode.minestom.net.packet.server.play.SpawnPositionPacket; +import fr.themode.minestom.net.packet.server.play.*; import fr.themode.minestom.net.player.PlayerConnection; import fr.themode.minestom.utils.Utils; -import fr.themode.minestom.world.*; +import fr.themode.minestom.world.Dimension; import java.util.HashMap; import java.util.UUID; @@ -25,6 +25,16 @@ public class LoginStartPacket implements ClientPreplayPacket { private String username; + // Test + private static Instance instance; + + static { + instance = Main.getInstanceManager().createInstance(); + for (int x = -64; x < 64; x++) + for (int z = -64; z < 64; z++) + instance.setBlock(x, 4, z, new Block(1)); + } + @Override public void process(PlayerConnection connection, ConnectionManager connectionManager) { HashMap uuids = new HashMap<>(); @@ -41,15 +51,15 @@ public class LoginStartPacket implements ClientPreplayPacket { connection.setConnectionState(ConnectionState.PLAY); connectionManager.createPlayer(uuids.get(username), username, connection); Player player = connectionManager.getPlayer(connection); - player.addToWorld(); // TODO complete login sequence with optionals packets JoinGamePacket joinGamePacket = new JoinGamePacket(); joinGamePacket.entityId = player.getEntityId(); joinGamePacket.gameMode = GameMode.CREATIVE; joinGamePacket.dimension = Dimension.OVERWORLD; - joinGamePacket.maxPlayers = 10; + joinGamePacket.maxPlayers = 0; // Unused joinGamePacket.levelType = "default"; + joinGamePacket.viewDistance = 14; joinGamePacket.reducedDebugInfo = false; connection.sendPacket(joinGamePacket); @@ -59,16 +69,12 @@ public class LoginStartPacket implements ClientPreplayPacket { // TODO player abilities - CustomChunk customChunk = new CustomChunk(CustomBiome.VOID); - for (int x = 0; x < 16; x++) - for (int z = 0; z < 16; z++) - customChunk.setBlock(x, 4, z, new CustomBlock(1)); // Stone - - for (int x = -2; x < 2; x++) { - for (int z = -2; z < 2; z++) { - Packet packet = ChunkPacketCreator.create(x, z, customChunk, 0, 15); - connection.getConnection().sendPacket(packet); - } + player.setInstance(instance); + ChunkDataPacket chunkDataPacket = new ChunkDataPacket(); + chunkDataPacket.fullChunk = true; + for (Chunk chunk : instance.getChunks()) { + chunkDataPacket.chunk = chunk; + connection.sendPacket(chunkDataPacket); } @@ -99,9 +105,8 @@ public class LoginStartPacket implements ClientPreplayPacket { for (int z = -2; z < 2; z++) { // TODO test entity ChickenCreature chickenCreature = new ChickenCreature(); - chickenCreature.setPosition(0 + (double) x * 1, 5, 0 + (double) z * 1); - connectionManager.getOnlinePlayers().forEach(p -> chickenCreature.addViewer(p)); - chickenCreature.addToWorld(); + chickenCreature.refreshPosition(0 + (double) x * 1, 5, 0 + (double) z * 1); + instance.addEntity(chickenCreature); } diff --git a/src/main/java/fr/themode/minestom/net/packet/server/login/JoinGamePacket.java b/src/main/java/fr/themode/minestom/net/packet/server/login/JoinGamePacket.java index 506bfdb0a..89eed8240 100644 --- a/src/main/java/fr/themode/minestom/net/packet/server/login/JoinGamePacket.java +++ b/src/main/java/fr/themode/minestom/net/packet/server/login/JoinGamePacket.java @@ -13,6 +13,7 @@ public class JoinGamePacket implements ServerPacket { public Dimension dimension = Dimension.OVERWORLD; public byte maxPlayers = 0; // Unused public String levelType = "default"; + public int viewDistance; public boolean reducedDebugInfo = false; @Override @@ -26,7 +27,7 @@ public class JoinGamePacket implements ServerPacket { buffer.putInt(dimension.getId()); buffer.putByte(maxPlayers); Utils.writeString(buffer, levelType); - Utils.writeVarInt(buffer, 8); + Utils.writeVarInt(buffer, viewDistance); buffer.putBoolean(reducedDebugInfo); } diff --git a/src/main/java/fr/themode/minestom/net/packet/server/play/ChunkDataPacket.java b/src/main/java/fr/themode/minestom/net/packet/server/play/ChunkDataPacket.java index 8cfed70ee..1a26880be 100644 --- a/src/main/java/fr/themode/minestom/net/packet/server/play/ChunkDataPacket.java +++ b/src/main/java/fr/themode/minestom/net/packet/server/play/ChunkDataPacket.java @@ -1,124 +1,93 @@ package fr.themode.minestom.net.packet.server.play; import fr.adamaq01.ozao.net.Buffer; +import fr.themode.minestom.instance.Block; +import fr.themode.minestom.instance.Chunk; import fr.themode.minestom.net.packet.server.ServerPacket; import fr.themode.minestom.utils.Utils; +import net.querz.nbt.CompoundTag; +import net.querz.nbt.LongArrayTag; -import java.io.UnsupportedEncodingException; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; public class ChunkDataPacket implements ServerPacket { - public int columnX; - public int columnZ; public boolean fullChunk; - public int mask; - public ChunkSection[] chunkSections; - public int tileEntitesSize; + public Chunk chunk; // TODO nbt tile entities @Override public void write(Buffer buffer) { - buffer.putInt(columnX); - buffer.putInt(columnZ); + buffer.putInt(chunk.getChunkX()); + buffer.putInt(chunk.getChunkZ()); buffer.putBoolean(fullChunk); + + + int mask = 0; + Buffer blocks = Buffer.create(); + for (int i = 0; i < 16; i++) { + // TODO if fullchunk is false then only send changed sections + mask |= 1 << i; + Block[] section = getSection(chunk, i); + Utils.writeBlocks(blocks, section, 14); + } + // Biome data + if (fullChunk) { + int[] biomeData = new int[256]; + for (int z = 0; z < 16; z++) { + for (int x = 0; x < 16; x++) { + biomeData[z * 16 | x] = chunk.getBiome().getId(); + } + } + for (int i = 0; i < biomeData.length; i++) { + blocks.putInt(biomeData[i]); + } + } Utils.writeVarInt(buffer, mask); - // Nbt - buffer.putByte((byte) 10); - buffer.putShort((short) 0); - buffer.putByte((byte) 12); - buffer.putShort((short) "MOTION_BLOCKING".length()); + // Heightmap + int[] motionBlocking = new int[16 * 16]; + int[] worldSurface = new int[16 * 16]; + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + motionBlocking[x + z * 16] = 4; + worldSurface[x + z * 16] = 5; + } + } + CompoundTag compound = new CompoundTag(); + compound.put("MOTION_BLOCKING", new LongArrayTag(Utils.encodeBlocks(motionBlocking, 9))); + compound.put("WORLD_SURFACE", new LongArrayTag(Utils.encodeBlocks(worldSurface, 9))); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); try { - buffer.putBytes("MOTION_BLOCKING".getBytes("UTF-8")); - } catch (UnsupportedEncodingException e) { + compound.serialize(new DataOutputStream(outputStream), 100); + } catch (IOException e) { e.printStackTrace(); } - buffer.putInt(256); - for (int i = 0; i < 256; i++) { - buffer.putLong(Long.MAX_VALUE); - } + byte[] data = outputStream.toByteArray(); + buffer.putBytes(data); - buffer.putByte((byte) 12); - buffer.putShort((short) "WORLD_SURFACE".length()); - try { - buffer.putBytes("WORLD_SURFACE".getBytes("UTF-8")); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - } - buffer.putInt(256); - for (int i = 0; i < 256; i++) { - buffer.putLong(Long.MAX_VALUE); - } - buffer.putByte((byte) 0); // End nbt - - Utils.writeVarInt(buffer, getDataSize()); - writeData(buffer); - Utils.writeVarInt(buffer, tileEntitesSize); - // TODO nbt tile entities + Utils.writeVarInt(buffer, blocks.length()); + buffer.putBuffer(blocks); + Utils.writeVarInt(buffer, 0); } - private int getDataSize() { - int result = Integer.BYTES * 256; // Size for 256 biomes value - for (int i = 0; i < chunkSections.length; i++) { - result += chunkSections[i].getSize(); - } - return result; - } - - private void writeData(Buffer buffer) { - for (ChunkSection chunkSection : chunkSections) { - chunkSection.write(buffer); - } - // Biomes data - for (int i = 0; i < 256; i++) { - buffer.putInt(127); // Void biome + private Block[] getSection(Chunk chunk, int section) { + Block[] blocks = new Block[16 * 16 * 16]; + for (int y = 0; y < 16; y++) { + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + int index = (((y * 16) + x) * 16) + z; + blocks[index] = chunk.getBlock(x, y + 16 * section, z); + } + } } + return blocks; } @Override public int getId() { return 0x21; } - - public static class ChunkSection { - - public byte bitsPerBlock; - public int paletteLength; // Optional - public int[] palette; // Optional - public long[] data; - - public void write(Buffer buffer) { - buffer.putShort((short) 3); - buffer.putByte(bitsPerBlock); - - if (bitsPerBlock < 9) { - Utils.writeVarInt(buffer, paletteLength); - for (int p : palette) { - Utils.writeVarInt(buffer, p); - } - } - - Utils.writeVarInt(buffer, data.length); - for (long d : data) { - buffer.putLong(d); - } - } - - public int getSize() { - int size = 0; - size += Short.BYTES; - size++; //bitsPerBlock - if (bitsPerBlock < 9) { - size += Utils.lengthVarInt(paletteLength); - for (int p : palette) { - size += Utils.lengthVarInt(p); - } - } - - size += Utils.lengthVarInt(data.length); - size += Long.BYTES * data.length; - return size; - } - - } } diff --git a/src/main/java/fr/themode/minestom/net/packet/server/play/EntityLookAndRelativeMovePacket.java b/src/main/java/fr/themode/minestom/net/packet/server/play/EntityLookAndRelativeMovePacket.java index be6792d3b..cc6d9fbf5 100644 --- a/src/main/java/fr/themode/minestom/net/packet/server/play/EntityLookAndRelativeMovePacket.java +++ b/src/main/java/fr/themode/minestom/net/packet/server/play/EntityLookAndRelativeMovePacket.java @@ -17,11 +17,12 @@ public class EntityLookAndRelativeMovePacket implements ServerPacket { buffer.putShort(deltaX); buffer.putShort(deltaY); buffer.putShort(deltaZ); - buffer.putFloat(yaw); - buffer.putFloat(pitch); + buffer.putByte((byte) (this.yaw * 256 / 360)); + buffer.putByte((byte) (this.pitch * 256 / 360)); buffer.putBoolean(onGround); } + @Override public int getId() { return 0x29; diff --git a/src/main/java/fr/themode/minestom/utils/GroupedCollections.java b/src/main/java/fr/themode/minestom/utils/GroupedCollections.java new file mode 100644 index 000000000..bcccb94b1 --- /dev/null +++ b/src/main/java/fr/themode/minestom/utils/GroupedCollections.java @@ -0,0 +1,47 @@ +package fr.themode.minestom.utils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; + +public class GroupedCollections implements Iterable { + + private ArrayList> collections; + + public GroupedCollections() { + this.collections = new ArrayList<>(); + } + + public int size() { + return collections.stream().mapToInt(es -> es.size()).sum(); + } + + public boolean isEmpty() { + return collections.stream().allMatch(es -> es.isEmpty()); + } + + public boolean contains(Object o) { + return collections.stream().anyMatch(es -> es.contains(o)); + } + + @Override + public Iterator iterator() { + return collections.stream().flatMap(Collection::stream).iterator(); + } + + /*public Object[] toArray() { + return collections.stream().flatMap(Collection::stream).collect(Collectors.toList()).toArray(); + } + + public T[] toArray(T[] ts) { + return collections.stream().flatMap(Collection::stream).collect(Collectors.toList()).toArray(ts); + } + + public boolean containsAll(Collection collection) { + return collections.stream().flatMap(Collection::stream).collect(Collectors.toList()).containsAll(collection); + }*/ + + public void addCollection(Collection list) { + this.collections.add(list); + } +} diff --git a/src/main/java/fr/themode/minestom/utils/Utils.java b/src/main/java/fr/themode/minestom/utils/Utils.java index aa29aaff7..73f3ff874 100644 --- a/src/main/java/fr/themode/minestom/utils/Utils.java +++ b/src/main/java/fr/themode/minestom/utils/Utils.java @@ -1,7 +1,7 @@ package fr.themode.minestom.utils; import fr.adamaq01.ozao.net.Buffer; -import fr.themode.minestom.world.CustomBlock; +import fr.themode.minestom.instance.Block; import java.io.UnsupportedEncodingException; import java.util.Arrays; @@ -124,7 +124,7 @@ public class Utils { buffer.putLong(((x & 0x3FFFFFF) << 38) | ((y & 0xFFF) << 26) | (z & 0x3FFFFFF)); } - public static void writeBlocks(Buffer buffer, CustomBlock[] blocks, int bitsPerEntry) { + public static void writeBlocks(Buffer buffer, Block[] blocks, int bitsPerEntry) { buffer.putShort((short) Arrays.stream(blocks).filter(customBlock -> customBlock.getType() != 0).collect(Collectors.toList()).size()); buffer.putByte((byte) bitsPerEntry); int[] blocksData = new int[16 * 16 * 16]; diff --git a/src/main/java/fr/themode/minestom/world/ChunkPacketCreator.java b/src/main/java/fr/themode/minestom/world/ChunkPacketCreator.java deleted file mode 100644 index d5fc04087..000000000 --- a/src/main/java/fr/themode/minestom/world/ChunkPacketCreator.java +++ /dev/null @@ -1,82 +0,0 @@ -package fr.themode.minestom.world; - -import fr.adamaq01.ozao.net.Buffer; -import fr.adamaq01.ozao.net.packet.Packet; -import fr.themode.minestom.utils.Utils; -import net.querz.nbt.CompoundTag; -import net.querz.nbt.LongArrayTag; - -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.IOException; - -public class ChunkPacketCreator { - - public static Packet create(int chunkX, int chunkZ, CustomChunk customChunk, int start, int end) { - Packet packet = Packet.create(); - packet.put("id", 0x21); - Buffer payload = packet.getPayload(); - - payload.putInt(chunkX); - payload.putInt(chunkZ); - payload.putBoolean(true); // Send biome data (loading chunk, not modifying it) - int mask = 0; - Buffer blocks = Buffer.create(); - for (int i = 0; i < 16; i++) { - mask |= 1 << i; - CustomBlock[] section = getSection(customChunk, i); - Utils.writeBlocks(blocks, section, 14); - } - // Biome data - int[] biomeData = new int[256]; - for (int z = 0; z < 16; z++) { - for (int x = 0; x < 16; x++) { - biomeData[z * 16 | x] = customChunk.getBiome().getId(); - } - } - for (int i = 0; i < biomeData.length; i++) { - blocks.putInt(biomeData[i]); - } - Utils.writeVarInt(payload, mask); - - // Heightmap - int[] motionBlocking = new int[16 * 16]; - int[] worldSurface = new int[16 * 16]; - for (int x = 0; x < 16; x++) { - for (int z = 0; z < 16; z++) { - motionBlocking[x + z * 16] = 4; - worldSurface[x + z * 16] = 5; - } - } - CompoundTag compound = new CompoundTag(); - compound.put("MOTION_BLOCKING", new LongArrayTag(Utils.encodeBlocks(motionBlocking, 9))); - compound.put("WORLD_SURFACE", new LongArrayTag(Utils.encodeBlocks(worldSurface, 9))); - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - try { - compound.serialize(new DataOutputStream(outputStream), 100); - } catch (IOException e) { - e.printStackTrace(); - } - byte[] data = outputStream.toByteArray(); - payload.putBytes(data); - - Utils.writeVarInt(payload, blocks.length()); - payload.putBuffer(blocks); - Utils.writeVarInt(payload, 0); - - return packet; - } - - public static CustomBlock[] getSection(CustomChunk customChunk, int section) { - CustomBlock[] blocks = new CustomBlock[16 * 16 * 16]; - for (int y = 0; y < 16; y++) { - for (int x = 0; x < 16; x++) { - for (int z = 0; z < 16; z++) { - int index = (((y * 16) + x) * 16) + z; - blocks[index] = customChunk.getBlock(x, y + 16 * section, z); - } - } - } - return blocks; - } -} diff --git a/src/main/java/fr/themode/minestom/world/CustomChunk.java b/src/main/java/fr/themode/minestom/world/CustomChunk.java deleted file mode 100644 index 7653c50df..000000000 --- a/src/main/java/fr/themode/minestom/world/CustomChunk.java +++ /dev/null @@ -1,41 +0,0 @@ -package fr.themode.minestom.world; - -import java.util.HashMap; - -public class CustomChunk { - - private CustomBiome biome; - private HashMap blocks; - - public CustomChunk(CustomBiome biome) { - this.biome = biome; - this.blocks = new HashMap<>(); - } - - public CustomBiome getBiome() { - return biome; - } - - public HashMap getBlocks() { - return blocks; - } - - public void setBlock(int x, int y, int z, CustomBlock customBlock) { - short index = (short) (x & 0x000F); - index |= (y << 4) & 0x0FF0; - index |= (z << 12) & 0xF000; - this.blocks.put(index, customBlock); - } - - public CustomBlock getBlock(int x, int y, int z) { - short index = (short) (x & 0x000F); - index |= (y << 4) & 0x0FF0; - index |= (z << 12) & 0xF000; - return this.blocks.getOrDefault(index, new CustomBlock(0)); - } - - @Override - public String toString() { - return String.format("CustomChunk{biome=%s, blocks=%s}", biome, blocks.values()); - } -} diff --git a/src/main/java/fr/themode/minestom/world/CustomWorld.java b/src/main/java/fr/themode/minestom/world/CustomWorld.java deleted file mode 100644 index ced029b0c..000000000 --- a/src/main/java/fr/themode/minestom/world/CustomWorld.java +++ /dev/null @@ -1,51 +0,0 @@ -package fr.themode.minestom.world; - -import java.util.HashMap; - -public class CustomWorld { - - private String name; - private HashMap chunks; - - public CustomWorld(String name) { - this.name = name; - this.chunks = new HashMap<>(); - } - - public String getName() { - return name; - } - - public HashMap getChunks() { - return chunks; - } - - public void setChunk(int x, int z, CustomChunk customChunk) { - int index = x & 0x0000FFFF; - index |= (z << 16) & 0xFFFF0000; - this.chunks.put(index, customChunk); - } - - public CustomChunk getChunk(int x, int z) { - int index = x & 0x0000FFFF; - index |= (z << 16) & 0xFFFF0000; - return this.chunks.get(index); - } - - public void setBlock(int x, int y, int z, CustomBlock customBlock) { - int chunkX = x / 16; - int chunkZ = z / 16; - getChunk(chunkX, chunkZ).setBlock(x % 16, y, z % 16, customBlock); - } - - public CustomBlock getBlock(int x, int y, int z) { - int chunkX = x / 16; - int chunkZ = z / 16; - return getChunk(chunkX, chunkZ).getBlock(x % 16, y, z % 16); - } - - @Override - public String toString() { - return String.format("CustomWorld{name=%s, chunks=%s}", name, chunks.values()); - } -}