diff --git a/src/main/java/fr/themode/minestom/Main.java b/src/main/java/fr/themode/minestom/Main.java index 11417846e..9b2bad581 100644 --- a/src/main/java/fr/themode/minestom/Main.java +++ b/src/main/java/fr/themode/minestom/Main.java @@ -5,6 +5,7 @@ import fr.adamaq01.ozao.net.server.Connection; import fr.adamaq01.ozao.net.server.Server; 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.net.ConnectionManager; import fr.themode.minestom.net.PacketProcessor; import fr.themode.minestom.net.packet.server.play.KeepAlivePacket; @@ -14,10 +15,15 @@ import java.lang.reflect.InvocationTargetException; public class Main { + // In-Game Manager + private static EntityManager entityManager; + + // Others private static ConnectionManager connectionManager; private static PacketProcessor packetProcessor; public static void main(String[] args) { + entityManager = new EntityManager(); connectionManager = new ConnectionManager(); packetProcessor = new PacketProcessor(connectionManager); @@ -70,7 +76,18 @@ public class Main { KeepAlivePacket keepAlivePacket = new KeepAlivePacket(id); player.getPlayerConnection().sendPacket(keepAlivePacket); }); + + // Entities update + entityManager.update(); } } } + + public static EntityManager getEntityManager() { + return entityManager; + } + + public static ConnectionManager getConnectionManager() { + return connectionManager; + } } diff --git a/src/main/java/fr/themode/minestom/entity/Entity.java b/src/main/java/fr/themode/minestom/entity/Entity.java new file mode 100644 index 000000000..92881fe3f --- /dev/null +++ b/src/main/java/fr/themode/minestom/entity/Entity.java @@ -0,0 +1,83 @@ +package fr.themode.minestom.entity; + +import fr.themode.minestom.Main; + +import java.util.UUID; + +public class Entity { + + private static volatile int lastEntityId; + protected double x, y, z; + private int id; + private UUID uuid; + private boolean isActive; // False if entity has only been instanced without being added somewhere + private boolean shouldRemove; + + public Entity() { + this.id = generateId(); + this.uuid = UUID.randomUUID(); + } + + private static int generateId() { + return ++lastEntityId; + } + + public int getEntityId() { + return id; + } + + public UUID getUuid() { + return uuid; + } + + public boolean isActive() { + return isActive; + } + + public void addToWorld() { + this.isActive = true; + EntityManager entityManager = Main.getEntityManager(); + if (this instanceof LivingEntity) { + entityManager.addLivingEntity((LivingEntity) this); + } + } + + public void setPosition(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + + public double getX() { + return x; + } + + public void setX(double x) { + this.x = x; + } + + public double getY() { + return y; + } + + public void setY(double y) { + this.y = y; + } + + public double getZ() { + return z; + } + + public void setZ(double z) { + this.z = z; + } + + public void remove() { + this.shouldRemove = true; + } + + protected boolean shouldRemove() { + return shouldRemove; + } + +} diff --git a/src/main/java/fr/themode/minestom/entity/EntityCreature.java b/src/main/java/fr/themode/minestom/entity/EntityCreature.java new file mode 100644 index 000000000..f775ec732 --- /dev/null +++ b/src/main/java/fr/themode/minestom/entity/EntityCreature.java @@ -0,0 +1,55 @@ +package fr.themode.minestom.entity; + +import fr.themode.minestom.net.packet.server.play.EntityPacket; +import fr.themode.minestom.net.packet.server.play.SpawnMobPacket; +import fr.themode.minestom.net.player.PlayerConnection; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +public abstract class EntityCreature extends LivingEntity { + + private Set viewers = Collections.synchronizedSet(new HashSet<>()); + + private int entityType; + + public EntityCreature(int entityType) { + super(); + this.entityType = entityType; + } + + public void addViewer(Player player) { + this.viewers.add(player); + PlayerConnection playerConnection = player.getPlayerConnection(); + + EntityPacket entityPacket = new EntityPacket(); + entityPacket.entityId = getEntityId(); + SpawnMobPacket spawnMobPacket = new SpawnMobPacket(); + spawnMobPacket.entityId = getEntityId(); + spawnMobPacket.entityUuid = getUuid(); + spawnMobPacket.entityType = getEntityType(); + spawnMobPacket.x = getX(); + spawnMobPacket.y = getY(); + spawnMobPacket.z = getZ(); + spawnMobPacket.yaw = getYaw(); + spawnMobPacket.pitch = getPitch(); + spawnMobPacket.headPitch = 0; + playerConnection.sendPacket(entityPacket); + playerConnection.sendPacket(spawnMobPacket); + } + + public void removeViewer(Player player) { + if (!viewers.contains(player)) + return; + // TODO send packet to remove entity + } + + public Set getViewers() { + return Collections.unmodifiableSet(viewers); + } + + public int getEntityType() { + return entityType; + } +} diff --git a/src/main/java/fr/themode/minestom/entity/EntityManager.java b/src/main/java/fr/themode/minestom/entity/EntityManager.java new file mode 100644 index 000000000..9d3b83de7 --- /dev/null +++ b/src/main/java/fr/themode/minestom/entity/EntityManager.java @@ -0,0 +1,36 @@ +package fr.themode.minestom.entity; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class EntityManager { + + private Set livingEntities = Collections.synchronizedSet(new HashSet<>()); + + private ExecutorService pool = Executors.newFixedThreadPool(5); + + public void update() { + + livingEntities.removeIf(livingEntity -> livingEntity.shouldRemove()); + + synchronized (livingEntities) { + Iterator iterator = livingEntities.iterator(); + while (iterator.hasNext()) { + LivingEntity entity = iterator.next(); + pool.submit(entity::update); + } + } + } + + public Set getEntities() { + return Collections.unmodifiableSet(livingEntities); + } + + protected void addLivingEntity(LivingEntity livingEntity) { + this.livingEntities.add(livingEntity); + } +} diff --git a/src/main/java/fr/themode/minestom/entity/LivingEntity.java b/src/main/java/fr/themode/minestom/entity/LivingEntity.java new file mode 100644 index 000000000..81de08902 --- /dev/null +++ b/src/main/java/fr/themode/minestom/entity/LivingEntity.java @@ -0,0 +1,22 @@ +package fr.themode.minestom.entity; + +public abstract class LivingEntity extends Entity { + + protected float yaw, pitch; + protected boolean onGround; + + public LivingEntity() { + super(); + } + + public abstract void update(); + + public float getYaw() { + return yaw; + } + + public float getPitch() { + return pitch; + } + +} diff --git a/src/main/java/fr/themode/minestom/entity/Player.java b/src/main/java/fr/themode/minestom/entity/Player.java index 2be39cf1b..6c462806e 100644 --- a/src/main/java/fr/themode/minestom/entity/Player.java +++ b/src/main/java/fr/themode/minestom/entity/Player.java @@ -2,19 +2,25 @@ package fr.themode.minestom.entity; import fr.themode.minestom.net.player.PlayerConnection; -public class Player { +public class Player extends LivingEntity { + + private boolean isSneaking; + private boolean isSprinting; - private double x, y, z; - private float yaw, pitch; - private boolean onGround; private long lastKeepAlive; private PlayerConnection playerConnection; + // TODO set proper UUID public Player(PlayerConnection playerConnection) { this.playerConnection = playerConnection; } + @Override + public void update() { + // System.out.println("Je suis l'update"); + } + public PlayerConnection getPlayerConnection() { return playerConnection; } @@ -34,6 +40,14 @@ public class Player { this.onGround = onGround; } + public void refreshSneaking(boolean sneaking) { + isSneaking = sneaking; + } + + public void refreshSprinting(boolean sprinting) { + isSprinting = sprinting; + } + public void refreshKeepAlive(long lastKeepAlive) { this.lastKeepAlive = lastKeepAlive; } diff --git a/src/main/java/fr/themode/minestom/entity/demo/ChickenCreature.java b/src/main/java/fr/themode/minestom/entity/demo/ChickenCreature.java new file mode 100644 index 000000000..f7261e5c7 --- /dev/null +++ b/src/main/java/fr/themode/minestom/entity/demo/ChickenCreature.java @@ -0,0 +1,27 @@ +package fr.themode.minestom.entity.demo; + +import fr.themode.minestom.entity.EntityCreature; +import fr.themode.minestom.net.packet.server.play.EntityRelativeMovePacket; + +public class ChickenCreature extends EntityCreature { + + public ChickenCreature() { + super(8); + } + + @Override + public void update() { + //System.out.println("Update poulet"); + onGround = true; + + double speed = 0.01; + double newPos = getZ() + speed; + + EntityRelativeMovePacket entityRelativeMovePacket = new EntityRelativeMovePacket(); + entityRelativeMovePacket.entityId = getEntityId(); + entityRelativeMovePacket.deltaZ = (short) ((newPos * 32 - getZ() * 32) * 128); + entityRelativeMovePacket.onGround = true; + getViewers().forEach(player -> player.getPlayerConnection().sendPacket(entityRelativeMovePacket)); + setZ(newPos); + } +} diff --git a/src/main/java/fr/themode/minestom/net/ConnectionManager.java b/src/main/java/fr/themode/minestom/net/ConnectionManager.java index b8b4d6a00..5c65f5378 100644 --- a/src/main/java/fr/themode/minestom/net/ConnectionManager.java +++ b/src/main/java/fr/themode/minestom/net/ConnectionManager.java @@ -3,10 +3,7 @@ package fr.themode.minestom.net; import fr.themode.minestom.entity.Player; import fr.themode.minestom.net.player.PlayerConnection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; +import java.util.*; public class ConnectionManager { @@ -17,6 +14,10 @@ public class ConnectionManager { return connectionPlayerMap.get(connection); } + public Collection getOnlinePlayers() { + return Collections.unmodifiableCollection(connectionPlayerMap.values()); + } + // Is only used at LoginStartPacket#process public void createPlayer(PlayerConnection connection) { this.connectionPlayerMap.put(connection, new Player(connection)); diff --git a/src/main/java/fr/themode/minestom/net/packet/client/handler/ClientPlayPacketsHandler.java b/src/main/java/fr/themode/minestom/net/packet/client/handler/ClientPlayPacketsHandler.java index ed629af6e..3c6749774 100644 --- a/src/main/java/fr/themode/minestom/net/packet/client/handler/ClientPlayPacketsHandler.java +++ b/src/main/java/fr/themode/minestom/net/packet/client/handler/ClientPlayPacketsHandler.java @@ -15,5 +15,6 @@ public class ClientPlayPacketsHandler extends ClientPacketsHandler { register(0x13, ClientPlayerLookPacket.class); register(0x14, ClientPlayerPacket.class); register(0x2A, ClientAnimationPacket.class); + register(0x1B, ClientEntityActionPacket.class); } } 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 0ac3d3783..2681f0495 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 @@ -3,6 +3,8 @@ 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.entity.GameMode; +import fr.themode.minestom.entity.Player; +import fr.themode.minestom.entity.demo.ChickenCreature; import fr.themode.minestom.net.ConnectionManager; import fr.themode.minestom.net.ConnectionState; import fr.themode.minestom.net.packet.client.ClientPreplayPacket; @@ -26,10 +28,12 @@ public class LoginStartPacket implements ClientPreplayPacket { connection.setConnectionState(ConnectionState.PLAY); connectionManager.createPlayer(connection); + Player player = connectionManager.getPlayer(connection); + player.addToWorld(); // TODO complete login sequence with optionals packets JoinGamePacket joinGamePacket = new JoinGamePacket(); - joinGamePacket.entityId = 32; + joinGamePacket.entityId = player.getEntityId(); joinGamePacket.gameMode = GameMode.CREATIVE; joinGamePacket.dimension = Dimension.OVERWORLD; joinGamePacket.maxPlayers = 0; @@ -71,6 +75,15 @@ public class LoginStartPacket implements ClientPreplayPacket { playerPositionAndLookPacket.flags = 0; playerPositionAndLookPacket.teleportId = 42; connection.sendPacket(playerPositionAndLookPacket); + + for (int x = -20; x < 20; x++) + for (int z = -20; z < 20; z++) { + // TODO test entity + ChickenCreature chickenCreature = new ChickenCreature(); + chickenCreature.setPosition(50 + (double) x * 1, 5, 50 + (double) z * 1); + connectionManager.getOnlinePlayers().forEach(p -> chickenCreature.addViewer(p)); + chickenCreature.addToWorld(); + } } @Override diff --git a/src/main/java/fr/themode/minestom/net/packet/client/play/ClientAnimationPacket.java b/src/main/java/fr/themode/minestom/net/packet/client/play/ClientAnimationPacket.java index 303c3f2bf..4b2a91cfb 100644 --- a/src/main/java/fr/themode/minestom/net/packet/client/play/ClientAnimationPacket.java +++ b/src/main/java/fr/themode/minestom/net/packet/client/play/ClientAnimationPacket.java @@ -18,8 +18,7 @@ public class ClientAnimationPacket implements ClientPlayPacket { this.hand = Hand.values()[Utils.readVarInt(buffer)]; } - public static enum Hand { - + public enum Hand { MAIN, OFF } diff --git a/src/main/java/fr/themode/minestom/net/packet/client/play/ClientEntityActionPacket.java b/src/main/java/fr/themode/minestom/net/packet/client/play/ClientEntityActionPacket.java new file mode 100644 index 000000000..9bbd998ba --- /dev/null +++ b/src/main/java/fr/themode/minestom/net/packet/client/play/ClientEntityActionPacket.java @@ -0,0 +1,52 @@ +package fr.themode.minestom.net.packet.client.play; + +import fr.adamaq01.ozao.net.Buffer; +import fr.themode.minestom.entity.Player; +import fr.themode.minestom.net.packet.client.ClientPlayPacket; +import fr.themode.minestom.utils.Utils; + +public class ClientEntityActionPacket implements ClientPlayPacket { + + public int playerId; + public Action action; + public int horseJumpBoost; + + @Override + public void process(Player player) { + switch (action) { + case START_SNEAKING: + player.refreshSneaking(true); + break; + case STOP_SNEAKING: + player.refreshSneaking(false); + break; + case START_SPRINTING: + player.refreshSprinting(true); + break; + case STOP_SPRINTING: + player.refreshSprinting(false); + break; + // TODO do remaining actions + } + } + + @Override + public void read(Buffer buffer) { + this.playerId = Utils.readVarInt(buffer); + this.action = Action.values()[Utils.readVarInt(buffer)]; + this.horseJumpBoost = Utils.readVarInt(buffer); + } + + public enum Action { + START_SNEAKING, + STOP_SNEAKING, + LEAVE_BED, + START_SPRINTING, + STOP_SPRINTING, + START_JUMP_HORSE, + STOP_JUMP_HORSE, + OPEN_HORSE_INVENTORY, + START_FLYING_ELYTRA; + } + +} 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 new file mode 100644 index 000000000..be6792d3b --- /dev/null +++ b/src/main/java/fr/themode/minestom/net/packet/server/play/EntityLookAndRelativeMovePacket.java @@ -0,0 +1,29 @@ +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; + +public class EntityLookAndRelativeMovePacket implements ServerPacket { + + public int entityId; + public short deltaX, deltaY, deltaZ; + public float yaw, pitch; + public boolean onGround; + + @Override + public void write(Buffer buffer) { + Utils.writeVarInt(buffer, entityId); + buffer.putShort(deltaX); + buffer.putShort(deltaY); + buffer.putShort(deltaZ); + buffer.putFloat(yaw); + buffer.putFloat(pitch); + buffer.putBoolean(onGround); + } + + @Override + public int getId() { + return 0x29; + } +} diff --git a/src/main/java/fr/themode/minestom/net/packet/server/play/EntityPacket.java b/src/main/java/fr/themode/minestom/net/packet/server/play/EntityPacket.java new file mode 100644 index 000000000..409fc24e3 --- /dev/null +++ b/src/main/java/fr/themode/minestom/net/packet/server/play/EntityPacket.java @@ -0,0 +1,20 @@ +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; + +public class EntityPacket implements ServerPacket { + + public int entityId; + + @Override + public void write(Buffer buffer) { + Utils.writeVarInt(buffer, entityId); + } + + @Override + public int getId() { + return 0x2B; + } +} diff --git a/src/main/java/fr/themode/minestom/net/packet/server/play/EntityRelativeMovePacket.java b/src/main/java/fr/themode/minestom/net/packet/server/play/EntityRelativeMovePacket.java new file mode 100644 index 000000000..e580c376f --- /dev/null +++ b/src/main/java/fr/themode/minestom/net/packet/server/play/EntityRelativeMovePacket.java @@ -0,0 +1,26 @@ +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; + +public class EntityRelativeMovePacket implements ServerPacket { + + public int entityId; + public short deltaX, deltaY, deltaZ; + public boolean onGround; + + @Override + public void write(Buffer buffer) { + Utils.writeVarInt(buffer, entityId); + buffer.putShort(deltaX); + buffer.putShort(deltaY); + buffer.putShort(deltaZ); + buffer.putBoolean(onGround); + } + + @Override + public int getId() { + return 0x28; + } +} diff --git a/src/main/java/fr/themode/minestom/net/packet/server/play/EntityVelocityPacket.java b/src/main/java/fr/themode/minestom/net/packet/server/play/EntityVelocityPacket.java new file mode 100644 index 000000000..03c43f59e --- /dev/null +++ b/src/main/java/fr/themode/minestom/net/packet/server/play/EntityVelocityPacket.java @@ -0,0 +1,24 @@ +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; + +public class EntityVelocityPacket implements ServerPacket { + + public int entityId; + public short velocityX, velocityY, velocityZ; + + @Override + public void write(Buffer buffer) { + Utils.writeVarInt(buffer, entityId); + buffer.putShort(velocityX); + buffer.putShort(velocityY); + buffer.putShort(velocityZ); + } + + @Override + public int getId() { + return 0x45; + } +} diff --git a/src/main/java/fr/themode/minestom/net/packet/server/play/SpawnMobPacket.java b/src/main/java/fr/themode/minestom/net/packet/server/play/SpawnMobPacket.java new file mode 100644 index 000000000..8aa791650 --- /dev/null +++ b/src/main/java/fr/themode/minestom/net/packet/server/play/SpawnMobPacket.java @@ -0,0 +1,42 @@ +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.UUID; + +public class SpawnMobPacket implements ServerPacket { + + public int entityId; + public UUID entityUuid; + public int entityType; + public double x, y, z; + public float yaw, pitch; + public float headPitch; + public short velocityX, velocityY, velocityZ; + // TODO metadata + + @Override + public void write(Buffer buffer) { + Utils.writeVarInt(buffer, entityId); + buffer.putLong(entityUuid.getMostSignificantBits()); + buffer.putLong(entityUuid.getLeastSignificantBits()); + Utils.writeVarInt(buffer, entityType); + buffer.putDouble(x); + buffer.putDouble(y); + buffer.putDouble(z); + buffer.putFloat(yaw); + buffer.putFloat(pitch); + buffer.putFloat(headPitch); + buffer.putShort(velocityX); + buffer.putShort(velocityY); + buffer.putShort(velocityZ); + buffer.putByte((byte) 0xff); // TODO metadata + } + + @Override + public int getId() { + return 0x03; + } +}