diff --git a/build.gradle.kts b/build.gradle.kts index a7529c84..d00cb870 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -27,6 +27,7 @@ dependencies { annotationProcessor("org.projectlombok:lombok:1.18.32") // lombok implementation("org.tomlj:tomlj:1.1.1") // Config lang implementation("com.rabbitmq:amqp-client:5.21.0") // Message broker + implementation("dev.hollowcube:polar:1.8.1") // Polar } tasks.withType { diff --git a/src/main/java/net/cytonic/cytosis/Cytosis.java b/src/main/java/net/cytonic/cytosis/Cytosis.java index ab87fd6b..e87649c5 100644 --- a/src/main/java/net/cytonic/cytosis/Cytosis.java +++ b/src/main/java/net/cytonic/cytosis/Cytosis.java @@ -10,6 +10,7 @@ import net.cytonic.cytosis.logging.Logger; import net.cytonic.cytosis.messaging.MessagingManager; import net.cytonic.cytosis.ranks.RankManager; +import net.hollowcube.polar.PolarLoader; import net.minestom.server.MinecraftServer; import net.minestom.server.command.CommandManager; import net.minestom.server.command.ConsoleSender; @@ -22,10 +23,8 @@ import net.minestom.server.instance.block.Block; import net.minestom.server.network.ConnectionManager; import net.minestom.server.permission.Permission; - import java.util.*; - @Getter public class Cytosis { @@ -72,10 +71,6 @@ public static void main(String[] args) { Logger.info("Starting connection manager."); connectionManager = MinecraftServer.getConnectionManager(); - - Logger.info("Starting manager."); - databaseManager = new DatabaseManager(); - // Commands Logger.info("Starting command manager."); commandManager = MinecraftServer.getCommandManager(); @@ -144,12 +139,31 @@ public static void mojangAuth() { MojangAuth.init(); //VERY IMPORTANT! (This is online mode!) } - public static void completeNonEssentialTasks(long start) { - // basic world generator - Logger.info("Generating basic world"); - defaultInstance.setGenerator(unit -> unit.modifier().fillHeight(0, 1, Block.WHITE_STAINED_GLASS)); - defaultInstance.setChunkSupplier(LightingChunk::new); + public static void loadWorld() { + if (CytosisSettings.SERVER_WORLD_NAME.isEmpty()) { + Logger.info("Generating basic world"); + defaultInstance.setGenerator(unit -> unit.modifier().fillHeight(0, 1, Block.WHITE_STAINED_GLASS)); + defaultInstance.setChunkSupplier(LightingChunk::new); + Logger.info("Basic world loaded!"); + return; + } + Logger.info(STR."Loading world '\{CytosisSettings.SERVER_WORLD_NAME}'"); + databaseManager.getDatabase().getWorld(CytosisSettings.SERVER_WORLD_NAME).whenComplete((polarWorld, throwable) -> { + if (throwable != null) { + Logger.error("An error occurred whilst initializing the world!", throwable); + } else { + defaultInstance.setChunkLoader(new PolarLoader(polarWorld)); + defaultInstance.setChunkSupplier(LightingChunk::new); + Logger.info("World loaded!"); + } + }); + } + public static void completeNonEssentialTasks(long start) { + Logger.info("Initializing database"); + databaseManager = new DatabaseManager(); + databaseManager.setupDatabase(); + Logger.info("Database initialized!"); Logger.info("Setting up event handlers"); eventHandler = new EventHandler(MinecraftServer.getGlobalEventHandler()); eventHandler.init(); @@ -157,10 +171,10 @@ public static void completeNonEssentialTasks(long start) { Logger.info("Initializing server events"); ServerEventListeners.initServerEvents(); - Logger.info("Initializing database"); - databaseManager.setupDatabase(); - - MinecraftServer.getSchedulerManager().buildShutdownTask(() -> databaseManager.shutdown()); + MinecraftServer.getSchedulerManager().buildShutdownTask(() -> { + databaseManager.shutdown(); + Logger.info("Good night!"); + }); Logger.info("Initializing server commands"); commandHandler = new CommandHandler(); @@ -183,7 +197,7 @@ public static void completeNonEssentialTasks(long start) { // Start the server Logger.info(STR."Server started on port \{CytosisSettings.SERVER_PORT}"); minecraftServer.start("0.0.0.0", CytosisSettings.SERVER_PORT); - + long end = System.currentTimeMillis(); Logger.info(STR."Server started in \{end - start}ms!"); diff --git a/src/main/java/net/cytonic/cytosis/DatabaseManager.java b/src/main/java/net/cytonic/cytosis/DatabaseManager.java deleted file mode 100644 index b9cc6a6c..00000000 --- a/src/main/java/net/cytonic/cytosis/DatabaseManager.java +++ /dev/null @@ -1,20 +0,0 @@ -package net.cytonic.cytosis; - -import lombok.Getter; -import net.cytonic.cytosis.data.Database; - -@Getter -public class DatabaseManager { - - private Database database; - - public void shutdown() { - database.disconnect(); - } - - public void setupDatabase() { - database = new Database(); - database.connect(); - database.createTables(); - } -} \ No newline at end of file diff --git a/src/main/java/net/cytonic/cytosis/commands/CommandHandler.java b/src/main/java/net/cytonic/cytosis/commands/CommandHandler.java index f3b82273..60f29c5e 100644 --- a/src/main/java/net/cytonic/cytosis/commands/CommandHandler.java +++ b/src/main/java/net/cytonic/cytosis/commands/CommandHandler.java @@ -3,7 +3,6 @@ import net.cytonic.cytosis.Cytosis; import net.minestom.server.command.CommandManager; import net.minestom.server.entity.Player; - import java.util.Scanner; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; diff --git a/src/main/java/net/cytonic/cytosis/commands/RankCommand.java b/src/main/java/net/cytonic/cytosis/commands/RankCommand.java index 906367f3..131c5b89 100644 --- a/src/main/java/net/cytonic/cytosis/commands/RankCommand.java +++ b/src/main/java/net/cytonic/cytosis/commands/RankCommand.java @@ -9,9 +9,7 @@ import net.minestom.server.command.builder.arguments.ArgumentType; import net.minestom.server.command.builder.suggestion.SuggestionEntry; import net.minestom.server.entity.Player; - import java.util.Locale; - import static net.cytonic.cytosis.utils.MiniMessageTemplate.MM; public class RankCommand extends Command { @@ -19,7 +17,7 @@ public class RankCommand extends Command { public RankCommand() { super("rank"); setCondition((sender, _) -> sender.hasPermission("cytosis.commands.rank")); - setDefaultExecutor((sender, _) -> sender.sendMessage(MM."You must specify a valid rank and player!")); + setDefaultExecutor((sender, _) -> sender.sendMessage(MM."You must specify a valid player and rank!")); var rankArg = ArgumentType.Enum("rank", PlayerRank.class).setFormat(ArgumentEnum.Format.LOWER_CASED); rankArg.setCallback((sender, exception) -> sender.sendMessage(STR."The rank \{exception.getInput()} is invalid!")); @@ -50,7 +48,7 @@ public RankCommand() { Cytosis.getDatabaseManager().getDatabase().getPlayerRank(player.getUuid()).whenComplete((rank, throwable) -> { if (throwable != null) { - sender.sendMessage("An error occured whilst fetching the old rank!"); + sender.sendMessage("An error occurred whilst fetching the old rank!"); return; } diff --git a/src/main/java/net/cytonic/cytosis/config/CytosisSettings.java b/src/main/java/net/cytonic/cytosis/config/CytosisSettings.java index e2af524b..9a4aad45 100644 --- a/src/main/java/net/cytonic/cytosis/config/CytosisSettings.java +++ b/src/main/java/net/cytonic/cytosis/config/CytosisSettings.java @@ -1,6 +1,8 @@ package net.cytonic.cytosis.config; import net.cytonic.cytosis.logging.Logger; +import net.cytonic.cytosis.utils.PosSerializer; +import net.minestom.server.coordinate.Pos; import java.util.Map; /** @@ -25,6 +27,8 @@ public class CytosisSettings { public static boolean SERVER_PROXY_MODE = false; public static String SERVER_SECRET = ""; public static int SERVER_PORT = 25565; + public static String SERVER_WORLD_NAME = ""; + public static Pos SERVER_SPAWN_POS = new Pos(0,1,0); // RabbitMQ public static boolean RABBITMQ_ENABLED = false; public static String RABBITMQ_HOST = ""; @@ -57,6 +61,8 @@ public static void inportConfig(Map config) { case "server.proxy_mode" -> SERVER_PROXY_MODE = (boolean) value; case "server.secret" -> SERVER_SECRET = (String) value; case "server.port" -> SERVER_PORT = toInt(value); + case "server.world_name" -> SERVER_WORLD_NAME = (String) value; + case "server.spawn_point" -> SERVER_SPAWN_POS = PosSerializer.deserialize((String) value); // RabbitMQ case "rabbitmq.host" -> RABBITMQ_HOST = (String) value; @@ -98,6 +104,8 @@ public static void loadEnvironmentVariables() { if (!(System.getenv("SERVER_PROXY_MODE") == null)) CytosisSettings.SERVER_PROXY_MODE = Boolean.parseBoolean(System.getenv("SERVER_PROXY_MODE")); if (!(System.getenv("SERVER_SECRET") == null)) CytosisSettings.SERVER_SECRET = System.getenv("SERVER_SECRET"); if (!(System.getenv("SERVER_PORT") == null)) CytosisSettings.SERVER_PORT = Integer.parseInt(System.getenv("SERVER_PORT")); + if (!(System.getenv("SERVER_WORLD_NAME") == null)) CytosisSettings.SERVER_WORLD_NAME = System.getenv("SERVER_WORLD_NAME"); + if (!(System.getenv("SERVER_SPAWN_POINT") == null)) CytosisSettings.SERVER_SPAWN_POS = PosSerializer.deserialize(System.getenv("SERVER_SPAWN_POINT")); // RabbitMQ if (!(System.getenv("RABBITMQ_ENABLED") == null)) CytosisSettings.RABBITMQ_ENABLED = Boolean.parseBoolean(System.getenv("RABBITMQ_ENABLED")); if (!(System.getenv("RABBITMQ_HOST") == null)) CytosisSettings.RABBITMQ_HOST = System.getenv("RABBITMQ_HOST"); @@ -125,6 +133,8 @@ public static void loadCommandArgs() { if (!(System.getProperty("SERVER_PROXY_MODE") == null)) CytosisSettings.SERVER_PROXY_MODE = Boolean.parseBoolean(System.getProperty("SERVER_PROXY_MODE")); if (!(System.getProperty("SERVER_SECRET") == null)) CytosisSettings.SERVER_SECRET = System.getProperty("SERVER_SECRET"); if (!(System.getProperty("SERVER_PORT") == null)) CytosisSettings.SERVER_PORT = Integer.parseInt(System.getProperty("SERVER_PORT")); + if (!(System.getProperty("SERVER_WORLD_NAME") == null)) CytosisSettings.SERVER_WORLD_NAME = System.getProperty("SERVER_WORLD_NAME"); + if (!(System.getProperty("SERVER_SPAWN_POINT") == null)) CytosisSettings.SERVER_SPAWN_POS = PosSerializer.deserialize(System.getProperty("SERVER_SPAWN_POINT")); // RabbitMQ if (!(System.getProperty("RABBITMQ_ENABLED") == null)) CytosisSettings.RABBITMQ_ENABLED = Boolean.parseBoolean(System.getProperty("RABBITMQ_ENABLED")); if (!(System.getProperty("RABBITMQ_HOST") == null)) CytosisSettings.RABBITMQ_HOST = System.getProperty("RABBITMQ_HOST"); diff --git a/src/main/java/net/cytonic/cytosis/data/Database.java b/src/main/java/net/cytonic/cytosis/data/Database.java index f68b7377..ae3d9198 100644 --- a/src/main/java/net/cytonic/cytosis/data/Database.java +++ b/src/main/java/net/cytonic/cytosis/data/Database.java @@ -1,11 +1,16 @@ package net.cytonic.cytosis.data; +import net.cytonic.cytosis.Cytosis; import net.cytonic.cytosis.config.CytosisSettings; import net.cytonic.cytosis.logging.Logger; import net.cytonic.cytosis.ranks.PlayerRank; +import net.cytonic.cytosis.utils.PosSerializer; +import net.hollowcube.polar.PolarReader; +import net.hollowcube.polar.PolarWorld; +import net.hollowcube.polar.PolarWriter; import net.minestom.server.MinecraftServer; +import net.minestom.server.coordinate.Pos; import org.jetbrains.annotations.NotNull; - import java.sql.*; import java.util.UUID; import java.util.concurrent.CompletableFuture; @@ -48,6 +53,7 @@ public void connect() { try { connection = DriverManager.getConnection(STR."jdbc:mysql://\{host}:\{port}/\{database}?useSSL=\{ssl}&autoReconnect=true&allowPublicKeyRetrieval=true", username, password); Logger.info("Successfully connected to the MySQL Database!"); + Cytosis.loadWorld(); } catch (SQLException e) { Logger.error("Invalid Database Credentials!", e); MinecraftServer.stopCleanly(); @@ -73,26 +79,39 @@ public void disconnect() { public void createTables() { createRanksTable(); createChatTable(); + createWorldTable(); } private Connection getConnection() { return connection; } + /** + * Creates the 'cytonic_chat' table in the database if it doesn't exist. + * The table contains information about player chat messages. + * + * @throws IllegalStateException if the database connection is not open. + */ private void createChatTable() { worker.submit(() -> { if (isConnected()) { PreparedStatement ps; try { - ps = getConnection().prepareStatement("CREATE TABLE IF NOT EXISTS cytonicchat (id INT NOT NULL AUTO_INCREMENT, timestamp TIMESTAMP, uuid VARCHAR(36), message TEXT, PRIMARY KEY(id))"); + ps = getConnection().prepareStatement("CREATE TABLE IF NOT EXISTS cytonic_chat (id INT NOT NULL AUTO_INCREMENT, timestamp TIMESTAMP, uuid VARCHAR(36), message TEXT, PRIMARY KEY(id))"); ps.executeUpdate(); } catch (SQLException e) { - Logger.error("An error occoured whilst fetching data from the database. Please report the following stacktrace to Foxikle:", e); + Logger.error("An error occurred whilst creating the `cytonic_chat` table.", e); } } }); } + /** + * Creates the 'cytonic_ranks' table in the database if it doesn't exist. + * The table contains information about player ranks. + * + * @throws IllegalStateException if the database connection is not open. + */ private void createRanksTable() { worker.submit(() -> { if (isConnected()) { @@ -101,7 +120,27 @@ private void createRanksTable() { ps = getConnection().prepareStatement("CREATE TABLE IF NOT EXISTS cytonic_ranks (uuid VARCHAR(36), rank_id VARCHAR(16), PRIMARY KEY(uuid))"); ps.executeUpdate(); } catch (SQLException e) { - Logger.error("An error occoured whilst creating the `cytonic_ranks` table.", e); + Logger.error("An error occurred whilst creating the `cytonic_ranks` table.", e); + } + } + }); + } + + /** + * Creates the 'cytonic_worlds' table in the database if it doesn't exist. + * The table contains information about the worlds stored in the database. + * + * @throws IllegalStateException if the database connection is not open. + */ + public void createWorldTable() { + worker.submit(() -> { + if (isConnected()) { + PreparedStatement ps; + try { + ps = getConnection().prepareStatement("CREATE TABLE IF NOT EXISTS cytonic_worlds (world_name TEXT, world_type TEXT, last_modified TIMESTAMP, world_data MEDIUMBLOB, spawn_point TEXT, extra_data varchar(100))"); + ps.executeUpdate(); + } catch (SQLException e) { + Logger.error("An error occurred whilst creating the `cytonic_worlds` table.", e); } } }); @@ -156,16 +195,26 @@ public CompletableFuture setPlayerRank(UUID uuid, PlayerRank rank) { future.complete(null); } catch (SQLException e) { Logger.error(STR."An error occurred whilst setting the rank of '\{uuid}'"); + future.completeExceptionally(e); } }); return future; } + /** + * Adds a players chat message to the database. + * + * @param uuid The player's UUID. + * @param message The player's message. + * @throws IllegalStateException if the database isn't connected. + */ public void addChat(UUID uuid, String message) { worker.submit(() -> { + if (!isConnected()) + throw new IllegalStateException("The database must have an open connection to add a player's chat!"); PreparedStatement ps; try { - ps = connection.prepareStatement("INSERT INTO cytonicchat (timestamp, uuid, message) VALUES (CURRENT_TIMESTAMP,?,?)"); + ps = connection.prepareStatement("INSERT INTO cytonic_chat (timestamp, uuid, message) VALUES (CURRENT_TIMESTAMP,?,?)"); ps.setString(1, uuid.toString()); ps.setString(2, message); ps.executeUpdate(); @@ -174,4 +223,63 @@ public void addChat(UUID uuid, String message) { } }); } + + /** + * Adds a new world to the database. + * + * @param worldName The name of the world to be added. + * @param worldType The type of the world. + * @param world The PolarWorld object representing the world. + * @param spawnPoint The spawn point of the world. + * @throws IllegalStateException If the database connection is not open. + */ + public void addWorld(String worldName, String worldType, PolarWorld world, Pos spawnPoint) { + if (!isConnected()) + throw new IllegalStateException("The database must have an open connection to add a world!"); + worker.submit(() -> { + try { + PreparedStatement ps = connection.prepareStatement("INSERT INTO cytonic_worlds (world_name, world_type, last_modified, world_data, spawn_point) VALUES (?,?, CURRENT_TIMESTAMP,?,?)"); + ps.setString(1, worldName); + ps.setString(2, worldType); + ps.setBytes(3, PolarWriter.write(world)); + ps.setString(4, PosSerializer.serialize(spawnPoint)); + ps.executeUpdate(); + } catch (SQLException e) { + Logger.error("An error occurred whilst adding a world!",e); + } + }); + } + + /** + * Retrieves a world from the database. + * + * @param worldName The name of the world to fetch. + * @return A {@link CompletableFuture} that completes with the fetched {@link PolarWorld}. + * If the world does not exist in the database, the future will complete exceptionally with a {@link RuntimeException}. + * @throws IllegalStateException If the database connection is not open. + */ + public CompletableFuture getWorld(String worldName) { + CompletableFuture future = new CompletableFuture<>(); + if (!isConnected()) + throw new IllegalStateException("The database must have an open connection to fetch a world!"); + worker.submit(() -> { + try (PreparedStatement ps = connection.prepareStatement("SELECT * FROM cytonic_worlds WHERE world_name = ?")) { + ps.setString(1, worldName); + ResultSet rs = ps.executeQuery(); + if (rs.next()) { + PolarWorld world = PolarReader.read(rs.getBytes("world_data")); + CytosisSettings.SERVER_SPAWN_POS = PosSerializer.deserialize(rs.getString("spawn_point")); + future.complete(world); + } else { + Logger.error("The result set is empty!"); + throw new RuntimeException(STR."World not found: \{worldName}"); + } + } catch (Exception e) { + Logger.error("An error occurred whilst fetching a world!", e); + future.completeExceptionally(e); + throw new RuntimeException(e); + } + }); + return future; + } } \ No newline at end of file diff --git a/src/main/java/net/cytonic/cytosis/data/DatabaseManager.java b/src/main/java/net/cytonic/cytosis/data/DatabaseManager.java index 5c52469d..60fbfaa8 100644 --- a/src/main/java/net/cytonic/cytosis/data/DatabaseManager.java +++ b/src/main/java/net/cytonic/cytosis/data/DatabaseManager.java @@ -1,7 +1,6 @@ package net.cytonic.cytosis.data; import lombok.Getter; -import net.cytonic.cytosis.logging.Logger; @Getter public class DatabaseManager { @@ -12,14 +11,12 @@ public DatabaseManager() { } public void shutdown() { - database.disconnect(); - Logger.info("Good night!"); + database.disconnect(); } public void setupDatabase() { - database = new Database(); - database.connect(); + database = new Database(); + database.connect(); database.createTables(); } - } \ No newline at end of file diff --git a/src/main/java/net/cytonic/cytosis/events/ServerEventListeners.java b/src/main/java/net/cytonic/cytosis/events/ServerEventListeners.java index a8bff37d..98258b84 100644 --- a/src/main/java/net/cytonic/cytosis/events/ServerEventListeners.java +++ b/src/main/java/net/cytonic/cytosis/events/ServerEventListeners.java @@ -3,7 +3,6 @@ import net.cytonic.cytosis.Cytosis; import net.cytonic.cytosis.config.CytosisSettings; import net.cytonic.cytosis.logging.Logger; -import net.minestom.server.coordinate.Pos; import net.minestom.server.entity.Player; import net.minestom.server.event.player.AsyncPlayerConfigurationEvent; import net.minestom.server.event.player.PlayerChatEvent; @@ -13,12 +12,11 @@ public class ServerEventListeners { public static void initServerEvents() { - Logger.info("Registering player configuration event."); Cytosis.getEventHandler().registerListener(new EventListener<>("core:player-configuration", true, 1, AsyncPlayerConfigurationEvent.class, (event -> { final Player player = event.getPlayer(); event.setSpawningInstance(Cytosis.getDefaultInstance()); - player.setRespawnPoint(new Pos(0, 45, 0)); + player.setRespawnPoint(CytosisSettings.SERVER_SPAWN_POS); }))); Logger.info("Registering player spawn event."); @@ -27,7 +25,6 @@ public static void initServerEvents() { if (CytosisSettings.LOG_PLAYER_IPS) Logger.info(STR."\{event.getPlayer().getUsername()} (\{event.getPlayer().getUuid()}) joined with the ip: \{player.getPlayerConnection().getServerAddress()}"); else Logger.info(STR."\{event.getPlayer().getUsername()} (\{event.getPlayer().getUuid()}) joined."); - Cytosis.getRankManager().addPlayer(player); }))); diff --git a/src/main/java/net/cytonic/cytosis/ranks/PlayerRank.java b/src/main/java/net/cytonic/cytosis/ranks/PlayerRank.java index 15783d63..606cf646 100644 --- a/src/main/java/net/cytonic/cytosis/ranks/PlayerRank.java +++ b/src/main/java/net/cytonic/cytosis/ranks/PlayerRank.java @@ -3,7 +3,6 @@ import lombok.Getter; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; - import static net.cytonic.cytosis.utils.MiniMessageTemplate.MM; /** diff --git a/src/main/java/net/cytonic/cytosis/utils/PosSerializer.java b/src/main/java/net/cytonic/cytosis/utils/PosSerializer.java new file mode 100644 index 00000000..324279e4 --- /dev/null +++ b/src/main/java/net/cytonic/cytosis/utils/PosSerializer.java @@ -0,0 +1,39 @@ +package net.cytonic.cytosis.utils; + +import net.minestom.server.coordinate.Pos; + +public class PosSerializer { + + /** + * Serializes a {@link Pos} object into a human-readable string format. + * + * @param pos The position to be serialized. + * @return A string representation of the position in the format: "Pos{x=%.2f, y=%.2f, z=%.2f, yaw=%.2f, pitch=%.2f}" + */ + public static String serialize(Pos pos) { + return String.format("Pos{x=%.2f, y=%.2f, z=%.2f, yaw=%.2f, pitch=%.2f}", pos.x(), pos.y(), pos.z(), pos.yaw(), pos.pitch()); + } + + /** + * Deserializes a human-readable string format into a {@link Pos} object. + * + * @param serializedPos The string representation of the position to be deserialized. + * The string should be in the format: "Pos{x=%.2f, y=%.2f, z=%.2f, yaw=%.2f, pitch=%.2f}" + * @return A {@link Pos} object representing the deserialized position. + * If the input string is empty, a new {@link Pos} object with all coordinates set to 0 is returned. + */ + public static Pos deserialize(String serializedPos) { + if (!serializedPos.isEmpty()) { + serializedPos = serializedPos.replace("}", ""); + String[] parts = serializedPos.split("[=,\\s]+"); + double x = Double.parseDouble(parts[1]); + double y = Double.parseDouble(parts[3]); + double z = Double.parseDouble(parts[5]); + float yaw = Float.parseFloat(parts[7]); + float pitch = Float.parseFloat(parts[9]); + return new Pos(x, y, z, yaw, pitch); + } else { + return new Pos(0, 0, 0, 180, 0); + } + } +} \ No newline at end of file diff --git a/src/main/resources/config.toml b/src/main/resources/config.toml index 9859398a..93f32c37 100644 --- a/src/main/resources/config.toml +++ b/src/main/resources/config.toml @@ -13,6 +13,8 @@ use_ssl = false # Whether to use SSL proxy_mode = true secret = "hi i am the secret" # this can NOT be empty port = 25565 +world_name = "" +spawn_point = "" # Logging Configuration # Enable/disable logging for various player activities