diff --git a/anvil-loader/build.gradle b/anvil-loader/build.gradle index f12d7e9..790d415 100644 --- a/anvil-loader/build.gradle +++ b/anvil-loader/build.gradle @@ -4,10 +4,4 @@ version '1.0-SNAPSHOT' dependencies { /* Core */ compile_excludeCopy project(':core') - - /* Simple log */ - compile_excludeCopy (group: 'org.slf4j', name: 'slf4j-simple', version: slf4j_version) - - /* Named Binary Tags */ - compile (group: 'com.flowpowered', name: 'flow-nbt', version: '1.0.0') } \ No newline at end of file diff --git a/build.gradle b/build.gradle index 8735a6b..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,15 +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) - /* Components */ - compile (group: 'org.projectlombok', name: 'lombok', version: '1.16.16') + /* Lombok */ + annotationProcessor (group: 'org.projectlombok', name: 'lombok', version: lombok_version) + compileOnly (group: 'org.projectlombok', name: 'lombok', version: lombok_version) - /* Test */ - testCompile (group: 'junit', name: 'junit', version: '4.12') + /* 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) + 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) { @@ -63,7 +77,7 @@ task runApp(type: JavaExec) { /* Uncomment, if you used VM args */ //jvmArgs = [ - // "-DspringConfig=spring-flat.xml", + // "-DspringConfig=spring.xml", // "-Dlog4j.configurationFile=log4j2.xml" //] 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 0eec9d2..0b4e237 100644 --- a/core/src/main/java/mc/core/EntityLocation.java +++ b/core/src/main/java/mc/core/EntityLocation.java @@ -1,21 +1,29 @@ -/* - * DmitriyMX - * 2018-08-08 - */ package mc.core; -import lombok.Getter; -import lombok.Setter; -import mc.core.world.World; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; -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; - public EntityLocation(double x, double y, double z, float yaw, float pitch, World world) { - super(x, y, z, world); - setYawPitch(yaw, pitch); + 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) { @@ -23,12 +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 int getBlockY() { + return Double.valueOf(Math.floor(y)).intValue(); + } + + 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 da9cc1c..4b5affc 100644 --- a/core/src/main/java/mc/core/GameLoop.java +++ b/core/src/main/java/mc/core/GameLoop.java @@ -4,17 +4,10 @@ */ 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.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; @Slf4j @@ -45,59 +38,16 @@ public class GameLoop extends Thread { TPS_WATCHER.setTraceTPS(value); } - @Subscribe - public void playerMoveEventHandler(CS_PlayerMoveEvent event) { - log.trace("(GameLoop) playerMoveEventHandler()"); - event.getPlayer().getLocation().setXYZ(event.getNewLocation()); - - Chunk chunk = event.getNewLocation().getChunk(); // Next chunk - int ncX = chunk.getX(); - int ncZ = chunk.getZ(); - chunk = event.getPlayer().getLocation().getChunk(); // Current chunk - int ccX = chunk.getX(); - int ccZ = chunk.getZ(); - - if (event.isRecalcChunk() || (ncX != ccX && ncZ != ccZ)) { - final int viewDistance = event.getPlayer().getSettings().getViewDistance(); - int cMinX = chunk.getX() - viewDistance; - int cMaxX = chunk.getX() + viewDistance; - int cMinZ = chunk.getZ() - viewDistance; - int cMaxZ = chunk.getZ() + viewDistance; - - 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); - } - } - } - } - - if (!eventChunkLoad.getNeedLoadChunks().isEmpty()) { - EventBusGetter.INSTANCE.post(eventChunkLoad); - } - } - - // 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 319cf87..0000000 --- a/core/src/main/java/mc/core/Location.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * 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 java.lang.ref.Reference; -import java.lang.ref.WeakReference; - -public class Location implements Cloneable { - @Getter - @Setter - private double x, y, z; - private Reference refWorld; - - public Location (double x, double y, double z, World world) { - setXYZ(x, y, z); - setWorld(world); - } - - 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 World getWorld() { - if (refWorld == null) { - return null; - } else if (refWorld.get() == null) { - throw new ResourceUnloadedException("World unloaded"); - } else { - return refWorld.get(); - } - } - - public void setWorld (World world) { - this.refWorld = new WeakReference<>(world); - } - - public int getBlockX() { - return (int) x; - } - - public int getBlockY() { - return (int) y; - } - - public int getBlockZ() { - return (int) z; - } - - public Chunk getChunk() { - World world = getWorld(); - if (world == null) { - return null; - } else { - return world.getChunk((int)(x / 16), (int)(z / 16)); - } - } - - public ChunkSection getChunkSection() { - Chunk chunk = getChunk(); - if (chunk == null) { - return null; - } else { - return chunk.getChunkSection((int)(y / 16)); - } - } - - @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..a167573 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,18 +8,13 @@ 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; -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 +44,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 +57,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 +67,7 @@ public class FakePlayerManager implements PlayerManager { } @Override - public int getCountOnlinePlayers() { + public int getCountPlayers() { return 0; } @@ -89,4 +75,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/eventbus/events/SC_ChunkUnloadEvent.java b/core/src/main/java/mc/core/eventbus/events/SC_ChunkUnloadEvent.java new file mode 100644 index 0000000..b2cce0d --- /dev/null +++ b/core/src/main/java/mc/core/eventbus/events/SC_ChunkUnloadEvent.java @@ -0,0 +1,17 @@ +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; +import java.util.List; + +@RequiredArgsConstructor +public class SC_ChunkUnloadEvent extends EventBase { + @Getter + private final Player player; + @Getter + private List needUnloadChunks = new 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 8a33b41..cac3db6 100644 --- a/core/src/main/java/mc/core/utils/CompactedCoords.java +++ b/core/src/main/java/mc/core/utils/CompactedCoords.java @@ -20,4 +20,9 @@ public class CompactedCoords { (int)(short) (compactValue | 0xFFFF0000) }; } + + private static int floor_double(double value) { + int i = (int)value; + return value < (double)i ? i - 1 : i; + } } diff --git a/core/src/main/java/mc/core/world/World.java b/core/src/main/java/mc/core/world/World.java index 20bf437..37d35e0 100644 --- a/core/src/main/java/mc/core/world/World.java +++ b/core/src/main/java/mc/core/world/World.java @@ -5,14 +5,30 @@ 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(); void setSpawn(EntityLocation location); + default void setSpawn(double x, double y, double z, float yaw, float pitch) { + setSpawn(new EntityLocation(x, y, z, yaw, pitch)); + } + default void setSpawn(double x, double y, double z) { + setSpawn(x, y, z, 0f, 0f); + } 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 caceb58..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,19 +1,16 @@ 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, World world) { - return new EmbeddedBlock(blockType, x, y, z, world); + public Block create(BlockType blockType, int x, int y, int z) { + return new EmbeddedBlock(blockType, x, y, z); } /** For first-time generation */ private class EmbeddedBlock extends AbstractBlock { - EmbeddedBlock(BlockType type, int x, int y, int z, World world) { + EmbeddedBlock(BlockType type, int x, int y, int z) { super(type); - setLocation(new Location(x,y,z, world)); + 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..9ac33f0 --- /dev/null +++ b/core/src/main/java/mc/core/world/block/BlockLocation.java @@ -0,0 +1,32 @@ +package mc.core.world.block; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@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/main/java/mc/core/world/chunk/Chunk.java b/core/src/main/java/mc/core/world/chunk/Chunk.java index 9436ccf..0ee2e28 100644 --- a/core/src/main/java/mc/core/world/chunk/Chunk.java +++ b/core/src/main/java/mc/core/world/chunk/Chunk.java @@ -1,7 +1,6 @@ package mc.core.world.chunk; import mc.core.world.Biome; -import mc.core.world.World; public interface Chunk { int getX(); @@ -12,7 +11,4 @@ public interface Chunk { Biome getBiome(int localX, int localZ); void setBiome(int localX, int localZ, Biome biome); - - World getWorld(); - void setWorld(World world); } diff --git a/core/src/main/java/mc/core/world/chunk/ChunkProvider.java b/core/src/main/java/mc/core/world/chunk/ChunkProvider.java new file mode 100644 index 0000000..4726b63 --- /dev/null +++ b/core/src/main/java/mc/core/world/chunk/ChunkProvider.java @@ -0,0 +1,8 @@ +package mc.core.world.chunk; + +public interface ChunkProvider { + Chunk getChunk(int x , int z); + + void saveChunk(Chunk chunk); + void saveChunk(Chunk... chunks); +} diff --git a/core/src/main/java/mc/core/world/chunk/ChunkSection.java b/core/src/main/java/mc/core/world/chunk/ChunkSection.java index 3c10125..ae65a8b 100644 --- a/core/src/main/java/mc/core/world/chunk/ChunkSection.java +++ b/core/src/main/java/mc/core/world/chunk/ChunkSection.java @@ -5,7 +5,6 @@ package mc.core.world.chunk; import mc.core.world.Biome; -import mc.core.world.World; import mc.core.world.block.Block; /* 16x16x16 */ @@ -24,6 +23,4 @@ public interface ChunkSection { void setAddition(int x, int y, int z, int value); Biome getBiome(int localX, int localZ); - - World getWorld(); } 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..0e31fdc --- /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.*; + +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); + assertNotSame(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 344991d..0000000 --- a/core/src/test/java/mc/core/TestEntityLocation.java +++ /dev/null @@ -1,50 +0,0 @@ -package mc.core; - -import mc.core.world.World; -import mc.core.world.WorldType; -import mc.core.world.chunk.Chunk; -import org.junit.Assert; -import org.junit.Test; - -public class TestEntityLocation { - @Test - public void cloneTest() { - World dummyWorld = new World() { - @Override - public WorldType getWorldType() { - return null; - } - - @Override - public EntityLocation getSpawn() { - return null; - } - - @Override - public void setSpawn(EntityLocation location) { - - } - - @Override - public Chunk getChunk(int x, int z) { - return null; - } - - @Override - public void setChunk(int x, int z, Chunk chunk) { - - } - }; - - EntityLocation firstLocation = new EntityLocation(10, 20, 30, 40, 50, dummyWorld); - Assert.assertSame("Lost world reference before cloning", dummyWorld, firstLocation.getWorld()); - EntityLocation locationClone = firstLocation.clone(); - - Assert.assertEquals("X mismatch", firstLocation.getX(), locationClone.getX(), 0); - Assert.assertEquals("Y mismatch", firstLocation.getY(), locationClone.getY(), 0); - Assert.assertEquals("Z mismatch", firstLocation.getZ(), locationClone.getZ(), 0); - Assert.assertEquals("Pitch mismatch", firstLocation.getPitch(), locationClone.getPitch(), 0); - Assert.assertEquals("Yaw mismatch", firstLocation.getYaw(), locationClone.getYaw(), 0); - Assert.assertSame("World mismatch (accidental clone of the World object?)", firstLocation.getWorld(), locationClone.getWorld()); - } -} 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 4150c53..0000000 --- a/core/src/test/java/mc/core/utils/TestCompactedCoords.java +++ /dev/null @@ -1,45 +0,0 @@ -package mc.core.utils; - -import org.junit.Assert; -import org.junit.Test; - -import java.util.Random; - - -public class TestCompactedCoords { - @Test - public void testSimple() { - 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); - - Assert.assertEquals(x, xz[0]); - Assert.assertEquals(z, xz[1]); - } - } - } - - @Test - public void testRandom() { - 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); - - Assert.assertEquals(x, xz[0]); - Assert.assertEquals(z, xz[1]); - } - } -} 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/flat_world/src/main/java/mc/world/flat/FlatWorld.java b/flat_world/src/main/java/mc/world/flat/FlatWorld.java deleted file mode 100644 index f687e85..0000000 --- a/flat_world/src/main/java/mc/world/flat/FlatWorld.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * DmitriyMX - * 2018-04-28 - */ -package mc.world.flat; - -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import mc.core.EntityLocation; -import mc.core.world.World; -import mc.core.world.WorldType; -import mc.core.world.block.BlockType; -import mc.core.world.chunk.Chunk; -import mc.core.world.chunk.ChunkSection; - -import java.util.ArrayList; -import java.util.List; - -@Slf4j -public class FlatWorld implements World { - @Getter - private final WorldType worldType = WorldType.FLAT; - private EntityLocation spawn; - private ChunkSection chunkSection; - - @Override - public EntityLocation getSpawn() { - if (this.spawn == null) { - log.warn("Spawn is not defined! Set spawn [0, 6, 0]"); - this.spawn = new EntityLocation(0d, 6d, 0d, 0f, 0f, this); - } - - return this.spawn; - } - - @Override - public void setSpawn(EntityLocation location) { - this.spawn = location; - this.spawn.setWorld(this); - } - - @Override - public Chunk getChunk(int x, int z) { - Chunk chunk = new SimpleChunk(x, z, this); - chunk.setChunkSection(0, chunkSection); - return chunk; - } - - @Override - public void setChunk(int x, int z, Chunk chunk) { - throw new UnsupportedOperationException(); - } - - public void setLayersBlock(List listOfLayers) { - List layoutsBlock = new ArrayList<>(); - - for (String value : listOfLayers) { - String[] splitValue = value.split(";"); - - BlockType blockType; - try { - blockType = BlockType.valueOf(splitValue[1]); - } catch (IllegalArgumentException e) { - continue; - } - - for (int i = 0; i < Integer.parseInt(splitValue[0]); i++) { - layoutsBlock.add(blockType); - } - } - - this.chunkSection = new SimpleChunkSection(layoutsBlock, this); - } -} 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..7db2b42 --- /dev/null +++ b/h2_playermanager/src/main/java/mc/core/h2db/H2PlayerManager.java @@ -0,0 +1,98 @@ +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.context.ApplicationContext; +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<>()); + + @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) { + return playerList.stream() + .filter(player -> player.getName().equals(name)) + .filter(player -> !player.isOnline()) + .findFirst().orElseGet(() -> h2PlayerService.getByName(name)); + } +} 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..86e9cda --- /dev/null +++ b/h2_playermanager/src/main/java/mc/core/h2db/entity/H2PlayerEntity.java @@ -0,0 +1,102 @@ +package mc.core.h2db.entity; + +import lombok.Data; +import lombok.NoArgsConstructor; +import mc.core.EntityLocation; +import mc.core.h2db.H2Player; +import mc.core.world.World; +import org.hibernate.annotations.GenericGenerator; +import org.springframework.context.ApplicationContext; + +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(); + this.locationWorld = player.getWorld().getName(); + } + + 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(ApplicationContext context) { + H2Player player = new H2Player(); + return toPlayer(player, context); + } + + public H2Player toPlayer(H2Player player, ApplicationContext context) { + 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(context.getBean(this.locationWorld, World.class)); + + 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..b920fbf --- /dev/null +++ b/h2_playermanager/src/main/java/mc/core/h2db/service/H2PlayerServiceImpl.java @@ -0,0 +1,44 @@ +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.context.ApplicationContext; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +@Service +public class H2PlayerServiceImpl implements H2PlayerService { + @Autowired + private ApplicationContext context; + @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, context); + } + + @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(entiry -> entiry.toPlayer(context)).orElse(null); + } + + @Override + public H2Player getById(int id) { + Optional optEntity = h2PlayerEntityRepository.findById((long) id); + return optEntity.map(entiry -> entiry.toPlayer(context)).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..8dd5f80 --- /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("mockWorld") + public World mockWorld() { + World mockWorld = mock(World.class); + when(mockWorld.getName()).thenReturn("mockWorld"); + 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..464e0e4 --- /dev/null +++ b/h2_playermanager/src/test/java/mc/core/h2db/service/H2PlayerServiceTest.java @@ -0,0 +1,155 @@ +package mc.core.h2db.service; + +import mc.core.EntityLocation; +import mc.core.h2db.H2Player; +import mc.core.h2db.TestSpringConfig; +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.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; + @Autowired + private World world; + + 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(world); + + return player; + } + + private void assertPlayers(H2Player expected, H2Player actual) { + assertEquals(expected, actual); + assertEquals(expected.getName(), actual.getName()); + assertEquals(expected.getLocation(), actual.getLocation()); + assertNotNull(actual.getWorld()); + assertEquals(expected.getWorld(), actual.getWorld()); + } + + @Test + void save() { + H2Player player = buildPlayer(); + H2Player savedPlayer = h2PlayerService.save(player); + + player.setId(savedPlayer.getId()); //FIXME костыль, однако + assertPlayers(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()); + assertPlayers(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()); + assertPlayers(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/Direction.java b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/Direction.java new file mode 100644 index 0000000..8deb848 --- /dev/null +++ b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/Direction.java @@ -0,0 +1,26 @@ +package mc.core.network.proto_1_12_2; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; + +@RequiredArgsConstructor +public enum Direction { + BOTTOM(0), // -Y + TOP(1), // +Y + NORTH(2), // -Z + SOUTH(3), // +Z + WEST(4), // -X + EAST(5); // +X + + public static Direction getById(final int id) { + return Arrays.stream(Direction.values()) + .filter(direction -> direction.id == id) + .findFirst() + .orElse(null); + } + + @Getter + private final int id; +} 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/State.java b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/State.java index d963a59..a548a3d 100644 --- a/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/State.java +++ b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/State.java @@ -80,13 +80,18 @@ public enum State { .put(0x0D, PlayerPositionPacket.class) .put(0x0E, PlayerPositionAndLookPacket.class) .put(0x0F, PlayerLookPacket.class) + .put(0x13, PlayerAbilitiesPacket.class) + .put(0x14, PlayerDiggingPacket.class) + .put(0x15, EntityActionPacket.class) .put(0x1A, HeldItemChangePacket.class) .put(0x1D, AnimationPacket.class) + .put(0x1F, PlayerBlockPlacementPacket.class) .build(), ImmutableMap., Integer>builder() .put(BossBarPacket.class, 0x0C) .put(ChatMessageServerPacket.class, 0x0F) .put(PluginMessagePacket.class, 0x18) + .put(UnloadChunkPacket.class, 0x1D) .put(ChangeGameState.class, 0x1E) .put(KeepAlivePacket.class, 0x1F) .put(ChunkDataPacket.class, 0x20) 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/BossBarPacket.java b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/BossBarPacket.java index 523904d..7339c11 100644 --- a/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/BossBarPacket.java +++ b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/BossBarPacket.java @@ -4,7 +4,10 @@ */ package mc.core.network.proto_1_12_2.packets; -import lombok.*; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import lombok.ToString; import mc.core.network.NetOutputStream; import mc.core.network.SCPacket; import mc.core.network.proto_1_12_2.serializers.TextMapper; 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/EntityActionPacket.java b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/EntityActionPacket.java new file mode 100644 index 0000000..0e02a01 --- /dev/null +++ b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/EntityActionPacket.java @@ -0,0 +1,45 @@ +package mc.core.network.proto_1_12_2.packets; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import mc.core.network.CSPacket; +import mc.core.network.NetInputStream; + +import java.util.Arrays; + +@Getter +public class EntityActionPacket implements CSPacket { + @RequiredArgsConstructor + public enum Action { + START_SNEAKING(0), + STOP_SNEAKING(1), + LEAVE_BED(2), // Leave bed is only sent when the “Leave Bed” button is clicked on the sleep GUI, not when waking up due today time. + START_SPRINTING(3), + STOP_SPRINTING(4), + START_JUMP_WITH_HORSE(5), + STOP_JUMP_WITH_HORSE(6), + OPEN_HORSE_INVENTORY(7), // Open horse inventory is only sent when pressing the inventory key (default: E) while on a horse — all other methods of opening a horse's inventory (involving right-clicking or shift-right-clicking it) do not use this packet. + START_FLYING_WITH_ELYTRA(8); + + public static Action getById(final int id) { + return Arrays.stream(Action.values()) + .filter(action -> action.id == id) + .findFirst() + .orElse(null); + } + + @Getter + private final int id; + } + + private int entityId; + private Action action; + private int jumpBoost; // Only used by the “start jump with horse” action, in which case it ranges from 0 to 100. In all other cases it is 0. + + @Override + public void readSelf(NetInputStream netStream) { + entityId = netStream.readVarInt(); + action = Action.getById(netStream.readVarInt()); + jumpBoost = netStream.readVarInt(); + } +} 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 d0397c5..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 @@ -4,33 +4,55 @@ */ package mc.core.network.proto_1_12_2.packets; +import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; +import mc.core.network.CSPacket; +import mc.core.network.NetInputStream; import mc.core.network.NetOutputStream; import mc.core.network.SCPacket; @NoArgsConstructor +@Getter @Setter @ToString -public class PlayerAbilitiesPacket implements SCPacket { +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; private boolean instantDestroyBlocks = false; private float flyingSpeed = 0.05f; private float fieldOfView = flyingSpeed; + private float walkingSpeed; @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); netStream.writeFloat(fieldOfView); } + + @Override + public void readSelf(NetInputStream netStream) { + byte flag = netStream.readByte(); + 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 new file mode 100644 index 0000000..b9c2a2e --- /dev/null +++ b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/PlayerBlockPlacementPacket.java @@ -0,0 +1,30 @@ +package mc.core.network.proto_1_12_2.packets; + +import lombok.Getter; +import lombok.ToString; +import mc.core.network.CSPacket; +import mc.core.network.NetInputStream; +import mc.core.network.proto_1_12_2.Direction; +import mc.core.network.proto_1_12_2.serializers.BlockLocationSerializer; +import mc.core.world.block.BlockLocation; + +@Getter +@ToString +public class PlayerBlockPlacementPacket implements CSPacket { + private BlockLocation location; + private Direction face; + /** true - main hand; false - off hand */ + private boolean hand; + private float cursorX, cursorY, cursorZ; + + @Override + public void readSelf(NetInputStream netStream) { + long compactedCoords = netStream.readLong(); + location = BlockLocationSerializer.fromLong(compactedCoords); + face = Direction.getById(netStream.readVarInt()); + hand = (netStream.readVarInt() == 1); + cursorX = netStream.readFloat(); + cursorY = netStream.readFloat(); + cursorZ = 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 new file mode 100644 index 0000000..341ca67 --- /dev/null +++ b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/PlayerDiggingPacket.java @@ -0,0 +1,55 @@ +package mc.core.network.proto_1_12_2.packets; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.ToString; +import mc.core.network.CSPacket; +import mc.core.network.NetInputStream; +import mc.core.network.proto_1_12_2.Direction; +import mc.core.network.proto_1_12_2.serializers.BlockLocationSerializer; +import mc.core.world.block.BlockLocation; + +import java.util.Arrays; + +@Getter +@ToString +public class PlayerDiggingPacket implements CSPacket { + @RequiredArgsConstructor + public enum Status { + STARTED_DIGGING(0), + CANCELLED_DIGGING(1), + FINISHED_DIGGING(2), + DROP_ITEM_STACK(3), + DROP_ITEM(4), + /* Indicates that the currently held item should have its + * state updated such as eating food, pulling back bows, + * using buckets, etc. Location is always set to 0/0/0, + * Face is always set to -Y. + */ + SHOOT_ARROW(5), + FINISH_EATING(5), + SWAP_ITEM_IN_HAND(6); + + public static Status getById(final int id) { + return Arrays.stream(Status.values()) + .filter(status -> status.id == id) + .findFirst() + .orElse(null); + } + + @Getter + private final int id; + } + + private Status status; + private BlockLocation location; + private Direction face; + + @Override + public void readSelf(NetInputStream netStream) { + status = Status.getById(netStream.readVarInt()); + long compactCoord = netStream.readLong(); + 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 ffbfe50..4e1082e 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,15 +4,15 @@ */ package mc.core.network.proto_1_12_2.packets; -import mc.core.Location; import mc.core.network.CSPacket; import mc.core.network.NetInputStream; +import mc.core.world.block.BlockLocation; 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, null); + 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/packets/UnloadChunkPacket.java b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/UnloadChunkPacket.java new file mode 100644 index 0000000..5bcfe15 --- /dev/null +++ b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/UnloadChunkPacket.java @@ -0,0 +1,16 @@ +package mc.core.network.proto_1_12_2.packets; + +import lombok.Setter; +import mc.core.network.NetOutputStream; +import mc.core.network.SCPacket; + +public class UnloadChunkPacket implements SCPacket { + @Setter + private int x, z; + + @Override + public void writeSelf(NetOutputStream netStream) { + netStream.writeInt(x); + netStream.writeInt(z); + } +} 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/ChunkdataPacketTest.java b/proto_1.12.2/src/test/java/mc/core/network/proto_1_12_2/packets/ChunkdataPacketTest.java new file mode 100644 index 0000000..5060676 --- /dev/null +++ b/proto_1.12.2/src/test/java/mc/core/network/proto_1_12_2/packets/ChunkdataPacketTest.java @@ -0,0 +1,94 @@ +package mc.core.network.proto_1_12_2.packets; + +import com.google.common.io.ByteStreams; +import mc.core.network.proto_1_12_2.ByteArrayOutputNetStream; +import mc.core.world.Biome; +import mc.core.world.World; +import mc.core.world.WorldType; +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.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.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; + +class ChunkdataPacketTest { + private static byte[] expectedPacketData; + private World world; + + @BeforeAll + static void beforeClassTest() throws IOException { + InputStream inputStream = ChunkdataPacketTest.class.getResourceAsStream("ChunkDataPacket.bin"); + expectedPacketData = ByteStreams.toByteArray(inputStream); + } + + @BeforeEach + void prepareWorld() { + final ChunkSection chunkSection = mock(ChunkSection.class); + when(chunkSection.getSkyLight(anyInt(), anyInt(), anyInt())).thenAnswer(invocation -> { + int y = (int)invocation.getArguments()[1]; + + if (y <= 3) return 0; + else return 15; + }); + when(chunkSection.getBiome(anyInt(), anyInt())).thenReturn(Biome.PLAINS); + when(chunkSection.getBlock(anyInt(), anyInt(), anyInt())).thenAnswer(invocation -> { + Object[] args = invocation.getArguments(); + int x = (int) args[0]; + int y = (int) args[1]; + int z = (int) args[2]; + + BlockFactory blockFactory = new BlockFactory(); + + if (y == 0) return blockFactory.create(BlockType.BEDROCK, x, y, z); + else if (y >= 1 && y <= 2) return blockFactory.create(BlockType.DIRT, x, y, z); + else if (y == 3) return blockFactory.create(BlockType.GRASS, x, y, z); + else return blockFactory.create(BlockType.AIR, x, y, z); + }); + + world = mock(World.class); + when(world.getWorldType()).thenReturn(WorldType.FLAT); + 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]); + when(chunk.getBiome(anyInt(), anyInt())).thenReturn(Biome.PLAINS); + when(chunk.getChunkSection(anyInt())).thenAnswer(invocation1 -> { + int height = (int)invocation1.getArguments()[0]; + + if (height < 1) return chunkSection; + else return null; + }); + + return chunk; + }); + } + + @Test + void writePacket() { + ChunkDataPacket packet = new ChunkDataPacket(); + packet.setX(0); + packet.setZ(0); + packet.setChunk(world.getChunk(0, 0)); + packet.setInitChunk(true); + + ByteArrayOutputNetStream netStream = new ByteArrayOutputNetStream(); + packet.writeSelf(netStream); + byte[] actualPacketData = netStream.toByteArray(); + + assertEquals(expectedPacketData.length, actualPacketData.length); + assertArrayEquals(expectedPacketData, actualPacketData); + } +} diff --git a/proto_1.12.2/src/test/java/mc/core/network/proto_1_12_2/packets/DummyWorld.java b/proto_1.12.2/src/test/java/mc/core/network/proto_1_12_2/packets/DummyWorld.java deleted file mode 100644 index 52e8a88..0000000 --- a/proto_1.12.2/src/test/java/mc/core/network/proto_1_12_2/packets/DummyWorld.java +++ /dev/null @@ -1,138 +0,0 @@ -package mc.core.network.proto_1_12_2.packets; - -import mc.core.EntityLocation; -import mc.core.world.Biome; -import mc.core.world.World; -import mc.core.world.WorldType; -import mc.core.world.block.Block; -import mc.core.world.block.BlockFactory; -import mc.core.world.block.BlockType; -import mc.core.world.chunk.Chunk; -import mc.core.world.chunk.ChunkSection; - -public class DummyWorld implements World { - private class DummyChunkSection implements ChunkSection { - @Override - public int getSkyLight(int x, int y, int z) { - if (y <= 3) return 0; - else return 15; - } - - @Override - public void setSkyLight(int x, int y, int z, int lightLevel) { - } - - @Override - public int getAddition(int x, int y, int z) { - return 0; - } - - @Override - public void setAddition(int x, int y, int z, int value) { - } - - @Override - public Biome getBiome(int localX, int localZ) { - return Biome.PLAINS; - } - - @Override - public int getX() { - return 0; - } - - @Override - public int getY() { - return 0; - } - - @Override - public int getZ() { - return 0; - } - - @Override - public void setBlock(Block block) { - } - - @Override - public Block getBlock(int x, int y, int z) { - BlockFactory blockFactory = new BlockFactory(); - - if (y == 0) return blockFactory.create(BlockType.BEDROCK, x, y, z, getWorld()); - else if (y >= 1 && y <= 2) return blockFactory.create(BlockType.DIRT, x, y, z, getWorld()); - else if (y == 3) return blockFactory.create(BlockType.GRASS, x, y, z, getWorld()); - else return blockFactory.create(BlockType.AIR, x, y, z, getWorld()); - } - - @Override - public World getWorld() { - return DummyWorld.this; - } - } - - private class DummyChunk implements Chunk { - @Override - public World getWorld() { - return DummyWorld.this; - } - - @Override - public void setWorld(World world) { - } - - @Override - public ChunkSection getChunkSection(int height) { - if (height < 1) return new DummyChunkSection(); - else return null; - } - - @Override - public void setChunkSection(int height, ChunkSection chunkSection) { - } - - @Override - public Biome getBiome(int localX, int localZ) { - return Biome.PLAINS; - } - - @Override - public void setBiome(int localX, int localZ, Biome biome) { - } - - @Override - public int getX() { - return 0; - } - - @Override - public int getZ() { - return 0; - } - } - - private final Chunk chunk = new DummyChunk(); - - @Override - public WorldType getWorldType() { - return WorldType.FLAT; - } - - @Override - public EntityLocation getSpawn() { - return null; - } - - @Override - public void setSpawn(EntityLocation location) { - } - - @Override - public Chunk getChunk(int x, int z) { - return chunk; - } - - @Override - public void setChunk(int x, int z, Chunk chunk) { - } -} 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/packets/TestChunkdataPacket.java b/proto_1.12.2/src/test/java/mc/core/network/proto_1_12_2/packets/TestChunkdataPacket.java deleted file mode 100644 index 05f8528..0000000 --- a/proto_1.12.2/src/test/java/mc/core/network/proto_1_12_2/packets/TestChunkdataPacket.java +++ /dev/null @@ -1,40 +0,0 @@ -package mc.core.network.proto_1_12_2.packets; - -import com.google.common.io.ByteStreams; -import mc.core.network.proto_1_12_2.ByteArrayOutputNetStream; -import mc.core.world.World; -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Test; - -import java.io.*; - -public class TestChunkdataPacket { - private static byte[] expectedPacketData; - - @BeforeClass - public static void beforeClassTest() throws IOException { - InputStream inputStream = TestChunkdataPacket.class.getResourceAsStream("ChunkDataPacket.bin"); - expectedPacketData = ByteStreams.toByteArray(inputStream); - } - - private World createDummyWorld() { - return new DummyWorld(); - } - - @Test - public void test() { - ChunkDataPacket packet = new ChunkDataPacket(); - packet.setX(0); - packet.setZ(0); - packet.setChunk(createDummyWorld().getChunk(0, 0)); - packet.setInitChunk(true); - - ByteArrayOutputNetStream netStream = new ByteArrayOutputNetStream(); - packet.writeSelf(netStream); - byte[] actualPacketData = netStream.toByteArray(); - - Assert.assertEquals(expectedPacketData.length, actualPacketData.length); - Assert.assertArrayEquals(expectedPacketData, actualPacketData); - } -} 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..e747d82 --- /dev/null +++ b/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/KeepAliveThread.java @@ -0,0 +1,48 @@ +package mc.core.network.proto_1_12_2.netty; + +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 dc1090b..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,31 +31,35 @@ 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(packetSize - rb); + in.skipBytes(leftDataPacket); } else { - netStream.setDataSize(packetSize - rb); + netStream.setDataSize(leftDataPacket); CSPacket packet = packetClass.newInstance(); - packet.readSelf(netStream); - log.debug("Known packet: {}:{}", state.name(), packet.toString()); - out.add(packet); + try { + packet.readSelf(netStream); + log.debug("Known packet: {}:{}", state.name(), packet.toString()); + out.add(packet); + } 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(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 63415cd..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,14 +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_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 { @@ -26,11 +27,9 @@ class PlayerEventListener { @Subscribe public void playerChunkLoadHandler(SC_ChunkLoadEvent event) { - log.debug("(SC) playerChunkLoadHandler()"); - 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]); @@ -38,9 +37,20 @@ class PlayerEventListener { packet.setInitChunk(true); packet.setChunk(chunk); - event.getPlayer().getChannel().write(packet); + event.getPlayer().getChannel().writeAndFlush(packet); } + } - event.getPlayer().getChannel().flush(); + @Subscribe + public void playerChunkUnloadHandler(SC_ChunkUnloadEvent event) { + for(Integer compressXZ : event.getNeedUnloadChunks()) { + int[] xz = CompactedCoords.uncompressXZ(compressXZ); + + UnloadChunkPacket packet = new UnloadChunkPacket(); + packet.setX(xz[0]); + packet.setZ(xz[1]); + + event.getPlayer().getChannel().writeAndFlush(packet); + } } } 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..0f8332c 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.EventBusGetter; +import mc.core.eventbus.events.CS_PlayerMoveEvent; 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,22 +82,14 @@ 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 - ChunkDataPacket pkt8 = new ChunkDataPacket(); - pkt8.setX(player.getLocation().getChunk().getX()); - pkt8.setZ(player.getLocation().getChunk().getZ()); - pkt8.setChunk(player.getLocation().getChunk()); - pkt8.setInitChunk(true); - channel.writeAndFlush(pkt8); - player.getLoadedChunks().add(CompactedCoords.compressXZ(0, 0)); + channel.flush(); // Player Position And Look PlayerPositionAndLookPacket pkt4 = new PlayerPositionAndLookPacket(); @@ -99,10 +100,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 +117,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..83b1a7e 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.EventBusGetter; +import mc.core.eventbus.events.CS_PlayerMoveEvent; 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..3d03a82 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 @@ -10,24 +10,15 @@ import mc.core.chat.MessageType; import mc.core.network.NetChannel; import mc.core.network.SCPacket; import mc.core.network.proto_1_12_2.packets.ChatMessageServerPacket; -import mc.core.network.proto_1_12_2.packets.KeepAlivePacket; import mc.core.network.proto_1_12_2.packets.TimeUpdatePacket; import mc.core.network.proto_1_12_2.packets.TitlePacket; import mc.core.text.Text; import mc.core.text.Title; -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 4b7872c..6e2132d 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,7 +1,8 @@ rootProject.name = 'mc-server' include('core') // Core -include('flat_world') +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/flat_world/README.MD b/simple_world/README.MD similarity index 86% rename from flat_world/README.MD rename to simple_world/README.MD index ca1899e..02871ed 100644 --- a/flat_world/README.MD +++ b/simple_world/README.MD @@ -1,11 +1,11 @@ -# Flat world +# Simple world -Плоский мир +Простая реализация мира ## Spring bean ```xml - + @@ -31,7 +31,7 @@ `spawn` - точка спавна. При указании точки спавна, указывать шестой параметр `World` не имеет смысла, -т.к. `FlatWorld` всё равно перезапишет этот параметр. +т.к. `SimpleWorld` всё равно перезапишет этот параметр. `layersBlock` - слои блоков. diff --git a/flat_world/build.gradle b/simple_world/build.gradle similarity index 89% rename from flat_world/build.gradle rename to simple_world/build.gradle index a9ac8b6..6d1bcee 100644 --- a/flat_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/FlatChunkProvider.java b/simple_world/src/main/java/mc/world/simple/FlatChunkProvider.java new file mode 100644 index 0000000..45a3cd0 --- /dev/null +++ b/simple_world/src/main/java/mc/world/simple/FlatChunkProvider.java @@ -0,0 +1,55 @@ +package mc.world.simple; + +import mc.core.world.block.BlockType; +import mc.core.world.chunk.Chunk; +import mc.core.world.chunk.ChunkProvider; +import mc.core.world.chunk.ChunkSection; + +import java.util.ArrayList; +import java.util.List; + +public class FlatChunkProvider implements ChunkProvider { + private ChunkSection chunkSection; + + public void setLayersBlockAsString(List listOfLayers) { + List layoutsBlock = new ArrayList<>(); + + for (String value : listOfLayers) { + String[] splitValue = value.split(";"); + + BlockType blockType; + try { + blockType = BlockType.valueOf(splitValue[1]); + } catch (IllegalArgumentException e) { + continue; + } + + for (int i = 0; i < Integer.parseInt(splitValue[0]); i++) { + layoutsBlock.add(blockType); + } + } + + setLayersBlock(layoutsBlock); + } + + public void setLayersBlock(List layoutsBlock) { + this.chunkSection = new SimpleChunkSection(layoutsBlock); + } + + @Override + public Chunk getChunk(int x, int z) { + Chunk chunk = new SimpleChunk(x, z); + chunk.setChunkSection(0, chunkSection); + return chunk; + } + + @Override + public void saveChunk(Chunk chunk) { + //FIXME nope... + } + + @Override + public void saveChunk(Chunk... chunks) { + //FIXME nope... + } +} diff --git a/flat_world/src/main/java/mc/world/flat/SimpleChunk.java b/simple_world/src/main/java/mc/world/simple/SimpleChunk.java similarity index 52% rename from flat_world/src/main/java/mc/world/flat/SimpleChunk.java rename to simple_world/src/main/java/mc/world/simple/SimpleChunk.java index 13d81f6..aadf810 100644 --- a/flat_world/src/main/java/mc/world/flat/SimpleChunk.java +++ b/simple_world/src/main/java/mc/world/simple/SimpleChunk.java @@ -1,30 +1,20 @@ -package mc.world.flat; +package mc.world.simple; import lombok.Getter; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import mc.core.exception.ResourceUnloadedException; import mc.core.world.Biome; -import mc.core.world.World; import mc.core.world.chunk.Chunk; import mc.core.world.chunk.ChunkSection; -import java.lang.ref.Reference; -import java.lang.ref.WeakReference; - @Slf4j +@RequiredArgsConstructor public class SimpleChunk implements Chunk { @Getter - private int x, z; - private Reference refWorld; + private final int x, z; private ChunkSection chunkSection; private final Biome biome = Biome.PLAINS; - public SimpleChunk(int x, int z, World world) { - this.x = x; - this.z = z; - setWorld(world); - } - @Override public ChunkSection getChunkSection(int height) { return chunkSection; @@ -44,18 +34,4 @@ public class SimpleChunk implements Chunk { public void setBiome(int localX, int localZ, Biome biome) { // ignore } - - @Override - public World getWorld() { - if (refWorld.get() == null) { - throw new ResourceUnloadedException("World unloaded"); - } else { - return refWorld.get(); - } - } - - @Override - public void setWorld(World world) { - this.refWorld = new WeakReference<>(world); - } } diff --git a/flat_world/src/main/java/mc/world/flat/SimpleChunkSection.java b/simple_world/src/main/java/mc/world/simple/SimpleChunkSection.java similarity index 64% rename from flat_world/src/main/java/mc/world/flat/SimpleChunkSection.java rename to simple_world/src/main/java/mc/world/simple/SimpleChunkSection.java index 7b3265e..4674ac7 100644 --- a/flat_world/src/main/java/mc/world/flat/SimpleChunkSection.java +++ b/simple_world/src/main/java/mc/world/simple/SimpleChunkSection.java @@ -1,29 +1,19 @@ -/* - * DmitriyMX - * 2018-04-28 - */ -package mc.world.flat; +package mc.world.simple; -import mc.core.exception.ResourceUnloadedException; import mc.core.world.Biome; -import mc.core.world.World; import mc.core.world.block.Block; import mc.core.world.block.BlockFactory; import mc.core.world.block.BlockType; import mc.core.world.chunk.ChunkSection; -import java.lang.ref.Reference; -import java.lang.ref.WeakReference; import java.util.List; public class SimpleChunkSection implements ChunkSection { private final BlockFactory blockFactory = new BlockFactory(); private final List layersBlock; - private Reference refWorld; - public SimpleChunkSection(List layersBlock, World world) { + public SimpleChunkSection(List layersBlock) { this.layersBlock = layersBlock; - this.refWorld = new WeakReference<>(world); } @Override @@ -71,23 +61,20 @@ public class SimpleChunkSection implements ChunkSection { @Override public Block getBlock(int x, int y, int z) { + if (x < 0) x = 0; + else if (x > 15) x = 15; + if (y < 0) y = 0; + else if (y > 15) y = 15; + if (z < 0) z = 0; + else if (z > 15) z = 15; + if (y >= layersBlock.size()) { - return blockFactory.create(BlockType.AIR, x, y, z, getWorld()); + return blockFactory.create(BlockType.AIR, x, y, z); } BlockType blockType = layersBlock.get(y); - if (blockType == null) return blockFactory.create(BlockType.AIR, x, y, z, getWorld()); + if (blockType == null) return blockFactory.create(BlockType.AIR, x, y, z); - return blockFactory.create(blockType, x, y, z, getWorld()); - } - - @Override - public World getWorld() { - World world = refWorld.get(); - if (world == null) { - throw new ResourceUnloadedException("World unloaded"); - } - - return world; + return blockFactory.create(blockType, x, y, z); } } diff --git a/simple_world/src/main/java/mc/world/simple/SimpleWorld.java b/simple_world/src/main/java/mc/world/simple/SimpleWorld.java new file mode 100644 index 0000000..b4ac9e4 --- /dev/null +++ b/simple_world/src/main/java/mc/world/simple/SimpleWorld.java @@ -0,0 +1,56 @@ +package mc.world.simple; + +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import mc.core.EntityLocation; +import mc.core.world.World; +import mc.core.world.WorldType; +import mc.core.world.chunk.Chunk; +import mc.core.world.chunk.ChunkProvider; +import org.springframework.beans.factory.BeanNameAware; +import org.springframework.stereotype.Component; + +import javax.annotation.Nonnull; + +@Slf4j +@Component +public class SimpleWorld implements World, BeanNameAware { + @Getter + private String name; + @Getter + private final WorldType worldType = WorldType.FLAT; + private EntityLocation spawn; + @Setter + private ChunkProvider chunkProvider; + + @Override + public EntityLocation getSpawn() { + if (this.spawn == null) { + log.warn("Spawn is not defined! Set spawn [0, 6, 0]"); + setSpawn(0d, 6d, 0d); + } + + return this.spawn; + } + + @Override + public void setSpawn(EntityLocation location) { + this.spawn = location; + } + + @Override + public Chunk getChunk(int x, int z) { + return chunkProvider.getChunk(x, z); + } + + @Override + public void setChunk(int x, int z, Chunk chunk) { + throw new UnsupportedOperationException(); + } + + @Override + public void setBeanName(@Nonnull String name) { + this.name = name; + } +} diff --git a/simple_world/src/test/java/mc/world/simple/SimpleChunkSectionTest.java b/simple_world/src/test/java/mc/world/simple/SimpleChunkSectionTest.java new file mode 100644 index 0000000..4804190 --- /dev/null +++ b/simple_world/src/test/java/mc/world/simple/SimpleChunkSectionTest.java @@ -0,0 +1,44 @@ +package mc.world.simple; + +import com.google.common.collect.Lists; +import mc.core.world.block.Block; +import mc.core.world.block.BlockType; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class SimpleChunkSectionTest { + private SimpleChunkSection chunkSection; + private List layersBlock; + + @BeforeEach + void before() { + layersBlock = Lists.newArrayList( + BlockType.BEDROCK, + BlockType.DIRT, + BlockType.DIRT, + BlockType.GRASS + ); + + chunkSection = new SimpleChunkSection(layersBlock); + } + + @Test + void getBlock() { + for (int y = 15; y >= 0; y--) { + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + Block block = chunkSection.getBlock(x, y, z); + if (y > layersBlock.size()-1) { + assertEquals(block.getBlockType(), BlockType.AIR); + } else { + assertEquals(block.getBlockType(), layersBlock.get(y)); + } + } + } + } + } +} \ No newline at end of file diff --git a/simple_world/src/test/java/mc/world/simple/SimpleWorldTest.java b/simple_world/src/test/java/mc/world/simple/SimpleWorldTest.java new file mode 100644 index 0000000..110a830 --- /dev/null +++ b/simple_world/src/test/java/mc/world/simple/SimpleWorldTest.java @@ -0,0 +1,37 @@ +package mc.world.simple; + +import mc.core.EntityLocation; +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 static org.junit.jupiter.api.Assertions.*; + +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = {TestSpringConfig.class}) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +class SimpleWorldTest { + @Autowired + private SimpleWorld world; + + @Test + void spawn() { + final EntityLocation location = new EntityLocation(1d, 2d, 3d,4f, 5f); + + world.setSpawn(location); + assertEquals(location, world.getSpawn()); + assertSame(location, world.getSpawn()); + + world.setSpawn(1d, 2d, 3d, 4f, 5f); + assertEquals(location, world.getSpawn()); + assertNotSame(location, world.getSpawn()); + + location.setYawPitch(0, 0); + world.setSpawn(1d, 2d, 3d); + assertEquals(location, world.getSpawn()); + assertNotSame(location, world.getSpawn()); + } +} \ No newline at end of file diff --git a/simple_world/src/test/java/mc/world/simple/TestSpringConfig.java b/simple_world/src/test/java/mc/world/simple/TestSpringConfig.java new file mode 100644 index 0000000..865fdd9 --- /dev/null +++ b/simple_world/src/test/java/mc/world/simple/TestSpringConfig.java @@ -0,0 +1,38 @@ +package mc.world.simple; + +import com.google.common.collect.Lists; +import mc.core.world.block.BlockType; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +import java.util.List; + +@Configuration +@ComponentScan("mc.world.simple") +public class TestSpringConfig { + @Bean + public List layersBlock() { + return Lists.newArrayList( + BlockType.BEDROCK, + BlockType.DIRT, + BlockType.DIRT, + BlockType.GRASS + ); + } + + @Bean + public SimpleChunkSection chunkSection(List layersBlock) { + return new SimpleChunkSection(layersBlock); + } + + @Bean + public SimpleWorld simpleWorld(List layersBlock) { + FlatChunkProvider chunkProvider = new FlatChunkProvider(); + chunkProvider.setLayersBlock(layersBlock); + + SimpleWorld world = new SimpleWorld(); + world.setChunkProvider(chunkProvider); + return world; + } +} 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); }