From cc7009caf140c8087e2239f0f0ec3d2b8c50ca08 Mon Sep 17 00:00:00 2001 From: Nanit Date: Fri, 27 Nov 2020 14:19:05 +0200 Subject: [PATCH] Changed config format. Added serializers. Removed static fields from registry classes --- build.gradle | 4 +- libs/napi-configurate-yaml-1.0.jar | Bin 0 -> 82502 bytes src/main/java/ru/nanit/limbo/LimboConfig.java | 217 ------------------ src/main/java/ru/nanit/limbo/NanoLimbo.java | 5 - .../limbo/configuration/LimboConfig.java | 109 +++++++++ .../SocketAddressSerializer.java | 31 +++ .../connection/ClientChannelInitializer.java | 5 +- .../limbo/connection/ClientConnection.java | 27 ++- .../protocol/packets/play/PacketBossBar.java | 62 +---- .../packets/status/PacketStatusResponse.java | 15 +- .../ru/nanit/limbo/server/Connections.java | 34 +++ .../ru/nanit/limbo/server/LimboServer.java | 74 +++--- .../ru/nanit/limbo/server/data/BossBar.java | 119 ++++++++++ .../limbo/server/data/InfoForwarding.java | 53 +++++ .../ru/nanit/limbo/server/data/PingData.java | 43 ++++ .../ru/nanit/limbo/server/data/Position.java | 72 ++++++ src/main/java/ru/nanit/limbo/util/Logger.java | 16 +- .../nanit/limbo/world/DimensionRegistry.java | 86 ++++--- src/main/resources/limbo.yml | 60 +++++ src/main/resources/settings.properties | 71 ------ 20 files changed, 661 insertions(+), 442 deletions(-) create mode 100644 libs/napi-configurate-yaml-1.0.jar delete mode 100644 src/main/java/ru/nanit/limbo/LimboConfig.java create mode 100644 src/main/java/ru/nanit/limbo/configuration/LimboConfig.java create mode 100644 src/main/java/ru/nanit/limbo/configuration/SocketAddressSerializer.java create mode 100644 src/main/java/ru/nanit/limbo/server/Connections.java create mode 100644 src/main/java/ru/nanit/limbo/server/data/BossBar.java create mode 100644 src/main/java/ru/nanit/limbo/server/data/InfoForwarding.java create mode 100644 src/main/java/ru/nanit/limbo/server/data/PingData.java create mode 100644 src/main/java/ru/nanit/limbo/server/data/Position.java create mode 100644 src/main/resources/limbo.yml delete mode 100644 src/main/resources/settings.properties diff --git a/build.gradle b/build.gradle index 3277d27..d3f7274 100644 --- a/build.gradle +++ b/build.gradle @@ -11,13 +11,15 @@ repositories { dependencies { testCompile group: 'junit', name: 'junit', version: '4.12' + compile group: 'org.yaml', name: 'snakeyaml', version: '1.27' + compile files('libs/napi-configurate-yaml-1.0.jar') compile group: 'io.netty', name: 'netty-all', version: '4.1.54.Final' compile group: 'net.kyori', name: 'adventure-nbt', version: '4.1.1' } jar { manifest { - attributes("Main-Class": "ru.nanit.limbo.NanoLimbo") + attributes('Main-Class': 'ru.nanit.limbo.NanoLimbo') } from { diff --git a/libs/napi-configurate-yaml-1.0.jar b/libs/napi-configurate-yaml-1.0.jar new file mode 100644 index 0000000000000000000000000000000000000000..cc69a1245c6b5d1bb4fb5e71916faf84ee1d8a0d GIT binary patch literal 82502 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 YrnSfn_BsT@7W|O}S&u~mBvr(J0b+D-%m4rY literal 0 HcmV?d00001 diff --git a/src/main/java/ru/nanit/limbo/LimboConfig.java b/src/main/java/ru/nanit/limbo/LimboConfig.java deleted file mode 100644 index 1dc7189..0000000 --- a/src/main/java/ru/nanit/limbo/LimboConfig.java +++ /dev/null @@ -1,217 +0,0 @@ -package ru.nanit.limbo; - -import ru.nanit.limbo.protocol.packets.play.PacketBossBar; -import ru.nanit.limbo.util.Colors; -import ru.nanit.limbo.util.Logger; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Properties; - -public final class LimboConfig { - - private static String host; - private static int port; - private static int maxPlayers; - private static String dimensionType; - private static SpawnPosition spawnPosition; - private static IpForwardingType ipForwardingType; - private static long readTimeout; - private static PingData pingData; - private static int debugLevel = 3; - private static JoinMessages joinMessages; - - public static void load(Path file) throws IOException { - if (!Files.exists(file)){ - Files.copy(NanoLimbo.getResource("/settings.properties"), file); - } - - Properties props = new Properties(); - props.load(Files.newInputStream(file)); - - host = props.getProperty("host"); - port = Integer.parseInt(props.getProperty("port")); - - maxPlayers = Integer.parseInt(props.getProperty("max-players")); - - dimensionType = props.getProperty("dimension"); - - String[] posData = props.getProperty("spawn-position").split(","); - - if (posData.length != 3){ - throw new IOException("Invalid spawn position. Check it in the settings.properties file"); - } - - spawnPosition = new SpawnPosition(Double.parseDouble(posData[0]), - Double.parseDouble(posData[1]), - Double.parseDouble(posData[2])); - - ipForwardingType = IpForwardingType.valueOf(props.getProperty("ip-forwarding").toUpperCase()); - readTimeout = Long.parseLong(props.getProperty("read-timeout")); - - pingData = new PingData(); - pingData.setVersion(props.getProperty("ping-version")); - pingData.setDescription(Colors.of(props.getProperty("ping-description"))); - - debugLevel = Integer.parseInt(props.getProperty("debug-level")); - - joinMessages = new JoinMessages(); - - if(props.containsKey("join-message")){ - joinMessages.setChatMessage(Colors.of(props.getProperty("join-message"))); - } - - if(props.containsKey("join-bossbar-text")){ - joinMessages.setBossBarText(Colors.of(props.getProperty("join-bossbar-text"))); - joinMessages.setBossBarHealth(Float.parseFloat(props.getProperty("join-bossbar-health"))); - joinMessages.setBossBarColor(PacketBossBar.Color.valueOf( - props.getProperty("join-bossbar-color").toUpperCase())); - joinMessages.setBossBarDivision(PacketBossBar.Division.valueOf( - props.getProperty("join-bossbar-division").toUpperCase())); - } - } - - public static String getHost() { - return host; - } - - public static int getPort() { - return port; - } - - public static int getMaxPlayers() { - return maxPlayers; - } - - public static String getDimensionType() { - return dimensionType; - } - - public static SpawnPosition getSpawnPosition() { - return spawnPosition; - } - - public static IpForwardingType getIpForwardingType() { - return ipForwardingType; - } - - public static long getReadTimeout() { - return readTimeout; - } - - public static PingData getPingData() { - return pingData; - } - - public static int getDebugLevel() { - return debugLevel; - } - - public static JoinMessages getJoinMessages() { - return joinMessages; - } - - public enum IpForwardingType { - NONE, - LEGACY, - MODERN - } - - public static class SpawnPosition { - - private final double x; - private final double y; - private final double z; - - public SpawnPosition(double x, double y, double z) { - this.x = x; - this.y = y; - this.z = z; - } - - public double getX() { - return x; - } - - public double getY() { - return y; - } - - public double getZ() { - return z; - } - } - - public static class PingData { - - private String version; - private String description; - - public String getVersion() { - return version; - } - - public void setVersion(String version) { - this.version = version; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - } - - public static class JoinMessages { - - private String chatMessage; - private String bossBarText; - private float bossBarHealth; - private PacketBossBar.Color bossBarColor; - private PacketBossBar.Division bossBarDivision; - - public String getChatMessage() { - return chatMessage; - } - - public void setChatMessage(String chatMessage) { - this.chatMessage = chatMessage; - } - - public String getBossBarText() { - return bossBarText; - } - - public void setBossBarText(String bossBarText) { - this.bossBarText = bossBarText; - } - - public float getBossBarHealth() { - return bossBarHealth; - } - - public void setBossBarHealth(float bossBarHealth) { - this.bossBarHealth = bossBarHealth; - } - - public PacketBossBar.Color getBossBarColor() { - return bossBarColor; - } - - public void setBossBarColor(PacketBossBar.Color bossBarColor) { - this.bossBarColor = bossBarColor; - } - - public PacketBossBar.Division getBossBarDivision() { - return bossBarDivision; - } - - public void setBossBarDivision(PacketBossBar.Division bossBarDivision) { - this.bossBarDivision = bossBarDivision; - } - } - -} diff --git a/src/main/java/ru/nanit/limbo/NanoLimbo.java b/src/main/java/ru/nanit/limbo/NanoLimbo.java index 1f5d957..a68c977 100644 --- a/src/main/java/ru/nanit/limbo/NanoLimbo.java +++ b/src/main/java/ru/nanit/limbo/NanoLimbo.java @@ -3,8 +3,6 @@ package ru.nanit.limbo; import ru.nanit.limbo.server.LimboServer; import ru.nanit.limbo.util.Logger; -import java.io.InputStream; - public final class NanoLimbo { public static void main(String[] args){ @@ -15,7 +13,4 @@ public final class NanoLimbo { } } - public static InputStream getResource(String path){ - return NanoLimbo.class.getResourceAsStream(path); - } } diff --git a/src/main/java/ru/nanit/limbo/configuration/LimboConfig.java b/src/main/java/ru/nanit/limbo/configuration/LimboConfig.java new file mode 100644 index 0000000..2208773 --- /dev/null +++ b/src/main/java/ru/nanit/limbo/configuration/LimboConfig.java @@ -0,0 +1,109 @@ +package ru.nanit.limbo.configuration; + +import napi.configurate.Configuration; +import napi.configurate.source.ConfigSources; +import napi.configurate.yaml.YamlConfiguration; +import ru.nanit.limbo.server.data.*; +import ru.nanit.limbo.util.Colors; + +import java.net.SocketAddress; +import java.nio.file.Path; + +public final class LimboConfig { + + private final Path root; + + private SocketAddress address; + private int maxPlayers; + private PingData pingData; + + private String dimensionType; + private Position spawnPosition; + + private boolean useJoinMessage; + private boolean useBossBar; + private String joinMessage; + private BossBar bossBar; + + private InfoForwarding infoForwarding; + private long readTimeout; + private int debugLevel = 3; + + public LimboConfig(Path root){ + this.root = root; + } + + public void load() throws Exception { + Configuration conf = YamlConfiguration.builder() + .source(ConfigSources.resource("/limbo.yml", this).copyTo(root)) + .build(); + + conf.reload(); + + address = conf.getNode("bind").getValue(SocketAddress.class); + maxPlayers = conf.getNode("maxPlayers").getInt(); + pingData = conf.getNode("ping").getValue(PingData.class); + dimensionType = conf.getNode("dimension").getString(); + spawnPosition = conf.getNode("spawnPosition").getValue(Position.class); + useJoinMessage = conf.getNode("joinMessage", "enable").getBoolean(); + useBossBar = conf.getNode("bossBar", "enable").getBoolean(); + + if (useJoinMessage) + joinMessage = Colors.of(conf.getNode("joinMessage", "text").getString()); + + if (useBossBar) + bossBar = conf.getNode("bossBar").getValue(BossBar.class); + + infoForwarding = conf.getNode("infoForwarding").getValue(InfoForwarding.class); + readTimeout = conf.getNode("readTimeout").getLong(); + debugLevel = conf.getNode("debugLevel").getInt(); + } + + public SocketAddress getAddress() { + return address; + } + + public int getMaxPlayers() { + return maxPlayers; + } + + public PingData getPingData() { + return pingData; + } + + public String getDimensionType() { + return dimensionType; + } + + public Position getSpawnPosition() { + return spawnPosition; + } + + public InfoForwarding getInfoForwarding() { + return infoForwarding; + } + + public long getReadTimeout() { + return readTimeout; + } + + public int getDebugLevel() { + return debugLevel; + } + + public boolean isUseJoinMessage() { + return useJoinMessage; + } + + public boolean isUseBossBar() { + return useBossBar; + } + + public String getJoinMessage() { + return joinMessage; + } + + public BossBar getBossBar() { + return bossBar; + } +} diff --git a/src/main/java/ru/nanit/limbo/configuration/SocketAddressSerializer.java b/src/main/java/ru/nanit/limbo/configuration/SocketAddressSerializer.java new file mode 100644 index 0000000..f67bd9f --- /dev/null +++ b/src/main/java/ru/nanit/limbo/configuration/SocketAddressSerializer.java @@ -0,0 +1,31 @@ +package ru.nanit.limbo.configuration; + +import napi.configurate.data.ConfigNode; +import napi.configurate.serializing.NodeSerializer; +import napi.configurate.serializing.NodeSerializingException; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; + +public class SocketAddressSerializer implements NodeSerializer { + + @Override + public SocketAddress deserialize(ConfigNode node) { + String ip = node.getNode("ip").getString(); + int port = node.getNode("port").getInt(); + SocketAddress address; + + if (ip == null || ip.isEmpty()){ + address = new InetSocketAddress(port); + } else { + address = new InetSocketAddress(ip, port); + } + + return address; + } + + @Override + public void serialize(SocketAddress socketAddress, ConfigNode configNode) throws NodeSerializingException { + + } +} diff --git a/src/main/java/ru/nanit/limbo/connection/ClientChannelInitializer.java b/src/main/java/ru/nanit/limbo/connection/ClientChannelInitializer.java index 688eaf5..34c7712 100644 --- a/src/main/java/ru/nanit/limbo/connection/ClientChannelInitializer.java +++ b/src/main/java/ru/nanit/limbo/connection/ClientChannelInitializer.java @@ -4,7 +4,7 @@ import io.netty.channel.Channel; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.handler.timeout.ReadTimeoutHandler; -import ru.nanit.limbo.LimboConfig; +import ru.nanit.limbo.configuration.LimboConfig; import ru.nanit.limbo.protocol.pipeline.VarIntFrameDecoder; import ru.nanit.limbo.protocol.pipeline.PacketDecoder; import ru.nanit.limbo.protocol.pipeline.PacketEncoder; @@ -25,7 +25,8 @@ public class ClientChannelInitializer extends ChannelInitializer { protected void initChannel(Channel channel) { ChannelPipeline pipeline = channel.pipeline(); - pipeline.addLast("timeout", new ReadTimeoutHandler(LimboConfig.getReadTimeout(), TimeUnit.MILLISECONDS)); + pipeline.addLast("timeout", new ReadTimeoutHandler(server.getConfig().getReadTimeout(), + TimeUnit.MILLISECONDS)); pipeline.addLast("frame_decoder", new VarIntFrameDecoder()); pipeline.addLast("frame_encoder", new VarIntLengthEncoder()); pipeline.addLast("decoder", new PacketDecoder()); diff --git a/src/main/java/ru/nanit/limbo/connection/ClientConnection.java b/src/main/java/ru/nanit/limbo/connection/ClientConnection.java index 8e0f7c8..6c7507f 100644 --- a/src/main/java/ru/nanit/limbo/connection/ClientConnection.java +++ b/src/main/java/ru/nanit/limbo/connection/ClientConnection.java @@ -4,7 +4,6 @@ import io.netty.channel.Channel; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; -import ru.nanit.limbo.LimboConfig; import ru.nanit.limbo.protocol.packets.login.*; import ru.nanit.limbo.protocol.packets.play.*; import ru.nanit.limbo.protocol.pipeline.PacketDecoder; @@ -18,7 +17,6 @@ import ru.nanit.limbo.protocol.registry.Version; import ru.nanit.limbo.server.LimboServer; import ru.nanit.limbo.util.Logger; import ru.nanit.limbo.util.UuidUtil; -import ru.nanit.limbo.world.DimensionRegistry; import java.util.UUID; import java.util.concurrent.ThreadLocalRandom; @@ -50,7 +48,7 @@ public class ClientConnection extends ChannelInboundHandlerAdapter { @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { if (state.equals(State.PLAY)){ - server.removeConnection(this); + server.getConnections().removeConnection(this); Logger.info("Player %s disconnected", this.username); } super.channelInactive(ctx); @@ -73,10 +71,11 @@ public class ClientConnection extends ChannelInboundHandlerAdapter { PacketHandshake handshake = (PacketHandshake) packet; updateState(State.getById(handshake.getNextState())); clientVersion = handshake.getVersion(); + Logger.debug("Pinged from " + handshake.getHost() + ":" + handshake.getPort()); } if (packet instanceof PacketStatusRequest){ - sendPacket(new PacketStatusResponse(server.getConnectionsCount())); + sendPacket(new PacketStatusResponse(server)); } if (packet instanceof PacketStatusPing){ @@ -84,7 +83,7 @@ public class ClientConnection extends ChannelInboundHandlerAdapter { } if (packet instanceof PacketLoginStart){ - if (server.getConnectionsCount() >= LimboConfig.getMaxPlayers()){ + if (server.getConnections().getCount() >= server.getConfig().getMaxPlayers()){ disconnect("Too many players connected"); return; } @@ -105,7 +104,7 @@ public class ClientConnection extends ChannelInboundHandlerAdapter { sendPacket(loginSuccess); updateState(State.PLAY); - server.addConnection(this); + server.getConnections().addConnection(this); Logger.info("Player %s connected (%s)", this.username, channel.remoteAddress()); sendJoinPackets(); @@ -120,7 +119,7 @@ public class ClientConnection extends ChannelInboundHandlerAdapter { joinGame.setFlat(false); joinGame.setGameMode(2); joinGame.setHardcore(false); - joinGame.setMaxPlayers(LimboConfig.getMaxPlayers()); + joinGame.setMaxPlayers(server.getConfig().getMaxPlayers()); joinGame.setPreviousGameMode(-1); joinGame.setReducedDebugInfo(false); joinGame.setDebug(false); @@ -128,16 +127,16 @@ public class ClientConnection extends ChannelInboundHandlerAdapter { joinGame.setWorldName("minecraft:world"); joinGame.setWorldNames("minecraft:world"); joinGame.setHashedSeed(0); - joinGame.setDimensionCodec(DimensionRegistry.getCodec()); - joinGame.setDimension(DimensionRegistry.getDefaultDimension()); + joinGame.setDimensionCodec(server.getDimensionRegistry().getCodec()); + joinGame.setDimension(server.getDimensionRegistry().getDefaultDimension()); PacketPlayerPositionAndLook positionAndLook = new PacketPlayerPositionAndLook(); - positionAndLook.setX(LimboConfig.getSpawnPosition().getX()); - positionAndLook.setY(LimboConfig.getSpawnPosition().getY()); - positionAndLook.setZ(LimboConfig.getSpawnPosition().getZ()); - positionAndLook.setYaw(90.0F); - positionAndLook.setPitch(0.0F); + positionAndLook.setX(server.getConfig().getSpawnPosition().getX()); + positionAndLook.setY(server.getConfig().getSpawnPosition().getY()); + positionAndLook.setZ(server.getConfig().getSpawnPosition().getZ()); + positionAndLook.setYaw(server.getConfig().getSpawnPosition().getYaw()); + positionAndLook.setPitch(server.getConfig().getSpawnPosition().getPitch()); positionAndLook.setTeleportId(ThreadLocalRandom.current().nextInt()); PacketPlayerInfo info = new PacketPlayerInfo(); diff --git a/src/main/java/ru/nanit/limbo/protocol/packets/play/PacketBossBar.java b/src/main/java/ru/nanit/limbo/protocol/packets/play/PacketBossBar.java index 2fa8249..14577d7 100644 --- a/src/main/java/ru/nanit/limbo/protocol/packets/play/PacketBossBar.java +++ b/src/main/java/ru/nanit/limbo/protocol/packets/play/PacketBossBar.java @@ -2,36 +2,22 @@ package ru.nanit.limbo.protocol.packets.play; import ru.nanit.limbo.protocol.ByteMessage; import ru.nanit.limbo.protocol.PacketOut; +import ru.nanit.limbo.server.data.BossBar; import java.util.UUID; public class PacketBossBar implements PacketOut { private UUID uuid; - private String title; - private float health; - private Color color; - private Division division; + private BossBar bossBar; private int flags; public void setUuid(UUID uuid) { this.uuid = uuid; } - public void setTitle(String title) { - this.title = title; - } - - public void setHealth(float health) { - this.health = health; - } - - public void setColor(Color color) { - this.color = color; - } - - public void setDivision(Division division) { - this.division = division; + public void setBossBar(BossBar bossBar) { + this.bossBar = bossBar; } public void setFlags(int flags) { @@ -42,43 +28,11 @@ public class PacketBossBar implements PacketOut { public void encode(ByteMessage msg) { msg.writeUuid(uuid); msg.writeVarInt(0); // Create bossbar - msg.writeString(title); - msg.writeFloat(health); - msg.writeVarInt(color.index); - msg.writeVarInt(division.index); + msg.writeString(bossBar.getText()); + msg.writeFloat(bossBar.getHealth()); + msg.writeVarInt(bossBar.getColor().getIndex()); + msg.writeVarInt(bossBar.getDivision().getIndex()); msg.writeByte(flags); } - public enum Color { - - PINK(0), - BLUE(1), - RED(2), - GREEN(3), - YELLOW(4), - PURPLE(5), - WHITE(6); - - private final int index; - - Color(int index) { - this.index = index; - } - } - - public enum Division { - - SOLID(0), - DASHES_6(1), - DASHES_10(2), - DASHES_12(3), - DASHES_20(4); - - private final int index; - - Division(int index) { - this.index = index; - } - } - } diff --git a/src/main/java/ru/nanit/limbo/protocol/packets/status/PacketStatusResponse.java b/src/main/java/ru/nanit/limbo/protocol/packets/status/PacketStatusResponse.java index 83f037d..131f406 100644 --- a/src/main/java/ru/nanit/limbo/protocol/packets/status/PacketStatusResponse.java +++ b/src/main/java/ru/nanit/limbo/protocol/packets/status/PacketStatusResponse.java @@ -1,27 +1,28 @@ package ru.nanit.limbo.protocol.packets.status; -import ru.nanit.limbo.LimboConfig; import ru.nanit.limbo.protocol.*; import ru.nanit.limbo.protocol.registry.Version; +import ru.nanit.limbo.server.LimboServer; public class PacketStatusResponse implements PacketOut { private static final String TEMPLATE = "{ \"version\": { \"name\": \"%s\", \"protocol\": %d }, \"players\": { \"max\": %d, \"online\": %d, \"sample\": [] }, \"description\": %s }"; - private int online; + private LimboServer server; public PacketStatusResponse(){ } - public PacketStatusResponse(int online){ - this.online = online; + public PacketStatusResponse(LimboServer server){ + this.server = server; } @Override public void encode(ByteMessage msg) { - String ver = LimboConfig.getPingData().getVersion(); - String desc = LimboConfig.getPingData().getDescription(); + String ver = server.getConfig().getPingData().getVersion(); + String desc = server.getConfig().getPingData().getDescription(); String json = getResponseJson(ver, Version.getCurrentSupported().getProtocolNumber(), - LimboConfig.getMaxPlayers(), online, desc); + server.getConfig().getMaxPlayers(), server.getConnections().getCount(), desc); + msg.writeString(json); } diff --git a/src/main/java/ru/nanit/limbo/server/Connections.java b/src/main/java/ru/nanit/limbo/server/Connections.java new file mode 100644 index 0000000..c4fa340 --- /dev/null +++ b/src/main/java/ru/nanit/limbo/server/Connections.java @@ -0,0 +1,34 @@ +package ru.nanit.limbo.server; + +import ru.nanit.limbo.connection.ClientConnection; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public final class Connections { + + private final Map connections; + + public Connections(){ + connections = new ConcurrentHashMap<>(); + } + + public Collection getAllConnections(){ + return Collections.unmodifiableCollection(connections.values()); + } + + public int getCount(){ + return connections.size(); + } + + public void addConnection(ClientConnection connection){ + connections.put(connection.getUuid(), connection); + } + + public void removeConnection(ClientConnection connection){ + connections.remove(connection.getUuid()); + } +} diff --git a/src/main/java/ru/nanit/limbo/server/LimboServer.java b/src/main/java/ru/nanit/limbo/server/LimboServer.java index 88f456f..c100a1d 100644 --- a/src/main/java/ru/nanit/limbo/server/LimboServer.java +++ b/src/main/java/ru/nanit/limbo/server/LimboServer.java @@ -3,40 +3,43 @@ package ru.nanit.limbo.server; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; -import ru.nanit.limbo.LimboConfig; +import napi.configurate.serializing.NodeSerializers; +import ru.nanit.limbo.configuration.LimboConfig; +import ru.nanit.limbo.configuration.SocketAddressSerializer; import ru.nanit.limbo.connection.ClientChannelInitializer; import ru.nanit.limbo.connection.ClientConnection; import ru.nanit.limbo.protocol.packets.play.PacketBossBar; import ru.nanit.limbo.protocol.packets.play.PacketChatMessage; +import ru.nanit.limbo.server.data.*; import ru.nanit.limbo.util.Logger; import ru.nanit.limbo.world.DimensionRegistry; +import java.net.SocketAddress; import java.nio.file.Paths; -import java.util.Map; import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public final class LimboServer { - private final Map connections = new ConcurrentHashMap<>(); - private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); + private LimboConfig config; + private Connections connections; + private DimensionRegistry dimensionRegistry; private PacketChatMessage joinMessage; private PacketBossBar joinBossBar; - public int getConnectionsCount(){ - return connections.size(); + public LimboConfig getConfig(){ + return config; } - public void addConnection(ClientConnection connection){ - connections.put(connection.getUuid(), connection); + public Connections getConnections(){ + return connections; } - public void removeConnection(ClientConnection connection){ - connections.remove(connection.getUuid()); + public DimensionRegistry getDimensionRegistry() { + return dimensionRegistry; } public PacketChatMessage getJoinMessage() { @@ -50,47 +53,54 @@ public final class LimboServer { public void start() throws Exception { Logger.info("Starting server..."); - LimboConfig.load(Paths.get("./settings.properties")); - DimensionRegistry.init(LimboConfig.getDimensionType()); + NodeSerializers.register(SocketAddress.class, new SocketAddressSerializer()); + NodeSerializers.register(InfoForwarding.class, new InfoForwarding.Serializer()); + NodeSerializers.register(PingData.class, new PingData.Serializer()); + NodeSerializers.register(BossBar.class, new BossBar.Serializer()); + NodeSerializers.register(Position.class, new Position.Serializer()); - initializeInGameData(); + config = new LimboConfig(Paths.get("./")); + config.load(); + Logger.setLevel(config.getDebugLevel()); + + dimensionRegistry = new DimensionRegistry(); + dimensionRegistry.load(config.getDimensionType()); + + connections = new Connections(); + + initInGameData(); + + ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); executor.scheduleAtFixedRate(this::broadcastKeepAlive, 0L, 5L, TimeUnit.SECONDS); - ServerBootstrap bootstrap = new ServerBootstrap() + new ServerBootstrap() .group(new NioEventLoopGroup(), new NioEventLoopGroup()) .channel(NioServerSocketChannel.class) - .childHandler(new ClientChannelInitializer(this)); + .childHandler(new ClientChannelInitializer(this)) + .localAddress(config.getAddress()) + .bind(); - if (LimboConfig.getHost().isEmpty()){ - bootstrap.bind(LimboConfig.getPort()); - } else { - bootstrap.bind(LimboConfig.getHost(), LimboConfig.getPort()); - } - - Logger.info("Server started on %s:%d", LimboConfig.getHost(), LimboConfig.getPort()); + Logger.info("Server started on %s", config.getAddress()); } - private void initializeInGameData(){ - if (LimboConfig.getJoinMessages().getChatMessage() != null){ + private void initInGameData(){ + if (config.isUseJoinMessage()){ joinMessage = new PacketChatMessage(); - joinMessage.setJsonData(LimboConfig.getJoinMessages().getChatMessage()); + joinMessage.setJsonData(config.getJoinMessage()); joinMessage.setPosition(PacketChatMessage.Position.CHAT); joinMessage.setSender(UUID.randomUUID()); } - if (LimboConfig.getJoinMessages().getBossBarText() != null){ + if (config.isUseBossBar()){ joinBossBar = new PacketBossBar(); - joinBossBar.setTitle(LimboConfig.getJoinMessages().getBossBarText()); - joinBossBar.setHealth(LimboConfig.getJoinMessages().getBossBarHealth()); - joinBossBar.setColor(LimboConfig.getJoinMessages().getBossBarColor()); - joinBossBar.setDivision(LimboConfig.getJoinMessages().getBossBarDivision()); + joinBossBar.setBossBar(config.getBossBar()); joinBossBar.setUuid(UUID.randomUUID()); } } private void broadcastKeepAlive(){ - connections.values().forEach(ClientConnection::sendKeepAlive); + connections.getAllConnections().forEach(ClientConnection::sendKeepAlive); } } diff --git a/src/main/java/ru/nanit/limbo/server/data/BossBar.java b/src/main/java/ru/nanit/limbo/server/data/BossBar.java new file mode 100644 index 0000000..f14acef --- /dev/null +++ b/src/main/java/ru/nanit/limbo/server/data/BossBar.java @@ -0,0 +1,119 @@ +package ru.nanit.limbo.server.data; + +import napi.configurate.data.ConfigNode; +import napi.configurate.serializing.NodeSerializer; +import napi.configurate.serializing.NodeSerializingException; +import ru.nanit.limbo.util.Colors; + +public class BossBar { + + private String text; + private float health; + private Color color; + private Division division; + + public String getText() { + return text; + } + + public float getHealth() { + return health; + } + + public Color getColor() { + return color; + } + + public Division getDivision() { + return division; + } + + public void setText(String text) { + this.text = text; + } + + public void setHealth(float health) { + this.health = health; + } + + public void setColor(Color color) { + this.color = color; + } + + public void setDivision(Division division) { + this.division = division; + } + + public enum Color { + + PINK(0), + BLUE(1), + RED(2), + GREEN(3), + YELLOW(4), + PURPLE(5), + WHITE(6); + + private final int index; + + Color(int index) { + this.index = index; + } + + public int getIndex() { + return index; + } + } + + public enum Division { + + SOLID(0), + DASHES_6(1), + DASHES_10(2), + DASHES_12(3), + DASHES_20(4); + + private final int index; + + Division(int index) { + this.index = index; + } + + public int getIndex() { + return index; + } + } + + public static class Serializer implements NodeSerializer{ + + @Override + public BossBar deserialize(ConfigNode node) throws NodeSerializingException { + BossBar bossBar = new BossBar(); + + bossBar.setText(Colors.of(node.getNode("text").getString())); + bossBar.setHealth(node.getNode("health").getFloat()); + + if (bossBar.getHealth() < 0 || bossBar.getHealth() > 1) + throw new NodeSerializingException("BossBar health value must be between 0.0 and 1.0"); + + try { + bossBar.setColor(Color.valueOf(node.getNode("color").getString().toUpperCase())); + } catch (IllegalArgumentException e){ + throw new NodeSerializingException("Invalid bossbar color"); + } + + try { + bossBar.setDivision(Division.valueOf(node.getNode("division").getString().toUpperCase())); + } catch (IllegalArgumentException e){ + throw new NodeSerializingException("Invalid bossbar division"); + } + + return bossBar; + } + + @Override + public void serialize(BossBar bossBar, ConfigNode configNode) { + + } + } +} diff --git a/src/main/java/ru/nanit/limbo/server/data/InfoForwarding.java b/src/main/java/ru/nanit/limbo/server/data/InfoForwarding.java new file mode 100644 index 0000000..fcae3ee --- /dev/null +++ b/src/main/java/ru/nanit/limbo/server/data/InfoForwarding.java @@ -0,0 +1,53 @@ +package ru.nanit.limbo.server.data; + +import napi.configurate.data.ConfigNode; +import napi.configurate.serializing.NodeSerializer; +import napi.configurate.serializing.NodeSerializingException; + +import java.util.Optional; + +public class InfoForwarding { + + private Type type; + private String secret; + + public Type getType() { + return type; + } + + public Optional getSecret() { + return Optional.ofNullable(secret); + } + + public enum Type { + NONE, + LEGACY, + MODERN + } + + public static class Serializer implements NodeSerializer { + + @Override + public InfoForwarding deserialize(ConfigNode node) throws NodeSerializingException { + InfoForwarding forwarding = new InfoForwarding(); + + try { + forwarding.type = Type.valueOf(node.getNode("type").getString().toUpperCase()); + } catch (IllegalArgumentException e){ + throw new NodeSerializingException("Undefined info forwarding type"); + } + + if (forwarding.type == Type.MODERN){ + forwarding.secret = node.getNode("secret").getString(); + } + + return forwarding; + } + + @Override + public void serialize(InfoForwarding infoForwarding, ConfigNode configNode) throws NodeSerializingException { + + } + } + +} diff --git a/src/main/java/ru/nanit/limbo/server/data/PingData.java b/src/main/java/ru/nanit/limbo/server/data/PingData.java new file mode 100644 index 0000000..ccea251 --- /dev/null +++ b/src/main/java/ru/nanit/limbo/server/data/PingData.java @@ -0,0 +1,43 @@ +package ru.nanit.limbo.server.data; + +import napi.configurate.data.ConfigNode; +import napi.configurate.serializing.NodeSerializer; +import ru.nanit.limbo.util.Colors; + +public class PingData { + + private String version; + private String description; + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public static class Serializer implements NodeSerializer { + + @Override + public PingData deserialize(ConfigNode node) { + PingData pingData = new PingData(); + pingData.setDescription(Colors.of(node.getNode("description").getString())); + pingData.setVersion(Colors.of(node.getNode("version").getString())); + return pingData; + } + + @Override + public void serialize(PingData pingData, ConfigNode configNode) { + + } + } +} \ No newline at end of file diff --git a/src/main/java/ru/nanit/limbo/server/data/Position.java b/src/main/java/ru/nanit/limbo/server/data/Position.java new file mode 100644 index 0000000..80adab9 --- /dev/null +++ b/src/main/java/ru/nanit/limbo/server/data/Position.java @@ -0,0 +1,72 @@ +package ru.nanit.limbo.server.data; + +import napi.configurate.data.ConfigNode; +import napi.configurate.serializing.NodeSerializer; + +public class Position { + + private double x; + private double y; + private double z; + private float yaw; + private float pitch; + + public double getX() { + return x; + } + + public double getY() { + return y; + } + + public double getZ() { + return z; + } + + public float getYaw() { + return yaw; + } + + public float getPitch() { + return pitch; + } + + public void setX(double x) { + this.x = x; + } + + public void setY(double y) { + this.y = y; + } + + public void setZ(double z) { + this.z = z; + } + + public void setYaw(float yaw) { + this.yaw = yaw; + } + + public void setPitch(float pitch) { + this.pitch = pitch; + } + + public static class Serializer implements NodeSerializer { + + @Override + public Position deserialize(ConfigNode node) { + Position position = new Position(); + position.setX(node.getNode("x").getDouble()); + position.setY(node.getNode("y").getDouble()); + position.setZ(node.getNode("z").getDouble()); + position.setYaw(node.getNode("yaw").getFloat()); + position.setPitch(node.getNode("pitch").getFloat()); + return position; + } + + @Override + public void serialize(Position position, ConfigNode configNode) { + + } + } +} diff --git a/src/main/java/ru/nanit/limbo/util/Logger.java b/src/main/java/ru/nanit/limbo/util/Logger.java index 440848c..ab7b6b5 100644 --- a/src/main/java/ru/nanit/limbo/util/Logger.java +++ b/src/main/java/ru/nanit/limbo/util/Logger.java @@ -1,22 +1,25 @@ package ru.nanit.limbo.util; -import ru.nanit.limbo.LimboConfig; - import java.time.LocalTime; import java.time.format.DateTimeFormatter; public final class Logger { private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("hh:mm:ss"); + private static int debugLevel = 3; private Logger(){} + public static void setLevel(int level){ + debugLevel = level; + } + public static void info(Object msg, Object... args){ print(Level.INFO, msg, null, args); } - public static void info(Object msg, Throwable t, Object... args){ - print(Level.INFO, msg, t, args); + public static void debug(Object msg, Object... args){ + print(Level.INFO, msg, null, args); } public static void warning(Object msg, Object... args){ @@ -36,7 +39,7 @@ public final class Logger { } public static void print(Level level, Object msg, Throwable t, Object... args){ - if (LimboConfig.getDebugLevel() >= level.getIndex()){ + if (debugLevel >= level.getIndex()){ System.out.println(String.format("%s: %s", getPrefix(level), String.format(msg.toString(), args))); if (t != null) t.printStackTrace(); } @@ -52,7 +55,8 @@ public final class Logger { public enum Level { - INFO ("INFO", 1), + INFO ("INFO", 0), + DEBUG ("DEBUG", 1), WARNING("WARNING", 2), ERROR("ERROR", 3); diff --git a/src/main/java/ru/nanit/limbo/world/DimensionRegistry.java b/src/main/java/ru/nanit/limbo/world/DimensionRegistry.java index 2993013..77a6f89 100644 --- a/src/main/java/ru/nanit/limbo/world/DimensionRegistry.java +++ b/src/main/java/ru/nanit/limbo/world/DimensionRegistry.java @@ -6,11 +6,55 @@ import ru.nanit.limbo.util.Logger; public final class DimensionRegistry { - private static CompoundBinaryTag codec; - private static CompoundBinaryTag defaultDimension; + private CompoundBinaryTag defaultDimension; - public static void init(String defaultDimensionName){ - CompoundBinaryTag overworld = CompoundBinaryTag.builder() + private CompoundBinaryTag codec; + private CompoundBinaryTag overWorld; + private CompoundBinaryTag theEnd; + private CompoundBinaryTag nether; + + public CompoundBinaryTag getCodec(){ + return codec; + } + + public CompoundBinaryTag getDefaultDimension() { + return defaultDimension; + } + + public CompoundBinaryTag getOverWorld() { + return overWorld; + } + + public CompoundBinaryTag getTheEnd() { + return theEnd; + } + + public CompoundBinaryTag getNether() { + return nether; + } + + public void load(String def){ + initDimensions(); + + switch (def.toLowerCase()){ + case "overworld": + defaultDimension = overWorld; + break; + case "nether": + defaultDimension = nether; + break; + case "the_end": + defaultDimension = theEnd; + break; + default: + defaultDimension = theEnd; + Logger.warning("Undefined dimension type: '%s'. Using THE_END as default", def); + break; + } + } + + private void initDimensions(){ + overWorld = CompoundBinaryTag.builder() .putString("name", "minecraft:overworld") .putByte("piglin_safe", (byte) 0) .putByte("natural", (byte) 0) @@ -28,7 +72,7 @@ public final class DimensionRegistry { .putByte("has_ceiling", (byte) 0) .build(); - CompoundBinaryTag nether = CompoundBinaryTag.builder() + nether = CompoundBinaryTag.builder() .putString("name", "minecraft:the_nether") .putByte("piglin_safe", (byte) 0) .putByte("natural", (byte) 0) @@ -46,7 +90,7 @@ public final class DimensionRegistry { .putByte("has_ceiling", (byte) 0) .build(); - CompoundBinaryTag theEnd = CompoundBinaryTag.builder() + theEnd = CompoundBinaryTag.builder() .putString("name", "minecraft:the_end") .putByte("piglin_safe", (byte) 0) .putByte("natural", (byte) 0) @@ -64,10 +108,10 @@ public final class DimensionRegistry { .putByte("has_ceiling", (byte) 0) .build(); - CompoundBinaryTag overworldData = CompoundBinaryTag.builder() + CompoundBinaryTag overWorldData = CompoundBinaryTag.builder() .putString("name", "minecraft:overworld") .putInt("id", 2) - .put("element", overworld) + .put("element", overWorld) .build(); CompoundBinaryTag netherData = CompoundBinaryTag.builder() @@ -111,7 +155,7 @@ public final class DimensionRegistry { .put("minecraft:dimension_type", CompoundBinaryTag.builder() .putString("type", "minecraft:dimension_type") .put("value", ListBinaryTag.builder() - .add(overworldData) + .add(overWorldData) .add(netherData) .add(endData) .build()) @@ -123,29 +167,5 @@ public final class DimensionRegistry { .build()) .build()) .build(); - - switch (defaultDimensionName.toLowerCase()){ - case "overworld": - defaultDimension = overworld; - break; - case "nether": - defaultDimension = nether; - break; - case "the_end": - defaultDimension = theEnd; - break; - default: - defaultDimension = theEnd; - Logger.error("Undefined dimension type: '%s'. Using THE_END as default", defaultDimensionName); - break; - } - } - - public static CompoundBinaryTag getCodec(){ - return codec; - } - - public static CompoundBinaryTag getDefaultDimension() { - return defaultDimension; } } diff --git a/src/main/resources/limbo.yml b/src/main/resources/limbo.yml new file mode 100644 index 0000000..c9332b0 --- /dev/null +++ b/src/main/resources/limbo.yml @@ -0,0 +1,60 @@ +# +# NanoLimbo configuration +# + +# Server's host address and port. Set ip empty to use public address +bind: + ip: 'localhost' + port: 65535 + +# Max amount of players can join to server +maxPlayers: 100 + +# Server's data in servers list +ping: + description: '{"text": "&9NanoLimbo"}' + version: 'NanoLimbo' + +# Available dimensions: OVERWORLD, NETHER, THE_END +dimension: THE_END + +# Spawn position in the world +spawnPosition: + x: 0.0 + y: 0.0 + z: 0.0 + yaw: 0.0 + pitch: 0.0 + +# Message sends when player join to server +joinMessage: + enable: true + text: '{"text": "&eWelcome to the Limbo!"}' + +# Bossbar sends when player join to server +bossBar: + enable: true + text: '{"text": "Welcome to the Limbo!"}' + health: 1.0 + # Available colors: PINK, BLUE, RED, GREEN, YELLOW, PURPLE, WHITE + color: PINK + # Available divisions: SOLID, DASHES_6, DASHES_10, DASHES_12, DASHES_20 + division: SOLID + +# Player info forwarding support. Available types: NONE, LEGACY, MODERN +# Don't use secret if you not use MODERN type +infoForwarding: + type: LEGACY + secret: '' + +# Read timeout for connections in milliseconds +readTimeout: 30000 + +# Define debug level. On release, i recommend to use 0 level, since +# there are many useless for release information about ping, received packets, etc. +# Levels: +# 0 - Display only useful info +# 1 - Display info and some debug +# 2 - Display info and warnings +# 3 - Display info, warnings, errors +debugLevel: 3 \ No newline at end of file diff --git a/src/main/resources/settings.properties b/src/main/resources/settings.properties deleted file mode 100644 index cc1d1fc..0000000 --- a/src/main/resources/settings.properties +++ /dev/null @@ -1,71 +0,0 @@ -# ======= General Data ======= # - -# Server's host address. Set it empty to use public address -host=localhost - -# Server's port -port=65535 - -# Max amount of players can join to server -max-players=100 - -# Available dimensions: OVERWORLD, NETHER, THE_END -dimension=THE_END - -# Spawn position in the world in format x,y,z -spawn-position=0.0,65.0,0.0 - -# Version string when client version is not compatible with server one -ping-version=NanoLimbo - -# Server's description component -ping-description={"text": "&9NanoLimbo"} - -# Player info forwarding support. Available types: NONE, LEGACY, MODERN -# MODERN - Velocity native forwarding type. -# LEGACY - BungeeCord forwarding type (Velocity supports it too) -ip-forwarding=LEGACY - -# If you use MODERN type of forwarding, enter your secret code here -ip-forwarding-secret= - -# Read timeout for connections in milliseconds -read-timeout=30000 - -# Define debug level. On release, i recommend to use 1 level, since -# there are many useless for release warnings about undefined packets and other. -# Levels: -# 0 - Display nothing -# 1 - Display only useful info -# 2 - Display info and warnings -# 3 - Display info, warnings, errors -debug-level=3 - -# ======= In-game Data ======= # - -# Message when player join to server. Comment this parameter to disable -join-message={"text": "&eWelcome to the Limbo!"} - -# Bossbar text. Comment this parameter to disable bossbar -join-bossbar-text={"text": "Welcome to the Limbo!"} - -# Bossbar percentage between 0.0 and 1.0 inclusive -join-bossbar-health=1.0 - -# Available bossbar colors: -# - PINK -# - BLUE -# - RED -# - GREEN -# - YELLOW -# - PURPLE -# - WHITE -join-bossbar-color=PINK - -# Available bossbar divisions: -# - SOLID -# - DASHES_6 -# - DASHES_10 -# - DASHES_12 -# - DASHES_20 -join-bossbar-division=SOLID \ No newline at end of file