WIP entity implementation

This commit is contained in:
TheMode 2019-08-10 08:44:35 +02:00
parent 5130931b29
commit d4bf3bd2ec
17 changed files with 472 additions and 11 deletions

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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<Player> 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<Player> getViewers() {
return Collections.unmodifiableSet(viewers);
}
public int getEntityType() {
return entityType;
}
}

View File

@ -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<LivingEntity> livingEntities = Collections.synchronizedSet(new HashSet<>());
private ExecutorService pool = Executors.newFixedThreadPool(5);
public void update() {
livingEntities.removeIf(livingEntity -> livingEntity.shouldRemove());
synchronized (livingEntities) {
Iterator<LivingEntity> iterator = livingEntities.iterator();
while (iterator.hasNext()) {
LivingEntity entity = iterator.next();
pool.submit(entity::update);
}
}
}
public Set<LivingEntity> getEntities() {
return Collections.unmodifiableSet(livingEntities);
}
protected void addLivingEntity(LivingEntity livingEntity) {
this.livingEntities.add(livingEntity);
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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<Player> getOnlinePlayers() {
return Collections.unmodifiableCollection(connectionPlayerMap.values());
}
// Is only used at LoginStartPacket#process
public void createPlayer(PlayerConnection connection) {
this.connectionPlayerMap.put(connection, new Player(connection));

View File

@ -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);
}
}

View File

@ -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

View File

@ -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
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}