diff --git a/src/main/java/ru/nanit/limbo/NanoLimbo.java b/src/main/java/ru/nanit/limbo/NanoLimbo.java index 16ab4a8..177be2d 100644 --- a/src/main/java/ru/nanit/limbo/NanoLimbo.java +++ b/src/main/java/ru/nanit/limbo/NanoLimbo.java @@ -3,6 +3,7 @@ package ru.nanit.limbo; import ru.nanit.limbo.server.LimboServer; import ru.nanit.limbo.util.Logger; import ru.nanit.limbo.world.DefaultDimension; +import ru.nanit.limbo.world.DefaultWorld; import java.io.InputStream; import java.nio.file.Paths; @@ -14,6 +15,7 @@ public final class NanoLimbo { public void start() throws Exception { LimboConfig.load(Paths.get("./settings.properties")); DefaultDimension.init(); + DefaultWorld.init(); server = new LimboServer(); server.start(); diff --git a/src/main/java/ru/nanit/limbo/connection/ClientConnection.java b/src/main/java/ru/nanit/limbo/connection/ClientConnection.java index c158d5c..4e8e2ff 100644 --- a/src/main/java/ru/nanit/limbo/connection/ClientConnection.java +++ b/src/main/java/ru/nanit/limbo/connection/ClientConnection.java @@ -4,11 +4,10 @@ import io.netty.channel.Channel; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; +import net.kyori.adventure.nbt.CompoundBinaryTag; import ru.nanit.limbo.LimboConfig; import ru.nanit.limbo.protocol.packets.login.*; -import ru.nanit.limbo.protocol.packets.play.PacketJoinGame; -import ru.nanit.limbo.protocol.packets.play.PacketPlayerPositionAndLook; -import ru.nanit.limbo.protocol.packets.play.PacketUpdateViewPos; +import ru.nanit.limbo.protocol.packets.play.*; import ru.nanit.limbo.protocol.registry.Version; import ru.nanit.limbo.protocol.pipeline.PacketDecoder; import ru.nanit.limbo.protocol.pipeline.PacketEncoder; @@ -21,6 +20,7 @@ import ru.nanit.limbo.server.LimboServer; import ru.nanit.limbo.util.Logger; import ru.nanit.limbo.util.UuidUtil; import ru.nanit.limbo.world.DefaultDimension; +import ru.nanit.limbo.world.DefaultWorld; import java.util.concurrent.ThreadLocalRandom; @@ -33,13 +33,14 @@ public class ClientConnection extends ChannelInboundHandlerAdapter { private String username; public ClientConnection(Channel channel, LimboServer server){ - this.channel = channel; this.server = server; + this.channel = channel; } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { if (state.equals(State.PLAY)){ + server.decrementPlayers(); Logger.info("Player %s disconnected", this.username); } super.channelInactive(ctx); @@ -47,7 +48,9 @@ public class ClientConnection extends ChannelInboundHandlerAdapter { @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { - Logger.error("Unhandled exception: %s", cause.getMessage()); + if (channel.isActive()){ + Logger.error("Unhandled exception: %s", cause.getMessage()); + } } @Override @@ -71,6 +74,11 @@ public class ClientConnection extends ChannelInboundHandlerAdapter { } if (packet instanceof PacketLoginStart){ + if (server.getPlayersCount() >= LimboConfig.getMaxPlayers()){ + disconnect("Too many players connected"); + return; + } + this.username = ((PacketLoginStart) packet).getUsername(); PacketLoginSuccess loginSuccess = new PacketLoginSuccess(); @@ -80,9 +88,16 @@ public class ClientConnection extends ChannelInboundHandlerAdapter { sendPacket(loginSuccess); updateState(State.PLAY); + + server.incrementPlayers(); Logger.info("Player %s connected", this.username); + startJoinProcess(); } + + if (packet instanceof PacketKeepAlive){ + System.out.println("Get KeepAlive " + ((PacketKeepAlive)packet).getId()); + } } private void startJoinProcess(){ @@ -118,9 +133,40 @@ public class ClientConnection extends ChannelInboundHandlerAdapter { updateViewPos.setChunkX(0); updateViewPos.setChunkY(0); + PacketChunkData chunkData = new PacketChunkData(); + + chunkData.setChunkX(0); + chunkData.setChunkZ(0); + chunkData.setFullChunk(false); + chunkData.setPrimaryBitMask(1); + chunkData.setHeightMaps(DefaultWorld.getHeightMaps()); + chunkData.setData(new byte[0]); + chunkData.setBlockEntities(new CompoundBinaryTag[]{CompoundBinaryTag.empty()}); + sendPacket(joinGame); sendPacket(positionAndLook); sendPacket(updateViewPos); + sendPacket(chunkData); + sendPacket(updateViewPos); + sendPacket(positionAndLook); + + sendKeepAlive(); + } + + public void disconnect(String reason){ + if (isConnected() && state == State.LOGIN){ + PacketDisconnect disconnect = new PacketDisconnect(); + disconnect.setReason(reason); + sendPacketAndClose(disconnect); + } + } + + public void sendKeepAlive(){ + if (state.equals(State.PLAY)){ + PacketKeepAlive keepAlive = new PacketKeepAlive(); + keepAlive.setId(ThreadLocalRandom.current().nextLong()); + sendPacket(keepAlive); + } } public void sendPacket(Object packet){ @@ -133,18 +179,6 @@ public class ClientConnection extends ChannelInboundHandlerAdapter { channel.writeAndFlush(packet).addListener(ChannelFutureListener.CLOSE); } - public void disconnect(){ - if (channel.isActive()){ - channel.close(); - } - } - - public void disconnect(String reason){ - PacketDisconnect packet = new PacketDisconnect(); - packet.setReason(reason); - sendPacketAndClose(packet); - } - public boolean isConnected(){ return channel.isActive(); } diff --git a/src/main/java/ru/nanit/limbo/protocol/ByteMessage.java b/src/main/java/ru/nanit/limbo/protocol/ByteMessage.java index da7467b..0b0ecca 100644 --- a/src/main/java/ru/nanit/limbo/protocol/ByteMessage.java +++ b/src/main/java/ru/nanit/limbo/protocol/ByteMessage.java @@ -120,6 +120,27 @@ public class ByteMessage extends ByteBuf { } } + public void writeVarIntArray(int[] array) { + writeVarInt(array.length); + for (int i : array) { + writeVarInt(i); + } + } + + public void writeCompoundTagArray(CompoundBinaryTag[] compoundTags) { + try { + ByteBufOutputStream stream = new ByteBufOutputStream(buf); + + writeVarInt(compoundTags.length); + + for (CompoundBinaryTag tag : compoundTags){ + BinaryTagIO.writeDataOutput(tag, stream); + } + } catch (IOException e) { + throw new EncoderException("Cannot write NBT CompoundTag"); + } + } + public CompoundBinaryTag readCompoundTag() { try { return BinaryTagIO.readDataInput(new ByteBufInputStream(buf)); diff --git a/src/main/java/ru/nanit/limbo/protocol/packets/play/PacketChunkData.java b/src/main/java/ru/nanit/limbo/protocol/packets/play/PacketChunkData.java new file mode 100644 index 0000000..9205877 --- /dev/null +++ b/src/main/java/ru/nanit/limbo/protocol/packets/play/PacketChunkData.java @@ -0,0 +1,69 @@ +package ru.nanit.limbo.protocol.packets.play; + +import net.kyori.adventure.nbt.CompoundBinaryTag; +import ru.nanit.limbo.protocol.ByteMessage; +import ru.nanit.limbo.protocol.PacketOut; +import ru.nanit.limbo.protocol.registry.Version; + +public class PacketChunkData implements PacketOut { + + private int chunkX; + private int chunkZ; + private boolean fullChunk; + private int primaryBitMask; + private CompoundBinaryTag heightMaps; + private int[] biomes; + private byte[] data; + private CompoundBinaryTag[] blockEntities; + + public void setChunkX(int chunkX) { + this.chunkX = chunkX; + } + + public void setChunkZ(int chunkZ) { + this.chunkZ = chunkZ; + } + + public void setFullChunk(boolean fullChunk) { + this.fullChunk = fullChunk; + } + + public void setPrimaryBitMask(int primaryBitMask) { + this.primaryBitMask = primaryBitMask; + } + + public void setHeightMaps(CompoundBinaryTag heightMaps) { + this.heightMaps = heightMaps; + } + + public void setBiomes(int[] biomes) { + this.biomes = biomes; + } + + public void setData(byte[] data) { + this.data = data; + } + + public void setBlockEntities(CompoundBinaryTag[] blockEntities) { + this.blockEntities = blockEntities; + } + + @Override + public void encode(ByteMessage msg, Version version) { + msg.writeInt(chunkX); + msg.writeInt(chunkZ); + msg.writeBoolean(fullChunk); + msg.writeVarInt(primaryBitMask); + msg.writeCompoundTag(heightMaps); + + if (fullChunk){ + msg.writeVarIntArray(biomes); + } + + + + msg.writeBytesArray(data); + msg.writeCompoundTagArray(blockEntities); + } + +} diff --git a/src/main/java/ru/nanit/limbo/protocol/packets/play/PacketKeepAlive.java b/src/main/java/ru/nanit/limbo/protocol/packets/play/PacketKeepAlive.java new file mode 100644 index 0000000..0df50e4 --- /dev/null +++ b/src/main/java/ru/nanit/limbo/protocol/packets/play/PacketKeepAlive.java @@ -0,0 +1,29 @@ +package ru.nanit.limbo.protocol.packets.play; + +import ru.nanit.limbo.protocol.ByteMessage; +import ru.nanit.limbo.protocol.Packet; +import ru.nanit.limbo.protocol.registry.Version; + +public class PacketKeepAlive implements Packet { + + private long id; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + @Override + public void encode(ByteMessage msg, Version version) { + msg.writeLong(id); + } + + @Override + public void decode(ByteMessage msg, Version version) { + this.id = msg.readLong(); + } + +} diff --git a/src/main/java/ru/nanit/limbo/protocol/packets/play/PacketPlayerPositionAndLook.java b/src/main/java/ru/nanit/limbo/protocol/packets/play/PacketPlayerPositionAndLook.java index 905dc70..b0b5f40 100644 --- a/src/main/java/ru/nanit/limbo/protocol/packets/play/PacketPlayerPositionAndLook.java +++ b/src/main/java/ru/nanit/limbo/protocol/packets/play/PacketPlayerPositionAndLook.java @@ -11,7 +11,7 @@ public class PacketPlayerPositionAndLook implements PacketOut { private double z; private float yaw; private float pitch; - private byte flags = 0x01; + private byte flags = 0x08; private int teleportId; public void setX(double x) { diff --git a/src/main/java/ru/nanit/limbo/protocol/registry/State.java b/src/main/java/ru/nanit/limbo/protocol/registry/State.java index 8d0bb11..4567119 100644 --- a/src/main/java/ru/nanit/limbo/protocol/registry/State.java +++ b/src/main/java/ru/nanit/limbo/protocol/registry/State.java @@ -3,9 +3,7 @@ package ru.nanit.limbo.protocol.registry; import ru.nanit.limbo.protocol.Packet; import ru.nanit.limbo.protocol.packets.*; import ru.nanit.limbo.protocol.packets.login.*; -import ru.nanit.limbo.protocol.packets.play.PacketJoinGame; -import ru.nanit.limbo.protocol.packets.play.PacketPlayerPositionAndLook; -import ru.nanit.limbo.protocol.packets.play.PacketUpdateViewPos; +import ru.nanit.limbo.protocol.packets.play.*; import ru.nanit.limbo.protocol.packets.status.PacketStatusPing; import ru.nanit.limbo.protocol.packets.status.PacketStatusRequest; import ru.nanit.limbo.protocol.packets.status.PacketStatusResponse; @@ -38,9 +36,12 @@ public enum State { }, PLAY(3){ { + clientBound.register(Version.V1_16_4, 0x20, PacketChunkData::new); clientBound.register(Version.V1_16_4, 0x24, PacketJoinGame::new); clientBound.register(Version.V1_16_4, 0x34, PacketPlayerPositionAndLook::new); clientBound.register(Version.V1_16_4, 0x40, PacketUpdateViewPos::new); + clientBound.register(Version.V1_16_4, 0x1F, PacketKeepAlive::new); + serverBound.register(Version.V1_16_4, 0x1F, PacketKeepAlive::new); } }; diff --git a/src/main/java/ru/nanit/limbo/server/LimboServer.java b/src/main/java/ru/nanit/limbo/server/LimboServer.java index 293d3e9..99470b7 100644 --- a/src/main/java/ru/nanit/limbo/server/LimboServer.java +++ b/src/main/java/ru/nanit/limbo/server/LimboServer.java @@ -7,11 +7,29 @@ import ru.nanit.limbo.LimboConfig; import ru.nanit.limbo.connection.ClientChannelInitializer; import ru.nanit.limbo.util.Logger; +import java.util.concurrent.atomic.AtomicInteger; + public final class LimboServer { + private AtomicInteger playersCount; + + public int getPlayersCount(){ + return playersCount.get(); + } + + public void incrementPlayers(){ + playersCount.incrementAndGet(); + } + + public void decrementPlayers(){ + playersCount.decrementAndGet(); + } + public void start() throws Exception { Logger.info("Starting server..."); + playersCount = new AtomicInteger(); + ServerBootstrap bootstrap = new ServerBootstrap() .group(new NioEventLoopGroup(), new NioEventLoopGroup()) .channel(NioServerSocketChannel.class) diff --git a/src/main/java/ru/nanit/limbo/world/DefaultWorld.java b/src/main/java/ru/nanit/limbo/world/DefaultWorld.java new file mode 100644 index 0000000..1fbe99a --- /dev/null +++ b/src/main/java/ru/nanit/limbo/world/DefaultWorld.java @@ -0,0 +1,32 @@ +package ru.nanit.limbo.world; + +import net.kyori.adventure.nbt.CompoundBinaryTag; + +public final class DefaultWorld { + + private static CompoundBinaryTag heightMaps; + + private DefaultWorld(){} + + public static void init(){ + heightMaps = CompoundBinaryTag.builder() + .putLongArray("MOTION_BLOCKING", new long[]{ + 1371773531765642314L, 1389823183635651148L, 1371738278539598925L, + 1389823183635388492L, 1353688558756731469L, 1389823114781694027L, 1317765589597723213L, + 1371773531899860042L, 1389823183635651149L, 1371773462911685197L, 1389823183635650636L, + 1353688626805119565L, 1371773531900123211L, 1335639250618849869L, 1371738278674077258L, + 1389823114781694028L, 1353723811310638154L, 1371738278674077259L, 1335674228429068364L, + 1335674228429067338L, 1335674228698027594L, 1317624576693539402L, 1335709481520370249L, + 1299610178184057417L, 1335638906349064264L, 1299574993811968586L, 1299574924958011464L, + 1299610178184056904L, 1299574924958011464L, 1299610109330100296L, 1299574924958011464L, + 1299574924823793736L, 1299574924958011465L, 1281525273222484040L, 1299574924958011464L, + 1281525273222484040L, 9548107335L + }) + .build(); + } + + public static CompoundBinaryTag getHeightMaps(){ + return heightMaps; + } + +} diff --git a/src/main/resources/settings.properties b/src/main/resources/settings.properties index 5ff0d02..ebcc92a 100644 --- a/src/main/resources/settings.properties +++ b/src/main/resources/settings.properties @@ -5,12 +5,14 @@ host=localhost port=65535 max-players=100 +ping-version=NanoLimbo +ping-description={"text": "NanoLimbo"} -# Proxy forwarding support. Available types: NONE, LEGACY, MODERN +# Player info forwarding support. Available types: NONE, LEGACY, MODERN ip-forwarding=LEGACY -# Read timeout for connections in milliseconds -read-timeout=30000 +# If you use MODERN type of forwarding, enter your secret code here +ip-forwarding-secret= -ping-version=NanoLimbo -ping-description={"text": "NanoLimbo"} \ No newline at end of file +# Read timeout for connections in milliseconds +read-timeout=30000 \ No newline at end of file