diff --git a/build.gradle b/build.gradle index fc4d863..0eeddb5 100644 --- a/build.gradle +++ b/build.gradle @@ -9,13 +9,20 @@ allprojects { repositories { mavenCentral() + maven { + url 'https://oss.sonatype.org/content/groups/public/' + } } } subprojects { + group 'mc' + ext { - slf4j_version = '1.7.21' - spring_version = '4.2.5.RELEASE' + slf4j_version = '1.7.25' + spring_version = '5.1.0.RELEASE' + lombok_version = '1.18.2' + junit_version = '5.3.1' } configurations { @@ -29,20 +36,22 @@ subprojects { compile (group: 'org.slf4j', name: 'jcl-over-slf4j', version: slf4j_version) /* Spring */ - compile (group: 'org.springframework', name: 'spring-context', version: spring_version) { - exclude group: 'commons-logging' - } + compile (group: 'org.springframework', name: 'spring-context', version: spring_version) /* Lombok */ - annotationProcessor (group: 'org.projectlombok', name: 'lombok', version: '1.16.16') - compileOnly (group: 'org.projectlombok', name: 'lombok', version: '1.16.16') + annotationProcessor (group: 'org.projectlombok', name: 'lombok', version: lombok_version) + compileOnly (group: 'org.projectlombok', name: 'lombok', version: lombok_version) - /* JUnit */ - testCompile (group: 'junit', name: 'junit', version: '4.12') - /* Simple log */ + /* Testing */ + testImplementation (group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: junit_version) + testRuntimeOnly(group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: junit_version) testCompile (group: 'org.slf4j', name: 'slf4j-simple', version: slf4j_version) - /* Mockito */ - testCompile (group: 'org.mockito', name: 'mockito-core', version: '1.9.5') + testCompile (group: 'org.mockito', name: 'mockito-core', version: '1.10.19') + testCompile (group: 'org.springframework', name: 'spring-test', version: spring_version) + } + + test { + useJUnitPlatform() } task copyDep(type: Copy) { diff --git a/core/README.MD b/core/README.MD index 6176db3..cd77165 100644 --- a/core/README.MD +++ b/core/README.MD @@ -18,20 +18,6 @@ Bean: ``` -### InMemoryPlayerManager - -Implements: `mc.core.PlayerManager` - -Bean: - -```xml - - - -``` - -`keepAliveInterval` - как часто (в ms) отправлять клиентам пакет `KeepAlive` - ### IdleTime Implements: `mc.core.time.TimeProcessor` diff --git a/core/build.gradle b/core/build.gradle index 77e3054..1b923c1 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -1,4 +1,3 @@ -group 'mc' version '1.0-SNAPSHOT' apply plugin: 'maven' @@ -9,7 +8,7 @@ mainClassName = "mc.core.Main" dependencies { /* Components */ compile (group: 'commons-io', name: 'commons-io', version: '2.6') - compile (group: 'com.google.guava', name: 'guava', version: '24.1-jre') + compile (group: 'com.google.guava', name: 'guava', version: '26.0-jre') /* Named Binary Tags */ - compile (group: 'com.flowpowered', name: 'flow-nbt', version: '1.0.0') + compile (group: 'com.flowpowered', name: 'flow-nbt', version: '1.0.1-SNAPSHOT') } diff --git a/core/src/main/java/mc/core/CoreEventListener.java b/core/src/main/java/mc/core/CoreEventListener.java new file mode 100644 index 0000000..201b60e --- /dev/null +++ b/core/src/main/java/mc/core/CoreEventListener.java @@ -0,0 +1,85 @@ +package mc.core; + +import com.google.common.eventbus.Subscribe; +import lombok.extern.slf4j.Slf4j; +import mc.core.eventbus.EventBusGetter; +import mc.core.eventbus.events.CS_PlayerMoveEvent; +import mc.core.eventbus.events.SC_ChunkLoadEvent; +import mc.core.eventbus.events.SC_ChunkUnloadEvent; +import mc.core.utils.CompactedCoords; +import mc.core.world.chunk.Chunk; + +import javax.annotation.PostConstruct; +import java.util.Iterator; + +@Slf4j +public class CoreEventListener { + @PostConstruct + public void registerEventHandlers() { + EventBusGetter.getInstance().register(this); + } + + @Subscribe + public void handlerPlayerMoveEvent(CS_PlayerMoveEvent event) { + log.trace("(GameLoop) playerMoveEventHandler()"); + + Chunk chunk; + chunk = event.getPlayer().getWorld().getChunk(event.getOldLocation()); // Old chunk + int ccX = chunk.getX(); + int ccZ = chunk.getZ(); + chunk = event.getPlayer().getWorld().getChunk(event.getNewLocation()); // Next chunk + int ncX = chunk.getX(); + int ncZ = chunk.getZ(); + + if (event.isRecalcChunk() || (ncX != ccX || ncZ != ccZ)) { + final int viewDistance = event.getPlayer().getSettings().getViewDistance() + 1; + int cMinX = chunk.getX() - viewDistance; + int cMaxX = chunk.getX() + viewDistance; + int cMinZ = chunk.getZ() - viewDistance; + int cMaxZ = chunk.getZ() + viewDistance; + + SC_ChunkUnloadEvent eventChunkUnload = new SC_ChunkUnloadEvent(event.getPlayer()); + Iterator itr = event.getPlayer().getLoadedChunks().iterator(); + while(itr.hasNext()) { + int compressXZ = itr.next(); + int[] xz = CompactedCoords.uncompressXZ(compressXZ); + if (xz[0] > cMaxX || xz[0] < cMinX || xz[1] > cMaxZ || xz[1] < cMinZ) { + eventChunkUnload.getNeedUnloadChunks().add(compressXZ); + itr.remove(); + } + } + + if (!eventChunkUnload.getNeedUnloadChunks().isEmpty()) { + EventBusGetter.getInstance().post(eventChunkUnload); + } + + SC_ChunkLoadEvent eventChunkLoad = new SC_ChunkLoadEvent(event.getPlayer()); + for (int cZ = cMinZ; cZ <= cMaxZ; cZ++) { + for (int cX = cMinX; cX <= cMaxX; cX++) { + int compressXZ = CompactedCoords.compressXZ(cX, cZ); + if (!event.getPlayer().getLoadedChunks().contains(compressXZ)) { + if (!event.getPlayer().getLoadedChunks().contains(compressXZ)) { + eventChunkLoad.getNeedLoadChunks().add(compressXZ); + event.getPlayer().getLoadedChunks().add(compressXZ); + } + } + } + } + + if (!eventChunkLoad.getNeedLoadChunks().isEmpty()) { + EventBusGetter.getInstance().post(eventChunkLoad); + } + } + + event.getPlayer().getLocation().setXYZ( + event.getNewLocation().getX(), + event.getNewLocation().getY(), + event.getNewLocation().getZ() + ); + + // TODO отсылать клиенту только(!) для корректировки позиции + // SC_PlayerMoveEvent nextEvent = new SC_PlayerMoveEvent(event.getPlayer()); + // nextEvent.setNewLocation(event.getNewLocation()); + // EventBusGetter.INSTANCE.post(nextEvent); + } +} diff --git a/core/src/main/java/mc/core/EntityLocation.java b/core/src/main/java/mc/core/EntityLocation.java index 960fd08..0b4e237 100644 --- a/core/src/main/java/mc/core/EntityLocation.java +++ b/core/src/main/java/mc/core/EntityLocation.java @@ -1,29 +1,29 @@ -/* - * DmitriyMX - * 2018-08-08 - */ package mc.core; -import lombok.Getter; -import lombok.Setter; -import mc.core.exception.ResourceUnloadedException; -import mc.core.world.World; -import mc.core.world.chunk.Chunk; -import mc.core.world.chunk.ChunkSection; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; -import java.lang.ref.Reference; -import java.lang.ref.WeakReference; - -public class EntityLocation extends Location implements Cloneable { - @Getter - @Setter +@NoArgsConstructor +@AllArgsConstructor +@Data +public class EntityLocation implements Cloneable { + private double x, y, z; private float yaw, pitch; - private Reference refWorld; - public EntityLocation(double x, double y, double z, float yaw, float pitch, World world) { - super(x, y, z); - setYawPitch(yaw, pitch); - setWorld(world); + public static EntityLocation ZERO() { + return new EntityLocation(0d,0d,0d,0f,0f); + } + + public void set(EntityLocation location) { + setXYZ(location.x, location.y, location.z); + setYawPitch(location.yaw, location.pitch); + } + + public void setXYZ(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; } public void setYawPitch(float yaw, float pitch) { @@ -31,44 +31,25 @@ public class EntityLocation extends Location implements Cloneable { this.pitch = pitch; } - public void setYawPitch(EntityLocation entityLocation) { - setYawPitch(entityLocation.yaw, entityLocation.pitch); + public int getBlockX() { + return Double.valueOf(Math.floor(x)).intValue(); } - public World getWorld() { - if (refWorld == null) { - return null; - } else if (refWorld.get() == null) { - throw new ResourceUnloadedException("World unloaded"); - } else { - return refWorld.get(); - } + public int getBlockY() { + return Double.valueOf(Math.floor(y)).intValue(); } - public void setWorld (World world) { - this.refWorld = new WeakReference<>(world); - } - - public Chunk getChunk() { - World world = getWorld(); - if (world == null) { - return null; - } else { - return world.getChunk(getBlockX() >> 4, getBlockZ() >> 4); - } - } - - public ChunkSection getChunkSection() { - Chunk chunk = getChunk(); - if (chunk == null) { - return null; - } else { - return chunk.getChunkSection(getBlockY() >> 4); - } + public int getBlockZ() { + return Double.valueOf(Math.floor(z)).intValue(); } @Override public EntityLocation clone() { - return (EntityLocation) super.clone(); + try { + return (EntityLocation) super.clone(); + } catch (CloneNotSupportedException e) { + e.printStackTrace(); + return null; + } } } diff --git a/core/src/main/java/mc/core/GameLoop.java b/core/src/main/java/mc/core/GameLoop.java index 06fc0a1..4b5affc 100644 --- a/core/src/main/java/mc/core/GameLoop.java +++ b/core/src/main/java/mc/core/GameLoop.java @@ -4,22 +4,12 @@ */ package mc.core; -import com.google.common.eventbus.Subscribe; import lombok.Setter; import lombok.extern.slf4j.Slf4j; -import mc.core.events.CS_PlayerMoveEvent; -import mc.core.events.EventBusGetter; -import mc.core.events.SC_ChunkLoadEvent; -import mc.core.events.SC_ChunkUnloadEvent; import mc.core.player.PlayerManager; import mc.core.time.TimeProcessor; -import mc.core.utils.CompactedCoords; -import mc.core.world.World; -import mc.core.world.chunk.Chunk; import org.springframework.beans.factory.annotation.Autowired; -import java.util.Iterator; - @Slf4j public class GameLoop extends Thread { private final TpsWatcher TPS_WATCHER = TpsWatcher.getInstance(); @@ -48,77 +38,16 @@ public class GameLoop extends Thread { TPS_WATCHER.setTraceTPS(value); } - @Subscribe - public void playerMoveEventHandler(CS_PlayerMoveEvent event) { - log.trace("(GameLoop) playerMoveEventHandler()"); - - Chunk chunk; - chunk = event.getOldLocation().getChunk(); // Old chunk - int ccX = chunk.getX(); - int ccZ = chunk.getZ(); - chunk = event.getNewLocation().getChunk(); // Next chunk - int ncX = chunk.getX(); - int ncZ = chunk.getZ(); - - if (event.isRecalcChunk() || (ncX != ccX || ncZ != ccZ)) { - final int viewDistance = event.getPlayer().getSettings().getViewDistance() + 1; - int cMinX = chunk.getX() - viewDistance; - int cMaxX = chunk.getX() + viewDistance; - int cMinZ = chunk.getZ() - viewDistance; - int cMaxZ = chunk.getZ() + viewDistance; - - SC_ChunkUnloadEvent eventChunkUnload = new SC_ChunkUnloadEvent(event.getPlayer()); - Iterator itr = event.getPlayer().getLoadedChunks().iterator(); - while(itr.hasNext()) { - int compressXZ = itr.next(); - int[] xz = CompactedCoords.uncompressXZ(compressXZ); - if (xz[0] > cMaxX || xz[0] < cMinX || xz[1] > cMaxZ || xz[1] < cMinZ) { - eventChunkUnload.getNeedUnloadChunks().add(compressXZ); - itr.remove(); - } - } - - if (!eventChunkUnload.getNeedUnloadChunks().isEmpty()) { - EventBusGetter.INSTANCE.post(eventChunkUnload); - } - - SC_ChunkLoadEvent eventChunkLoad = new SC_ChunkLoadEvent(event.getPlayer()); - for (int cZ = cMinZ; cZ <= cMaxZ; cZ++) { - for (int cX = cMinX; cX <= cMaxX; cX++) { - int compressXZ = CompactedCoords.compressXZ(cX, cZ); - if (!event.getPlayer().getLoadedChunks().contains(compressXZ)) { - if (!event.getPlayer().getLoadedChunks().contains(compressXZ)) { - eventChunkLoad.getNeedLoadChunks().add(compressXZ); - event.getPlayer().getLoadedChunks().add(compressXZ); - } - } - } - } - - if (!eventChunkLoad.getNeedLoadChunks().isEmpty()) { - EventBusGetter.INSTANCE.post(eventChunkLoad); - } - } - - event.getPlayer().getLocation().setXYZ(event.getNewLocation()); - - // TODO отсылать клиенту только(!) для корректировки позиции -// SC_PlayerMoveEvent nextEvent = new SC_PlayerMoveEvent(event.getPlayer()); -// nextEvent.setNewLocation(event.getNewLocation()); -// EventBusGetter.INSTANCE.post(nextEvent); - } - @Override public void run() { TPS_WATCHER.startWatch(); - EventBusGetter.INSTANCE.register(this); - while (!isInterrupted()) { TPS_WATCHER.check(); /* --- --- --- */ + /* TODO нужно перенести этот функционал на Network */ playerManager.getBroadcastChannel().sendTimeUpdate( gameTimer.getGameTime(), gameTimer.getWorldAge() diff --git a/core/src/main/java/mc/core/ImmutableEntityLocation.java b/core/src/main/java/mc/core/ImmutableEntityLocation.java new file mode 100644 index 0000000..f71c1e3 --- /dev/null +++ b/core/src/main/java/mc/core/ImmutableEntityLocation.java @@ -0,0 +1,57 @@ +package mc.core; + +public class ImmutableEntityLocation extends EntityLocation { + public ImmutableEntityLocation(double x, double y, double z, float yaw, float pitch) { + super(x, y, z, yaw, pitch); + } + + public ImmutableEntityLocation(EntityLocation location) { + this( + location.getX(), + location.getY(), + location.getZ(), + location.getYaw(), + location.getPitch() + ); + } + + @Override + public void setX(double x) { + throw new UnsupportedOperationException(); + } + + @Override + public void setY(double y) { + throw new UnsupportedOperationException(); + } + + @Override + public void setZ(double z) { + throw new UnsupportedOperationException(); + } + + @Override + public void setYaw(float yaw) { + throw new UnsupportedOperationException(); + } + + @Override + public void setPitch(float pitch) { + throw new UnsupportedOperationException(); + } + + @Override + public void set(EntityLocation location) { + throw new UnsupportedOperationException(); + } + + @Override + public void setXYZ(double x, double y, double z) { + throw new UnsupportedOperationException(); + } + + @Override + public void setYawPitch(float yaw, float pitch) { + throw new UnsupportedOperationException(); + } +} diff --git a/core/src/main/java/mc/core/Location.java b/core/src/main/java/mc/core/Location.java deleted file mode 100644 index 62ca57d..0000000 --- a/core/src/main/java/mc/core/Location.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * DmitriyMX - * 2018-08-08 - */ -package mc.core; - -import lombok.Getter; -import lombok.Setter; - -public class Location implements Cloneable { - @Getter - @Setter - private double x, y, z; - - public Location (double x, double y, double z) { - setXYZ(x, y, z); - } - - public void setXYZ(double x, double y, double z) { - this.x = x; - this.y = y; - this.z = z; - } - - public void setXYZ(Location location) { - setXYZ(location.x, location.y, location.z); - } - - public int getBlockX() { - return Double.valueOf(Math.floor(x)).intValue(); - } - - public int getBlockY() { - return Double.valueOf(Math.floor(y)).intValue(); - } - - public int getBlockZ() { - return Double.valueOf(Math.floor(z)).intValue(); - } - - @Override - public Location clone() { - try { - return (Location) super.clone(); - } catch (CloneNotSupportedException e) { // такое в нашем случае вообще возможно? - e.printStackTrace(); - return null; - } - } -} diff --git a/core/src/main/java/mc/core/embedded/FakePlayerManager.java b/core/src/main/java/mc/core/embedded/FakePlayerManager.java index 612de62..f90255b 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; @@ -12,8 +8,7 @@ import mc.core.player.Player; import mc.core.player.PlayerManager; import mc.core.text.Text; import mc.core.text.Title; -import mc.core.world.chunk.Chunk; -import mc.core.world.chunk.ChunkSection; +import mc.core.world.World; import java.util.Collections; import java.util.List; @@ -21,9 +16,6 @@ import java.util.Optional; public class FakePlayerManager implements PlayerManager { public static class FakeNetChannet implements NetChannel { - @Override - public void sendKeepAlive() { - } @Override public void sendTimeUpdate(long time, long age) { @@ -53,7 +45,7 @@ public class FakePlayerManager implements PlayerManager { private static final NetChannel FAKE_NET_CHANNEL = new FakeNetChannet(); @Override - public Player createPlayer(String name, EntityLocation defaultLocation) { + public Player createPlayer(String name, EntityLocation location, World world) { return null; } @@ -66,13 +58,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 @@ -81,7 +68,7 @@ public class FakePlayerManager implements PlayerManager { } @Override - public int getCountOnlinePlayers() { + public int getCountPlayers() { return 0; } @@ -89,4 +76,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/events/Event.java b/core/src/main/java/mc/core/eventbus/Event.java similarity index 89% rename from core/src/main/java/mc/core/events/Event.java rename to core/src/main/java/mc/core/eventbus/Event.java index 066e7c8..08aa0ec 100644 --- a/core/src/main/java/mc/core/events/Event.java +++ b/core/src/main/java/mc/core/eventbus/Event.java @@ -2,7 +2,7 @@ * DmitriyMX * 2018-05-02 */ -package mc.core.events; +package mc.core.eventbus; public interface Event { void setCanceled(boolean value); diff --git a/core/src/main/java/mc/core/events/EventBase.java b/core/src/main/java/mc/core/eventbus/EventBase.java similarity index 89% rename from core/src/main/java/mc/core/events/EventBase.java rename to core/src/main/java/mc/core/eventbus/EventBase.java index 8c4f030..7756cce 100644 --- a/core/src/main/java/mc/core/events/EventBase.java +++ b/core/src/main/java/mc/core/eventbus/EventBase.java @@ -2,7 +2,7 @@ * DmitriyMX * 2018-05-02 */ -package mc.core.events; +package mc.core.eventbus; import lombok.Getter; import lombok.Setter; diff --git a/core/src/main/java/mc/core/events/EventBusGetter.java b/core/src/main/java/mc/core/eventbus/EventBusGetter.java similarity index 59% rename from core/src/main/java/mc/core/events/EventBusGetter.java rename to core/src/main/java/mc/core/eventbus/EventBusGetter.java index d2e5aa3..37e952b 100644 --- a/core/src/main/java/mc/core/events/EventBusGetter.java +++ b/core/src/main/java/mc/core/eventbus/EventBusGetter.java @@ -2,12 +2,14 @@ * DmitriyMX * 2018-05-02 */ -package mc.core.events; +package mc.core.eventbus; import com.google.common.eventbus.EventBus; +import lombok.Getter; public final class EventBusGetter { - public static final EventBus INSTANCE = new EventBus(); + @Getter + private static final EventBus instance = new EventBus(); private EventBusGetter() { } diff --git a/core/src/main/java/mc/core/events/CS_PlayerMoveEvent.java b/core/src/main/java/mc/core/eventbus/events/CS_PlayerMoveEvent.java similarity index 51% rename from core/src/main/java/mc/core/events/CS_PlayerMoveEvent.java rename to core/src/main/java/mc/core/eventbus/events/CS_PlayerMoveEvent.java index d44f7b1..4d759ab 100644 --- a/core/src/main/java/mc/core/events/CS_PlayerMoveEvent.java +++ b/core/src/main/java/mc/core/eventbus/events/CS_PlayerMoveEvent.java @@ -2,22 +2,26 @@ * DmitriyMX * 2018-05-02 */ -package mc.core.events; +package mc.core.eventbus.events; import lombok.Getter; -import lombok.RequiredArgsConstructor; import lombok.Setter; import mc.core.EntityLocation; +import mc.core.ImmutableEntityLocation; +import mc.core.eventbus.EventBase; import mc.core.player.Player; -@RequiredArgsConstructor @Getter public class CS_PlayerMoveEvent extends EventBase { private final Player player; - private final EntityLocation oldLocation; // TODO сомнительное решение - // вообще нужно будет создать реализацию "иммутабл локейшен" для подобных ситуаций + private final ImmutableEntityLocation oldLocation; @Setter private EntityLocation newLocation; @Setter private boolean recalcChunk = false; + + public CS_PlayerMoveEvent(Player player, EntityLocation oldLocation) { + this.player = player; + this.oldLocation = new ImmutableEntityLocation(oldLocation); + } } diff --git a/core/src/main/java/mc/core/events/SC_ChunkLoadEvent.java b/core/src/main/java/mc/core/eventbus/events/SC_ChunkLoadEvent.java similarity index 83% rename from core/src/main/java/mc/core/events/SC_ChunkLoadEvent.java rename to core/src/main/java/mc/core/eventbus/events/SC_ChunkLoadEvent.java index 62d35c6..ea55a7a 100644 --- a/core/src/main/java/mc/core/events/SC_ChunkLoadEvent.java +++ b/core/src/main/java/mc/core/eventbus/events/SC_ChunkLoadEvent.java @@ -1,7 +1,8 @@ -package mc.core.events; +package mc.core.eventbus.events; import lombok.Getter; import lombok.RequiredArgsConstructor; +import mc.core.eventbus.EventBase; import mc.core.player.Player; import java.util.ArrayList; diff --git a/core/src/main/java/mc/core/events/SC_ChunkUnloadEvent.java b/core/src/main/java/mc/core/eventbus/events/SC_ChunkUnloadEvent.java similarity index 83% rename from core/src/main/java/mc/core/events/SC_ChunkUnloadEvent.java rename to core/src/main/java/mc/core/eventbus/events/SC_ChunkUnloadEvent.java index 397cbac..b2cce0d 100644 --- a/core/src/main/java/mc/core/events/SC_ChunkUnloadEvent.java +++ b/core/src/main/java/mc/core/eventbus/events/SC_ChunkUnloadEvent.java @@ -1,7 +1,8 @@ -package mc.core.events; +package mc.core.eventbus.events; import lombok.Getter; import lombok.RequiredArgsConstructor; +import mc.core.eventbus.EventBase; import mc.core.player.Player; import java.util.ArrayList; diff --git a/core/src/main/java/mc/core/events/LoginEvent.java b/core/src/main/java/mc/core/eventbus/events/SC_LoginEvent.java similarity index 75% rename from core/src/main/java/mc/core/events/LoginEvent.java rename to core/src/main/java/mc/core/eventbus/events/SC_LoginEvent.java index 63e123f..95765f3 100644 --- a/core/src/main/java/mc/core/events/LoginEvent.java +++ b/core/src/main/java/mc/core/eventbus/events/SC_LoginEvent.java @@ -2,18 +2,19 @@ * DmitriyMX * 2018-05-02 */ -package mc.core.events; +package mc.core.eventbus.events; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.Setter; +import mc.core.eventbus.EventBase; import java.net.SocketAddress; @RequiredArgsConstructor @Getter @Setter -public class LoginEvent extends EventBase { +public class SC_LoginEvent extends EventBase { private String playerName; private final SocketAddress remoteAddress; private boolean deny; diff --git a/core/src/main/java/mc/core/events/PlayerLookEvent.java b/core/src/main/java/mc/core/eventbus/events/SC_PlayerLookEvent.java similarity index 72% rename from core/src/main/java/mc/core/events/PlayerLookEvent.java rename to core/src/main/java/mc/core/eventbus/events/SC_PlayerLookEvent.java index 7506530..90e5379 100644 --- a/core/src/main/java/mc/core/events/PlayerLookEvent.java +++ b/core/src/main/java/mc/core/eventbus/events/SC_PlayerLookEvent.java @@ -2,18 +2,19 @@ * DmitriyMX * 2018-05-02 */ -package mc.core.events; +package mc.core.eventbus.events; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.Setter; import mc.core.EntityLocation; +import mc.core.eventbus.EventBase; import mc.core.player.Player; @RequiredArgsConstructor @Getter @Setter -public class PlayerLookEvent extends EventBase { +public class SC_PlayerLookEvent extends EventBase { private final Player player; private EntityLocation newLook; } diff --git a/core/src/main/java/mc/core/events/SC_PlayerMoveEvent.java b/core/src/main/java/mc/core/eventbus/events/SC_PlayerMoveEvent.java similarity index 82% rename from core/src/main/java/mc/core/events/SC_PlayerMoveEvent.java rename to core/src/main/java/mc/core/eventbus/events/SC_PlayerMoveEvent.java index d64350b..d0634a7 100644 --- a/core/src/main/java/mc/core/events/SC_PlayerMoveEvent.java +++ b/core/src/main/java/mc/core/eventbus/events/SC_PlayerMoveEvent.java @@ -1,9 +1,10 @@ -package mc.core.events; +package mc.core.eventbus.events; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.Setter; import mc.core.EntityLocation; +import mc.core.eventbus.EventBase; import mc.core.player.Player; @RequiredArgsConstructor diff --git a/core/src/main/java/mc/core/events/ServerPingEvent.java b/core/src/main/java/mc/core/eventbus/events/SC_ServerPingEvent.java similarity index 74% rename from core/src/main/java/mc/core/events/ServerPingEvent.java rename to core/src/main/java/mc/core/eventbus/events/SC_ServerPingEvent.java index 3bbafce..877df70 100644 --- a/core/src/main/java/mc/core/events/ServerPingEvent.java +++ b/core/src/main/java/mc/core/eventbus/events/SC_ServerPingEvent.java @@ -2,18 +2,19 @@ * DmitriyMX * 2018-05-02 */ -package mc.core.events; +package mc.core.eventbus.events; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.Setter; +import mc.core.eventbus.EventBase; import java.net.SocketAddress; @RequiredArgsConstructor @Getter @Setter -public class ServerPingEvent extends EventBase { +public class SC_ServerPingEvent extends EventBase { private final SocketAddress remoteAddress; private String description; private int online; diff --git a/core/src/main/java/mc/core/network/BroadcastNetChannel.java b/core/src/main/java/mc/core/network/BroadcastNetChannel.java index 3f5e7bb..ed15fab 100644 --- a/core/src/main/java/mc/core/network/BroadcastNetChannel.java +++ b/core/src/main/java/mc/core/network/BroadcastNetChannel.java @@ -16,11 +16,6 @@ import java.util.stream.Stream; public class BroadcastNetChannel implements NetChannel { private final Stream playerStream; - @Override - public void sendKeepAlive() { - playerStream.forEach(player -> player.getChannel().sendKeepAlive()); - } - @Override public void sendTimeUpdate(final long time, final long age) { playerStream.forEach(player -> player.getChannel().sendTimeUpdate(time, age)); diff --git a/core/src/main/java/mc/core/network/NetChannel.java b/core/src/main/java/mc/core/network/NetChannel.java index 69c05c7..bfd6899 100644 --- a/core/src/main/java/mc/core/network/NetChannel.java +++ b/core/src/main/java/mc/core/network/NetChannel.java @@ -9,7 +9,6 @@ import mc.core.text.Text; import mc.core.text.Title; public interface NetChannel { - void sendKeepAlive(); void sendTimeUpdate(long time, long age); default void sendChatMessage(Text text) { sendChatMessage(text, MessageType.CHAT_MESSAGE); diff --git a/core/src/main/java/mc/core/network/NetInputStream.java b/core/src/main/java/mc/core/network/NetInputStream.java index a2e7b54..9ae1049 100644 --- a/core/src/main/java/mc/core/network/NetInputStream.java +++ b/core/src/main/java/mc/core/network/NetInputStream.java @@ -22,6 +22,7 @@ public abstract class NetInputStream { public abstract short readShort(); public abstract int readInt(); public abstract int readVarInt(); + public abstract int readVarInt(int[] countReadBytes); public abstract long readLong(); public abstract float readFloat(); public abstract double readDouble(); 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 d373c59..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 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 defaultLocation) { - SimplePlayer player = new SimplePlayer(); - player.setId(rand.nextInt(10000)); - player.setUUID(UUID.nameUUIDFromBytes(name.getBytes())); - player.setName(name); - player.getLocation().setXYZ(defaultLocation); - player.getLocation().setYawPitch(defaultLocation); - player.getLocation().setWorld(defaultLocation.getWorld()); - 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 11c41a3..eefe499 100644 --- a/core/src/main/java/mc/core/player/Player.java +++ b/core/src/main/java/mc/core/player/Player.java @@ -6,13 +6,14 @@ package mc.core.player; import mc.core.EntityLocation; import mc.core.network.NetChannel; +import mc.core.world.World; import java.util.List; import java.util.UUID; public interface Player { int getId(); - UUID getUUID(); + UUID getUuid(); String getName(); boolean isOnline(); @@ -23,7 +24,8 @@ public interface Player { void setChannel(NetChannel channel); EntityLocation getLocation(); - //TODO надо определиться - нужно ли здесь setLocation() или нет + World getWorld(); + void setWorld(World world); boolean isFlying(); void setFlying(boolean value); diff --git a/core/src/main/java/mc/core/player/PlayerManager.java b/core/src/main/java/mc/core/player/PlayerManager.java index 8e07d6e..4fe5485 100644 --- a/core/src/main/java/mc/core/player/PlayerManager.java +++ b/core/src/main/java/mc/core/player/PlayerManager.java @@ -1,22 +1,20 @@ -/* - * DmitriyMX - * 2018-04-15 - */ package mc.core.player; import mc.core.EntityLocation; 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 defaultLocation); + 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/player/SimplePlayer.java b/core/src/main/java/mc/core/player/SimplePlayer.java deleted file mode 100644 index 2472758..0000000 --- a/core/src/main/java/mc/core/player/SimplePlayer.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * DmitriyMX - * 2018-04-23 - */ -package mc.core.player; - -import lombok.Data; -import mc.core.EntityLocation; -import mc.core.network.NetChannel; - -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -@Data -public class SimplePlayer implements Player { - private int id; - private UUID uuid; - private String name; - private boolean online = false; - private NetChannel channel; - private EntityLocation location = new EntityLocation(0d, 0d, 0d, 0f, 0f, null); - private boolean flying = false; - private PlayerSettings settings; - private List loadedChunks = new ArrayList<>(); - - public void setLocation(EntityLocation location) { - this.location.setXYZ(location); - this.location.setYawPitch(location); - } - - @Override - public UUID getUUID() { - return uuid; - } - - public void setUUID(UUID uuid) { - this.uuid = uuid; - } -} diff --git a/core/src/main/java/mc/core/text/Text.java b/core/src/main/java/mc/core/text/Text.java index 14957f7..4d470fe 100644 --- a/core/src/main/java/mc/core/text/Text.java +++ b/core/src/main/java/mc/core/text/Text.java @@ -1,20 +1,11 @@ -/* - * DmitriyMX - * 2018-06-11 - */ package mc.core.text; import com.google.common.collect.ImmutableList; -import lombok.EqualsAndHashCode; import lombok.Getter; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.StringJoiner; +import java.util.*; @Getter -@EqualsAndHashCode public class Text { private static final Text EMPTY = new Text(); private static final Text NEW_LINE = new Text("\n", null, null, null); @@ -39,7 +30,7 @@ public class Text { } public boolean isEmpty() { - boolean result = content.isEmpty(); + boolean result = (content == null || content.isEmpty()); if (children != null && !children.isEmpty()) { for (Text child : children) { @@ -60,6 +51,19 @@ public class Text { } } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Text text = (Text) o; + return Objects.equals(toPlain(), text.toPlain()); + } + + @Override + public int hashCode() { + return Objects.hash(toPlain()); + } + public static class Builder { @Getter private String content; @@ -81,6 +85,8 @@ public class Text { } public Builder(Object... objects) { + this.children = new ArrayList<>(); + for(Object obj : objects) { if (obj instanceof String) { if (this.content == null) { @@ -96,10 +102,10 @@ public class Text { } } else if (obj instanceof TextColor) { this.color = (TextColor) obj; + } else if (obj instanceof Text) { + children.add((Text) obj); } } - - this.children = new ArrayList<>(); } public List getChildren() { @@ -133,8 +139,14 @@ public class Text { return this; } + public Builder append(String string) { + return append(Text.of(string)); + } + public Builder append(Text child) { - this.children.add(child); + if (child != null) { + this.children.add(child); + } return this; } @@ -144,16 +156,20 @@ public class Text { } public Text build() { - if (children.isEmpty()) { + if (children.isEmpty() && (content == null || content.isEmpty())) { return Text.EMPTY; } - return new Text( - content, - color, - style, - ImmutableList.copyOf(children) - ); + if (children.size() == 1 && children.get(0) != null) { + return children.get(0); + } else { + return new Text( + content, + color, + style, + ImmutableList.copyOf(children) + ); + } } } diff --git a/core/src/main/java/mc/core/utils/CompactedCoords.java b/core/src/main/java/mc/core/utils/CompactedCoords.java index 43daef0..cac3db6 100644 --- a/core/src/main/java/mc/core/utils/CompactedCoords.java +++ b/core/src/main/java/mc/core/utils/CompactedCoords.java @@ -25,18 +25,4 @@ public class CompactedCoords { int i = (int)value; return value < (double)i ? i - 1 : i; } - - public static long compressXYZ(double x, double y, double z) { - return ((floor_double(x) & 0x3FFFFFF) << 38) - | ((floor_double(y) & 0xFFF) << 26) - | (floor_double(z) & 0x3FFFFFF); - } - - public static double[] uncompressXYZ(long compactValue) { - return new double[]{ - compactValue >> 38, - (compactValue >> 26) & 0x0FFF, - compactValue << 38 >> 38 // is normal? - }; - } } diff --git a/core/src/main/java/mc/core/world/World.java b/core/src/main/java/mc/core/world/World.java index a6cbf9d..0c29708 100644 --- a/core/src/main/java/mc/core/world/World.java +++ b/core/src/main/java/mc/core/world/World.java @@ -5,9 +5,11 @@ package mc.core.world; import mc.core.EntityLocation; +import mc.core.world.block.BlockLocation; import mc.core.world.chunk.Chunk; public interface World { + String getName(); WorldType getWorldType(); EntityLocation getSpawn(); @@ -18,4 +20,12 @@ public interface World { Chunk getChunk(int x, int z); void setChunk(int x, int z, Chunk chunkSection); + + default Chunk getChunk(BlockLocation location) { + return getChunk(location.getX() >> 4, location.getZ() >> 4); + } + + default Chunk getChunk(EntityLocation location) { + return getChunk(location.getBlockX() >> 4, location.getBlockZ() >> 4); + } } diff --git a/core/src/main/java/mc/core/world/block/AbstractBlock.java b/core/src/main/java/mc/core/world/block/AbstractBlock.java index 0c6d90b..b00b8af 100644 --- a/core/src/main/java/mc/core/world/block/AbstractBlock.java +++ b/core/src/main/java/mc/core/world/block/AbstractBlock.java @@ -3,7 +3,6 @@ package mc.core.world.block; import com.flowpowered.nbt.Tag; import lombok.Getter; import lombok.Setter; -import mc.core.Location; import java.util.HashMap; import java.util.Map; @@ -12,7 +11,7 @@ import java.util.stream.Stream; public abstract class AbstractBlock implements Block { @Getter @Setter - private Location location; + private BlockLocation location; @Getter private int light = 0; @Getter diff --git a/core/src/main/java/mc/core/world/block/Block.java b/core/src/main/java/mc/core/world/block/Block.java index d1a392f..b54a7d0 100644 --- a/core/src/main/java/mc/core/world/block/Block.java +++ b/core/src/main/java/mc/core/world/block/Block.java @@ -1,11 +1,10 @@ package mc.core.world.block; -import mc.core.Location; import mc.core.nbt.Taggable; public interface Block extends Taggable{ int getLight(); void setLight(int light); BlockType getBlockType(); - Location getLocation(); + BlockLocation getLocation(); } diff --git a/core/src/main/java/mc/core/world/block/BlockFactory.java b/core/src/main/java/mc/core/world/block/BlockFactory.java index 095e312..04d039f 100644 --- a/core/src/main/java/mc/core/world/block/BlockFactory.java +++ b/core/src/main/java/mc/core/world/block/BlockFactory.java @@ -1,8 +1,5 @@ package mc.core.world.block; -import mc.core.Location; -import mc.core.world.World; - public class BlockFactory { public Block create(BlockType blockType, int x, int y, int z) { @@ -13,7 +10,7 @@ public class BlockFactory { private class EmbeddedBlock extends AbstractBlock { EmbeddedBlock(BlockType type, int x, int y, int z) { super(type); - setLocation(new Location(x, y, z)); + setLocation(new BlockLocation(x, y, z)); } } } diff --git a/core/src/main/java/mc/core/world/block/BlockLocation.java b/core/src/main/java/mc/core/world/block/BlockLocation.java new file mode 100644 index 0000000..7c9ca85 --- /dev/null +++ b/core/src/main/java/mc/core/world/block/BlockLocation.java @@ -0,0 +1,30 @@ +package mc.core.world.block; + +import lombok.*; + +@NoArgsConstructor +@AllArgsConstructor +@Data +public class BlockLocation implements Cloneable { + private int x, y, z; + + public static BlockLocation ZERO() { + return new BlockLocation(0,0,0); + } + + public void setXYZ(int x, int y, int z) { + this.x = x; + this.y = y; + this.z = z; + } + + @Override + public BlockLocation clone() { + try { + return (BlockLocation) super.clone(); + } catch (CloneNotSupportedException e) { + e.printStackTrace(); + return null; + } + } +} diff --git a/core/src/test/java/mc/core/EntityLocationTest.java b/core/src/test/java/mc/core/EntityLocationTest.java new file mode 100644 index 0000000..052e8a6 --- /dev/null +++ b/core/src/test/java/mc/core/EntityLocationTest.java @@ -0,0 +1,91 @@ +package mc.core; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.concurrent.ThreadLocalRandom; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +class EntityLocationTest { + private static final ThreadLocalRandom rnd = ThreadLocalRandom.current(); + private static final double minD = 0.0d, maxD = 10.0d; + private static final float minF = 0.0f, maxF = 359.9f; + private double x, y, z; + private float yaw, pitch; + + @BeforeEach + void before() { + x = rnd.nextDouble(minD, maxD); + y = rnd.nextDouble(minD, maxD); + z = rnd.nextDouble(minD, maxD); + yaw = rnd.nextFloat() * (maxF - minF) + minF; + pitch = rnd.nextFloat() * (maxF - minF) + minF; + } + + @Test + void equals_() { + EntityLocation loc1 = new EntityLocation(x, y, z, yaw, pitch); + EntityLocation loc2 = new EntityLocation(x, y, z, yaw, pitch); + assertEquals(loc1, loc2); + + loc2 = new EntityLocation(x+1, y+2, z-3, yaw, pitch); + assertNotEquals(loc1, loc2); + + loc2 = new EntityLocation(x, y, z, yaw-1, pitch+2); + assertNotEquals(loc1, loc2); + } + + @Test + void clone_() { + EntityLocation locOrig = new EntityLocation(x, y, z, yaw, pitch); + EntityLocation locClone = locOrig.clone(); + assertEquals(locOrig, locClone); + } + + @Test + void getBlockXZ() { + EntityLocation location; + + location = new EntityLocation(0d, 0, 0d, 0f, 0f); + assertEquals(0, location.getBlockX()); + assertEquals(0, location.getBlockZ()); + + location.setXYZ(0.1d, 0, 0.1d); + assertEquals(0, location.getBlockX()); + assertEquals(0, location.getBlockZ()); + + location.setXYZ(0.5d, 0, 0.5d); + assertEquals(0, location.getBlockX()); + assertEquals(0, location.getBlockZ()); + + location.setXYZ(0.9d, 0, 0.9d); + assertEquals(0, location.getBlockX()); + assertEquals(0, location.getBlockZ()); + + location.setXYZ(1d, 0, 1d); + assertEquals(1, location.getBlockX()); + assertEquals(1, location.getBlockZ()); + + location.setXYZ(-0.1d, 0, -0.1d); + assertEquals(-1, location.getBlockX()); + assertEquals(-1, location.getBlockZ()); + + location.setXYZ(-0.5d, 0, -0.5d); + assertEquals(-1, location.getBlockX()); + assertEquals(-1, location.getBlockZ()); + + location.setXYZ(-0.9d, 0, -0.9d); + assertEquals(-1, location.getBlockX()); + assertEquals(-1, location.getBlockZ()); + + location.setXYZ(-1d, 0, -1d); + assertEquals(-1, location.getBlockX()); + assertEquals(-1, location.getBlockZ()); + + location.setXYZ(-1.1d, 0, -1.1d); + assertEquals(-2, location.getBlockX()); + assertEquals(-2, location.getBlockZ()); + } +} diff --git a/core/src/test/java/mc/core/ImmutableEntityLocationTest.java b/core/src/test/java/mc/core/ImmutableEntityLocationTest.java new file mode 100644 index 0000000..0335538 --- /dev/null +++ b/core/src/test/java/mc/core/ImmutableEntityLocationTest.java @@ -0,0 +1,33 @@ +package mc.core; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class ImmutableEntityLocationTest { + + @Test + void setValue() { + EntityLocation location = new ImmutableEntityLocation(1d, 2d, 3d, 4f, 5f); + + assertThrows(UnsupportedOperationException.class, () -> { + location.setX(1); + location.setY(1); + location.setZ(1); + location.setYaw(1); + location.setPitch(1); + location.setXYZ(1, 2, 3); + location.setYawPitch(1, 2); + location.set(EntityLocation.ZERO()); + }); + } + + @Test + void clone_() { + EntityLocation locOrig = new ImmutableEntityLocation(1d, 2d, 3d, 4f, 5f); + EntityLocation locClone = locOrig.clone(); + + assertEquals(locOrig, locClone); + } +} \ No newline at end of file diff --git a/core/src/test/java/mc/core/TestEntityLocation.java b/core/src/test/java/mc/core/TestEntityLocation.java deleted file mode 100644 index 1b8e5fb..0000000 --- a/core/src/test/java/mc/core/TestEntityLocation.java +++ /dev/null @@ -1,96 +0,0 @@ -package mc.core; - -import mc.core.world.World; -import mc.core.world.chunk.Chunk; -import org.junit.Before; -import org.junit.Test; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertSame; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class TestEntityLocation { - private World world; - - @Before - public void prepareWorld() { - this.world = mock(World.class); - when(world.getChunk(anyInt(), anyInt())).thenAnswer(invocation -> { - Object[] args = invocation.getArguments(); - - Chunk chunk = mock(Chunk.class); - when(chunk.getX()).thenReturn((int) args[0]); - when(chunk.getZ()).thenReturn((int) args[1]); - - return chunk; - }); - } - - @Test - public void cloneTest() { - EntityLocation firstLocation = new EntityLocation(10, 20, 30, 40, 50, world); - assertSame("Lost world reference before cloning", world, firstLocation.getWorld()); - EntityLocation locationClone = firstLocation.clone(); - - assertEquals("X mismatch", firstLocation.getX(), locationClone.getX(), 0); - assertEquals("Y mismatch", firstLocation.getY(), locationClone.getY(), 0); - assertEquals("Z mismatch", firstLocation.getZ(), locationClone.getZ(), 0); - assertEquals("Pitch mismatch", firstLocation.getPitch(), locationClone.getPitch(), 0); - assertEquals("Yaw mismatch", firstLocation.getYaw(), locationClone.getYaw(), 0); - assertSame("World mismatch (accidental clone of the World object?)", firstLocation.getWorld(), locationClone.getWorld()); - } - - @Test - public void testGetChunk() { - EntityLocation location; - Chunk chunk; - - location = new EntityLocation(0d, 0, 0d, 0f, 0f, world); - chunk = location.getChunk(); - assertEquals(0, chunk.getX()); - assertEquals(0, chunk.getZ()); - - location.setXYZ(1d, 0, 1d); - chunk = location.getChunk(); - assertEquals(0, chunk.getX()); - assertEquals(0, chunk.getZ()); - - location.setXYZ(15d, 0, 15d); - chunk = location.getChunk(); - assertEquals(0, chunk.getX()); - assertEquals(0, chunk.getZ()); - - location.setXYZ(16d, 0, 16d); - chunk = location.getChunk(); - assertEquals(1, chunk.getX()); - assertEquals(1, chunk.getZ()); - - location.setXYZ(-0.1d, 0, -0.1d); - chunk = location.getChunk(); - assertEquals(-1, chunk.getX()); - assertEquals(-1, chunk.getZ()); - - location.setXYZ(-1d, 0, -1d); - chunk = location.getChunk(); - assertEquals(-1, chunk.getX()); - assertEquals(-1, chunk.getZ()); - - location.setXYZ(-15d, 0, -15d); - chunk = location.getChunk(); - assertEquals(-1, chunk.getX()); - assertEquals(-1, chunk.getZ()); - - //TODO на практике, таких точных значений не встретиться, но тем не менее данный тест не проходит - //location.setXYZ(-16.0d, 0, -16.0d); - //chunk = location.getChunk(); - //assertEquals(-2, chunk.getX()); - //assertEquals(-2, chunk.getZ()); - - location.setXYZ(-16.001d, 0, -16.001d); - chunk = location.getChunk(); - assertEquals(-2, chunk.getX()); - assertEquals(-2, chunk.getZ()); - } -} diff --git a/core/src/test/java/mc/core/TestLocation.java b/core/src/test/java/mc/core/TestLocation.java deleted file mode 100644 index 4faef07..0000000 --- a/core/src/test/java/mc/core/TestLocation.java +++ /dev/null @@ -1,52 +0,0 @@ -package mc.core; - -import org.junit.Test; - -import static org.junit.Assert.assertEquals; - -public class TestLocation { - @Test - public void testGetBlockXZ() { - Location location; - - location = new Location(0d, 0, 0d); - assertEquals(0, location.getBlockX()); - assertEquals(0, location.getBlockZ()); - - location.setXYZ(0.1d, 0, 0.1d); - assertEquals(0, location.getBlockX()); - assertEquals(0, location.getBlockZ()); - - location.setXYZ(0.5d, 0, 0.5d); - assertEquals(0, location.getBlockX()); - assertEquals(0, location.getBlockZ()); - - location.setXYZ(0.9d, 0, 0.9d); - assertEquals(0, location.getBlockX()); - assertEquals(0, location.getBlockZ()); - - location.setXYZ(1d, 0, 1d); - assertEquals(1, location.getBlockX()); - assertEquals(1, location.getBlockZ()); - - location.setXYZ(-0.1d, 0, -0.1d); - assertEquals(-1, location.getBlockX()); - assertEquals(-1, location.getBlockZ()); - - location.setXYZ(-0.5d, 0, -0.5d); - assertEquals(-1, location.getBlockX()); - assertEquals(-1, location.getBlockZ()); - - location.setXYZ(-0.9d, 0, -0.9d); - assertEquals(-1, location.getBlockX()); - assertEquals(-1, location.getBlockZ()); - - location.setXYZ(-1d, 0, -1d); - assertEquals(-1, location.getBlockX()); - assertEquals(-1, location.getBlockZ()); - - location.setXYZ(-1.1d, 0, -1.1d); - assertEquals(-2, location.getBlockX()); - assertEquals(-2, location.getBlockZ()); - } -} diff --git a/core/src/test/java/mc/core/TestSpringConfig.java b/core/src/test/java/mc/core/TestSpringConfig.java new file mode 100644 index 0000000..34dc1de --- /dev/null +++ b/core/src/test/java/mc/core/TestSpringConfig.java @@ -0,0 +1,34 @@ +package mc.core; + +import mc.core.world.World; +import mc.core.world.chunk.Chunk; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@Configuration +public class TestSpringConfig { + @Bean() + public World simpleMockWorld() { + return mock(World.class); + } + + @Bean + public World chunkedMockWorld() { + World world = mock(World.class); + when(world.getChunk(anyInt(), anyInt())).thenAnswer(invocation -> { + Object[] args = invocation.getArguments(); + + Chunk chunk = mock(Chunk.class); + when(chunk.getX()).thenReturn((int) args[0]); + when(chunk.getZ()).thenReturn((int) args[1]); + + return chunk; + }); + + return world; + } +} diff --git a/core/src/test/java/mc/core/text/TextTest.java b/core/src/test/java/mc/core/text/TextTest.java new file mode 100644 index 0000000..fbb417b --- /dev/null +++ b/core/src/test/java/mc/core/text/TextTest.java @@ -0,0 +1,76 @@ +package mc.core.text; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class TextTest { + @Test + void toPlain() { + final String m1 = "mes"; + final String m2 = "sage"; + final String message = m1 + m2; + + assertEquals(message, Text.of(message).toPlain()); + assertEquals(message, Text.builder(message).build().toPlain()); + assertEquals(message, Text.builder(Text.of(message)).build().toPlain()); + assertEquals(message, Text.builder().append(message).build().toPlain()); + assertEquals(message, Text.builder().append(Text.of(message)).build().toPlain()); + + assertEquals(message, Text.builder(m1, m2).build().toPlain()); + assertEquals(message, Text.builder(Text.of(m1), Text.of(m2)).build().toPlain()); + assertEquals(message, Text.builder().append(Text.of(m1), Text.of(m2)).build().toPlain()); + assertEquals(message, Text.builder().append(Text.of(m1)).append(Text.of(m2)).build().toPlain()); + + + } + + @Test + void equals_() { + assertEquals(Text.of(), Text.of("")); + assertEquals(Text.of(), Text.builder().build()); + assertEquals(Text.of(), Text.builder("").build()); + assertEquals(Text.of(), Text.builder().append().build()); + assertEquals(Text.of(), Text.builder().append("").build()); + + assertNotEquals(Text.of(), Text.of("??")); + assertNotEquals(Text.of(), Text.builder("??").build()); + assertNotEquals(Text.of(), Text.builder().append("??").build()); + + assertEquals(Text.of("message"), Text.builder("message").build()); + assertEquals(Text.of("message"), Text.builder(Text.of("message")).build()); + assertEquals(Text.of("message"), Text.builder().append("message").build()); + assertEquals(Text.of("message"), Text.builder().append(Text.of("message")).build()); + } + + @Test + void isEmpty() { + assertTrue(Text.of().isEmpty()); + assertTrue(Text.of((String) null).isEmpty()); + assertTrue(Text.of((Text) null).isEmpty()); + assertTrue(Text.of("").isEmpty()); + assertTrue(Text.of("", "").isEmpty()); + + assertTrue(Text.builder().build().isEmpty()); + assertTrue(Text.builder((String) null).build().isEmpty()); + assertTrue(Text.builder((Text) null).build().isEmpty()); + assertTrue(Text.builder("").build().isEmpty()); + assertTrue(Text.builder("", "").build().isEmpty()); + assertTrue(Text.builder(Text.of()).build().isEmpty()); + assertTrue(Text.builder(Text.of(), Text.of()).build().isEmpty()); + + assertTrue(Text.builder().append().build().isEmpty()); + assertTrue(Text.builder().append((String) null).build().isEmpty()); + assertTrue(Text.builder().append((Text) null).build().isEmpty()); + assertTrue(Text.builder().append("").build().isEmpty()); + assertTrue(Text.builder().append(Text.of()).build().isEmpty()); + assertTrue(Text.builder().append(Text.of(), Text.of()).build().isEmpty()); + assertTrue(Text.builder().append(Text.of()).append(Text.of()).build().isEmpty()); + + assertFalse(Text.of("??").isEmpty()); + assertFalse(Text.builder("??").build().isEmpty()); + assertFalse(Text.builder(Text.of("??")).build().isEmpty()); + assertFalse(Text.builder().append("??").build().isEmpty()); + assertFalse(Text.builder().append(Text.of("??")).build().isEmpty()); + } +} diff --git a/core/src/test/java/mc/core/utils/CompactedCoordsTest.java b/core/src/test/java/mc/core/utils/CompactedCoordsTest.java new file mode 100644 index 0000000..f88443e --- /dev/null +++ b/core/src/test/java/mc/core/utils/CompactedCoordsTest.java @@ -0,0 +1,25 @@ +package mc.core.utils; + +import org.junit.jupiter.api.Test; + +import java.util.concurrent.ThreadLocalRandom; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class CompactedCoordsTest { + @Test + void compressXZ() { + ThreadLocalRandom random = ThreadLocalRandom.current(); + + for (int i = 0; i < 100; i++) { + final int x = random.nextInt(Short.MIN_VALUE, Short.MAX_VALUE); + final int z = random.nextInt(Short.MIN_VALUE, Short.MAX_VALUE); + + final int compressXZ = CompactedCoords.compressXZ(x, z); + int[] xz = CompactedCoords.uncompressXZ(compressXZ); + + assertEquals(x, xz[0]); + assertEquals(z, xz[1]); + } + } +} diff --git a/core/src/test/java/mc/core/utils/TestCompactedCoords.java b/core/src/test/java/mc/core/utils/TestCompactedCoords.java deleted file mode 100644 index 0aef3da..0000000 --- a/core/src/test/java/mc/core/utils/TestCompactedCoords.java +++ /dev/null @@ -1,62 +0,0 @@ -package mc.core.utils; - -import org.junit.Test; - -import java.util.Random; - -import static org.junit.Assert.assertEquals; - - -public class TestCompactedCoords { - @Test - public void testXZSimple() { - for (int z = -100; z <= 100; z++) { - for (int x = -100; x <= 100; x++) { - int compressXZ = CompactedCoords.compressXZ(x, z); - int[] xz = CompactedCoords.uncompressXZ(compressXZ); - - assertEquals(x, xz[0]); - assertEquals(z, xz[1]); - } - } - } - - @Test - public void testXZRandom() { - Random random = new Random(); - int x,z; - - for (int i = 0; i < 100; i++) { - do { - x = random.nextInt(); - } while (x < Short.MIN_VALUE || x > Short.MAX_VALUE); - - do { - z = random.nextInt(); - } while (z < Short.MIN_VALUE || z > Short.MAX_VALUE); - - - int compressXZ = CompactedCoords.compressXZ(x, z); - int[] xz = CompactedCoords.uncompressXZ(compressXZ); - - assertEquals(x, xz[0]); - assertEquals(z, xz[1]); - } - } - -// @Test - public void testXYZSimple() { - for (int z = -100; z <= 100; z++) { - for (int x = -100; x <= 100; x++) { - for (int y = -100; y <= 100; y++) { - long compressXYZ = CompactedCoords.compressXYZ(x, y, z); - double[] xyz = CompactedCoords.uncompressXYZ(compressXYZ); - - assertEquals(x, xyz[0], 0.001d); - assertEquals(y, xyz[1], 0.001d); - assertEquals(z, xyz[2], 0.001d); - } - } - } - } -} diff --git a/core/src/test/java/mc/core/world/block/BlockLocationTest.java b/core/src/test/java/mc/core/world/block/BlockLocationTest.java new file mode 100644 index 0000000..d716995 --- /dev/null +++ b/core/src/test/java/mc/core/world/block/BlockLocationTest.java @@ -0,0 +1,39 @@ +package mc.core.world.block; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.concurrent.ThreadLocalRandom; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +class BlockLocationTest { + private static final ThreadLocalRandom rnd = ThreadLocalRandom.current(); + private static final int minI = 0, maxI = 10; + private int x, y, z; + + @BeforeEach + void before() { + x = rnd.nextInt(minI, maxI); + y = rnd.nextInt(minI, maxI); + z = rnd.nextInt(minI, maxI); + } + + @Test + void equals_() { + BlockLocation loc1 = new BlockLocation(x, y, z); + BlockLocation loc2 = new BlockLocation(x, y, z); + assertEquals(loc1, loc2); + + loc2 = new BlockLocation(x+1, y+2, z-3); + assertNotEquals(loc1, loc2); + } + + @Test + void clone_() { + BlockLocation locOrig = new BlockLocation(x, y, z); + BlockLocation locClone = locOrig.clone(); + assertEquals(locOrig, locClone); + } +} diff --git a/h2_playermanager/build.gradle b/h2_playermanager/build.gradle new file mode 100644 index 0000000..acdbf0c --- /dev/null +++ b/h2_playermanager/build.gradle @@ -0,0 +1,17 @@ +version '1.0-SNAPSHOT' + +ext { + spring_data_version = '2.1.0.RELEASE' +} + +dependencies { + /* Core */ + compile_excludeCopy project(':core') + + /* Spring */ + compile (group: 'org.springframework.data', name: 'spring-data-jpa', version: spring_data_version) + + /* Database */ + compile (group: 'org.hibernate', name: 'hibernate-entitymanager', version: '5.3.6.Final') + compile (group: 'com.h2database', name: 'h2', version: '1.4.197') +} \ No newline at end of file diff --git a/h2_playermanager/src/main/java/mc/core/h2db/H2Player.java b/h2_playermanager/src/main/java/mc/core/h2db/H2Player.java new file mode 100644 index 0000000..8880503 --- /dev/null +++ b/h2_playermanager/src/main/java/mc/core/h2db/H2Player.java @@ -0,0 +1,63 @@ +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.List; +import java.util.Objects; +import java.util.UUID; + +@Data +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; + private Reference $refWorld; + private boolean flying = false; + private PlayerSettings settings; + + @Override + public World getWorld() { + if ($refWorld == null) { + return null; + } else if ($refWorld.get() == null) { + throw new ResourceUnloadedException("You're trying to get unloaded world"); + } else { + return $refWorld.get(); + } + } + + @Override + public void setWorld(World world) { + if (world == null) { + this.$refWorld = null; + } else { + this.$refWorld = new WeakReference<>(world); + } + } + + @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/H2PlayerManager.java b/h2_playermanager/src/main/java/mc/core/h2db/H2PlayerManager.java new file mode 100644 index 0000000..9741d30 --- /dev/null +++ b/h2_playermanager/src/main/java/mc/core/h2db/H2PlayerManager.java @@ -0,0 +1,115 @@ +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.h2db.service.H2PlayerService; +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 java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +@Slf4j +@Component +public class H2PlayerManager implements PlayerManager { + @Setter + @Autowired + private H2PlayerService h2PlayerService; + private List playerList = Collections.synchronizedList(new ArrayList<>()); + @Autowired + private World world; //FIXME + + @Override + public Player createPlayer(String name, EntityLocation location, World world) { + 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()); + + return h2PlayerService.save(h2Player); + } + + @Override + public void joinServer(Player player) { + //TODO в дальнейшем следует именно этому методу передать функции инсерта в БД + H2Player h2Player = (H2Player) player; + playerList.add(h2Player); + h2Player.setOnline(true); + } + + @Override + public void leftServer(Player player) { + H2Player h2Player = (H2Player) player; + h2PlayerService.save(h2Player); + h2Player.setOnline(false); + 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) { + //TODO похоже в попытке где-то оптимизировать/сэконопить я сам себя ******[обманул] + //необходимо этот участок кода переписать + //потому как похоже на экономию на спичках + H2Player h2Player = playerList.stream() + .filter(player -> player.getName().equals(name)) + .filter(player -> !player.isOnline()) + .findFirst().orElse(null); + + if (h2Player == null) { + h2Player = h2PlayerService.getByName(name); + if (h2Player != null) { + h2Player.setWorld(world); + return h2Player; + } else { + return null; + } + } else { + h2Player.setWorld(world); + return h2Player; + } + } +} diff --git a/h2_playermanager/src/main/java/mc/core/h2db/entity/H2PlayerEntity.java b/h2_playermanager/src/main/java/mc/core/h2db/entity/H2PlayerEntity.java new file mode 100644 index 0000000..1edc5f4 --- /dev/null +++ b/h2_playermanager/src/main/java/mc/core/h2db/entity/H2PlayerEntity.java @@ -0,0 +1,104 @@ +package mc.core.h2db.entity; + +import lombok.Data; +import lombok.NoArgsConstructor; +import mc.core.EntityLocation; +import mc.core.h2db.H2Player; +import org.hibernate.annotations.GenericGenerator; + +import javax.persistence.*; +import java.util.UUID; + +@Entity +@Table(name = "players", + indexes = {@Index(name = "idx_players_uuid", columnList = "uuid", unique = true), + @Index(name = "idx_players_name", columnList = "name")}) +@NoArgsConstructor +@Data +public class H2PlayerEntity { + @Id + @GeneratedValue(generator = "increment") + @GenericGenerator(name= "increment", strategy= "increment") + @Column(nullable = false) + private Long id; + + @Column(length = 36, nullable = false) + private String uuid; + + @Column(length = 16, nullable = false) + private String name; + + @Column(name = "location_x", nullable = false) + private Double locationX; + + @Column(name = "location_y", nullable = false) + private Double locationY; + + @Column(name = "location_z", nullable = false) + private Double locationZ; + + @Column(name = "location_yaw", nullable = false) + private Float locationYaw; + + @Column(name = "location_pitch", nullable = false) + private Float locationPitch; + + @Column(name = "location_world", length = 64, nullable = false) + private String locationWorld; + + public H2PlayerEntity(H2Player player) { + this.id = (long) player.getId(); + setUuid(player.getUuid().toString()); + setName(this.name = player.getName()); + this.locationX = player.getLocation().getX(); + this.locationY = player.getLocation().getY(); + this.locationZ = player.getLocation().getZ(); + this.locationYaw = player.getLocation().getYaw(); + this.locationPitch = player.getLocation().getPitch(); + if (player.getWorld() != null) { //FIXME + this.locationWorld = player.getWorld().getName(); + } else { + this.locationWorld = "null_world"; + } + } + + public void setUuid(String uuid) { + if (uuid == null || uuid.trim().isEmpty()) { + this.uuid = null; + } else { + this.uuid = uuid; + } + } + + public void setName(String name) { + if (name == null || name.trim().isEmpty()) { + this.name = null; + } else { + this.name = name; + } + } + + public H2Player toPlayer() { + H2Player player = new H2Player(); + return toPlayer(player); + } + + public H2Player toPlayer(H2Player player) { + player.setId(this.id.intValue()); + player.setUuid(UUID.fromString(this.uuid)); + player.setName(this.name); + if (player.getLocation() == null) { + player.setLocation(new EntityLocation( + this.locationX, this.locationY, this.locationZ, + this.locationYaw, this.locationPitch + )); + } else { + player.getLocation().setXYZ(this.locationX, this.locationY, this.locationZ); + player.getLocation().setYawPitch(this.locationYaw, this.locationPitch); + } + + player.setWorld(null); //FIXME + + return player; + } +} diff --git a/h2_playermanager/src/main/java/mc/core/h2db/repository/H2PlayerEntityRepository.java b/h2_playermanager/src/main/java/mc/core/h2db/repository/H2PlayerEntityRepository.java new file mode 100644 index 0000000..47b4f45 --- /dev/null +++ b/h2_playermanager/src/main/java/mc/core/h2db/repository/H2PlayerEntityRepository.java @@ -0,0 +1,12 @@ +package mc.core.h2db.repository; + +import mc.core.h2db.entity.H2PlayerEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface H2PlayerEntityRepository extends JpaRepository { + Optional findByName(String name); +} diff --git a/h2_playermanager/src/main/java/mc/core/h2db/service/H2PlayerService.java b/h2_playermanager/src/main/java/mc/core/h2db/service/H2PlayerService.java new file mode 100644 index 0000000..a20fb7b --- /dev/null +++ b/h2_playermanager/src/main/java/mc/core/h2db/service/H2PlayerService.java @@ -0,0 +1,11 @@ +package mc.core.h2db.service; + +import mc.core.h2db.H2Player; + +public interface H2PlayerService { + H2Player save(H2Player player); + void remove(H2Player player); + + H2Player getByName(String name); + H2Player getById(int id); +} diff --git a/h2_playermanager/src/main/java/mc/core/h2db/service/H2PlayerServiceImpl.java b/h2_playermanager/src/main/java/mc/core/h2db/service/H2PlayerServiceImpl.java new file mode 100644 index 0000000..d716444 --- /dev/null +++ b/h2_playermanager/src/main/java/mc/core/h2db/service/H2PlayerServiceImpl.java @@ -0,0 +1,41 @@ +package mc.core.h2db.service; + +import mc.core.h2db.H2Player; +import mc.core.h2db.entity.H2PlayerEntity; +import mc.core.h2db.repository.H2PlayerEntityRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +@Service +public class H2PlayerServiceImpl implements H2PlayerService { + @Autowired + private H2PlayerEntityRepository h2PlayerEntityRepository; + + @Override + public H2Player save(H2Player player) { + H2PlayerEntity entity = new H2PlayerEntity(player); + //TODO возможно имеет смысл здесь оптимизация + //вместо toPlayer() сделать toPlayer(H2Player) который в существующий + //будет дописывать/обновлять данные + return h2PlayerEntityRepository.saveAndFlush(entity).toPlayer(player); + } + + @Override + public void remove(H2Player player) { + h2PlayerEntityRepository.deleteById((long) player.getId()); + } + + @Override + public H2Player getByName(String name) { + Optional optEntity = h2PlayerEntityRepository.findByName(name); + return optEntity.map(H2PlayerEntity::toPlayer).orElse(null); + } + + @Override + public H2Player getById(int id) { + Optional optEntity = h2PlayerEntityRepository.findById((long) id); + return optEntity.map(H2PlayerEntity::toPlayer).orElse(null); + } +} diff --git a/h2_playermanager/src/test/java/mc/core/h2db/H2PlayerManagerTest.java b/h2_playermanager/src/test/java/mc/core/h2db/H2PlayerManagerTest.java new file mode 100644 index 0000000..dd25625 --- /dev/null +++ b/h2_playermanager/src/test/java/mc/core/h2db/H2PlayerManagerTest.java @@ -0,0 +1,159 @@ +package mc.core.h2db; + +import mc.core.EntityLocation; +import mc.core.h2db.service.H2PlayerService; +import mc.core.player.Player; +import mc.core.world.World; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = {TestSpringConfig.class}) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +class H2PlayerManagerTest { + @Autowired + private H2PlayerService h2PlayerService; + @Autowired + private World mockWorld; + @Autowired + private H2PlayerManager playerManager; + + @Test + void createPlayer() { + 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 = h2PlayerService.getByName(playerName); + assertTrue(queryPlayer.getId() > 0); + + assertEquals(newPlayer, queryPlayer); + assertEquals(newPlayer.getName(), queryPlayer.getName()); + assertEquals(newPlayer.getLocation(), queryPlayer.getLocation()); + assertEquals(newPlayer.getWorld(), queryPlayer.getWorld()); + } + + @Test + void joinServer() { + 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 + void leftServer() { + 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()); + assertTrue(player.getLoadedChunks().isEmpty()); + + final H2Player queryPlayer = h2PlayerService.getById(playerId); + + assertNotNull(queryPlayer); + ((H2Player)player).setId(playerId); + assertEquals(player, queryPlayer); + } + + @Test + void getPlayer() { + 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 + void getPlayers() { + 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 + void getCountPlayers() { + 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 + void getOfflinePlayer() { + 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/java/mc/core/h2db/H2PlayerTest.java b/h2_playermanager/src/test/java/mc/core/h2db/H2PlayerTest.java new file mode 100644 index 0000000..271fae7 --- /dev/null +++ b/h2_playermanager/src/test/java/mc/core/h2db/H2PlayerTest.java @@ -0,0 +1,36 @@ +package mc.core.h2db; + +import org.junit.jupiter.api.Test; + +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +class H2PlayerTest { + @Test + void equals_() { + 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/TestSpringConfig.java b/h2_playermanager/src/test/java/mc/core/h2db/TestSpringConfig.java new file mode 100644 index 0000000..5ff647b --- /dev/null +++ b/h2_playermanager/src/test/java/mc/core/h2db/TestSpringConfig.java @@ -0,0 +1,90 @@ +package mc.core.h2db; + +import mc.core.h2db.service.H2PlayerService; +import mc.core.world.World; +import org.hibernate.jpa.HibernatePersistenceProvider; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.jdbc.datasource.DriverManagerDataSource; +import org.springframework.orm.jpa.JpaTransactionManager; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import javax.persistence.EntityManagerFactory; +import javax.sql.DataSource; +import java.util.Properties; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@Configuration +@EnableJpaRepositories +@EnableTransactionManagement +@ComponentScan("mc.core.h2db") +public class TestSpringConfig { + private static final String DATABASE_DRIVER = "org.h2.Driver"; + private static final String DATABASE_URL = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1"; + private static final String DATABASE_USERNAME = "sa"; + private static final String DATABASE_PASSWORD = "s3cReT"; + + static { + System.setProperty("org.jboss.logging.provider", "slf4j"); + } + + private Properties hibernateProp() { + Properties properties = new Properties(); + properties.put("hibernate.dialect", "org.hibernate.dialect.H2Dialect"); + properties.put("hibernate.show_sql", "true"); + properties.put("hibernate.format_sql", "true"); + properties.put("hibernate.use_sql_comments", "true"); + properties.put("hibernate.hbm2ddl.auto", "create"); + + return properties; + } + + @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(DATABASE_DRIVER); + dmds.setUrl(DATABASE_URL); + dmds.setUsername(DATABASE_USERNAME); + dmds.setPassword(DATABASE_PASSWORD); + + return dmds; + } + + @Bean + public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) { + LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean(); + entityManagerFactoryBean.setDataSource(dataSource); + entityManagerFactoryBean.setPersistenceProviderClass(HibernatePersistenceProvider.class); + entityManagerFactoryBean.setPackagesToScan("mc.core.h2db.entity"); + entityManagerFactoryBean.setJpaProperties(hibernateProp()); + + return entityManagerFactoryBean; + } + + @Bean + public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) { + JpaTransactionManager transactionManager = new JpaTransactionManager(); + transactionManager.setEntityManagerFactory(entityManagerFactory); + + return transactionManager; + } + + @Bean + public H2PlayerManager h2PlayerManager(H2PlayerService h2PlayerService) { + H2PlayerManager playerManager = new H2PlayerManager(); + playerManager.setH2PlayerService(h2PlayerService); + return playerManager; + } +} diff --git a/h2_playermanager/src/test/java/mc/core/h2db/service/H2PlayerServiceTest.java b/h2_playermanager/src/test/java/mc/core/h2db/service/H2PlayerServiceTest.java new file mode 100644 index 0000000..6440b3e --- /dev/null +++ b/h2_playermanager/src/test/java/mc/core/h2db/service/H2PlayerServiceTest.java @@ -0,0 +1,144 @@ +package mc.core.h2db.service; + +import mc.core.EntityLocation; +import mc.core.h2db.H2Player; +import mc.core.h2db.TestSpringConfig; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; + +import static org.junit.jupiter.api.Assertions.*; + +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = {TestSpringConfig.class}) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +class H2PlayerServiceTest { + @Autowired + private H2PlayerService h2PlayerService; + + private H2Player buildPlayer() { + 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; + + final H2Player 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(null); //FIXME + + return player; + } + + @Test + void save() { + H2Player player = buildPlayer(); + H2Player savedPlayer = h2PlayerService.save(player); + + player.setId(savedPlayer.getId()); //FIXME костыль, однако + assertEquals(player, savedPlayer); + } + + @Test + void save_NameEmpty() { + assertThrows(Exception.class, () -> { + H2Player player = buildPlayer(); + player.setName(""); + h2PlayerService.save(player); + }); + } + + @Test + void save_NameNull() { + assertThrows(Exception.class, () -> { + H2Player player = buildPlayer(); + player.setName(null); + h2PlayerService.save(player); + }); + } + + @Test + void save_UuidNull() { + assertThrows(Exception.class, () -> { + H2Player player = buildPlayer(); + player.setUuid(null); + h2PlayerService.save(player); + }); + } + + @Test + void save_LocationNull() { + assertThrows(Exception.class, () -> { + H2Player player = buildPlayer(); + player.setLocation(null); + h2PlayerService.save(player); + }); + } + + @Test + void remove() { + H2Player player = h2PlayerService.save(buildPlayer()); + h2PlayerService.remove(player); + + H2Player player2 = h2PlayerService.getById(player.getId()); + assertNull(player2); + } + + @Test + void remove_NotExists() { + assertThrows(Exception.class, () -> { + H2Player player = h2PlayerService.save(buildPlayer()); + h2PlayerService.remove(player); + h2PlayerService.remove(player); + }); + } + + @Test + void getByName() { + H2Player player = h2PlayerService.save(buildPlayer()); + + H2Player player2 = h2PlayerService.getByName(player.getName()); + assertEquals(player, player2); + } + + @Test + void getByName_NotExists() { + assertNull(h2PlayerService.getByName("UNKNOW_PLAYER")); + } + + @Test + void getByName_Empty() { + assertNull(h2PlayerService.getByName("")); + } + + @Test + void getByName_Null() { + assertNull(h2PlayerService.getByName(null)); + } + + @Test + void getById() { + H2Player player = h2PlayerService.save(buildPlayer()); + + H2Player player2 = h2PlayerService.getById(player.getId()); + assertEquals(player, player2); + } + + @Test + void getById_NotExists() { + assertNull(h2PlayerService.getById(9999)); + } +} diff --git a/proto_1.12.2/build.gradle b/proto_1.12.2/build.gradle index 463d1fb..8e3a7e2 100644 --- a/proto_1.12.2/build.gradle +++ b/proto_1.12.2/build.gradle @@ -1,4 +1,3 @@ -group 'mc' version '1.0-SNAPSHOT' dependencies { @@ -6,6 +5,5 @@ dependencies { compile_excludeCopy project(':core') /* Components */ - compile (group: 'com.google.guava', name: 'guava', version: '24.1-jre') - compile (group: 'com.google.code.gson', name: 'gson', version: '2.8.2') + compile (group: 'com.google.code.gson', name: 'gson', version: '2.8.5') } diff --git a/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/NetInputStream_p340.java b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/NetInputStream_p340.java index 418f9aa..84bbd08 100644 --- a/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/NetInputStream_p340.java +++ b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/NetInputStream_p340.java @@ -13,25 +13,31 @@ import java.util.UUID; @Slf4j public abstract class NetInputStream_p340 extends NetInputStream { @Override - public int readVarInt() { + public int readVarInt(int[] countReadBytes) { int numRead = 0; int result = 0; byte read; do { + if ((numRead+1) > 5) { + log.warn("VarInt is too big"); + break; + } read = readByte(); int value = (read & 0b01111111); result |= (value << (7 * numRead)); numRead++; - if (numRead > 5) { - log.warn("VarInt is too big"); - break; - } } while ((read & 0b10000000) != 0); + if (countReadBytes != null && countReadBytes.length == 1) countReadBytes[0] = numRead; return result; } + @Override + public int readVarInt() { + return readVarInt(null); + } + @Override public String readString() { int size = readVarInt(); diff --git a/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/TeleportManager.java b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/TeleportManager.java index 951f098..ba9cd07 100644 --- a/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/TeleportManager.java +++ b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/TeleportManager.java @@ -45,8 +45,7 @@ public class TeleportManager { public void apply(int teleportId) { if (teleportMap.containsKey(teleportId)) { TpData data = teleportMap.remove(teleportId); - data.player.getLocation().setXYZ(data.newLocation); - data.player.getLocation().setYawPitch(data.newLocation); + data.player.getLocation().set(data.newLocation); } } 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 6600db6..38db3cc 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/PlayerAbilitiesPacket.java b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/PlayerAbilitiesPacket.java index b3a8312..6258bc5 100644 --- a/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/PlayerAbilitiesPacket.java +++ b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/PlayerAbilitiesPacket.java @@ -18,6 +18,11 @@ import mc.core.network.SCPacket; @Setter @ToString public class PlayerAbilitiesPacket implements SCPacket, CSPacket { + private static final byte $GOD_MODE_MASK = 0x01, + $FLYING_MASK = 0x02, + $CAN_FLY_MASK = 0x04, + $IDB_MASK = 0x08; + private boolean godMode = false; private boolean flying = false; private boolean canFly = false; @@ -29,10 +34,10 @@ public class PlayerAbilitiesPacket implements SCPacket, CSPacket { @Override public void writeSelf(NetOutputStream netStream) { byte flag = 0; - if (godMode) flag = (byte)(flag | 0x01); - if (flying) flag = (byte)(flag | 0x02); - if (canFly) flag = (byte)(flag | 0x04); - if (instantDestroyBlocks) flag = (byte)(flag | 0x08); + if (godMode) flag = (byte)(flag | $GOD_MODE_MASK); + if (flying) flag = (byte)(flag | $FLYING_MASK); + if (canFly) flag = (byte)(flag | $CAN_FLY_MASK); + if (instantDestroyBlocks) flag = (byte)(flag | $IDB_MASK); netStream.writeByte(flag); netStream.writeFloat(flyingSpeed); @@ -42,11 +47,10 @@ public class PlayerAbilitiesPacket implements SCPacket, CSPacket { @Override public void readSelf(NetInputStream netStream) { byte flag = netStream.readByte(); - //FIXME треубет проверки - godMode = (flag == 0x08); - canFly = (flag == 0x04); - flying = (flag == 0x02); - instantDestroyBlocks = (flag == 0x01); + godMode = (flag & $GOD_MODE_MASK) > 0; + canFly = (flag & $CAN_FLY_MASK) > 0; + flying = (flag & $FLYING_MASK) > 0; + instantDestroyBlocks = (flag & $IDB_MASK) > 0; flyingSpeed = netStream.readFloat(); walkingSpeed = netStream.readFloat(); 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 1a1ac1d..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 @@ -2,16 +2,16 @@ package mc.core.network.proto_1_12_2.packets; import lombok.Getter; import lombok.ToString; -import mc.core.Location; +import mc.core.network.proto_1_12_2.serializers.BlockLocationSerializer; +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 public class PlayerBlockPlacementPacket implements CSPacket { - private Location location; + private BlockLocation location; private Direction face; /** true - main hand; false - off hand */ private boolean hand; @@ -20,8 +20,7 @@ public class PlayerBlockPlacementPacket implements CSPacket { @Override public void readSelf(NetInputStream netStream) { long compactedCoords = netStream.readLong(); - double[] xyz = CompactedCoords.uncompressXYZ(compactedCoords); - location = new Location(xyz[0], xyz[1], xyz[2]); + location = BlockLocationSerializer.fromLong(compactedCoords); face = Direction.getById(netStream.readVarInt()); hand = (netStream.readVarInt() == 1); cursorX = netStream.readFloat(); 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 a2bde1e..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 @@ -3,11 +3,11 @@ package mc.core.network.proto_1_12_2.packets; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.ToString; -import mc.core.Location; +import mc.core.network.proto_1_12_2.serializers.BlockLocationSerializer; +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; @@ -42,15 +42,14 @@ public class PlayerDiggingPacket implements CSPacket { } private Status status; - private Location location; + private BlockLocation location; private Direction face; @Override public void readSelf(NetInputStream netStream) { status = Status.getById(netStream.readVarInt()); long compactCoord = netStream.readLong(); - double[] xyz = CompactedCoords.uncompressXYZ(compactCoord); - location = new Location(xyz[0], xyz[1], xyz[2]); + location = BlockLocationSerializer.fromLong(compactCoord); face = Direction.getById(netStream.readByte()); } } diff --git a/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/PlayerPositionAndLookPacket.java b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/PlayerPositionAndLookPacket.java index 985d8df..3c783a7 100644 --- a/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/PlayerPositionAndLookPacket.java +++ b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/PlayerPositionAndLookPacket.java @@ -48,8 +48,7 @@ public class PlayerPositionAndLookPacket implements SCPacket, CSPacket { netStream.readDouble(), netStream.readDouble(), netStream.readFloat(), - netStream.readFloat(), - null + netStream.readFloat() ); this.onGround = netStream.readBoolean(); diff --git a/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/TabCompletePacket.java b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/TabCompletePacket.java index 09f686d..558e8dc 100644 --- a/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/TabCompletePacket.java +++ b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/TabCompletePacket.java @@ -4,7 +4,7 @@ */ package mc.core.network.proto_1_12_2.packets; -import mc.core.Location; +import mc.core.world.block.BlockLocation; import mc.core.network.CSPacket; import mc.core.network.NetInputStream; @@ -12,7 +12,7 @@ public class TabCompletePacket implements CSPacket { private String text; private boolean assumeCommand; private boolean hasPosition; - private Location location; + private BlockLocation location; @Override public void readSelf(NetInputStream netStream) { @@ -27,7 +27,7 @@ public class TabCompletePacket implements CSPacket { double y = (compactValue >> 26) & 0xFFF; double z = compactValue << 38 >> 38; // is normal? - this.location = new Location(x, y, z); + this.location = new BlockLocation((int)x, (int)y, (int)z); //FIXME } } } diff --git a/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/serializers/BlockLocationSerializer.java b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/serializers/BlockLocationSerializer.java new file mode 100644 index 0000000..9aea2f5 --- /dev/null +++ b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/serializers/BlockLocationSerializer.java @@ -0,0 +1,63 @@ +package mc.core.network.proto_1_12_2.serializers; + +import mc.core.world.block.BlockLocation; + +import static com.google.common.math.IntMath.isPowerOfTwo; + +public class BlockLocationSerializer { + private static final int[] MULTIPLY_DE_BRUIJN_BIT_POSITION = new int[] {0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, 31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9}; + private static final int NUM_X_BITS = 1 + log2(smallestEncompassingPowerOfTwo(30000000)); + private static final int NUM_Z_BITS = NUM_X_BITS; + private static final int NUM_Y_BITS = 64 - NUM_X_BITS - NUM_Z_BITS; + private static final int Y_SHIFT = NUM_Z_BITS; + private static final int X_SHIFT = Y_SHIFT + NUM_Y_BITS; + private static final long X_MASK = (1L << NUM_X_BITS) - 1L; + private static final long Y_MASK = (1L << NUM_Y_BITS) - 1L; + private static final long Z_MASK = (1L << NUM_Z_BITS) - 1L; + + /* + * net.minecraft.util.math.MathHelper#log2(int) + */ + private static int log2(int value) { + return log2DeBruijn(value) - (isPowerOfTwo(value) ? 0 : 1); + } + + /* + * net.minecraft.util.math.MathHelper#log2DeBruijn(int) + */ + private static int log2DeBruijn(int value) { + value = isPowerOfTwo(value) ? value : smallestEncompassingPowerOfTwo(value); + return MULTIPLY_DE_BRUIJN_BIT_POSITION[(int)((long)value * 125613361L >> 27) & 31]; + } + + /* + * net.minecraft.util.math.MathHelper#smallestEncompassingPowerOfTwo(int) + */ + private static int smallestEncompassingPowerOfTwo(int value) { + int i = value - 1; + i = i | i >> 1; + i = i | i >> 2; + i = i | i >> 4; + i = i | i >> 8; + i = i | i >> 16; + return i + 1; + } + + public static long toLong(BlockLocation location) { + return ((long)location.getX() & X_MASK) << X_SHIFT | + ((long)location.getY() & Y_MASK) << Y_SHIFT | + ((long)location.getZ() & Z_MASK); + } + + public static BlockLocation fromLong(long value) { + BlockLocation location = BlockLocation.ZERO(); + fromLong(value, location); + return location; + } + + public static void fromLong(long value, BlockLocation location) { + location.setX((int)(value << 64 - X_SHIFT - NUM_X_BITS >> 64 - NUM_X_BITS)); + location.setY((int)(value << 64 - Y_SHIFT - NUM_Y_BITS >> 64 - NUM_Y_BITS)); + location.setZ((int)(value << 64 - NUM_Z_BITS >> 64 - NUM_Z_BITS)); + } +} 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 new file mode 100644 index 0000000..c51beaf --- /dev/null +++ b/proto_1.12.2/src/test/java/mc/core/network/proto_1_12_2/packets/ByteArrayInputNetStream.java @@ -0,0 +1,72 @@ +package mc.core.network.proto_1_12_2.packets; + +import mc.core.network.proto_1_12_2.NetInputStream_p340; + +import java.io.ByteArrayInputStream; + +public class ByteArrayInputNetStream extends NetInputStream_p340 { + private ByteArrayInputStream bais; + + public ByteArrayInputNetStream(byte[] buff) { + bais = new ByteArrayInputStream(buff); + } + + @Override + public boolean readBoolean() { + return false; + } + + @Override + public byte readByte() { + return (byte) bais.read(); + } + + @Override + public void readBytes(byte[] buffer) { + } + + @Override + public int readUnsignedByte() { + return 0; + } + + @Override + public int readUnsignedShort() { + return 0; + } + + @Override + public short readShort() { + return 0; + } + + @Override + public int readInt() { + int ch1 = bais.read(); + int ch2 = bais.read(); + int ch3 = bais.read(); + int ch4 = bais.read(); + if ((ch1 | ch2 | ch3 | ch4) < 0) return 0; + return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0)); + } + + @Override + public long readLong() { + return 0; + } + + @Override + public float readFloat() { + return Float.intBitsToFloat(readInt()); + } + + @Override + public double readDouble() { + return 0; + } + + @Override + public void skipBytes(int count) { + + } +} diff --git a/proto_1.12.2/src/test/java/mc/core/network/proto_1_12_2/packets/ByteArrayInputNetStreamTest.java b/proto_1.12.2/src/test/java/mc/core/network/proto_1_12_2/packets/ByteArrayInputNetStreamTest.java new file mode 100644 index 0000000..4964a77 --- /dev/null +++ b/proto_1.12.2/src/test/java/mc/core/network/proto_1_12_2/packets/ByteArrayInputNetStreamTest.java @@ -0,0 +1,48 @@ +package mc.core.network.proto_1_12_2.packets; + +import mc.core.network.proto_1_12_2.ByteArrayOutputNetStream; +import org.junit.jupiter.api.Test; + +import java.util.Random; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ByteArrayInputNetStreamTest { + private Random rnd = new Random(); + + @Test + void readByte() { + final byte b0 = (byte) rnd.nextInt(); + ByteArrayOutputNetStream netStream = new ByteArrayOutputNetStream(); + netStream.writeByte(b0); + byte[] buffer = netStream.toByteArray(); + + ByteArrayInputNetStream netInputStream = new ByteArrayInputNetStream(buffer); + byte b1 = netInputStream.readByte(); + assertEquals(b0, b1); + } + + @Test + void readInt() { + final int i0 = rnd.nextInt(); + ByteArrayOutputNetStream netStream = new ByteArrayOutputNetStream(); + netStream.writeInt(i0); + byte[] buffer = netStream.toByteArray(); + + ByteArrayInputNetStream netInputStream = new ByteArrayInputNetStream(buffer); + int i1 = netInputStream.readInt(); + assertEquals(i0, i1); + } + + @Test + void readFloat() { + final float f0 = rnd.nextFloat(); + ByteArrayOutputNetStream netStream = new ByteArrayOutputNetStream(); + netStream.writeFloat(f0); + byte[] buffer = netStream.toByteArray(); + + ByteArrayInputNetStream netInputStream = new ByteArrayInputNetStream(buffer); + float f1 = netInputStream.readFloat(); + assertEquals(f0, f1, 0.00001f); + } +} \ No newline at end of file diff --git a/proto_1.12.2/src/test/java/mc/core/network/proto_1_12_2/packets/TestChunkdataPacket.java b/proto_1.12.2/src/test/java/mc/core/network/proto_1_12_2/packets/ChunkdataPacketTest.java similarity index 86% rename from proto_1.12.2/src/test/java/mc/core/network/proto_1_12_2/packets/TestChunkdataPacket.java rename to proto_1.12.2/src/test/java/mc/core/network/proto_1_12_2/packets/ChunkdataPacketTest.java index 449a672..5060676 100644 --- a/proto_1.12.2/src/test/java/mc/core/network/proto_1_12_2/packets/TestChunkdataPacket.java +++ b/proto_1.12.2/src/test/java/mc/core/network/proto_1_12_2/packets/ChunkdataPacketTest.java @@ -9,31 +9,31 @@ import mc.core.world.block.BlockFactory; import mc.core.world.block.BlockType; import mc.core.world.chunk.Chunk; import mc.core.world.chunk.ChunkSection; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.io.InputStream; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -public class TestChunkdataPacket { +class ChunkdataPacketTest { private static byte[] expectedPacketData; private World world; - @BeforeClass - public static void beforeClassTest() throws IOException { - InputStream inputStream = TestChunkdataPacket.class.getResourceAsStream("ChunkDataPacket.bin"); + @BeforeAll + static void beforeClassTest() throws IOException { + InputStream inputStream = ChunkdataPacketTest.class.getResourceAsStream("ChunkDataPacket.bin"); expectedPacketData = ByteStreams.toByteArray(inputStream); } - @Before - public void prepareWorld() { + @BeforeEach + void prepareWorld() { final ChunkSection chunkSection = mock(ChunkSection.class); when(chunkSection.getSkyLight(anyInt(), anyInt(), anyInt())).thenAnswer(invocation -> { int y = (int)invocation.getArguments()[1]; @@ -77,7 +77,7 @@ public class TestChunkdataPacket { } @Test - public void test() { + void writePacket() { ChunkDataPacket packet = new ChunkDataPacket(); packet.setX(0); packet.setZ(0); diff --git a/proto_1.12.2/src/test/java/mc/core/network/proto_1_12_2/packets/PlayerAbilitiesPacketTest.java b/proto_1.12.2/src/test/java/mc/core/network/proto_1_12_2/packets/PlayerAbilitiesPacketTest.java new file mode 100644 index 0000000..229a15a --- /dev/null +++ b/proto_1.12.2/src/test/java/mc/core/network/proto_1_12_2/packets/PlayerAbilitiesPacketTest.java @@ -0,0 +1,40 @@ +package mc.core.network.proto_1_12_2.packets; + +import mc.core.network.proto_1_12_2.ByteArrayOutputNetStream; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Random; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class PlayerAbilitiesPacketTest { + private Random rnd = new Random(); + private PlayerAbilitiesPacket packet; + + @BeforeEach + void before() { + packet = new PlayerAbilitiesPacket(); + packet.setGodMode(rnd.nextBoolean()); + packet.setFlying(rnd.nextBoolean()); + packet.setCanFly(rnd.nextBoolean()); + packet.setInstantDestroyBlocks(rnd.nextBoolean()); + packet.setFlyingSpeed(rnd.nextFloat()); + } + + @Test + void writePacket() { + ByteArrayOutputNetStream netOutputStream = new ByteArrayOutputNetStream(); + packet.writeSelf(netOutputStream); + + ByteArrayInputNetStream netInputStream = new ByteArrayInputNetStream(netOutputStream.toByteArray()); + PlayerAbilitiesPacket outPkt = new PlayerAbilitiesPacket(); + outPkt.readSelf(netInputStream); + + assertEquals(packet.isGodMode(), outPkt.isGodMode(), "god mode"); + assertEquals(packet.isFlying(), outPkt.isFlying(), "flying"); + assertEquals(packet.isCanFly(), outPkt.isCanFly(), "can fly"); + assertEquals(packet.isInstantDestroyBlocks(), outPkt.isInstantDestroyBlocks(), "instant destroy block"); + assertEquals(packet.getFlyingSpeed(), outPkt.getFlyingSpeed(), 0.00001f, "flying speed"); + } +} diff --git a/proto_1.12.2/src/test/java/mc/core/network/proto_1_12_2/serializers/BlockLocationSerializerTest.java b/proto_1.12.2/src/test/java/mc/core/network/proto_1_12_2/serializers/BlockLocationSerializerTest.java new file mode 100644 index 0000000..dda7500 --- /dev/null +++ b/proto_1.12.2/src/test/java/mc/core/network/proto_1_12_2/serializers/BlockLocationSerializerTest.java @@ -0,0 +1,32 @@ +package mc.core.network.proto_1_12_2.serializers; + +import mc.core.world.block.BlockLocation; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.concurrent.ThreadLocalRandom; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class BlockLocationSerializerTest { + private static final ThreadLocalRandom rnd = ThreadLocalRandom.current(); + private static final int minI = 0, maxI = 10; + private int x, y, z; + + @BeforeEach + void before() { + x = rnd.nextInt(minI, maxI); + y = rnd.nextInt(minI, maxI); + z = rnd.nextInt(minI, maxI); + } + + @Test + void serialize() { + BlockLocation location = new BlockLocation(x, y, z); + final long serializedCoords = BlockLocationSerializer.toLong(location); + + BlockLocation deserLoc = BlockLocationSerializer.fromLong(serializedCoords); + + assertEquals(location, deserLoc); + } +} \ No newline at end of file diff --git a/proto_1.12.2_netty/build.gradle b/proto_1.12.2_netty/build.gradle index 9704176..0e9cbf0 100644 --- a/proto_1.12.2_netty/build.gradle +++ b/proto_1.12.2_netty/build.gradle @@ -1,4 +1,3 @@ -group 'mc' version '1.0-SNAPSHOT' ext { diff --git a/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/KeepAliveThread.java b/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/KeepAliveThread.java new file mode 100644 index 0000000..afa4607 --- /dev/null +++ b/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/KeepAliveThread.java @@ -0,0 +1,49 @@ +package mc.core.network.proto_1_12_2.netty; + +import lombok.Getter; +import lombok.Setter; +import mc.core.network.proto_1_12_2.packets.KeepAlivePacket; +import mc.core.player.PlayerManager; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.Random; + +@Component +public class KeepAliveThread extends Thread { + private static final Random RAND = new Random(); + private final Object lock = new Object(); + @Autowired + private PlayerManager playerManager; + @Setter + private int interval = 10; + + public void notifyLock() { + synchronized (lock) { + lock.notify(); + } + } + + @Override + public void run() { + while (!Thread.currentThread().isInterrupted()) { + while(playerManager.getCountPlayers() == 0) { + synchronized (lock) { + try { + lock.wait(); + } catch (InterruptedException e) { + return; + } + } + } + + playerManager.getBroadcastChannel().writeAndFlush(new KeepAlivePacket(RAND.nextLong())); + + try { + Thread.sleep(interval); + } catch (InterruptedException e) { + return; + } + } + } +} diff --git a/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/NettyServer.java b/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/NettyServer.java index 79be3f9..7df1c3f 100644 --- a/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/NettyServer.java +++ b/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/NettyServer.java @@ -1,10 +1,7 @@ -/* - * DmitriyMX - * 2018-06-10 - */ package mc.core.network.proto_1_12_2.netty; import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelInitializer; import io.netty.channel.EventLoopGroup; @@ -14,7 +11,7 @@ import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.util.AttributeKey; import lombok.Setter; import lombok.extern.slf4j.Slf4j; -import mc.core.events.EventBusGetter; +import mc.core.eventbus.EventBusGetter; import mc.core.network.Server; import mc.core.network.StartServerException; import mc.core.network.proto_1_12_2.State; @@ -22,16 +19,20 @@ import mc.core.network.proto_1_12_2.packets.StatusResponsePacket; import mc.core.player.Player; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; import java.util.Map; @Slf4j +@Component public class NettyServer implements Server { public static final AttributeKey ATTR_STATE = AttributeKey.newInstance("ATTR_STATE"); public static final AttributeKey ATTR_PLAYER = AttributeKey.newInstance("ATTR_PLAYER"); @Autowired - private ApplicationContext applicationContext; + private ApplicationContext context; + @Autowired + private KeepAliveThread keepAliveThread; @Setter private String host; @Setter @@ -44,7 +45,7 @@ public class NettyServer implements Server { return new ChannelInitializer() { @Override protected void initChannel(SocketChannel socketChannel) { - Map beans = applicationContext.getBeansOfType(ChannelHandler.class); + Map beans = context.getBeansOfType(ChannelHandler.class); beans.forEach(socketChannel.pipeline()::addLast); } }; @@ -64,7 +65,7 @@ public class NettyServer implements Server { public void start() throws StartServerException { log.info("Use protocol {}", StatusResponsePacket.NAME); - EventBusGetter.INSTANCE.register(new PlayerEventListener()); + EventBusGetter.getInstance().register(new PlayerEventListener()); bossGroup = new NioEventLoopGroup(1); workerGroup = new NioEventLoopGroup(workerGroupCount); @@ -73,7 +74,9 @@ public class NettyServer implements Server { log.info("Start server: {}:{}", host, port); try { - serverBootstrap.bind(host, port).sync().channel().closeFuture().sync(); + ChannelFuture channelFuture = serverBootstrap.bind(host, port).sync(); + keepAliveThread.start(); + channelFuture.channel().closeFuture().sync(); } catch (InterruptedException e) { throw new StartServerException(e); } @@ -82,6 +85,7 @@ public class NettyServer implements Server { @Override public void stop() { log.info("Server shutdown"); + keepAliveThread.interrupt(); workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } diff --git a/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/PacketDecoder.java b/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/PacketDecoder.java index 57b1832..6115ced 100644 --- a/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/PacketDecoder.java +++ b/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/PacketDecoder.java @@ -1,12 +1,8 @@ -/* - * DmitriyMX - * 2018-06-10 - */ package mc.core.network.proto_1_12_2.netty; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.ByteToMessageDecoder; +import io.netty.handler.codec.ReplayingDecoder; import lombok.extern.slf4j.Slf4j; import mc.core.network.CSPacket; import mc.core.network.NetInputStream; @@ -18,7 +14,9 @@ import java.util.List; import static mc.core.network.proto_1_12_2.netty.NettyServer.ATTR_STATE; @Slf4j -public class PacketDecoder extends ByteToMessageDecoder { +public class PacketDecoder extends ReplayingDecoder { + private int[] countReadBytes = new int[]{0}; + @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { ctx.channel().attr(ATTR_STATE).set(State.HANDSHAKE); @@ -33,27 +31,25 @@ public class PacketDecoder extends ByteToMessageDecoder { @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { - log.debug("ByteBuf readableBytes: {}", in.readableBytes()); - State state = ctx.channel().attr(ATTR_STATE).get(); NetInputStream netStream = new WrapperNetInputStream(in); int packetSize = netStream.readVarInt(); log.debug("Packet size: {}", packetSize); - int rb = in.readableBytes(); + int leftDataPacket = packetSize; - int packetId = netStream.readVarInt(); + int packetId = netStream.readVarInt(countReadBytes); String hexPacketId = Integer.toHexString(packetId).toUpperCase(); if (hexPacketId.length() == 1) hexPacketId = "0" + hexPacketId; log.debug("Packet id: 0x{}", hexPacketId); - rb = rb - in.readableBytes(); + leftDataPacket = leftDataPacket - countReadBytes[0]; Class packetClass = state.getClientSidePacket(packetId); if (packetClass == null) { log.warn("Unknown packet: {}:0x{}", state.name(), hexPacketId); - in.skipBytes(in.readableBytes()); + in.skipBytes(leftDataPacket); } else { - netStream.setDataSize(packetSize - rb); + netStream.setDataSize(leftDataPacket); CSPacket packet = packetClass.newInstance(); try { packet.readSelf(netStream); @@ -62,7 +58,7 @@ public class PacketDecoder extends ByteToMessageDecoder { } catch (Exception e) { log.warn("Known packet: {}:{}. But throw exception. See debug log.", state.name(), packet.getClass().getSimpleName()); log.debug("Read packet", e); - in.skipBytes(in.readableBytes()); + in.skipBytes(leftDataPacket); } } } diff --git a/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/PlayerEventListener.java b/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/PlayerEventListener.java index 66d46ec..4c7e533 100644 --- a/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/PlayerEventListener.java +++ b/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/PlayerEventListener.java @@ -2,16 +2,15 @@ package mc.core.network.proto_1_12_2.netty; import com.google.common.eventbus.Subscribe; import lombok.extern.slf4j.Slf4j; -import mc.core.events.SC_ChunkLoadEvent; -import mc.core.events.SC_ChunkUnloadEvent; -import mc.core.events.SC_PlayerMoveEvent; +import mc.core.eventbus.events.SC_ChunkLoadEvent; +import mc.core.eventbus.events.SC_ChunkUnloadEvent; +import mc.core.eventbus.events.SC_PlayerMoveEvent; import mc.core.network.proto_1_12_2.TeleportManager; import mc.core.network.proto_1_12_2.packets.ChunkDataPacket; import mc.core.network.proto_1_12_2.packets.PlayerPositionAndLookPacket; import mc.core.network.proto_1_12_2.packets.UnloadChunkPacket; import mc.core.utils.CompactedCoords; import mc.core.world.chunk.Chunk; -import mc.core.world.chunk.ChunkSection; @Slf4j class PlayerEventListener { @@ -30,7 +29,7 @@ class PlayerEventListener { public void playerChunkLoadHandler(SC_ChunkLoadEvent event) { for(Integer compressXZ : event.getNeedLoadChunks()) { int[] xz = CompactedCoords.uncompressXZ(compressXZ); - Chunk chunk = event.getPlayer().getLocation().getWorld().getChunk(xz[0], xz[1]); + Chunk chunk = event.getPlayer().getWorld().getChunk(xz[0], xz[1]); ChunkDataPacket packet = new ChunkDataPacket(); packet.setX(xz[0]); 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 15cf04b..829392f 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 @@ -1,15 +1,12 @@ -/* - * DmitriyMX - * 2018-06-23 - */ package mc.core.network.proto_1_12_2.netty.handlers; import io.netty.channel.Channel; import io.netty.channel.ChannelFutureListener; -import mc.core.events.CS_PlayerMoveEvent; -import mc.core.events.EventBusGetter; +import mc.core.eventbus.events.CS_PlayerMoveEvent; +import mc.core.eventbus.EventBusGetter; import mc.core.network.proto_1_12_2.State; import mc.core.network.proto_1_12_2.TeleportManager; +import mc.core.network.proto_1_12_2.netty.KeepAliveThread; import mc.core.network.proto_1_12_2.netty.wrappers.WrapperNetChannel; import mc.core.network.proto_1_12_2.packets.*; import mc.core.player.Player; @@ -20,12 +17,10 @@ import mc.core.text.TextColor; import mc.core.text.TextStyle; import mc.core.utils.CompactedCoords; import mc.core.world.World; +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; @@ -34,12 +29,14 @@ public class LoginHandler extends AbstractStateHandler implements LoginStateHand @Autowired private PlayerManager playerManager; @Autowired + private KeepAliveThread keepAliveThread; + @Autowired private World world; @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)) @@ -47,23 +44,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())); + 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); @@ -73,21 +82,25 @@ 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(); - pkt8.setX(player.getLocation().getChunk().getX()); - pkt8.setZ(player.getLocation().getChunk().getZ()); - pkt8.setChunk(player.getLocation().getChunk()); + Chunk chunk = player.getWorld().getChunk(player.getLocation()); + pkt8.setX(chunk.getX()); + pkt8.setZ(chunk.getZ()); + pkt8.setChunk(chunk); pkt8.setInitChunk(true); channel.writeAndFlush(pkt8); + player.getLoadedChunks().add(CompactedCoords.compressXZ(0, 0)); // Player Position And Look @@ -99,10 +112,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); @@ -115,32 +129,13 @@ 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); + keepAliveThread.notifyLock(); CS_PlayerMoveEvent event = new CS_PlayerMoveEvent(player, player.getLocation()); event.setNewLocation(player.getLocation()); event.setRecalcChunk(true); - EventBusGetter.INSTANCE.post(event); + EventBusGetter.getInstance().post(event); } } } diff --git a/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/handlers/PlayHandler.java b/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/handlers/PlayHandler.java index e4162c5..d132486 100644 --- a/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/handlers/PlayHandler.java +++ b/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/handlers/PlayHandler.java @@ -8,8 +8,8 @@ import io.netty.channel.Channel; import lombok.extern.slf4j.Slf4j; import mc.core.EntityLocation; import mc.core.chat.ChatProcessor; -import mc.core.events.CS_PlayerMoveEvent; -import mc.core.events.EventBusGetter; +import mc.core.eventbus.events.CS_PlayerMoveEvent; +import mc.core.eventbus.EventBusGetter; import mc.core.network.proto_1_12_2.TeleportManager; import mc.core.network.proto_1_12_2.packets.*; import mc.core.player.Player; @@ -55,8 +55,7 @@ public class PlayHandler extends AbstractStateHandler implements PlayStateHandle @Handler public void onPositionAndLook(Channel channel, PlayerPositionAndLookPacket packet) { Player player = channel.attr(ATTR_PLAYER).get(); - player.getLocation().setXYZ(packet.getLocation()); - player.getLocation().setYawPitch(packet.getLocation()); + player.getLocation().set(packet.getLocation()); } @Handler @@ -82,10 +81,9 @@ public class PlayHandler extends AbstractStateHandler implements PlayStateHandle event.setNewLocation(new EntityLocation( packet.getX(), packet.getY(), packet.getZ(), player.getLocation().getYaw(), - player.getLocation().getPitch(), - player.getLocation().getWorld() + player.getLocation().getPitch() )); - EventBusGetter.INSTANCE.post(event); + EventBusGetter.getInstance().post(event); } @Handler 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/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/wrappers/WrapperNetChannel.java b/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/wrappers/WrapperNetChannel.java index 192edd6..fd53188 100644 --- a/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/wrappers/WrapperNetChannel.java +++ b/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/wrappers/WrapperNetChannel.java @@ -20,14 +20,8 @@ import java.util.Random; @RequiredArgsConstructor public class WrapperNetChannel implements NetChannel { - private static final Random RAND = new Random(); private final Channel channel; - @Override - public void sendKeepAlive() { - writeAndFlush(new KeepAlivePacket(RAND.nextLong())); - } - @Override public void sendTimeUpdate(long time, long age) { writeAndFlush(new TimeUpdatePacket(time, age)); diff --git a/settings.gradle b/settings.gradle index c7f27f4..f49bc91 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,6 +2,7 @@ rootProject.name = 'mc-server' include('core') // Core include('simple_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/simple_world/build.gradle b/simple_world/build.gradle index a9ac8b6..6d1bcee 100644 --- a/simple_world/build.gradle +++ b/simple_world/build.gradle @@ -1,4 +1,3 @@ -group 'mc' version '1.0-SNAPSHOT' dependencies { diff --git a/simple_world/src/main/java/mc/world/simple/SimpleWorld.java b/simple_world/src/main/java/mc/world/simple/SimpleWorld.java index 4346820..ef2981d 100644 --- a/simple_world/src/main/java/mc/world/simple/SimpleWorld.java +++ b/simple_world/src/main/java/mc/world/simple/SimpleWorld.java @@ -20,6 +20,8 @@ import java.util.List; @Slf4j public class SimpleWorld implements World { + @Getter + private final String name = "flat"; @Getter private final WorldType worldType = WorldType.FLAT; private EntityLocation spawn; @@ -38,7 +40,7 @@ public class SimpleWorld implements World { @Override public void setSpawn(double x, double y, double z, float yaw, float pitch) { - this.spawn = new EntityLocation(x, y, z, yaw, pitch, this); + this.spawn = new EntityLocation(x, y, z, yaw, pitch); } @Override diff --git a/vanilla_commands/build.gradle b/vanilla_commands/build.gradle index a9ac8b6..6d1bcee 100644 --- a/vanilla_commands/build.gradle +++ b/vanilla_commands/build.gradle @@ -1,4 +1,3 @@ -group 'mc' version '1.0-SNAPSHOT' dependencies { 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); }