diff --git a/core/src/main/java/mc/core/embedded/FakePlayerManager.java b/core/src/main/java/mc/core/embedded/FakePlayerManager.java index a1932e3..5743632 100644 --- a/core/src/main/java/mc/core/embedded/FakePlayerManager.java +++ b/core/src/main/java/mc/core/embedded/FakePlayerManager.java @@ -1,7 +1,3 @@ -/* - * DmitriyMX - * 2018-06-29 - */ package mc.core.embedded; import mc.core.EntityLocation; @@ -65,13 +61,8 @@ public class FakePlayerManager implements PlayerManager { } @Override - public Optional getPlayer(String name) { - return Optional.empty(); - } - - @Override - public Optional getPlayerById(int id) { - return Optional.empty(); + public Player getPlayer(String name) { + return null; } @Override @@ -80,7 +71,7 @@ public class FakePlayerManager implements PlayerManager { } @Override - public int getCountOnlinePlayers() { + public int getCountPlayers() { return 0; } @@ -88,4 +79,9 @@ public class FakePlayerManager implements PlayerManager { public NetChannel getBroadcastChannel() { return FAKE_NET_CHANNEL; } + + @Override + public Player getOfflinePlayer(String name) { + return null; + } } diff --git a/core/src/main/java/mc/core/player/InMemoryPlayerManager.java b/core/src/main/java/mc/core/player/InMemoryPlayerManager.java deleted file mode 100644 index 765201e..0000000 --- a/core/src/main/java/mc/core/player/InMemoryPlayerManager.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * DmitriyMX - * 2018-04-15 - */ -package mc.core.player; - -import lombok.Setter; -import lombok.extern.slf4j.Slf4j; -import mc.core.Config; -import mc.core.EntityLocation; -import mc.core.network.BroadcastNetChannel; -import mc.core.network.NetChannel; -import mc.core.world.World; -import org.springframework.beans.factory.annotation.Autowired; - -import java.util.*; -import java.util.stream.Collectors; - -@Slf4j -public class InMemoryPlayerManager implements PlayerManager, Runnable { - private static final Random rand = new Random(); - private List players; - private final Object lock = new Object(); - @Setter - private int keepAliveInterval = 1; - - @Autowired - public InMemoryPlayerManager(Config config) { - final int c = config.getMaxPlayers() > 50 ? 50 : config.getMaxPlayers(); - players = Collections.synchronizedList(new ArrayList<>(c)); - (new Thread(this, "KeepAlive")).start(); - } - - @Override - public Player createPlayer(String name, EntityLocation location, World world) { - SimplePlayer player = new SimplePlayer(); - player.setId(rand.nextInt(10000)); - player.setUUID(UUID.nameUUIDFromBytes(name.getBytes())); - player.setName(name); - player.getLocation().set(location); - player.setWorld(world); - player.setSettings(new PlayerSettings()); - - synchronized (lock) { - players.add(player); - lock.notify(); - } - return player; - } - - @Override - public void joinServer(Player player) { - ((SimplePlayer) player).setOnline(true); - } - - @Override - public void leftServer(Player player) { - ((SimplePlayer) player).setOnline(false); - } - - @Override - public Optional getPlayer(final String name) { - return players.stream() - .filter(player -> player.getName().equalsIgnoreCase(name)) - .findFirst(); - } - - @Override - public Optional getPlayerById(final int id) { - return players.stream() - .filter(player -> player.getId() == id) - .findFirst(); - } - - @Override - public List getPlayers() { - return players.stream().filter(Player::isOnline).collect(Collectors.toList()); - } - - @Override - public int getCountOnlinePlayers() { - return (int) players.stream().filter(Player::isOnline).count(); - } - - @Override - public NetChannel getBroadcastChannel() { - return new BroadcastNetChannel(players.stream().filter(Player::isOnline)); - } - - @Override - public void run() { - while (!Thread.currentThread().isInterrupted()) { - synchronized (lock) { - while (players.size() == 0) { - try { - lock.wait(); - } catch (InterruptedException e) { - return; - } - } - } - - getBroadcastChannel().sendKeepAlive(); - - try { - Thread.sleep(keepAliveInterval); - } catch (InterruptedException e) { - return; - } - } - } -} diff --git a/core/src/main/java/mc/core/player/Player.java b/core/src/main/java/mc/core/player/Player.java index 175b0cf..eefe499 100644 --- a/core/src/main/java/mc/core/player/Player.java +++ b/core/src/main/java/mc/core/player/Player.java @@ -13,7 +13,7 @@ import java.util.UUID; public interface Player { int getId(); - UUID getUUID(); + UUID getUuid(); String getName(); boolean isOnline(); diff --git a/core/src/main/java/mc/core/player/PlayerManager.java b/core/src/main/java/mc/core/player/PlayerManager.java index 1ac8543..4fe5485 100644 --- a/core/src/main/java/mc/core/player/PlayerManager.java +++ b/core/src/main/java/mc/core/player/PlayerManager.java @@ -1,7 +1,3 @@ -/* - * DmitriyMX - * 2018-04-15 - */ package mc.core.player; import mc.core.EntityLocation; @@ -9,15 +5,16 @@ import mc.core.network.NetChannel; import mc.core.world.World; import java.util.List; -import java.util.Optional; public interface PlayerManager { Player createPlayer(String name, EntityLocation location, World world); void joinServer(Player player); void leftServer(Player player); - Optional getPlayer(String name); - Optional getPlayerById(int id); + + Player getPlayer(String name); List getPlayers(); - int getCountOnlinePlayers(); + int getCountPlayers(); NetChannel getBroadcastChannel(); + + Player getOfflinePlayer(String name); } diff --git a/core/src/main/java/mc/core/world/World.java b/core/src/main/java/mc/core/world/World.java index 85f79cb..aa69161 100644 --- a/core/src/main/java/mc/core/world/World.java +++ b/core/src/main/java/mc/core/world/World.java @@ -9,6 +9,7 @@ import mc.core.world.block.BlockLocation; import mc.core.world.chunk.Chunk; public interface World { + String getName(); WorldType getWorldType(); EntityLocation getSpawn(); diff --git a/flat_world/src/main/java/mc/world/flat/FlatWorld.java b/flat_world/src/main/java/mc/world/flat/FlatWorld.java index ca5d1db..7739827 100644 --- a/flat_world/src/main/java/mc/world/flat/FlatWorld.java +++ b/flat_world/src/main/java/mc/world/flat/FlatWorld.java @@ -15,6 +15,8 @@ import mc.core.world.chunk.ChunkSection; @Slf4j public class FlatWorld implements World { + @Getter + private final String name = "flat"; @Getter private final WorldType worldType = WorldType.FLAT; @Setter diff --git a/h2_playermanager/build.gradle b/h2_playermanager/build.gradle new file mode 100644 index 0000000..727fc10 --- /dev/null +++ b/h2_playermanager/build.gradle @@ -0,0 +1,16 @@ +version '1.0-SNAPSHOT' + +dependencies { + /* Core */ + compile_excludeCopy project(':core') + + /* Spring */ + compile (group: 'org.springframework', name: 'spring-jdbc', version: spring_version) { + exclude group: 'commons-logging' + } + + /* Database */ + compile (group: 'com.h2database', name: 'h2', version: '1.4.196') + + testCompile (group: 'org.springframework', name: 'spring-test', version: spring_version) +} \ No newline at end of file diff --git a/core/src/main/java/mc/core/player/SimplePlayer.java b/h2_playermanager/src/main/java/mc/core/h2db/H2Player.java similarity index 55% rename from core/src/main/java/mc/core/player/SimplePlayer.java rename to h2_playermanager/src/main/java/mc/core/h2db/H2Player.java index 8fb40b6..8880503 100644 --- a/core/src/main/java/mc/core/player/SimplePlayer.java +++ b/h2_playermanager/src/main/java/mc/core/h2db/H2Player.java @@ -1,38 +1,31 @@ -/* - * DmitriyMX - * 2018-04-23 - */ -package mc.core.player; +package mc.core.h2db; import lombok.Data; import mc.core.EntityLocation; import mc.core.exception.ResourceUnloadedException; import mc.core.network.NetChannel; +import mc.core.player.Player; +import mc.core.player.PlayerSettings; import mc.core.world.World; import java.lang.ref.Reference; import java.lang.ref.WeakReference; -import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.UUID; @Data -public class SimplePlayer implements Player { +public class H2Player implements Player { private int id; private UUID uuid; private String name; private boolean online = false; + private List loadedChunks; private NetChannel channel; - private EntityLocation location = EntityLocation.ZERO(); + private EntityLocation location; private Reference $refWorld; private boolean flying = false; private PlayerSettings settings; - private List loadedChunks = new ArrayList<>(); - - @Override - public UUID getUUID() { - return uuid; - } @Override public World getWorld() { @@ -47,10 +40,24 @@ public class SimplePlayer implements Player { @Override public void setWorld(World world) { - this.$refWorld = new WeakReference<>(world); + if (world == null) { + this.$refWorld = null; + } else { + this.$refWorld = new WeakReference<>(world); + } } - public void setUUID(UUID uuid) { - this.uuid = uuid; + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + H2Player player = (H2Player) obj; + return id == player.id && + Objects.equals(uuid, player.uuid); + } + + @Override + public int hashCode() { + return Objects.hash(id, uuid); } } diff --git a/h2_playermanager/src/main/java/mc/core/h2db/H2PlayerDAO.java b/h2_playermanager/src/main/java/mc/core/h2db/H2PlayerDAO.java new file mode 100644 index 0000000..7004ba8 --- /dev/null +++ b/h2_playermanager/src/main/java/mc/core/h2db/H2PlayerDAO.java @@ -0,0 +1,259 @@ +package mc.core.h2db; + +import lombok.extern.slf4j.Slf4j; +import mc.core.EntityLocation; +import mc.core.world.World; +import org.apache.commons.io.IOUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.UUID; + +@Slf4j +@Component +public class H2PlayerDAO { + private static String INSERT_SQL, INSERT2_SQL, + SELECT_BYNAME_SQL, SELECT_BYID_SQL, + UPDATE_SQL, UPDATE_LOCATION_SQL, + DELETE_SQL; + + @Autowired + private JdbcTemplate jdbcTemplate; + @Autowired + private World world; + + @PostConstruct + public void init() throws IOException { + jdbcTemplate.execute(IOUtils.resourceToString("/sqls/create_tables.sql", StandardCharsets.UTF_8)); + } + + private void checkPlayer(H2Player player) throws SQLException { + if (player.getName() == null || player.getName().isEmpty()) { + throw new SQLException("Field 'name' is " + (player.getName() == null ? "null" : "empty")); + } + + if (player.getUuid() == null) { + throw new SQLException("Field 'uuid' is null"); + } + + if (player.getLocation() == null) { + throw new SQLException("Fields 'location_*' is null"); + } + + if (player.getWorld() == null) { + throw new SQLException("Field 'location_world' is null"); + } + } + + private void insertWithoutId(H2Player player) throws SQLException { + KeyHolder keyHolder = new GeneratedKeyHolder(); + + int affectedRows = jdbcTemplate.update(psc -> { + PreparedStatement stmt = psc.prepareStatement(INSERT_SQL, Statement.RETURN_GENERATED_KEYS); + + stmt.setString(1, player.getUuid().toString()); + stmt.setString(2, player.getName()); + stmt.setDouble(3, player.getLocation().getX()); + stmt.setDouble(4, player.getLocation().getY()); + stmt.setDouble(5, player.getLocation().getZ()); + stmt.setFloat(6, player.getLocation().getYaw()); + stmt.setFloat(7, player.getLocation().getPitch()); + stmt.setString(8, player.getWorld().getName()); + + return stmt; + }, keyHolder); + + if (affectedRows == 0) { + throw new SQLException("Serialize player failed: no rows affected."); + } + + player.setId(keyHolder.getKey().intValue()); + } + + private void insertWithId(H2Player player) throws SQLException { + int affectedRows = jdbcTemplate.update(psc -> { + PreparedStatement stmt = psc.prepareStatement(INSERT2_SQL); + + stmt.setString(1, player.getUuid().toString()); + stmt.setString(2, player.getName()); + stmt.setDouble(3, player.getLocation().getX()); + stmt.setDouble(4, player.getLocation().getY()); + stmt.setDouble(5, player.getLocation().getZ()); + stmt.setFloat(6, player.getLocation().getYaw()); + stmt.setFloat(7, player.getLocation().getPitch()); + stmt.setString(8, player.getWorld().getName()); + stmt.setInt(9, player.getId()); + + return stmt; + }); + + if (affectedRows == 0) { + throw new SQLException("Serialize player failed: no rows affected."); + } + } + + public void insert(H2Player player) throws SQLException { + checkPlayer(player); + + if (player.getId() > 0) { + insertWithId(player); + } else { + insertWithoutId(player); + } + } + + private void locationDeserialize(H2Player playerBuffer, ResultSet resultSet) throws SQLException { + if (!world.getName().equals(resultSet.getString("location_world"))) { + log.warn("Unknown world \"{}\"", resultSet.getString("location_world")); + log.warn("Using default (spawn) location for user \"{}\"", playerBuffer.getName()); + playerBuffer.setLocation(world.getSpawn().clone()); + } else { + playerBuffer.setLocation(new EntityLocation( + resultSet.getDouble("location_x"), + resultSet.getDouble("location_y"), + resultSet.getDouble("location_z"), + resultSet.getFloat("location_yaw"), + resultSet.getFloat("location_pitch") + )); + playerBuffer.setWorld(world); + } + } + + public boolean getByName(H2Player playerBuffer) throws SQLException { + if (playerBuffer.getName() == null || playerBuffer.getName().isEmpty()) { + throw new SQLException("Field 'name' is " + (playerBuffer.getName() == null ? "null" : "empty")); + } + + final boolean[] result = new boolean[]{false}; + jdbcTemplate.query(SELECT_BYNAME_SQL, + ps -> ps.setString(1, playerBuffer.getName()), + rs -> { + playerBuffer.setId(rs.getInt("id")); + playerBuffer.setUuid(UUID.fromString(rs.getString("uuid"))); + locationDeserialize(playerBuffer, rs); + result[0] = true; + }); + + return result[0]; + } + + public boolean getById(H2Player playerBuffer) throws SQLException { + if (playerBuffer.getId() == 0) { + throw new SQLException("Field 'id' is zero"); + } + + final boolean[] result = new boolean[]{false}; + jdbcTemplate.query(SELECT_BYID_SQL, + ps -> ps.setInt(1, playerBuffer.getId()), + rs -> { + playerBuffer.setUuid(UUID.fromString(rs.getString("uuid"))); + playerBuffer.setName(rs.getString("name")); + locationDeserialize(playerBuffer, rs); + result[0] = true; + }); + + return result[0]; + } + + public void update(H2Player player) throws SQLException { + if (player.getId() == 0) { + throw new SQLException("Field 'id' is zero"); + } + + checkPlayer(player); + + int affectedRows = jdbcTemplate.update(UPDATE_SQL, pss -> { + pss.setInt(9, player.getId()); + pss.setString(1, player.getUuid().toString()); + pss.setString(2, player.getName()); + pss.setDouble(3, player.getLocation().getX()); + pss.setDouble(4, player.getLocation().getY()); + pss.setDouble(5, player.getLocation().getZ()); + pss.setFloat(6, player.getLocation().getYaw()); + pss.setFloat(7, player.getLocation().getPitch()); + pss.setString(8, player.getWorld().getName()); + }); + + if (affectedRows == 0) { + throw new SQLException("Update player failed, no rows affected."); + } + } + + public void updateLocation(H2Player player) throws SQLException { + if (player.getId() == 0) { + throw new SQLException("Field 'id' is zero"); + } + + if (player.getLocation() == null) { + throw new SQLException("Fields 'location_*' is null"); + } + + if (player.getWorld() == null) { + throw new SQLException("Field 'location_world' is null"); + } + + if (player.getLocation() == null) { + throw new SQLException("Fields 'location_*' is null"); + } + + if (player.getWorld() == null) { + throw new SQLException("Field 'location_world' is null"); + } + + int affectedRows = jdbcTemplate.update(connection -> { + PreparedStatement stmt = connection.prepareStatement(UPDATE_LOCATION_SQL); + + stmt.setInt(7, player.getId()); + stmt.setDouble(1, player.getLocation().getX()); + stmt.setDouble(2, player.getLocation().getY()); + stmt.setDouble(3, player.getLocation().getZ()); + stmt.setFloat(4, player.getLocation().getYaw()); + stmt.setFloat(5, player.getLocation().getPitch()); + stmt.setString(6, player.getWorld().getName()); + + return stmt; + }); + + if (affectedRows == 0) { + throw new SQLException("Update player failed, no rows affected."); + } + } + + public void remove(H2Player player) throws SQLException { + if (player.getId() == 0) { + throw new SQLException("Field 'id' is zero"); + } + + int affectedRows = jdbcTemplate.update(DELETE_SQL, pss -> pss.setInt(1, player.getId())); + + if (affectedRows == 0) { + throw new SQLException("Remove player failed, no rows affected."); + } + + player.setId(0); + } + + static { + try { + INSERT_SQL = IOUtils.resourceToString("/sqls/insert_player.sql", StandardCharsets.UTF_8); + INSERT2_SQL = IOUtils.resourceToString("/sqls/insert_player_withId.sql", StandardCharsets.UTF_8); + SELECT_BYNAME_SQL = IOUtils.resourceToString("/sqls/select_player_byName.sql", StandardCharsets.UTF_8); + SELECT_BYID_SQL = IOUtils.resourceToString("/sqls/select_player_byId.sql", StandardCharsets.UTF_8); + UPDATE_SQL = IOUtils.resourceToString("/sqls/update_player.sql", StandardCharsets.UTF_8); + UPDATE_LOCATION_SQL = IOUtils.resourceToString("/sqls/update_player_location.sql", StandardCharsets.UTF_8); + DELETE_SQL = IOUtils.resourceToString("/sqls/delete_player.sql", StandardCharsets.UTF_8); + } catch (IOException e) { + log.error("Load sql templates", e); + } + } +} diff --git a/h2_playermanager/src/main/java/mc/core/h2db/H2PlayerManager.java b/h2_playermanager/src/main/java/mc/core/h2db/H2PlayerManager.java new file mode 100644 index 0000000..636460b --- /dev/null +++ b/h2_playermanager/src/main/java/mc/core/h2db/H2PlayerManager.java @@ -0,0 +1,179 @@ +package mc.core.h2db; + +import com.google.common.collect.ImmutableList; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import mc.core.EntityLocation; +import mc.core.network.BroadcastNetChannel; +import mc.core.network.NetChannel; +import mc.core.player.Player; +import mc.core.player.PlayerManager; +import mc.core.player.PlayerSettings; +import mc.core.world.World; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +import static org.slf4j.helpers.MessageFormatter.format; + +@Slf4j +@Component +public class H2PlayerManager implements PlayerManager, Runnable { + @Setter + @Autowired + private H2PlayerDAO h2playerDao; + private List playerList = Collections.synchronizedList(new ArrayList<>()); + private final Object lock = new Object(); + @Setter + private int keepAliveInterval = 1; + @Autowired + private World world; + + @PostConstruct + public void init() { + (new Thread(this, "KeepAlive")).start(); + } + + @Override + public Player createPlayer(String name, EntityLocation location, World world) { + //TODO в дальнейшем следует в этом методе только имплементацию Player + H2Player h2Player = new H2Player(); + h2Player.setName(name); + h2Player.setUuid(UUID.randomUUID()); + h2Player.setLocation(location.clone()); + h2Player.setLoadedChunks(new ArrayList<>()); + h2Player.setWorld(world); + h2Player.setSettings(new PlayerSettings()); + + try { + h2playerDao.insert(h2Player); + } catch (SQLException e) { + log.error(format("Insert player '{}'", h2Player.getName()).getMessage(), e); + return null; + } + + return h2Player; + } + + @Override + public void joinServer(Player player) { + //TODO в дальнейшем следует именно этому методу передать функции инсерта в БД + H2Player h2Player = (H2Player) player; + synchronized (lock) { + playerList.add(h2Player); + h2Player.setOnline(true); + lock.notify(); + } + } + + @Override + public void leftServer(Player player) { + H2Player h2Player = (H2Player) player; + try { + h2playerDao.update(h2Player); + } catch (SQLException e) { + log.error(format("Update player '{}'", h2Player.getName()).getMessage(), e); + synchronized (lock) { + playerList.remove(h2Player); + } + } finally { + h2Player.setId(0); + h2Player.setOnline(false); + h2Player.setWorld(null); + h2Player.getLoadedChunks().clear(); + } + } + + @Override + public Player getPlayer(String name) { + return playerList.stream() + .filter(player -> player.getName().equals(name)) + .filter(H2Player::isOnline) + .findFirst().orElse(null); + } + + @Override + public List getPlayers() { + return playerList.stream() + .filter(H2Player::isOnline) + .collect(ImmutableList.toImmutableList()); + } + + @Override + public int getCountPlayers() { + return (int) playerList.stream() + .filter(H2Player::isOnline) + .count(); + } + + @Override + public NetChannel getBroadcastChannel() { + return new BroadcastNetChannel( + playerList.stream() + .filter(H2Player::isOnline) + .map(player -> (Player)player) + ); + } + + @Override + public Player getOfflinePlayer(String name) { + H2Player h2Player = playerList.stream() + .filter(player -> player.getName().equals(name)) + .filter(player -> !player.isOnline()) + .findFirst().orElse(null); + + if (h2Player == null) { + h2Player = playerList.stream() + .filter(player -> !player.isOnline()) + .findAny().orElse(new H2Player()); + h2Player.setName(name); + + boolean result; + try { + result = h2playerDao.getByName(h2Player); + } catch (SQLException e) { + log.error(format("getByName player '{}'", h2Player.getName()).getMessage(), e); + return null; + } + + if (result) { + h2Player.setWorld(world); + return h2Player; + } else { + return null; + } + } else { + h2Player.setWorld(world); + return h2Player; + } + } + + @Override + public void run() { + while (!Thread.currentThread().isInterrupted()) { + while(getCountPlayers() == 0) { + synchronized (lock) { + try { + lock.wait(); + } catch (InterruptedException e) { + return; + } + } + } + + getBroadcastChannel().sendKeepAlive(); + + try { + Thread.sleep(keepAliveInterval); + } catch (InterruptedException e) { + return; + } + } + } +} diff --git a/h2_playermanager/src/main/resources/sqls/create_tables.sql b/h2_playermanager/src/main/resources/sqls/create_tables.sql new file mode 100644 index 0000000..c436f80 --- /dev/null +++ b/h2_playermanager/src/main/resources/sqls/create_tables.sql @@ -0,0 +1,14 @@ +CREATE TABLE IF NOT EXISTS players ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + uuid VARCHAR(36) NOT NULL UNIQUE, + name VARCHAR(16) NOT NULL, + location_x DOUBLE NOT NULL, + location_y DOUBLE NOT NULL, + location_z DOUBLE NOT NULL, + location_yaw FLOAT NOT NULL, + location_pitch FLOAT NOT NULL, + location_world VARCHAR(64) NOT NULL +); + +CREATE INDEX IF NOT EXISTS idx_players_uuid ON players(uuid); +CREATE INDEX IF NOT EXISTS idx_players_name ON players(name); \ No newline at end of file diff --git a/h2_playermanager/src/main/resources/sqls/delete_player.sql b/h2_playermanager/src/main/resources/sqls/delete_player.sql new file mode 100644 index 0000000..d16f13d --- /dev/null +++ b/h2_playermanager/src/main/resources/sqls/delete_player.sql @@ -0,0 +1 @@ +DELETE FROM players WHERE id = ?; \ No newline at end of file diff --git a/h2_playermanager/src/main/resources/sqls/insert_player.sql b/h2_playermanager/src/main/resources/sqls/insert_player.sql new file mode 100644 index 0000000..ab9bdff --- /dev/null +++ b/h2_playermanager/src/main/resources/sqls/insert_player.sql @@ -0,0 +1,2 @@ +INSERT INTO players (uuid, name, location_x, location_y, location_z, location_yaw, location_pitch, location_world) + VALUES (?, ?, ?, ?, ?, ?, ?, ?); \ No newline at end of file diff --git a/h2_playermanager/src/main/resources/sqls/insert_player_withId.sql b/h2_playermanager/src/main/resources/sqls/insert_player_withId.sql new file mode 100644 index 0000000..c0119bc --- /dev/null +++ b/h2_playermanager/src/main/resources/sqls/insert_player_withId.sql @@ -0,0 +1,2 @@ +INSERT INTO players (uuid, name, location_x, location_y, location_z, location_yaw, location_pitch, location_world, id) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?); \ No newline at end of file diff --git a/h2_playermanager/src/main/resources/sqls/select_player_byId.sql b/h2_playermanager/src/main/resources/sqls/select_player_byId.sql new file mode 100644 index 0000000..0267ce6 --- /dev/null +++ b/h2_playermanager/src/main/resources/sqls/select_player_byId.sql @@ -0,0 +1,3 @@ +SELECT uuid, name, location_x, location_y, location_z, location_yaw, location_pitch, location_world +FROM players WHERE id = ? +LIMIT 1; \ No newline at end of file diff --git a/h2_playermanager/src/main/resources/sqls/select_player_byName.sql b/h2_playermanager/src/main/resources/sqls/select_player_byName.sql new file mode 100644 index 0000000..36a9b3d --- /dev/null +++ b/h2_playermanager/src/main/resources/sqls/select_player_byName.sql @@ -0,0 +1,3 @@ +SELECT id, uuid, location_x, location_y, location_z, location_yaw, location_pitch, location_world +FROM players WHERE name LIKE ? +LIMIT 1; \ No newline at end of file diff --git a/h2_playermanager/src/main/resources/sqls/update_player.sql b/h2_playermanager/src/main/resources/sqls/update_player.sql new file mode 100644 index 0000000..0e7bb2d --- /dev/null +++ b/h2_playermanager/src/main/resources/sqls/update_player.sql @@ -0,0 +1,10 @@ +UPDATE players +SET uuid = ?, + name = ?, + location_x = ?, + location_y = ?, + location_z = ?, + location_yaw = ?, + location_pitch = ?, + location_world = ? +WHERE id = ?; \ No newline at end of file diff --git a/h2_playermanager/src/main/resources/sqls/update_player_location.sql b/h2_playermanager/src/main/resources/sqls/update_player_location.sql new file mode 100644 index 0000000..30b2d29 --- /dev/null +++ b/h2_playermanager/src/main/resources/sqls/update_player_location.sql @@ -0,0 +1,8 @@ +UPDATE players +SET location_x = ?, + location_y = ?, + location_z = ?, + location_yaw = ?, + location_pitch = ?, + location_world = ? +WHERE id = ?; \ No newline at end of file diff --git a/h2_playermanager/src/test/java/mc/core/h2db/SpringConfig.java b/h2_playermanager/src/test/java/mc/core/h2db/SpringConfig.java new file mode 100644 index 0000000..58eb283 --- /dev/null +++ b/h2_playermanager/src/test/java/mc/core/h2db/SpringConfig.java @@ -0,0 +1,49 @@ +package mc.core.h2db; + +import mc.core.world.World; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.DriverManagerDataSource; + +import javax.sql.DataSource; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@Configuration +@ComponentScan("mc.core.h2db") +public class SpringConfig { + @Bean + public World mockWorld() { + World mockWorld = mock(World.class); + when(mockWorld.getName()).thenReturn("mock_world"); + return mockWorld; + } + + @Bean + public DataSource dataSource() { + DriverManagerDataSource dmds = new DriverManagerDataSource(); + dmds.setDriverClassName("org.h2.Driver"); + dmds.setUrl("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1"); + dmds.setUsername("sa"); + return dmds; + } + + @Bean + public JdbcTemplate jdbcTemplate(DataSource dataSource) { + JdbcTemplate jdbcTemplate = new JdbcTemplate(); + jdbcTemplate.setDataSource(dataSource); + return jdbcTemplate; + } + + @Bean + @Scope(value = "prototype") + public H2PlayerManager h2PlayerManager(H2PlayerDAO h2PlayerDAO) { + H2PlayerManager playerManager = new H2PlayerManager(); + playerManager.setH2playerDao(h2PlayerDAO); + return playerManager; + } +} diff --git a/h2_playermanager/src/test/java/mc/core/h2db/TestDAO.java b/h2_playermanager/src/test/java/mc/core/h2db/TestDAO.java new file mode 100644 index 0000000..4c9c094 --- /dev/null +++ b/h2_playermanager/src/test/java/mc/core/h2db/TestDAO.java @@ -0,0 +1,286 @@ +package mc.core.h2db; + +import mc.core.EntityLocation; +import mc.core.world.World; +import org.apache.commons.io.IOUtils; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.sql.SQLException; +import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; + +import static org.junit.Assert.*; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = {SpringConfig.class}) +public class TestDAO { + @Autowired + private JdbcTemplate jdbcTemplate; + @Autowired + private World mockWorld; + @Autowired + private H2PlayerDAO playerDAO; + private H2Player player; + + private void createPlayer() { + final ThreadLocalRandom rnd = ThreadLocalRandom.current(); + final double minD = 0.0d, maxD = 10.0d; + final float minF = 0.0f, maxF = 359.9f; + final int minI = 1000, maxI = 9999; + + player = new H2Player(); + player.setUuid(UUID.randomUUID()); + player.setName("player" + rnd.nextInt(minI, maxI)); + player.setLocation(new EntityLocation( + rnd.nextDouble(minD, maxD), + rnd.nextDouble(minD, maxD), + rnd.nextDouble(minD, maxD), + rnd.nextFloat() * (maxF - minF) + minF, + rnd.nextFloat() * (maxF - minF) + minF + )); + player.setWorld(mockWorld); + } + + private void assertPlayer(H2Player actualPlayer) { + final String sql = "SELECT * FROM players WHERE id = ?"; + jdbcTemplate.query(sql, + ps -> ps.setInt(1, player.getId()), + rs -> { + assertEquals(actualPlayer.getId(), rs.getInt("id")); + assertEquals(actualPlayer.getName(), rs.getString("name")); + assertEquals(actualPlayer.getLocation().getX(), rs.getDouble("location_x"), 0.01d); + assertEquals(actualPlayer.getLocation().getY(), rs.getDouble("location_y"), 0.01d); + assertEquals(actualPlayer.getLocation().getZ(), rs.getDouble("location_z"), 0.01d); + assertEquals(actualPlayer.getLocation().getYaw(), rs.getFloat("location_yaw"), 0.01f); + assertEquals(actualPlayer.getLocation().getPitch(), rs.getFloat("location_pitch"), 0.01f); + assertEquals(actualPlayer.getWorld().getName(), rs.getString("location_world")); + }); + } + + @Before + public void before() throws IOException { + jdbcTemplate.execute(IOUtils.resourceToString("/sqls/drop_table_players.sql", StandardCharsets.UTF_8)); + jdbcTemplate.execute(IOUtils.resourceToString("/sqls/create_tables.sql", StandardCharsets.UTF_8)); + createPlayer(); + assertEquals(0, player.getId()); + } + + @Test + public void testInsert() throws SQLException { + playerDAO.insert(player); + assertNotEquals(0, player.getId()); + + assertPlayer(player); + } + + @Test(expected = DuplicateKeyException.class) + public void testInsertDuplicateKey() throws SQLException { + playerDAO.insert(player); + assertNotEquals(0, player.getId()); + + playerDAO.insert(player); + } + + @Test(expected = SQLException.class) + public void testInsertEmptyName() throws SQLException { + player.setName(""); + playerDAO.insert(player); + } + + @Test(expected = SQLException.class) + public void testInsertNullName() throws SQLException { + player.setName(null); + playerDAO.insert(player); + } + + @Test(expected = SQLException.class) + public void testInsertNullUuid() throws SQLException { + player.setUuid(null); + playerDAO.insert(player); + } + + @Test(expected = SQLException.class) + public void testInsertNullLocation() throws SQLException { + player.setLocation(null); + playerDAO.insert(player); + } + + @Test(expected = SQLException.class) + public void testInsertNullWorld() throws SQLException { + player.setWorld(null); + playerDAO.insert(player); + } + + @Test + public void testGetByName() throws SQLException { + playerDAO.insert(this.player); + + H2Player queryPlayer = new H2Player(); + queryPlayer.setName(player.getName()); + + boolean result = playerDAO.getByName(queryPlayer); + + assertTrue(result); + assertEquals(player, queryPlayer); + } + + @Test + public void testGetById() throws SQLException { + playerDAO.insert(this.player); + + H2Player queryPlayer = new H2Player(); + queryPlayer.setId(player.getId()); + + boolean result = playerDAO.getById(queryPlayer); + + assertTrue(result); + assertEquals(player, queryPlayer); + } + + @Test + public void testGetByNonExistsName() throws SQLException { + playerDAO.insert(this.player); + + H2Player player = new H2Player(); + player.setName("NON_EXISTS_NAME"); + + boolean result = playerDAO.getByName(player); + assertFalse(result); + } + + @Test + public void testGetByNonExistsId() throws SQLException { + playerDAO.insert(player); + assertEquals(1, player.getId()); + + H2Player queryPlayer = new H2Player(); + queryPlayer.setId(999); + + boolean result = playerDAO.getById(queryPlayer); + assertFalse(result); + } + + @Test + public void testUpdate() throws SQLException { + playerDAO.insert(player); + assertNotEquals(0, player.getId()); + + player.setName("UNKNOWN_PLAYER"); + playerDAO.update(player); + + assertPlayer(player); + } + + @Test(expected = SQLException.class) + public void testUpdateEmptyName() throws SQLException { + playerDAO.insert(player); + assertNotEquals(0, player.getId()); + + player.setName(""); + playerDAO.update(player); + } + + @Test(expected = SQLException.class) + public void testUpdateNullName() throws SQLException { + playerDAO.insert(player); + assertNotEquals(0, player.getId()); + + player.setName(null); + playerDAO.update(player); + } + + @Test(expected = SQLException.class) + public void testUpdateNullUuid() throws SQLException { + playerDAO.insert(player); + assertNotEquals(0, player.getId()); + + player.setUuid(null); + playerDAO.update(player); + } + + @Test(expected = SQLException.class) + public void testUpdateNullLocation() throws SQLException { + playerDAO.insert(player); + assertNotEquals(0, player.getId()); + + player.setLocation(null); + playerDAO.update(player); + } + + @Test(expected = SQLException.class) + public void testUpdateNullWorld() throws SQLException { + playerDAO.insert(player); + assertNotEquals(0, player.getId()); + + player.setWorld(null); + playerDAO.update(player); + } + + @Test + public void testUpdateLocation() throws SQLException { + playerDAO.insert(player); + assertNotEquals(0, player.getId()); + + final String origName = player.getName(); + player.setName("UNKNOWN_PLAYER"); + player.getLocation().setX(33.1d); + player.getLocation().setZ(28.99d); + playerDAO.updateLocation(player); + + final String sql = "SELECT * FROM players WHERE id = ?"; + jdbcTemplate.query(sql, + ps -> ps.setInt(1, player.getId()), + rs -> { + assertEquals(player.getId(), rs.getInt("id")); + assertEquals(origName, rs.getString("name")); + assertEquals(player.getLocation().getX(), rs.getDouble("location_x"), 0.01d); + assertEquals(player.getLocation().getY(), rs.getDouble("location_y"), 0.01d); + assertEquals(player.getLocation().getZ(), rs.getDouble("location_z"), 0.01d); + assertEquals(player.getLocation().getYaw(), rs.getFloat("location_yaw"), 0.01f); + assertEquals(player.getLocation().getPitch(), rs.getFloat("location_pitch"), 0.01f); + assertEquals(player.getWorld().getName(), rs.getString("location_world")); + }); + } + + @Test(expected = SQLException.class) + public void testUpdateLocationNull() throws SQLException { + playerDAO.insert(player); + assertNotEquals(0, player.getId()); + + player.setLocation(null); + playerDAO.updateLocation(player); + } + + @Test + public void testRemove() throws SQLException { + playerDAO.insert(player); + assertNotEquals(0, player.getId()); + + final int origId = player.getId(); + playerDAO.remove(player); + assertEquals(0, player.getId()); + + final String sql = "SELECT COUNT(*) FROM players WHERE id = ?"; + jdbcTemplate.query(sql, + ps -> ps.setInt(1, origId), + rs -> {assertEquals(0, rs.getInt(1));}); + } + + @Test(expected = SQLException.class) + public void testRemoveNonExistsId() throws SQLException { + playerDAO.insert(player); + assertNotEquals(0, player.getId()); + + player.setId(999); + playerDAO.remove(player); + } +} diff --git a/h2_playermanager/src/test/java/mc/core/h2db/TestH2Player.java b/h2_playermanager/src/test/java/mc/core/h2db/TestH2Player.java new file mode 100644 index 0000000..4149c22 --- /dev/null +++ b/h2_playermanager/src/test/java/mc/core/h2db/TestH2Player.java @@ -0,0 +1,36 @@ +package mc.core.h2db; + +import org.junit.Test; + +import java.util.UUID; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +public class TestH2Player { + @Test + public void testEquals() { + UUID uuid = UUID.randomUUID(); + + H2Player player1 = new H2Player(); + player1.setId(1); + player1.setUuid(uuid); + player1.setName("Player1"); + + H2Player player2 = new H2Player(); + player2.setId(1); + player2.setUuid(uuid); + player2.setName("Player2"); + + assertEquals(player1, player2); + + player2.setId(2); + + assertNotEquals(player1, player2); + + player2.setId(1); + player2.setUuid(UUID.randomUUID()); + + assertNotEquals(player1, player2); + } +} diff --git a/h2_playermanager/src/test/java/mc/core/h2db/TestH2PlayerManager.java b/h2_playermanager/src/test/java/mc/core/h2db/TestH2PlayerManager.java new file mode 100644 index 0000000..163f72c --- /dev/null +++ b/h2_playermanager/src/test/java/mc/core/h2db/TestH2PlayerManager.java @@ -0,0 +1,181 @@ +package mc.core.h2db; + +import mc.core.EntityLocation; +import mc.core.player.Player; +import mc.core.world.World; +import org.apache.commons.io.IOUtils; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.sql.SQLException; +import java.util.List; + +import static org.junit.Assert.*; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = {SpringConfig.class}) +public class TestH2PlayerManager { + @Autowired + private ApplicationContext context; + @Autowired + private JdbcTemplate jdbcTemplate; + @Autowired + private H2PlayerDAO h2PlayerDAO; + @Autowired + private World mockWorld; + private H2PlayerManager playerManager; + + @Before + public void before() throws IOException { + playerManager = context.getBean(H2PlayerManager.class); + + jdbcTemplate.execute(IOUtils.resourceToString("/sqls/drop_table_players.sql", StandardCharsets.UTF_8)); + jdbcTemplate.execute(IOUtils.resourceToString("/sqls/create_tables.sql", StandardCharsets.UTF_8)); + } + + @Test + public void testCreatePlayer() throws SQLException { + final String playerName = "NEW_PLAYER"; + final Player newPlayer = playerManager.createPlayer(playerName, EntityLocation.ZERO(), mockWorld); + + assertNotNull(newPlayer); + assertEquals(H2Player.class, newPlayer.getClass()); + assertTrue(newPlayer.getId() > 0); + + final H2Player queryPlayer = new H2Player(); + queryPlayer.setName(playerName); + h2PlayerDAO.getByName(queryPlayer); + assertTrue(queryPlayer.getId() > 0); + + assertEquals(newPlayer, queryPlayer); + assertEquals(newPlayer.getName(), queryPlayer.getName()); + assertEquals(newPlayer.getLocation(), queryPlayer.getLocation()); + assertEquals(newPlayer.getWorld(), queryPlayer.getWorld()); + } + + @Test + public void testJoinServer() { + assertEquals(0, playerManager.getCountPlayers()); + + final String playerName = "NEW_PLAYER"; + final Player player = playerManager.createPlayer(playerName, EntityLocation.ZERO(), mockWorld); + playerManager.joinServer(player); + + assertEquals(1, playerManager.getCountPlayers()); + assertTrue(player.isOnline()); + } + + @Test + public void testLeftServer() throws SQLException { + assertEquals(0, playerManager.getCountPlayers()); + + final String playerName = "NEW_PLAYER"; + final Player player = playerManager.createPlayer(playerName, EntityLocation.ZERO(), mockWorld); + playerManager.joinServer(player); + + assertEquals(1, playerManager.getCountPlayers()); + assertTrue(player.isOnline()); + + final int playerId = player.getId(); + + final String anotherName = "ANOTHER_NAME"; + ((H2Player)player).setName(anotherName); + + playerManager.leftServer(player); + + assertEquals(0, playerManager.getCountPlayers()); + + assertFalse(player.isOnline()); + assertEquals(0, player.getId()); + assertNull(player.getWorld()); + assertTrue(player.getLoadedChunks().isEmpty()); + assertNull(player.getSettings()); + + H2Player queryPlayer = new H2Player(); + queryPlayer.setId(playerId); + boolean result = h2PlayerDAO.getById(queryPlayer); + + assertTrue(result); + ((H2Player)player).setId(playerId); + assertEquals(player, queryPlayer); + } + + @Test + public void testGetPlayer() { + assertEquals(0, playerManager.getCountPlayers()); + + final String playerName = "NEW_PLAYER"; + final Player player = playerManager.createPlayer(playerName, EntityLocation.ZERO(), mockWorld); + assertNotNull(player); + + playerManager.joinServer(player); + + assertEquals(1, playerManager.getCountPlayers()); + + Player queryPlayer = playerManager.getPlayer(playerName); + + assertEquals(player, queryPlayer); + } + + @Test + public void testGetPlayers() { + assertEquals(0, playerManager.getCountPlayers()); + + final String playerName = "NEW_PLAYER"; + final Player player = playerManager.createPlayer(playerName, EntityLocation.ZERO(), mockWorld); + assertNotNull(player); + + playerManager.joinServer(player); + + assertEquals(1, playerManager.getCountPlayers()); + + List players = playerManager.getPlayers(); + assertEquals(1, players.size()); + try { + players.add(new H2Player()); + fail(); + } catch (Exception e) { + assertTrue(true); + } + + assertEquals(player, players.get(0)); + } + + @Test + public void testGetCountPlayers() { + assertEquals(0, playerManager.getCountPlayers()); + + final String playerName = "NEW_PLAYER"; + final Player player = playerManager.createPlayer(playerName, EntityLocation.ZERO(), mockWorld); + assertNotNull(player); + + playerManager.joinServer(player); + + assertEquals(1, playerManager.getCountPlayers()); + + playerManager.leftServer(player); + + assertEquals(0, playerManager.getCountPlayers()); + } + + @Test + public void testGetOfflinePlayer() { + final String playerName = "NEW_PLAYER"; + final Player player = playerManager.createPlayer(playerName, EntityLocation.ZERO(), mockWorld); + playerManager.joinServer(player); + playerManager.leftServer(player); + + assertEquals(0, playerManager.getCountPlayers()); + + Player offlinePlayer = playerManager.getOfflinePlayer(playerName); + assertEquals(player, offlinePlayer); + } +} diff --git a/h2_playermanager/src/test/resources/sqls/drop_table_players.sql b/h2_playermanager/src/test/resources/sqls/drop_table_players.sql new file mode 100644 index 0000000..dd4ab91 --- /dev/null +++ b/h2_playermanager/src/test/resources/sqls/drop_table_players.sql @@ -0,0 +1,3 @@ +DROP INDEX IF EXISTS idx_players_uuid; +DROP INDEX IF EXISTS idx_players_name; +DROP TABLE IF EXISTS players; \ No newline at end of file diff --git a/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/ChunkDataPacket.java b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/ChunkDataPacket.java index b3d6cd7..ae0ab8d 100644 --- a/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/ChunkDataPacket.java +++ b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/ChunkDataPacket.java @@ -4,7 +4,6 @@ */ package mc.core.network.proto_1_12_2.packets; -import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.extern.slf4j.Slf4j; @@ -17,7 +16,6 @@ import mc.core.world.chunk.Chunk; import mc.core.world.chunk.ChunkSection; import java.util.ArrayList; -import java.util.Collections; import java.util.Comparator; import java.util.List; diff --git a/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/PlayerBlockPlacementPacket.java b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/PlayerBlockPlacementPacket.java index c40fffd..ebcf00d 100644 --- a/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/PlayerBlockPlacementPacket.java +++ b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/PlayerBlockPlacementPacket.java @@ -7,7 +7,6 @@ import mc.core.world.block.BlockLocation; import mc.core.network.CSPacket; import mc.core.network.NetInputStream; import mc.core.network.proto_1_12_2.Direction; -import mc.core.utils.CompactedCoords; @Getter @ToString diff --git a/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/PlayerDiggingPacket.java b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/PlayerDiggingPacket.java index 9ce0dc9..355cc57 100644 --- a/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/PlayerDiggingPacket.java +++ b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/PlayerDiggingPacket.java @@ -8,7 +8,6 @@ import mc.core.world.block.BlockLocation; import mc.core.network.CSPacket; import mc.core.network.NetInputStream; import mc.core.network.proto_1_12_2.Direction; -import mc.core.utils.CompactedCoords; import java.util.Arrays; diff --git a/proto_1.12.2/src/test/java/mc/core/network/proto_1_12_2/packets/ByteArrayInputNetStream.java b/proto_1.12.2/src/test/java/mc/core/network/proto_1_12_2/packets/ByteArrayInputNetStream.java index 2c17c19..c51beaf 100644 --- a/proto_1.12.2/src/test/java/mc/core/network/proto_1_12_2/packets/ByteArrayInputNetStream.java +++ b/proto_1.12.2/src/test/java/mc/core/network/proto_1_12_2/packets/ByteArrayInputNetStream.java @@ -3,7 +3,6 @@ package mc.core.network.proto_1_12_2.packets; import mc.core.network.proto_1_12_2.NetInputStream_p340; import java.io.ByteArrayInputStream; -import java.io.EOFException; public class ByteArrayInputNetStream extends NetInputStream_p340 { private ByteArrayInputStream bais; diff --git a/proto_1.12.2/src/test/java/mc/core/network/proto_1_12_2/packets/TestByteArrayInputNetStream.java b/proto_1.12.2/src/test/java/mc/core/network/proto_1_12_2/packets/TestByteArrayInputNetStream.java index 85bfc7c..72d1574 100644 --- a/proto_1.12.2/src/test/java/mc/core/network/proto_1_12_2/packets/TestByteArrayInputNetStream.java +++ b/proto_1.12.2/src/test/java/mc/core/network/proto_1_12_2/packets/TestByteArrayInputNetStream.java @@ -3,7 +3,6 @@ package mc.core.network.proto_1_12_2.packets; import mc.core.network.proto_1_12_2.ByteArrayOutputNetStream; import org.junit.Test; -import java.io.ByteArrayInputStream; import java.util.Random; import static org.junit.Assert.*; diff --git a/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/handlers/LoginHandler.java b/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/handlers/LoginHandler.java index d38af9c..084d7f7 100644 --- a/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/handlers/LoginHandler.java +++ b/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/handlers/LoginHandler.java @@ -24,9 +24,6 @@ import mc.core.world.chunk.Chunk; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import java.util.Optional; -import java.util.UUID; - import static mc.core.network.proto_1_12_2.netty.NettyServer.ATTR_PLAYER; import static mc.core.network.proto_1_12_2.netty.NettyServer.ATTR_STATE; @@ -39,8 +36,8 @@ public class LoginHandler extends AbstractStateHandler implements LoginStateHand @Handler public void onLoginStart(Channel channel, LoginStartPacket packet) { - Optional optPlayer = playerManager.getPlayer(packet.getPlayerName()); - if (optPlayer.isPresent() && optPlayer.get().isOnline()) { + Player player = playerManager.getPlayer(packet.getPlayerName()); + if (player != null) { channel.writeAndFlush(new DisconnectPacket( Text.builder("Player \"") .append(Text.of(packet.getPlayerName(), TextColor.YELLOW)) @@ -48,24 +45,35 @@ public class LoginHandler extends AbstractStateHandler implements LoginStateHand .build())) .addListener(ChannelFutureListener.CLOSE); } else { - Player player = playerManager.getPlayer(packet.getPlayerName()) - .orElseGet(() -> playerManager.createPlayer( - packet.getPlayerName(), - world.getSpawn(), - world)); + player = playerManager.getOfflinePlayer(packet.getPlayerName()); + + if (player == null) { + player = playerManager.createPlayer( + packet.getPlayerName(), + world.getSpawn(), + world + ); + + if (player == null) { + channel.writeAndFlush(new DisconnectPacket( + Text.of("Internal server error: can't create new player")) + ).addListener(ChannelFutureListener.CLOSE); + return; + } + } channel.writeAndFlush(new LoginSuccessPacket( - player.getUUID(), + player.getUuid(), packet.getPlayerName())); channel.attr(ATTR_PLAYER).set(player); channel.attr(ATTR_STATE).set(State.PLAY); // Join Game JoinGamePacket pkt1 = new JoinGamePacket(); - pkt1.setEntityId(player.getId()); - pkt1.setMode(PlayerMode.CREATIVE); - pkt1.setDimension(0/*Overworld*/); - pkt1.setDifficulty(0/*Peaceful*/); + pkt1.setEntityId(player.getId()); //TODO отделить системный ID от EntityID + pkt1.setMode(PlayerMode.CREATIVE); //TODO перенести в Config + pkt1.setDimension(0/*Overworld*/); //TODO перенести в World + pkt1.setDifficulty(0/*Peaceful*/); //TODO перенести в Config pkt1.setLevelType(world.getWorldType().getName()); channel.write(pkt1); @@ -75,15 +83,17 @@ public class LoginHandler extends AbstractStateHandler implements LoginStateHand channel.write(pkt2); // Player Abilities - PlayerAbilitiesPacket pkt3 = new PlayerAbilitiesPacket(); + PlayerAbilitiesPacket pkt3 = new PlayerAbilitiesPacket(); //TODO перенести в Player pkt3.setCanFly(true); pkt3.setFlying(true); pkt3.setGodMode(true); pkt3.setInstantDestroyBlocks(true); channel.write(pkt3); + channel.flush(); // First Chunk + //TODO необходимо отправлять больше начальных чанков ChunkDataPacket pkt8 = new ChunkDataPacket(); Chunk chunk = player.getWorld().getChunk(player.getLocation()); pkt8.setX(chunk.getX()); @@ -91,6 +101,7 @@ public class LoginHandler extends AbstractStateHandler implements LoginStateHand pkt8.setChunk(chunk); pkt8.setInitChunk(true); channel.writeAndFlush(pkt8); + player.getLoadedChunks().add(CompactedCoords.compressXZ(0, 0)); // Player Position And Look @@ -102,10 +113,11 @@ public class LoginHandler extends AbstractStateHandler implements LoginStateHand player.setChannel(new WrapperNetChannel(channel)); // Send items + //TODO обновление должно приходить всем игрокам на сервере PlayerListItemPacket pkt5 = new PlayerListItemPacket(); pkt5.setAction(PlayerListItemPacket.Action.ADD_PLAYER); PlayerListItemPacket.PlayerData playerData = new PlayerListItemPacket.PlayerData(); - playerData.setUuid(player.getUUID()); + playerData.setUuid(player.getUuid()); playerData.setName(player.getName()); playerData.setGameMode(PlayerMode.CREATIVE); playerData.setPing(0); @@ -118,26 +130,6 @@ public class LoginHandler extends AbstractStateHandler implements LoginStateHand pkt5.getListPlayers().add(playerData); channel.writeAndFlush(pkt5); - // Send header/footer list - PlayerListHeaderAndFooterPacket pkt6 = new PlayerListHeaderAndFooterPacket(); - Text text = Text.of(TextColor.GOLD, "============================="); - pkt6.setHeader(text); - pkt6.setFooter(text); - channel.writeAndFlush(pkt6); - - // Send Boss bar - BossBarPacket pkt7 = new BossBarPacket(); - BossBarPacket.BarData barData = new BossBarPacket.BarData(); - barData.setTitle(Text.of(TextColor.GREEN, TextStyle.BOLD, "FORWOLK")); - barData.setColor(BossBarPacket.Color.WHITE); - barData.setDivision(BossBarPacket.Division._12); - barData.setHealth(1.0f); - barData.setFlags(BossBarPacket.Flag.NO); - pkt7.setUuid(UUID.randomUUID()); - pkt7.setAction(BossBarPacket.Action.ADD); - pkt7.setBarData(barData); - channel.writeAndFlush(pkt7); - playerManager.joinServer(player); CS_PlayerMoveEvent event = new CS_PlayerMoveEvent(player, player.getLocation()); diff --git a/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/handlers/StatusHandler.java b/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/handlers/StatusHandler.java index 1216cce..10c48fc 100644 --- a/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/handlers/StatusHandler.java +++ b/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/handlers/StatusHandler.java @@ -26,7 +26,7 @@ public class StatusHandler extends AbstractStateHandler implements StatusStateHa responsePacket.setMaxOnline(config.getMaxPlayers()); responsePacket.setDescription(config.getDescriptionServer()); responsePacket.setFaviconBase64(config.getFaviconBase64()); - responsePacket.setOnline(playerManager.getCountOnlinePlayers()); + responsePacket.setOnline(playerManager.getCountPlayers()); channel.writeAndFlush(responsePacket); } diff --git a/settings.gradle b/settings.gradle index 52ad5e4..f9dbb2c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,6 +2,7 @@ rootProject.name = 'mc-server' include('core') // Core include('flat_world') +include('h2_playermanager') include('vanilla_commands') include('proto_1.12.2') // Protocol 1.12.2 include('proto_1.12.2_netty') // Protocol 1.12.2 (Netty impl.) diff --git a/vanilla_commands/src/main/java/mc/commands/ListCommand.java b/vanilla_commands/src/main/java/mc/commands/ListCommand.java index a899c0e..82411a6 100644 --- a/vanilla_commands/src/main/java/mc/commands/ListCommand.java +++ b/vanilla_commands/src/main/java/mc/commands/ListCommand.java @@ -53,7 +53,7 @@ public class ListCommand implements CommandExecutor { playerManager.getPlayers().forEach(pl -> sj.add(pl.getName())); Text message = messageFormat.apply( - "count", playerManager.getCountOnlinePlayers(), + "count", playerManager.getCountPlayers(), "players", sj.toString()); sender.getChannel().sendChatMessage(message, MessageType.SYSTEM_MESSAGE); }