diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000000..29217e86391 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,36 @@ +FROM gradle:8.5.0-jdk17-alpine as builder + +WORKDIR /app + +COPY ./ /app/ + +RUN gradle jar --no-daemon + +FROM bitnami/git:2.43.0-debian-11-r1 as data + +ARG DATA_REPOSITORY=https://gitlab.com/YuukiPS/GC-Resources.git +ARG DATA_BRANCH=4.0 + +WORKDIR /app + +RUN git clone --branch ${DATA_BRANCH} --depth 1 ${DATA_REPOSITORY} + +FROM bitnami/java:17.0.9-11 + +RUN apt-get update && apt-get install unzip + +WORKDIR /app + +# Copy built assets +COPY --from=builder /app/grasscutter-1.7.4.jar /app/grasscutter.jar +COPY --from=builder /app/keystore.p12 /app/keystore.p12 + +# Copy the resources +COPY --from=data /app/GC-Resources/Resources /app/resources/ + +# Copy startup files +COPY ./entrypoint.sh /app/ + +CMD [ "sh", "/app/entrypoint.sh" ] + +EXPOSE 80 443 8888 22102 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000000..6b8d08bf3a2 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,30 @@ +version: "3.8" +services: + grasscutter: + image: grasscutter:latest + build: . + ports: + - "80:80" + - "443:443" + - "8080:8080" + - "8888:8888" + - "22102:22102" + environment: + DATABASE_INFO_SERVER_CONNECTION_URI: "mongodb://lawnmower:grasscutter@database:27017" + DATABASE_INFO_SERVER_COLLECTION: grasscutter + DATABASE_INFO_GAME_CONNECTION_URI: "mongodb://lawnmower:grasscutter@database:27017" + DATABASE_INFO_GAME_COLLECTION: grasscutter + + stdin_open: true + + database: + image: mongo:7.0.4 + environment: + MONGO_INITDB_ROOT_USERNAME: lawnmower + MONGO_INITDB_ROOT_PASSWORD: grasscutter + MONGO_INITDB_DATABASE: grasscutter + volumes: + - mongodata:/data/db + +volumes: + mongodata: diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100755 index 00000000000..41bfceffad3 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,3 @@ +#/bin/sh + +java -jar /app/grasscutter.jar diff --git a/src/main/java/emu/grasscutter/Grasscutter.java b/src/main/java/emu/grasscutter/Grasscutter.java index 8c3248dd7c2..197126ec065 100644 --- a/src/main/java/emu/grasscutter/Grasscutter.java +++ b/src/main/java/emu/grasscutter/Grasscutter.java @@ -77,8 +77,6 @@ public final class Grasscutter { // Load server configuration. Grasscutter.loadConfig(); - // Attempt to update configuration. - ConfigContainer.updateConfig(); Grasscutter.getLogger().info("Loading Grasscutter..."); @@ -238,22 +236,7 @@ public static void loadLanguage() { /** Attempts to load the configuration from a file. */ public static void loadConfig() { // Check if config.json exists. If not, we generate a new config. - if (!configFile.exists()) { - getLogger().info("config.json could not be found. Generating a default configuration ..."); - config = new ConfigContainer(); - Grasscutter.saveConfig(config); - return; - } - - // If the file already exists, we attempt to load it. - try { - config = JsonUtils.loadToClass(configFile.toPath(), ConfigContainer.class); - } catch (Exception exception) { - getLogger() - .error( - "There was an error while trying to load the configuration from config.json. Please make sure that there are no syntax errors. If you want to start with a default configuration, delete your existing config.json."); - System.exit(1); - } + config = new ConfigContainer(); } /** diff --git a/src/main/java/emu/grasscutter/config/ConfigContainer.java b/src/main/java/emu/grasscutter/config/ConfigContainer.java index fd264e26bb8..e145360a012 100644 --- a/src/main/java/emu/grasscutter/config/ConfigContainer.java +++ b/src/main/java/emu/grasscutter/config/ConfigContainer.java @@ -1,127 +1,412 @@ package emu.grasscutter.config; import ch.qos.logback.classic.Level; -import com.google.gson.JsonObject; import com.google.gson.annotations.SerializedName; -import emu.grasscutter.Grasscutter; -import emu.grasscutter.utils.*; +import emu.grasscutter.utils.Crypto; +import emu.grasscutter.utils.Utils; import lombok.NoArgsConstructor; -import java.util.*; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.stream.Collectors; -import static emu.grasscutter.Grasscutter.*; +import static emu.grasscutter.Grasscutter.ServerDebugMode; +import static emu.grasscutter.Grasscutter.ServerRunMode; /** * *when your JVM fails* */ public class ConfigContainer { - /* - * Configuration changes: - * Version 5 - 'questing' has been changed from a boolean - * to a container of options ('questOptions'). - * This field will be removed in future versions. - * Version 6 - 'questing' has been fully replaced with 'questOptions'. - * The field for 'legacyResources' has been removed. - * Version 7 - 'regionKey' is being added for authentication - * with the new dispatch server. - * Version 8 - 'server' is being added for enforcing handbook server - * addresses. - * Version 9 - 'limits' was added for handbook requests. - * Version 10 - 'trialCostumes' was added for enabling costumes - * on trial avatars. - * Version 11 - 'server.fastRequire' was added for disabling the new - * Lua script require system if performance is a concern. - * Version 12 - 'http.startImmediately' was added to control whether the - * HTTP server should start immediately. - * Version 13 - 'game.useUniquePacketKey' was added to control whether the - * encryption key used for packets is a constant or randomly generated. + /** + * Retrieves the given key from the environment variables. + *

+ * When the key is not set it will return the given default value. + * + * @param key The name of the environment variable + * @param defaultValue The default value when the key is not set + * @return The value from the environment variable or the default value */ - private static int version() { - return 13; + static String getStringFromEnv(String key, String defaultValue) { + var currentValue = System.getenv(key); + + if (currentValue == null) { + return defaultValue; + } + + return currentValue; } /** - * Attempts to update the server's existing configuration. + * Retrieves the given key from the environment variables and tries to parse it as integer. + *

+ * If the environment variable is not present or the parsing fails then the default value will be returned. + * + * @param key The name of the environment variable to parse + * @param defaultValue The default value when the environment variable does not exists or is not a valid integer + * @return The parsed integer or the default value */ - public static void updateConfig() { - try { // Check if the server is using a legacy config. - var configObject = JsonUtils.loadToClass(Grasscutter.configFile.toPath(), JsonObject.class); - if (!configObject.has("version")) { - Grasscutter.getLogger().info("Updating legacy config..."); - Grasscutter.saveConfig(null); - } - } catch (Exception ignored) { } - - var existing = config.version; - var latest = version(); - - if (existing == latest) - return; - - // Create a new configuration instance. - var updated = new ConfigContainer(); - // Update all configuration fields. - var fields = ConfigContainer.class.getDeclaredFields(); - Arrays.stream(fields).forEach(field -> { - try { - field.set(updated, field.get(config)); - } catch (Exception exception) { - Grasscutter.getLogger().error("Failed to update a configuration field.", exception); - } - }); updated.version = version(); + static int getIntFromEnv(String key, int defaultValue) { + var currentValue = System.getenv(key); + + if (currentValue == null) { + return defaultValue; + } + + try { + return Integer.parseInt(currentValue, 10); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Retrieves the given key from the environment variables and tries to parse it as float. + *

+ * If the environment variable is not present or the parsing fails then the default value will be returned. + * + * @param key The name of the environment variable to parse + * @param defaultValue The default value when the environment variable does not exist or is not a valid float + * @return The parsed float or the default value + */ + static float getFloatFromEnv(String key, float defaultValue) { + var currentValue = System.getenv(key); + + if (currentValue == null) { + return defaultValue; + } + + try { + return Float.parseFloat(currentValue); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Retrieves the given key from the environment variables and tries to parse it as float. + *

+ * If the environment variable is not present or the parsing fails then the default value will be returned. + * + * @param key The name of the environment variable to parse + * @param defaultValue The default value when the environment variable does not exists or is not a valid bool + * @return The parsed boolean or the default value + */ + static boolean getBoolFromEnv(String key, boolean defaultValue) { + var currentValue = System.getenv(key); + + if (currentValue == null) { + return defaultValue; + } + + return switch (currentValue.trim()) { + case "true", "on", "1" -> true; + case "false", "off", "0" -> false; + default -> defaultValue; + }; + } + + /** + * Retrieves the given from the environment variables and tries to parse it as a Set. + *

+ * If the environment variable is not present or the parsing fails then the default value will be returned. + *

+ * It expects the following structure: + *

+ * hello,world + *

+ * | |- second entry + *

+ * |- first entry + * + * @param key The name of the environment variable to parse + * @param defaultValue The default value when the environment variable does not exist or is not a valid set + * @param separator The separator which will be used for splitting up the string + * @return The parsed set or the default value + */ + static Set getStringSetFromEnv(String key, Set defaultValue, String separator) { + var currentValue = System.getenv(key); + + if (currentValue == null) { + return defaultValue; + } + + var parts = currentValue.split(separator); + + return Set.of(parts); + } + + /** + * Retrieves the given key from the environment variables and tries to parse it as a Set. + *

+ * If the environment variable is not present or the parsing fails then the default value will be returned. + *

+ * It expects the following structure: + *

+ * 42,1337 + *

+ * | |- second entry + *

+ * |- first entry + * + * @param key The name of the environment variable + * @param defaultValue The default value when the environment variable does not exist + * @param separator The separator which will be used for splitting up the environment variable + * @return The parsed integer set or the default value + */ + static Set getIntSetFromEnv(String key, Set defaultValue, String separator) { + var defaultValues = defaultValue.stream().map(Object::toString).collect(Collectors.toSet()); + var currentValue = getStringSetFromEnv(key, defaultValues, separator); + + return currentValue.stream().map(entry -> Integer.parseInt(entry, 10)).collect(Collectors.toSet()); + } + + /** + * Retrieves the given key from the environment variables and tries to parse it as an enum member. + *

+ * If the environment variable is not present or the parsing fails then the default value will be returned. + * + * @param key The name of the environment variable to parse + * @param enumClass The enum class which contains all members + * @param defaultValue The default value when the environment variable does not exists or is not a valid enum member + * @param The type of the enum member + * @return The parsed enum member or the default value + */ + static > T getEnumFromEnv(String key, Class enumClass, T defaultValue) { + var currentValue = System.getenv(key); + + if (currentValue == null) { + return defaultValue; + } - try { // Save configuration and reload. - Grasscutter.saveConfig(updated); - Grasscutter.loadConfig(); - } catch (Exception exception) { - Grasscutter.getLogger().warn("Failed to save the updated configuration.", exception); + try { + return Enum.valueOf(enumClass, currentValue); + } catch (Exception e) { + return defaultValue; } } + /** + * Retrieves the given key from the environment variables and tries to parse it as string array. + *

+ * If the environment variable is not present or the parsing fails then the default value will be returned. + *

+ * It expects the following structure: + *

+ * hello,world + *

+ * | |- second entry + *

+ * |- first entry + * + * @param key The name of the environment variable to parse + * @param defaultValue The default value when the environment variable does not exist + * @param separator The separator which will be used for splitting up the string + * @return The parsed string array or the default value + */ + static String[] getStringArrayFromEnv(String key, String[] defaultValue, String separator) { + var currentValue = System.getenv(key); + + if (currentValue == null) { + return defaultValue; + } + + return currentValue.split(separator); + } + + /** + * Retrieves the given key from the environment variables and tries to parse it as an int array. + *

+ * If the environment variable is not present or the parsing fails then the default value will be returned. + *

+ * It expects the following structure: + *

+ * 1,2 + *

+ * | |- second entry + *

+ * |- first entry + * + * @param key The name of the environment variable to parse + * @param defaultValue The default value when the environment variable does not exist + * @param separator The separator which will be used for splitting up the string + * @return The parsed int array or the default value + */ + static int[] getIntArrayFromEnv(String key, int[] defaultValue, String separator) { + var currentValue = System.getenv(key); + + if (currentValue == null) { + return defaultValue; + } + + return Arrays.stream(currentValue.split(separator)).mapToInt(Integer::parseInt).toArray(); + } + + /** + * Retrieves the given key from the environment variables and tries to parse it as a MailItem array. + *

+ * If the environment variable is not present or the parsing fails then the default value will be returned. + *

+ * It expects the following structure: + *

+ * 1,1,1|2,1,1 + *

+ * | | |- Item level + *

+ * | |- Item count + *

+ * |- Item ID + * + * @param key The name of the environment variable to parse + * @param defaultValue The default value when the environment variable does not exist + * @param partsSeparator The separator which will be used for splitting up the string into parts + * @param valuesSeparator The separator which will be used for splitting up the parts into values + * @return The parsed MailItem array or the default value + */ + static emu.grasscutter.game.mail.Mail.MailItem[] getMailItemsFromEnv(String key, emu.grasscutter.game.mail.Mail.MailItem[] defaultValue, String partsSeparator, String valuesSeparator) { + var currentValue = System.getenv(key); + + if (currentValue == null) { + return defaultValue; + } + + var parts = Arrays.stream(currentValue.split(partsSeparator)).map(part -> part.split(valuesSeparator)); + + return (emu.grasscutter.game.mail.Mail.MailItem[]) parts.filter(part -> part.length != 3).map(part -> { + var itemId = Integer.parseInt(part[0], 10); + var itemCount = Integer.parseInt(part[1], 10); + var itemLevel = Integer.parseInt(part[2], 10); + + return new emu.grasscutter.game.mail.Mail.MailItem(itemId, itemCount, itemLevel); + }).toArray(); + } + + /** + * Retrieves the given key from the environment variables and tries to parse it as a MailItem array. + *

+ * If the environment variable is not present or the parsing fails then the default value will be returned. + *

+ * It expects the following structure: + * VISION_LEVEL_NORMAL,80,20|VISION_LEVEL_LITTLE_REMOTE,16,40 + *

+ * | | |- Grid width + *

+ * | |- Vision range + *

+ * |- Name + * + * @param key The name of the environment variable to parse + * @param defaultValue The default value when the environment variable does not exist + * @param partsSeparator The separator which will be used for splitting up the string into parts + * @param valuesSeparator The separator which will be used for splitting up the parts into values + * @return The parsed VisionOptions or the default value + */ + static VisionOptions[] getVisionOptionsFromEnv(String key, VisionOptions[] defaultValue, String partsSeparator, String valuesSeparator) { + var currentValue = System.getenv(key); + + if (currentValue == null) { + return defaultValue; + } + + var parts = currentValue.split(partsSeparator); + return (VisionOptions[]) Arrays.stream(parts).map(part -> part.split(valuesSeparator)).filter(values -> values.length == 3).map(values -> { + var name = values[0]; + var visionRange = Integer.parseInt(values[1]); + var gridWidth = Integer.parseInt(values[2]); + + return new VisionOptions(name, visionRange, gridWidth); + }).toArray(); + } + + /** + * Retrieves the given key from the environment variables and tries to parse it as a Region array. + *

+ * If the environment variable is not present or the parsing fails then the default value will be returned. + *

+ * It expects the following structure: + *

+ * my region,my title,127.0.0.1,1337|my second region, my second title,127.0.0.1,42 + *

+ * | | | |- port + *

+ * | | |- address + *

+ * | |- title + *

+ * |- name + * + * @param key The name of the environment variable to parse + * @param defaultValue The default value when the environment variable does not exist + * @param partsSeparator The separator which will be used for splitting up the string into parts + * @param valuesSeparator The separator which will be used for splitting up the parts into values + * @return The parsed Region list or the default value + */ + static List getRegionsFromEnv(String key, List defaultValue, String partsSeparator, String valuesSeparator) { + var currentValue = System.getenv(key); + + if (currentValue == null) { + return defaultValue; + } + + var parts = currentValue.split(partsSeparator); + return Arrays.stream(parts).map(part -> part.split(valuesSeparator)).filter(values -> values.length == 4).map(values -> { + var name = values[0]; + var title = values[1]; + var address = values[2]; + var port = Integer.parseInt(values[3]); + + return new Region(name, title, address, port); + }).collect(Collectors.toList()); + } + public Structure folderStructure = new Structure(); public Database databaseInfo = new Database(); public Language language = new Language(); public Account account = new Account(); public Server server = new Server(); - // DO NOT. TOUCH. THE VERSION NUMBER. - public int version = version(); - /* Option containers. */ public static class Database { - public DataStore server = new DataStore(); - public DataStore game = new DataStore(); + public DataStore server = new DataStore("SERVER"); + public DataStore game = new DataStore("GAME"); public static class DataStore { - public String connectionUri = "mongodb://localhost:27017"; - public String collection = "grasscutter"; + public String connectionUri; + public String collection; + + public DataStore(String key) { + this.connectionUri = getStringFromEnv("DATABASE_INFO_" + key + "_CONNECTION_URI", "mongodb://localhost:27017"); + this.collection = getStringFromEnv("DATABASE_INFO_" + key + "_COLLECTION", "grasscutter"); + } } } public static class Structure { - public String resources = "./resources/"; - public String data = "./data/"; - public String packets = "./packets/"; - public String scripts = "resources:Scripts/"; - public String plugins = "./plugins/"; - public String cache = "./cache/"; + public String resources = getStringFromEnv("FOLDER_STRUCTURE_RESOURCES", "./resources/"); + public String data = getStringFromEnv("FOLDER_STRUCTURE_DATA", "./data/"); + public String packets = getStringFromEnv("FOLDER_STRUCTURE_PACKETS", "./packets/"); + public String scripts = getStringFromEnv("FOLDER_STRUCTURE_SCRIPTS", "resources:Scripts/"); + public String plugins = getStringFromEnv("FOLDER_STRUCTURE_PLUGINS", "./plugins/"); + public String cache = getStringFromEnv("FOLDER_STRUCTURE_CACHE", "./cache/"); // UNUSED (potentially added later?) // public String dumps = "./dumps/"; } public static class Server { - public Set debugWhitelist = Set.of(); - public Set debugBlacklist = Set.of(); - public ServerRunMode runMode = ServerRunMode.HYBRID; - public boolean logCommands = false; + public Set debugWhitelist = getIntSetFromEnv("SERVER_DEBUG_WHITELIST", Set.of(), ","); + public Set debugBlacklist = getIntSetFromEnv("SERVER_DEBUG_BLACKLIST", Set.of(), ","); + public ServerRunMode runMode = getEnumFromEnv("SERVER_RUN_MODE", ServerRunMode.class, ServerRunMode.HYBRID); + + public boolean logCommands = getBoolFromEnv("SERVER_LOG_COMMANDS", false); /** - * If enabled, the 'require' Lua function will load the script's compiled varient into the context. (faster; doesn't work as well) + * If enabled, the 'require' Lua function will load the script's compiled variant into the context. (faster; doesn't work as well) * If disabled, all 'require' calls will be replaced with the referenced script's source. (slower; works better) */ - public boolean fastRequire = true; + public boolean fastRequire = getBoolFromEnv("SERVER_FAST_REQUIRE", true); public HTTP http = new HTTP(); public Game game = new Game(); @@ -133,29 +418,29 @@ public static class Server { public static class Language { public Locale language = Locale.getDefault(); public Locale fallback = Locale.US; - public String document = "EN"; + public String document = getStringFromEnv("LANGUAGE_DOCUMENT", "EN"); } public static class Account { - public boolean autoCreate = false; - public boolean EXPERIMENTAL_RealPassword = false; - public String[] defaultPermissions = {}; - public int maxPlayer = -1; + public boolean autoCreate = getBoolFromEnv("ACCOUNT_AUTO_CREATE", false); + public boolean EXPERIMENTAL_RealPassword = getBoolFromEnv("ACCOUNT_EXPERIMENTAL_REAL_PASSWORD", false); + public String[] defaultPermissions = getStringArrayFromEnv("ACCOUNT_DEFAULT_PERMISSIONS", new String[]{}, ","); + public int maxPlayer = getIntFromEnv("ACCOUNT_MAX_PLAYER", -1); } /* Server options. */ public static class HTTP { /* This starts the HTTP server before the game server. */ - public boolean startImmediately = false; + public boolean startImmediately = getBoolFromEnv("SERVER_HTTP_START_IMMEDIATELY", false); - public String bindAddress = "0.0.0.0"; - public int bindPort = 443; + public String bindAddress = getStringFromEnv("SERVER_HTTP_BIND_ADDRESS", "0.0.0.0"); + public int bindPort = getIntFromEnv("SERVER_HTTP_BIND_PORT", 443); /* This is the address used in URLs. */ - public String accessAddress = "127.0.0.1"; + public String accessAddress = getStringFromEnv("SERVER_HTTP_ACCESS_ADDRESS", "127.0.0.1"); /* This is the port used in URLs. */ - public int accessPort = 0; + public int accessPort = getIntFromEnv("SERVER_HTTP_ACCESS_PORT", 0); public Encryption encryption = new Encryption(); public Policies policies = new Policies(); @@ -163,66 +448,65 @@ public static class HTTP { } public static class Game { - public String bindAddress = "0.0.0.0"; - public int bindPort = 22102; + public String bindAddress = getStringFromEnv("SERVER_GAME_BIND_ADDRESS", "0.0.0.0"); + public int bindPort = getIntFromEnv("SERVER_GAME_BIND_PORT", 22102); /* This is the address used in the default region. */ - public String accessAddress = "127.0.0.1"; + public String accessAddress = getStringFromEnv("SERVER_GAME_ACCESS_ADDRESS", "127.0.0.1"); /* This is the port used in the default region. */ - public int accessPort = 0; + public int accessPort = getIntFromEnv("SERVER_GAME_ACCESS_PORT", 0); /* Enabling this will generate a unique packet encryption key for each player. */ - public boolean useUniquePacketKey = true; + public boolean useUniquePacketKey = getBoolFromEnv("SERVER_GAME_USE_UNIQUE_PACKET_KEY", true); /* Entities within a certain range will be loaded for the player */ - public int loadEntitiesForPlayerRange = 300; + public int loadEntitiesForPlayerRange = getIntFromEnv("SERVER_GAME_LOAD_ENTITIES_FOR_PLAYER_RANGE", 300); /* Start in 'unstable-quests', Lua scripts will be enabled by default. */ - public boolean enableScriptInBigWorld = true; - public boolean enableConsole = true; + public boolean enableScriptInBigWorld = getBoolFromEnv("SERVER_GAME_ENABLE_SCRIPT_IN_BIG_WORLD", true); + public boolean enableConsole = getBoolFromEnv("SERVER_GAME_ENABLE_CONSOLE", true); /* Kcp internal work interval (milliseconds) */ - public int kcpInterval = 20; + public int kcpInterval = getIntFromEnv("SERVER_GAME_KCP_INTERVAL", 20); /* Controls whether packets should be logged in console or not */ - public ServerDebugMode logPackets = ServerDebugMode.NONE; + public ServerDebugMode logPackets = getEnumFromEnv("SERVER_GAME_LOG_PACKETS", ServerDebugMode.class, ServerDebugMode.NONE); /* Show packet payload in console or no (in any case the payload is shown in encrypted view) */ - public boolean isShowPacketPayload = false; + public boolean isShowPacketPayload = getBoolFromEnv("SERVER_GAME_IS_SHOW_PACKET_PAYLOAD", false); /* Show annoying loop packets or no */ - public boolean isShowLoopPackets = false; + public boolean isShowLoopPackets = getBoolFromEnv("SERVER_GAME_IS_SHOW_LOOP_PACKETS", false); - public boolean cacheSceneEntitiesEveryRun = false; + public boolean cacheSceneEntitiesEveryRun = getBoolFromEnv("SERVER_GAME_CACHE_SCENE_ENTITIES_EVERY_RUN", false); public GameOptions gameOptions = new GameOptions(); public JoinOptions joinOptions = new JoinOptions(); public ConsoleAccount serverAccount = new ConsoleAccount(); - public VisionOptions[] visionOptions = new VisionOptions[] { - new VisionOptions("VISION_LEVEL_NORMAL" , 80 , 20), - new VisionOptions("VISION_LEVEL_LITTLE_REMOTE" , 16 , 40), - new VisionOptions("VISION_LEVEL_REMOTE" , 1000 , 250), - new VisionOptions("VISION_LEVEL_SUPER" , 4000 , 1000), - new VisionOptions("VISION_LEVEL_NEARBY" , 40 , 20), - new VisionOptions("VISION_LEVEL_SUPER_NEARBY" , 20 , 20) - }; + public VisionOptions[] visionOptions = getVisionOptionsFromEnv("SERVER_GAME_VISION_OPTIONS", new VisionOptions[]{ + new VisionOptions("VISION_LEVEL_NORMAL", 80, 20), + new VisionOptions("VISION_LEVEL_LITTLE_REMOTE", 16, 40), + new VisionOptions("VISION_LEVEL_REMOTE", 1000, 250), + new VisionOptions("VISION_LEVEL_SUPER", 4000, 1000), + new VisionOptions("VISION_LEVEL_NEARBY", 40, 20), + new VisionOptions("VISION_LEVEL_SUPER_NEARBY", 20, 20) + }, "|", ","); } /* Data containers. */ public static class Dispatch { /* An array of servers. */ - public List regions = List.of(); + public List regions = getRegionsFromEnv("SERVER_DISPATCH_REGIONS", List.of(), "|", ","); /* The URL used to make HTTP requests to the dispatch server. */ - public String dispatchUrl = "ws://127.0.0.1:1111"; + public String dispatchUrl = getStringFromEnv("SERVER_DISPATCH_DISPATCH_URL", "ws://127.0.0.1:1111"); /* A unique key used for encryption. */ - public byte[] encryptionKey = Crypto.createSessionKey(32); + public byte[] encryptionKey = Utils.base64Decode(getStringFromEnv("SERVER_DISPATCH_ENCRYPTION_KEY", Utils.base64Encode(Crypto.createSessionKey(32)))); /* A unique key used for authentication. */ - public String dispatchKey = Utils.base64Encode( - Crypto.createSessionKey(32)); + public String dispatchKey = getStringFromEnv("SERVER_DISPATCH_DISPATCH_KEY", Utils.base64Encode(Crypto.createSessionKey(32))); - public String defaultName = "Grasscutter"; + public String defaultName = getStringFromEnv("SERVER_DISPATCH_DEFAULT_NAME", "Grasscutter"); /* Controls whether http requests should be logged in console or not */ - public ServerDebugMode logRequests = ServerDebugMode.NONE; + public ServerDebugMode logRequests = getEnumFromEnv("SERVER_DISPATCH_SERVER_DEBUG_MODE", ServerDebugMode.class, ServerDebugMode.NONE); } /* Debug options container, used when jar launch argument is -debug | -debugall and override default values @@ -236,46 +520,46 @@ public static class DebugMode { public Level servicesLoggersLevel = Level.INFO; /* Controls whether packets should be logged in console or not */ - public ServerDebugMode logPackets = ServerDebugMode.ALL; + public ServerDebugMode logPackets = getEnumFromEnv("SERVER_DEBUG_MODE_LOG_PACKETS", ServerDebugMode.class, ServerDebugMode.ALL); /* Show packet payload in console or no (in any case the payload is shown in encrypted view) */ - public boolean isShowPacketPayload = false; + public boolean isShowPacketPayload = getBoolFromEnv("SERVER_DEBUG_MODE_IS_SHOW_PACKET_PAYLOAD", false); /* Show annoying loop packets or no */ - public boolean isShowLoopPackets = false; + public boolean isShowLoopPackets = getBoolFromEnv("SERVER_DEBUG_MODE_IS_SHOW_LOOP_PACKETS", false); /* Controls whether http requests should be logged in console or not */ - public ServerDebugMode logRequests = ServerDebugMode.ALL; + public ServerDebugMode logRequests = getEnumFromEnv("SERVER_DEBUG_MODE_LOG_REQUESTS", ServerDebugMode.class, ServerDebugMode.ALL); } public static class Encryption { - public boolean useEncryption = true; + public boolean useEncryption = getBoolFromEnv("SERVER_HTTP_ENCRYPTION_USE_ENCRYPTION", true); /* Should 'https' be appended to URLs? */ - public boolean useInRouting = true; - public String keystore = "./keystore.p12"; - public String keystorePassword = "123456"; + public boolean useInRouting = getBoolFromEnv("SERVER_HTTP_ENCRYPTION_USE_IN_ROUTING", true); + public String keystore = getStringFromEnv("SERVER_HTTP_ENCRYPTION_KEYSTORE", "./keystore.p12"); + public String keystorePassword = getStringFromEnv("SERVER_HTTP_ENCRYPTION_KEYSTORE_PASSWORD", "123456"); } public static class Policies { public Policies.CORS cors = new Policies.CORS(); public static class CORS { - public boolean enabled = true; - public String[] allowedOrigins = new String[]{"*"}; + public boolean enabled = getBoolFromEnv("SERVER_HTTP_POLICIES_CORS_ENABLED", true); + public String[] allowedOrigins = getStringArrayFromEnv("SERVER_HTTP_POLICIES_ALLOWED_ORIGINS", new String[]{"*"}, ","); } } public static class GameOptions { public InventoryLimits inventoryLimits = new InventoryLimits(); public AvatarLimits avatarLimits = new AvatarLimits(); - public int sceneEntityLimit = 1000; // Unenforced. TODO: Implement. + public int sceneEntityLimit = getIntFromEnv("SERVER_GAME_GAME_OPTIONS_SCENE_ENTITY_LIMIT", 1000); // Unenforced. TODO: Implement. - public boolean watchGachaConfig = false; - public boolean enableShopItems = true; - public boolean staminaUsage = true; - public boolean energyUsage = true; - public boolean fishhookTeleport = true; - public boolean trialCostumes = false; + public boolean watchGachaConfig = getBoolFromEnv("SERVER_GAME_GAME_OPTIONS_WATCH_GACHA_CONFIG", false); + public boolean enableShopItems = getBoolFromEnv("SERVER_GAME_GAME_OPTIONS_ENABLE_SHOP_ITEMS", true); + public boolean staminaUsage = getBoolFromEnv("SERVER_GAME_GAME_OPTIONS_STAMINA_USAGE", true); + public boolean energyUsage = getBoolFromEnv("SERVER_GAME_GAME_OPTIONS_ENERGY_USAGE", true); + public boolean fishhookTeleport = getBoolFromEnv("SERVER_GAME_GAME_OPTIONS_FISHHOOK_TELEPORT", true); + public boolean trialCostumes = getBoolFromEnv("SERVER_GAME_GAME_OPTIONS_TRIAL_COSTUMES", false); @SerializedName(value = "questing", alternate = "questOptions") public Questing questing = new Questing(); @@ -285,63 +569,63 @@ public static class GameOptions { public HandbookOptions handbook = new HandbookOptions(); public static class InventoryLimits { - public int weapons = 2000; - public int relics = 2000; - public int materials = 2000; - public int furniture = 2000; - public int all = 30000; + public int weapons = getIntFromEnv("SERVER_GAME_GAME_OPTIONS_INVENTORY_LIMITS_WEAPONS", 2000); + public int relics = getIntFromEnv("SERVER_GAME_GAME_OPTIONS_INVENTORY_LIMITS_RELICS", 2000); + public int materials = getIntFromEnv("SERVER_GAME_GAME_OPTIONS_INVENTORY_LIMITS_MATERIALS", 2000); + public int furniture = getIntFromEnv("SERVER_GAME_GAME_OPTIONS_INVENTORY_LIMITS_FURNITURE", 2000); + public int all = getIntFromEnv("SERVER_GAME_GAME_OPTIONS_INVENTORY_LIMITS_ALL", 30000); } public static class AvatarLimits { - public int singlePlayerTeam = 4; - public int multiplayerTeam = 4; + public int singlePlayerTeam = getIntFromEnv("SERVER_GAME_GAME_OPTIONS_AVATAR_LIMITS_SINGLE_PLAYER_TEAM", 4); + public int multiplayerTeam = getIntFromEnv("SERVER_GAME_GAME_OPTIONS_AVATAR_LIMITS_MULTIPLAYER_TEAM", 4); } public static class Rates { - public float adventureExp = 1.0f; - public float mora = 1.0f; - public float leyLines = 1.0f; + public float adventureExp = getFloatFromEnv("SERVER_GAME_GAME_OPTIONS_RATES_ADVENTURE_EXP", 1.0f); + public float mora = getFloatFromEnv("SERVER_GAME_GAME_OPTIONS_RATES_MORA", 1.0f); + public float leyLines = getFloatFromEnv("SERVER_GAME_GAME_OPTIONS_RATES_LEY_LINES", 1.0f); } public static class ResinOptions { - public boolean resinUsage = false; - public int cap = 160; - public int rechargeTime = 480; + public boolean resinUsage = getBoolFromEnv("SERVER_GAME_GAME_OPTIONS_RESIN_OPTIONS_RESIN_USAGE", false); + public int cap = getIntFromEnv("SERVER_GAME_GAME_OPTIONS_RESIN_OPTIONS_CAP", 160); + public int rechargeTime = getIntFromEnv("SERVER_GAME_GAME_OPTIONS_RESIN_OPTIONS_RECHARGE_TIME", 480); } public static class Questing { /* Should questing behavior be used? */ - public boolean enabled = true; + public boolean enabled = getBoolFromEnv("SERVER_GAME_GAME_OPTIONS_QUESTING_ENABLED", true); } public static class HandbookOptions { - public boolean enable = false; - public boolean allowCommands = true; + public boolean enable = getBoolFromEnv("SERVER_GAME_GAME_OPTIONS_HANDBOOK_OPTIONS_ENABLE", false); + public boolean allowCommands = getBoolFromEnv("SERVER_GAME_GAME_OPTIONS_HANDBOOK_OPTIONS_ALLOW_COMMANDS", true); public Limits limits = new Limits(); public Server server = new Server(); public static class Limits { /* Are rate limits checked? */ - public boolean enabled = false; + public boolean enabled = getBoolFromEnv("SERVER_GAME_GAME_OPTIONS_HANDBOOK_OPTIONS_LIMITS_ENABLED", false); /* The time for limits to expire. */ - public int interval = 3; + public int interval = getIntFromEnv("SERVER_GAME_GAME_OPTIONS_HANDBOOK_OPTIONS_LIMITS_INTERVAL", 3); /* The maximum amount of normal requests. */ - public int maxRequests = 10; + public int maxRequests = getIntFromEnv("SERVER_GAME_GAME_OPTIONS_HANDBOOK_OPTIONS_LIMITS_MAX_REQUESTS", 10); /* The maximum amount of entities to be spawned in one request. */ - public int maxEntities = 25; + public int maxEntities = getIntFromEnv("SERVER_GAME_GAME_OPTIONS_HANDBOOK_OPTIONS_LIMITS_MAX_ENTITIES", 25); } public static class Server { /* Are the server settings sent to the handbook? */ - public boolean enforced = false; + public boolean enforced = getBoolFromEnv("SERVER_GAME_GAME_OPTIONS_HANDBOOK_CONFIG_SERVER_ENFORCED", false); /* The default server address for the handbook's authentication. */ - public String address = "127.0.0.1"; + public String address = getStringFromEnv("SERVER_GAME_GAME_OPTIONS_HANDBOOK_CONFIG_SERVER_ADDRESS", "127.0.0.1"); /* The default server port for the handbook's authentication. */ - public int port = 443; + public int port = getIntFromEnv("SERVER_GAME_GAME_OPTIONS_HANDBOOK_CONFIG_SERVER_PORT", 443); /* Should the defaults be enforced? */ - public boolean canChange = true; + public boolean canChange = getBoolFromEnv("SERVER_GAME_GAME_OPTIONS_HANDBOOK_CONFIG_SERVER_CAN_CHANGE", true); } } } @@ -359,40 +643,37 @@ public VisionOptions(String name, int visionRange, int gridWidth) { } public static class JoinOptions { - public int[] welcomeEmotes = {2007, 1002, 4010}; - public String welcomeMessage = "Welcome to a Grasscutter server."; + public int[] welcomeEmotes = getIntArrayFromEnv("SERVER_GAME_JOIN_OPTIONS_WELCOME_EMOTES", new int[]{2007, 1002, 4010}, ","); + public String welcomeMessage = getStringFromEnv("SERVER_GAME_JOIN_OPTIONS_WELCOME_MESSAGE", "Welcome to a Grasscutter server."); public JoinOptions.Mail welcomeMail = new JoinOptions.Mail(); public static class Mail { - public String title = "Welcome to Grasscutter!"; - public String content = """ - Hi there!\r - First of all, welcome to Grasscutter. If you have any issues, please let us know so that Lawnmower can help you! \r - \r - Check out our:\r - - """; - public String sender = "Lawnmower"; - public emu.grasscutter.game.mail.Mail.MailItem[] items = { - new emu.grasscutter.game.mail.Mail.MailItem(13509, 1, 1), - new emu.grasscutter.game.mail.Mail.MailItem(201, 99999, 1) - }; + public String title = getStringFromEnv("SERVER_GAME_JOIN_OPTIONS_WELCOME_MAIL_TITLE", "Welcome to Grasscutter!"); + public String content = getStringFromEnv("SERVER_GAME_JOIN_OPTIONS_WELCOME_MAIL_CONTENT", """ + Hi there!\r + First of all, welcome to Grasscutter. If you have any issues, please let us know so that Lawnmower can help you! \r + \r + Check out our:\r + + """); + public String sender = getStringFromEnv("SERVER_GAME_JOIN_OPTIONS_WELCOME_MAIL_SENDER", "Lawnmower"); + public emu.grasscutter.game.mail.Mail.MailItem[] items = getMailItemsFromEnv("SERVER_GAME_JOIN_OPTIONS_WELCOME_MAIL_ITEMS", new emu.grasscutter.game.mail.Mail.MailItem[]{new emu.grasscutter.game.mail.Mail.MailItem(13509, 1, 1), new emu.grasscutter.game.mail.Mail.MailItem(201, 99999, 1)}, "|", ","); } } public static class ConsoleAccount { - public int avatarId = 10000007; - public int nameCardId = 210001; - public int adventureRank = 1; - public int worldLevel = 0; + public int avatarId = getIntFromEnv("SERVER_GAME_CONSOLE_ACCOUNT_AVATAR_ID", 10000007); + public int nameCardId = getIntFromEnv("SERVER_GAME_CONSOLE_ACCOUNT_NAME_CARD_ID", 210001); + public int adventureRank = getIntFromEnv("SERVER_GAME_CONSOLE_ACCOUNT_ADVENTURE_RANK", 1); + public int worldLevel = getIntFromEnv("SERVER_GAME_CONSOLE_ACCOUNT_WORLD_LEVEL", 0); - public String nickName = "Server"; - public String signature = "Welcome to Grasscutter!"; + public String nickName = getStringFromEnv("SERVER_GAME_CONSOLE_ACCOUNT_NICK_NAME", "Server"); + public String signature = getStringFromEnv("SERVER_GAME_CONSOLE_ACCOUNT_SIGNATURE", "Welcome to Grasscutter!"); } public static class Files { - public String indexFile = "./index.html"; - public String errorFile = "./404.html"; + public String indexFile = getStringFromEnv("SERVER_HTTP_FILES_INDEX_FILE", "./index.html"); + public String errorFile = getStringFromEnv("SERVER_HTTP_FILES_ERROR_FILE", "./404.html"); } /* Objects. */ @@ -404,14 +685,11 @@ public static class Region { public String Ip = "127.0.0.1"; public int Port = 22102; - public Region( - String name, String title, - String address, int port - ) { + public Region(String name, String title, String address, int port) { this.Name = name; this.Title = title; this.Ip = address; - this.Port = port; + this.Port = port; } } }