Archived
0

Merge branch 'proto_1.12.2' into world

This commit is contained in:
2018-10-08 12:23:52 +03:00
82 changed files with 1854 additions and 802 deletions

View File

@@ -9,13 +9,20 @@ allprojects {
repositories { repositories {
mavenCentral() mavenCentral()
maven {
url 'https://oss.sonatype.org/content/groups/public/'
}
} }
} }
subprojects { subprojects {
group 'mc'
ext { ext {
slf4j_version = '1.7.21' slf4j_version = '1.7.25'
spring_version = '4.2.5.RELEASE' spring_version = '5.1.0.RELEASE'
lombok_version = '1.18.2'
junit_version = '5.3.1'
} }
configurations { configurations {
@@ -29,20 +36,22 @@ subprojects {
compile (group: 'org.slf4j', name: 'jcl-over-slf4j', version: slf4j_version) compile (group: 'org.slf4j', name: 'jcl-over-slf4j', version: slf4j_version)
/* Spring */ /* Spring */
compile (group: 'org.springframework', name: 'spring-context', version: spring_version) { compile (group: 'org.springframework', name: 'spring-context', version: spring_version)
exclude group: 'commons-logging'
}
/* Lombok */ /* Lombok */
annotationProcessor (group: 'org.projectlombok', name: 'lombok', version: '1.16.16') annotationProcessor (group: 'org.projectlombok', name: 'lombok', version: lombok_version)
compileOnly (group: 'org.projectlombok', name: 'lombok', version: '1.16.16') compileOnly (group: 'org.projectlombok', name: 'lombok', version: lombok_version)
/* JUnit */ /* Testing */
testCompile (group: 'junit', name: 'junit', version: '4.12') testImplementation (group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: junit_version)
/* Simple log */ 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.slf4j', name: 'slf4j-simple', version: slf4j_version)
/* Mockito */ testCompile (group: 'org.mockito', name: 'mockito-core', version: '1.10.19')
testCompile (group: 'org.mockito', name: 'mockito-core', version: '1.9.5') testCompile (group: 'org.springframework', name: 'spring-test', version: spring_version)
}
test {
useJUnitPlatform()
} }
task copyDep(type: Copy) { task copyDep(type: Copy) {

View File

@@ -18,20 +18,6 @@ Bean:
</bean> </bean>
``` ```
### InMemoryPlayerManager
Implements: `mc.core.PlayerManager`
Bean:
```xml
<bean id="playerManager" class="mc.core.embedded.InMemoryPlayerManager">
<property name="keepAliveInterval" value="10"/>
</bean>
```
`keepAliveInterval` - как часто (в ms) отправлять клиентам пакет `KeepAlive`
### IdleTime ### IdleTime
Implements: `mc.core.time.TimeProcessor` Implements: `mc.core.time.TimeProcessor`

View File

@@ -1,4 +1,3 @@
group 'mc'
version '1.0-SNAPSHOT' version '1.0-SNAPSHOT'
apply plugin: 'maven' apply plugin: 'maven'
@@ -9,7 +8,7 @@ mainClassName = "mc.core.Main"
dependencies { dependencies {
/* Components */ /* Components */
compile (group: 'commons-io', name: 'commons-io', version: '2.6') 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 */ /* 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')
} }

View File

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

View File

@@ -1,29 +1,29 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 2018-08-08
*/
package mc.core; package mc.core;
import lombok.Getter; import lombok.AllArgsConstructor;
import lombok.Setter; import lombok.Data;
import mc.core.exception.ResourceUnloadedException; import lombok.NoArgsConstructor;
import mc.core.world.World;
import mc.core.world.chunk.Chunk;
import mc.core.world.chunk.ChunkSection;
import java.lang.ref.Reference; @NoArgsConstructor
import java.lang.ref.WeakReference; @AllArgsConstructor
@Data
public class EntityLocation extends Location implements Cloneable { public class EntityLocation implements Cloneable {
@Getter private double x, y, z;
@Setter
private float yaw, pitch; private float yaw, pitch;
private Reference<World> refWorld;
public EntityLocation(double x, double y, double z, float yaw, float pitch, World world) { public static EntityLocation ZERO() {
super(x, y, z); return new EntityLocation(0d,0d,0d,0f,0f);
setYawPitch(yaw, pitch); }
setWorld(world);
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) { public void setYawPitch(float yaw, float pitch) {
@@ -31,44 +31,25 @@ public class EntityLocation extends Location implements Cloneable {
this.pitch = pitch; this.pitch = pitch;
} }
public void setYawPitch(EntityLocation entityLocation) { public int getBlockX() {
setYawPitch(entityLocation.yaw, entityLocation.pitch); return Double.valueOf(Math.floor(x)).intValue();
} }
public World getWorld() { public int getBlockY() {
if (refWorld == null) { return Double.valueOf(Math.floor(y)).intValue();
return null;
} else if (refWorld.get() == null) {
throw new ResourceUnloadedException("World unloaded");
} else {
return refWorld.get();
}
} }
public void setWorld (World world) { public int getBlockZ() {
this.refWorld = new WeakReference<>(world); return Double.valueOf(Math.floor(z)).intValue();
}
public Chunk getChunk() {
World world = getWorld();
if (world == null) {
return null;
} else {
return world.getChunk(getBlockX() >> 4, getBlockZ() >> 4);
}
}
public ChunkSection getChunkSection() {
Chunk chunk = getChunk();
if (chunk == null) {
return null;
} else {
return chunk.getChunkSection(getBlockY() >> 4);
}
} }
@Override @Override
public EntityLocation clone() { public EntityLocation clone() {
try {
return (EntityLocation) super.clone(); return (EntityLocation) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
} }
} }

View File

@@ -4,22 +4,12 @@
*/ */
package mc.core; package mc.core;
import com.google.common.eventbus.Subscribe;
import lombok.Setter; import lombok.Setter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import mc.core.events.CS_PlayerMoveEvent;
import mc.core.events.EventBusGetter;
import mc.core.events.SC_ChunkLoadEvent;
import mc.core.events.SC_ChunkUnloadEvent;
import mc.core.player.PlayerManager; import mc.core.player.PlayerManager;
import mc.core.time.TimeProcessor; import mc.core.time.TimeProcessor;
import mc.core.utils.CompactedCoords;
import mc.core.world.World;
import mc.core.world.chunk.Chunk;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import java.util.Iterator;
@Slf4j @Slf4j
public class GameLoop extends Thread { public class GameLoop extends Thread {
private final TpsWatcher TPS_WATCHER = TpsWatcher.getInstance(); private final TpsWatcher TPS_WATCHER = TpsWatcher.getInstance();
@@ -48,77 +38,16 @@ public class GameLoop extends Thread {
TPS_WATCHER.setTraceTPS(value); TPS_WATCHER.setTraceTPS(value);
} }
@Subscribe
public void playerMoveEventHandler(CS_PlayerMoveEvent event) {
log.trace("(GameLoop) playerMoveEventHandler()");
Chunk chunk;
chunk = event.getOldLocation().getChunk(); // Old chunk
int ccX = chunk.getX();
int ccZ = chunk.getZ();
chunk = event.getNewLocation().getChunk(); // Next chunk
int ncX = chunk.getX();
int ncZ = chunk.getZ();
if (event.isRecalcChunk() || (ncX != ccX || ncZ != ccZ)) {
final int viewDistance = event.getPlayer().getSettings().getViewDistance() + 1;
int cMinX = chunk.getX() - viewDistance;
int cMaxX = chunk.getX() + viewDistance;
int cMinZ = chunk.getZ() - viewDistance;
int cMaxZ = chunk.getZ() + viewDistance;
SC_ChunkUnloadEvent eventChunkUnload = new SC_ChunkUnloadEvent(event.getPlayer());
Iterator<Integer> itr = event.getPlayer().getLoadedChunks().iterator();
while(itr.hasNext()) {
int compressXZ = itr.next();
int[] xz = CompactedCoords.uncompressXZ(compressXZ);
if (xz[0] > cMaxX || xz[0] < cMinX || xz[1] > cMaxZ || xz[1] < cMinZ) {
eventChunkUnload.getNeedUnloadChunks().add(compressXZ);
itr.remove();
}
}
if (!eventChunkUnload.getNeedUnloadChunks().isEmpty()) {
EventBusGetter.INSTANCE.post(eventChunkUnload);
}
SC_ChunkLoadEvent eventChunkLoad = new SC_ChunkLoadEvent(event.getPlayer());
for (int cZ = cMinZ; cZ <= cMaxZ; cZ++) {
for (int cX = cMinX; cX <= cMaxX; cX++) {
int compressXZ = CompactedCoords.compressXZ(cX, cZ);
if (!event.getPlayer().getLoadedChunks().contains(compressXZ)) {
if (!event.getPlayer().getLoadedChunks().contains(compressXZ)) {
eventChunkLoad.getNeedLoadChunks().add(compressXZ);
event.getPlayer().getLoadedChunks().add(compressXZ);
}
}
}
}
if (!eventChunkLoad.getNeedLoadChunks().isEmpty()) {
EventBusGetter.INSTANCE.post(eventChunkLoad);
}
}
event.getPlayer().getLocation().setXYZ(event.getNewLocation());
// TODO отсылать клиенту только(!) для корректировки позиции
// SC_PlayerMoveEvent nextEvent = new SC_PlayerMoveEvent(event.getPlayer());
// nextEvent.setNewLocation(event.getNewLocation());
// EventBusGetter.INSTANCE.post(nextEvent);
}
@Override @Override
public void run() { public void run() {
TPS_WATCHER.startWatch(); TPS_WATCHER.startWatch();
EventBusGetter.INSTANCE.register(this);
while (!isInterrupted()) { while (!isInterrupted()) {
TPS_WATCHER.check(); TPS_WATCHER.check();
/* --- --- --- */ /* --- --- --- */
/* TODO нужно перенести этот функционал на Network */
playerManager.getBroadcastChannel().sendTimeUpdate( playerManager.getBroadcastChannel().sendTimeUpdate(
gameTimer.getGameTime(), gameTimer.getGameTime(),
gameTimer.getWorldAge() gameTimer.getWorldAge()

View File

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

View File

@@ -1,50 +0,0 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 2018-08-08
*/
package mc.core;
import lombok.Getter;
import lombok.Setter;
public class Location implements Cloneable {
@Getter
@Setter
private double x, y, z;
public Location (double x, double y, double z) {
setXYZ(x, y, z);
}
public void setXYZ(double x, double y, double z) {
this.x = x;
this.y = y;
this.z = z;
}
public void setXYZ(Location location) {
setXYZ(location.x, location.y, location.z);
}
public int getBlockX() {
return Double.valueOf(Math.floor(x)).intValue();
}
public int getBlockY() {
return Double.valueOf(Math.floor(y)).intValue();
}
public int getBlockZ() {
return Double.valueOf(Math.floor(z)).intValue();
}
@Override
public Location clone() {
try {
return (Location) super.clone();
} catch (CloneNotSupportedException e) { // такое в нашем случае вообще возможно?
e.printStackTrace();
return null;
}
}
}

View File

@@ -1,7 +1,3 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 2018-06-29
*/
package mc.core.embedded; package mc.core.embedded;
import mc.core.EntityLocation; import mc.core.EntityLocation;
@@ -12,8 +8,7 @@ import mc.core.player.Player;
import mc.core.player.PlayerManager; import mc.core.player.PlayerManager;
import mc.core.text.Text; import mc.core.text.Text;
import mc.core.text.Title; import mc.core.text.Title;
import mc.core.world.chunk.Chunk; import mc.core.world.World;
import mc.core.world.chunk.ChunkSection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@@ -21,9 +16,6 @@ import java.util.Optional;
public class FakePlayerManager implements PlayerManager { public class FakePlayerManager implements PlayerManager {
public static class FakeNetChannet implements NetChannel { public static class FakeNetChannet implements NetChannel {
@Override
public void sendKeepAlive() {
}
@Override @Override
public void sendTimeUpdate(long time, long age) { public void sendTimeUpdate(long time, long age) {
@@ -53,7 +45,7 @@ public class FakePlayerManager implements PlayerManager {
private static final NetChannel FAKE_NET_CHANNEL = new FakeNetChannet(); private static final NetChannel FAKE_NET_CHANNEL = new FakeNetChannet();
@Override @Override
public Player createPlayer(String name, EntityLocation defaultLocation) { public Player createPlayer(String name, EntityLocation location, World world) {
return null; return null;
} }
@@ -66,13 +58,8 @@ public class FakePlayerManager implements PlayerManager {
} }
@Override @Override
public Optional<Player> getPlayer(String name) { public Player getPlayer(String name) {
return Optional.empty(); return null;
}
@Override
public Optional<Player> getPlayerById(int id) {
return Optional.empty();
} }
@Override @Override
@@ -81,7 +68,7 @@ public class FakePlayerManager implements PlayerManager {
} }
@Override @Override
public int getCountOnlinePlayers() { public int getCountPlayers() {
return 0; return 0;
} }
@@ -89,4 +76,9 @@ public class FakePlayerManager implements PlayerManager {
public NetChannel getBroadcastChannel() { public NetChannel getBroadcastChannel() {
return FAKE_NET_CHANNEL; return FAKE_NET_CHANNEL;
} }
@Override
public Player getOfflinePlayer(String name) {
return null;
}
} }

View File

@@ -2,7 +2,7 @@
* DmitriyMX <dimon550@gmail.com> * DmitriyMX <dimon550@gmail.com>
* 2018-05-02 * 2018-05-02
*/ */
package mc.core.events; package mc.core.eventbus;
public interface Event { public interface Event {
void setCanceled(boolean value); void setCanceled(boolean value);

View File

@@ -2,7 +2,7 @@
* DmitriyMX <dimon550@gmail.com> * DmitriyMX <dimon550@gmail.com>
* 2018-05-02 * 2018-05-02
*/ */
package mc.core.events; package mc.core.eventbus;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;

View File

@@ -2,12 +2,14 @@
* DmitriyMX <dimon550@gmail.com> * DmitriyMX <dimon550@gmail.com>
* 2018-05-02 * 2018-05-02
*/ */
package mc.core.events; package mc.core.eventbus;
import com.google.common.eventbus.EventBus; import com.google.common.eventbus.EventBus;
import lombok.Getter;
public final class EventBusGetter { public final class EventBusGetter {
public static final EventBus INSTANCE = new EventBus(); @Getter
private static final EventBus instance = new EventBus();
private EventBusGetter() { private EventBusGetter() {
} }

View File

@@ -2,22 +2,26 @@
* DmitriyMX <dimon550@gmail.com> * DmitriyMX <dimon550@gmail.com>
* 2018-05-02 * 2018-05-02
*/ */
package mc.core.events; package mc.core.eventbus.events;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter; import lombok.Setter;
import mc.core.EntityLocation; import mc.core.EntityLocation;
import mc.core.ImmutableEntityLocation;
import mc.core.eventbus.EventBase;
import mc.core.player.Player; import mc.core.player.Player;
@RequiredArgsConstructor
@Getter @Getter
public class CS_PlayerMoveEvent extends EventBase { public class CS_PlayerMoveEvent extends EventBase {
private final Player player; private final Player player;
private final EntityLocation oldLocation; // TODO сомнительное решение private final ImmutableEntityLocation oldLocation;
// вообще нужно будет создать реализацию "иммутабл локейшен" для подобных ситуаций
@Setter @Setter
private EntityLocation newLocation; private EntityLocation newLocation;
@Setter @Setter
private boolean recalcChunk = false; private boolean recalcChunk = false;
public CS_PlayerMoveEvent(Player player, EntityLocation oldLocation) {
this.player = player;
this.oldLocation = new ImmutableEntityLocation(oldLocation);
}
} }

View File

@@ -1,7 +1,8 @@
package mc.core.events; package mc.core.eventbus.events;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import mc.core.eventbus.EventBase;
import mc.core.player.Player; import mc.core.player.Player;
import java.util.ArrayList; import java.util.ArrayList;

View File

@@ -1,7 +1,8 @@
package mc.core.events; package mc.core.eventbus.events;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import mc.core.eventbus.EventBase;
import mc.core.player.Player; import mc.core.player.Player;
import java.util.ArrayList; import java.util.ArrayList;

View File

@@ -2,18 +2,19 @@
* DmitriyMX <dimon550@gmail.com> * DmitriyMX <dimon550@gmail.com>
* 2018-05-02 * 2018-05-02
*/ */
package mc.core.events; package mc.core.eventbus.events;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.Setter; import lombok.Setter;
import mc.core.eventbus.EventBase;
import java.net.SocketAddress; import java.net.SocketAddress;
@RequiredArgsConstructor @RequiredArgsConstructor
@Getter @Getter
@Setter @Setter
public class LoginEvent extends EventBase { public class SC_LoginEvent extends EventBase {
private String playerName; private String playerName;
private final SocketAddress remoteAddress; private final SocketAddress remoteAddress;
private boolean deny; private boolean deny;

View File

@@ -2,18 +2,19 @@
* DmitriyMX <dimon550@gmail.com> * DmitriyMX <dimon550@gmail.com>
* 2018-05-02 * 2018-05-02
*/ */
package mc.core.events; package mc.core.eventbus.events;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.Setter; import lombok.Setter;
import mc.core.EntityLocation; import mc.core.EntityLocation;
import mc.core.eventbus.EventBase;
import mc.core.player.Player; import mc.core.player.Player;
@RequiredArgsConstructor @RequiredArgsConstructor
@Getter @Getter
@Setter @Setter
public class PlayerLookEvent extends EventBase { public class SC_PlayerLookEvent extends EventBase {
private final Player player; private final Player player;
private EntityLocation newLook; private EntityLocation newLook;
} }

View File

@@ -1,9 +1,10 @@
package mc.core.events; package mc.core.eventbus.events;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.Setter; import lombok.Setter;
import mc.core.EntityLocation; import mc.core.EntityLocation;
import mc.core.eventbus.EventBase;
import mc.core.player.Player; import mc.core.player.Player;
@RequiredArgsConstructor @RequiredArgsConstructor

View File

@@ -2,18 +2,19 @@
* DmitriyMX <dimon550@gmail.com> * DmitriyMX <dimon550@gmail.com>
* 2018-05-02 * 2018-05-02
*/ */
package mc.core.events; package mc.core.eventbus.events;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.Setter; import lombok.Setter;
import mc.core.eventbus.EventBase;
import java.net.SocketAddress; import java.net.SocketAddress;
@RequiredArgsConstructor @RequiredArgsConstructor
@Getter @Getter
@Setter @Setter
public class ServerPingEvent extends EventBase { public class SC_ServerPingEvent extends EventBase {
private final SocketAddress remoteAddress; private final SocketAddress remoteAddress;
private String description; private String description;
private int online; private int online;

View File

@@ -16,11 +16,6 @@ import java.util.stream.Stream;
public class BroadcastNetChannel implements NetChannel { public class BroadcastNetChannel implements NetChannel {
private final Stream<Player> playerStream; private final Stream<Player> playerStream;
@Override
public void sendKeepAlive() {
playerStream.forEach(player -> player.getChannel().sendKeepAlive());
}
@Override @Override
public void sendTimeUpdate(final long time, final long age) { public void sendTimeUpdate(final long time, final long age) {
playerStream.forEach(player -> player.getChannel().sendTimeUpdate(time, age)); playerStream.forEach(player -> player.getChannel().sendTimeUpdate(time, age));

View File

@@ -9,7 +9,6 @@ import mc.core.text.Text;
import mc.core.text.Title; import mc.core.text.Title;
public interface NetChannel { public interface NetChannel {
void sendKeepAlive();
void sendTimeUpdate(long time, long age); void sendTimeUpdate(long time, long age);
default void sendChatMessage(Text text) { default void sendChatMessage(Text text) {
sendChatMessage(text, MessageType.CHAT_MESSAGE); sendChatMessage(text, MessageType.CHAT_MESSAGE);

View File

@@ -22,6 +22,7 @@ public abstract class NetInputStream {
public abstract short readShort(); public abstract short readShort();
public abstract int readInt(); public abstract int readInt();
public abstract int readVarInt(); public abstract int readVarInt();
public abstract int readVarInt(int[] countReadBytes);
public abstract long readLong(); public abstract long readLong();
public abstract float readFloat(); public abstract float readFloat();
public abstract double readDouble(); public abstract double readDouble();

View File

@@ -1,112 +0,0 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 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<Player> 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<Player> getPlayer(final String name) {
return players.stream()
.filter(player -> player.getName().equalsIgnoreCase(name))
.findFirst();
}
@Override
public Optional<Player> getPlayerById(final int id) {
return players.stream()
.filter(player -> player.getId() == id)
.findFirst();
}
@Override
public List<Player> 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;
}
}
}
}

View File

@@ -6,13 +6,14 @@ package mc.core.player;
import mc.core.EntityLocation; import mc.core.EntityLocation;
import mc.core.network.NetChannel; import mc.core.network.NetChannel;
import mc.core.world.World;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
public interface Player { public interface Player {
int getId(); int getId();
UUID getUUID(); UUID getUuid();
String getName(); String getName();
boolean isOnline(); boolean isOnline();
@@ -23,7 +24,8 @@ public interface Player {
void setChannel(NetChannel channel); void setChannel(NetChannel channel);
EntityLocation getLocation(); EntityLocation getLocation();
//TODO надо определиться - нужно ли здесь setLocation() или нет World getWorld();
void setWorld(World world);
boolean isFlying(); boolean isFlying();
void setFlying(boolean value); void setFlying(boolean value);

View File

@@ -1,22 +1,20 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 2018-04-15
*/
package mc.core.player; package mc.core.player;
import mc.core.EntityLocation; import mc.core.EntityLocation;
import mc.core.network.NetChannel; import mc.core.network.NetChannel;
import mc.core.world.World;
import java.util.List; import java.util.List;
import java.util.Optional;
public interface PlayerManager { public interface PlayerManager {
Player createPlayer(String name, EntityLocation defaultLocation); Player createPlayer(String name, EntityLocation location, World world);
void joinServer(Player player); void joinServer(Player player);
void leftServer(Player player); void leftServer(Player player);
Optional<Player> getPlayer(String name);
Optional<Player> getPlayerById(int id); Player getPlayer(String name);
List<Player> getPlayers(); List<Player> getPlayers();
int getCountOnlinePlayers(); int getCountPlayers();
NetChannel getBroadcastChannel(); NetChannel getBroadcastChannel();
Player getOfflinePlayer(String name);
} }

View File

@@ -1,40 +0,0 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 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<Integer> 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;
}
}

View File

@@ -1,20 +1,11 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 2018-06-11
*/
package mc.core.text; package mc.core.text;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import java.util.ArrayList; import java.util.*;
import java.util.Collections;
import java.util.List;
import java.util.StringJoiner;
@Getter @Getter
@EqualsAndHashCode
public class Text { public class Text {
private static final Text EMPTY = new Text(); private static final Text EMPTY = new Text();
private static final Text NEW_LINE = new Text("\n", null, null, null); private static final Text NEW_LINE = new Text("\n", null, null, null);
@@ -39,7 +30,7 @@ public class Text {
} }
public boolean isEmpty() { public boolean isEmpty() {
boolean result = content.isEmpty(); boolean result = (content == null || content.isEmpty());
if (children != null && !children.isEmpty()) { if (children != null && !children.isEmpty()) {
for (Text child : children) { 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 { public static class Builder {
@Getter @Getter
private String content; private String content;
@@ -81,6 +85,8 @@ public class Text {
} }
public Builder(Object... objects) { public Builder(Object... objects) {
this.children = new ArrayList<>();
for(Object obj : objects) { for(Object obj : objects) {
if (obj instanceof String) { if (obj instanceof String) {
if (this.content == null) { if (this.content == null) {
@@ -96,10 +102,10 @@ public class Text {
} }
} else if (obj instanceof TextColor) { } else if (obj instanceof TextColor) {
this.color = (TextColor) obj; this.color = (TextColor) obj;
} else if (obj instanceof Text) {
children.add((Text) obj);
} }
} }
this.children = new ArrayList<>();
} }
public List<Text> getChildren() { public List<Text> getChildren() {
@@ -133,8 +139,14 @@ public class Text {
return this; return this;
} }
public Builder append(String string) {
return append(Text.of(string));
}
public Builder append(Text child) { public Builder append(Text child) {
if (child != null) {
this.children.add(child); this.children.add(child);
}
return this; return this;
} }
@@ -144,10 +156,13 @@ public class Text {
} }
public Text build() { public Text build() {
if (children.isEmpty()) { if (children.isEmpty() && (content == null || content.isEmpty())) {
return Text.EMPTY; return Text.EMPTY;
} }
if (children.size() == 1 && children.get(0) != null) {
return children.get(0);
} else {
return new Text( return new Text(
content, content,
color, color,
@@ -156,6 +171,7 @@ public class Text {
); );
} }
} }
}
public static Builder builder() { public static Builder builder() {
return new Builder(); return new Builder();

View File

@@ -25,18 +25,4 @@ public class CompactedCoords {
int i = (int)value; int i = (int)value;
return value < (double)i ? i - 1 : i; return value < (double)i ? i - 1 : i;
} }
public static long compressXYZ(double x, double y, double z) {
return ((floor_double(x) & 0x3FFFFFF) << 38)
| ((floor_double(y) & 0xFFF) << 26)
| (floor_double(z) & 0x3FFFFFF);
}
public static double[] uncompressXYZ(long compactValue) {
return new double[]{
compactValue >> 38,
(compactValue >> 26) & 0x0FFF,
compactValue << 38 >> 38 // is normal?
};
}
} }

View File

@@ -5,9 +5,11 @@
package mc.core.world; package mc.core.world;
import mc.core.EntityLocation; import mc.core.EntityLocation;
import mc.core.world.block.BlockLocation;
import mc.core.world.chunk.Chunk; import mc.core.world.chunk.Chunk;
public interface World { public interface World {
String getName();
WorldType getWorldType(); WorldType getWorldType();
EntityLocation getSpawn(); EntityLocation getSpawn();
@@ -18,4 +20,12 @@ public interface World {
Chunk getChunk(int x, int z); Chunk getChunk(int x, int z);
void setChunk(int x, int z, Chunk chunkSection); 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);
}
} }

View File

@@ -3,7 +3,6 @@ package mc.core.world.block;
import com.flowpowered.nbt.Tag; import com.flowpowered.nbt.Tag;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import mc.core.Location;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@@ -12,7 +11,7 @@ import java.util.stream.Stream;
public abstract class AbstractBlock implements Block { public abstract class AbstractBlock implements Block {
@Getter @Getter
@Setter @Setter
private Location location; private BlockLocation location;
@Getter @Getter
private int light = 0; private int light = 0;
@Getter @Getter

View File

@@ -1,11 +1,10 @@
package mc.core.world.block; package mc.core.world.block;
import mc.core.Location;
import mc.core.nbt.Taggable; import mc.core.nbt.Taggable;
public interface Block extends Taggable{ public interface Block extends Taggable{
int getLight(); int getLight();
void setLight(int light); void setLight(int light);
BlockType getBlockType(); BlockType getBlockType();
Location getLocation(); BlockLocation getLocation();
} }

View File

@@ -1,8 +1,5 @@
package mc.core.world.block; package mc.core.world.block;
import mc.core.Location;
import mc.core.world.World;
public class BlockFactory { public class BlockFactory {
public Block create(BlockType blockType, int x, int y, int z) { public Block create(BlockType blockType, int x, int y, int z) {
@@ -13,7 +10,7 @@ public class BlockFactory {
private class EmbeddedBlock extends AbstractBlock { private class EmbeddedBlock extends AbstractBlock {
EmbeddedBlock(BlockType type, int x, int y, int z) { EmbeddedBlock(BlockType type, int x, int y, int z) {
super(type); super(type);
setLocation(new Location(x, y, z)); setLocation(new BlockLocation(x, y, z));
} }
} }
} }

View File

@@ -0,0 +1,30 @@
package mc.core.world.block;
import lombok.*;
@NoArgsConstructor
@AllArgsConstructor
@Data
public class BlockLocation implements Cloneable {
private int x, y, z;
public static BlockLocation ZERO() {
return new BlockLocation(0,0,0);
}
public void setXYZ(int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
}
@Override
public BlockLocation clone() {
try {
return (BlockLocation) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}

View File

@@ -0,0 +1,91 @@
package mc.core;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.concurrent.ThreadLocalRandom;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
class EntityLocationTest {
private static final ThreadLocalRandom rnd = ThreadLocalRandom.current();
private static final double minD = 0.0d, maxD = 10.0d;
private static final float minF = 0.0f, maxF = 359.9f;
private double x, y, z;
private float yaw, pitch;
@BeforeEach
void before() {
x = rnd.nextDouble(minD, maxD);
y = rnd.nextDouble(minD, maxD);
z = rnd.nextDouble(minD, maxD);
yaw = rnd.nextFloat() * (maxF - minF) + minF;
pitch = rnd.nextFloat() * (maxF - minF) + minF;
}
@Test
void equals_() {
EntityLocation loc1 = new EntityLocation(x, y, z, yaw, pitch);
EntityLocation loc2 = new EntityLocation(x, y, z, yaw, pitch);
assertEquals(loc1, loc2);
loc2 = new EntityLocation(x+1, y+2, z-3, yaw, pitch);
assertNotEquals(loc1, loc2);
loc2 = new EntityLocation(x, y, z, yaw-1, pitch+2);
assertNotEquals(loc1, loc2);
}
@Test
void clone_() {
EntityLocation locOrig = new EntityLocation(x, y, z, yaw, pitch);
EntityLocation locClone = locOrig.clone();
assertEquals(locOrig, locClone);
}
@Test
void getBlockXZ() {
EntityLocation location;
location = new EntityLocation(0d, 0, 0d, 0f, 0f);
assertEquals(0, location.getBlockX());
assertEquals(0, location.getBlockZ());
location.setXYZ(0.1d, 0, 0.1d);
assertEquals(0, location.getBlockX());
assertEquals(0, location.getBlockZ());
location.setXYZ(0.5d, 0, 0.5d);
assertEquals(0, location.getBlockX());
assertEquals(0, location.getBlockZ());
location.setXYZ(0.9d, 0, 0.9d);
assertEquals(0, location.getBlockX());
assertEquals(0, location.getBlockZ());
location.setXYZ(1d, 0, 1d);
assertEquals(1, location.getBlockX());
assertEquals(1, location.getBlockZ());
location.setXYZ(-0.1d, 0, -0.1d);
assertEquals(-1, location.getBlockX());
assertEquals(-1, location.getBlockZ());
location.setXYZ(-0.5d, 0, -0.5d);
assertEquals(-1, location.getBlockX());
assertEquals(-1, location.getBlockZ());
location.setXYZ(-0.9d, 0, -0.9d);
assertEquals(-1, location.getBlockX());
assertEquals(-1, location.getBlockZ());
location.setXYZ(-1d, 0, -1d);
assertEquals(-1, location.getBlockX());
assertEquals(-1, location.getBlockZ());
location.setXYZ(-1.1d, 0, -1.1d);
assertEquals(-2, location.getBlockX());
assertEquals(-2, location.getBlockZ());
}
}

View File

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

View File

@@ -1,96 +0,0 @@
package mc.core;
import mc.core.world.World;
import mc.core.world.chunk.Chunk;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class TestEntityLocation {
private World world;
@Before
public void prepareWorld() {
this.world = mock(World.class);
when(world.getChunk(anyInt(), anyInt())).thenAnswer(invocation -> {
Object[] args = invocation.getArguments();
Chunk chunk = mock(Chunk.class);
when(chunk.getX()).thenReturn((int) args[0]);
when(chunk.getZ()).thenReturn((int) args[1]);
return chunk;
});
}
@Test
public void cloneTest() {
EntityLocation firstLocation = new EntityLocation(10, 20, 30, 40, 50, world);
assertSame("Lost world reference before cloning", world, firstLocation.getWorld());
EntityLocation locationClone = firstLocation.clone();
assertEquals("X mismatch", firstLocation.getX(), locationClone.getX(), 0);
assertEquals("Y mismatch", firstLocation.getY(), locationClone.getY(), 0);
assertEquals("Z mismatch", firstLocation.getZ(), locationClone.getZ(), 0);
assertEquals("Pitch mismatch", firstLocation.getPitch(), locationClone.getPitch(), 0);
assertEquals("Yaw mismatch", firstLocation.getYaw(), locationClone.getYaw(), 0);
assertSame("World mismatch (accidental clone of the World object?)", firstLocation.getWorld(), locationClone.getWorld());
}
@Test
public void testGetChunk() {
EntityLocation location;
Chunk chunk;
location = new EntityLocation(0d, 0, 0d, 0f, 0f, world);
chunk = location.getChunk();
assertEquals(0, chunk.getX());
assertEquals(0, chunk.getZ());
location.setXYZ(1d, 0, 1d);
chunk = location.getChunk();
assertEquals(0, chunk.getX());
assertEquals(0, chunk.getZ());
location.setXYZ(15d, 0, 15d);
chunk = location.getChunk();
assertEquals(0, chunk.getX());
assertEquals(0, chunk.getZ());
location.setXYZ(16d, 0, 16d);
chunk = location.getChunk();
assertEquals(1, chunk.getX());
assertEquals(1, chunk.getZ());
location.setXYZ(-0.1d, 0, -0.1d);
chunk = location.getChunk();
assertEquals(-1, chunk.getX());
assertEquals(-1, chunk.getZ());
location.setXYZ(-1d, 0, -1d);
chunk = location.getChunk();
assertEquals(-1, chunk.getX());
assertEquals(-1, chunk.getZ());
location.setXYZ(-15d, 0, -15d);
chunk = location.getChunk();
assertEquals(-1, chunk.getX());
assertEquals(-1, chunk.getZ());
//TODO на практике, таких точных значений не встретиться, но тем не менее данный тест не проходит
//location.setXYZ(-16.0d, 0, -16.0d);
//chunk = location.getChunk();
//assertEquals(-2, chunk.getX());
//assertEquals(-2, chunk.getZ());
location.setXYZ(-16.001d, 0, -16.001d);
chunk = location.getChunk();
assertEquals(-2, chunk.getX());
assertEquals(-2, chunk.getZ());
}
}

View File

@@ -1,52 +0,0 @@
package mc.core;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class TestLocation {
@Test
public void testGetBlockXZ() {
Location location;
location = new Location(0d, 0, 0d);
assertEquals(0, location.getBlockX());
assertEquals(0, location.getBlockZ());
location.setXYZ(0.1d, 0, 0.1d);
assertEquals(0, location.getBlockX());
assertEquals(0, location.getBlockZ());
location.setXYZ(0.5d, 0, 0.5d);
assertEquals(0, location.getBlockX());
assertEquals(0, location.getBlockZ());
location.setXYZ(0.9d, 0, 0.9d);
assertEquals(0, location.getBlockX());
assertEquals(0, location.getBlockZ());
location.setXYZ(1d, 0, 1d);
assertEquals(1, location.getBlockX());
assertEquals(1, location.getBlockZ());
location.setXYZ(-0.1d, 0, -0.1d);
assertEquals(-1, location.getBlockX());
assertEquals(-1, location.getBlockZ());
location.setXYZ(-0.5d, 0, -0.5d);
assertEquals(-1, location.getBlockX());
assertEquals(-1, location.getBlockZ());
location.setXYZ(-0.9d, 0, -0.9d);
assertEquals(-1, location.getBlockX());
assertEquals(-1, location.getBlockZ());
location.setXYZ(-1d, 0, -1d);
assertEquals(-1, location.getBlockX());
assertEquals(-1, location.getBlockZ());
location.setXYZ(-1.1d, 0, -1.1d);
assertEquals(-2, location.getBlockX());
assertEquals(-2, location.getBlockZ());
}
}

View File

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

View File

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

View File

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

View File

@@ -1,62 +0,0 @@
package mc.core.utils;
import org.junit.Test;
import java.util.Random;
import static org.junit.Assert.assertEquals;
public class TestCompactedCoords {
@Test
public void testXZSimple() {
for (int z = -100; z <= 100; z++) {
for (int x = -100; x <= 100; x++) {
int compressXZ = CompactedCoords.compressXZ(x, z);
int[] xz = CompactedCoords.uncompressXZ(compressXZ);
assertEquals(x, xz[0]);
assertEquals(z, xz[1]);
}
}
}
@Test
public void testXZRandom() {
Random random = new Random();
int x,z;
for (int i = 0; i < 100; i++) {
do {
x = random.nextInt();
} while (x < Short.MIN_VALUE || x > Short.MAX_VALUE);
do {
z = random.nextInt();
} while (z < Short.MIN_VALUE || z > Short.MAX_VALUE);
int compressXZ = CompactedCoords.compressXZ(x, z);
int[] xz = CompactedCoords.uncompressXZ(compressXZ);
assertEquals(x, xz[0]);
assertEquals(z, xz[1]);
}
}
// @Test
public void testXYZSimple() {
for (int z = -100; z <= 100; z++) {
for (int x = -100; x <= 100; x++) {
for (int y = -100; y <= 100; y++) {
long compressXYZ = CompactedCoords.compressXYZ(x, y, z);
double[] xyz = CompactedCoords.uncompressXYZ(compressXYZ);
assertEquals(x, xyz[0], 0.001d);
assertEquals(y, xyz[1], 0.001d);
assertEquals(z, xyz[2], 0.001d);
}
}
}
}
}

View File

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

View File

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

View File

@@ -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<Integer> loadedChunks;
private NetChannel channel;
private EntityLocation location;
private Reference<World> $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);
}
}

View File

@@ -0,0 +1,115 @@
package mc.core.h2db;
import com.google.common.collect.ImmutableList;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import mc.core.EntityLocation;
import mc.core.h2db.service.H2PlayerService;
import mc.core.network.BroadcastNetChannel;
import mc.core.network.NetChannel;
import mc.core.player.Player;
import mc.core.player.PlayerManager;
import mc.core.player.PlayerSettings;
import mc.core.world.World;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
@Slf4j
@Component
public class H2PlayerManager implements PlayerManager {
@Setter
@Autowired
private H2PlayerService h2PlayerService;
private List<H2Player> playerList = Collections.synchronizedList(new ArrayList<>());
@Autowired
private World world; //FIXME
@Override
public Player createPlayer(String name, EntityLocation location, World world) {
H2Player h2Player = new H2Player();
h2Player.setName(name);
h2Player.setUuid(UUID.randomUUID());
h2Player.setLocation(location.clone());
h2Player.setLoadedChunks(new ArrayList<>());
h2Player.setWorld(world);
h2Player.setSettings(new PlayerSettings());
return h2PlayerService.save(h2Player);
}
@Override
public void joinServer(Player player) {
//TODO в дальнейшем следует именно этому методу передать функции инсерта в БД
H2Player h2Player = (H2Player) player;
playerList.add(h2Player);
h2Player.setOnline(true);
}
@Override
public void leftServer(Player player) {
H2Player h2Player = (H2Player) player;
h2PlayerService.save(h2Player);
h2Player.setOnline(false);
h2Player.getLoadedChunks().clear();
}
@Override
public Player getPlayer(String name) {
return playerList.stream()
.filter(player -> player.getName().equals(name))
.filter(H2Player::isOnline)
.findFirst().orElse(null);
}
@Override
public List<Player> getPlayers() {
return playerList.stream()
.filter(H2Player::isOnline)
.collect(ImmutableList.toImmutableList());
}
@Override
public int getCountPlayers() {
return (int) playerList.stream()
.filter(H2Player::isOnline)
.count();
}
@Override
public NetChannel getBroadcastChannel() {
return new BroadcastNetChannel(
playerList.stream()
.filter(H2Player::isOnline)
.map(player -> (Player)player)
);
}
@Override
public Player getOfflinePlayer(String name) {
//TODO похоже в попытке где-то оптимизировать/сэконопить я сам себя ******[обманул]
//необходимо этот участок кода переписать
//потому как похоже на экономию на спичках
H2Player h2Player = playerList.stream()
.filter(player -> player.getName().equals(name))
.filter(player -> !player.isOnline())
.findFirst().orElse(null);
if (h2Player == null) {
h2Player = h2PlayerService.getByName(name);
if (h2Player != null) {
h2Player.setWorld(world);
return h2Player;
} else {
return null;
}
} else {
h2Player.setWorld(world);
return h2Player;
}
}
}

View File

@@ -0,0 +1,104 @@
package mc.core.h2db.entity;
import lombok.Data;
import lombok.NoArgsConstructor;
import mc.core.EntityLocation;
import mc.core.h2db.H2Player;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import java.util.UUID;
@Entity
@Table(name = "players",
indexes = {@Index(name = "idx_players_uuid", columnList = "uuid", unique = true),
@Index(name = "idx_players_name", columnList = "name")})
@NoArgsConstructor
@Data
public class H2PlayerEntity {
@Id
@GeneratedValue(generator = "increment")
@GenericGenerator(name= "increment", strategy= "increment")
@Column(nullable = false)
private Long id;
@Column(length = 36, nullable = false)
private String uuid;
@Column(length = 16, nullable = false)
private String name;
@Column(name = "location_x", nullable = false)
private Double locationX;
@Column(name = "location_y", nullable = false)
private Double locationY;
@Column(name = "location_z", nullable = false)
private Double locationZ;
@Column(name = "location_yaw", nullable = false)
private Float locationYaw;
@Column(name = "location_pitch", nullable = false)
private Float locationPitch;
@Column(name = "location_world", length = 64, nullable = false)
private String locationWorld;
public H2PlayerEntity(H2Player player) {
this.id = (long) player.getId();
setUuid(player.getUuid().toString());
setName(this.name = player.getName());
this.locationX = player.getLocation().getX();
this.locationY = player.getLocation().getY();
this.locationZ = player.getLocation().getZ();
this.locationYaw = player.getLocation().getYaw();
this.locationPitch = player.getLocation().getPitch();
if (player.getWorld() != null) { //FIXME
this.locationWorld = player.getWorld().getName();
} else {
this.locationWorld = "null_world";
}
}
public void setUuid(String uuid) {
if (uuid == null || uuid.trim().isEmpty()) {
this.uuid = null;
} else {
this.uuid = uuid;
}
}
public void setName(String name) {
if (name == null || name.trim().isEmpty()) {
this.name = null;
} else {
this.name = name;
}
}
public H2Player toPlayer() {
H2Player player = new H2Player();
return toPlayer(player);
}
public H2Player toPlayer(H2Player player) {
player.setId(this.id.intValue());
player.setUuid(UUID.fromString(this.uuid));
player.setName(this.name);
if (player.getLocation() == null) {
player.setLocation(new EntityLocation(
this.locationX, this.locationY, this.locationZ,
this.locationYaw, this.locationPitch
));
} else {
player.getLocation().setXYZ(this.locationX, this.locationY, this.locationZ);
player.getLocation().setYawPitch(this.locationYaw, this.locationPitch);
}
player.setWorld(null); //FIXME
return player;
}
}

View File

@@ -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<H2PlayerEntity, Long> {
Optional<H2PlayerEntity> findByName(String name);
}

View File

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

View File

@@ -0,0 +1,41 @@
package mc.core.h2db.service;
import mc.core.h2db.H2Player;
import mc.core.h2db.entity.H2PlayerEntity;
import mc.core.h2db.repository.H2PlayerEntityRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Optional;
@Service
public class H2PlayerServiceImpl implements H2PlayerService {
@Autowired
private H2PlayerEntityRepository h2PlayerEntityRepository;
@Override
public H2Player save(H2Player player) {
H2PlayerEntity entity = new H2PlayerEntity(player);
//TODO возможно имеет смысл здесь оптимизация
//вместо toPlayer() сделать toPlayer(H2Player) который в существующий
//будет дописывать/обновлять данные
return h2PlayerEntityRepository.saveAndFlush(entity).toPlayer(player);
}
@Override
public void remove(H2Player player) {
h2PlayerEntityRepository.deleteById((long) player.getId());
}
@Override
public H2Player getByName(String name) {
Optional<H2PlayerEntity> optEntity = h2PlayerEntityRepository.findByName(name);
return optEntity.map(H2PlayerEntity::toPlayer).orElse(null);
}
@Override
public H2Player getById(int id) {
Optional<H2PlayerEntity> optEntity = h2PlayerEntityRepository.findById((long) id);
return optEntity.map(H2PlayerEntity::toPlayer).orElse(null);
}
}

View File

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

View File

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

View File

@@ -0,0 +1,90 @@
package mc.core.h2db;
import mc.core.h2db.service.H2PlayerService;
import mc.core.world.World;
import org.hibernate.jpa.HibernatePersistenceProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import java.util.Properties;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@Configuration
@EnableJpaRepositories
@EnableTransactionManagement
@ComponentScan("mc.core.h2db")
public class TestSpringConfig {
private static final String DATABASE_DRIVER = "org.h2.Driver";
private static final String DATABASE_URL = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1";
private static final String DATABASE_USERNAME = "sa";
private static final String DATABASE_PASSWORD = "s3cReT";
static {
System.setProperty("org.jboss.logging.provider", "slf4j");
}
private Properties hibernateProp() {
Properties properties = new Properties();
properties.put("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
properties.put("hibernate.show_sql", "true");
properties.put("hibernate.format_sql", "true");
properties.put("hibernate.use_sql_comments", "true");
properties.put("hibernate.hbm2ddl.auto", "create");
return properties;
}
@Bean
public World mockWorld() {
World mockWorld = mock(World.class);
when(mockWorld.getName()).thenReturn("mock_world");
return mockWorld;
}
@Bean
public DataSource dataSource() {
DriverManagerDataSource dmds = new DriverManagerDataSource();
dmds.setDriverClassName(DATABASE_DRIVER);
dmds.setUrl(DATABASE_URL);
dmds.setUsername(DATABASE_USERNAME);
dmds.setPassword(DATABASE_PASSWORD);
return dmds;
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setDataSource(dataSource);
entityManagerFactoryBean.setPersistenceProviderClass(HibernatePersistenceProvider.class);
entityManagerFactoryBean.setPackagesToScan("mc.core.h2db.entity");
entityManagerFactoryBean.setJpaProperties(hibernateProp());
return entityManagerFactoryBean;
}
@Bean
public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;
}
@Bean
public H2PlayerManager h2PlayerManager(H2PlayerService h2PlayerService) {
H2PlayerManager playerManager = new H2PlayerManager();
playerManager.setH2PlayerService(h2PlayerService);
return playerManager;
}
}

View File

@@ -0,0 +1,144 @@
package mc.core.h2db.service;
import mc.core.EntityLocation;
import mc.core.h2db.H2Player;
import mc.core.h2db.TestSpringConfig;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
import static org.junit.jupiter.api.Assertions.*;
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {TestSpringConfig.class})
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
class H2PlayerServiceTest {
@Autowired
private H2PlayerService h2PlayerService;
private H2Player buildPlayer() {
final ThreadLocalRandom rnd = ThreadLocalRandom.current();
final double minD = 0.0d, maxD = 10.0d;
final float minF = 0.0f, maxF = 359.9f;
final int minI = 1000, maxI = 9999;
final H2Player player = new H2Player();
player.setUuid(UUID.randomUUID());
player.setName("player" + rnd.nextInt(minI, maxI));
player.setLocation(new EntityLocation(
rnd.nextDouble(minD, maxD),
rnd.nextDouble(minD, maxD),
rnd.nextDouble(minD, maxD),
rnd.nextFloat() * (maxF - minF) + minF,
rnd.nextFloat() * (maxF - minF) + minF
));
player.setWorld(null); //FIXME
return player;
}
@Test
void save() {
H2Player player = buildPlayer();
H2Player savedPlayer = h2PlayerService.save(player);
player.setId(savedPlayer.getId()); //FIXME костыль, однако
assertEquals(player, savedPlayer);
}
@Test
void save_NameEmpty() {
assertThrows(Exception.class, () -> {
H2Player player = buildPlayer();
player.setName("");
h2PlayerService.save(player);
});
}
@Test
void save_NameNull() {
assertThrows(Exception.class, () -> {
H2Player player = buildPlayer();
player.setName(null);
h2PlayerService.save(player);
});
}
@Test
void save_UuidNull() {
assertThrows(Exception.class, () -> {
H2Player player = buildPlayer();
player.setUuid(null);
h2PlayerService.save(player);
});
}
@Test
void save_LocationNull() {
assertThrows(Exception.class, () -> {
H2Player player = buildPlayer();
player.setLocation(null);
h2PlayerService.save(player);
});
}
@Test
void remove() {
H2Player player = h2PlayerService.save(buildPlayer());
h2PlayerService.remove(player);
H2Player player2 = h2PlayerService.getById(player.getId());
assertNull(player2);
}
@Test
void remove_NotExists() {
assertThrows(Exception.class, () -> {
H2Player player = h2PlayerService.save(buildPlayer());
h2PlayerService.remove(player);
h2PlayerService.remove(player);
});
}
@Test
void getByName() {
H2Player player = h2PlayerService.save(buildPlayer());
H2Player player2 = h2PlayerService.getByName(player.getName());
assertEquals(player, player2);
}
@Test
void getByName_NotExists() {
assertNull(h2PlayerService.getByName("UNKNOW_PLAYER"));
}
@Test
void getByName_Empty() {
assertNull(h2PlayerService.getByName(""));
}
@Test
void getByName_Null() {
assertNull(h2PlayerService.getByName(null));
}
@Test
void getById() {
H2Player player = h2PlayerService.save(buildPlayer());
H2Player player2 = h2PlayerService.getById(player.getId());
assertEquals(player, player2);
}
@Test
void getById_NotExists() {
assertNull(h2PlayerService.getById(9999));
}
}

View File

@@ -1,4 +1,3 @@
group 'mc'
version '1.0-SNAPSHOT' version '1.0-SNAPSHOT'
dependencies { dependencies {
@@ -6,6 +5,5 @@ dependencies {
compile_excludeCopy project(':core') compile_excludeCopy project(':core')
/* Components */ /* Components */
compile (group: 'com.google.guava', name: 'guava', version: '24.1-jre') compile (group: 'com.google.code.gson', name: 'gson', version: '2.8.5')
compile (group: 'com.google.code.gson', name: 'gson', version: '2.8.2')
} }

View File

@@ -13,25 +13,31 @@ import java.util.UUID;
@Slf4j @Slf4j
public abstract class NetInputStream_p340 extends NetInputStream { public abstract class NetInputStream_p340 extends NetInputStream {
@Override @Override
public int readVarInt() { public int readVarInt(int[] countReadBytes) {
int numRead = 0; int numRead = 0;
int result = 0; int result = 0;
byte read; byte read;
do { do {
if ((numRead+1) > 5) {
log.warn("VarInt is too big");
break;
}
read = readByte(); read = readByte();
int value = (read & 0b01111111); int value = (read & 0b01111111);
result |= (value << (7 * numRead)); result |= (value << (7 * numRead));
numRead++; numRead++;
if (numRead > 5) {
log.warn("VarInt is too big");
break;
}
} while ((read & 0b10000000) != 0); } while ((read & 0b10000000) != 0);
if (countReadBytes != null && countReadBytes.length == 1) countReadBytes[0] = numRead;
return result; return result;
} }
@Override
public int readVarInt() {
return readVarInt(null);
}
@Override @Override
public String readString() { public String readString() {
int size = readVarInt(); int size = readVarInt();

View File

@@ -45,8 +45,7 @@ public class TeleportManager {
public void apply(int teleportId) { public void apply(int teleportId) {
if (teleportMap.containsKey(teleportId)) { if (teleportMap.containsKey(teleportId)) {
TpData data = teleportMap.remove(teleportId); TpData data = teleportMap.remove(teleportId);
data.player.getLocation().setXYZ(data.newLocation); data.player.getLocation().set(data.newLocation);
data.player.getLocation().setYawPitch(data.newLocation);
} }
} }

View File

@@ -4,7 +4,6 @@
*/ */
package mc.core.network.proto_1_12_2.packets; package mc.core.network.proto_1_12_2.packets;
import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -17,7 +16,6 @@ import mc.core.world.chunk.Chunk;
import mc.core.world.chunk.ChunkSection; import mc.core.world.chunk.ChunkSection;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;

View File

@@ -18,6 +18,11 @@ import mc.core.network.SCPacket;
@Setter @Setter
@ToString @ToString
public class PlayerAbilitiesPacket implements SCPacket, CSPacket { 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 godMode = false;
private boolean flying = false; private boolean flying = false;
private boolean canFly = false; private boolean canFly = false;
@@ -29,10 +34,10 @@ public class PlayerAbilitiesPacket implements SCPacket, CSPacket {
@Override @Override
public void writeSelf(NetOutputStream netStream) { public void writeSelf(NetOutputStream netStream) {
byte flag = 0; byte flag = 0;
if (godMode) flag = (byte)(flag | 0x01); if (godMode) flag = (byte)(flag | $GOD_MODE_MASK);
if (flying) flag = (byte)(flag | 0x02); if (flying) flag = (byte)(flag | $FLYING_MASK);
if (canFly) flag = (byte)(flag | 0x04); if (canFly) flag = (byte)(flag | $CAN_FLY_MASK);
if (instantDestroyBlocks) flag = (byte)(flag | 0x08); if (instantDestroyBlocks) flag = (byte)(flag | $IDB_MASK);
netStream.writeByte(flag); netStream.writeByte(flag);
netStream.writeFloat(flyingSpeed); netStream.writeFloat(flyingSpeed);
@@ -42,11 +47,10 @@ public class PlayerAbilitiesPacket implements SCPacket, CSPacket {
@Override @Override
public void readSelf(NetInputStream netStream) { public void readSelf(NetInputStream netStream) {
byte flag = netStream.readByte(); byte flag = netStream.readByte();
//FIXME треубет проверки godMode = (flag & $GOD_MODE_MASK) > 0;
godMode = (flag == 0x08); canFly = (flag & $CAN_FLY_MASK) > 0;
canFly = (flag == 0x04); flying = (flag & $FLYING_MASK) > 0;
flying = (flag == 0x02); instantDestroyBlocks = (flag & $IDB_MASK) > 0;
instantDestroyBlocks = (flag == 0x01);
flyingSpeed = netStream.readFloat(); flyingSpeed = netStream.readFloat();
walkingSpeed = netStream.readFloat(); walkingSpeed = netStream.readFloat();

View File

@@ -2,16 +2,16 @@ package mc.core.network.proto_1_12_2.packets;
import lombok.Getter; import lombok.Getter;
import lombok.ToString; import lombok.ToString;
import mc.core.Location; import mc.core.network.proto_1_12_2.serializers.BlockLocationSerializer;
import mc.core.world.block.BlockLocation;
import mc.core.network.CSPacket; import mc.core.network.CSPacket;
import mc.core.network.NetInputStream; import mc.core.network.NetInputStream;
import mc.core.network.proto_1_12_2.Direction; import mc.core.network.proto_1_12_2.Direction;
import mc.core.utils.CompactedCoords;
@Getter @Getter
@ToString @ToString
public class PlayerBlockPlacementPacket implements CSPacket { public class PlayerBlockPlacementPacket implements CSPacket {
private Location location; private BlockLocation location;
private Direction face; private Direction face;
/** true - main hand; false - off hand */ /** true - main hand; false - off hand */
private boolean hand; private boolean hand;
@@ -20,8 +20,7 @@ public class PlayerBlockPlacementPacket implements CSPacket {
@Override @Override
public void readSelf(NetInputStream netStream) { public void readSelf(NetInputStream netStream) {
long compactedCoords = netStream.readLong(); long compactedCoords = netStream.readLong();
double[] xyz = CompactedCoords.uncompressXYZ(compactedCoords); location = BlockLocationSerializer.fromLong(compactedCoords);
location = new Location(xyz[0], xyz[1], xyz[2]);
face = Direction.getById(netStream.readVarInt()); face = Direction.getById(netStream.readVarInt());
hand = (netStream.readVarInt() == 1); hand = (netStream.readVarInt() == 1);
cursorX = netStream.readFloat(); cursorX = netStream.readFloat();

View File

@@ -3,11 +3,11 @@ package mc.core.network.proto_1_12_2.packets;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.ToString; import lombok.ToString;
import mc.core.Location; import mc.core.network.proto_1_12_2.serializers.BlockLocationSerializer;
import mc.core.world.block.BlockLocation;
import mc.core.network.CSPacket; import mc.core.network.CSPacket;
import mc.core.network.NetInputStream; import mc.core.network.NetInputStream;
import mc.core.network.proto_1_12_2.Direction; import mc.core.network.proto_1_12_2.Direction;
import mc.core.utils.CompactedCoords;
import java.util.Arrays; import java.util.Arrays;
@@ -42,15 +42,14 @@ public class PlayerDiggingPacket implements CSPacket {
} }
private Status status; private Status status;
private Location location; private BlockLocation location;
private Direction face; private Direction face;
@Override @Override
public void readSelf(NetInputStream netStream) { public void readSelf(NetInputStream netStream) {
status = Status.getById(netStream.readVarInt()); status = Status.getById(netStream.readVarInt());
long compactCoord = netStream.readLong(); long compactCoord = netStream.readLong();
double[] xyz = CompactedCoords.uncompressXYZ(compactCoord); location = BlockLocationSerializer.fromLong(compactCoord);
location = new Location(xyz[0], xyz[1], xyz[2]);
face = Direction.getById(netStream.readByte()); face = Direction.getById(netStream.readByte());
} }
} }

View File

@@ -48,8 +48,7 @@ public class PlayerPositionAndLookPacket implements SCPacket, CSPacket {
netStream.readDouble(), netStream.readDouble(),
netStream.readDouble(), netStream.readDouble(),
netStream.readFloat(), netStream.readFloat(),
netStream.readFloat(), netStream.readFloat()
null
); );
this.onGround = netStream.readBoolean(); this.onGround = netStream.readBoolean();

View File

@@ -4,7 +4,7 @@
*/ */
package mc.core.network.proto_1_12_2.packets; package mc.core.network.proto_1_12_2.packets;
import mc.core.Location; import mc.core.world.block.BlockLocation;
import mc.core.network.CSPacket; import mc.core.network.CSPacket;
import mc.core.network.NetInputStream; import mc.core.network.NetInputStream;
@@ -12,7 +12,7 @@ public class TabCompletePacket implements CSPacket {
private String text; private String text;
private boolean assumeCommand; private boolean assumeCommand;
private boolean hasPosition; private boolean hasPosition;
private Location location; private BlockLocation location;
@Override @Override
public void readSelf(NetInputStream netStream) { public void readSelf(NetInputStream netStream) {
@@ -27,7 +27,7 @@ public class TabCompletePacket implements CSPacket {
double y = (compactValue >> 26) & 0xFFF; double y = (compactValue >> 26) & 0xFFF;
double z = compactValue << 38 >> 38; // is normal? double z = compactValue << 38 >> 38; // is normal?
this.location = new Location(x, y, z); this.location = new BlockLocation((int)x, (int)y, (int)z); //FIXME
} }
} }
} }

View File

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

View File

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

View File

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

View File

@@ -9,31 +9,31 @@ import mc.core.world.block.BlockFactory;
import mc.core.world.block.BlockType; import mc.core.world.block.BlockType;
import mc.core.world.chunk.Chunk; import mc.core.world.chunk.Chunk;
import mc.core.world.chunk.ChunkSection; import mc.core.world.chunk.ChunkSection;
import org.junit.Before; import org.junit.jupiter.api.BeforeAll;
import org.junit.BeforeClass; import org.junit.jupiter.api.BeforeEach;
import org.junit.Test; import org.junit.jupiter.api.Test;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import static org.junit.Assert.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.Assert.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
public class TestChunkdataPacket { class ChunkdataPacketTest {
private static byte[] expectedPacketData; private static byte[] expectedPacketData;
private World world; private World world;
@BeforeClass @BeforeAll
public static void beforeClassTest() throws IOException { static void beforeClassTest() throws IOException {
InputStream inputStream = TestChunkdataPacket.class.getResourceAsStream("ChunkDataPacket.bin"); InputStream inputStream = ChunkdataPacketTest.class.getResourceAsStream("ChunkDataPacket.bin");
expectedPacketData = ByteStreams.toByteArray(inputStream); expectedPacketData = ByteStreams.toByteArray(inputStream);
} }
@Before @BeforeEach
public void prepareWorld() { void prepareWorld() {
final ChunkSection chunkSection = mock(ChunkSection.class); final ChunkSection chunkSection = mock(ChunkSection.class);
when(chunkSection.getSkyLight(anyInt(), anyInt(), anyInt())).thenAnswer(invocation -> { when(chunkSection.getSkyLight(anyInt(), anyInt(), anyInt())).thenAnswer(invocation -> {
int y = (int)invocation.getArguments()[1]; int y = (int)invocation.getArguments()[1];
@@ -77,7 +77,7 @@ public class TestChunkdataPacket {
} }
@Test @Test
public void test() { void writePacket() {
ChunkDataPacket packet = new ChunkDataPacket(); ChunkDataPacket packet = new ChunkDataPacket();
packet.setX(0); packet.setX(0);
packet.setZ(0); packet.setZ(0);

View File

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

View File

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

View File

@@ -1,4 +1,3 @@
group 'mc'
version '1.0-SNAPSHOT' version '1.0-SNAPSHOT'
ext { ext {

View File

@@ -0,0 +1,49 @@
package mc.core.network.proto_1_12_2.netty;
import lombok.Getter;
import lombok.Setter;
import mc.core.network.proto_1_12_2.packets.KeepAlivePacket;
import mc.core.player.PlayerManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Random;
@Component
public class KeepAliveThread extends Thread {
private static final Random RAND = new Random();
private final Object lock = new Object();
@Autowired
private PlayerManager playerManager;
@Setter
private int interval = 10;
public void notifyLock() {
synchronized (lock) {
lock.notify();
}
}
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
while(playerManager.getCountPlayers() == 0) {
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException e) {
return;
}
}
}
playerManager.getBroadcastChannel().writeAndFlush(new KeepAlivePacket(RAND.nextLong()));
try {
Thread.sleep(interval);
} catch (InterruptedException e) {
return;
}
}
}
}

View File

@@ -1,10 +1,7 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 2018-06-10
*/
package mc.core.network.proto_1_12_2.netty; package mc.core.network.proto_1_12_2.netty;
import io.netty.bootstrap.ServerBootstrap; import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup; import io.netty.channel.EventLoopGroup;
@@ -14,7 +11,7 @@ import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.util.AttributeKey; import io.netty.util.AttributeKey;
import lombok.Setter; import lombok.Setter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import mc.core.events.EventBusGetter; import mc.core.eventbus.EventBusGetter;
import mc.core.network.Server; import mc.core.network.Server;
import mc.core.network.StartServerException; import mc.core.network.StartServerException;
import mc.core.network.proto_1_12_2.State; 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 mc.core.player.Player;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import java.util.Map; import java.util.Map;
@Slf4j @Slf4j
@Component
public class NettyServer implements Server { public class NettyServer implements Server {
public static final AttributeKey<State> ATTR_STATE = AttributeKey.newInstance("ATTR_STATE"); public static final AttributeKey<State> ATTR_STATE = AttributeKey.newInstance("ATTR_STATE");
public static final AttributeKey<Player> ATTR_PLAYER = AttributeKey.newInstance("ATTR_PLAYER"); public static final AttributeKey<Player> ATTR_PLAYER = AttributeKey.newInstance("ATTR_PLAYER");
@Autowired @Autowired
private ApplicationContext applicationContext; private ApplicationContext context;
@Autowired
private KeepAliveThread keepAliveThread;
@Setter @Setter
private String host; private String host;
@Setter @Setter
@@ -44,7 +45,7 @@ public class NettyServer implements Server {
return new ChannelInitializer<SocketChannel>() { return new ChannelInitializer<SocketChannel>() {
@Override @Override
protected void initChannel(SocketChannel socketChannel) { protected void initChannel(SocketChannel socketChannel) {
Map<String, ChannelHandler> beans = applicationContext.getBeansOfType(ChannelHandler.class); Map<String, ChannelHandler> beans = context.getBeansOfType(ChannelHandler.class);
beans.forEach(socketChannel.pipeline()::addLast); beans.forEach(socketChannel.pipeline()::addLast);
} }
}; };
@@ -64,7 +65,7 @@ public class NettyServer implements Server {
public void start() throws StartServerException { public void start() throws StartServerException {
log.info("Use protocol {}", StatusResponsePacket.NAME); log.info("Use protocol {}", StatusResponsePacket.NAME);
EventBusGetter.INSTANCE.register(new PlayerEventListener()); EventBusGetter.getInstance().register(new PlayerEventListener());
bossGroup = new NioEventLoopGroup(1); bossGroup = new NioEventLoopGroup(1);
workerGroup = new NioEventLoopGroup(workerGroupCount); workerGroup = new NioEventLoopGroup(workerGroupCount);
@@ -73,7 +74,9 @@ public class NettyServer implements Server {
log.info("Start server: {}:{}", host, port); log.info("Start server: {}:{}", host, port);
try { 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) { } catch (InterruptedException e) {
throw new StartServerException(e); throw new StartServerException(e);
} }
@@ -82,6 +85,7 @@ public class NettyServer implements Server {
@Override @Override
public void stop() { public void stop() {
log.info("Server shutdown"); log.info("Server shutdown");
keepAliveThread.interrupt();
workerGroup.shutdownGracefully(); workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully(); bossGroup.shutdownGracefully();
} }

View File

@@ -1,12 +1,8 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 2018-06-10
*/
package mc.core.network.proto_1_12_2.netty; package mc.core.network.proto_1_12_2.netty;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder; import io.netty.handler.codec.ReplayingDecoder;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import mc.core.network.CSPacket; import mc.core.network.CSPacket;
import mc.core.network.NetInputStream; 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; import static mc.core.network.proto_1_12_2.netty.NettyServer.ATTR_STATE;
@Slf4j @Slf4j
public class PacketDecoder extends ByteToMessageDecoder { public class PacketDecoder extends ReplayingDecoder<CSPacket> {
private int[] countReadBytes = new int[]{0};
@Override @Override
public void channelActive(ChannelHandlerContext ctx) throws Exception { public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.channel().attr(ATTR_STATE).set(State.HANDSHAKE); ctx.channel().attr(ATTR_STATE).set(State.HANDSHAKE);
@@ -33,27 +31,25 @@ public class PacketDecoder extends ByteToMessageDecoder {
@Override @Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
log.debug("ByteBuf readableBytes: {}", in.readableBytes());
State state = ctx.channel().attr(ATTR_STATE).get(); State state = ctx.channel().attr(ATTR_STATE).get();
NetInputStream netStream = new WrapperNetInputStream(in); NetInputStream netStream = new WrapperNetInputStream(in);
int packetSize = netStream.readVarInt(); int packetSize = netStream.readVarInt();
log.debug("Packet size: {}", packetSize); 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(); String hexPacketId = Integer.toHexString(packetId).toUpperCase();
if (hexPacketId.length() == 1) hexPacketId = "0" + hexPacketId; if (hexPacketId.length() == 1) hexPacketId = "0" + hexPacketId;
log.debug("Packet id: 0x{}", hexPacketId); log.debug("Packet id: 0x{}", hexPacketId);
rb = rb - in.readableBytes(); leftDataPacket = leftDataPacket - countReadBytes[0];
Class<? extends CSPacket> packetClass = state.getClientSidePacket(packetId); Class<? extends CSPacket> packetClass = state.getClientSidePacket(packetId);
if (packetClass == null) { if (packetClass == null) {
log.warn("Unknown packet: {}:0x{}", state.name(), hexPacketId); log.warn("Unknown packet: {}:0x{}", state.name(), hexPacketId);
in.skipBytes(in.readableBytes()); in.skipBytes(leftDataPacket);
} else { } else {
netStream.setDataSize(packetSize - rb); netStream.setDataSize(leftDataPacket);
CSPacket packet = packetClass.newInstance(); CSPacket packet = packetClass.newInstance();
try { try {
packet.readSelf(netStream); packet.readSelf(netStream);
@@ -62,7 +58,7 @@ public class PacketDecoder extends ByteToMessageDecoder {
} catch (Exception e) { } catch (Exception e) {
log.warn("Known packet: {}:{}. But throw exception. See debug log.", state.name(), packet.getClass().getSimpleName()); log.warn("Known packet: {}:{}. But throw exception. See debug log.", state.name(), packet.getClass().getSimpleName());
log.debug("Read packet", e); log.debug("Read packet", e);
in.skipBytes(in.readableBytes()); in.skipBytes(leftDataPacket);
} }
} }
} }

View File

@@ -2,16 +2,15 @@ package mc.core.network.proto_1_12_2.netty;
import com.google.common.eventbus.Subscribe; import com.google.common.eventbus.Subscribe;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import mc.core.events.SC_ChunkLoadEvent; import mc.core.eventbus.events.SC_ChunkLoadEvent;
import mc.core.events.SC_ChunkUnloadEvent; import mc.core.eventbus.events.SC_ChunkUnloadEvent;
import mc.core.events.SC_PlayerMoveEvent; import mc.core.eventbus.events.SC_PlayerMoveEvent;
import mc.core.network.proto_1_12_2.TeleportManager; 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.ChunkDataPacket;
import mc.core.network.proto_1_12_2.packets.PlayerPositionAndLookPacket; import mc.core.network.proto_1_12_2.packets.PlayerPositionAndLookPacket;
import mc.core.network.proto_1_12_2.packets.UnloadChunkPacket; import mc.core.network.proto_1_12_2.packets.UnloadChunkPacket;
import mc.core.utils.CompactedCoords; import mc.core.utils.CompactedCoords;
import mc.core.world.chunk.Chunk; import mc.core.world.chunk.Chunk;
import mc.core.world.chunk.ChunkSection;
@Slf4j @Slf4j
class PlayerEventListener { class PlayerEventListener {
@@ -30,7 +29,7 @@ class PlayerEventListener {
public void playerChunkLoadHandler(SC_ChunkLoadEvent event) { public void playerChunkLoadHandler(SC_ChunkLoadEvent event) {
for(Integer compressXZ : event.getNeedLoadChunks()) { for(Integer compressXZ : event.getNeedLoadChunks()) {
int[] xz = CompactedCoords.uncompressXZ(compressXZ); 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(); ChunkDataPacket packet = new ChunkDataPacket();
packet.setX(xz[0]); packet.setX(xz[0]);

View File

@@ -1,15 +1,12 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 2018-06-23
*/
package mc.core.network.proto_1_12_2.netty.handlers; package mc.core.network.proto_1_12_2.netty.handlers;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelFutureListener;
import mc.core.events.CS_PlayerMoveEvent; import mc.core.eventbus.events.CS_PlayerMoveEvent;
import mc.core.events.EventBusGetter; import mc.core.eventbus.EventBusGetter;
import mc.core.network.proto_1_12_2.State; 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.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.netty.wrappers.WrapperNetChannel;
import mc.core.network.proto_1_12_2.packets.*; import mc.core.network.proto_1_12_2.packets.*;
import mc.core.player.Player; import mc.core.player.Player;
@@ -20,12 +17,10 @@ import mc.core.text.TextColor;
import mc.core.text.TextStyle; import mc.core.text.TextStyle;
import mc.core.utils.CompactedCoords; import mc.core.utils.CompactedCoords;
import mc.core.world.World; import mc.core.world.World;
import mc.core.world.chunk.Chunk;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; 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_PLAYER;
import static mc.core.network.proto_1_12_2.netty.NettyServer.ATTR_STATE; 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 @Autowired
private PlayerManager playerManager; private PlayerManager playerManager;
@Autowired @Autowired
private KeepAliveThread keepAliveThread;
@Autowired
private World world; private World world;
@Handler @Handler
public void onLoginStart(Channel channel, LoginStartPacket packet) { public void onLoginStart(Channel channel, LoginStartPacket packet) {
Optional<Player> optPlayer = playerManager.getPlayer(packet.getPlayerName()); Player player = playerManager.getPlayer(packet.getPlayerName());
if (optPlayer.isPresent() && optPlayer.get().isOnline()) { if (player != null) {
channel.writeAndFlush(new DisconnectPacket( channel.writeAndFlush(new DisconnectPacket(
Text.builder("Player \"") Text.builder("Player \"")
.append(Text.of(packet.getPlayerName(), TextColor.YELLOW)) .append(Text.of(packet.getPlayerName(), TextColor.YELLOW))
@@ -47,23 +44,35 @@ public class LoginHandler extends AbstractStateHandler implements LoginStateHand
.build())) .build()))
.addListener(ChannelFutureListener.CLOSE); .addListener(ChannelFutureListener.CLOSE);
} else { } else {
Player player = playerManager.getPlayer(packet.getPlayerName()) player = playerManager.getOfflinePlayer(packet.getPlayerName());
.orElseGet(() -> playerManager.createPlayer(
if (player == null) {
player = playerManager.createPlayer(
packet.getPlayerName(), packet.getPlayerName(),
world.getSpawn())); 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( channel.writeAndFlush(new LoginSuccessPacket(
player.getUUID(), player.getUuid(),
packet.getPlayerName())); packet.getPlayerName()));
channel.attr(ATTR_PLAYER).set(player); channel.attr(ATTR_PLAYER).set(player);
channel.attr(ATTR_STATE).set(State.PLAY); channel.attr(ATTR_STATE).set(State.PLAY);
// Join Game // Join Game
JoinGamePacket pkt1 = new JoinGamePacket(); JoinGamePacket pkt1 = new JoinGamePacket();
pkt1.setEntityId(player.getId()); pkt1.setEntityId(player.getId()); //TODO отделить системный ID от EntityID
pkt1.setMode(PlayerMode.CREATIVE); pkt1.setMode(PlayerMode.CREATIVE); //TODO перенести в Config
pkt1.setDimension(0/*Overworld*/); pkt1.setDimension(0/*Overworld*/); //TODO перенести в World
pkt1.setDifficulty(0/*Peaceful*/); pkt1.setDifficulty(0/*Peaceful*/); //TODO перенести в Config
pkt1.setLevelType(world.getWorldType().getName()); pkt1.setLevelType(world.getWorldType().getName());
channel.write(pkt1); channel.write(pkt1);
@@ -73,21 +82,25 @@ public class LoginHandler extends AbstractStateHandler implements LoginStateHand
channel.write(pkt2); channel.write(pkt2);
// Player Abilities // Player Abilities
PlayerAbilitiesPacket pkt3 = new PlayerAbilitiesPacket(); PlayerAbilitiesPacket pkt3 = new PlayerAbilitiesPacket(); //TODO перенести в Player
pkt3.setCanFly(true); pkt3.setCanFly(true);
pkt3.setFlying(true); pkt3.setFlying(true);
pkt3.setGodMode(true); pkt3.setGodMode(true);
pkt3.setInstantDestroyBlocks(true); pkt3.setInstantDestroyBlocks(true);
channel.write(pkt3); channel.write(pkt3);
channel.flush(); channel.flush();
// First Chunk // First Chunk
//TODO необходимо отправлять больше начальных чанков
ChunkDataPacket pkt8 = new ChunkDataPacket(); ChunkDataPacket pkt8 = new ChunkDataPacket();
pkt8.setX(player.getLocation().getChunk().getX()); Chunk chunk = player.getWorld().getChunk(player.getLocation());
pkt8.setZ(player.getLocation().getChunk().getZ()); pkt8.setX(chunk.getX());
pkt8.setChunk(player.getLocation().getChunk()); pkt8.setZ(chunk.getZ());
pkt8.setChunk(chunk);
pkt8.setInitChunk(true); pkt8.setInitChunk(true);
channel.writeAndFlush(pkt8); channel.writeAndFlush(pkt8);
player.getLoadedChunks().add(CompactedCoords.compressXZ(0, 0)); player.getLoadedChunks().add(CompactedCoords.compressXZ(0, 0));
// Player Position And Look // Player Position And Look
@@ -99,10 +112,11 @@ public class LoginHandler extends AbstractStateHandler implements LoginStateHand
player.setChannel(new WrapperNetChannel(channel)); player.setChannel(new WrapperNetChannel(channel));
// Send <Tab> items // Send <Tab> items
//TODO обновление должно приходить всем игрокам на сервере
PlayerListItemPacket pkt5 = new PlayerListItemPacket(); PlayerListItemPacket pkt5 = new PlayerListItemPacket();
pkt5.setAction(PlayerListItemPacket.Action.ADD_PLAYER); pkt5.setAction(PlayerListItemPacket.Action.ADD_PLAYER);
PlayerListItemPacket.PlayerData playerData = new PlayerListItemPacket.PlayerData(); PlayerListItemPacket.PlayerData playerData = new PlayerListItemPacket.PlayerData();
playerData.setUuid(player.getUUID()); playerData.setUuid(player.getUuid());
playerData.setName(player.getName()); playerData.setName(player.getName());
playerData.setGameMode(PlayerMode.CREATIVE); playerData.setGameMode(PlayerMode.CREATIVE);
playerData.setPing(0); playerData.setPing(0);
@@ -115,32 +129,13 @@ public class LoginHandler extends AbstractStateHandler implements LoginStateHand
pkt5.getListPlayers().add(playerData); pkt5.getListPlayers().add(playerData);
channel.writeAndFlush(pkt5); channel.writeAndFlush(pkt5);
// Send header/footer <Tab> 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); playerManager.joinServer(player);
keepAliveThread.notifyLock();
CS_PlayerMoveEvent event = new CS_PlayerMoveEvent(player, player.getLocation()); CS_PlayerMoveEvent event = new CS_PlayerMoveEvent(player, player.getLocation());
event.setNewLocation(player.getLocation()); event.setNewLocation(player.getLocation());
event.setRecalcChunk(true); event.setRecalcChunk(true);
EventBusGetter.INSTANCE.post(event); EventBusGetter.getInstance().post(event);
} }
} }
} }

View File

@@ -8,8 +8,8 @@ import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import mc.core.EntityLocation; import mc.core.EntityLocation;
import mc.core.chat.ChatProcessor; import mc.core.chat.ChatProcessor;
import mc.core.events.CS_PlayerMoveEvent; import mc.core.eventbus.events.CS_PlayerMoveEvent;
import mc.core.events.EventBusGetter; import mc.core.eventbus.EventBusGetter;
import mc.core.network.proto_1_12_2.TeleportManager; import mc.core.network.proto_1_12_2.TeleportManager;
import mc.core.network.proto_1_12_2.packets.*; import mc.core.network.proto_1_12_2.packets.*;
import mc.core.player.Player; import mc.core.player.Player;
@@ -55,8 +55,7 @@ public class PlayHandler extends AbstractStateHandler implements PlayStateHandle
@Handler @Handler
public void onPositionAndLook(Channel channel, PlayerPositionAndLookPacket packet) { public void onPositionAndLook(Channel channel, PlayerPositionAndLookPacket packet) {
Player player = channel.attr(ATTR_PLAYER).get(); Player player = channel.attr(ATTR_PLAYER).get();
player.getLocation().setXYZ(packet.getLocation()); player.getLocation().set(packet.getLocation());
player.getLocation().setYawPitch(packet.getLocation());
} }
@Handler @Handler
@@ -82,10 +81,9 @@ public class PlayHandler extends AbstractStateHandler implements PlayStateHandle
event.setNewLocation(new EntityLocation( event.setNewLocation(new EntityLocation(
packet.getX(), packet.getY(), packet.getZ(), packet.getX(), packet.getY(), packet.getZ(),
player.getLocation().getYaw(), player.getLocation().getYaw(),
player.getLocation().getPitch(), player.getLocation().getPitch()
player.getLocation().getWorld()
)); ));
EventBusGetter.INSTANCE.post(event); EventBusGetter.getInstance().post(event);
} }
@Handler @Handler

View File

@@ -26,7 +26,7 @@ public class StatusHandler extends AbstractStateHandler implements StatusStateHa
responsePacket.setMaxOnline(config.getMaxPlayers()); responsePacket.setMaxOnline(config.getMaxPlayers());
responsePacket.setDescription(config.getDescriptionServer()); responsePacket.setDescription(config.getDescriptionServer());
responsePacket.setFaviconBase64(config.getFaviconBase64()); responsePacket.setFaviconBase64(config.getFaviconBase64());
responsePacket.setOnline(playerManager.getCountOnlinePlayers()); responsePacket.setOnline(playerManager.getCountPlayers());
channel.writeAndFlush(responsePacket); channel.writeAndFlush(responsePacket);
} }

View File

@@ -20,14 +20,8 @@ import java.util.Random;
@RequiredArgsConstructor @RequiredArgsConstructor
public class WrapperNetChannel implements NetChannel { public class WrapperNetChannel implements NetChannel {
private static final Random RAND = new Random();
private final Channel channel; private final Channel channel;
@Override
public void sendKeepAlive() {
writeAndFlush(new KeepAlivePacket(RAND.nextLong()));
}
@Override @Override
public void sendTimeUpdate(long time, long age) { public void sendTimeUpdate(long time, long age) {
writeAndFlush(new TimeUpdatePacket(time, age)); writeAndFlush(new TimeUpdatePacket(time, age));

View File

@@ -2,6 +2,7 @@ rootProject.name = 'mc-server'
include('core') // Core include('core') // Core
include('simple_world') include('simple_world')
include('h2_playermanager')
include('vanilla_commands') include('vanilla_commands')
include('proto_1.12.2') // Protocol 1.12.2 include('proto_1.12.2') // Protocol 1.12.2
include('proto_1.12.2_netty') // Protocol 1.12.2 (Netty impl.) include('proto_1.12.2_netty') // Protocol 1.12.2 (Netty impl.)

View File

@@ -1,4 +1,3 @@
group 'mc'
version '1.0-SNAPSHOT' version '1.0-SNAPSHOT'
dependencies { dependencies {

View File

@@ -20,6 +20,8 @@ import java.util.List;
@Slf4j @Slf4j
public class SimpleWorld implements World { public class SimpleWorld implements World {
@Getter
private final String name = "flat";
@Getter @Getter
private final WorldType worldType = WorldType.FLAT; private final WorldType worldType = WorldType.FLAT;
private EntityLocation spawn; private EntityLocation spawn;
@@ -38,7 +40,7 @@ public class SimpleWorld implements World {
@Override @Override
public void setSpawn(double x, double y, double z, float yaw, float pitch) { public void setSpawn(double x, double y, double z, float yaw, float pitch) {
this.spawn = new EntityLocation(x, y, z, yaw, pitch, this); this.spawn = new EntityLocation(x, y, z, yaw, pitch);
} }
@Override @Override

View File

@@ -1,4 +1,3 @@
group 'mc'
version '1.0-SNAPSHOT' version '1.0-SNAPSHOT'
dependencies { dependencies {

View File

@@ -53,7 +53,7 @@ public class ListCommand implements CommandExecutor {
playerManager.getPlayers().forEach(pl -> sj.add(pl.getName())); playerManager.getPlayers().forEach(pl -> sj.add(pl.getName()));
Text message = messageFormat.apply( Text message = messageFormat.apply(
"count", playerManager.getCountOnlinePlayers(), "count", playerManager.getCountPlayers(),
"players", sj.toString()); "players", sj.toString());
sender.getChannel().sendChatMessage(message, MessageType.SYSTEM_MESSAGE); sender.getChannel().sendChatMessage(message, MessageType.SYSTEM_MESSAGE);
} }