Archived
0

Merge branch 'proto_1.12.2' into world

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

View File

@@ -18,20 +18,6 @@ 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
Implements: `mc.core.time.TimeProcessor`

View File

@@ -1,4 +1,3 @@
group 'mc'
version '1.0-SNAPSHOT'
apply plugin: 'maven'
@@ -9,7 +8,7 @@ mainClassName = "mc.core.Main"
dependencies {
/* Components */
compile (group: 'commons-io', name: 'commons-io', version: '2.6')
compile (group: 'com.google.guava', name: 'guava', version: '24.1-jre')
compile (group: 'com.google.guava', name: 'guava', version: '26.0-jre')
/* Named Binary Tags */
compile (group: 'com.flowpowered', name: 'flow-nbt', version: '1.0.0')
compile (group: 'com.flowpowered', name: 'flow-nbt', version: '1.0.1-SNAPSHOT')
}

View File

@@ -0,0 +1,85 @@
package mc.core;
import com.google.common.eventbus.Subscribe;
import lombok.extern.slf4j.Slf4j;
import mc.core.eventbus.EventBusGetter;
import mc.core.eventbus.events.CS_PlayerMoveEvent;
import mc.core.eventbus.events.SC_ChunkLoadEvent;
import mc.core.eventbus.events.SC_ChunkUnloadEvent;
import mc.core.utils.CompactedCoords;
import mc.core.world.chunk.Chunk;
import javax.annotation.PostConstruct;
import java.util.Iterator;
@Slf4j
public class CoreEventListener {
@PostConstruct
public void registerEventHandlers() {
EventBusGetter.getInstance().register(this);
}
@Subscribe
public void handlerPlayerMoveEvent(CS_PlayerMoveEvent event) {
log.trace("(GameLoop) playerMoveEventHandler()");
Chunk chunk;
chunk = event.getPlayer().getWorld().getChunk(event.getOldLocation()); // Old chunk
int ccX = chunk.getX();
int ccZ = chunk.getZ();
chunk = event.getPlayer().getWorld().getChunk(event.getNewLocation()); // Next chunk
int ncX = chunk.getX();
int ncZ = chunk.getZ();
if (event.isRecalcChunk() || (ncX != ccX || ncZ != ccZ)) {
final int viewDistance = event.getPlayer().getSettings().getViewDistance() + 1;
int cMinX = chunk.getX() - viewDistance;
int cMaxX = chunk.getX() + viewDistance;
int cMinZ = chunk.getZ() - viewDistance;
int cMaxZ = chunk.getZ() + viewDistance;
SC_ChunkUnloadEvent eventChunkUnload = new SC_ChunkUnloadEvent(event.getPlayer());
Iterator<Integer> itr = event.getPlayer().getLoadedChunks().iterator();
while(itr.hasNext()) {
int compressXZ = itr.next();
int[] xz = CompactedCoords.uncompressXZ(compressXZ);
if (xz[0] > cMaxX || xz[0] < cMinX || xz[1] > cMaxZ || xz[1] < cMinZ) {
eventChunkUnload.getNeedUnloadChunks().add(compressXZ);
itr.remove();
}
}
if (!eventChunkUnload.getNeedUnloadChunks().isEmpty()) {
EventBusGetter.getInstance().post(eventChunkUnload);
}
SC_ChunkLoadEvent eventChunkLoad = new SC_ChunkLoadEvent(event.getPlayer());
for (int cZ = cMinZ; cZ <= cMaxZ; cZ++) {
for (int cX = cMinX; cX <= cMaxX; cX++) {
int compressXZ = CompactedCoords.compressXZ(cX, cZ);
if (!event.getPlayer().getLoadedChunks().contains(compressXZ)) {
if (!event.getPlayer().getLoadedChunks().contains(compressXZ)) {
eventChunkLoad.getNeedLoadChunks().add(compressXZ);
event.getPlayer().getLoadedChunks().add(compressXZ);
}
}
}
}
if (!eventChunkLoad.getNeedLoadChunks().isEmpty()) {
EventBusGetter.getInstance().post(eventChunkLoad);
}
}
event.getPlayer().getLocation().setXYZ(
event.getNewLocation().getX(),
event.getNewLocation().getY(),
event.getNewLocation().getZ()
);
// TODO отсылать клиенту только(!) для корректировки позиции
// SC_PlayerMoveEvent nextEvent = new SC_PlayerMoveEvent(event.getPlayer());
// nextEvent.setNewLocation(event.getNewLocation());
// EventBusGetter.INSTANCE.post(nextEvent);
}
}

View File

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

View File

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

View File

@@ -0,0 +1,57 @@
package mc.core;
public class ImmutableEntityLocation extends EntityLocation {
public ImmutableEntityLocation(double x, double y, double z, float yaw, float pitch) {
super(x, y, z, yaw, pitch);
}
public ImmutableEntityLocation(EntityLocation location) {
this(
location.getX(),
location.getY(),
location.getZ(),
location.getYaw(),
location.getPitch()
);
}
@Override
public void setX(double x) {
throw new UnsupportedOperationException();
}
@Override
public void setY(double y) {
throw new UnsupportedOperationException();
}
@Override
public void setZ(double z) {
throw new UnsupportedOperationException();
}
@Override
public void setYaw(float yaw) {
throw new UnsupportedOperationException();
}
@Override
public void setPitch(float pitch) {
throw new UnsupportedOperationException();
}
@Override
public void set(EntityLocation location) {
throw new UnsupportedOperationException();
}
@Override
public void setXYZ(double x, double y, double z) {
throw new UnsupportedOperationException();
}
@Override
public void setYawPitch(float yaw, float pitch) {
throw new UnsupportedOperationException();
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,112 +0,0 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 2018-04-15
*/
package mc.core.player;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import mc.core.Config;
import mc.core.EntityLocation;
import mc.core.network.BroadcastNetChannel;
import mc.core.network.NetChannel;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.*;
import java.util.stream.Collectors;
@Slf4j
public class InMemoryPlayerManager implements PlayerManager, Runnable {
private static final Random rand = new Random();
private List<Player> players;
private final Object lock = new Object();
@Setter
private int keepAliveInterval = 1;
@Autowired
public InMemoryPlayerManager(Config config) {
final int c = config.getMaxPlayers() > 50 ? 50 : config.getMaxPlayers();
players = Collections.synchronizedList(new ArrayList<>(c));
(new Thread(this, "KeepAlive")).start();
}
@Override
public Player createPlayer(String name, EntityLocation defaultLocation) {
SimplePlayer player = new SimplePlayer();
player.setId(rand.nextInt(10000));
player.setUUID(UUID.nameUUIDFromBytes(name.getBytes()));
player.setName(name);
player.getLocation().setXYZ(defaultLocation);
player.getLocation().setYawPitch(defaultLocation);
player.getLocation().setWorld(defaultLocation.getWorld());
player.setSettings(new PlayerSettings());
synchronized (lock) {
players.add(player);
lock.notify();
}
return player;
}
@Override
public void joinServer(Player player) {
((SimplePlayer) player).setOnline(true);
}
@Override
public void leftServer(Player player) {
((SimplePlayer) player).setOnline(false);
}
@Override
public Optional<Player> getPlayer(final String name) {
return players.stream()
.filter(player -> player.getName().equalsIgnoreCase(name))
.findFirst();
}
@Override
public Optional<Player> getPlayerById(final int id) {
return players.stream()
.filter(player -> player.getId() == id)
.findFirst();
}
@Override
public List<Player> getPlayers() {
return players.stream().filter(Player::isOnline).collect(Collectors.toList());
}
@Override
public int getCountOnlinePlayers() {
return (int) players.stream().filter(Player::isOnline).count();
}
@Override
public NetChannel getBroadcastChannel() {
return new BroadcastNetChannel(players.stream().filter(Player::isOnline));
}
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
synchronized (lock) {
while (players.size() == 0) {
try {
lock.wait();
} catch (InterruptedException e) {
return;
}
}
}
getBroadcastChannel().sendKeepAlive();
try {
Thread.sleep(keepAliveInterval);
} catch (InterruptedException e) {
return;
}
}
}
}

View File

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

View File

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

View File

@@ -1,40 +0,0 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 2018-04-23
*/
package mc.core.player;
import lombok.Data;
import mc.core.EntityLocation;
import mc.core.network.NetChannel;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@Data
public class SimplePlayer implements Player {
private int id;
private UUID uuid;
private String name;
private boolean online = false;
private NetChannel channel;
private EntityLocation location = new EntityLocation(0d, 0d, 0d, 0f, 0f, null);
private boolean flying = false;
private PlayerSettings settings;
private List<Integer> loadedChunks = new ArrayList<>();
public void setLocation(EntityLocation location) {
this.location.setXYZ(location);
this.location.setYawPitch(location);
}
@Override
public UUID getUUID() {
return uuid;
}
public void setUUID(UUID uuid) {
this.uuid = uuid;
}
}

View File

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

View File

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

View File

@@ -5,9 +5,11 @@
package mc.core.world;
import mc.core.EntityLocation;
import mc.core.world.block.BlockLocation;
import mc.core.world.chunk.Chunk;
public interface World {
String getName();
WorldType getWorldType();
EntityLocation getSpawn();
@@ -18,4 +20,12 @@ public interface World {
Chunk getChunk(int x, int z);
void setChunk(int x, int z, Chunk chunkSection);
default Chunk getChunk(BlockLocation location) {
return getChunk(location.getX() >> 4, location.getZ() >> 4);
}
default Chunk getChunk(EntityLocation location) {
return getChunk(location.getBlockX() >> 4, location.getBlockZ() >> 4);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,33 @@
package mc.core;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
class ImmutableEntityLocationTest {
@Test
void setValue() {
EntityLocation location = new ImmutableEntityLocation(1d, 2d, 3d, 4f, 5f);
assertThrows(UnsupportedOperationException.class, () -> {
location.setX(1);
location.setY(1);
location.setZ(1);
location.setYaw(1);
location.setPitch(1);
location.setXYZ(1, 2, 3);
location.setYawPitch(1, 2);
location.set(EntityLocation.ZERO());
});
}
@Test
void clone_() {
EntityLocation locOrig = new ImmutableEntityLocation(1d, 2d, 3d, 4f, 5f);
EntityLocation locClone = locOrig.clone();
assertEquals(locOrig, locClone);
}
}

View File

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

View File

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

View File

@@ -0,0 +1,34 @@
package mc.core;
import mc.core.world.World;
import mc.core.world.chunk.Chunk;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@Configuration
public class TestSpringConfig {
@Bean()
public World simpleMockWorld() {
return mock(World.class);
}
@Bean
public World chunkedMockWorld() {
World world = mock(World.class);
when(world.getChunk(anyInt(), anyInt())).thenAnswer(invocation -> {
Object[] args = invocation.getArguments();
Chunk chunk = mock(Chunk.class);
when(chunk.getX()).thenReturn((int) args[0]);
when(chunk.getZ()).thenReturn((int) args[1]);
return chunk;
});
return world;
}
}

View File

@@ -0,0 +1,76 @@
package mc.core.text;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class TextTest {
@Test
void toPlain() {
final String m1 = "mes";
final String m2 = "sage";
final String message = m1 + m2;
assertEquals(message, Text.of(message).toPlain());
assertEquals(message, Text.builder(message).build().toPlain());
assertEquals(message, Text.builder(Text.of(message)).build().toPlain());
assertEquals(message, Text.builder().append(message).build().toPlain());
assertEquals(message, Text.builder().append(Text.of(message)).build().toPlain());
assertEquals(message, Text.builder(m1, m2).build().toPlain());
assertEquals(message, Text.builder(Text.of(m1), Text.of(m2)).build().toPlain());
assertEquals(message, Text.builder().append(Text.of(m1), Text.of(m2)).build().toPlain());
assertEquals(message, Text.builder().append(Text.of(m1)).append(Text.of(m2)).build().toPlain());
}
@Test
void equals_() {
assertEquals(Text.of(), Text.of(""));
assertEquals(Text.of(), Text.builder().build());
assertEquals(Text.of(), Text.builder("").build());
assertEquals(Text.of(), Text.builder().append().build());
assertEquals(Text.of(), Text.builder().append("").build());
assertNotEquals(Text.of(), Text.of("??"));
assertNotEquals(Text.of(), Text.builder("??").build());
assertNotEquals(Text.of(), Text.builder().append("??").build());
assertEquals(Text.of("message"), Text.builder("message").build());
assertEquals(Text.of("message"), Text.builder(Text.of("message")).build());
assertEquals(Text.of("message"), Text.builder().append("message").build());
assertEquals(Text.of("message"), Text.builder().append(Text.of("message")).build());
}
@Test
void isEmpty() {
assertTrue(Text.of().isEmpty());
assertTrue(Text.of((String) null).isEmpty());
assertTrue(Text.of((Text) null).isEmpty());
assertTrue(Text.of("").isEmpty());
assertTrue(Text.of("", "").isEmpty());
assertTrue(Text.builder().build().isEmpty());
assertTrue(Text.builder((String) null).build().isEmpty());
assertTrue(Text.builder((Text) null).build().isEmpty());
assertTrue(Text.builder("").build().isEmpty());
assertTrue(Text.builder("", "").build().isEmpty());
assertTrue(Text.builder(Text.of()).build().isEmpty());
assertTrue(Text.builder(Text.of(), Text.of()).build().isEmpty());
assertTrue(Text.builder().append().build().isEmpty());
assertTrue(Text.builder().append((String) null).build().isEmpty());
assertTrue(Text.builder().append((Text) null).build().isEmpty());
assertTrue(Text.builder().append("").build().isEmpty());
assertTrue(Text.builder().append(Text.of()).build().isEmpty());
assertTrue(Text.builder().append(Text.of(), Text.of()).build().isEmpty());
assertTrue(Text.builder().append(Text.of()).append(Text.of()).build().isEmpty());
assertFalse(Text.of("??").isEmpty());
assertFalse(Text.builder("??").build().isEmpty());
assertFalse(Text.builder(Text.of("??")).build().isEmpty());
assertFalse(Text.builder().append("??").build().isEmpty());
assertFalse(Text.builder().append(Text.of("??")).build().isEmpty());
}
}

View File

@@ -0,0 +1,25 @@
package mc.core.utils;
import org.junit.jupiter.api.Test;
import java.util.concurrent.ThreadLocalRandom;
import static org.junit.jupiter.api.Assertions.assertEquals;
class CompactedCoordsTest {
@Test
void compressXZ() {
ThreadLocalRandom random = ThreadLocalRandom.current();
for (int i = 0; i < 100; i++) {
final int x = random.nextInt(Short.MIN_VALUE, Short.MAX_VALUE);
final int z = random.nextInt(Short.MIN_VALUE, Short.MAX_VALUE);
final int compressXZ = CompactedCoords.compressXZ(x, z);
int[] xz = CompactedCoords.uncompressXZ(compressXZ);
assertEquals(x, xz[0]);
assertEquals(z, xz[1]);
}
}
}

View File

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

View File

@@ -0,0 +1,39 @@
package mc.core.world.block;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.concurrent.ThreadLocalRandom;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
class BlockLocationTest {
private static final ThreadLocalRandom rnd = ThreadLocalRandom.current();
private static final int minI = 0, maxI = 10;
private int x, y, z;
@BeforeEach
void before() {
x = rnd.nextInt(minI, maxI);
y = rnd.nextInt(minI, maxI);
z = rnd.nextInt(minI, maxI);
}
@Test
void equals_() {
BlockLocation loc1 = new BlockLocation(x, y, z);
BlockLocation loc2 = new BlockLocation(x, y, z);
assertEquals(loc1, loc2);
loc2 = new BlockLocation(x+1, y+2, z-3);
assertNotEquals(loc1, loc2);
}
@Test
void clone_() {
BlockLocation locOrig = new BlockLocation(x, y, z);
BlockLocation locClone = locOrig.clone();
assertEquals(locOrig, locClone);
}
}