Archived
0

12 Commits

Author SHA1 Message Date
325546a76d update README 2021-05-09 22:12:15 +03:00
4215b5615e update version 2021-05-09 22:07:25 +03:00
10af38e102 Merge branch 'develop' 2021-05-09 22:05:23 +03:00
b049352fe3 PlayerManager 2021-05-09 20:07:08 +03:00
c4767bd240 config: fake online 2021-05-09 18:54:23 +03:00
2d4895fef0 Merge branch 'dev/world' into develop 2021-05-09 18:48:54 +03:00
04316d9cbd грузим чанки при входе 2021-05-09 18:47:51 +03:00
20791ed881 VoidWorld, VoidChunk 2021-05-09 18:43:57 +03:00
2b0ad9895b add World, Chunk interfaces 2021-05-09 18:42:33 +03:00
ab17160f9d config: add view-distance 2021-05-09 18:41:31 +03:00
8a6f37924e PingPacket -> KeepAlivePacket 2021-05-09 17:00:25 +03:00
f10fb46d23 уменьшена скорость отдачи KeepAlive 2021-05-09 16:59:20 +03:00
30 changed files with 422 additions and 174 deletions

View File

@@ -1,12 +1,13 @@
# MC-SERVER # MC-SERVER
![version: 1.0-SNAPSHOT](https://img.shields.io/badge/version-1.0-05b.svg?style=flat) ![version: 1.1](https://img.shields.io/badge/version-1.1-05b.svg?style=flat)
![codename: ZERO](https://img.shields.io/badge/codename-ZERO-509.svg?style=flat) ![codename: VOID](https://img.shields.io/badge/codename-VOID-509.svg?style=flat)
![protocol: 1.12.2](https://img.shields.io/badge/protocol-1.12.2-075.svg?style=flat) ![protocol: 1.12.2](https://img.shields.io/badge/protocol-1.12.2-075.svg?style=flat)
Написанный с нуля сервер **Minecraft 1.12.2**. Написанный с нуля сервер **Minecraft 1.12.2**.
На данный момент может только показывать информацию о себе. Подключение к серверу не возможно. На данный момент сервер может показывать о себе информацию в списке серверов (motd, онлайн, иконка) и позволять
игрокам подключиться к себе. Загружается пустой мир.
--- ---

View File

@@ -1,3 +1,3 @@
project.group=mc-project project.group=mc-project
project.name=mc-server project.name=mc-server
project.version=1.1-SNAPSHOT project.version=1.1

View File

@@ -2,12 +2,16 @@ package mc.protocol;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
import mc.protocol.api.ConnectionContext; import mc.protocol.api.ConnectionContext;
import mc.protocol.packets.ServerSidePacket; import mc.protocol.packets.ServerSidePacket;
import mc.protocol.pool.Passivable; import mc.protocol.pool.Passivable;
import java.util.Map;
import java.util.Optional;
@EqualsAndHashCode @EqualsAndHashCode
public class NettyConnectionContext implements ConnectionContext, Passivable { public class NettyConnectionContext implements ConnectionContext, Passivable {
@@ -15,6 +19,14 @@ public class NettyConnectionContext implements ConnectionContext, Passivable {
@Setter @Setter
private ChannelHandlerContext ctx; private ChannelHandlerContext ctx;
/**
* @deprecated костыль
*/
@Deprecated
@Getter
@Setter
private boolean usedContext;
@Override @Override
public State getState() { public State getState() {
return ctx.channel().attr(NetworkAttributes.STATE).get(); return ctx.channel().attr(NetworkAttributes.STATE).get();
@@ -25,6 +37,27 @@ public class NettyConnectionContext implements ConnectionContext, Passivable {
ctx.channel().attr(NetworkAttributes.STATE).set(state); ctx.channel().attr(NetworkAttributes.STATE).set(state);
} }
/**
* @deprecated костыль
*/
@Deprecated
@Override
public <T> void setCustomProperty(String key, T value) {
Map<String, Object> map = ctx.channel().attr(NetworkAttributes.CUSTOM_PROPERTIES).get();
map.put(key, value);
}
/**
* @deprecated костыль
*/
@Deprecated
@SuppressWarnings("unchecked")
@Override
public <T> Optional<T> getCustomProperty(String key, Class<T> classResult) {
Map<String, Object> map = ctx.channel().attr(NetworkAttributes.CUSTOM_PROPERTIES).get();
return (Optional<T>) Optional.ofNullable(map.getOrDefault(key, null));
}
@Override @Override
public void send(ServerSidePacket packet) { public void send(ServerSidePacket packet) {
ctx.write(packet); ctx.write(packet);

View File

@@ -3,8 +3,16 @@ package mc.protocol;
import io.netty.util.AttributeKey; import io.netty.util.AttributeKey;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
import java.util.Map;
@UtilityClass @UtilityClass
public class NetworkAttributes { public class NetworkAttributes {
public static final AttributeKey<State> STATE = AttributeKey.newInstance("STATE"); public static final AttributeKey<State> STATE = AttributeKey.newInstance("STATE");
/**
* @deprecated костыль
*/
@Deprecated
public static final AttributeKey<Map<String, Object>> CUSTOM_PROPERTIES = AttributeKey.newInstance("CUSTOM_PROPERTIES");
} }

View File

@@ -3,14 +3,20 @@ package mc.protocol;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.SimpleChannelInboundHandler;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import mc.protocol.packets.ClientSidePacket; import mc.protocol.packets.ClientSidePacket;
import mc.protocol.event.EventBus; import mc.protocol.event.EventBus;
import mc.protocol.pool.PacketPool; import mc.protocol.pool.PacketPool;
import org.apache.commons.pool2.ObjectPool; import org.apache.commons.pool2.ObjectPool;
import java.io.IOException;
@Slf4j
@RequiredArgsConstructor @RequiredArgsConstructor
public class PacketInboundHandler extends SimpleChannelInboundHandler<ClientSidePacket> { public class PacketInboundHandler extends SimpleChannelInboundHandler<ClientSidePacket> {
private static final String CLIENT_FORCE_DISCONNECTED_IOEXCEPTION_MESSAGE_RU = "Программа на вашем хост-компьютере разорвала установленное подключение";
private final ObjectPool<NettyConnectionContext> poolNettyConnectionContext; private final ObjectPool<NettyConnectionContext> poolNettyConnectionContext;
private final PacketPool poolPackets; private final PacketPool poolPackets;
private final EventBus eventBus; private final EventBus eventBus;
@@ -22,7 +28,21 @@ public class PacketInboundHandler extends SimpleChannelInboundHandler<ClientSide
NettyConnectionContext context = poolNettyConnectionContext.borrowObject().setCtx(ctx); NettyConnectionContext context = poolNettyConnectionContext.borrowObject().setCtx(ctx);
eventBus.emit(state, context, packet); eventBus.emit(state, context, packet);
poolNettyConnectionContext.returnObject(context); if (!context.isUsedContext()) {
poolNettyConnectionContext.returnObject(context);
}
poolPackets.returnObject(packet); poolPackets.returnObject(packet);
} }
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
if (cause instanceof IOException && cause.getLocalizedMessage().equalsIgnoreCase(CLIENT_FORCE_DISCONNECTED_IOEXCEPTION_MESSAGE_RU)) {
log.warn("Client '{}' force disconnected", ctx.channel().remoteAddress());
if (log.isTraceEnabled()) {
log.trace("", cause);
}
} else {
log.error("{}", cause.getMessage(), cause);
}
}
} }

View File

@@ -4,7 +4,7 @@ import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import mc.protocol.packets.ClientSidePacket; import mc.protocol.packets.ClientSidePacket;
import mc.protocol.packets.Packet; import mc.protocol.packets.Packet;
import mc.protocol.packets.PingPacket; import mc.protocol.packets.KeepAlivePacket;
import mc.protocol.packets.ServerSidePacket; import mc.protocol.packets.ServerSidePacket;
import mc.protocol.packets.client.*; import mc.protocol.packets.client.*;
import mc.protocol.packets.server.*; import mc.protocol.packets.server.*;
@@ -24,12 +24,12 @@ public enum State {
// client side // client side
Map.of( Map.of(
0x00, StatusServerRequestPacket.class, 0x00, StatusServerRequestPacket.class,
0x01, PingPacket.class 0x01, KeepAlivePacket.class
), ),
// server side // server side
Map.of( Map.of(
StatusServerResponse.class, 0x00, StatusServerResponse.class, 0x00,
PingPacket.class, 0x01 KeepAlivePacket.class, 0x01
) )
), ),
LOGIN(2, LOGIN(2,
@@ -47,19 +47,17 @@ public enum State {
0x00, TeleportConfirmPacket.class, 0x00, TeleportConfirmPacket.class,
0x04, ClientSettingsPacket.class, 0x04, ClientSettingsPacket.class,
0x09, PluginMessagePacket.class, 0x09, PluginMessagePacket.class,
0x0B, PingPacket.class, 0x0B, KeepAlivePacket.class,
0x0D, PlayerPositionPacket.class, 0x0D, PlayerPositionPacket.class,
0x0E, CPlayerPositionAndLookPacket.class, 0x0E, CPlayerPositionAndLookPacket.class,
0x0F, PlayerLookPacket.class, 0x0F, PlayerLookPacket.class,
0x15, EntityActionPacket.class, 0x15, EntityActionPacket.class
0x1C, UpdateSignPacket.class
), ),
// client bound // client bound
Map.of( Map.of(
PingPacket.class, 0x1F, KeepAlivePacket.class, 0x1F,
ChunkDataPacket.class, 0x20, ChunkDataPacket.class, 0x20,
JoinGamePacket.class, 0x23, JoinGamePacket.class, 0x23,
OpenSignEditorPacket.class, 0x2A,
PlayerAbilitiesPacket.class,0x2C, PlayerAbilitiesPacket.class,0x2C,
SPlayerPositionAndLookPacket.class, 0x2F, SPlayerPositionAndLookPacket.class, 0x2F,
SpawnPositionPacket.class, 0x46 SpawnPositionPacket.class, 0x46

View File

@@ -3,11 +3,37 @@ package mc.protocol.api;
import mc.protocol.State; import mc.protocol.State;
import mc.protocol.packets.ServerSidePacket; import mc.protocol.packets.ServerSidePacket;
import java.util.Optional;
public interface ConnectionContext { public interface ConnectionContext {
/**
* @deprecated костыль
*/
@Deprecated
void setUsedContext(boolean value);
/**
* @deprecated костыль
*/
@Deprecated
boolean isUsedContext();
State getState(); State getState();
void setState(State state); void setState(State state);
/**
* @deprecated костыль
*/
@Deprecated
<T> void setCustomProperty(String key, T value);
/**
* @deprecated костыль
*/
@Deprecated
<T> Optional<T> getCustomProperty(String key, Class<T> classResult);
void send(ServerSidePacket packet); void send(ServerSidePacket packet);
void sendNow(ServerSidePacket packet); void sendNow(ServerSidePacket packet);
void flushSending(); void flushSending();

View File

@@ -17,6 +17,7 @@ import mc.protocol.pool.PacketPool;
import org.apache.commons.pool2.ObjectPool; import org.apache.commons.pool2.ObjectPool;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.function.Consumer; import java.util.function.Consumer;
@@ -36,6 +37,8 @@ public class ProtocolDecoder extends ByteToMessageDecoder {
@Override @Override
public void channelActive(@Nonnull ChannelHandlerContext ctx) throws Exception { public void channelActive(@Nonnull ChannelHandlerContext ctx) throws Exception {
ctx.channel().attr(NetworkAttributes.CUSTOM_PROPERTIES).set(new HashMap<>());
NettyConnectionContext context = poolNettyConnectionContext.borrowObject().setCtx(ctx); NettyConnectionContext context = poolNettyConnectionContext.borrowObject().setCtx(ctx);
consumerNewConnection.accept(context); consumerNewConnection.accept(context);
@@ -48,6 +51,9 @@ public class ProtocolDecoder extends ByteToMessageDecoder {
NettyConnectionContext context = poolNettyConnectionContext.borrowObject().setCtx(ctx); NettyConnectionContext context = poolNettyConnectionContext.borrowObject().setCtx(ctx);
consumerDisconnect.accept(context); consumerDisconnect.accept(context);
ctx.channel().attr(NetworkAttributes.CUSTOM_PROPERTIES).get().clear();
ctx.channel().attr(NetworkAttributes.CUSTOM_PROPERTIES).set(null);
poolNettyConnectionContext.returnObject(context); poolNettyConnectionContext.returnObject(context);
super.channelInactive(ctx); super.channelInactive(ctx);
} }

View File

@@ -9,4 +9,16 @@ public class Location {
private double x; private double x;
private double y; private double y;
private double z; private double z;
public int getIntX() {
return (int) x;
}
public int getIntZ() {
return (int) z;
}
public Location toChunkXZ() {
return new Location(this.getIntX() >> 4, 0d, this.getIntZ() >> 4);
}
} }

View File

@@ -25,7 +25,7 @@ import mc.protocol.io.NetByteBuf;
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=7368#Keep_Alive">Keep Alive</a> * @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=7368#Keep_Alive">Keep Alive</a>
*/ */
@Data @Data
public class PingPacket implements ClientSidePacket, ServerSidePacket { public class KeepAlivePacket implements ClientSidePacket, ServerSidePacket {
private Long payload; private Long payload;

View File

@@ -1,57 +0,0 @@
package mc.protocol.packets.client;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import mc.protocol.io.NetByteBuf;
import mc.protocol.model.Location;
import mc.protocol.packets.ClientSidePacket;
import mc.protocol.packets.server.OpenSignEditorPacket;
import mc.protocol.serializer.LocationSerializer;
/**
* Update Sign packet.
*
* <p>Отправляется клиентом, когда нажата кнопка "Done" в окне редактирования таблички.</p>
*
* <p>Структура пакета</p>
* <pre>
* | FIELD | TYPE | NOTES |
* |----------|--------------|--------------------------------|
* | Location | Location | Позиция редактируемой табилчки |
* | Line 1 | String (384) | Строка таблички |
* | Line 2 | String (384) | Строка таблички |
* | Line 3 | String (384) | Строка таблички |
* | Line 4 | String (384) | Строка таблички |
* </pre>
*
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Update_Sign" target="_top">Update Sign</a>
* @see OpenSignEditorPacket
*/
@NoArgsConstructor
@Getter
@EqualsAndHashCode
@ToString
public class UpdateSignPacket implements ClientSidePacket {
private Location location;
@SuppressWarnings("MismatchedReadAndWriteOfArray")
private String[] lines;
@Override
public void readSelf(NetByteBuf netByteBuf) {
this.location = LocationSerializer.fromLongValue(netByteBuf.readLong());
this.lines = new String[4];
this.lines[0] = netByteBuf.readString(384);
this.lines[1] = netByteBuf.readString(384);
this.lines[2] = netByteBuf.readString(384);
this.lines[3] = netByteBuf.readString(384);
}
@Override
public void passivate() {
this.location = null;
this.lines = null;
}
}

View File

@@ -28,13 +28,17 @@ import mc.protocol.packets.ServerSidePacket;
@Data @Data
public class ChunkDataPacket implements ServerSidePacket { public class ChunkDataPacket implements ServerSidePacket {
private static NetByteBuf voidData;
private int x; private int x;
private int z; private int z;
@SuppressWarnings("java:S125")
@Override @Override
public void writeSelf(NetByteBuf netByteBuf) { public void writeSelf(NetByteBuf netByteBuf) {
netByteBuf.writeInt(x); netByteBuf.writeInt(x);
netByteBuf.writeInt(z); netByteBuf.writeInt(z);
/* Временное отключение кода
netByteBuf.writeBoolean(true); // Is Full chunk netByteBuf.writeBoolean(true); // Is Full chunk
netByteBuf.writeVarInt(0b11111111); // Available Sections netByteBuf.writeVarInt(0b11111111); // Available Sections
@@ -64,6 +68,41 @@ public class ChunkDataPacket implements ServerSidePacket {
netByteBuf.writeVarInt(data.readableBytes()); // Size of Data netByteBuf.writeVarInt(data.readableBytes()); // Size of Data
netByteBuf.writeBytes(data); // Data netByteBuf.writeBytes(data); // Data
netByteBuf.writeVarInt(0); // Number of block entities netByteBuf.writeVarInt(0); // Number of block entities
/* write NBT's */ // write NBT's
*/
netByteBuf.writeBytes(voidData);
voidData.resetReaderIndex();
voidData.resetWriterIndex();
}
static {
voidData = new NetByteBuf(Unpooled.buffer());
voidData.writeBoolean(true); // Is Full chunk
voidData.writeVarInt(0b11111111); // Available Sections
NetByteBuf data = new NetByteBuf(Unpooled.buffer());
for (int i = 0; i < 16; i++) {
NetByteBuf dataBuff = new NetByteBuf(Unpooled.wrappedBuffer(new byte[4096]));
NetByteBuf blockLight = new NetByteBuf(Unpooled.wrappedBuffer(new byte[2048]));
NetByteBuf skyLight = new NetByteBuf(Unpooled.wrappedBuffer(new byte[2048]));
NetByteBuf biomes = new NetByteBuf(Unpooled.wrappedBuffer(new byte[256]));
data.writeUnsignedByte(13);
data.writeUnsignedByte(0);
data.writeVarInt(dataBuff.readableBytes());
data.writeBytes(dataBuff);
data.writeBytes(blockLight);
data.writeBytes(skyLight);
data.writeBytes(biomes);
}
voidData.writeVarInt(data.readableBytes());
voidData.writeBytes(data);
voidData.writeVarInt(0);
voidData.markReaderIndex();
voidData.markWriterIndex();
} }
} }

View File

@@ -1,33 +0,0 @@
package mc.protocol.packets.server;
import lombok.Data;
import mc.protocol.io.NetByteBuf;
import mc.protocol.model.Location;
import mc.protocol.packets.ServerSidePacket;
import mc.protocol.packets.client.UpdateSignPacket;
import mc.protocol.serializer.LocationSerializer;
/**
* Открыть окно редактирования таблички.
*
* <p>Структура пакета</p>
* <pre>
* | FIELD | TYPE | NOTES |
* |----------|----------|-------|
* | Location | Location | |
* </pre>
*
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Open_Sign_Editor">Open Sign Editor</a>
* @see UpdateSignPacket
*/
@Data
public class OpenSignEditorPacket implements ServerSidePacket {
private Location location;
@Override
public void writeSelf(NetByteBuf netByteBuf) {
long value = LocationSerializer.toLongValue(this.location);
netByteBuf.writeLong(value);
}
}

View File

@@ -4,7 +4,6 @@ import lombok.Data;
import mc.protocol.io.NetByteBuf; import mc.protocol.io.NetByteBuf;
import mc.protocol.model.Location; import mc.protocol.model.Location;
import mc.protocol.packets.ServerSidePacket; import mc.protocol.packets.ServerSidePacket;
import mc.protocol.serializer.LocationSerializer;
/** /**
* Спавн позиция игрока. * Спавн позиция игрока.
@@ -15,7 +14,7 @@ import mc.protocol.serializer.LocationSerializer;
* <pre> * <pre>
* | FIELD | TYPE | NOTES | * | FIELD | TYPE | NOTES |
* |----------|----------|-----------------------| * |----------|----------|-----------------------|
* | Location | Location | Локация спавна игрока | * | Location | Position | Локация спавна игрока |
* </pre> * </pre>
* *
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Spawn_Position">Spawn Position</a> * @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Spawn_Position">Spawn Position</a>
@@ -27,7 +26,16 @@ public class SpawnPositionPacket implements ServerSidePacket {
@Override @Override
public void writeSelf(NetByteBuf netByteBuf) { public void writeSelf(NetByteBuf netByteBuf) {
long spawnSerialized = LocationSerializer.toLongValue(this.spawn); long spawnSerialized =
((long) (floorDouble(spawn.getX()) & 0x3FFFFFF) << 38)
| ((long) (floorDouble(spawn.getY()) & 0xFFF) << 26)
| (floorDouble(spawn.getZ()) & 0x3FFFFFF);
netByteBuf.writeLong(spawnSerialized); netByteBuf.writeLong(spawnSerialized);
} }
private static int floorDouble(double value) {
int i = (int) value;
return value < (double) i ? i - 1 : i;
}
} }

View File

@@ -1,26 +0,0 @@
package mc.protocol.serializer;
import lombok.experimental.UtilityClass;
import mc.protocol.model.Location;
@UtilityClass
public class LocationSerializer {
public long toLongValue(Location location) {
return ((long) (floorDouble(location.getX()) & 0x3FFFFFF) << 38)
| ((long) (floorDouble(location.getY()) & 0xFFF) << 26)
| (floorDouble(location.getZ()) & 0x3FFFFFF);
}
public Location fromLongValue(long value) {
return new Location(
value >> 38,
(value >> 26) & 0xFFF,
value << 38 >> 38);
}
private static int floorDouble(double value) {
int i = (int) value;
return value < (double) i ? i - 1 : i;
}
}

View File

@@ -0,0 +1,7 @@
package mc.protocol.world;
public interface Chunk {
int getX();
int getZ();
}

View File

@@ -0,0 +1,13 @@
package mc.protocol.world;
import mc.protocol.model.Location;
import mc.protocol.utils.LevelType;
public interface World {
LevelType getLevelType();
Location getSpawn();
Chunk getChunk(int x, int z);
}

View File

@@ -12,7 +12,7 @@ import mc.protocol.api.Server;
import mc.protocol.di.DaggerProtocolComponent; import mc.protocol.di.DaggerProtocolComponent;
import mc.protocol.di.ProtocolComponent; import mc.protocol.di.ProtocolComponent;
import mc.protocol.di.ProtocolModule; import mc.protocol.di.ProtocolModule;
import mc.protocol.packets.PingPacket; import mc.protocol.packets.KeepAlivePacket;
import mc.protocol.packets.client.HandshakePacket; import mc.protocol.packets.client.HandshakePacket;
import mc.protocol.packets.client.LoginStartPacket; import mc.protocol.packets.client.LoginStartPacket;
import mc.protocol.packets.client.StatusServerRequestPacket; import mc.protocol.packets.client.StatusServerRequestPacket;
@@ -20,6 +20,7 @@ import mc.server.config.Config;
import mc.server.di.ConfigModule; import mc.server.di.ConfigModule;
import mc.server.di.DaggerServerComponent; import mc.server.di.DaggerServerComponent;
import mc.server.di.ServerComponent; import mc.server.di.ServerComponent;
import mc.server.service.PlayerManager;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -48,6 +49,7 @@ public class Main {
.build(); .build();
Config config = serverComponent.getConfig(); Config config = serverComponent.getConfig();
PlayerManager playerManager = serverComponent.getPlayerManager();
ProtocolComponent protocolComponent = DaggerProtocolComponent.builder() ProtocolComponent protocolComponent = DaggerProtocolComponent.builder()
.protocolModule(new ProtocolModule(true)) .protocolModule(new ProtocolModule(true))
@@ -57,13 +59,16 @@ public class Main {
PacketHandler packetHandler = serverComponent.getPacketHandler(); PacketHandler packetHandler = serverComponent.getPacketHandler();
server.onNewConnect(connectionContext -> connectionContext.setState(State.HANDSHAKING)); server.onNewConnect(connectionContext -> connectionContext.setState(State.HANDSHAKING));
server.onDisonnect(connectionContext -> connectionContext.setState(null)); server.onDisonnect(connectionContext -> {
connectionContext.setState(null);
connectionContext.getCustomProperty("player", Player.class).ifPresent(playerManager::remove);
});
server.listenPacket(State.HANDSHAKING, HandshakePacket.class, packetHandler::onHandshake); server.listenPacket(State.HANDSHAKING, HandshakePacket.class, packetHandler::onHandshake);
server.listenPacket(State.STATUS, PingPacket.class, packetHandler::onKeepAlive); server.listenPacket(State.STATUS, KeepAlivePacket.class, packetHandler::onKeepAlive);
server.listenPacket(State.STATUS, StatusServerRequestPacket.class, packetHandler::onServerStatus); server.listenPacket(State.STATUS, StatusServerRequestPacket.class, packetHandler::onServerStatus);
server.listenPacket(State.LOGIN, LoginStartPacket.class, packetHandler::onLoginStart); server.listenPacket(State.LOGIN, LoginStartPacket.class, packetHandler::onLoginStart);
server.listenPacket(State.PLAY, PingPacket.class, packetHandler::onKeepAlivePlay); server.listenPacket(State.PLAY, KeepAlivePacket.class, packetHandler::onKeepAlivePlay);
server.bind(config.server().host(), config.server().port()); server.bind(config.server().host(), config.server().port());
} }

View File

@@ -7,7 +7,7 @@ import mc.protocol.api.ConnectionContext;
import mc.protocol.model.Location; import mc.protocol.model.Location;
import mc.protocol.model.Look; import mc.protocol.model.Look;
import mc.protocol.model.ServerInfo; import mc.protocol.model.ServerInfo;
import mc.protocol.packets.PingPacket; import mc.protocol.packets.KeepAlivePacket;
import mc.protocol.packets.client.HandshakePacket; import mc.protocol.packets.client.HandshakePacket;
import mc.protocol.packets.client.LoginStartPacket; import mc.protocol.packets.client.LoginStartPacket;
import mc.protocol.packets.client.StatusServerRequestPacket; import mc.protocol.packets.client.StatusServerRequestPacket;
@@ -15,8 +15,10 @@ import mc.protocol.packets.server.*;
import mc.protocol.serializer.TextSerializer; import mc.protocol.serializer.TextSerializer;
import mc.protocol.utils.Difficulty; import mc.protocol.utils.Difficulty;
import mc.protocol.utils.GameMode; import mc.protocol.utils.GameMode;
import mc.protocol.utils.LevelType; import mc.protocol.world.Chunk;
import mc.protocol.world.World;
import mc.server.config.Config; import mc.server.config.Config;
import mc.server.service.PlayerManager;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import java.io.IOException; import java.io.IOException;
@@ -25,7 +27,7 @@ import java.nio.file.Path;
import java.util.Base64; import java.util.Base64;
import java.util.Collections; import java.util.Collections;
import java.util.Random; import java.util.Random;
import java.util.UUID; import java.util.concurrent.TimeUnit;
@Slf4j @Slf4j
@RequiredArgsConstructor @RequiredArgsConstructor
@@ -33,18 +35,27 @@ public class PacketHandler {
private final Random random = new Random(System.currentTimeMillis()); private final Random random = new Random(System.currentTimeMillis());
private final Config config; private final Config config;
private final World world;
private final PlayerManager playerManager;
public void onHandshake(ConnectionContext context, HandshakePacket packet) { public void onHandshake(ConnectionContext context, HandshakePacket packet) {
context.setState(packet.getNextState()); context.setState(packet.getNextState());
} }
public void onKeepAlive(ConnectionContext context, PingPacket packet) { public void onKeepAlive(ConnectionContext context, KeepAlivePacket packet) {
context.sendNow(packet); context.sendNow(packet);
context.disconnect(); context.disconnect();
} }
public void onKeepAlivePlay(ConnectionContext context, PingPacket packet) { public void onKeepAlivePlay(ConnectionContext context, KeepAlivePacket packet) {
context.sendNow(packet); try {
TimeUnit.MILLISECONDS.sleep(50);
context.sendNow(packet);
} catch (InterruptedException e) {
if (log.isTraceEnabled()) {
log.trace("{}", e.getMessage(), e);
}
}
} }
@SuppressWarnings("unused") @SuppressWarnings("unused")
@@ -53,7 +64,11 @@ public class PacketHandler {
serverInfo.version().name(ProtocolConstant.PROTOCOL_NAME); serverInfo.version().name(ProtocolConstant.PROTOCOL_NAME);
serverInfo.version().protocol(ProtocolConstant.PROTOCOL_NUMBER); serverInfo.version().protocol(ProtocolConstant.PROTOCOL_NUMBER);
serverInfo.players().max(config.players().maxOnlile()); serverInfo.players().max(config.players().maxOnlile());
serverInfo.players().online(config.players().onlile()); if (config.players().fakeOnline().enable()) {
serverInfo.players().online(config.players().fakeOnline().value());
} else {
serverInfo.players().online(playerManager.online());
}
serverInfo.players().sample(Collections.emptyList()); serverInfo.players().sample(Collections.emptyList());
serverInfo.description(TextSerializer.fromPlain(config.motd())); serverInfo.description(TextSerializer.fromPlain(config.motd()));
@@ -67,27 +82,29 @@ public class PacketHandler {
context.sendNow(response); context.sendNow(response);
} }
@SuppressWarnings("java:S2589")
public void onLoginStart(ConnectionContext context, LoginStartPacket loginStartPacket) { public void onLoginStart(ConnectionContext context, LoginStartPacket loginStartPacket) {
Player player = playerManager.addAndCreate(context, loginStartPacket.getName(), GameMode.SURVIVAL, world.getSpawn());
context.setCustomProperty("player", player);
var loginSuccessPacket = new LoginSuccessPacket(); var loginSuccessPacket = new LoginSuccessPacket();
loginSuccessPacket.setUuid(UUID.randomUUID()); loginSuccessPacket.setUuid(player.getUuid());
loginSuccessPacket.setName(loginStartPacket.getName()); loginSuccessPacket.setName(player.getName());
context.sendNow(loginSuccessPacket); context.sendNow(loginSuccessPacket);
context.setState(State.PLAY); context.setState(State.PLAY);
var joinGamePacket = new JoinGamePacket(); var joinGamePacket = new JoinGamePacket();
joinGamePacket.setEntityId(random.nextInt()); joinGamePacket.setEntityId(random.nextInt());
joinGamePacket.setGameMode(GameMode.SURVIVAL); joinGamePacket.setGameMode(player.getGameMode());
joinGamePacket.setDimension(0/*Overworld*/); joinGamePacket.setDimension(0/*Overworld*/);
joinGamePacket.setDifficulty(Difficulty.PEACEFUL); joinGamePacket.setDifficulty(Difficulty.PEACEFUL);
joinGamePacket.setLevelType(LevelType.FLAT); joinGamePacket.setLevelType(world.getLevelType());
context.send(joinGamePacket); context.send(joinGamePacket);
Location spawnLocation = new Location(7d, 130d, 7d);
var spawnPositionPacket = new SpawnPositionPacket(); var spawnPositionPacket = new SpawnPositionPacket();
spawnPositionPacket.setSpawn(spawnLocation); spawnPositionPacket.setSpawn(player.getLocation());
context.send(spawnPositionPacket); context.send(spawnPositionPacket);
@@ -103,32 +120,49 @@ public class PacketHandler {
context.flushSending(); context.flushSending();
var chunkDataPacket = new ChunkDataPacket(); Location chunkLocation = player.getLocation().toChunkXZ();
chunkDataPacket.setX(0); Chunk chunk = world.getChunk(chunkLocation.getIntX(), chunkLocation.getIntZ());
chunkDataPacket.setZ(0);
context.sendNow(chunkDataPacket); var chunkDataPacket = new ChunkDataPacket();
chunkDataPacket.setX(chunk.getX());
chunkDataPacket.setZ(chunk.getZ());
context.send(chunkDataPacket);
for (int i = 1; i <= config.world().viewDistance(); i++) {
int minX = chunkLocation.getIntX() - i;
int minZ = chunkLocation.getIntZ() - i;
int maxX = chunkLocation.getIntX() + i;
int maxZ = chunkLocation.getIntZ() + i;
for (int z = minZ; z <= maxZ; z++) {
for (int x = minX; x <= maxX; x++) {
if ((z == minZ || z == maxZ) || (x == minX || x == maxX)) {
chunkDataPacket = new ChunkDataPacket();
chunkDataPacket.setX(x);
chunkDataPacket.setZ(z);
context.send(chunkDataPacket);
}
}
}
}
context.flushSending();
var playerPositionAndLookPacket = new SPlayerPositionAndLookPacket(); var playerPositionAndLookPacket = new SPlayerPositionAndLookPacket();
playerPositionAndLookPacket.setPosition(spawnLocation); playerPositionAndLookPacket.setPosition(player.getLocation());
playerPositionAndLookPacket.setLook(new Look(0f, 0f)); playerPositionAndLookPacket.setLook(new Look(0f, 0f));
playerPositionAndLookPacket.setTeleportId(random.nextInt()); playerPositionAndLookPacket.setTeleportId(random.nextInt());
context.send(playerPositionAndLookPacket); context.send(playerPositionAndLookPacket);
PingPacket pingPacket = new PingPacket(); KeepAlivePacket keepAlivePacket = new KeepAlivePacket();
pingPacket.setPayload(System.currentTimeMillis()); keepAlivePacket.setPayload(System.currentTimeMillis());
context.send(pingPacket); context.send(keepAlivePacket);
context.flushSending(); context.flushSending();
// -- Эксперименты -- //
var openSignEditorPacket = new OpenSignEditorPacket();
openSignEditorPacket.setLocation(spawnLocation);
context.sendNow(openSignEditorPacket);
} }
private static String faviconToBase64(Path iconPath) { private static String faviconToBase64(Path iconPath) {

View File

@@ -0,0 +1,20 @@
package mc.server;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import mc.protocol.api.ConnectionContext;
import mc.protocol.model.Location;
import mc.protocol.utils.GameMode;
import java.util.UUID;
@RequiredArgsConstructor
@Getter
public class Player {
private final ConnectionContext connectionContext;
private final UUID uuid;
private final String name;
private final GameMode gameMode;
private final Location location;
}

View File

@@ -15,6 +15,7 @@ public class Config {
private final Server server = new Server(); private final Server server = new Server();
private final Players players = new Players(); private final Players players = new Players();
private final World world = new World();
private String motd; private String motd;
private String disconnectReason; private String disconnectReason;
@@ -32,7 +33,23 @@ public class Config {
@Setter @Setter
@ToString @ToString
public static class Players { public static class Players {
private final FakeOnline fakeOnline = new FakeOnline();
private int maxOnlile; private int maxOnlile;
private int onlile; }
@Getter
@Setter
@ToString
public static class FakeOnline {
private boolean enable;
private int value;
}
@Getter
@Setter
@ToString
public static class World {
private int viewDistance;
} }
} }

View File

@@ -27,10 +27,15 @@ public class ConfigModule {
config.server().host(fromYamlPath("server/host", map, "127.0.0.1")); config.server().host(fromYamlPath("server/host", map, "127.0.0.1"));
config.server().port(fromYamlPath("server/port", map, 25565)); config.server().port(fromYamlPath("server/port", map, 25565));
config.motd(fromYamlPath("motd", map, "")); config.motd(fromYamlPath("motd", map, ""));
config.disconnectReason(fromYamlPath("disconnect-reason", map, "")); config.disconnectReason(fromYamlPath("disconnect-reason", map, ""));
config.players().maxOnlile(fromYamlPath("players/max-online", map, 0)); config.players().maxOnlile(fromYamlPath("players/max-online", map, 0));
config.players().onlile(fromYamlPath("players/online", map, 0)); config.players().fakeOnline().enable(fromYamlPath("players/fake-online/enable", map, false));
config.players().fakeOnline().value(fromYamlPath("players/fake-online/value", map, 0));
config.world().viewDistance(fromYamlPath("world/view-distance", map, 0));
if (Boolean.TRUE.equals(fromYamlPath("icon/enable", map, false))) { if (Boolean.TRUE.equals(fromYamlPath("icon/enable", map, false))) {
config.iconPath(Paths.get(fromYamlPath("icon/path", map, "favicon.png"))); config.iconPath(Paths.get(fromYamlPath("icon/path", map, "favicon.png")));

View File

@@ -2,14 +2,16 @@ package mc.server.di;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
import mc.protocol.world.World;
import mc.server.PacketHandler; import mc.server.PacketHandler;
import mc.server.config.Config; import mc.server.config.Config;
import mc.server.service.PlayerManager;
@Module @Module
public class PacketHandlerModule { public class PacketHandlerModule {
@Provides @Provides
public PacketHandler providePacketHandler(Config config) { public PacketHandler providePacketHandler(Config config, World world, PlayerManager playerManager) {
return new PacketHandler(config); return new PacketHandler(config, world, playerManager);
} }
} }

View File

@@ -0,0 +1,16 @@
package mc.server.di;
import dagger.Module;
import dagger.Provides;
import mc.protocol.di.ServerScope;
import mc.server.service.PlayerManager;
@Module
public class PlayersModule {
@Provides
@ServerScope
PlayerManager providePlayerManager() {
return new PlayerManager();
}
}

View File

@@ -1,14 +1,18 @@
package mc.server.di; package mc.server.di;
import dagger.Component; import dagger.Component;
import mc.protocol.di.ServerScope;
import mc.server.PacketHandler; import mc.server.PacketHandler;
import mc.server.config.Config; import mc.server.config.Config;
import mc.server.service.PlayerManager;
@Component(modules = { @Component(modules = {
ConfigModule.class, PacketHandlerModule.class ConfigModule.class, PacketHandlerModule.class, WorldModule.class, PlayersModule.class
}) })
@ServerScope
public interface ServerComponent { public interface ServerComponent {
Config getConfig(); Config getConfig();
PacketHandler getPacketHandler(); PacketHandler getPacketHandler();
PlayerManager getPlayerManager();
} }

View File

@@ -0,0 +1,17 @@
package mc.server.di;
import dagger.Module;
import dagger.Provides;
import mc.protocol.di.ServerScope;
import mc.protocol.world.World;
import mc.server.world.VoidWorld;
@Module
public class WorldModule {
@Provides
@ServerScope
public World provideWorld() {
return new VoidWorld();
}
}

View File

@@ -0,0 +1,29 @@
package mc.server.service;
import mc.protocol.api.ConnectionContext;
import mc.protocol.model.Location;
import mc.protocol.utils.GameMode;
import mc.server.Player;
import java.util.LinkedList;
import java.util.UUID;
public class PlayerManager {
private final LinkedList<Player> players = new LinkedList<>();
public Player addAndCreate(ConnectionContext context, String name, GameMode gameMode, Location location) {
context.setUsedContext(true);
Player player = new Player(context, UUID.randomUUID(), name, gameMode, location);
players.add(player);
return player;
}
public void remove(Player player) {
players.remove(player);
}
public int online() {
return players.size();
}
}

View File

@@ -0,0 +1,13 @@
package mc.server.world;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import mc.protocol.world.Chunk;
@RequiredArgsConstructor
@Getter
public class VoidChunk implements Chunk {
private final int x;
private final int z;
}

View File

@@ -0,0 +1,26 @@
package mc.server.world;
import mc.protocol.model.Location;
import mc.protocol.utils.LevelType;
import mc.protocol.world.Chunk;
import mc.protocol.world.World;
public class VoidWorld implements World {
private static final Location spawn = new Location(7d, 130d, 7d);
@Override
public LevelType getLevelType() {
return LevelType.FLAT;
}
@Override
public Location getSpawn() {
return VoidWorld.spawn;
}
@Override
public Chunk getChunk(int x, int z) {
return new VoidChunk(x, z);
}
}

View File

@@ -10,9 +10,14 @@ disconnect-reason: '&4Server is not available.'
players: players:
max-online: 0 max-online: 0
online: 0 fake-online:
enable: false
value: 0
# Размер значка: 64x64 px # Размер значка: 64x64 px
icon: icon:
enable: false enable: false
path: favicon.png path: favicon.png
world:
view-distance: 1