diff --git a/src/main/java/fr/themode/minestom/Main.java b/src/main/java/fr/themode/minestom/Main.java index bd97c7007..33a2f3852 100644 --- a/src/main/java/fr/themode/minestom/Main.java +++ b/src/main/java/fr/themode/minestom/Main.java @@ -21,6 +21,13 @@ import java.lang.reflect.InvocationTargetException; public class Main { + // Thread number + public static final int THREAD_COUNT_PACKET_WRITER = 3; + public static final int THREAD_COUNT_CHUNK_BATCH = 2; + public static final int THREAD_COUNT_OBJECTS_ENTITIES = 2; + public static final int THREAD_COUNT_CREATURES_ENTITIES = 2; + public static final int THREAD_COUNT_PLAYERS_ENTITIES = 2; + public static final int TICK_MS = 50; // Networking @@ -101,12 +108,14 @@ public class Main { currentTime = System.nanoTime(); // Keep Alive Handling - server.getConnections().stream().filter(connection -> packetProcessor.hasPlayerConnection(connection) && connectionManager.getPlayer(packetProcessor.getPlayerConnection(connection)) != null && System.currentTimeMillis() - connectionManager.getPlayer(packetProcessor.getPlayerConnection(connection)).getLastKeepAlive() > 20000).map(connection -> connectionManager.getPlayer(packetProcessor.getPlayerConnection(connection))).forEach(player -> { - long id = System.currentTimeMillis(); - player.refreshKeepAlive(id); - KeepAlivePacket keepAlivePacket = new KeepAlivePacket(id); - player.getPlayerConnection().sendPacket(keepAlivePacket); - }); + for (Player player : getConnectionManager().getOnlinePlayers()) { + if (System.currentTimeMillis() - player.getLastKeepAlive() > 20000) { + long id = System.currentTimeMillis(); + player.refreshKeepAlive(id); + KeepAlivePacket keepAlivePacket = new KeepAlivePacket(id); + player.getPlayerConnection().sendPacket(keepAlivePacket); + } + } // Entities update entityManager.update(); diff --git a/src/main/java/fr/themode/minestom/Viewable.java b/src/main/java/fr/themode/minestom/Viewable.java index 5be1a9502..19996c27d 100644 --- a/src/main/java/fr/themode/minestom/Viewable.java +++ b/src/main/java/fr/themode/minestom/Viewable.java @@ -1,6 +1,7 @@ package fr.themode.minestom; import fr.themode.minestom.entity.Player; +import fr.themode.minestom.net.PacketWriter; import fr.themode.minestom.net.packet.server.ServerPacket; import java.util.Set; @@ -21,8 +22,13 @@ public interface Viewable { if (getViewers().isEmpty()) return; - //Packet p = PacketUtils.writePacket(packet); - getViewers().forEach(player -> player.getPlayerConnection().sendPacket(packet)); + PacketWriter.writeCallbackPacket(packet, buffer -> { + buffer.getData().retain(getViewers().size()).markReaderIndex(); + getViewers().forEach(player -> { + player.getPlayerConnection().sendUnencodedPacket(buffer); + buffer.getData().resetReaderIndex(); + }); + }); } default void sendPacketsToViewers(ServerPacket... packets) { @@ -30,17 +36,29 @@ public interface Viewable { return; for (ServerPacket packet : packets) { - //Packet p = PacketUtils.writePacket(packet); - getViewers().forEach(player -> player.getPlayerConnection().sendPacket(packet)); + PacketWriter.writeCallbackPacket(packet, buffer -> { + buffer.getData().retain(getViewers().size()).markReaderIndex(); + getViewers().forEach(player -> { + player.getPlayerConnection().sendUnencodedPacket(buffer); + buffer.getData().resetReaderIndex(); + }); + }); } } default void sendPacketToViewersAndSelf(ServerPacket packet) { if (this instanceof Player) { - //Packet p = PacketUtils.writePacket(packet); - ((Player) this).getPlayerConnection().sendPacket(packet); - if (!getViewers().isEmpty()) - getViewers().forEach(player -> player.getPlayerConnection().sendPacket(packet)); + PacketWriter.writeCallbackPacket(packet, buffer -> { + buffer.getData().retain(getViewers().size() + 1).markReaderIndex(); + ((Player) this).getPlayerConnection().sendUnencodedPacket(buffer); + buffer.getData().resetReaderIndex(); + if (!getViewers().isEmpty()) { + getViewers().forEach(player -> { + buffer.getData().resetReaderIndex(); + player.getPlayerConnection().sendUnencodedPacket(buffer); + }); + } + }); } } diff --git a/src/main/java/fr/themode/minestom/entity/Entity.java b/src/main/java/fr/themode/minestom/entity/Entity.java index c46a3b554..15125d3ca 100644 --- a/src/main/java/fr/themode/minestom/entity/Entity.java +++ b/src/main/java/fr/themode/minestom/entity/Entity.java @@ -225,7 +225,12 @@ public abstract class Entity implements Viewable { public void setOnFire(boolean fire) { this.onFire = fire; - sendMetadata(0); + sendMetadataIndex(0); + } + + public void setNoGravity(boolean noGravity) { + this.noGravity = noGravity; + sendMetadataIndex(5); } public boolean isChunkUnloaded(float x, float z) { @@ -264,12 +269,12 @@ public abstract class Entity implements Viewable { public void refreshSneaking(boolean sneaking) { this.crouched = sneaking; - sendMetadata(0); + sendMetadataIndex(0); } public void refreshSprinting(boolean sprinting) { this.sprinting = sprinting; - sendMetadata(0); + sendMetadataIndex(0); } public Position getPosition() { @@ -302,10 +307,11 @@ public abstract class Entity implements Viewable { Buffer buffer = Buffer.create(); fillMetadataIndex(buffer, 0); fillMetadataIndex(buffer, 1); + fillMetadataIndex(buffer, 5); return buffer; } - private void sendMetadata(int index) { + protected void sendMetadataIndex(int index) { Buffer buffer = Buffer.create(); fillMetadataIndex(buffer, index); @@ -329,6 +335,9 @@ public abstract class Entity implements Viewable { case 2: fillCustomNameMetaData(buffer); break; + case 5: + fillNoGravityMetaData(buffer); + break; } } @@ -367,6 +376,12 @@ public abstract class Entity implements Viewable { Utils.writeString(buffer, customName); } + private void fillNoGravityMetaData(Buffer buffer) { + buffer.putByte((byte) 5); + buffer.putByte(METADATA_BOOLEAN); + buffer.putBoolean(noGravity); + } + private boolean shouldUpdate() { return (float) (System.currentTimeMillis() - lastUpdate) >= Main.TICK_MS * 0.9f; // Margin of error } diff --git a/src/main/java/fr/themode/minestom/entity/EntityManager.java b/src/main/java/fr/themode/minestom/entity/EntityManager.java index 01a43ea82..859c71901 100644 --- a/src/main/java/fr/themode/minestom/entity/EntityManager.java +++ b/src/main/java/fr/themode/minestom/entity/EntityManager.java @@ -13,9 +13,9 @@ public class EntityManager { private static InstanceManager instanceManager = Main.getInstanceManager(); - private ExecutorService objectsPool = Executors.newFixedThreadPool(2); - private ExecutorService creaturesPool = Executors.newFixedThreadPool(2); - private ExecutorService playersPool = Executors.newFixedThreadPool(2); + private ExecutorService objectsPool = Executors.newFixedThreadPool(Main.THREAD_COUNT_OBJECTS_ENTITIES); + private ExecutorService creaturesPool = Executors.newFixedThreadPool(Main.THREAD_COUNT_CREATURES_ENTITIES); + private ExecutorService playersPool = Executors.newFixedThreadPool(Main.THREAD_COUNT_PLAYERS_ENTITIES); // TODO API for custom thread division ( public void update() { diff --git a/src/main/java/fr/themode/minestom/entity/ItemEntity.java b/src/main/java/fr/themode/minestom/entity/ItemEntity.java index b22a9b60a..2b6d5e39d 100644 --- a/src/main/java/fr/themode/minestom/entity/ItemEntity.java +++ b/src/main/java/fr/themode/minestom/entity/ItemEntity.java @@ -2,6 +2,7 @@ package fr.themode.minestom.entity; import fr.adamaq01.ozao.net.Buffer; import fr.themode.minestom.item.ItemStack; +import fr.themode.minestom.net.packet.server.play.EntityRelativeMovePacket; import fr.themode.minestom.utils.Utils; public class ItemEntity extends ObjectEntity { @@ -16,7 +17,11 @@ public class ItemEntity extends ObjectEntity { @Override public void update() { - + // TODO how to keep items at the same position? + EntityRelativeMovePacket entityRelativeMovePacket = new EntityRelativeMovePacket(); + entityRelativeMovePacket.entityId = getEntityId(); + entityRelativeMovePacket.onGround = false; + sendPacketToViewers(entityRelativeMovePacket); } @Override @@ -37,6 +42,11 @@ public class ItemEntity extends ObjectEntity { return itemStack; } + public void setItemStack(ItemStack itemStack) { + this.itemStack = itemStack; + sendMetadataIndex(7); // Refresh itemstack for viewers + } + public boolean isPickable() { return pickable; } diff --git a/src/main/java/fr/themode/minestom/entity/ObjectEntity.java b/src/main/java/fr/themode/minestom/entity/ObjectEntity.java index b8069d880..25379756b 100644 --- a/src/main/java/fr/themode/minestom/entity/ObjectEntity.java +++ b/src/main/java/fr/themode/minestom/entity/ObjectEntity.java @@ -1,16 +1,9 @@ package fr.themode.minestom.entity; -import fr.themode.minestom.Viewable; import fr.themode.minestom.net.packet.server.play.SpawnObjectPacket; import fr.themode.minestom.net.player.PlayerConnection; -import java.util.Collections; -import java.util.Set; -import java.util.concurrent.CopyOnWriteArraySet; - -public abstract class ObjectEntity extends Entity implements Viewable { - - private Set viewers = new CopyOnWriteArraySet<>(); +public abstract class ObjectEntity extends Entity { public ObjectEntity(int entityType) { super(entityType); @@ -37,9 +30,4 @@ public abstract class ObjectEntity extends Entity implements Viewable { public void removeViewer(Player player) { super.removeViewer(player); } - - @Override - public Set getViewers() { - return Collections.unmodifiableSet(viewers); - } } diff --git a/src/main/java/fr/themode/minestom/entity/Player.java b/src/main/java/fr/themode/minestom/entity/Player.java index f9ae177e2..c35a1687e 100644 --- a/src/main/java/fr/themode/minestom/entity/Player.java +++ b/src/main/java/fr/themode/minestom/entity/Player.java @@ -20,9 +20,9 @@ import fr.themode.minestom.world.Dimension; import fr.themode.minestom.world.LevelType; import java.util.Collections; -import java.util.LinkedList; import java.util.Set; import java.util.UUID; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CopyOnWriteArraySet; public class Player extends LivingEntity { @@ -31,7 +31,7 @@ public class Player extends LivingEntity { private String username; private PlayerConnection playerConnection; - private LinkedList packets = new LinkedList<>(); + private ConcurrentLinkedQueue packets = new ConcurrentLinkedQueue<>(); private Dimension dimension; private GameMode gameMode; @@ -87,6 +87,7 @@ public class Player extends LivingEntity { ((EntityCreature) entity).kill(); sendMessage("You killed an entity!"); } + sendMessage("ATTACK"); /*UpdateHealthPacket updateHealthPacket = new UpdateHealthPacket(); updateHealthPacket.health = -1f; updateHealthPacket.food = 5; @@ -101,11 +102,9 @@ public class Player extends LivingEntity { @Override public void update() { - synchronized (packets) { - while (!packets.isEmpty()) { - ClientPlayPacket packet = packets.pollFirst(); - packet.process(this); - } + ClientPlayPacket packet = null; + while ((packet = packets.poll()) != null) { + packet.process(this); } // Target block stage @@ -422,9 +421,7 @@ public class Player extends LivingEntity { } public void addPacketToQueue(ClientPlayPacket packet) { - synchronized (packets) { - this.packets.add(packet); - } + this.packets.add(packet); } public void refreshDimension(Dimension dimension) { diff --git a/src/main/java/fr/themode/minestom/entity/demo/TestArrow.java b/src/main/java/fr/themode/minestom/entity/demo/TestArrow.java new file mode 100644 index 000000000..cfbdba94c --- /dev/null +++ b/src/main/java/fr/themode/minestom/entity/demo/TestArrow.java @@ -0,0 +1,24 @@ +package fr.themode.minestom.entity.demo; + +import fr.themode.minestom.entity.LivingEntity; +import fr.themode.minestom.entity.ObjectEntity; + +public class TestArrow extends ObjectEntity { + + private LivingEntity shooter; + + public TestArrow(LivingEntity shooter) { + super(2); + this.shooter = shooter; + } + + @Override + public int getData() { + return shooter.getEntityId() + 1; + } + + @Override + public void update() { + + } +} diff --git a/src/main/java/fr/themode/minestom/instance/Chunk.java b/src/main/java/fr/themode/minestom/instance/Chunk.java index d95f76167..e4a6e1734 100644 --- a/src/main/java/fr/themode/minestom/instance/Chunk.java +++ b/src/main/java/fr/themode/minestom/instance/Chunk.java @@ -1,10 +1,14 @@ package fr.themode.minestom.instance; +import fr.adamaq01.ozao.net.Buffer; +import fr.adamaq01.ozao.net.packet.Packet; import fr.themode.minestom.Main; import fr.themode.minestom.entity.Entity; import fr.themode.minestom.entity.EntityCreature; import fr.themode.minestom.entity.ObjectEntity; import fr.themode.minestom.entity.Player; +import fr.themode.minestom.net.packet.server.play.ChunkDataPacket; +import fr.themode.minestom.utils.PacketUtils; import java.util.Collections; import java.util.Set; @@ -26,10 +30,14 @@ public class Chunk { // Block entities private Set blockEntities = new CopyOnWriteArraySet<>(); + // Cache + private Buffer fullDataPacket; + public Chunk(Biome biome, int chunkX, int chunkZ) { this.biome = biome; this.chunkX = chunkX; this.chunkZ = chunkZ; + //refreshDataPacket(); // TODO remove } protected void setBlock(byte x, byte y, byte z, short blockId) { @@ -130,6 +138,10 @@ public class Chunk { return Collections.unmodifiableSet(players); } + public Buffer getFullDataPacket() { + return fullDataPacket; + } + private boolean isBlockEntity(short blockId) { // TODO complete return blockId == 2033; @@ -145,4 +157,20 @@ public class Chunk { index |= (z << 12) & 0xF000; return index & 0xffff; } + + public void setFullDataPacket(Buffer fullDataPacket) { + this.fullDataPacket = fullDataPacket; + } + + protected ChunkDataPacket getFreshFullDataPacket() { + ChunkDataPacket fullDataPacket = new ChunkDataPacket(); + fullDataPacket.chunk = this; + fullDataPacket.fullChunk = true; + return fullDataPacket; + } + + protected void refreshDataPacket() { + Packet packet = PacketUtils.writePacket(getFreshFullDataPacket()); + this.fullDataPacket = PacketUtils.encode(packet); // TODO write packet buffer in another thread (heavy calculations) + } } diff --git a/src/main/java/fr/themode/minestom/instance/ChunkBatch.java b/src/main/java/fr/themode/minestom/instance/ChunkBatch.java index c504fe09e..92bd70c52 100644 --- a/src/main/java/fr/themode/minestom/instance/ChunkBatch.java +++ b/src/main/java/fr/themode/minestom/instance/ChunkBatch.java @@ -1,5 +1,7 @@ package fr.themode.minestom.instance; +import fr.themode.minestom.Main; + import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; @@ -7,7 +9,7 @@ import java.util.concurrent.Executors; public class ChunkBatch implements BlockModifier { - private static volatile ExecutorService batchesPool = Executors.newFixedThreadPool(3); + private static volatile ExecutorService batchesPool = Executors.newFixedThreadPool(Main.THREAD_COUNT_CHUNK_BATCH); private Instance instance; private Chunk chunk; @@ -47,6 +49,7 @@ public class ChunkBatch implements BlockModifier { for (BlockData data : dataList) { data.apply(chunk); } + chunk.refreshDataPacket(); // TODO partial refresh instead of full instance.sendChunkUpdate(chunk); // TODO partial chunk data }); } diff --git a/src/main/java/fr/themode/minestom/instance/Instance.java b/src/main/java/fr/themode/minestom/instance/Instance.java index ba5dcfad4..5cc121b8f 100644 --- a/src/main/java/fr/themode/minestom/instance/Instance.java +++ b/src/main/java/fr/themode/minestom/instance/Instance.java @@ -1,10 +1,12 @@ package fr.themode.minestom.instance; +import fr.adamaq01.ozao.net.Buffer; import fr.themode.minestom.entity.Entity; import fr.themode.minestom.entity.EntityCreature; import fr.themode.minestom.entity.ObjectEntity; import fr.themode.minestom.entity.Player; import fr.themode.minestom.event.BlockBreakEvent; +import fr.themode.minestom.net.PacketWriter; import fr.themode.minestom.net.packet.server.play.ChunkDataPacket; import fr.themode.minestom.net.packet.server.play.DestroyEntitiesPacket; import fr.themode.minestom.net.packet.server.play.ParticlePacket; @@ -39,7 +41,10 @@ public class Instance implements BlockModifier { Chunk chunk = getChunkAt(x, z); synchronized (chunk) { chunk.setBlock((byte) (x % 16), (byte) y, (byte) (z % 16), blockId); - sendChunkUpdate(chunk); + PacketWriter.writeCallbackPacket(chunk.getFreshFullDataPacket(), buffer -> { + chunk.setFullDataPacket(buffer); + sendChunkUpdate(chunk); + }); } } @@ -48,7 +53,10 @@ public class Instance implements BlockModifier { Chunk chunk = getChunkAt(x, z); synchronized (chunk) { chunk.setBlock((byte) (x % 16), (byte) y, (byte) (z % 16), blockId); - sendChunkUpdate(chunk); + PacketWriter.writeCallbackPacket(chunk.getFreshFullDataPacket(), buffer -> { + chunk.setFullDataPacket(buffer); + sendChunkUpdate(chunk); + }); } } @@ -56,6 +64,7 @@ public class Instance implements BlockModifier { BlockBreakEvent blockBreakEvent = new BlockBreakEvent(blockPosition); player.callEvent(BlockBreakEvent.class, blockBreakEvent); if (!blockBreakEvent.isCancelled()) { + // TODO blockbreak setBlock result int x = blockPosition.getX(); int y = blockPosition.getY(); int z = blockPosition.getZ(); @@ -67,7 +76,7 @@ public class Instance implements BlockModifier { particlePacket.y = y; particlePacket.z = z + 0.5f; particlePacket.offsetX = 0.4f; - particlePacket.offsetY = 0.65f; + particlePacket.offsetY = 0.6f; particlePacket.offsetZ = 0.4f; particlePacket.particleData = 0.3f; particlePacket.particleCount = 75; @@ -221,18 +230,29 @@ public class Instance implements BlockModifier { } protected void sendChunkUpdate(Chunk chunk) { - ChunkDataPacket chunkDataPacket = new ChunkDataPacket(); - chunkDataPacket.fullChunk = false; - chunkDataPacket.chunk = chunk; - getPlayers().forEach(player -> player.getPlayerConnection().sendPacket(chunkDataPacket)); // TODO write packet buffer in another thread (Chunk packets are heavy) + Buffer chunkData = chunk.getFullDataPacket(); + chunkData.getData().retain(getPlayers().size()).markReaderIndex(); + getPlayers().forEach(player -> { + player.getPlayerConnection().sendUnencodedPacket(chunkData); + chunkData.getData().resetReaderIndex(); + }); } private void sendChunks(Player player) { - ChunkDataPacket chunkDataPacket = new ChunkDataPacket(); - chunkDataPacket.fullChunk = true; for (Chunk chunk : getChunks()) { - chunkDataPacket.chunk = chunk; - player.getPlayerConnection().sendPacket(chunkDataPacket); // TODO write packet buffer in another thread (Chunk packets are heavy) + Buffer chunkData = chunk.getFullDataPacket(); + if (chunkData == null) { + PacketWriter.writeCallbackPacket(chunk.getFreshFullDataPacket(), buffer -> { + buffer.getData().retain(1).markReaderIndex(); + player.getPlayerConnection().sendUnencodedPacket(buffer); + buffer.getData().resetReaderIndex(); + chunk.setFullDataPacket(buffer); + }); + } else { + chunkData.getData().retain(1).markReaderIndex(); + player.getPlayerConnection().sendUnencodedPacket(chunkData); + chunkData.getData().resetReaderIndex(); + } } } diff --git a/src/main/java/fr/themode/minestom/instance/demo/ChunkGeneratorDemo.java b/src/main/java/fr/themode/minestom/instance/demo/ChunkGeneratorDemo.java index 5a7bce95e..fcdd8c526 100644 --- a/src/main/java/fr/themode/minestom/instance/demo/ChunkGeneratorDemo.java +++ b/src/main/java/fr/themode/minestom/instance/demo/ChunkGeneratorDemo.java @@ -14,10 +14,8 @@ public class ChunkGeneratorDemo extends ChunkGenerator { public void generateChunkData(ChunkBatch batch, int chunkX, int chunkZ) { for (byte x = 0; x < 16; x++) for (byte z = 0; z < 16; z++) { - if (random.nextInt(10) > 5) { - batch.setBlock(x, (byte) 4, z, (short) 10); - } else { - batch.setBlock(x, (byte) 4, z, "custom_block"); + for (byte y = 0; y < 65; y++) { + batch.setBlock(x, y, z, "custom_block"); } } } diff --git a/src/main/java/fr/themode/minestom/listener/ChatMessageListener.java b/src/main/java/fr/themode/minestom/listener/ChatMessageListener.java index 29ccdbade..6525e1ace 100644 --- a/src/main/java/fr/themode/minestom/listener/ChatMessageListener.java +++ b/src/main/java/fr/themode/minestom/listener/ChatMessageListener.java @@ -7,6 +7,7 @@ import fr.themode.minestom.net.packet.client.play.ClientChatMessagePacket; public class ChatMessageListener { public static void listener(ClientChatMessagePacket packet, Player player) { + // TODO commands check Main.getConnectionManager().getOnlinePlayers().forEach(p -> p.sendMessage(String.format("<%s> %s", player.getUsername(), packet.message))); } diff --git a/src/main/java/fr/themode/minestom/net/PacketProcessor.java b/src/main/java/fr/themode/minestom/net/PacketProcessor.java index c271ee3bc..7f28b6e08 100644 --- a/src/main/java/fr/themode/minestom/net/PacketProcessor.java +++ b/src/main/java/fr/themode/minestom/net/PacketProcessor.java @@ -44,14 +44,15 @@ public class PacketProcessor { public void process(Connection connection, Packet packet) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { int id = packet.get(PACKET_ID_IDENTIFIER); - if (!printBlackList.contains(id)) { - //System.out.println("RECEIVED ID: 0x" + Integer.toHexString(id)); - } Buffer buffer = packet.getPayload(); connectionPlayerConnectionMap.get(connection); PlayerConnection playerConnection = connectionPlayerConnectionMap.computeIfAbsent(connection, c -> new PlayerConnection(c)); ConnectionState connectionState = playerConnection.getConnectionState(); + if (!printBlackList.contains(id)) { + System.out.println("RECEIVED ID: 0x" + Integer.toHexString(id) + " State: " + connectionState); + } + if (connectionState == ConnectionState.UNKNOWN) { // Should be handshake packet if (id == 0) { diff --git a/src/main/java/fr/themode/minestom/net/PacketWriter.java b/src/main/java/fr/themode/minestom/net/PacketWriter.java new file mode 100644 index 000000000..c29f46a8a --- /dev/null +++ b/src/main/java/fr/themode/minestom/net/PacketWriter.java @@ -0,0 +1,24 @@ +package fr.themode.minestom.net; + +import fr.adamaq01.ozao.net.Buffer; +import fr.adamaq01.ozao.net.packet.Packet; +import fr.themode.minestom.Main; +import fr.themode.minestom.net.packet.server.ServerPacket; +import fr.themode.minestom.utils.PacketUtils; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.function.Consumer; + +public class PacketWriter { + + private static volatile ExecutorService batchesPool = Executors.newFixedThreadPool(Main.THREAD_COUNT_PACKET_WRITER); + + public static void writeCallbackPacket(ServerPacket serverPacket, Consumer consumer) { + batchesPool.submit(() -> { + Packet p = PacketUtils.writePacket(serverPacket); + consumer.accept(PacketUtils.encode(p)); + }); + } + +} 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 5e3484f60..4e1d1a36b 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 @@ -9,6 +9,7 @@ import fr.themode.minestom.entity.GameMode; import fr.themode.minestom.entity.ItemEntity; import fr.themode.minestom.entity.Player; import fr.themode.minestom.entity.demo.ChickenCreature; +import fr.themode.minestom.entity.demo.TestArrow; import fr.themode.minestom.instance.Instance; import fr.themode.minestom.instance.demo.ChunkGeneratorDemo; import fr.themode.minestom.inventory.PlayerInventory; @@ -19,6 +20,7 @@ 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.DeclareCommandsPacket; 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.SpawnPositionPacket; @@ -31,6 +33,9 @@ import java.util.UUID; public class LoginStartPacket implements ClientPreplayPacket { + // Test + private static Instance instance; + static { ChunkGeneratorDemo chunkGeneratorDemo = new ChunkGeneratorDemo(); instance = Main.getInstanceManager().createInstance(); @@ -45,9 +50,6 @@ public class LoginStartPacket implements ClientPreplayPacket { System.out.println("Time to load all chunks: " + (System.currentTimeMillis() - time) + " ms"); } - // Test - private static Instance instance; - public String username; @Override @@ -72,7 +74,7 @@ public class LoginStartPacket implements ClientPreplayPacket { Dimension dimension = Dimension.OVERWORLD; LevelType levelType = LevelType.DEFAULT; float x = 5; - float y = 5; + float y = 65; float z = 5; player.refreshDimension(dimension); @@ -117,12 +119,15 @@ public class LoginStartPacket implements ClientPreplayPacket { playerInfoPacket.playerInfos.add(addPlayer); connection.sendPacket(playerInfoPacket); + // Next is optional TODO put all that somewhere else (LoginEvent) + // TODO LoginEvent in another thread (here we are in netty thread) + player.setInstance(instance); for (int cx = 0; cx < 4; cx++) for (int cz = 0; cz < 4; cz++) { ChickenCreature chickenCreature = new ChickenCreature(); - chickenCreature.refreshPosition(0 + (float) cx * 1, 5, 0 + (float) cz * 1); + chickenCreature.refreshPosition(0 + (float) cx * 1, 65, 0 + (float) cz * 1); //chickenCreature.setOnFire(true); chickenCreature.setInstance(instance); //chickenCreature.addPassenger(player); @@ -144,10 +149,36 @@ public class LoginStartPacket implements ClientPreplayPacket { for (int ix = 0; ix < 4; ix++) for (int iz = 0; iz < 4; iz++) { ItemEntity itemEntity = new ItemEntity(new ItemStack(1, (byte) 32)); - itemEntity.refreshPosition(ix, 5, iz); + itemEntity.refreshPosition(ix, 66, iz); + itemEntity.setNoGravity(true); itemEntity.setInstance(instance); //itemEntity.remove(); } + + TestArrow arrow = new TestArrow(player); + arrow.refreshPosition(5, 65, 5); + arrow.setInstance(instance); + arrow.setNoGravity(true); + + DeclareCommandsPacket declareCommandsPacket = new DeclareCommandsPacket(); + DeclareCommandsPacket.Node argumentNode = new DeclareCommandsPacket.Node(); + argumentNode.flags = 0b1010; + argumentNode.children = new int[0]; + argumentNode.name = "arg name"; + argumentNode.parser = "minecraft:nbt_path"; + DeclareCommandsPacket.Node literalNode = new DeclareCommandsPacket.Node(); + literalNode.flags = 0b1; + literalNode.children = new int[]{2}; + literalNode.name = "hey"; + DeclareCommandsPacket.Node rootNode = new DeclareCommandsPacket.Node(); + rootNode.flags = 0; + rootNode.children = new int[]{1}; + + declareCommandsPacket.nodes = new DeclareCommandsPacket.Node[]{rootNode, literalNode, argumentNode}; + declareCommandsPacket.rootIndex = 0; + + + connection.sendPacket(declareCommandsPacket); } @Override diff --git a/src/main/java/fr/themode/minestom/net/packet/server/play/DeclareCommandsPacket.java b/src/main/java/fr/themode/minestom/net/packet/server/play/DeclareCommandsPacket.java new file mode 100644 index 000000000..e2e80df19 --- /dev/null +++ b/src/main/java/fr/themode/minestom/net/packet/server/play/DeclareCommandsPacket.java @@ -0,0 +1,77 @@ +package fr.themode.minestom.net.packet.server.play; + +import fr.adamaq01.ozao.net.Buffer; +import fr.themode.minestom.net.packet.server.ServerPacket; +import fr.themode.minestom.utils.Utils; + +import java.util.function.Consumer; + +public class DeclareCommandsPacket implements ServerPacket { + + + public Node[] nodes; + public int rootIndex; + + @Override + public void write(Buffer buffer) { + Utils.writeVarInt(buffer, nodes.length); + for (Node node : nodes) { + node.write(buffer); + } + Utils.writeVarInt(buffer, rootIndex); + } + + @Override + public int getId() { + return 0x11; + } + + public static class Node { + + public byte flags; + public int[] children; + public int redirectedNode; // Only if flags & 0x08 + public String name; // Only for literal and argument + public String parser; // Only for argument + public Consumer properties; // Only for argument + public String suggestionsType; // Only if flags 0x10 + + private void write(Buffer buffer) { + buffer.putByte(flags); + Utils.writeVarInt(buffer, children.length); + for (int child : children) { + Utils.writeVarInt(buffer, child); + } + + if ((flags & 0x08) != 0) { + Utils.writeVarInt(buffer, redirectedNode); + } + + if (isLiteral() || isArgument()) { + Utils.writeString(buffer, name); + } + + if (isArgument()) { + Utils.writeString(buffer, parser); + if (properties != null) { + properties.accept(buffer); + } + } + + if ((flags & 0x10) != 0) { + Utils.writeString(buffer, suggestionsType); + } + + } + + private boolean isLiteral() { + return (flags & 0b1) != 0; + } + + private boolean isArgument() { + return (flags & 0b10) != 0; + } + + } + +} diff --git a/src/main/java/fr/themode/minestom/net/player/PlayerConnection.java b/src/main/java/fr/themode/minestom/net/player/PlayerConnection.java index b9ff66e61..dcff589f2 100644 --- a/src/main/java/fr/themode/minestom/net/player/PlayerConnection.java +++ b/src/main/java/fr/themode/minestom/net/player/PlayerConnection.java @@ -1,10 +1,14 @@ package fr.themode.minestom.net.player; +import fr.adamaq01.ozao.net.Buffer; import fr.adamaq01.ozao.net.packet.Packet; import fr.adamaq01.ozao.net.server.Connection; import fr.themode.minestom.net.ConnectionState; import fr.themode.minestom.net.packet.server.ServerPacket; import fr.themode.minestom.utils.PacketUtils; +import io.netty.channel.socket.SocketChannel; + +import java.lang.reflect.Field; public class PlayerConnection { @@ -20,6 +24,30 @@ public class PlayerConnection { this.connection.sendPacket(packet); } + + // TODO make that proper (remove reflection) + private Field field; + + { + try { + field = Class.forName("fr.adamaq01.ozao.net.server.backend.tcp.TCPConnection").getDeclaredField("channel"); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + field.setAccessible(true); + } + + public void sendUnencodedPacket(Buffer packet) { + try { + SocketChannel channel = ((SocketChannel) field.get(connection)); + channel.writeAndFlush(packet.getData()); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + public void sendPacket(ServerPacket serverPacket) { sendPacket(PacketUtils.writePacket(serverPacket)); } diff --git a/src/main/java/fr/themode/minestom/net/protocol/MinecraftProtocol.java b/src/main/java/fr/themode/minestom/net/protocol/MinecraftProtocol.java index b6b4224e9..04f28fa02 100644 --- a/src/main/java/fr/themode/minestom/net/protocol/MinecraftProtocol.java +++ b/src/main/java/fr/themode/minestom/net/protocol/MinecraftProtocol.java @@ -3,12 +3,12 @@ package fr.themode.minestom.net.protocol; import fr.adamaq01.ozao.net.Buffer; import fr.adamaq01.ozao.net.packet.Packet; import fr.adamaq01.ozao.net.protocol.Protocol; +import fr.themode.minestom.utils.PacketUtils; import java.util.ArrayList; import java.util.Collection; import static fr.themode.minestom.utils.Utils.readVarInt; -import static fr.themode.minestom.utils.Utils.writeVarInt; public class MinecraftProtocol extends Protocol { @@ -29,7 +29,7 @@ public class MinecraftProtocol extends Protocol { @Override public boolean verify(Packet packet) { - return packet.get("id") != null; + return PacketUtils.verify(packet); } @Override @@ -56,12 +56,6 @@ public class MinecraftProtocol extends Protocol { @Override public Buffer encode(Packet packet) { - Buffer buffer = Buffer.create(); - Buffer idAndPayload = Buffer.create(); - writeVarInt(idAndPayload, packet.get(PACKET_ID_IDENTIFIER)); - idAndPayload.putBuffer(packet.getPayload()); - writeVarInt(buffer, idAndPayload.length()); - buffer.putBuffer(idAndPayload); - return buffer; + return PacketUtils.encode(packet); } } diff --git a/src/main/java/fr/themode/minestom/utils/BlockPosition.java b/src/main/java/fr/themode/minestom/utils/BlockPosition.java index 8f88fb080..4e1082a74 100644 --- a/src/main/java/fr/themode/minestom/utils/BlockPosition.java +++ b/src/main/java/fr/themode/minestom/utils/BlockPosition.java @@ -43,6 +43,6 @@ public class BlockPosition { @Override public String toString() { - return "Position[" + x + ":" + y + ":" + z + "]"; + return "BlockPosition[" + x + ":" + y + ":" + z + "]"; } } diff --git a/src/main/java/fr/themode/minestom/utils/PacketUtils.java b/src/main/java/fr/themode/minestom/utils/PacketUtils.java index d8149d3ac..e6681336e 100644 --- a/src/main/java/fr/themode/minestom/utils/PacketUtils.java +++ b/src/main/java/fr/themode/minestom/utils/PacketUtils.java @@ -5,6 +5,7 @@ import fr.adamaq01.ozao.net.packet.Packet; import fr.themode.minestom.net.packet.server.ServerPacket; import static fr.themode.minestom.net.protocol.MinecraftProtocol.PACKET_ID_IDENTIFIER; +import static fr.themode.minestom.utils.Utils.writeVarInt; public class PacketUtils { @@ -13,10 +14,22 @@ public class PacketUtils { Packet packet = Packet.create(); Buffer buffer = packet.getPayload(); serverPacket.write(buffer); - /*if (id != 40 && id != 64) - System.out.println("ID: 0x" + Integer.toHexString(id));*/ packet.put(PACKET_ID_IDENTIFIER, id); return packet; } + public static boolean verify(Packet packet) { + return packet.get("id") != null; + } + + public static Buffer encode(Packet packet) { + Buffer buffer = Buffer.create(); + Buffer idAndPayload = Buffer.create(); + writeVarInt(idAndPayload, packet.get(PACKET_ID_IDENTIFIER)); + idAndPayload.putBuffer(packet.getPayload()); + writeVarInt(buffer, idAndPayload.length()); + buffer.putBuffer(idAndPayload); + return buffer; + } + } diff --git a/src/main/java/fr/themode/minestom/utils/Position.java b/src/main/java/fr/themode/minestom/utils/Position.java index c7ad503e1..e2426c6c3 100644 --- a/src/main/java/fr/themode/minestom/utils/Position.java +++ b/src/main/java/fr/themode/minestom/utils/Position.java @@ -21,10 +21,20 @@ public class Position { this(0, 0, 0); } + public void add(float x, float y, float z) { + this.x += x; + this.y += y; + this.z += z; + } + public float getDistance(Position position) { return (float) Math.sqrt(Math.pow(position.getX() - getX(), 2) + Math.pow(position.getY() - getY(), 2) + Math.pow(position.getZ() - getZ(), 2)); } + public Position clone() { + return new Position(getX(), getY(), getZ(), getYaw(), getPitch()); + } + public float getX() { return x; } @@ -64,4 +74,9 @@ public class Position { public void setPitch(float pitch) { this.pitch = pitch; } + + @Override + public String toString() { + return "Position[" + x + ":" + y + ":" + z + "] (" + yaw + "/" + pitch + ")"; + } }