Merge branch 'proto_1.12.2' into world-loader-anvil
This commit is contained in:
@@ -4,10 +4,4 @@ version '1.0-SNAPSHOT'
|
|||||||
dependencies {
|
dependencies {
|
||||||
/* Core */
|
/* Core */
|
||||||
compile_excludeCopy project(':core')
|
compile_excludeCopy project(':core')
|
||||||
|
|
||||||
/* Simple log */
|
|
||||||
compile_excludeCopy (group: 'org.slf4j', name: 'slf4j-simple', version: slf4j_version)
|
|
||||||
|
|
||||||
/* Named Binary Tags */
|
|
||||||
compile (group: 'com.flowpowered', name: 'flow-nbt', version: '1.0.0')
|
|
||||||
}
|
}
|
||||||
34
build.gradle
34
build.gradle
@@ -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,15 +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'
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Components */
|
/* Lombok */
|
||||||
compile (group: 'org.projectlombok', name: 'lombok', version: '1.16.16')
|
annotationProcessor (group: 'org.projectlombok', name: 'lombok', version: lombok_version)
|
||||||
|
compileOnly (group: 'org.projectlombok', name: 'lombok', version: lombok_version)
|
||||||
|
|
||||||
/* Test */
|
/* Testing */
|
||||||
testCompile (group: 'junit', name: 'junit', version: '4.12')
|
testImplementation (group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: junit_version)
|
||||||
|
testRuntimeOnly(group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: junit_version)
|
||||||
|
testCompile (group: 'org.slf4j', name: 'slf4j-simple', version: slf4j_version)
|
||||||
|
testCompile (group: 'org.mockito', name: 'mockito-core', version: '1.10.19')
|
||||||
|
testCompile (group: 'org.springframework', name: 'spring-test', version: spring_version)
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
useJUnitPlatform()
|
||||||
}
|
}
|
||||||
|
|
||||||
task copyDep(type: Copy) {
|
task copyDep(type: Copy) {
|
||||||
@@ -63,7 +77,7 @@ task runApp(type: JavaExec) {
|
|||||||
|
|
||||||
/* Uncomment, if you used VM args */
|
/* Uncomment, if you used VM args */
|
||||||
//jvmArgs = [
|
//jvmArgs = [
|
||||||
// "-DspringConfig=spring-flat.xml",
|
// "-DspringConfig=spring.xml",
|
||||||
// "-Dlog4j.configurationFile=log4j2.xml"
|
// "-Dlog4j.configurationFile=log4j2.xml"
|
||||||
//]
|
//]
|
||||||
|
|
||||||
|
|||||||
@@ -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`
|
||||||
|
|||||||
@@ -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')
|
||||||
}
|
}
|
||||||
|
|||||||
85
core/src/main/java/mc/core/CoreEventListener.java
Normal file
85
core/src/main/java/mc/core/CoreEventListener.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,21 +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.world.World;
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
public class EntityLocation extends Location implements Cloneable {
|
@NoArgsConstructor
|
||||||
@Getter
|
@AllArgsConstructor
|
||||||
@Setter
|
@Data
|
||||||
|
public class EntityLocation implements Cloneable {
|
||||||
|
private double x, y, z;
|
||||||
private float yaw, pitch;
|
private float yaw, pitch;
|
||||||
|
|
||||||
public EntityLocation(double x, double y, double z, float yaw, float pitch, World world) {
|
public static EntityLocation ZERO() {
|
||||||
super(x, y, z, world);
|
return new EntityLocation(0d,0d,0d,0f,0f);
|
||||||
setYawPitch(yaw, pitch);
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
@@ -23,12 +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 int getBlockY() {
|
||||||
|
return Double.valueOf(Math.floor(y)).intValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBlockZ() {
|
||||||
|
return Double.valueOf(Math.floor(z)).intValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public EntityLocation clone() {
|
public EntityLocation clone() {
|
||||||
return (EntityLocation) super.clone();
|
try {
|
||||||
|
return (EntityLocation) super.clone();
|
||||||
|
} catch (CloneNotSupportedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,17 +4,10 @@
|
|||||||
*/
|
*/
|
||||||
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.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;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@@ -45,59 +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()");
|
|
||||||
event.getPlayer().getLocation().setXYZ(event.getNewLocation());
|
|
||||||
|
|
||||||
Chunk chunk = event.getNewLocation().getChunk(); // Next chunk
|
|
||||||
int ncX = chunk.getX();
|
|
||||||
int ncZ = chunk.getZ();
|
|
||||||
chunk = event.getPlayer().getLocation().getChunk(); // Current chunk
|
|
||||||
int ccX = chunk.getX();
|
|
||||||
int ccZ = chunk.getZ();
|
|
||||||
|
|
||||||
if (event.isRecalcChunk() || (ncX != ccX && ncZ != ccZ)) {
|
|
||||||
final int viewDistance = event.getPlayer().getSettings().getViewDistance();
|
|
||||||
int cMinX = chunk.getX() - viewDistance;
|
|
||||||
int cMaxX = chunk.getX() + viewDistance;
|
|
||||||
int cMinZ = chunk.getZ() - viewDistance;
|
|
||||||
int cMaxZ = chunk.getZ() + viewDistance;
|
|
||||||
|
|
||||||
SC_ChunkLoadEvent eventChunkLoad = new SC_ChunkLoadEvent(event.getPlayer());
|
|
||||||
for (int cZ = cMinZ; cZ <= cMaxZ; cZ++) {
|
|
||||||
for (int cX = cMinX; cX <= cMaxX; cX++) {
|
|
||||||
int compressXZ = CompactedCoords.compressXZ(cX, cZ);
|
|
||||||
if (!event.getPlayer().getLoadedChunks().contains(compressXZ)) {
|
|
||||||
if (!event.getPlayer().getLoadedChunks().contains(compressXZ)) {
|
|
||||||
eventChunkLoad.getNeedLoadChunks().add(compressXZ);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!eventChunkLoad.getNeedLoadChunks().isEmpty()) {
|
|
||||||
EventBusGetter.INSTANCE.post(eventChunkLoad);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO отсылать клиенту только(!) для корректировки позиции
|
|
||||||
// SC_PlayerMoveEvent nextEvent = new SC_PlayerMoveEvent(event.getPlayer());
|
|
||||||
// nextEvent.setNewLocation(event.getNewLocation());
|
|
||||||
// EventBusGetter.INSTANCE.post(nextEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@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()
|
||||||
|
|||||||
57
core/src/main/java/mc/core/ImmutableEntityLocation.java
Normal file
57
core/src/main/java/mc/core/ImmutableEntityLocation.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
/*
|
|
||||||
* DmitriyMX <dimon550@gmail.com>
|
|
||||||
* 2018-08-08
|
|
||||||
*/
|
|
||||||
package mc.core;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
import mc.core.exception.ResourceUnloadedException;
|
|
||||||
import mc.core.world.World;
|
|
||||||
import mc.core.world.chunk.Chunk;
|
|
||||||
import mc.core.world.chunk.ChunkSection;
|
|
||||||
|
|
||||||
import java.lang.ref.Reference;
|
|
||||||
import java.lang.ref.WeakReference;
|
|
||||||
|
|
||||||
public class Location implements Cloneable {
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private double x, y, z;
|
|
||||||
private Reference<World> refWorld;
|
|
||||||
|
|
||||||
public Location (double x, double y, double z, World world) {
|
|
||||||
setXYZ(x, y, z);
|
|
||||||
setWorld(world);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setXYZ(double x, double y, double z) {
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
this.z = z;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setXYZ(Location location) {
|
|
||||||
setXYZ(location.x, location.y, location.z);
|
|
||||||
}
|
|
||||||
|
|
||||||
public World getWorld() {
|
|
||||||
if (refWorld == null) {
|
|
||||||
return null;
|
|
||||||
} else if (refWorld.get() == null) {
|
|
||||||
throw new ResourceUnloadedException("World unloaded");
|
|
||||||
} else {
|
|
||||||
return refWorld.get();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setWorld (World world) {
|
|
||||||
this.refWorld = new WeakReference<>(world);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getBlockX() {
|
|
||||||
return (int) x;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getBlockY() {
|
|
||||||
return (int) y;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getBlockZ() {
|
|
||||||
return (int) z;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Chunk getChunk() {
|
|
||||||
World world = getWorld();
|
|
||||||
if (world == null) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return world.getChunk((int)(x / 16), (int)(z / 16));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ChunkSection getChunkSection() {
|
|
||||||
Chunk chunk = getChunk();
|
|
||||||
if (chunk == null) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return chunk.getChunkSection((int)(y / 16));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Location clone() {
|
|
||||||
try {
|
|
||||||
return (Location) super.clone();
|
|
||||||
} catch (CloneNotSupportedException e) { // такое в нашем случае вообще возможно?
|
|
||||||
e.printStackTrace();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,18 +8,13 @@ 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;
|
||||||
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 +44,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 +57,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 +67,7 @@ public class FakePlayerManager implements PlayerManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getCountOnlinePlayers() {
|
public int getCountPlayers() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,4 +75,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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
@@ -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;
|
||||||
@@ -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() {
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package mc.core.eventbus.events;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import mc.core.eventbus.EventBase;
|
||||||
|
import mc.core.player.Player;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SC_ChunkUnloadEvent extends EventBase {
|
||||||
|
@Getter
|
||||||
|
private final Player player;
|
||||||
|
@Getter
|
||||||
|
private List<Integer> needUnloadChunks = new ArrayList<>();
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
@@ -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;
|
||||||
@@ -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));
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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) {
|
||||||
this.children.add(child);
|
if (child != null) {
|
||||||
|
this.children.add(child);
|
||||||
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,16 +156,20 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Text(
|
if (children.size() == 1 && children.get(0) != null) {
|
||||||
content,
|
return children.get(0);
|
||||||
color,
|
} else {
|
||||||
style,
|
return new Text(
|
||||||
ImmutableList.copyOf(children)
|
content,
|
||||||
);
|
color,
|
||||||
|
style,
|
||||||
|
ImmutableList.copyOf(children)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,4 +20,9 @@ public class CompactedCoords {
|
|||||||
(int)(short) (compactValue | 0xFFFF0000)
|
(int)(short) (compactValue | 0xFFFF0000)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int floor_double(double value) {
|
||||||
|
int i = (int)value;
|
||||||
|
return value < (double)i ? i - 1 : i;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,14 +5,30 @@
|
|||||||
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();
|
||||||
void setSpawn(EntityLocation location);
|
void setSpawn(EntityLocation location);
|
||||||
|
default void setSpawn(double x, double y, double z, float yaw, float pitch) {
|
||||||
|
setSpawn(new EntityLocation(x, y, z, yaw, pitch));
|
||||||
|
}
|
||||||
|
default void setSpawn(double x, double y, double z) {
|
||||||
|
setSpawn(x, y, z, 0f, 0f);
|
||||||
|
}
|
||||||
|
|
||||||
Chunk getChunk(int x, int z);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,16 @@
|
|||||||
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, World world) {
|
public Block create(BlockType blockType, int x, int y, int z) {
|
||||||
return new EmbeddedBlock(blockType, x, y, z, world);
|
return new EmbeddedBlock(blockType, x, y, z);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** For first-time generation */
|
/** For first-time generation */
|
||||||
private class EmbeddedBlock extends AbstractBlock {
|
private class EmbeddedBlock extends AbstractBlock {
|
||||||
EmbeddedBlock(BlockType type, int x, int y, int z, World world) {
|
EmbeddedBlock(BlockType type, int x, int y, int z) {
|
||||||
super(type);
|
super(type);
|
||||||
setLocation(new Location(x,y,z, world));
|
setLocation(new BlockLocation(x, y, z));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
32
core/src/main/java/mc/core/world/block/BlockLocation.java
Normal file
32
core/src/main/java/mc/core/world/block/BlockLocation.java
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package mc.core.world.block;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Data
|
||||||
|
public class BlockLocation implements Cloneable {
|
||||||
|
private int x, y, z;
|
||||||
|
|
||||||
|
public static BlockLocation ZERO() {
|
||||||
|
return new BlockLocation(0,0,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setXYZ(int x, int y, int z) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.z = z;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BlockLocation clone() {
|
||||||
|
try {
|
||||||
|
return (BlockLocation) super.clone();
|
||||||
|
} catch (CloneNotSupportedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package mc.core.world.chunk;
|
package mc.core.world.chunk;
|
||||||
|
|
||||||
import mc.core.world.Biome;
|
import mc.core.world.Biome;
|
||||||
import mc.core.world.World;
|
|
||||||
|
|
||||||
public interface Chunk {
|
public interface Chunk {
|
||||||
int getX();
|
int getX();
|
||||||
@@ -12,7 +11,4 @@ public interface Chunk {
|
|||||||
|
|
||||||
Biome getBiome(int localX, int localZ);
|
Biome getBiome(int localX, int localZ);
|
||||||
void setBiome(int localX, int localZ, Biome biome);
|
void setBiome(int localX, int localZ, Biome biome);
|
||||||
|
|
||||||
World getWorld();
|
|
||||||
void setWorld(World world);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package mc.core.world.chunk;
|
||||||
|
|
||||||
|
public interface ChunkProvider {
|
||||||
|
Chunk getChunk(int x , int z);
|
||||||
|
|
||||||
|
void saveChunk(Chunk chunk);
|
||||||
|
void saveChunk(Chunk... chunks);
|
||||||
|
}
|
||||||
@@ -5,7 +5,6 @@
|
|||||||
package mc.core.world.chunk;
|
package mc.core.world.chunk;
|
||||||
|
|
||||||
import mc.core.world.Biome;
|
import mc.core.world.Biome;
|
||||||
import mc.core.world.World;
|
|
||||||
import mc.core.world.block.Block;
|
import mc.core.world.block.Block;
|
||||||
|
|
||||||
/* 16x16x16 */
|
/* 16x16x16 */
|
||||||
@@ -24,6 +23,4 @@ public interface ChunkSection {
|
|||||||
void setAddition(int x, int y, int z, int value);
|
void setAddition(int x, int y, int z, int value);
|
||||||
|
|
||||||
Biome getBiome(int localX, int localZ);
|
Biome getBiome(int localX, int localZ);
|
||||||
|
|
||||||
World getWorld();
|
|
||||||
}
|
}
|
||||||
|
|||||||
91
core/src/test/java/mc/core/EntityLocationTest.java
Normal file
91
core/src/test/java/mc/core/EntityLocationTest.java
Normal 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.*;
|
||||||
|
|
||||||
|
class EntityLocationTest {
|
||||||
|
private static final ThreadLocalRandom rnd = ThreadLocalRandom.current();
|
||||||
|
private static final double minD = 0.0d, maxD = 10.0d;
|
||||||
|
private static final float minF = 0.0f, maxF = 359.9f;
|
||||||
|
private double x, y, z;
|
||||||
|
private float yaw, pitch;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void before() {
|
||||||
|
x = rnd.nextDouble(minD, maxD);
|
||||||
|
y = rnd.nextDouble(minD, maxD);
|
||||||
|
z = rnd.nextDouble(minD, maxD);
|
||||||
|
yaw = rnd.nextFloat() * (maxF - minF) + minF;
|
||||||
|
pitch = rnd.nextFloat() * (maxF - minF) + minF;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void equals_() {
|
||||||
|
EntityLocation loc1 = new EntityLocation(x, y, z, yaw, pitch);
|
||||||
|
EntityLocation loc2 = new EntityLocation(x, y, z, yaw, pitch);
|
||||||
|
assertEquals(loc1, loc2);
|
||||||
|
|
||||||
|
loc2 = new EntityLocation(x+1, y+2, z-3, yaw, pitch);
|
||||||
|
assertNotEquals(loc1, loc2);
|
||||||
|
|
||||||
|
loc2 = new EntityLocation(x, y, z, yaw-1, pitch+2);
|
||||||
|
assertNotEquals(loc1, loc2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void clone_() {
|
||||||
|
EntityLocation locOrig = new EntityLocation(x, y, z, yaw, pitch);
|
||||||
|
EntityLocation locClone = locOrig.clone();
|
||||||
|
assertEquals(locOrig, locClone);
|
||||||
|
assertNotSame(locOrig, locClone);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getBlockXZ() {
|
||||||
|
EntityLocation location;
|
||||||
|
|
||||||
|
location = new EntityLocation(0d, 0, 0d, 0f, 0f);
|
||||||
|
assertEquals(0, location.getBlockX());
|
||||||
|
assertEquals(0, location.getBlockZ());
|
||||||
|
|
||||||
|
location.setXYZ(0.1d, 0, 0.1d);
|
||||||
|
assertEquals(0, location.getBlockX());
|
||||||
|
assertEquals(0, location.getBlockZ());
|
||||||
|
|
||||||
|
location.setXYZ(0.5d, 0, 0.5d);
|
||||||
|
assertEquals(0, location.getBlockX());
|
||||||
|
assertEquals(0, location.getBlockZ());
|
||||||
|
|
||||||
|
location.setXYZ(0.9d, 0, 0.9d);
|
||||||
|
assertEquals(0, location.getBlockX());
|
||||||
|
assertEquals(0, location.getBlockZ());
|
||||||
|
|
||||||
|
location.setXYZ(1d, 0, 1d);
|
||||||
|
assertEquals(1, location.getBlockX());
|
||||||
|
assertEquals(1, location.getBlockZ());
|
||||||
|
|
||||||
|
location.setXYZ(-0.1d, 0, -0.1d);
|
||||||
|
assertEquals(-1, location.getBlockX());
|
||||||
|
assertEquals(-1, location.getBlockZ());
|
||||||
|
|
||||||
|
location.setXYZ(-0.5d, 0, -0.5d);
|
||||||
|
assertEquals(-1, location.getBlockX());
|
||||||
|
assertEquals(-1, location.getBlockZ());
|
||||||
|
|
||||||
|
location.setXYZ(-0.9d, 0, -0.9d);
|
||||||
|
assertEquals(-1, location.getBlockX());
|
||||||
|
assertEquals(-1, location.getBlockZ());
|
||||||
|
|
||||||
|
location.setXYZ(-1d, 0, -1d);
|
||||||
|
assertEquals(-1, location.getBlockX());
|
||||||
|
assertEquals(-1, location.getBlockZ());
|
||||||
|
|
||||||
|
location.setXYZ(-1.1d, 0, -1.1d);
|
||||||
|
assertEquals(-2, location.getBlockX());
|
||||||
|
assertEquals(-2, location.getBlockZ());
|
||||||
|
}
|
||||||
|
}
|
||||||
33
core/src/test/java/mc/core/ImmutableEntityLocationTest.java
Normal file
33
core/src/test/java/mc/core/ImmutableEntityLocationTest.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
package mc.core;
|
|
||||||
|
|
||||||
import mc.core.world.World;
|
|
||||||
import mc.core.world.WorldType;
|
|
||||||
import mc.core.world.chunk.Chunk;
|
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
public class TestEntityLocation {
|
|
||||||
@Test
|
|
||||||
public void cloneTest() {
|
|
||||||
World dummyWorld = new World() {
|
|
||||||
@Override
|
|
||||||
public WorldType getWorldType() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public EntityLocation getSpawn() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setSpawn(EntityLocation location) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Chunk getChunk(int x, int z) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setChunk(int x, int z, Chunk chunk) {
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
EntityLocation firstLocation = new EntityLocation(10, 20, 30, 40, 50, dummyWorld);
|
|
||||||
Assert.assertSame("Lost world reference before cloning", dummyWorld, firstLocation.getWorld());
|
|
||||||
EntityLocation locationClone = firstLocation.clone();
|
|
||||||
|
|
||||||
Assert.assertEquals("X mismatch", firstLocation.getX(), locationClone.getX(), 0);
|
|
||||||
Assert.assertEquals("Y mismatch", firstLocation.getY(), locationClone.getY(), 0);
|
|
||||||
Assert.assertEquals("Z mismatch", firstLocation.getZ(), locationClone.getZ(), 0);
|
|
||||||
Assert.assertEquals("Pitch mismatch", firstLocation.getPitch(), locationClone.getPitch(), 0);
|
|
||||||
Assert.assertEquals("Yaw mismatch", firstLocation.getYaw(), locationClone.getYaw(), 0);
|
|
||||||
Assert.assertSame("World mismatch (accidental clone of the World object?)", firstLocation.getWorld(), locationClone.getWorld());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
34
core/src/test/java/mc/core/TestSpringConfig.java
Normal file
34
core/src/test/java/mc/core/TestSpringConfig.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
76
core/src/test/java/mc/core/text/TextTest.java
Normal file
76
core/src/test/java/mc/core/text/TextTest.java
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
25
core/src/test/java/mc/core/utils/CompactedCoordsTest.java
Normal file
25
core/src/test/java/mc/core/utils/CompactedCoordsTest.java
Normal 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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
package mc.core.utils;
|
|
||||||
|
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
|
|
||||||
public class TestCompactedCoords {
|
|
||||||
@Test
|
|
||||||
public void testSimple() {
|
|
||||||
for (int z = -100; z <= 100; z++) {
|
|
||||||
for (int x = -100; x <= 100; x++) {
|
|
||||||
int compressXZ = CompactedCoords.compressXZ(x, z);
|
|
||||||
int[] xz = CompactedCoords.uncompressXZ(compressXZ);
|
|
||||||
|
|
||||||
Assert.assertEquals(x, xz[0]);
|
|
||||||
Assert.assertEquals(z, xz[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRandom() {
|
|
||||||
Random random = new Random();
|
|
||||||
int x,z;
|
|
||||||
|
|
||||||
for (int i = 0; i < 100; i++) {
|
|
||||||
do {
|
|
||||||
x = random.nextInt();
|
|
||||||
} while (x < Short.MIN_VALUE || x > Short.MAX_VALUE);
|
|
||||||
|
|
||||||
do {
|
|
||||||
z = random.nextInt();
|
|
||||||
} while (z < Short.MIN_VALUE || z > Short.MAX_VALUE);
|
|
||||||
|
|
||||||
|
|
||||||
int compressXZ = CompactedCoords.compressXZ(x, z);
|
|
||||||
int[] xz = CompactedCoords.uncompressXZ(compressXZ);
|
|
||||||
|
|
||||||
Assert.assertEquals(x, xz[0]);
|
|
||||||
Assert.assertEquals(z, xz[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
/*
|
|
||||||
* DmitriyMX <dimon550@gmail.com>
|
|
||||||
* 2018-04-28
|
|
||||||
*/
|
|
||||||
package mc.world.flat;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import mc.core.EntityLocation;
|
|
||||||
import mc.core.world.World;
|
|
||||||
import mc.core.world.WorldType;
|
|
||||||
import mc.core.world.block.BlockType;
|
|
||||||
import mc.core.world.chunk.Chunk;
|
|
||||||
import mc.core.world.chunk.ChunkSection;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
public class FlatWorld implements World {
|
|
||||||
@Getter
|
|
||||||
private final WorldType worldType = WorldType.FLAT;
|
|
||||||
private EntityLocation spawn;
|
|
||||||
private ChunkSection chunkSection;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public EntityLocation getSpawn() {
|
|
||||||
if (this.spawn == null) {
|
|
||||||
log.warn("Spawn is not defined! Set spawn [0, 6, 0]");
|
|
||||||
this.spawn = new EntityLocation(0d, 6d, 0d, 0f, 0f, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.spawn;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setSpawn(EntityLocation location) {
|
|
||||||
this.spawn = location;
|
|
||||||
this.spawn.setWorld(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Chunk getChunk(int x, int z) {
|
|
||||||
Chunk chunk = new SimpleChunk(x, z, this);
|
|
||||||
chunk.setChunkSection(0, chunkSection);
|
|
||||||
return chunk;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setChunk(int x, int z, Chunk chunk) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLayersBlock(List<String> listOfLayers) {
|
|
||||||
List<BlockType> layoutsBlock = new ArrayList<>();
|
|
||||||
|
|
||||||
for (String value : listOfLayers) {
|
|
||||||
String[] splitValue = value.split(";");
|
|
||||||
|
|
||||||
BlockType blockType;
|
|
||||||
try {
|
|
||||||
blockType = BlockType.valueOf(splitValue[1]);
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < Integer.parseInt(splitValue[0]); i++) {
|
|
||||||
layoutsBlock.add(blockType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.chunkSection = new SimpleChunkSection(layoutsBlock, this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
17
h2_playermanager/build.gradle
Normal file
17
h2_playermanager/build.gradle
Normal 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')
|
||||||
|
}
|
||||||
63
h2_playermanager/src/main/java/mc/core/h2db/H2Player.java
Normal file
63
h2_playermanager/src/main/java/mc/core/h2db/H2Player.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
package mc.core.h2db;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import mc.core.EntityLocation;
|
||||||
|
import mc.core.h2db.service.H2PlayerService;
|
||||||
|
import mc.core.network.BroadcastNetChannel;
|
||||||
|
import mc.core.network.NetChannel;
|
||||||
|
import mc.core.player.Player;
|
||||||
|
import mc.core.player.PlayerManager;
|
||||||
|
import mc.core.player.PlayerSettings;
|
||||||
|
import mc.core.world.World;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class H2PlayerManager implements PlayerManager {
|
||||||
|
@Setter
|
||||||
|
@Autowired
|
||||||
|
private H2PlayerService h2PlayerService;
|
||||||
|
private List<H2Player> playerList = Collections.synchronizedList(new ArrayList<>());
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Player createPlayer(String name, EntityLocation location, World world) {
|
||||||
|
H2Player h2Player = new H2Player();
|
||||||
|
h2Player.setName(name);
|
||||||
|
h2Player.setUuid(UUID.randomUUID());
|
||||||
|
h2Player.setLocation(location.clone());
|
||||||
|
h2Player.setLoadedChunks(new ArrayList<>());
|
||||||
|
h2Player.setWorld(world);
|
||||||
|
h2Player.setSettings(new PlayerSettings());
|
||||||
|
|
||||||
|
return h2PlayerService.save(h2Player);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void joinServer(Player player) {
|
||||||
|
//TODO в дальнейшем следует именно этому методу передать функции инсерта в БД
|
||||||
|
H2Player h2Player = (H2Player) player;
|
||||||
|
playerList.add(h2Player);
|
||||||
|
h2Player.setOnline(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void leftServer(Player player) {
|
||||||
|
H2Player h2Player = (H2Player) player;
|
||||||
|
h2PlayerService.save(h2Player);
|
||||||
|
h2Player.setOnline(false);
|
||||||
|
h2Player.getLoadedChunks().clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Player getPlayer(String name) {
|
||||||
|
return playerList.stream()
|
||||||
|
.filter(player -> player.getName().equals(name))
|
||||||
|
.filter(H2Player::isOnline)
|
||||||
|
.findFirst().orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<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) {
|
||||||
|
return playerList.stream()
|
||||||
|
.filter(player -> player.getName().equals(name))
|
||||||
|
.filter(player -> !player.isOnline())
|
||||||
|
.findFirst().orElseGet(() -> h2PlayerService.getByName(name));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
package mc.core.h2db.entity;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import mc.core.EntityLocation;
|
||||||
|
import mc.core.h2db.H2Player;
|
||||||
|
import mc.core.world.World;
|
||||||
|
import org.hibernate.annotations.GenericGenerator;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
|
||||||
|
import javax.persistence.*;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "players",
|
||||||
|
indexes = {@Index(name = "idx_players_uuid", columnList = "uuid", unique = true),
|
||||||
|
@Index(name = "idx_players_name", columnList = "name")})
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
public class H2PlayerEntity {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(generator = "increment")
|
||||||
|
@GenericGenerator(name= "increment", strategy= "increment")
|
||||||
|
@Column(nullable = false)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(length = 36, nullable = false)
|
||||||
|
private String uuid;
|
||||||
|
|
||||||
|
@Column(length = 16, nullable = false)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Column(name = "location_x", nullable = false)
|
||||||
|
private Double locationX;
|
||||||
|
|
||||||
|
@Column(name = "location_y", nullable = false)
|
||||||
|
private Double locationY;
|
||||||
|
|
||||||
|
@Column(name = "location_z", nullable = false)
|
||||||
|
private Double locationZ;
|
||||||
|
|
||||||
|
@Column(name = "location_yaw", nullable = false)
|
||||||
|
private Float locationYaw;
|
||||||
|
|
||||||
|
@Column(name = "location_pitch", nullable = false)
|
||||||
|
private Float locationPitch;
|
||||||
|
|
||||||
|
@Column(name = "location_world", length = 64, nullable = false)
|
||||||
|
private String locationWorld;
|
||||||
|
|
||||||
|
public H2PlayerEntity(H2Player player) {
|
||||||
|
this.id = (long) player.getId();
|
||||||
|
setUuid(player.getUuid().toString());
|
||||||
|
setName(this.name = player.getName());
|
||||||
|
this.locationX = player.getLocation().getX();
|
||||||
|
this.locationY = player.getLocation().getY();
|
||||||
|
this.locationZ = player.getLocation().getZ();
|
||||||
|
this.locationYaw = player.getLocation().getYaw();
|
||||||
|
this.locationPitch = player.getLocation().getPitch();
|
||||||
|
this.locationWorld = player.getWorld().getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUuid(String uuid) {
|
||||||
|
if (uuid == null || uuid.trim().isEmpty()) {
|
||||||
|
this.uuid = null;
|
||||||
|
} else {
|
||||||
|
this.uuid = uuid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
if (name == null || name.trim().isEmpty()) {
|
||||||
|
this.name = null;
|
||||||
|
} else {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public H2Player toPlayer(ApplicationContext context) {
|
||||||
|
H2Player player = new H2Player();
|
||||||
|
return toPlayer(player, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public H2Player toPlayer(H2Player player, ApplicationContext context) {
|
||||||
|
player.setId(this.id.intValue());
|
||||||
|
player.setUuid(UUID.fromString(this.uuid));
|
||||||
|
player.setName(this.name);
|
||||||
|
if (player.getLocation() == null) {
|
||||||
|
player.setLocation(new EntityLocation(
|
||||||
|
this.locationX, this.locationY, this.locationZ,
|
||||||
|
this.locationYaw, this.locationPitch
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
player.getLocation().setXYZ(this.locationX, this.locationY, this.locationZ);
|
||||||
|
player.getLocation().setYawPitch(this.locationYaw, this.locationPitch);
|
||||||
|
}
|
||||||
|
|
||||||
|
player.setWorld(context.getBean(this.locationWorld, World.class));
|
||||||
|
|
||||||
|
return player;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package mc.core.h2db.service;
|
||||||
|
|
||||||
|
import mc.core.h2db.H2Player;
|
||||||
|
import mc.core.h2db.entity.H2PlayerEntity;
|
||||||
|
import mc.core.h2db.repository.H2PlayerEntityRepository;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class H2PlayerServiceImpl implements H2PlayerService {
|
||||||
|
@Autowired
|
||||||
|
private ApplicationContext context;
|
||||||
|
@Autowired
|
||||||
|
private H2PlayerEntityRepository h2PlayerEntityRepository;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public H2Player save(H2Player player) {
|
||||||
|
H2PlayerEntity entity = new H2PlayerEntity(player);
|
||||||
|
//TODO возможно имеет смысл здесь оптимизация
|
||||||
|
//вместо toPlayer() сделать toPlayer(H2Player) который в существующий
|
||||||
|
//будет дописывать/обновлять данные
|
||||||
|
return h2PlayerEntityRepository.saveAndFlush(entity).toPlayer(player, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove(H2Player player) {
|
||||||
|
h2PlayerEntityRepository.deleteById((long) player.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public H2Player getByName(String name) {
|
||||||
|
Optional<H2PlayerEntity> optEntity = h2PlayerEntityRepository.findByName(name);
|
||||||
|
return optEntity.map(entiry -> entiry.toPlayer(context)).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public H2Player getById(int id) {
|
||||||
|
Optional<H2PlayerEntity> optEntity = h2PlayerEntityRepository.findById((long) id);
|
||||||
|
return optEntity.map(entiry -> entiry.toPlayer(context)).orElse(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
package mc.core.h2db;
|
||||||
|
|
||||||
|
import mc.core.h2db.service.H2PlayerService;
|
||||||
|
import mc.core.world.World;
|
||||||
|
import org.hibernate.jpa.HibernatePersistenceProvider;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.ComponentScan;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||||
|
import org.springframework.jdbc.datasource.DriverManagerDataSource;
|
||||||
|
import org.springframework.orm.jpa.JpaTransactionManager;
|
||||||
|
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
||||||
|
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||||
|
|
||||||
|
import javax.persistence.EntityManagerFactory;
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableJpaRepositories
|
||||||
|
@EnableTransactionManagement
|
||||||
|
@ComponentScan("mc.core.h2db")
|
||||||
|
public class TestSpringConfig {
|
||||||
|
private static final String DATABASE_DRIVER = "org.h2.Driver";
|
||||||
|
private static final String DATABASE_URL = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1";
|
||||||
|
private static final String DATABASE_USERNAME = "sa";
|
||||||
|
private static final String DATABASE_PASSWORD = "s3cReT";
|
||||||
|
|
||||||
|
static {
|
||||||
|
System.setProperty("org.jboss.logging.provider", "slf4j");
|
||||||
|
}
|
||||||
|
|
||||||
|
private Properties hibernateProp() {
|
||||||
|
Properties properties = new Properties();
|
||||||
|
properties.put("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
|
||||||
|
properties.put("hibernate.show_sql", "true");
|
||||||
|
properties.put("hibernate.format_sql", "true");
|
||||||
|
properties.put("hibernate.use_sql_comments", "true");
|
||||||
|
properties.put("hibernate.hbm2ddl.auto", "create");
|
||||||
|
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean("mockWorld")
|
||||||
|
public World mockWorld() {
|
||||||
|
World mockWorld = mock(World.class);
|
||||||
|
when(mockWorld.getName()).thenReturn("mockWorld");
|
||||||
|
return mockWorld;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public DataSource dataSource() {
|
||||||
|
DriverManagerDataSource dmds = new DriverManagerDataSource();
|
||||||
|
dmds.setDriverClassName(DATABASE_DRIVER);
|
||||||
|
dmds.setUrl(DATABASE_URL);
|
||||||
|
dmds.setUsername(DATABASE_USERNAME);
|
||||||
|
dmds.setPassword(DATABASE_PASSWORD);
|
||||||
|
|
||||||
|
return dmds;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {
|
||||||
|
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
|
||||||
|
entityManagerFactoryBean.setDataSource(dataSource);
|
||||||
|
entityManagerFactoryBean.setPersistenceProviderClass(HibernatePersistenceProvider.class);
|
||||||
|
entityManagerFactoryBean.setPackagesToScan("mc.core.h2db.entity");
|
||||||
|
entityManagerFactoryBean.setJpaProperties(hibernateProp());
|
||||||
|
|
||||||
|
return entityManagerFactoryBean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
|
||||||
|
JpaTransactionManager transactionManager = new JpaTransactionManager();
|
||||||
|
transactionManager.setEntityManagerFactory(entityManagerFactory);
|
||||||
|
|
||||||
|
return transactionManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public H2PlayerManager h2PlayerManager(H2PlayerService h2PlayerService) {
|
||||||
|
H2PlayerManager playerManager = new H2PlayerManager();
|
||||||
|
playerManager.setH2PlayerService(h2PlayerService);
|
||||||
|
return playerManager;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
package mc.core.h2db.service;
|
||||||
|
|
||||||
|
import mc.core.EntityLocation;
|
||||||
|
import mc.core.h2db.H2Player;
|
||||||
|
import mc.core.h2db.TestSpringConfig;
|
||||||
|
import mc.core.world.World;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.test.annotation.DirtiesContext;
|
||||||
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
|
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
@ExtendWith(SpringExtension.class)
|
||||||
|
@ContextConfiguration(classes = {TestSpringConfig.class})
|
||||||
|
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
|
||||||
|
class H2PlayerServiceTest {
|
||||||
|
@Autowired
|
||||||
|
private H2PlayerService h2PlayerService;
|
||||||
|
@Autowired
|
||||||
|
private World world;
|
||||||
|
|
||||||
|
private H2Player buildPlayer() {
|
||||||
|
final ThreadLocalRandom rnd = ThreadLocalRandom.current();
|
||||||
|
final double minD = 0.0d, maxD = 10.0d;
|
||||||
|
final float minF = 0.0f, maxF = 359.9f;
|
||||||
|
final int minI = 1000, maxI = 9999;
|
||||||
|
|
||||||
|
final H2Player player = new H2Player();
|
||||||
|
player.setUuid(UUID.randomUUID());
|
||||||
|
player.setName("player" + rnd.nextInt(minI, maxI));
|
||||||
|
player.setLocation(new EntityLocation(
|
||||||
|
rnd.nextDouble(minD, maxD),
|
||||||
|
rnd.nextDouble(minD, maxD),
|
||||||
|
rnd.nextDouble(minD, maxD),
|
||||||
|
rnd.nextFloat() * (maxF - minF) + minF,
|
||||||
|
rnd.nextFloat() * (maxF - minF) + minF
|
||||||
|
));
|
||||||
|
player.setWorld(world);
|
||||||
|
|
||||||
|
return player;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertPlayers(H2Player expected, H2Player actual) {
|
||||||
|
assertEquals(expected, actual);
|
||||||
|
assertEquals(expected.getName(), actual.getName());
|
||||||
|
assertEquals(expected.getLocation(), actual.getLocation());
|
||||||
|
assertNotNull(actual.getWorld());
|
||||||
|
assertEquals(expected.getWorld(), actual.getWorld());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void save() {
|
||||||
|
H2Player player = buildPlayer();
|
||||||
|
H2Player savedPlayer = h2PlayerService.save(player);
|
||||||
|
|
||||||
|
player.setId(savedPlayer.getId()); //FIXME костыль, однако
|
||||||
|
assertPlayers(player, savedPlayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void save_NameEmpty() {
|
||||||
|
assertThrows(Exception.class, () -> {
|
||||||
|
H2Player player = buildPlayer();
|
||||||
|
player.setName("");
|
||||||
|
h2PlayerService.save(player);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void save_NameNull() {
|
||||||
|
assertThrows(Exception.class, () -> {
|
||||||
|
H2Player player = buildPlayer();
|
||||||
|
player.setName(null);
|
||||||
|
h2PlayerService.save(player);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void save_UuidNull() {
|
||||||
|
assertThrows(Exception.class, () -> {
|
||||||
|
H2Player player = buildPlayer();
|
||||||
|
player.setUuid(null);
|
||||||
|
h2PlayerService.save(player);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void save_LocationNull() {
|
||||||
|
assertThrows(Exception.class, () -> {
|
||||||
|
H2Player player = buildPlayer();
|
||||||
|
player.setLocation(null);
|
||||||
|
h2PlayerService.save(player);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void remove() {
|
||||||
|
H2Player player = h2PlayerService.save(buildPlayer());
|
||||||
|
h2PlayerService.remove(player);
|
||||||
|
|
||||||
|
H2Player player2 = h2PlayerService.getById(player.getId());
|
||||||
|
assertNull(player2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void remove_NotExists() {
|
||||||
|
assertThrows(Exception.class, () -> {
|
||||||
|
H2Player player = h2PlayerService.save(buildPlayer());
|
||||||
|
h2PlayerService.remove(player);
|
||||||
|
h2PlayerService.remove(player);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getByName() {
|
||||||
|
H2Player player = h2PlayerService.save(buildPlayer());
|
||||||
|
|
||||||
|
H2Player player2 = h2PlayerService.getByName(player.getName());
|
||||||
|
assertPlayers(player, player2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getByName_NotExists() {
|
||||||
|
assertNull(h2PlayerService.getByName("UNKNOW_PLAYER"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getByName_Empty() {
|
||||||
|
assertNull(h2PlayerService.getByName(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getByName_Null() {
|
||||||
|
assertNull(h2PlayerService.getByName(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getById() {
|
||||||
|
H2Player player = h2PlayerService.save(buildPlayer());
|
||||||
|
|
||||||
|
H2Player player2 = h2PlayerService.getById(player.getId());
|
||||||
|
assertPlayers(player, player2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getById_NotExists() {
|
||||||
|
assertNull(h2PlayerService.getById(9999));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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')
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package mc.core.network.proto_1_12_2;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public enum Direction {
|
||||||
|
BOTTOM(0), // -Y
|
||||||
|
TOP(1), // +Y
|
||||||
|
NORTH(2), // -Z
|
||||||
|
SOUTH(3), // +Z
|
||||||
|
WEST(4), // -X
|
||||||
|
EAST(5); // +X
|
||||||
|
|
||||||
|
public static Direction getById(final int id) {
|
||||||
|
return Arrays.stream(Direction.values())
|
||||||
|
.filter(direction -> direction.id == id)
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final int id;
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
|||||||
@@ -80,13 +80,18 @@ public enum State {
|
|||||||
.put(0x0D, PlayerPositionPacket.class)
|
.put(0x0D, PlayerPositionPacket.class)
|
||||||
.put(0x0E, PlayerPositionAndLookPacket.class)
|
.put(0x0E, PlayerPositionAndLookPacket.class)
|
||||||
.put(0x0F, PlayerLookPacket.class)
|
.put(0x0F, PlayerLookPacket.class)
|
||||||
|
.put(0x13, PlayerAbilitiesPacket.class)
|
||||||
|
.put(0x14, PlayerDiggingPacket.class)
|
||||||
|
.put(0x15, EntityActionPacket.class)
|
||||||
.put(0x1A, HeldItemChangePacket.class)
|
.put(0x1A, HeldItemChangePacket.class)
|
||||||
.put(0x1D, AnimationPacket.class)
|
.put(0x1D, AnimationPacket.class)
|
||||||
|
.put(0x1F, PlayerBlockPlacementPacket.class)
|
||||||
.build(),
|
.build(),
|
||||||
ImmutableMap.<Class<? extends SCPacket>, Integer>builder()
|
ImmutableMap.<Class<? extends SCPacket>, Integer>builder()
|
||||||
.put(BossBarPacket.class, 0x0C)
|
.put(BossBarPacket.class, 0x0C)
|
||||||
.put(ChatMessageServerPacket.class, 0x0F)
|
.put(ChatMessageServerPacket.class, 0x0F)
|
||||||
.put(PluginMessagePacket.class, 0x18)
|
.put(PluginMessagePacket.class, 0x18)
|
||||||
|
.put(UnloadChunkPacket.class, 0x1D)
|
||||||
.put(ChangeGameState.class, 0x1E)
|
.put(ChangeGameState.class, 0x1E)
|
||||||
.put(KeepAlivePacket.class, 0x1F)
|
.put(KeepAlivePacket.class, 0x1F)
|
||||||
.put(ChunkDataPacket.class, 0x20)
|
.put(ChunkDataPacket.class, 0x20)
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,10 @@
|
|||||||
*/
|
*/
|
||||||
package mc.core.network.proto_1_12_2.packets;
|
package mc.core.network.proto_1_12_2.packets;
|
||||||
|
|
||||||
import lombok.*;
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
import mc.core.network.NetOutputStream;
|
import mc.core.network.NetOutputStream;
|
||||||
import mc.core.network.SCPacket;
|
import mc.core.network.SCPacket;
|
||||||
import mc.core.network.proto_1_12_2.serializers.TextMapper;
|
import mc.core.network.proto_1_12_2.serializers.TextMapper;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package mc.core.network.proto_1_12_2.packets;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import mc.core.network.CSPacket;
|
||||||
|
import mc.core.network.NetInputStream;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class EntityActionPacket implements CSPacket {
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public enum Action {
|
||||||
|
START_SNEAKING(0),
|
||||||
|
STOP_SNEAKING(1),
|
||||||
|
LEAVE_BED(2), // Leave bed is only sent when the “Leave Bed” button is clicked on the sleep GUI, not when waking up due today time.
|
||||||
|
START_SPRINTING(3),
|
||||||
|
STOP_SPRINTING(4),
|
||||||
|
START_JUMP_WITH_HORSE(5),
|
||||||
|
STOP_JUMP_WITH_HORSE(6),
|
||||||
|
OPEN_HORSE_INVENTORY(7), // Open horse inventory is only sent when pressing the inventory key (default: E) while on a horse — all other methods of opening a horse's inventory (involving right-clicking or shift-right-clicking it) do not use this packet.
|
||||||
|
START_FLYING_WITH_ELYTRA(8);
|
||||||
|
|
||||||
|
public static Action getById(final int id) {
|
||||||
|
return Arrays.stream(Action.values())
|
||||||
|
.filter(action -> action.id == id)
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final int id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int entityId;
|
||||||
|
private Action action;
|
||||||
|
private int jumpBoost; // Only used by the “start jump with horse” action, in which case it ranges from 0 to 100. In all other cases it is 0.
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readSelf(NetInputStream netStream) {
|
||||||
|
entityId = netStream.readVarInt();
|
||||||
|
action = Action.getById(netStream.readVarInt());
|
||||||
|
jumpBoost = netStream.readVarInt();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,33 +4,55 @@
|
|||||||
*/
|
*/
|
||||||
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.ToString;
|
import lombok.ToString;
|
||||||
|
import mc.core.network.CSPacket;
|
||||||
|
import mc.core.network.NetInputStream;
|
||||||
import mc.core.network.NetOutputStream;
|
import mc.core.network.NetOutputStream;
|
||||||
import mc.core.network.SCPacket;
|
import mc.core.network.SCPacket;
|
||||||
|
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
@ToString
|
@ToString
|
||||||
public class PlayerAbilitiesPacket implements SCPacket {
|
public class PlayerAbilitiesPacket implements SCPacket, CSPacket {
|
||||||
|
private static final byte $GOD_MODE_MASK = 0x01,
|
||||||
|
$FLYING_MASK = 0x02,
|
||||||
|
$CAN_FLY_MASK = 0x04,
|
||||||
|
$IDB_MASK = 0x08;
|
||||||
|
|
||||||
private boolean godMode = false;
|
private boolean godMode = false;
|
||||||
private boolean flying = false;
|
private boolean flying = false;
|
||||||
private boolean canFly = false;
|
private boolean canFly = false;
|
||||||
private boolean instantDestroyBlocks = false;
|
private boolean instantDestroyBlocks = false;
|
||||||
private float flyingSpeed = 0.05f;
|
private float flyingSpeed = 0.05f;
|
||||||
private float fieldOfView = flyingSpeed;
|
private float fieldOfView = flyingSpeed;
|
||||||
|
private float walkingSpeed;
|
||||||
|
|
||||||
@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);
|
||||||
netStream.writeFloat(fieldOfView);
|
netStream.writeFloat(fieldOfView);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readSelf(NetInputStream netStream) {
|
||||||
|
byte flag = netStream.readByte();
|
||||||
|
godMode = (flag & $GOD_MODE_MASK) > 0;
|
||||||
|
canFly = (flag & $CAN_FLY_MASK) > 0;
|
||||||
|
flying = (flag & $FLYING_MASK) > 0;
|
||||||
|
instantDestroyBlocks = (flag & $IDB_MASK) > 0;
|
||||||
|
|
||||||
|
flyingSpeed = netStream.readFloat();
|
||||||
|
walkingSpeed = netStream.readFloat();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package mc.core.network.proto_1_12_2.packets;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.ToString;
|
||||||
|
import mc.core.network.CSPacket;
|
||||||
|
import mc.core.network.NetInputStream;
|
||||||
|
import mc.core.network.proto_1_12_2.Direction;
|
||||||
|
import mc.core.network.proto_1_12_2.serializers.BlockLocationSerializer;
|
||||||
|
import mc.core.world.block.BlockLocation;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@ToString
|
||||||
|
public class PlayerBlockPlacementPacket implements CSPacket {
|
||||||
|
private BlockLocation location;
|
||||||
|
private Direction face;
|
||||||
|
/** true - main hand; false - off hand */
|
||||||
|
private boolean hand;
|
||||||
|
private float cursorX, cursorY, cursorZ;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readSelf(NetInputStream netStream) {
|
||||||
|
long compactedCoords = netStream.readLong();
|
||||||
|
location = BlockLocationSerializer.fromLong(compactedCoords);
|
||||||
|
face = Direction.getById(netStream.readVarInt());
|
||||||
|
hand = (netStream.readVarInt() == 1);
|
||||||
|
cursorX = netStream.readFloat();
|
||||||
|
cursorY = netStream.readFloat();
|
||||||
|
cursorZ = netStream.readFloat();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package mc.core.network.proto_1_12_2.packets;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.ToString;
|
||||||
|
import mc.core.network.CSPacket;
|
||||||
|
import mc.core.network.NetInputStream;
|
||||||
|
import mc.core.network.proto_1_12_2.Direction;
|
||||||
|
import mc.core.network.proto_1_12_2.serializers.BlockLocationSerializer;
|
||||||
|
import mc.core.world.block.BlockLocation;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@ToString
|
||||||
|
public class PlayerDiggingPacket implements CSPacket {
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public enum Status {
|
||||||
|
STARTED_DIGGING(0),
|
||||||
|
CANCELLED_DIGGING(1),
|
||||||
|
FINISHED_DIGGING(2),
|
||||||
|
DROP_ITEM_STACK(3),
|
||||||
|
DROP_ITEM(4),
|
||||||
|
/* Indicates that the currently held item should have its
|
||||||
|
* state updated such as eating food, pulling back bows,
|
||||||
|
* using buckets, etc. Location is always set to 0/0/0,
|
||||||
|
* Face is always set to -Y.
|
||||||
|
*/
|
||||||
|
SHOOT_ARROW(5),
|
||||||
|
FINISH_EATING(5),
|
||||||
|
SWAP_ITEM_IN_HAND(6);
|
||||||
|
|
||||||
|
public static Status getById(final int id) {
|
||||||
|
return Arrays.stream(Status.values())
|
||||||
|
.filter(status -> status.id == id)
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final int id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Status status;
|
||||||
|
private BlockLocation location;
|
||||||
|
private Direction face;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readSelf(NetInputStream netStream) {
|
||||||
|
status = Status.getById(netStream.readVarInt());
|
||||||
|
long compactCoord = netStream.readLong();
|
||||||
|
location = BlockLocationSerializer.fromLong(compactCoord);
|
||||||
|
face = Direction.getById(netStream.readByte());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
|||||||
@@ -4,15 +4,15 @@
|
|||||||
*/
|
*/
|
||||||
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.network.CSPacket;
|
import mc.core.network.CSPacket;
|
||||||
import mc.core.network.NetInputStream;
|
import mc.core.network.NetInputStream;
|
||||||
|
import mc.core.world.block.BlockLocation;
|
||||||
|
|
||||||
public class TabCompletePacket implements CSPacket {
|
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, null);
|
this.location = new BlockLocation((int)x, (int)y, (int)z); //FIXME
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package mc.core.network.proto_1_12_2.packets;
|
||||||
|
|
||||||
|
import lombok.Setter;
|
||||||
|
import mc.core.network.NetOutputStream;
|
||||||
|
import mc.core.network.SCPacket;
|
||||||
|
|
||||||
|
public class UnloadChunkPacket implements SCPacket {
|
||||||
|
@Setter
|
||||||
|
private int x, z;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeSelf(NetOutputStream netStream) {
|
||||||
|
netStream.writeInt(x);
|
||||||
|
netStream.writeInt(z);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
package mc.core.network.proto_1_12_2.packets;
|
||||||
|
|
||||||
|
import com.google.common.io.ByteStreams;
|
||||||
|
import mc.core.network.proto_1_12_2.ByteArrayOutputNetStream;
|
||||||
|
import mc.core.world.Biome;
|
||||||
|
import mc.core.world.World;
|
||||||
|
import mc.core.world.WorldType;
|
||||||
|
import mc.core.world.block.BlockFactory;
|
||||||
|
import mc.core.world.block.BlockType;
|
||||||
|
import mc.core.world.chunk.Chunk;
|
||||||
|
import mc.core.world.chunk.ChunkSection;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.Matchers.anyInt;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
class ChunkdataPacketTest {
|
||||||
|
private static byte[] expectedPacketData;
|
||||||
|
private World world;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void beforeClassTest() throws IOException {
|
||||||
|
InputStream inputStream = ChunkdataPacketTest.class.getResourceAsStream("ChunkDataPacket.bin");
|
||||||
|
expectedPacketData = ByteStreams.toByteArray(inputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void prepareWorld() {
|
||||||
|
final ChunkSection chunkSection = mock(ChunkSection.class);
|
||||||
|
when(chunkSection.getSkyLight(anyInt(), anyInt(), anyInt())).thenAnswer(invocation -> {
|
||||||
|
int y = (int)invocation.getArguments()[1];
|
||||||
|
|
||||||
|
if (y <= 3) return 0;
|
||||||
|
else return 15;
|
||||||
|
});
|
||||||
|
when(chunkSection.getBiome(anyInt(), anyInt())).thenReturn(Biome.PLAINS);
|
||||||
|
when(chunkSection.getBlock(anyInt(), anyInt(), anyInt())).thenAnswer(invocation -> {
|
||||||
|
Object[] args = invocation.getArguments();
|
||||||
|
int x = (int) args[0];
|
||||||
|
int y = (int) args[1];
|
||||||
|
int z = (int) args[2];
|
||||||
|
|
||||||
|
BlockFactory blockFactory = new BlockFactory();
|
||||||
|
|
||||||
|
if (y == 0) return blockFactory.create(BlockType.BEDROCK, x, y, z);
|
||||||
|
else if (y >= 1 && y <= 2) return blockFactory.create(BlockType.DIRT, x, y, z);
|
||||||
|
else if (y == 3) return blockFactory.create(BlockType.GRASS, x, y, z);
|
||||||
|
else return blockFactory.create(BlockType.AIR, x, y, z);
|
||||||
|
});
|
||||||
|
|
||||||
|
world = mock(World.class);
|
||||||
|
when(world.getWorldType()).thenReturn(WorldType.FLAT);
|
||||||
|
when(world.getChunk(anyInt(), anyInt())).thenAnswer(invocation -> {
|
||||||
|
Object[] args = invocation.getArguments();
|
||||||
|
|
||||||
|
Chunk chunk = mock(Chunk.class);
|
||||||
|
when(chunk.getX()).thenReturn((int) args[0]);
|
||||||
|
when(chunk.getZ()).thenReturn((int) args[1]);
|
||||||
|
when(chunk.getBiome(anyInt(), anyInt())).thenReturn(Biome.PLAINS);
|
||||||
|
when(chunk.getChunkSection(anyInt())).thenAnswer(invocation1 -> {
|
||||||
|
int height = (int)invocation1.getArguments()[0];
|
||||||
|
|
||||||
|
if (height < 1) return chunkSection;
|
||||||
|
else return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
return chunk;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void writePacket() {
|
||||||
|
ChunkDataPacket packet = new ChunkDataPacket();
|
||||||
|
packet.setX(0);
|
||||||
|
packet.setZ(0);
|
||||||
|
packet.setChunk(world.getChunk(0, 0));
|
||||||
|
packet.setInitChunk(true);
|
||||||
|
|
||||||
|
ByteArrayOutputNetStream netStream = new ByteArrayOutputNetStream();
|
||||||
|
packet.writeSelf(netStream);
|
||||||
|
byte[] actualPacketData = netStream.toByteArray();
|
||||||
|
|
||||||
|
assertEquals(expectedPacketData.length, actualPacketData.length);
|
||||||
|
assertArrayEquals(expectedPacketData, actualPacketData);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
package mc.core.network.proto_1_12_2.packets;
|
|
||||||
|
|
||||||
import mc.core.EntityLocation;
|
|
||||||
import mc.core.world.Biome;
|
|
||||||
import mc.core.world.World;
|
|
||||||
import mc.core.world.WorldType;
|
|
||||||
import mc.core.world.block.Block;
|
|
||||||
import mc.core.world.block.BlockFactory;
|
|
||||||
import mc.core.world.block.BlockType;
|
|
||||||
import mc.core.world.chunk.Chunk;
|
|
||||||
import mc.core.world.chunk.ChunkSection;
|
|
||||||
|
|
||||||
public class DummyWorld implements World {
|
|
||||||
private class DummyChunkSection implements ChunkSection {
|
|
||||||
@Override
|
|
||||||
public int getSkyLight(int x, int y, int z) {
|
|
||||||
if (y <= 3) return 0;
|
|
||||||
else return 15;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setSkyLight(int x, int y, int z, int lightLevel) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getAddition(int x, int y, int z) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setAddition(int x, int y, int z, int value) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Biome getBiome(int localX, int localZ) {
|
|
||||||
return Biome.PLAINS;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getX() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getY() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getZ() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setBlock(Block block) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Block getBlock(int x, int y, int z) {
|
|
||||||
BlockFactory blockFactory = new BlockFactory();
|
|
||||||
|
|
||||||
if (y == 0) return blockFactory.create(BlockType.BEDROCK, x, y, z, getWorld());
|
|
||||||
else if (y >= 1 && y <= 2) return blockFactory.create(BlockType.DIRT, x, y, z, getWorld());
|
|
||||||
else if (y == 3) return blockFactory.create(BlockType.GRASS, x, y, z, getWorld());
|
|
||||||
else return blockFactory.create(BlockType.AIR, x, y, z, getWorld());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public World getWorld() {
|
|
||||||
return DummyWorld.this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class DummyChunk implements Chunk {
|
|
||||||
@Override
|
|
||||||
public World getWorld() {
|
|
||||||
return DummyWorld.this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setWorld(World world) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChunkSection getChunkSection(int height) {
|
|
||||||
if (height < 1) return new DummyChunkSection();
|
|
||||||
else return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setChunkSection(int height, ChunkSection chunkSection) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Biome getBiome(int localX, int localZ) {
|
|
||||||
return Biome.PLAINS;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setBiome(int localX, int localZ, Biome biome) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getX() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getZ() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Chunk chunk = new DummyChunk();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public WorldType getWorldType() {
|
|
||||||
return WorldType.FLAT;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public EntityLocation getSpawn() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setSpawn(EntityLocation location) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Chunk getChunk(int x, int z) {
|
|
||||||
return chunk;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setChunk(int x, int z, Chunk chunk) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
package mc.core.network.proto_1_12_2.packets;
|
|
||||||
|
|
||||||
import com.google.common.io.ByteStreams;
|
|
||||||
import mc.core.network.proto_1_12_2.ByteArrayOutputNetStream;
|
|
||||||
import mc.core.world.World;
|
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.BeforeClass;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
|
|
||||||
public class TestChunkdataPacket {
|
|
||||||
private static byte[] expectedPacketData;
|
|
||||||
|
|
||||||
@BeforeClass
|
|
||||||
public static void beforeClassTest() throws IOException {
|
|
||||||
InputStream inputStream = TestChunkdataPacket.class.getResourceAsStream("ChunkDataPacket.bin");
|
|
||||||
expectedPacketData = ByteStreams.toByteArray(inputStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
private World createDummyWorld() {
|
|
||||||
return new DummyWorld();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test() {
|
|
||||||
ChunkDataPacket packet = new ChunkDataPacket();
|
|
||||||
packet.setX(0);
|
|
||||||
packet.setZ(0);
|
|
||||||
packet.setChunk(createDummyWorld().getChunk(0, 0));
|
|
||||||
packet.setInitChunk(true);
|
|
||||||
|
|
||||||
ByteArrayOutputNetStream netStream = new ByteArrayOutputNetStream();
|
|
||||||
packet.writeSelf(netStream);
|
|
||||||
byte[] actualPacketData = netStream.toByteArray();
|
|
||||||
|
|
||||||
Assert.assertEquals(expectedPacketData.length, actualPacketData.length);
|
|
||||||
Assert.assertArrayEquals(expectedPacketData, actualPacketData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
group 'mc'
|
|
||||||
version '1.0-SNAPSHOT'
|
version '1.0-SNAPSHOT'
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package mc.core.network.proto_1_12_2.netty;
|
||||||
|
|
||||||
|
import lombok.Setter;
|
||||||
|
import mc.core.network.proto_1_12_2.packets.KeepAlivePacket;
|
||||||
|
import mc.core.player.PlayerManager;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class KeepAliveThread extends Thread {
|
||||||
|
private static final Random RAND = new Random();
|
||||||
|
private final Object lock = new Object();
|
||||||
|
@Autowired
|
||||||
|
private PlayerManager playerManager;
|
||||||
|
@Setter
|
||||||
|
private int interval = 10;
|
||||||
|
|
||||||
|
public void notifyLock() {
|
||||||
|
synchronized (lock) {
|
||||||
|
lock.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
while (!Thread.currentThread().isInterrupted()) {
|
||||||
|
while(playerManager.getCountPlayers() == 0) {
|
||||||
|
synchronized (lock) {
|
||||||
|
try {
|
||||||
|
lock.wait();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
playerManager.getBroadcastChannel().writeAndFlush(new KeepAlivePacket(RAND.nextLong()));
|
||||||
|
|
||||||
|
try {
|
||||||
|
Thread.sleep(interval);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,31 +31,35 @@ 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(packetSize - rb);
|
in.skipBytes(leftDataPacket);
|
||||||
} else {
|
} else {
|
||||||
netStream.setDataSize(packetSize - rb);
|
netStream.setDataSize(leftDataPacket);
|
||||||
CSPacket packet = packetClass.newInstance();
|
CSPacket packet = packetClass.newInstance();
|
||||||
packet.readSelf(netStream);
|
try {
|
||||||
log.debug("Known packet: {}:{}", state.name(), packet.toString());
|
packet.readSelf(netStream);
|
||||||
out.add(packet);
|
log.debug("Known packet: {}:{}", state.name(), packet.toString());
|
||||||
|
out.add(packet);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("Known packet: {}:{}. But throw exception. See debug log.", state.name(), packet.getClass().getSimpleName());
|
||||||
|
log.debug("Read packet", e);
|
||||||
|
in.skipBytes(leftDataPacket);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +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_PlayerMoveEvent;
|
import mc.core.eventbus.events.SC_ChunkUnloadEvent;
|
||||||
|
import mc.core.eventbus.events.SC_PlayerMoveEvent;
|
||||||
import mc.core.network.proto_1_12_2.TeleportManager;
|
import mc.core.network.proto_1_12_2.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.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 {
|
||||||
@@ -26,11 +27,9 @@ class PlayerEventListener {
|
|||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void playerChunkLoadHandler(SC_ChunkLoadEvent event) {
|
public void playerChunkLoadHandler(SC_ChunkLoadEvent event) {
|
||||||
log.debug("(SC) playerChunkLoadHandler()");
|
|
||||||
|
|
||||||
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]);
|
||||||
@@ -38,9 +37,20 @@ class PlayerEventListener {
|
|||||||
packet.setInitChunk(true);
|
packet.setInitChunk(true);
|
||||||
packet.setChunk(chunk);
|
packet.setChunk(chunk);
|
||||||
|
|
||||||
event.getPlayer().getChannel().write(packet);
|
event.getPlayer().getChannel().writeAndFlush(packet);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
event.getPlayer().getChannel().flush();
|
@Subscribe
|
||||||
|
public void playerChunkUnloadHandler(SC_ChunkUnloadEvent event) {
|
||||||
|
for(Integer compressXZ : event.getNeedUnloadChunks()) {
|
||||||
|
int[] xz = CompactedCoords.uncompressXZ(compressXZ);
|
||||||
|
|
||||||
|
UnloadChunkPacket packet = new UnloadChunkPacket();
|
||||||
|
packet.setX(xz[0]);
|
||||||
|
packet.setZ(xz[1]);
|
||||||
|
|
||||||
|
event.getPlayer().getChannel().writeAndFlush(packet);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.EventBusGetter;
|
||||||
import mc.core.events.EventBusGetter;
|
import mc.core.eventbus.events.CS_PlayerMoveEvent;
|
||||||
import mc.core.network.proto_1_12_2.State;
|
import mc.core.network.proto_1_12_2.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(
|
|
||||||
packet.getPlayerName(),
|
if (player == null) {
|
||||||
world.getSpawn()));
|
player = playerManager.createPlayer(
|
||||||
|
packet.getPlayerName(),
|
||||||
|
world.getSpawn(),
|
||||||
|
world
|
||||||
|
);
|
||||||
|
|
||||||
|
if (player == null) {
|
||||||
|
channel.writeAndFlush(new DisconnectPacket(
|
||||||
|
Text.of("Internal server error: can't create new player"))
|
||||||
|
).addListener(ChannelFutureListener.CLOSE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
channel.writeAndFlush(new LoginSuccessPacket(
|
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,22 +82,14 @@ 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();
|
|
||||||
|
|
||||||
// First Chunk
|
channel.flush();
|
||||||
ChunkDataPacket pkt8 = new ChunkDataPacket();
|
|
||||||
pkt8.setX(player.getLocation().getChunk().getX());
|
|
||||||
pkt8.setZ(player.getLocation().getChunk().getZ());
|
|
||||||
pkt8.setChunk(player.getLocation().getChunk());
|
|
||||||
pkt8.setInitChunk(true);
|
|
||||||
channel.writeAndFlush(pkt8);
|
|
||||||
player.getLoadedChunks().add(CompactedCoords.compressXZ(0, 0));
|
|
||||||
|
|
||||||
// Player Position And Look
|
// Player Position And Look
|
||||||
PlayerPositionAndLookPacket pkt4 = new PlayerPositionAndLookPacket();
|
PlayerPositionAndLookPacket pkt4 = new PlayerPositionAndLookPacket();
|
||||||
@@ -99,10 +100,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 +117,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.EventBusGetter;
|
||||||
import mc.core.events.EventBusGetter;
|
import mc.core.eventbus.events.CS_PlayerMoveEvent;
|
||||||
import mc.core.network.proto_1_12_2.TeleportManager;
|
import mc.core.network.proto_1_12_2.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
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,24 +10,15 @@ import mc.core.chat.MessageType;
|
|||||||
import mc.core.network.NetChannel;
|
import mc.core.network.NetChannel;
|
||||||
import mc.core.network.SCPacket;
|
import mc.core.network.SCPacket;
|
||||||
import mc.core.network.proto_1_12_2.packets.ChatMessageServerPacket;
|
import mc.core.network.proto_1_12_2.packets.ChatMessageServerPacket;
|
||||||
import mc.core.network.proto_1_12_2.packets.KeepAlivePacket;
|
|
||||||
import mc.core.network.proto_1_12_2.packets.TimeUpdatePacket;
|
import mc.core.network.proto_1_12_2.packets.TimeUpdatePacket;
|
||||||
import mc.core.network.proto_1_12_2.packets.TitlePacket;
|
import mc.core.network.proto_1_12_2.packets.TitlePacket;
|
||||||
import mc.core.text.Text;
|
import mc.core.text.Text;
|
||||||
import mc.core.text.Title;
|
import mc.core.text.Title;
|
||||||
|
|
||||||
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));
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
rootProject.name = 'mc-server'
|
rootProject.name = 'mc-server'
|
||||||
|
|
||||||
include('core') // Core
|
include('core') // Core
|
||||||
include('flat_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.)
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
# Flat world
|
# Simple world
|
||||||
|
|
||||||
Плоский мир
|
Простая реализация мира
|
||||||
|
|
||||||
## Spring bean
|
## Spring bean
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<bean id="flatWorld" class="mc.world.flat.FlatWorld">
|
<bean id="simpleWorld" class="mc.world.simple.SimpleWorld">
|
||||||
<property name="spawn">
|
<property name="spawn">
|
||||||
<bean class="mc.core.EntityLocation">
|
<bean class="mc.core.EntityLocation">
|
||||||
<constructor-arg index="0" type="double" value="8"/>
|
<constructor-arg index="0" type="double" value="8"/>
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
`spawn` - точка спавна.
|
`spawn` - точка спавна.
|
||||||
|
|
||||||
При указании точки спавна, указывать шестой параметр `World` не имеет смысла,
|
При указании точки спавна, указывать шестой параметр `World` не имеет смысла,
|
||||||
т.к. `FlatWorld` всё равно перезапишет этот параметр.
|
т.к. `SimpleWorld` всё равно перезапишет этот параметр.
|
||||||
|
|
||||||
`layersBlock` - слои блоков.
|
`layersBlock` - слои блоков.
|
||||||
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
group 'mc'
|
|
||||||
version '1.0-SNAPSHOT'
|
version '1.0-SNAPSHOT'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package mc.world.simple;
|
||||||
|
|
||||||
|
import mc.core.world.block.BlockType;
|
||||||
|
import mc.core.world.chunk.Chunk;
|
||||||
|
import mc.core.world.chunk.ChunkProvider;
|
||||||
|
import mc.core.world.chunk.ChunkSection;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class FlatChunkProvider implements ChunkProvider {
|
||||||
|
private ChunkSection chunkSection;
|
||||||
|
|
||||||
|
public void setLayersBlockAsString(List<String> listOfLayers) {
|
||||||
|
List<BlockType> layoutsBlock = new ArrayList<>();
|
||||||
|
|
||||||
|
for (String value : listOfLayers) {
|
||||||
|
String[] splitValue = value.split(";");
|
||||||
|
|
||||||
|
BlockType blockType;
|
||||||
|
try {
|
||||||
|
blockType = BlockType.valueOf(splitValue[1]);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < Integer.parseInt(splitValue[0]); i++) {
|
||||||
|
layoutsBlock.add(blockType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setLayersBlock(layoutsBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLayersBlock(List<BlockType> layoutsBlock) {
|
||||||
|
this.chunkSection = new SimpleChunkSection(layoutsBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Chunk getChunk(int x, int z) {
|
||||||
|
Chunk chunk = new SimpleChunk(x, z);
|
||||||
|
chunk.setChunkSection(0, chunkSection);
|
||||||
|
return chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void saveChunk(Chunk chunk) {
|
||||||
|
//FIXME nope...
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void saveChunk(Chunk... chunks) {
|
||||||
|
//FIXME nope...
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,30 +1,20 @@
|
|||||||
package mc.world.flat;
|
package mc.world.simple;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import mc.core.exception.ResourceUnloadedException;
|
|
||||||
import mc.core.world.Biome;
|
import mc.core.world.Biome;
|
||||||
import mc.core.world.World;
|
|
||||||
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 java.lang.ref.Reference;
|
|
||||||
import java.lang.ref.WeakReference;
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class SimpleChunk implements Chunk {
|
public class SimpleChunk implements Chunk {
|
||||||
@Getter
|
@Getter
|
||||||
private int x, z;
|
private final int x, z;
|
||||||
private Reference<World> refWorld;
|
|
||||||
private ChunkSection chunkSection;
|
private ChunkSection chunkSection;
|
||||||
private final Biome biome = Biome.PLAINS;
|
private final Biome biome = Biome.PLAINS;
|
||||||
|
|
||||||
public SimpleChunk(int x, int z, World world) {
|
|
||||||
this.x = x;
|
|
||||||
this.z = z;
|
|
||||||
setWorld(world);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ChunkSection getChunkSection(int height) {
|
public ChunkSection getChunkSection(int height) {
|
||||||
return chunkSection;
|
return chunkSection;
|
||||||
@@ -44,18 +34,4 @@ public class SimpleChunk implements Chunk {
|
|||||||
public void setBiome(int localX, int localZ, Biome biome) {
|
public void setBiome(int localX, int localZ, Biome biome) {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public World getWorld() {
|
|
||||||
if (refWorld.get() == null) {
|
|
||||||
throw new ResourceUnloadedException("World unloaded");
|
|
||||||
} else {
|
|
||||||
return refWorld.get();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setWorld(World world) {
|
|
||||||
this.refWorld = new WeakReference<>(world);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,29 +1,19 @@
|
|||||||
/*
|
package mc.world.simple;
|
||||||
* DmitriyMX <dimon550@gmail.com>
|
|
||||||
* 2018-04-28
|
|
||||||
*/
|
|
||||||
package mc.world.flat;
|
|
||||||
|
|
||||||
import mc.core.exception.ResourceUnloadedException;
|
|
||||||
import mc.core.world.Biome;
|
import mc.core.world.Biome;
|
||||||
import mc.core.world.World;
|
|
||||||
import mc.core.world.block.Block;
|
import mc.core.world.block.Block;
|
||||||
import mc.core.world.block.BlockFactory;
|
import mc.core.world.block.BlockFactory;
|
||||||
import mc.core.world.block.BlockType;
|
import mc.core.world.block.BlockType;
|
||||||
import mc.core.world.chunk.ChunkSection;
|
import mc.core.world.chunk.ChunkSection;
|
||||||
|
|
||||||
import java.lang.ref.Reference;
|
|
||||||
import java.lang.ref.WeakReference;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class SimpleChunkSection implements ChunkSection {
|
public class SimpleChunkSection implements ChunkSection {
|
||||||
private final BlockFactory blockFactory = new BlockFactory();
|
private final BlockFactory blockFactory = new BlockFactory();
|
||||||
private final List<BlockType> layersBlock;
|
private final List<BlockType> layersBlock;
|
||||||
private Reference<World> refWorld;
|
|
||||||
|
|
||||||
public SimpleChunkSection(List<BlockType> layersBlock, World world) {
|
public SimpleChunkSection(List<BlockType> layersBlock) {
|
||||||
this.layersBlock = layersBlock;
|
this.layersBlock = layersBlock;
|
||||||
this.refWorld = new WeakReference<>(world);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -71,23 +61,20 @@ public class SimpleChunkSection implements ChunkSection {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Block getBlock(int x, int y, int z) {
|
public Block getBlock(int x, int y, int z) {
|
||||||
|
if (x < 0) x = 0;
|
||||||
|
else if (x > 15) x = 15;
|
||||||
|
if (y < 0) y = 0;
|
||||||
|
else if (y > 15) y = 15;
|
||||||
|
if (z < 0) z = 0;
|
||||||
|
else if (z > 15) z = 15;
|
||||||
|
|
||||||
if (y >= layersBlock.size()) {
|
if (y >= layersBlock.size()) {
|
||||||
return blockFactory.create(BlockType.AIR, x, y, z, getWorld());
|
return blockFactory.create(BlockType.AIR, x, y, z);
|
||||||
}
|
}
|
||||||
|
|
||||||
BlockType blockType = layersBlock.get(y);
|
BlockType blockType = layersBlock.get(y);
|
||||||
if (blockType == null) return blockFactory.create(BlockType.AIR, x, y, z, getWorld());
|
if (blockType == null) return blockFactory.create(BlockType.AIR, x, y, z);
|
||||||
|
|
||||||
return blockFactory.create(blockType, x, y, z, getWorld());
|
return blockFactory.create(blockType, x, y, z);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public World getWorld() {
|
|
||||||
World world = refWorld.get();
|
|
||||||
if (world == null) {
|
|
||||||
throw new ResourceUnloadedException("World unloaded");
|
|
||||||
}
|
|
||||||
|
|
||||||
return world;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
56
simple_world/src/main/java/mc/world/simple/SimpleWorld.java
Normal file
56
simple_world/src/main/java/mc/world/simple/SimpleWorld.java
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package mc.world.simple;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import mc.core.EntityLocation;
|
||||||
|
import mc.core.world.World;
|
||||||
|
import mc.core.world.WorldType;
|
||||||
|
import mc.core.world.chunk.Chunk;
|
||||||
|
import mc.core.world.chunk.ChunkProvider;
|
||||||
|
import org.springframework.beans.factory.BeanNameAware;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class SimpleWorld implements World, BeanNameAware {
|
||||||
|
@Getter
|
||||||
|
private String name;
|
||||||
|
@Getter
|
||||||
|
private final WorldType worldType = WorldType.FLAT;
|
||||||
|
private EntityLocation spawn;
|
||||||
|
@Setter
|
||||||
|
private ChunkProvider chunkProvider;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EntityLocation getSpawn() {
|
||||||
|
if (this.spawn == null) {
|
||||||
|
log.warn("Spawn is not defined! Set spawn [0, 6, 0]");
|
||||||
|
setSpawn(0d, 6d, 0d);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.spawn;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSpawn(EntityLocation location) {
|
||||||
|
this.spawn = location;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Chunk getChunk(int x, int z) {
|
||||||
|
return chunkProvider.getChunk(x, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setChunk(int x, int z, Chunk chunk) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBeanName(@Nonnull String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package mc.world.simple;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import mc.core.world.block.Block;
|
||||||
|
import mc.core.world.block.BlockType;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
class SimpleChunkSectionTest {
|
||||||
|
private SimpleChunkSection chunkSection;
|
||||||
|
private List<BlockType> layersBlock;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void before() {
|
||||||
|
layersBlock = Lists.newArrayList(
|
||||||
|
BlockType.BEDROCK,
|
||||||
|
BlockType.DIRT,
|
||||||
|
BlockType.DIRT,
|
||||||
|
BlockType.GRASS
|
||||||
|
);
|
||||||
|
|
||||||
|
chunkSection = new SimpleChunkSection(layersBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getBlock() {
|
||||||
|
for (int y = 15; y >= 0; y--) {
|
||||||
|
for (int x = 0; x < 16; x++) {
|
||||||
|
for (int z = 0; z < 16; z++) {
|
||||||
|
Block block = chunkSection.getBlock(x, y, z);
|
||||||
|
if (y > layersBlock.size()-1) {
|
||||||
|
assertEquals(block.getBlockType(), BlockType.AIR);
|
||||||
|
} else {
|
||||||
|
assertEquals(block.getBlockType(), layersBlock.get(y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package mc.world.simple;
|
||||||
|
|
||||||
|
import mc.core.EntityLocation;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.test.annotation.DirtiesContext;
|
||||||
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
|
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
@ExtendWith(SpringExtension.class)
|
||||||
|
@ContextConfiguration(classes = {TestSpringConfig.class})
|
||||||
|
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
|
||||||
|
class SimpleWorldTest {
|
||||||
|
@Autowired
|
||||||
|
private SimpleWorld world;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void spawn() {
|
||||||
|
final EntityLocation location = new EntityLocation(1d, 2d, 3d,4f, 5f);
|
||||||
|
|
||||||
|
world.setSpawn(location);
|
||||||
|
assertEquals(location, world.getSpawn());
|
||||||
|
assertSame(location, world.getSpawn());
|
||||||
|
|
||||||
|
world.setSpawn(1d, 2d, 3d, 4f, 5f);
|
||||||
|
assertEquals(location, world.getSpawn());
|
||||||
|
assertNotSame(location, world.getSpawn());
|
||||||
|
|
||||||
|
location.setYawPitch(0, 0);
|
||||||
|
world.setSpawn(1d, 2d, 3d);
|
||||||
|
assertEquals(location, world.getSpawn());
|
||||||
|
assertNotSame(location, world.getSpawn());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package mc.world.simple;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import mc.core.world.block.BlockType;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.ComponentScan;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ComponentScan("mc.world.simple")
|
||||||
|
public class TestSpringConfig {
|
||||||
|
@Bean
|
||||||
|
public List<BlockType> layersBlock() {
|
||||||
|
return Lists.newArrayList(
|
||||||
|
BlockType.BEDROCK,
|
||||||
|
BlockType.DIRT,
|
||||||
|
BlockType.DIRT,
|
||||||
|
BlockType.GRASS
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SimpleChunkSection chunkSection(List<BlockType> layersBlock) {
|
||||||
|
return new SimpleChunkSection(layersBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SimpleWorld simpleWorld(List<BlockType> layersBlock) {
|
||||||
|
FlatChunkProvider chunkProvider = new FlatChunkProvider();
|
||||||
|
chunkProvider.setLayersBlock(layersBlock);
|
||||||
|
|
||||||
|
SimpleWorld world = new SimpleWorld();
|
||||||
|
world.setChunkProvider(chunkProvider);
|
||||||
|
return world;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
group 'mc'
|
|
||||||
version '1.0-SNAPSHOT'
|
version '1.0-SNAPSHOT'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user