diff --git a/src/main/java/emu/grasscutter/Config.java b/src/main/java/emu/grasscutter/Config.java deleted file mode 100644 index 4ec16e0d1..000000000 --- a/src/main/java/emu/grasscutter/Config.java +++ /dev/null @@ -1,110 +0,0 @@ -package emu.grasscutter; - -import java.util.Locale; -import emu.grasscutter.Grasscutter.ServerDebugMode; -import emu.grasscutter.Grasscutter.ServerRunMode; -import emu.grasscutter.game.mail.Mail; - -public final class Config { - public String DatabaseUrl = "mongodb://localhost:27017"; - public String DatabaseCollection = "grasscutter"; - - public String RESOURCE_FOLDER = "./resources/"; - public String DATA_FOLDER = "./data/"; - public String PACKETS_FOLDER = "./packets/"; - public String DUMPS_FOLDER = "./dumps/"; - public String KEY_FOLDER = "./keys/"; - public String SCRIPTS_FOLDER = "./resources/Scripts/"; - public String PLUGINS_FOLDER = "./plugins/"; - - public ServerDebugMode DebugMode = ServerDebugMode.NONE; // ALL, MISSING, NONE - public ServerRunMode RunMode = ServerRunMode.HYBRID; // HYBRID, DISPATCH_ONLY, GAME_ONLY - public GameServerOptions GameServer = new GameServerOptions(); - public DispatchServerOptions DispatchServer = new DispatchServerOptions(); - public Locale LocaleLanguage = Locale.getDefault(); - public Locale DefaultLanguage = Locale.US; - - public Boolean OpenStamina = true; - public GameServerOptions getGameServerOptions() { - return GameServer; - } - - public DispatchServerOptions getDispatchOptions() { return DispatchServer; } - - public static class DispatchServerOptions { - public String Ip = "0.0.0.0"; - public String PublicIp = "127.0.0.1"; - public int Port = 443; - public int PublicPort = 0; - public String KeystorePath = "./keystore.p12"; - public String KeystorePassword = "123456"; - public Boolean UseSSL = true; - public Boolean FrontHTTPS = true; - public Boolean CORS = false; - public String[] CORSAllowedOrigins = new String[] { "*" }; - - public boolean AutomaticallyCreateAccounts = false; - public String[] defaultPermissions = new String[] { "" }; - - public RegionInfo[] GameServers = {}; - - public RegionInfo[] getGameServers() { - return GameServers; - } - - public static class RegionInfo { - public String Name = "os_usa"; - public String Title = "Test"; - public String Ip = "127.0.0.1"; - public int Port = 22102; - } - } - - public static class GameServerOptions { - public String Name = "Test"; - public String Ip = "0.0.0.0"; - public String PublicIp = "127.0.0.1"; - public int Port = 22102; - public int PublicPort = 0; - - public String DispatchServerDatabaseUrl = "mongodb://localhost:27017"; - public String DispatchServerDatabaseCollection = "grasscutter"; - - public int InventoryLimitWeapon = 2000; - public int InventoryLimitRelic = 2000; - public int InventoryLimitMaterial = 2000; - public int InventoryLimitFurniture = 2000; - public int InventoryLimitAll = 30000; - public int MaxAvatarsInTeam = 4; - public int MaxAvatarsInTeamMultiplayer = 4; - public int MaxEntityLimit = 1000; // Max entity limit per world. // TODO: Enforce later. - public boolean WatchGacha = false; - public String ServerNickname = "Server"; - public int ServerAvatarId = 10000007; - public int ServerNameCardId = 210001; - public int ServerLevel = 1; - public int ServerWorldLevel = 1; - public String ServerSignature = "Server Signature"; - public int[] WelcomeEmotes = {2007, 1002, 4010}; - public String WelcomeMotd = "Welcome to Grasscutter emu"; - public String WelcomeMailTitle = "Welcome to Grasscutter!"; - public String WelcomeMailSender = "Lawnmower"; - public String WelcomeMailContent = "Hi there!\r\nFirst of all, welcome to Grasscutter. If you have any issues, please let us know so that Lawnmower can help you! \r\n\r\nCheck out our:\r\n"; - public Mail.MailItem[] WelcomeMailItems = { - new Mail.MailItem(13509, 1, 1), - new Mail.MailItem(201, 10000, 1), - }; - - public boolean EnableOfficialShop = true; - - public GameRates Game = new GameRates(); - - public GameRates getGameRates() { return Game; } - - public static class GameRates { - public float ADVENTURE_EXP_RATE = 1.0f; - public float MORA_RATE = 1.0f; - public float DOMAIN_DROP_RATE = 1.0f; - } - } -} diff --git a/src/main/java/emu/grasscutter/Configuration.java b/src/main/java/emu/grasscutter/Configuration.java new file mode 100644 index 000000000..7adc334c1 --- /dev/null +++ b/src/main/java/emu/grasscutter/Configuration.java @@ -0,0 +1,91 @@ +package emu.grasscutter; + +import emu.grasscutter.utils.ConfigContainer; + +import java.util.Locale; + +import static emu.grasscutter.Grasscutter.config; + +/** + * A data container for the server's configuration. + * + * Use `import static emu.grasscutter.Configuration.*;` + * to import all configuration constants. + */ +public final class Configuration extends ConfigContainer { + + /* + * Constants + */ + + // 'c' is short for 'config' and makes code look 'cleaner'. + public static final ConfigContainer c = config; + + public static final Locale LANGUAGE = config.language.language; + public static final Locale FALLBACK_LANGUAGE = config.language.fallback; + public static final String DATA_FOLDER = config.folderStructure.data; + public static final String RESOURCES_FOLDER = config.folderStructure.resources; + public static final String KEYS_FOLDER = config.folderStructure.keys; + public static final String PLUGINS_FOLDER = config.folderStructure.plugins; + public static final String SCRIPTS_FOLDER = config.folderStructure.scripts; + public static final String PACKETS_FOLDER = config.folderStructure.packets; + + public static final Server SERVER = config.server; + public static final Database DATABASE = config.databaseInfo; + public static final Account ACCOUNT = config.account; + + public static final Dispatch DISPATCH_INFO = config.server.dispatch; + public static final Game GAME_INFO = config.server.game; + + public static final Encryption DISPATCH_ENCRYPTION = config.server.dispatch.encryption; + public static final Policies DISPATCH_POLICIES = config.server.dispatch.policies; + + public static final GameOptions GAME_OPTIONS = config.server.game.gameOptions; + public static final GameOptions.InventoryLimits INVENTORY_LIMITS = config.server.game.gameOptions.inventoryLimits; + + /* + * Utilities + */ + + public static String DATA(String path) { + return DATA_FOLDER + "/" + path; + } + + public static String RESOURCE(String path) { + return RESOURCES_FOLDER + "/" + path; + } + + public static String SCRIPT(String path) { + return SCRIPTS_FOLDER + "/" + path; + } + + /** + * Fallback method. + * @param left Attempt to use. + * @param right Use if left is undefined. + * @return Left or right. + */ + public static T lr(T left, T right) { + return left == null ? right : left; + } + + /** + * {@link Configuration#lr(Object, Object)} for {@link String}s. + * @param left Attempt to use. + * @param right Use if left is empty. + * @return Left or right. + */ + public static String lr(String left, String right) { + return left.isEmpty() ? right : left; + } + + /** + * {@link Configuration#lr(Object, Object)} for {@link Integer}s. + * @param left Attempt to use. + * @param right Use if left is 0. + * @return Left or right. + */ + public static int lr(int left, int right) { + return left == 0 ? right : left; + } +} \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/Grasscutter.java b/src/main/java/emu/grasscutter/Grasscutter.java index 8bdb3c207..73e761e6e 100644 --- a/src/main/java/emu/grasscutter/Grasscutter.java +++ b/src/main/java/emu/grasscutter/Grasscutter.java @@ -1,15 +1,13 @@ package emu.grasscutter; -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOError; +import java.io.*; import java.util.Calendar; import emu.grasscutter.command.CommandMap; import emu.grasscutter.plugin.PluginManager; import emu.grasscutter.plugin.api.ServerHook; import emu.grasscutter.scripts.ScriptLoader; +import emu.grasscutter.utils.ConfigContainer; import emu.grasscutter.utils.Utils; import org.jline.reader.EndOfFileException; import org.jline.reader.LineReader; @@ -32,17 +30,19 @@ import emu.grasscutter.server.game.GameServer; import emu.grasscutter.tools.Tools; import emu.grasscutter.utils.Crypto; +import javax.annotation.Nullable; + import static emu.grasscutter.utils.Language.translate; +import static emu.grasscutter.Configuration.*; public final class Grasscutter { private static final Logger log = (Logger) LoggerFactory.getLogger(Grasscutter.class); private static LineReader consoleLineReader = null; - - private static Config config; + private static Language language; private static final Gson gson = new GsonBuilder().setPrettyPrinting().create(); - private static final File configFile = new File("./config.json"); + public static final File configFile = new File("./config.json"); private static int day; // Current day of week. @@ -51,6 +51,7 @@ public final class Grasscutter { private static PluginManager pluginManager; public static final Reflections reflector = new Reflections("emu.grasscutter"); + public static ConfigContainer config; static { // Declare logback configuration. @@ -58,6 +59,8 @@ public final class Grasscutter { // Load server configuration. Grasscutter.loadConfig(); + // Attempt to update configuration. + ConfigContainer.updateConfig(); // Load translation files. Grasscutter.loadLanguage(); @@ -66,9 +69,9 @@ public final class Grasscutter { Utils.startupCheck(); } - public static void main(String[] args) throws Exception { - Crypto.loadKeys(); // Load keys from buffers. - + public static void main(String[] args) throws Exception { + Crypto.loadKeys(); // Load keys from buffers. + // Parse arguments. boolean exitEarly = false; for (String arg : args) { @@ -77,25 +80,25 @@ public final class Grasscutter { Tools.createGmHandbook(); exitEarly = true; } case "-gachamap" -> { - Tools.createGachaMapping(Grasscutter.getConfig().DATA_FOLDER + "/gacha_mappings.js"); exitEarly = true; + Tools.createGachaMapping(DATA("gacha_mappings.js")); exitEarly = true; } } } // Exit early if argument sets it. if(exitEarly) System.exit(0); - + // Initialize server. Grasscutter.getLogger().info(translate("messages.status.starting")); - + // Load all resources. Grasscutter.updateDayOfWeek(); ResourceLoader.loadAll(); ScriptLoader.init(); - + // Initialize database. DatabaseManager.initialize(); - + // Create server instances. dispatchServer = new DispatchServer(); gameServer = new GameServer(); @@ -103,31 +106,32 @@ public final class Grasscutter { new ServerHook(gameServer, dispatchServer); // Create plugin manager instance. pluginManager = new PluginManager(); - + // Start servers. - if (getConfig().RunMode == ServerRunMode.HYBRID) { + var runMode = SERVER.runMode; + if (runMode == ServerRunMode.HYBRID) { dispatchServer.start(); gameServer.start(); - } else if (getConfig().RunMode == ServerRunMode.DISPATCH_ONLY) { + } else if (runMode == ServerRunMode.DISPATCH_ONLY) { dispatchServer.start(); - } else if (getConfig().RunMode == ServerRunMode.GAME_ONLY) { + } else if (runMode == ServerRunMode.GAME_ONLY) { gameServer.start(); } else { - getLogger().error(translate("messages.status.run_mode_error", getConfig().RunMode)); + getLogger().error(translate("messages.status.run_mode_error", runMode)); getLogger().error(translate("messages.status.run_mode_help")); getLogger().error(translate("messages.status.shutdown")); System.exit(1); } - + // Enable all plugins. pluginManager.enablePlugins(); - + // Hook into shutdown event. Runtime.getRuntime().addShutdownHook(new Thread(Grasscutter::onShutdown)); - + // Open console. startConsole(); - } + } /** * Server shutdown event. @@ -137,32 +141,46 @@ public final class Grasscutter { pluginManager.disablePlugins(); } + /** + * Attempts to load the configuration from a file. + */ public static void loadConfig() { try (FileReader file = new FileReader(configFile)) { - config = gson.fromJson(file, Config.class); - saveConfig(); - } catch (Exception e) { - Grasscutter.config = new Config(); - saveConfig(); + config = gson.fromJson(file, ConfigContainer.class); + } catch (Exception exception) { + Grasscutter.saveConfig(null); + config = new ConfigContainer(); + } catch (Error error) { + // Occurred probably from an outdated config file. + Grasscutter.saveConfig(null); + config = new ConfigContainer(); } } public static void loadLanguage() { - var locale = config.LocaleLanguage; + var locale = config.language.language; language = Language.getLanguage(Utils.getLanguageCode(locale)); } - public static void saveConfig() { + /** + * Saves the provided server configuration. + * @param config The configuration to save, or null for a new one. + */ + public static void saveConfig(@Nullable ConfigContainer config) { + if(config == null) config = new ConfigContainer(); + try (FileWriter file = new FileWriter(configFile)) { file.write(gson.toJson(config)); + } catch (IOException ignored) { + Grasscutter.getLogger().error("Unable to write to config file."); } catch (Exception e) { - Grasscutter.getLogger().error("Unable to save config file."); + Grasscutter.getLogger().error("Unable to save config file.", e); } } public static void startConsole() { // Console should not start in dispatch only mode. - if (getConfig().RunMode == ServerRunMode.DISPATCH_ONLY) { + if (SERVER.runMode == ServerRunMode.DISPATCH_ONLY) { getLogger().info(translate("messages.dispatch.no_commands_error")); return; } @@ -198,7 +216,7 @@ public final class Grasscutter { } } - public static Config getConfig() { + public static ConfigContainer getConfig() { return config; } diff --git a/src/main/java/emu/grasscutter/command/commands/GiveCharCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveCharCommand.java index 2917c64c4..040d07d59 100644 --- a/src/main/java/emu/grasscutter/command/commands/GiveCharCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/GiveCharCommand.java @@ -63,9 +63,10 @@ public final class GiveCharCommand implements CommandHandler { // Calculate ascension level. int ascension; if (level <= 40) { - ascension = (int) Math.ceil(level / 20f); + ascension = (int) Math.ceil(level / 20f) - 1; } else { ascension = (int) Math.ceil(level / 10f) - 3; + ascension = Math.min(ascension, 6); } Avatar avatar = new Avatar(avatarId); diff --git a/src/main/java/emu/grasscutter/command/commands/LanguageCommand.java b/src/main/java/emu/grasscutter/command/commands/LanguageCommand.java index 5966c6167..4783af0cc 100644 --- a/src/main/java/emu/grasscutter/command/commands/LanguageCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/LanguageCommand.java @@ -31,19 +31,26 @@ public final class LanguageCommand implements CommandHandler { } String langCode = args.get(0); - String actualLangCode = null; + + var languageInst = Grasscutter.getLanguage(langCode); + var actualLangCode = languageInst.getLanguageCode(); + var locale = Locale.forLanguageTag(actualLangCode); if (sender != null) { - var locale = Locale.forLanguageTag(langCode); - actualLangCode = Utils.getLanguageCode(locale); var account = sender.getAccount(); account.setLocale(locale); account.save(); } else { - var languageInst = Grasscutter.getLanguage(langCode); - actualLangCode = languageInst.getLanguageCode(); Grasscutter.setLanguage(languageInst); + var config = Grasscutter.getConfig(); + config.language.language = locale; + Grasscutter.saveConfig(config); } + + if (!langCode.equals(actualLangCode)) { + CommandHandler.sendMessage(sender, translate(sender, "commands.language.language_not_found", langCode)); + } + CommandHandler.sendMessage(sender, translate(sender, "commands.language.language_changed", actualLangCode)); } diff --git a/src/main/java/emu/grasscutter/data/ResourceLoader.java b/src/main/java/emu/grasscutter/data/ResourceLoader.java index ae73de8ff..92fda7bb1 100644 --- a/src/main/java/emu/grasscutter/data/ResourceLoader.java +++ b/src/main/java/emu/grasscutter/data/ResourceLoader.java @@ -28,10 +28,11 @@ import emu.grasscutter.data.custom.QuestConfig; import emu.grasscutter.data.custom.QuestConfigData; import emu.grasscutter.data.custom.QuestConfigData.SubQuestConfigData; import emu.grasscutter.data.custom.ScenePointEntry; -import emu.grasscutter.game.world.SpawnDataEntry; -import emu.grasscutter.game.world.SpawnDataEntry.SpawnGroupEntry; +import emu.grasscutter.game.world.SpawnDataEntry.*; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import static emu.grasscutter.Configuration.*; + public class ResourceLoader { public static List> getResourceDefClasses() { @@ -131,7 +132,7 @@ public class ResourceLoader { @SuppressWarnings({"rawtypes", "unchecked"}) protected static void loadFromResource(Class c, String fileName, Int2ObjectMap map) throws Exception { - FileReader fileReader = new FileReader(Grasscutter.getConfig().RESOURCE_FOLDER + "ExcelBinOutput/" + fileName); + FileReader fileReader = new FileReader(RESOURCE("ExcelBinOutput/" + fileName)); Gson gson = Grasscutter.getGsonFactory(); List list = gson.fromJson(fileReader, List.class); @@ -145,7 +146,7 @@ public class ResourceLoader { private static void loadScenePoints() { Pattern pattern = Pattern.compile("(?<=scene)(.*?)(?=_point.json)"); - File folder = new File(Grasscutter.getConfig().RESOURCE_FOLDER + "BinOutput/Scene/Point"); + File folder = new File(RESOURCE("BinOutput/Scene/Point")); if (!folder.isDirectory() || !folder.exists() || folder.listFiles() == null) { Grasscutter.getLogger().error("Scene point files cannot be found, you cannot use teleport waypoints!"); @@ -154,8 +155,7 @@ public class ResourceLoader { List scenePointList = new ArrayList<>(); for (File file : Objects.requireNonNull(folder.listFiles())) { - ScenePointConfig config = null; - Integer sceneId = null; + ScenePointConfig config; Integer sceneId; Matcher matcher = pattern.matcher(file.getName()); if (matcher.find()) { @@ -194,7 +194,7 @@ public class ResourceLoader { private static void loadAbilityEmbryos() { // Read from cached file if exists - File embryoCache = new File(Grasscutter.getConfig().DATA_FOLDER + "AbilityEmbryos.json"); + File embryoCache = new File(DATA("AbilityEmbryos.json")); List embryoList = null; if (embryoCache.exists()) { @@ -209,7 +209,7 @@ public class ResourceLoader { Pattern pattern = Pattern.compile("(?<=ConfigAvatar_)(.*?)(?=.json)"); embryoList = new LinkedList<>(); - File folder = new File(Utils.toFilePath(Grasscutter.getConfig().RESOURCE_FOLDER + "BinOutput/Avatar/")); + File folder = new File(Utils.toFilePath(RESOURCE("BinOutput/Avatar/"))); File[] files = folder.listFiles(); if(files == null) { Grasscutter.getLogger().error("Error loading ability embryos: no files found in " + folder.getAbsolutePath()); @@ -256,7 +256,7 @@ public class ResourceLoader { private static void loadAbilityModifiers() { // Load from BinOutput - File folder = new File(Utils.toFilePath(Grasscutter.getConfig().RESOURCE_FOLDER + "BinOutput/Ability/Temp/AvatarAbilities/")); + File folder = new File(Utils.toFilePath(RESOURCE("BinOutput/Ability/Temp/AvatarAbilities/"))); File[] files = folder.listFiles(); if (files == null) { Grasscutter.getLogger().error("Error loading ability modifiers: no files found in " + folder.getAbsolutePath()); @@ -264,7 +264,7 @@ public class ResourceLoader { } for (File file : files) { - List abilityConfigList = null; + List abilityConfigList; try (FileReader fileReader = new FileReader(file)) { abilityConfigList = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, AbilityConfigData.class).getType()); @@ -319,7 +319,7 @@ public class ResourceLoader { private static void loadSpawnData() { // Read from cached file if exists - File spawnDataEntries = new File(Grasscutter.getConfig().DATA_FOLDER + "Spawns.json"); + File spawnDataEntries = new File(DATA("Spawns.json")); List spawnEntryList = null; if (spawnDataEntries.exists()) { @@ -337,16 +337,14 @@ public class ResourceLoader { } for (SpawnGroupEntry entry : spawnEntryList) { - entry.getSpawns().stream().forEach(s -> { - s.setGroup(entry); - }); + entry.getSpawns().forEach(s -> s.setGroup(entry)); GameDepot.getSpawnListById(entry.getSceneId()).insert(entry, entry.getPos().getX(), entry.getPos().getZ()); } } private static void loadOpenConfig() { // Read from cached file if exists - File openConfigCache = new File(Grasscutter.getConfig().DATA_FOLDER + "OpenConfig.json"); + File openConfigCache = new File(DATA("OpenConfig.json")); List list = null; if (openConfigCache.exists()) { @@ -361,7 +359,7 @@ public class ResourceLoader { String[] folderNames = {"BinOutput/Talent/EquipTalents/", "BinOutput/Talent/AvatarTalents/"}; for (String name : folderNames) { - File folder = new File(Utils.toFilePath(Grasscutter.getConfig().RESOURCE_FOLDER + name)); + File folder = new File(Utils.toFilePath(RESOURCE(name))); File[] files = folder.listFiles(); if(files == null) { Grasscutter.getLogger().error("Error loading open config: no files found in " + folder.getAbsolutePath()); return; diff --git a/src/main/java/emu/grasscutter/database/DatabaseManager.java b/src/main/java/emu/grasscutter/database/DatabaseManager.java index 12bb444b8..3770c3eb2 100644 --- a/src/main/java/emu/grasscutter/database/DatabaseManager.java +++ b/src/main/java/emu/grasscutter/database/DatabaseManager.java @@ -1,6 +1,5 @@ package emu.grasscutter.database; -import com.mongodb.MongoClientURI; import com.mongodb.MongoCommandException; import com.mongodb.client.MongoClient; import com.mongodb.client.MongoClients; @@ -23,11 +22,9 @@ import emu.grasscutter.game.player.Player; import emu.grasscutter.game.quest.GameMainQuest; import emu.grasscutter.game.quest.GameQuest; +import static emu.grasscutter.Configuration.*; + public final class DatabaseManager { - - private static MongoClient mongoClient; - private static MongoClient dispatchMongoClient; - private static Datastore datastore; private static Datastore dispatchDatastore; @@ -47,7 +44,7 @@ public final class DatabaseManager { // Yes. I very dislike this method. However, this will be good for now. // TODO: Add dispatch routes for player account management public static Datastore getAccountDatastore() { - if(Grasscutter.getConfig().RunMode == ServerRunMode.GAME_ONLY) { + if(SERVER.runMode == ServerRunMode.GAME_ONLY) { return dispatchDatastore; } else { return datastore; @@ -56,13 +53,13 @@ public final class DatabaseManager { public static void initialize() { // Initialize - MongoClient mongoClient = MongoClients.create(Grasscutter.getConfig().DatabaseUrl); + MongoClient mongoClient = MongoClients.create(DATABASE.connectionUri); // Set mapper options. MapperOptions mapperOptions = MapperOptions.builder() .storeEmpties(true).storeNulls(false).build(); // Create data store. - datastore = Morphia.createDatastore(mongoClient, Grasscutter.getConfig().DatabaseCollection, mapperOptions); + datastore = Morphia.createDatastore(mongoClient, DATABASE.collection, mapperOptions); // Map classes. datastore.getMapper().map(mappedClasses); @@ -83,9 +80,9 @@ public final class DatabaseManager { } } - if(Grasscutter.getConfig().RunMode == ServerRunMode.GAME_ONLY) { - dispatchMongoClient = MongoClients.create(Grasscutter.getConfig().getGameServerOptions().DispatchServerDatabaseUrl); - dispatchDatastore = Morphia.createDatastore(dispatchMongoClient, Grasscutter.getConfig().getGameServerOptions().DispatchServerDatabaseCollection); + if(SERVER.runMode == ServerRunMode.GAME_ONLY) { + MongoClient dispatchMongoClient = MongoClients.create(GAME_OPTIONS.databaseInfo.connectionUri); + dispatchDatastore = Morphia.createDatastore(dispatchMongoClient, GAME_OPTIONS.databaseInfo.collection); // Ensure indexes for dispatch server try { diff --git a/src/main/java/emu/grasscutter/game/Account.java b/src/main/java/emu/grasscutter/game/Account.java index 821dea80b..6c3daf61a 100644 --- a/src/main/java/emu/grasscutter/game/Account.java +++ b/src/main/java/emu/grasscutter/game/Account.java @@ -12,7 +12,7 @@ import java.util.Locale; import org.bson.Document; -import com.mongodb.DBObject; +import static emu.grasscutter.Configuration.*; @Entity(value = "accounts", useDiscriminator = false) public class Account { @@ -34,7 +34,7 @@ public class Account { @Deprecated public Account() { this.permissions = new ArrayList<>(); - this.locale = Grasscutter.getConfig().LocaleLanguage; + this.locale = LANGUAGE; } public String getId() { @@ -180,7 +180,7 @@ public class Account { // Set account default language as server default language if (!document.containsKey("locale")) { - this.locale = Grasscutter.getConfig().LocaleLanguage; + this.locale = LANGUAGE; } } } diff --git a/src/main/java/emu/grasscutter/game/drop/DropManager.java b/src/main/java/emu/grasscutter/game/drop/DropManager.java index e304d37b5..218624d1a 100644 --- a/src/main/java/emu/grasscutter/game/drop/DropManager.java +++ b/src/main/java/emu/grasscutter/game/drop/DropManager.java @@ -21,6 +21,8 @@ import java.io.FileReader; import java.util.Collection; import java.util.List; +import static emu.grasscutter.Configuration.*; + public class DropManager { public GameServer getGameServer() { return gameServer; @@ -41,7 +43,7 @@ public class DropManager { } public synchronized void load() { - try (FileReader fileReader = new FileReader(Grasscutter.getConfig().DATA_FOLDER + "Drop.json")) { + try (FileReader fileReader = new FileReader(DATA("Drop.json"))) { getDropData().clear(); List banners = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, DropInfo.class).getType()); if(banners.size() > 0) { @@ -69,9 +71,7 @@ public class DropManager { } else { // target is null if items will be added are shared. no one could pick it up because of the combination(give + shared) // so it will be sent to all players' inventories directly. - dropScene.getPlayers().forEach(x -> { - x.getInventory().addItem(new GameItem(itemData, num), ActionReason.SubfieldDrop, true); - }); + dropScene.getPlayers().forEach(x -> x.getInventory().addItem(new GameItem(itemData, num), ActionReason.SubfieldDrop, true)); } } } diff --git a/src/main/java/emu/grasscutter/game/expedition/ExpeditionManager.java b/src/main/java/emu/grasscutter/game/expedition/ExpeditionManager.java index 5d1b652e1..1b75d7306 100644 --- a/src/main/java/emu/grasscutter/game/expedition/ExpeditionManager.java +++ b/src/main/java/emu/grasscutter/game/expedition/ExpeditionManager.java @@ -10,6 +10,8 @@ import java.io.FileReader; import java.util.Collection; import java.util.List; +import static emu.grasscutter.Configuration.*; + public class ExpeditionManager { public GameServer getGameServer() { return gameServer; @@ -28,7 +30,7 @@ public class ExpeditionManager { } public synchronized void load() { - try (FileReader fileReader = new FileReader(Grasscutter.getConfig().DATA_FOLDER + "ExpeditionReward.json")) { + try (FileReader fileReader = new FileReader(DATA("ExpeditionReward.json"))) { getExpeditionRewardDataList().clear(); List banners = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, ExpeditionRewardInfo.class).getType()); if(banners.size() > 0) { diff --git a/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java b/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java index b48cb0898..dce433fcf 100644 --- a/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java +++ b/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java @@ -1,9 +1,10 @@ package emu.grasscutter.game.gacha; -import emu.grasscutter.Grasscutter; import emu.grasscutter.net.proto.GachaInfoOuterClass.GachaInfo; import emu.grasscutter.net.proto.GachaUpInfoOuterClass.GachaUpInfo; +import static emu.grasscutter.Configuration.*; + public class GachaBanner { private int gachaType; private int scheduleId; @@ -95,15 +96,11 @@ public class GachaBanner { public GachaInfo toProto() { return toProto(""); } + public GachaInfo toProto(String sessionKey) { - String record = "http" + (Grasscutter.getConfig().getDispatchOptions().FrontHTTPS ? "s" : "") + "://" - + (Grasscutter.getConfig().getDispatchOptions().PublicIp.isEmpty() ? - Grasscutter.getConfig().getDispatchOptions().Ip : - Grasscutter.getConfig().getDispatchOptions().PublicIp) - + ":" - + Integer.toString(Grasscutter.getConfig().getDispatchOptions().PublicPort == 0 ? - Grasscutter.getConfig().getDispatchOptions().Port : - Grasscutter.getConfig().getDispatchOptions().PublicPort) + String record = "http" + (DISPATCH_INFO.encryption.useInRouting ? "s" : "") + "://" + + lr(DISPATCH_INFO.accessAddress, DISPATCH_INFO.bindAddress) + ":" + + lr(DISPATCH_INFO.accessPort, DISPATCH_INFO.bindPort) + "/gacha?s=" + sessionKey + "&gachaType=" + gachaType; // Grasscutter.getLogger().info("record = " + record); GachaInfo.Builder info = GachaInfo.newBuilder() diff --git a/src/main/java/emu/grasscutter/game/gacha/GachaManager.java b/src/main/java/emu/grasscutter/game/gacha/GachaManager.java index ca7640e17..03edca09a 100644 --- a/src/main/java/emu/grasscutter/game/gacha/GachaManager.java +++ b/src/main/java/emu/grasscutter/game/gacha/GachaManager.java @@ -34,20 +34,22 @@ import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; import org.greenrobot.eventbus.Subscribe; +import static emu.grasscutter.Configuration.*; + public class GachaManager { private final GameServer server; private final Int2ObjectMap gachaBanners; private GetGachaInfoRsp cachedProto; WatchService watchService; - private int[] yellowAvatars = new int[] {1003, 1016, 1042, 1035, 1041}; - private int[] yellowWeapons = new int[] {11501, 11502, 12501, 12502, 13502, 13505, 14501, 14502, 15501, 15502}; - private int[] purpleAvatars = new int[] {1006, 1014, 1015, 1020, 1021, 1023, 1024, 1025, 1027, 1031, 1032, 1034, 1036, 1039, 1043, 1044, 1045, 1048, 1053, 1055, 1056, 1064}; - private int[] purpleWeapons = new int[] {11401, 11402, 11403, 11405, 12401, 12402, 12403, 12405, 13401, 13407, 14401, 14402, 14403, 14409, 15401, 15402, 15403, 15405}; - private int[] blueWeapons = new int[] {11301, 11302, 11306, 12301, 12302, 12305, 13303, 14301, 14302, 14304, 15301, 15302, 15304}; + private final int[] yellowAvatars = new int[] {1003, 1016, 1042, 1035, 1041}; + private final int[] yellowWeapons = new int[] {11501, 11502, 12501, 12502, 13502, 13505, 14501, 14502, 15501, 15502}; + private final int[] purpleAvatars = new int[] {1006, 1014, 1015, 1020, 1021, 1023, 1024, 1025, 1027, 1031, 1032, 1034, 1036, 1039, 1043, 1044, 1045, 1048, 1053, 1055, 1056, 1064}; + private final int[] purpleWeapons = new int[] {11401, 11402, 11403, 11405, 12401, 12402, 12403, 12405, 13401, 13407, 14401, 14402, 14403, 14409, 15401, 15402, 15403, 15405}; + private final int[] blueWeapons = new int[] {11301, 11302, 11306, 12301, 12302, 12305, 13303, 14301, 14302, 14304, 15301, 15302, 15304}; - private static int starglitterId = 221; - private static int stardustId = 222; + private static final int starglitterId = 221; + private static final int stardustId = 222; public GachaManager(GameServer server) { this.server = server; @@ -73,7 +75,7 @@ public class GachaManager { } public synchronized void load() { - try (FileReader fileReader = new FileReader(Grasscutter.getConfig().DATA_FOLDER + "Banners.json")) { + try (FileReader fileReader = new FileReader(DATA("Banners.json"))) { getGachaBanners().clear(); List banners = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, GachaBanner.class).getType()); if(banners.size() > 0) { @@ -242,15 +244,9 @@ public class GachaManager { } else { // Is weapon switch (itemData.getRankLevel()) { - case 5: - addStarglitter = 10; - break; - case 4: - addStarglitter = 2; - break; - case 3: - addStardust = 15; - break; + case 5 -> addStarglitter = 10; + case 4 -> addStarglitter = 2; + case 3 -> addStardust = 15; } } @@ -290,7 +286,7 @@ public class GachaManager { if(this.watchService == null) { try { this.watchService = FileSystems.getDefault().newWatchService(); - Path path = new File(Grasscutter.getConfig().DATA_FOLDER).toPath(); + Path path = new File(DATA_FOLDER).toPath(); path.register(watchService, new WatchEvent.Kind[]{StandardWatchEventKinds.ENTRY_MODIFY}, SensitivityWatchEventModifier.HIGH); } catch (Exception e) { Grasscutter.getLogger().error("Unable to load the Gacha Manager Watch Service. If ServerOptions.watchGacha is true it will not auto-reload"); @@ -303,7 +299,7 @@ public class GachaManager { @Subscribe public synchronized void watchBannerJson(GameServerTickEvent tickEvent) { - if(Grasscutter.getConfig().getGameServerOptions().WatchGacha) { + if(GAME_OPTIONS.watchGachaConfig) { try { WatchKey watchKey = watchService.take(); diff --git a/src/main/java/emu/grasscutter/game/inventory/Inventory.java b/src/main/java/emu/grasscutter/game/inventory/Inventory.java index c4158ee6f..4a217ba54 100644 --- a/src/main/java/emu/grasscutter/game/inventory/Inventory.java +++ b/src/main/java/emu/grasscutter/game/inventory/Inventory.java @@ -6,7 +6,6 @@ import java.util.LinkedList; import java.util.List; import emu.grasscutter.GameConstants; -import emu.grasscutter.Grasscutter; import emu.grasscutter.data.GameData; import emu.grasscutter.data.def.AvatarCostumeData; import emu.grasscutter.data.def.AvatarData; @@ -15,7 +14,6 @@ import emu.grasscutter.data.def.ItemData; import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.game.avatar.AvatarStorage; import emu.grasscutter.game.avatar.Avatar; -import emu.grasscutter.game.entity.EntityAvatar; import emu.grasscutter.game.player.Player; import emu.grasscutter.game.props.ActionReason; import emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam; @@ -28,6 +26,8 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import static emu.grasscutter.Configuration.*; + public class Inventory implements Iterable { private final Player player; @@ -39,10 +39,10 @@ public class Inventory implements Iterable { this.store = new Long2ObjectOpenHashMap<>(); this.inventoryTypes = new Int2ObjectOpenHashMap<>(); - this.createInventoryTab(ItemType.ITEM_WEAPON, new EquipInventoryTab(Grasscutter.getConfig().getGameServerOptions().InventoryLimitWeapon)); - this.createInventoryTab(ItemType.ITEM_RELIQUARY, new EquipInventoryTab(Grasscutter.getConfig().getGameServerOptions().InventoryLimitRelic)); - this.createInventoryTab(ItemType.ITEM_MATERIAL, new MaterialInventoryTab(Grasscutter.getConfig().getGameServerOptions().InventoryLimitMaterial)); - this.createInventoryTab(ItemType.ITEM_FURNITURE, new MaterialInventoryTab(Grasscutter.getConfig().getGameServerOptions().InventoryLimitFurniture)); + this.createInventoryTab(ItemType.ITEM_WEAPON, new EquipInventoryTab(INVENTORY_LIMITS.weapons)); + this.createInventoryTab(ItemType.ITEM_RELIQUARY, new EquipInventoryTab(INVENTORY_LIMITS.relics)); + this.createInventoryTab(ItemType.ITEM_MATERIAL, new MaterialInventoryTab(INVENTORY_LIMITS.materials)); + this.createInventoryTab(ItemType.ITEM_FURNITURE, new MaterialInventoryTab(INVENTORY_LIMITS.furniture)); } public Player getPlayer() { @@ -242,24 +242,18 @@ public class Inventory implements Iterable { private void addVirtualItem(int itemId, int count) { switch (itemId) { - case 101: // Character exp - getPlayer().getServer().getInventoryManager().upgradeAvatar(player, getPlayer().getTeamManager().getCurrentAvatarEntity().getAvatar(), count); - break; - case 102: // Adventure exp - getPlayer().addExpDirectly(count); - break; - case 105: // Companionship exp - getPlayer().getServer().getInventoryManager().upgradeAvatarFetterLevel(player, getPlayer().getTeamManager().getCurrentAvatarEntity().getAvatar(), count); - break; - case 201: // Primogem - getPlayer().setPrimogems(player.getPrimogems() + count); - break; - case 202: // Mora - getPlayer().setMora(player.getMora() + count); - break; - case 203: // Genesis Crystals - getPlayer().setCrystals(player.getCrystals() + count); - break; + case 101 -> // Character exp + getPlayer().getServer().getInventoryManager().upgradeAvatar(player, getPlayer().getTeamManager().getCurrentAvatarEntity().getAvatar(), count); + case 102 -> // Adventure exp + getPlayer().addExpDirectly(count); + case 105 -> // Companionship exp + getPlayer().getServer().getInventoryManager().upgradeAvatarFetterLevel(player, getPlayer().getTeamManager().getCurrentAvatarEntity().getAvatar(), count); + case 201 -> // Primogem + getPlayer().setPrimogems(player.getPrimogems() + count); + case 202 -> // Mora + getPlayer().setMora(player.getMora() + count); + case 203 -> // Genesis Crystals + getPlayer().setCrystals(player.getCrystals() + count); } } diff --git a/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java b/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java index 0795a152d..7a949b8ab 100644 --- a/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java +++ b/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java @@ -21,6 +21,8 @@ import org.jetbrains.annotations.NotNull; import java.lang.Math; import java.util.*; +import static emu.grasscutter.Configuration.*; + public class StaminaManager { // TODO: Skiff state detection? @@ -293,9 +295,10 @@ public class StaminaManager { // Returns new stamina and sends PlayerPropNotify public int setStamina(GameSession session, String reason, int newStamina) { - if (!Grasscutter.getConfig().OpenStamina) { + if (!GAME_OPTIONS.staminaUsage) { newStamina = player.getProperty(PlayerProperty.PROP_MAX_STAMINA); } + // set stamina player.setProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA, newStamina); session.send(new PacketPlayerPropNotify(player, PlayerProperty.PROP_CUR_PERSIST_STAMINA)); diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index e8c4cd61b..ab429b557 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -63,6 +63,8 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import java.util.*; import java.util.concurrent.LinkedBlockingQueue; +import static emu.grasscutter.Configuration.*; + @Entity(value = "players", useDiscriminator = false) public class Player { @@ -358,7 +360,7 @@ public class Player { } private float getExpModifier() { - return Grasscutter.getConfig().getGameServerOptions().getGameRates().ADVENTURE_EXP_RATE; + return GAME_OPTIONS.rates.adventureExp; } // Affected by exp rate @@ -1248,7 +1250,7 @@ public class Player { } else if (prop == PlayerProperty.PROP_LAST_CHANGE_AVATAR_TIME) { // 10001 // TODO: implement sanity check } else if (prop == PlayerProperty.PROP_MAX_SPRING_VOLUME) { // 10002 - if (!(value >= 0 && value <= getSotSManager().GlobalMaximumSpringVolume)) { return false; } + if (!(value >= 0 && value <= SotSManager.GlobalMaximumSpringVolume)) { return false; } } else if (prop == PlayerProperty.PROP_CUR_SPRING_VOLUME) { // 10003 int playerMaximumSpringVolume = getProperty(PlayerProperty.PROP_MAX_SPRING_VOLUME); if (!(value >= 0 && value <= playerMaximumSpringVolume)) { return false; } @@ -1265,7 +1267,7 @@ public class Player { } else if (prop == PlayerProperty.PROP_IS_TRANSFERABLE) { // 10009 if (!(0 <= value && value <= 1)) { return false; } } else if (prop == PlayerProperty.PROP_MAX_STAMINA) { // 10010 - if (!(value >= 0 && value <= getStaminaManager().GlobalMaximumStamina)) { return false; } + if (!(value >= 0 && value <= StaminaManager.GlobalMaximumStamina)) { return false; } } else if (prop == PlayerProperty.PROP_CUR_PERSIST_STAMINA) { // 10011 int playerMaximumStamina = getProperty(PlayerProperty.PROP_MAX_STAMINA); if (!(value >= 0 && value <= playerMaximumStamina)) { return false; } diff --git a/src/main/java/emu/grasscutter/game/player/TeamInfo.java b/src/main/java/emu/grasscutter/game/player/TeamInfo.java index 5794a7913..7d1232e50 100644 --- a/src/main/java/emu/grasscutter/game/player/TeamInfo.java +++ b/src/main/java/emu/grasscutter/game/player/TeamInfo.java @@ -4,10 +4,10 @@ import java.util.ArrayList; import java.util.List; import dev.morphia.annotations.Entity; -import emu.grasscutter.GameConstants; -import emu.grasscutter.Grasscutter; import emu.grasscutter.game.avatar.Avatar; +import static emu.grasscutter.Configuration.*; + @Entity public class TeamInfo { private String name; @@ -15,7 +15,7 @@ public class TeamInfo { public TeamInfo() { this.name = ""; - this.avatars = new ArrayList<>(Grasscutter.getConfig().getGameServerOptions().MaxAvatarsInTeam); + this.avatars = new ArrayList<>(GAME_OPTIONS.avatarLimits.singlePlayerTeam); } public TeamInfo(List avatars) { @@ -44,7 +44,7 @@ public class TeamInfo { } public boolean addAvatar(Avatar avatar) { - if (size() >= Grasscutter.getConfig().getGameServerOptions().MaxAvatarsInTeam || contains(avatar)) { + if (size() >= GAME_OPTIONS.avatarLimits.singlePlayerTeam || contains(avatar)) { return false; } @@ -64,7 +64,7 @@ public class TeamInfo { } public void copyFrom(TeamInfo team) { - copyFrom(team, Grasscutter.getConfig().getGameServerOptions().MaxAvatarsInTeam); + copyFrom(team, GAME_OPTIONS.avatarLimits.singlePlayerTeam); } public void copyFrom(TeamInfo team, int maxTeamSize) { diff --git a/src/main/java/emu/grasscutter/game/player/TeamManager.java b/src/main/java/emu/grasscutter/game/player/TeamManager.java index 891d0a215..71961645f 100644 --- a/src/main/java/emu/grasscutter/game/player/TeamManager.java +++ b/src/main/java/emu/grasscutter/game/player/TeamManager.java @@ -5,7 +5,6 @@ import java.util.*; import dev.morphia.annotations.Entity; import dev.morphia.annotations.Transient; import emu.grasscutter.GameConstants; -import emu.grasscutter.Grasscutter; import emu.grasscutter.data.def.AvatarSkillDepotData; import emu.grasscutter.game.avatar.Avatar; import emu.grasscutter.game.entity.EntityAvatar; @@ -40,6 +39,8 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; +import static emu.grasscutter.Configuration.*; + @Entity public class TeamManager { @Transient private Player player; @@ -174,13 +175,14 @@ public class TeamManager { public int getMaxTeamSize() { if (getPlayer().isInMultiplayer()) { - int max = Grasscutter.getConfig().getGameServerOptions().MaxAvatarsInTeamMultiplayer; + int max = GAME_OPTIONS.avatarLimits.multiplayerTeam; if (getPlayer().getWorld().getHost() == this.getPlayer()) { return Math.max(1, (int) Math.ceil(max / (double) getWorld().getPlayerCount())); } return Math.max(1, (int) Math.floor(max / (double) getWorld().getPlayerCount())); } - return Grasscutter.getConfig().getGameServerOptions().MaxAvatarsInTeam; + + return GAME_OPTIONS.avatarLimits.singlePlayerTeam; } // Methods @@ -236,7 +238,7 @@ public class TeamManager { // Add back entities into team for (int i = 0; i < this.getCurrentTeamInfo().getAvatars().size(); i++) { int avatarId = this.getCurrentTeamInfo().getAvatars().get(i); - EntityAvatar entity = null; + EntityAvatar entity; if (existingAvatars.containsKey(avatarId)) { entity = existingAvatars.get(avatarId); @@ -303,8 +305,8 @@ public class TeamManager { // Set team data LinkedHashSet newTeam = new LinkedHashSet<>(); - for (int i = 0; i < list.size(); i++) { - Avatar avatar = getPlayer().getAvatars().getAvatarByGuid(list.get(i)); + for (Long aLong : list) { + Avatar avatar = getPlayer().getAvatars().getAvatarByGuid(aLong); if (avatar == null || newTeam.contains(avatar)) { // Should never happen return; @@ -339,8 +341,8 @@ public class TeamManager { // Set team data LinkedHashSet newTeam = new LinkedHashSet<>(); - for (int i = 0; i < list.size(); i++) { - Avatar avatar = getPlayer().getAvatars().getAvatarByGuid(list.get(i)); + for (Long aLong : list) { + Avatar avatar = getPlayer().getAvatars().getAvatarByGuid(aLong); if (avatar == null || newTeam.contains(avatar)) { // Should never happen return; @@ -359,7 +361,7 @@ public class TeamManager { } public void setupTemporaryTeam(List> guidList) { - var team = guidList.stream().map(list -> { + this.temporaryTeam = guidList.stream().map(list -> { // Sanity checks if (list.size() == 0 || list.size() > getMaxTeamSize()) { return null; @@ -367,8 +369,8 @@ public class TeamManager { // Set team data LinkedHashSet newTeam = new LinkedHashSet<>(); - for (int i = 0; i < list.size(); i++) { - Avatar avatar = getPlayer().getAvatars().getAvatarByGuid(list.get(i)); + for (Long aLong : list) { + Avatar avatar = getPlayer().getAvatars().getAvatarByGuid(aLong); if (avatar == null || newTeam.contains(avatar)) { // Should never happen return null; @@ -384,7 +386,6 @@ public class TeamManager { .filter(Objects::nonNull) .map(TeamInfo::new) .toList(); - this.temporaryTeam = team; } public void useTemporaryTeam(int index) { diff --git a/src/main/java/emu/grasscutter/game/shop/ShopManager.java b/src/main/java/emu/grasscutter/game/shop/ShopManager.java index 2c5d014f5..a27011012 100644 --- a/src/main/java/emu/grasscutter/game/shop/ShopManager.java +++ b/src/main/java/emu/grasscutter/game/shop/ShopManager.java @@ -16,6 +16,8 @@ import java.util.Collection; import java.util.Iterator; import java.util.List; +import static emu.grasscutter.Configuration.*; + public class ShopManager { private final GameServer server; @@ -56,7 +58,7 @@ public class ShopManager { } private void loadShop() { - try (FileReader fileReader = new FileReader(Grasscutter.getConfig().DATA_FOLDER + "Shop.json")) { + try (FileReader fileReader = new FileReader(DATA("Shop.json"))) { getShopData().clear(); List banners = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, ShopTable.class).getType()); if(banners.size() > 0) { @@ -84,7 +86,7 @@ public class ShopManager { Grasscutter.getLogger().error("Unable to load shop data. Shop data size is 0."); } - if (Grasscutter.getConfig().getGameServerOptions().EnableOfficialShop) { + if (GAME_OPTIONS.enableShopItems) { GameData.getShopGoodsDataEntries().forEach((k, v) -> { if (!getShopData().containsKey(k.intValue())) getShopData().put(k.intValue(), new ArrayList<>()); @@ -100,7 +102,7 @@ public class ShopManager { } private void loadShopChest() { - try (FileReader fileReader = new FileReader(Grasscutter.getConfig().DATA_FOLDER + "ShopChest.json")) { + try (FileReader fileReader = new FileReader(DATA("ShopChest.json"))) { getShopChestData().clear(); List shopChestTableList = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, ShopChestTable.class).getType()); if (shopChestTableList.size() > 0) { @@ -115,7 +117,7 @@ public class ShopManager { } private void loadShopChestBatchUse() { - try (FileReader fileReader = new FileReader(Grasscutter.getConfig().DATA_FOLDER + "ShopChestBatchUse.json")) { + try (FileReader fileReader = new FileReader(DATA("ShopChestBatchUse.json"))) { getShopChestBatchUseData().clear(); List shopChestBatchUseTableList = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, ShopChestBatchUseTable.class).getType()); if (shopChestBatchUseTableList.size() > 0) { diff --git a/src/main/java/emu/grasscutter/game/tower/TowerScheduleManager.java b/src/main/java/emu/grasscutter/game/tower/TowerScheduleManager.java index 952acd806..ae756f009 100644 --- a/src/main/java/emu/grasscutter/game/tower/TowerScheduleManager.java +++ b/src/main/java/emu/grasscutter/game/tower/TowerScheduleManager.java @@ -8,6 +8,8 @@ import emu.grasscutter.server.game.GameServer; import java.io.FileReader; import java.util.List; +import static emu.grasscutter.Configuration.*; + public class TowerScheduleManager { private final GameServer gameServer; @@ -23,9 +25,8 @@ public class TowerScheduleManager { private TowerScheduleConfig towerScheduleConfig; public synchronized void load(){ - try (FileReader fileReader = new FileReader(Grasscutter.getConfig().DATA_FOLDER + "TowerSchedule.json")) { + try (FileReader fileReader = new FileReader(DATA("TowerSchedule.json"))) { towerScheduleConfig = Grasscutter.getGsonFactory().fromJson(fileReader, TowerScheduleConfig.class); - } catch (Exception e) { Grasscutter.getLogger().error("Unable to load tower schedule config.", e); } @@ -40,6 +41,7 @@ public class TowerScheduleManager { if(data == null){ Grasscutter.getLogger().error("Could not get current tower schedule data by config:{}", towerScheduleConfig); } + return data; } @@ -51,28 +53,31 @@ public class TowerScheduleManager { var entranceFloors = getCurrentTowerScheduleData().getEntranceFloorId(); var scheduleFloors = getScheduleFloors(); var nextId = 0; + // find in entrance floors first for(int i=0;i Objects.equals(x, req.baseUrl()))) { - Grasscutter.getLogger().info(translate("messages.dispatch.request", req.ip(), req.method(), req.baseUrl()) + (Grasscutter.getConfig().DebugMode == ServerDebugMode.MISSING ? "(MISSING)" : "")); + if(SERVER.debugLevel == ServerDebugMode.MISSING && Arrays.stream(missingRoutes).anyMatch(x -> Objects.equals(x, req.baseUrl()))) { + Grasscutter.getLogger().info(translate("messages.dispatch.request", req.ip(), req.method(), req.baseUrl()) + (SERVER.debugLevel == ServerDebugMode.MISSING ? "(MISSING)" : "")); } res.send(response); } diff --git a/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java b/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java index f7f14019f..df1ce666d 100644 --- a/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java +++ b/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java @@ -4,7 +4,7 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.protobuf.ByteString; -import emu.grasscutter.Config; +import emu.grasscutter.GameConstants; import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter.ServerDebugMode; import emu.grasscutter.Grasscutter.ServerRunMode; @@ -33,9 +33,11 @@ import org.eclipse.jetty.util.ssl.SslContextFactory; import java.io.*; import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; import java.util.*; import static emu.grasscutter.utils.Language.translate; +import static emu.grasscutter.Configuration.*; public final class DispatchServer { public static String query_region_list = ""; @@ -64,7 +66,7 @@ public final class DispatchServer { public void setHttpServer(Express httpServer) { this.httpServer.stop(); this.httpServer = httpServer; - this.httpServer.listen(Grasscutter.getConfig().getDispatchOptions().Port); + this.httpServer.listen(DISPATCH_INFO.bindPort); } public Gson getGsonFactory() { @@ -73,7 +75,7 @@ public final class DispatchServer { public QueryCurrRegionHttpRsp getCurrRegion() { // Needs to be fixed by having the game servers connect to the dispatch server. - if (Grasscutter.getConfig().RunMode == ServerRunMode.HYBRID) { + if (SERVER.runMode == ServerRunMode.HYBRID) { return regions.get(defaultServerName).parsedRegionQuery; } @@ -84,14 +86,14 @@ public final class DispatchServer { public void loadQueries() { File file; - file = new File(Grasscutter.getConfig().DATA_FOLDER + "query_region_list.txt"); + file = new File(DATA("query_region_list.txt")); if (file.exists()) { query_region_list = new String(FileUtils.read(file)); } else { Grasscutter.getLogger().warn("[Dispatch] query_region_list not found! Using default region list."); } - file = new File(Grasscutter.getConfig().DATA_FOLDER + "query_cur_region.txt"); + file = new File(DATA("query_cur_region.txt")); if (file.exists()) { query_cur_region = new String(FileUtils.read(file)); } else { @@ -108,52 +110,37 @@ public final class DispatchServer { QueryCurrRegionHttpRsp regionQuery = QueryCurrRegionHttpRsp.parseFrom(decoded2); List servers = new ArrayList<>(); - List usedNames = new ArrayList<>(); // List to check for potential naming conflicts - if (Grasscutter.getConfig().RunMode == ServerRunMode.HYBRID) { // Automatically add the game server if in - // hybrid mode + List usedNames = new ArrayList<>(); // List to check for potential naming conflicts. + if (SERVER.runMode == ServerRunMode.HYBRID) { // Automatically add the game server if in hybrid mode. RegionSimpleInfo server = RegionSimpleInfo.newBuilder() .setName("os_usa") - .setTitle(Grasscutter.getConfig().getGameServerOptions().Name) + .setTitle(DISPATCH_INFO.defaultName) .setType("DEV_PUBLIC") .setDispatchUrl( - "http" + (Grasscutter.getConfig().getDispatchOptions().FrontHTTPS ? "s" : "") + "://" - + (Grasscutter.getConfig().getDispatchOptions().PublicIp.isEmpty() - ? Grasscutter.getConfig().getDispatchOptions().Ip - : Grasscutter.getConfig().getDispatchOptions().PublicIp) - + ":" - + (Grasscutter.getConfig().getDispatchOptions().PublicPort != 0 - ? Grasscutter.getConfig().getDispatchOptions().PublicPort - : Grasscutter.getConfig().getDispatchOptions().Port) + "http" + (DISPATCH_ENCRYPTION.useEncryption ? "s" : "") + "://" + + lr(DISPATCH_INFO.accessAddress, DISPATCH_INFO.bindAddress) + ":" + + lr(DISPATCH_INFO.accessPort, DISPATCH_INFO.bindPort) + "/query_cur_region/" + defaultServerName) .build(); usedNames.add(defaultServerName); servers.add(server); RegionInfo serverRegion = regionQuery.getRegionInfo().toBuilder() - .setGateserverIp((Grasscutter.getConfig().getGameServerOptions().PublicIp.isEmpty() - ? Grasscutter.getConfig().getGameServerOptions().Ip - : Grasscutter.getConfig().getGameServerOptions().PublicIp)) - .setGateserverPort(Grasscutter.getConfig().getGameServerOptions().PublicPort != 0 - ? Grasscutter.getConfig().getGameServerOptions().PublicPort - : Grasscutter.getConfig().getGameServerOptions().Port) - .setSecretKey(ByteString - .copyFrom(FileUtils.read(Grasscutter.getConfig().KEY_FOLDER + "dispatchSeed.bin"))) + .setGateserverIp(lr(GAME_INFO.accessAddress, GAME_INFO.bindAddress)) + .setGateserverPort(lr(GAME_INFO.accessPort, GAME_INFO.bindPort)) + .setSecretKey(ByteString.copyFrom(FileUtils.read(KEYS_FOLDER + "/dispatchSeed.bin"))) .build(); QueryCurrRegionHttpRsp parsedRegionQuery = regionQuery.toBuilder().setRegionInfo(serverRegion).build(); regions.put(defaultServerName, new RegionData(parsedRegionQuery, Base64.getEncoder().encodeToString(parsedRegionQuery.toByteString().toByteArray()))); - } else { - if (Grasscutter.getConfig().getDispatchOptions().getGameServers().length == 0) { - Grasscutter.getLogger() - .error("[Dispatch] There are no game servers available. Exiting due to unplayable state."); - System.exit(1); - } + } else if (DISPATCH_INFO.regions.length == 0) { + Grasscutter.getLogger().error("[Dispatch] There are no game servers available. Exiting due to unplayable state."); + System.exit(1); } - for (Config.DispatchServerOptions.RegionInfo regionInfo : Grasscutter.getConfig().getDispatchOptions() - .getGameServers()) { + for (var regionInfo : DISPATCH_INFO.regions) { if (usedNames.contains(regionInfo.Name)) { Grasscutter.getLogger().error("Region name already in use."); continue; @@ -163,13 +150,10 @@ public final class DispatchServer { .setTitle(regionInfo.Title) .setType("DEV_PUBLIC") .setDispatchUrl( - "http" + (Grasscutter.getConfig().getDispatchOptions().FrontHTTPS ? "s" : "") + "://" - + (Grasscutter.getConfig().getDispatchOptions().PublicIp.isEmpty() - ? Grasscutter.getConfig().getDispatchOptions().Ip - : Grasscutter.getConfig().getDispatchOptions().PublicIp) - + ":" + (Grasscutter.getConfig().getDispatchOptions().PublicPort != 0 - ? Grasscutter.getConfig().getDispatchOptions().PublicPort - : Grasscutter.getConfig().getDispatchOptions().Port) + "/query_cur_region/" + regionInfo.Name) + "http" + (DISPATCH_ENCRYPTION.useEncryption ? "s" : "") + "://" + + lr(DISPATCH_INFO.accessAddress, DISPATCH_INFO.bindAddress) + ":" + + lr(DISPATCH_INFO.accessPort, DISPATCH_INFO.bindPort) + + "/query_cur_region/" + regionInfo.Name) .build(); usedNames.add(regionInfo.Name); servers.add(server); @@ -178,7 +162,7 @@ public final class DispatchServer { .setGateserverIp(regionInfo.Ip) .setGateserverPort(regionInfo.Port) .setSecretKey(ByteString - .copyFrom(FileUtils.read(Grasscutter.getConfig().KEY_FOLDER + "dispatchSeed.bin"))) + .copyFrom(FileUtils.read(KEYS_FOLDER + "/dispatchSeed.bin"))) .build(); QueryCurrRegionHttpRsp parsedRegionQuery = regionQuery.toBuilder().setRegionInfo(serverRegion).build(); @@ -194,8 +178,8 @@ public final class DispatchServer { .build(); this.regionListBase64 = Base64.getEncoder().encodeToString(regionList.toByteString().toByteArray()); - } catch (Exception e) { - Grasscutter.getLogger().error("[Dispatch] Error while initializing region info!", e); + } catch (Exception exception) { + Grasscutter.getLogger().error("[Dispatch] Error while initializing region info!", exception); } } @@ -205,14 +189,14 @@ public final class DispatchServer { Server server = new Server(); ServerConnector serverConnector; - if(Grasscutter.getConfig().getDispatchOptions().UseSSL) { + if(DISPATCH_ENCRYPTION.useEncryption) { SslContextFactory.Server sslContextFactory = new SslContextFactory.Server(); - File keystoreFile = new File(Grasscutter.getConfig().getDispatchOptions().KeystorePath); + File keystoreFile = new File(DISPATCH_ENCRYPTION.keystore); if(keystoreFile.exists()) { try { sslContextFactory.setKeyStorePath(keystoreFile.getPath()); - sslContextFactory.setKeyStorePassword(Grasscutter.getConfig().getDispatchOptions().KeystorePassword); + sslContextFactory.setKeyStorePassword(DISPATCH_ENCRYPTION.keystorePassword); } catch (Exception e) { e.printStackTrace(); Grasscutter.getLogger().warn(translate("messages.dispatch.keystore.password_error")); @@ -230,7 +214,7 @@ public final class DispatchServer { serverConnector = new ServerConnector(server, sslContextFactory); } else { Grasscutter.getLogger().warn(translate("messages.dispatch.keystore.no_keystore_error")); - Grasscutter.getConfig().getDispatchOptions().UseSSL = false; + DISPATCH_ENCRYPTION.useEncryption = false; serverConnector = new ServerConnector(server); } @@ -238,24 +222,27 @@ public final class DispatchServer { serverConnector = new ServerConnector(server); } - serverConnector.setPort(Grasscutter.getConfig().getDispatchOptions().Port); + serverConnector.setPort(DISPATCH_INFO.bindPort); server.setConnectors(new Connector[]{serverConnector}); return server; }); - config.enforceSsl = Grasscutter.getConfig().getDispatchOptions().UseSSL; - if(Grasscutter.getConfig().DebugMode == ServerDebugMode.ALL) { + config.enforceSsl = DISPATCH_ENCRYPTION.useEncryption; + if(SERVER.debugLevel == ServerDebugMode.ALL) { config.enableDevLogging(); } - if (Grasscutter.getConfig().getDispatchOptions().CORS){ - if (Grasscutter.getConfig().getDispatchOptions().CORSAllowedOrigins.length > 0) config.enableCorsForOrigin(Grasscutter.getConfig().getDispatchOptions().CORSAllowedOrigins); + + if (DISPATCH_POLICIES.cors.enabled) { + var corsPolicy = DISPATCH_POLICIES.cors; + if (corsPolicy.allowedOrigins.length > 0) + config.enableCorsForOrigin(corsPolicy.allowedOrigins); else config.enableCorsForAllOrigins(); } }); httpServer.get("/", (req, res) -> res.send("" + translate("messages.status.welcome") + "")); httpServer.raw().error(404, ctx -> { - if(Grasscutter.getConfig().DebugMode == ServerDebugMode.MISSING) { + if(SERVER.debugLevel == ServerDebugMode.MISSING) { Grasscutter.getLogger().info(translate("messages.dispatch.unhandled_request_error", ctx.method(), ctx.url())); } ctx.contentType("text/html"); @@ -272,6 +259,15 @@ public final class DispatchServer { httpServer.post("/authentication/register", (req, res) -> this.getAuthHandler().handleRegister(req, res)); httpServer.post("/authentication/change_password", (req, res) -> this.getAuthHandler().handleChangePassword(req, res)); + // Server Status + httpServer.get("/status/server", (req, res) -> { + + int playerCount = Grasscutter.getGameServer().getPlayers().size(); + String version = GameConstants.VERSION; + + res.send("{\"retcode\":0,\"status\":{\"playerCount\":" + playerCount + ",\"version\":\"" + version + "\"}}"); + }); + // Dispatch httpServer.get("/query_region_list", (req, res) -> { // Log @@ -450,7 +446,7 @@ public final class DispatchServer { httpServer.get("/admin/mi18n/plat_oversea/m202003048/m202003048-version.json", new DispatchHttpJsonHandler("{\"version\":51}")); // gacha record. - String gachaMappingsPath = Utils.toFilePath(Grasscutter.getConfig().DATA_FOLDER + "/gacha_mappings.js"); + String gachaMappingsPath = Utils.toFilePath(DATA("/gacha_mappings.js")); // TODO: Only serve the html page and have a subsequent request to fetch the gacha data. httpServer.get("/gacha", new GachaRecordHandler()); if(!(new File(gachaMappingsPath).exists())) { @@ -462,7 +458,7 @@ public final class DispatchServer { // static file support for plugins httpServer.raw().config.precompressStaticFiles = false; // If this isn't set to false, files such as images may appear corrupted when serving static files - httpServer.listen(Grasscutter.getConfig().getDispatchOptions().Port); + httpServer.listen(DISPATCH_INFO.bindPort); Grasscutter.getLogger().info(translate("messages.dispatch.port_bind", Integer.toString(httpServer.raw().port()))); } @@ -481,15 +477,11 @@ public final class DispatchServer { if (next > last) { int eqPos = qs.indexOf('=', last); - try { - if (eqPos < 0 || eqPos > next) { - result.put(URLDecoder.decode(qs.substring(last, next), "utf-8"), ""); - } else { - result.put(URLDecoder.decode(qs.substring(last, eqPos), "utf-8"), - URLDecoder.decode(qs.substring(eqPos + 1, next), "utf-8")); - } - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); // will never happen, utf-8 support is mandatory for java + if (eqPos < 0 || eqPos > next) { + result.put(URLDecoder.decode(qs.substring(last, next), StandardCharsets.UTF_8), ""); + } else { + result.put(URLDecoder.decode(qs.substring(last, eqPos), StandardCharsets.UTF_8), + URLDecoder.decode(qs.substring(eqPos + 1, next), StandardCharsets.UTF_8)); } } last = next + 1; diff --git a/src/main/java/emu/grasscutter/server/dispatch/authentication/DefaultAuthenticationHandler.java b/src/main/java/emu/grasscutter/server/dispatch/authentication/DefaultAuthenticationHandler.java index e5a4ca055..67b3d4023 100644 --- a/src/main/java/emu/grasscutter/server/dispatch/authentication/DefaultAuthenticationHandler.java +++ b/src/main/java/emu/grasscutter/server/dispatch/authentication/DefaultAuthenticationHandler.java @@ -9,6 +9,7 @@ import express.http.Request; import express.http.Response; import static emu.grasscutter.utils.Language.translate; +import static emu.grasscutter.Configuration.*; public class DefaultAuthenticationHandler implements AuthenticationHandler { @@ -37,11 +38,11 @@ public class DefaultAuthenticationHandler implements AuthenticationHandler { // Check if account exists, else create a new one. if (account == null) { // Account doesn't exist, so we can either auto create it if the config value is set. - if (Grasscutter.getConfig().getDispatchOptions().AutomaticallyCreateAccounts) { + if (ACCOUNT.autoCreate) { // This account has been created AUTOMATICALLY. There will be no permissions added. account = DatabaseHelper.createAccountWithId(requestData.account, 0); - for (String permission : Grasscutter.getConfig().getDispatchOptions().defaultPermissions) { + for (String permission : ACCOUNT.defaultPermissions) { account.addPermission(permission); } diff --git a/src/main/java/emu/grasscutter/server/dispatch/http/GachaRecordHandler.java b/src/main/java/emu/grasscutter/server/dispatch/http/GachaRecordHandler.java index 8676574bb..b90510367 100644 --- a/src/main/java/emu/grasscutter/server/dispatch/http/GachaRecordHandler.java +++ b/src/main/java/emu/grasscutter/server/dispatch/http/GachaRecordHandler.java @@ -3,7 +3,6 @@ package emu.grasscutter.server.dispatch.http; import java.io.File; import java.io.IOException; -import emu.grasscutter.Grasscutter; import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.game.Account; import emu.grasscutter.utils.FileUtils; @@ -12,10 +11,12 @@ import express.http.HttpContextHandler; import express.http.Request; import express.http.Response; +import static emu.grasscutter.Configuration.*; + public final class GachaRecordHandler implements HttpContextHandler { String render_template; public GachaRecordHandler() { - File template = new File(Utils.toFilePath(Grasscutter.getConfig().DATA_FOLDER + "/gacha_records.html")); + File template = new File(Utils.toFilePath(DATA("/gacha_records.html"))); if (template.exists()) { // Load from cache render_template = new String(FileUtils.read(template)); @@ -31,11 +32,11 @@ public final class GachaRecordHandler implements HttpContextHandler { int page = 0; int gachaType = 0; if (req.query("p") != null) { - page = Integer.valueOf(req.query("p")); + page = Integer.parseInt(req.query("p")); } if (req.query("gachaType") != null) { - gachaType = Integer.valueOf(req.query("gachaType")); + gachaType = Integer.parseInt(req.query("gachaType")); } Account account = DatabaseHelper.getAccountBySessionKey(sessionKey); diff --git a/src/main/java/emu/grasscutter/server/game/GameServer.java b/src/main/java/emu/grasscutter/server/game/GameServer.java index cb0e4965d..71e1cf856 100644 --- a/src/main/java/emu/grasscutter/server/game/GameServer.java +++ b/src/main/java/emu/grasscutter/server/game/GameServer.java @@ -30,11 +30,9 @@ import java.net.InetSocketAddress; import java.time.OffsetDateTime; import java.util.*; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; import static emu.grasscutter.utils.Language.translate; +import static emu.grasscutter.Configuration.*; public final class GameServer extends KcpServer { private final InetSocketAddress address; @@ -59,8 +57,8 @@ public final class GameServer extends KcpServer { public GameServer() { this(new InetSocketAddress( - Grasscutter.getConfig().getGameServerOptions().Ip, - Grasscutter.getConfig().getGameServerOptions().Port + GAME_INFO.bindAddress, + GAME_INFO.bindPort )); } diff --git a/src/main/java/emu/grasscutter/server/game/GameServerPacketHandler.java b/src/main/java/emu/grasscutter/server/game/GameServerPacketHandler.java index 88e7fa17f..4bba854ef 100644 --- a/src/main/java/emu/grasscutter/server/game/GameServerPacketHandler.java +++ b/src/main/java/emu/grasscutter/server/game/GameServerPacketHandler.java @@ -14,6 +14,8 @@ import emu.grasscutter.server.game.GameSession.SessionState; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import static emu.grasscutter.Configuration.*; + @SuppressWarnings("unchecked") public class GameServerPacketHandler { private final Int2ObjectMap handlers; @@ -92,7 +94,7 @@ public class GameServerPacketHandler { } // Log unhandled packets - if (Grasscutter.getConfig().DebugMode == ServerDebugMode.MISSING) { + if (SERVER.debugLevel == ServerDebugMode.MISSING) { Grasscutter.getLogger().info("Unhandled packet (" + opcode + "): " + emu.grasscutter.net.packet.PacketOpcodesUtil.getOpcodeName(opcode)); } } diff --git a/src/main/java/emu/grasscutter/server/game/GameSession.java b/src/main/java/emu/grasscutter/server/game/GameSession.java index d1d7eef01..7cc9a799f 100644 --- a/src/main/java/emu/grasscutter/server/game/GameSession.java +++ b/src/main/java/emu/grasscutter/server/game/GameSession.java @@ -3,7 +3,6 @@ package emu.grasscutter.server.game; import java.io.File; import java.net.InetSocketAddress; import java.nio.ByteBuffer; -import java.util.HashSet; import java.util.Set; import emu.grasscutter.Grasscutter; @@ -23,9 +22,10 @@ import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import static emu.grasscutter.utils.Language.translate; +import static emu.grasscutter.Configuration.*; public class GameSession extends KcpChannel { - private GameServer server; + private final GameServer server; private Account account; private Player player; @@ -140,7 +140,7 @@ public class GameSession extends KcpChannel { } public void replayPacket(int opcode, String name) { - String filePath = Grasscutter.getConfig().PACKETS_FOLDER + name; + String filePath = PACKETS_FOLDER + name; File p = new File(filePath); if (!p.exists()) return; @@ -172,7 +172,7 @@ public class GameSession extends KcpChannel { } // Log - if (Grasscutter.getConfig().DebugMode == ServerDebugMode.ALL) { + if (SERVER.debugLevel == ServerDebugMode.ALL) { logPacket(packet); } @@ -239,7 +239,7 @@ public class GameSession extends KcpChannel { } // Log packet - if (Grasscutter.getConfig().DebugMode == ServerDebugMode.ALL) { + if (SERVER.debugLevel == ServerDebugMode.ALL) { if (!loopPacket.contains(opcode)) { Grasscutter.getLogger().info("RECV: " + PacketOpcodesUtil.getOpcodeName(opcode) + " (" + opcode + ")"); System.out.println(Utils.bytesToHex(payload)); diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetPlayerBornDataReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetPlayerBornDataReq.java index 53d141a99..ec591c91f 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetPlayerBornDataReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetPlayerBornDataReq.java @@ -19,6 +19,8 @@ import emu.grasscutter.server.game.GameSession.SessionState; import java.util.Arrays; +import static emu.grasscutter.Configuration.*; + @Opcodes(PacketOpcodes.SetPlayerBornDataReq) public class HandlerSetPlayerBornDataReq extends PacketHandler { @@ -85,12 +87,13 @@ public class HandlerSetPlayerBornDataReq extends PacketHandler { session.send(new BasePacket(PacketOpcodes.SetPlayerBornDataRsp)); // Default mail + var welcomeMail = GAME_INFO.joinOptions.welcomeMail; MailBuilder mailBuilder = new MailBuilder(player.getUid(), new Mail()); - mailBuilder.mail.mailContent.title = Grasscutter.getConfig().GameServer.WelcomeMailTitle; - mailBuilder.mail.mailContent.sender = Grasscutter.getConfig().GameServer.WelcomeMailSender; + mailBuilder.mail.mailContent.title = welcomeMail.title; + mailBuilder.mail.mailContent.sender = welcomeMail.sender; // Please credit Grasscutter if changing something here. We don't condone commercial use of the project. - mailBuilder.mail.mailContent.content = Grasscutter.getConfig().GameServer.WelcomeMailContent + "\n"; - mailBuilder.mail.itemList.addAll(Arrays.asList(Grasscutter.getConfig().GameServer.WelcomeMailItems)); + mailBuilder.mail.mailContent.content = welcomeMail.content + "\n"; + mailBuilder.mail.itemList.addAll(Arrays.asList(welcomeMail.items)); mailBuilder.mail.importance = 1; player.sendMail(mailBuilder.mail); } catch (Exception e) { diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGetPlayerFriendListRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGetPlayerFriendListRsp.java index ff373140b..a0948c737 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketGetPlayerFriendListRsp.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGetPlayerFriendListRsp.java @@ -1,7 +1,6 @@ package emu.grasscutter.server.packet.send; import emu.grasscutter.GameConstants; -import emu.grasscutter.Grasscutter; import emu.grasscutter.game.friends.Friendship; import emu.grasscutter.game.player.Player; import emu.grasscutter.net.packet.BasePacket; @@ -12,20 +11,23 @@ import emu.grasscutter.net.proto.GetPlayerFriendListRspOuterClass.GetPlayerFrien import emu.grasscutter.net.proto.ProfilePictureOuterClass.ProfilePicture; import emu.grasscutter.net.proto.PlatformTypeOuterClass; +import static emu.grasscutter.Configuration.*; + public class PacketGetPlayerFriendListRsp extends BasePacket { public PacketGetPlayerFriendListRsp(Player player) { super(PacketOpcodes.GetPlayerFriendListRsp); + var serverAccount = GAME_INFO.serverAccount; FriendBrief serverFriend = FriendBrief.newBuilder() .setUid(GameConstants.SERVER_CONSOLE_UID) - .setNickname(Grasscutter.getConfig().getGameServerOptions().ServerNickname) - .setLevel(Grasscutter.getConfig().getGameServerOptions().ServerLevel) - .setProfilePicture(ProfilePicture.newBuilder().setAvatarId(Grasscutter.getConfig().getGameServerOptions().ServerAvatarId)) - .setWorldLevel(Grasscutter.getConfig().getGameServerOptions().ServerWorldLevel) - .setSignature(Grasscutter.getConfig().getGameServerOptions().ServerSignature) + .setNickname(serverAccount.nickName) + .setLevel(serverAccount.adventureRank) + .setProfilePicture(ProfilePicture.newBuilder().setAvatarId(serverAccount.avatarId)) + .setWorldLevel(serverAccount.worldLevel) + .setSignature(serverAccount.signature) .setLastActiveTime((int) (System.currentTimeMillis() / 1000f)) - .setNameCardId(Grasscutter.getConfig().getGameServerOptions().ServerNameCardId) + .setNameCardId(serverAccount.nameCardId) .setOnlineState(FriendOnlineState.FRIEND_ONLINE) .setParam(1) .setIsGameSource(true) @@ -37,10 +39,12 @@ public class PacketGetPlayerFriendListRsp extends BasePacket { for (Friendship friendship : player.getFriendsList().getFriends().values()) { proto.addFriendList(friendship.toProto()); } + for (Friendship friendship : player.getFriendsList().getPendingFriends().values()) { if (friendship.getAskerId() == player.getUid()) { continue; } + proto.addAskFriendList(friendship.toProto()); } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerLoginRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerLoginRsp.java index 6407e9412..9a21e4143 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerLoginRsp.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerLoginRsp.java @@ -2,7 +2,6 @@ package emu.grasscutter.server.packet.send; import com.google.protobuf.ByteString; import emu.grasscutter.Grasscutter; -import emu.grasscutter.Grasscutter.ServerDebugMode; import emu.grasscutter.Grasscutter.ServerRunMode; import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.PacketOpcodes; @@ -13,9 +12,10 @@ import emu.grasscutter.server.game.GameSession; import emu.grasscutter.utils.FileUtils; import java.io.File; -import java.net.URL; import java.util.Base64; +import static emu.grasscutter.Configuration.*; + public class PacketPlayerLoginRsp extends BasePacket { private static QueryCurrRegionHttpRspOuterClass.QueryCurrRegionHttpRsp regionCache; @@ -27,10 +27,10 @@ public class PacketPlayerLoginRsp extends BasePacket { RegionInfo info; - if (Grasscutter.getConfig().RunMode == ServerRunMode.GAME_ONLY) { + if (SERVER.runMode == ServerRunMode.GAME_ONLY) { if (regionCache == null) { try { - File file = new File(Grasscutter.getConfig().DATA_FOLDER + "query_cur_region.txt"); + File file = new File(DATA("query_cur_region.txt")); String query_cur_region = ""; if (file.exists()) { query_cur_region = new String(FileUtils.read(file)); @@ -42,9 +42,9 @@ public class PacketPlayerLoginRsp extends BasePacket { QueryCurrRegionHttpRspOuterClass.QueryCurrRegionHttpRsp regionQuery = QueryCurrRegionHttpRspOuterClass.QueryCurrRegionHttpRsp.parseFrom(decodedCurRegion); RegionInfo serverRegion = regionQuery.getRegionInfo().toBuilder() - .setGateserverIp((Grasscutter.getConfig().getGameServerOptions().PublicIp.isEmpty() ? Grasscutter.getConfig().getGameServerOptions().Ip : Grasscutter.getConfig().getGameServerOptions().PublicIp)) - .setGateserverPort(Grasscutter.getConfig().getGameServerOptions().PublicPort != 0 ? Grasscutter.getConfig().getGameServerOptions().PublicPort : Grasscutter.getConfig().getGameServerOptions().Port) - .setSecretKey(ByteString.copyFrom(FileUtils.read(Grasscutter.getConfig().KEY_FOLDER + "dispatchSeed.bin"))) + .setGateserverIp(lr(GAME_INFO.accessAddress, GAME_INFO.bindAddress)) + .setGateserverPort(lr(GAME_INFO.accessPort, GAME_INFO.bindPort)) + .setSecretKey(ByteString.copyFrom(FileUtils.read(KEYS_FOLDER + "/dispatchSeed.bin"))) .build(); regionCache = regionQuery.toBuilder().setRegionInfo(serverRegion).build(); diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerStoreNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerStoreNotify.java index a3a6ff7b8..3ad196af3 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerStoreNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerStoreNotify.java @@ -1,7 +1,5 @@ package emu.grasscutter.server.packet.send; -import emu.grasscutter.GameConstants; -import emu.grasscutter.Grasscutter; import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.player.Player; import emu.grasscutter.net.packet.BasePacket; @@ -10,6 +8,8 @@ import emu.grasscutter.net.proto.ItemOuterClass.Item; import emu.grasscutter.net.proto.PlayerStoreNotifyOuterClass.PlayerStoreNotify; import emu.grasscutter.net.proto.StoreTypeOuterClass.StoreType; +import static emu.grasscutter.Configuration.*; + public class PacketPlayerStoreNotify extends BasePacket { public PacketPlayerStoreNotify(Player player) { @@ -19,7 +19,7 @@ public class PacketPlayerStoreNotify extends BasePacket { PlayerStoreNotify.Builder p = PlayerStoreNotify.newBuilder() .setStoreType(StoreType.STORE_PACK) - .setWeightLimit(Grasscutter.getConfig().getGameServerOptions().InventoryLimitAll); + .setWeightLimit(GAME_OPTIONS.inventoryLimits.all); for (GameItem item : player.getInventory()) { Item itemProto = item.toProto(); diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPullRecentChatRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPullRecentChatRsp.java index 871534b53..0e757d11b 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketPullRecentChatRsp.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPullRecentChatRsp.java @@ -1,42 +1,41 @@ package emu.grasscutter.server.packet.send; -import emu.grasscutter.Config.GameServerOptions; import emu.grasscutter.game.player.Player; import emu.grasscutter.GameConstants; -import emu.grasscutter.Grasscutter; import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.ChatInfoOuterClass.ChatInfo; import emu.grasscutter.net.proto.PullRecentChatRspOuterClass.PullRecentChatRsp; import emu.grasscutter.utils.Utils; +import static emu.grasscutter.Configuration.*; + public class PacketPullRecentChatRsp extends BasePacket { public PacketPullRecentChatRsp(Player player) { super(PacketOpcodes.PullRecentChatRsp); - GameServerOptions serverOptions = Grasscutter.getConfig().getGameServerOptions(); + var joinOptions = GAME_INFO.joinOptions; PullRecentChatRsp.Builder proto = PullRecentChatRsp.newBuilder(); - if (serverOptions.WelcomeEmotes != null && serverOptions.WelcomeEmotes.length > 0) { + if (joinOptions.welcomeEmotes != null && joinOptions.welcomeEmotes.length > 0) { ChatInfo welcomeEmote = ChatInfo.newBuilder() .setTime((int) (System.currentTimeMillis() / 1000)) .setUid(GameConstants.SERVER_CONSOLE_UID) .setToUid(player.getUid()) - .setIcon(serverOptions.WelcomeEmotes[Utils.randomRange(0, serverOptions.WelcomeEmotes.length - 1)]) + .setIcon(joinOptions.welcomeEmotes[Utils.randomRange(0, joinOptions.welcomeEmotes.length - 1)]) .build(); proto.addChatInfo(welcomeEmote); } - if (serverOptions.WelcomeMotd != null && serverOptions.WelcomeMotd.length() > 0) { - ChatInfo welcomeMotd = ChatInfo.newBuilder() + if (joinOptions.welcomeMessage != null && joinOptions.welcomeMessage.length() > 0) { + ChatInfo welcomeMessage = ChatInfo.newBuilder() .setTime((int) (System.currentTimeMillis() / 1000)) .setUid(GameConstants.SERVER_CONSOLE_UID) .setToUid(player.getUid()) - .setText(Grasscutter.getConfig().getGameServerOptions().WelcomeMotd) + .setText(joinOptions.welcomeMessage) .build(); - - proto.addChatInfo(welcomeMotd); + proto.addChatInfo(welcomeMessage); } this.setData(proto); diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketStoreWeightLimitNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketStoreWeightLimitNotify.java index 61b51948b..77f9da803 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketStoreWeightLimitNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketStoreWeightLimitNotify.java @@ -1,11 +1,12 @@ package emu.grasscutter.server.packet.send; -import emu.grasscutter.Grasscutter; import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.StoreTypeOuterClass.StoreType; import emu.grasscutter.net.proto.StoreWeightLimitNotifyOuterClass.StoreWeightLimitNotify; +import static emu.grasscutter.Configuration.*; + public class PacketStoreWeightLimitNotify extends BasePacket { public PacketStoreWeightLimitNotify() { @@ -13,11 +14,11 @@ public class PacketStoreWeightLimitNotify extends BasePacket { StoreWeightLimitNotify p = StoreWeightLimitNotify.newBuilder() .setStoreType(StoreType.STORE_PACK) - .setWeightLimit(Grasscutter.getConfig().getGameServerOptions().InventoryLimitAll) - .setWeaponCountLimit(Grasscutter.getConfig().getGameServerOptions().InventoryLimitWeapon) - .setReliquaryCountLimit(Grasscutter.getConfig().getGameServerOptions().InventoryLimitRelic) - .setMaterialCountLimit(Grasscutter.getConfig().getGameServerOptions().InventoryLimitMaterial) - .setFurnitureCountLimit(Grasscutter.getConfig().getGameServerOptions().InventoryLimitFurniture) + .setWeightLimit(INVENTORY_LIMITS.all) + .setWeaponCountLimit(INVENTORY_LIMITS.weapons) + .setReliquaryCountLimit(INVENTORY_LIMITS.relics) + .setMaterialCountLimit(INVENTORY_LIMITS.materials) + .setFurnitureCountLimit(INVENTORY_LIMITS.furniture) .build(); this.setData(p); diff --git a/src/main/java/emu/grasscutter/tools/Tools.java b/src/main/java/emu/grasscutter/tools/Tools.java index f69aafc80..4a5af6e49 100644 --- a/src/main/java/emu/grasscutter/tools/Tools.java +++ b/src/main/java/emu/grasscutter/tools/Tools.java @@ -1,12 +1,8 @@ package emu.grasscutter.tools; -import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.FilenameFilter; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; @@ -14,14 +10,12 @@ import java.nio.charset.StandardCharsets; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.*; -import java.util.stream.Collectors; import com.google.gson.reflect.TypeToken; import emu.grasscutter.GameConstants; import emu.grasscutter.Grasscutter; import emu.grasscutter.command.Command; -import emu.grasscutter.command.CommandHandler; import emu.grasscutter.command.CommandMap; import emu.grasscutter.data.GameData; import emu.grasscutter.data.ResourceLoader; @@ -32,6 +26,7 @@ import emu.grasscutter.data.def.SceneData; import emu.grasscutter.utils.Utils; import static emu.grasscutter.utils.Language.translate; +import static emu.grasscutter.Configuration.*; public final class Tools { public static void createGmHandbook() throws Exception { @@ -42,50 +37,45 @@ public final class Tools { ToolsWithLanguageOption.createGachaMapping(location, getLanguageOption()); } - public static List getAvailableLanguage() throws Exception { - File textMapFolder = new File(Grasscutter.getConfig().RESOURCE_FOLDER + "TextMap"); - List availableLangList = new ArrayList(); - for (String textMapFileName : textMapFolder.list(new FilenameFilter() { - @Override - public boolean accept(File dir, String name) { - if (name.startsWith("TextMap") && name.endsWith(".json")){ - return true; - } - return false; - } - })) { - availableLangList.add(textMapFileName.replace("TextMap","").replace(".json","").toLowerCase()); - } - return availableLangList; + public static List getAvailableLanguage() { + File textMapFolder = new File(RESOURCE("TextMap")); + List availableLangList = new ArrayList<>(); + for (String textMapFileName : Objects.requireNonNull(textMapFolder.list((dir, name) -> name.startsWith("TextMap") && name.endsWith(".json")))) { + availableLangList.add(textMapFileName.replace("TextMap", "").replace(".json", "").toLowerCase()); + } return availableLangList; } - public static String getLanguageOption() throws Exception { + public static String getLanguageOption() { List availableLangList = getAvailableLanguage(); // Use system out for better format if (availableLangList.size() == 1) { return availableLangList.get(0).toUpperCase(); } - String stagedMessage = ""; - stagedMessage += "The following languages mappings are available, please select one: [default: EN]\n"; - String groupedLangList = ">\t"; + StringBuilder stagedMessage = new StringBuilder(); + stagedMessage.append("The following languages mappings are available, please select one: [default: EN] \n"); + + StringBuilder groupedLangList = new StringBuilder(">\t"); String input; int groupedLangCount = 0; - String input = ""; + for (String availableLanguage: availableLangList){ groupedLangCount++; - groupedLangList = groupedLangList + "" + availableLanguage + "\t"; + groupedLangList.append(availableLanguage).append("\t"); + if (groupedLangCount == 6) { - stagedMessage += groupedLangList + "\n"; + stagedMessage.append(groupedLangList).append("\n"); groupedLangCount = 0; - groupedLangList = ">\t"; + groupedLangList = new StringBuilder(">\t"); } } - if (groupedLangCount > 0) { - stagedMessage += groupedLangList + "\n"; - } - stagedMessage += "\nYour choice:[EN] "; - input = Grasscutter.getConsole().readLine(stagedMessage); + if (groupedLangCount > 0) { + stagedMessage.append(groupedLangList).append("\n"); + } + + stagedMessage.append("\nYour choice:[EN] "); + + input = Grasscutter.getConsole().readLine(stagedMessage.toString()); if (availableLangList.contains(input.toLowerCase())) { return input.toUpperCase(); } @@ -101,7 +91,7 @@ final class ToolsWithLanguageOption { ResourceLoader.loadResources(); Map map; - try (InputStreamReader fileReader = new InputStreamReader(new FileInputStream(Utils.toFilePath(Grasscutter.getConfig().RESOURCE_FOLDER + "TextMap/TextMap"+language+".json")), StandardCharsets.UTF_8)) { + try (InputStreamReader fileReader = new InputStreamReader(new FileInputStream(Utils.toFilePath(RESOURCE("TextMap/TextMap"+language+".json"))), StandardCharsets.UTF_8)) { map = Grasscutter.getGsonFactory().fromJson(fileReader, new TypeToken>() {}.getType()); } @@ -119,9 +109,9 @@ final class ToolsWithLanguageOption { writer.println("// Commands"); for (Command cmd : cmdList) { - String cmdName = cmd.label(); + StringBuilder cmdName = new StringBuilder(cmd.label()); while (cmdName.length() <= 15) { - cmdName = " " + cmdName; + cmdName.insert(0, " "); } writer.println(cmdName + " : " + translate(cmd.description())); } @@ -178,16 +168,13 @@ final class ToolsWithLanguageOption { ResourceLoader.loadResources(); Map map; - try (InputStreamReader fileReader = new InputStreamReader(new FileInputStream(Utils.toFilePath(Grasscutter.getConfig().RESOURCE_FOLDER + "TextMap/TextMap"+language+".json")), StandardCharsets.UTF_8)) { + try (InputStreamReader fileReader = new InputStreamReader(new FileInputStream(Utils.toFilePath(RESOURCE("TextMap/TextMap" + language + ".json"))), StandardCharsets.UTF_8)) { map = Grasscutter.getGsonFactory().fromJson(fileReader, new TypeToken>() {}.getType()); } List list; - String fileName = location; - - try (PrintWriter writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream(fileName), StandardCharsets.UTF_8), false)) { - + try (PrintWriter writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream(location), StandardCharsets.UTF_8), false)) { list = new ArrayList<>(GameData.getAvatarDataMap().keySet()); Collections.sort(list); @@ -209,18 +196,11 @@ final class ToolsWithLanguageOption { } else { writer.print(","); } - String color; - switch (data.getQualityType()){ - case "QUALITY_PURPLE": - color = "purple"; - break; - case "QUALITY_ORANGE": - color = "yellow"; - break; - case "QUALITY_BLUE": - default: - color = "blue"; - } + String color = switch (data.getQualityType()) { + case "QUALITY_PURPLE" -> "purple"; + case "QUALITY_ORANGE" -> "yellow"; + default -> "blue"; + }; // Got the magic number 4233146695 from manually search in the json file writer.println( "\"" + (avatarID % 1000 + 1000) + "\" : [\"" diff --git a/src/main/java/emu/grasscutter/utils/ConfigContainer.java b/src/main/java/emu/grasscutter/utils/ConfigContainer.java new file mode 100644 index 000000000..0a453191c --- /dev/null +++ b/src/main/java/emu/grasscutter/utils/ConfigContainer.java @@ -0,0 +1,229 @@ +package emu.grasscutter.utils; + +import com.google.gson.JsonObject; +import emu.grasscutter.Grasscutter; + +import java.io.FileReader; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Locale; + +import static emu.grasscutter.Grasscutter.config; + +/** + * *when your JVM fails* + */ +public class ConfigContainer { + private static int version() { + return 1; + } + + /** + * Attempts to update the server's existing configuration to the latest + */ + public static void updateConfig() { + try { // Check if the server is using a legacy config. + JsonObject configObject = Grasscutter.getGsonFactory() + .fromJson(new FileReader(Grasscutter.configFile), JsonObject.class); + if(!configObject.has("version")) { + Grasscutter.getLogger().info("Updating legacy .."); + Grasscutter.saveConfig(null); + } + } catch (Exception ignored) { } + + var existing = config.version; + var latest = version(); + + if(existing == latest) + return; + + // Create a new configuration instance. + ConfigContainer updated = new ConfigContainer(); + // Update all configuration fields. + Field[] 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(); + + try { // Save configuration & reload. + Grasscutter.saveConfig(updated); + Grasscutter.loadConfig(); + } catch (Exception exception) { + Grasscutter.getLogger().warn("Failed to inject the updated ", exception); + } + } + + 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 String connectionUri = "mongodb://localhost:27017"; + public String collection = "grasscutter"; + } + + public static class Structure { + public String resources = "./resources/"; + public String data = "./data/"; + public String packets = "./packets/"; + public String keys = "./keys/"; + public String scripts = "./resources/scripts/"; + public String plugins = "./plugins/"; + + // UNUSED (potentially added later?) + // public String dumps = "./dumps/"; + } + + public static class Server { + public Grasscutter.ServerDebugMode debugLevel = Grasscutter.ServerDebugMode.NONE; + public Grasscutter.ServerRunMode runMode = Grasscutter.ServerRunMode.HYBRID; + + public Dispatch dispatch = new Dispatch(); + public Game game = new Game(); + } + + public static class Language { + public Locale language = Locale.getDefault(); + public Locale fallback = Locale.US; + } + + public static class Account { + public boolean autoCreate = false; + public String[] defaultPermissions = {}; + } + + /* Server options. */ + + public static class Dispatch { + public String bindAddress = "0.0.0.0"; + /* This is the address used in URLs. */ + public String accessAddress = "127.0.0.1"; + + public int bindPort = 443; + /* This is the port used in URLs. */ + public int accessPort = 443; + + public Encryption encryption = new Encryption(); + public Policies policies = new Policies(); + public Region[] regions = {}; + + public String defaultName = "Grasscutter"; + } + + public static class Game { + public String bindAddress = "0.0.0.0"; + /* This is the address used in the default region. */ + public String accessAddress = "127.0.0.1"; + + public int bindPort = 22102; + /* This is the port used in the default region. */ + public int accessPort = 22102; + + public GameOptions gameOptions = new GameOptions(); + public JoinOptions joinOptions = new JoinOptions(); + public ConsoleAccount serverAccount = new ConsoleAccount(); + } + + /* Data containers. */ + + public static class Encryption { + public boolean useEncryption = true; + /* Should 'https' be appended to URLs? */ + public boolean useInRouting = true; + public String keystore = "./keystore.p12"; + public String keystorePassword = "123456"; + } + + public static class Policies { + public Policies.CORS cors = new Policies.CORS(); + + public static class CORS { + public boolean enabled = false; + public String[] allowedOrigins = new String[]{"*"}; + } + } + + public static class GameOptions { + public GameOptions.InventoryLimits inventoryLimits = new GameOptions.InventoryLimits(); + public GameOptions.AvatarLimits avatarLimits = new GameOptions.AvatarLimits(); + public int worldEntityLimit = 1000; // Unenforced. TODO: Implement. + + public boolean watchGachaConfig = false; + public boolean enableShopItems = true; + public boolean staminaUsage = true; + public GameOptions.Rates rates = new GameOptions.Rates(); + + public Database databaseInfo = new Database(); + + 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 static class AvatarLimits { + public int singlePlayerTeam = 4; + public int multiplayerTeam = 4; + } + + public static class Rates { + public float adventureExp = 1.0f; + public float mora = 1.0f; + public float leyLines = 1.0f; + } + } + + public static class JoinOptions { + public int[] welcomeEmotes = {2007, 1002, 4010}; + public String welcomeMessage = "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 static class ConsoleAccount { + public int avatarId = 10000007; + public int nameCardId = 210001; + public int adventureRank = 1; + public int worldLevel = 0; + + public String nickName = "Server"; + public String signature = "Welcome to Grasscutter!"; + } + + /* Objects. */ + + public static class Region { + public String Name = "os_usa"; + public String Title = "Grasscutter"; + public String Ip = "127.0.0.1"; + public int Port = 22102; + } +} diff --git a/src/main/java/emu/grasscutter/utils/Crypto.java b/src/main/java/emu/grasscutter/utils/Crypto.java index e6d260e94..188a7192e 100644 --- a/src/main/java/emu/grasscutter/utils/Crypto.java +++ b/src/main/java/emu/grasscutter/utils/Crypto.java @@ -7,6 +7,8 @@ import emu.grasscutter.Grasscutter; import emu.grasscutter.net.proto.GetPlayerTokenRspOuterClass.GetPlayerTokenRsp; import emu.grasscutter.net.proto.QueryCurrRegionHttpRspOuterClass.QueryCurrRegionHttpRsp; +import static emu.grasscutter.Configuration.*; + public final class Crypto { private static final SecureRandom secureRandom = new SecureRandom(); public static final long ENCRYPT_SEED = Long.parseUnsignedLong("11468049314633205968"); @@ -16,9 +18,9 @@ public final class Crypto { public static byte[] ENCRYPT_KEY; public static void loadKeys() { - DISPATCH_KEY = FileUtils.read(Grasscutter.getConfig().KEY_FOLDER + "dispatchKey.bin"); - ENCRYPT_KEY = FileUtils.read(Grasscutter.getConfig().KEY_FOLDER + "secretKey.bin"); - ENCRYPT_SEED_BUFFER = FileUtils.read(Grasscutter.getConfig().KEY_FOLDER + "secretKeyBuffer.bin"); + DISPATCH_KEY = FileUtils.read(KEYS_FOLDER + "/dispatchKey.bin"); + ENCRYPT_KEY = FileUtils.read(KEYS_FOLDER + "/secretKey.bin"); + ENCRYPT_SEED_BUFFER = FileUtils.read(KEYS_FOLDER + "/secretKeyBuffer.bin"); } public static void xor(byte[] packet, byte[] key) { @@ -34,7 +36,7 @@ public final class Crypto { public static void extractSecretKeyBuffer(byte[] data) { try { GetPlayerTokenRsp p = GetPlayerTokenRsp.parseFrom(data); - FileUtils.write(Grasscutter.getConfig().KEY_FOLDER + "secretKeyBuffer.bin", p.getSecretKeyBytes().toByteArray()); + FileUtils.write(KEYS_FOLDER + "/secretKeyBuffer.bin", p.getSecretKeyBytes().toByteArray()); Grasscutter.getLogger().info("Secret Key: " + p.getSecretKey()); } catch (Exception e) { Grasscutter.getLogger().error("Crypto error.", e); @@ -44,7 +46,7 @@ public final class Crypto { public static void extractDispatchSeed(String data) { try { QueryCurrRegionHttpRsp p = QueryCurrRegionHttpRsp.parseFrom(Base64.getDecoder().decode(data)); - FileUtils.write(Grasscutter.getConfig().KEY_FOLDER + "dispatchSeed.bin", p.getRegionInfo().getSecretKey().toByteArray()); + FileUtils.write(KEYS_FOLDER + "/dispatchSeed.bin", p.getRegionInfo().getSecretKey().toByteArray()); } catch (Exception e) { Grasscutter.getLogger().error("Crypto error.", e); } diff --git a/src/main/java/emu/grasscutter/utils/Language.java b/src/main/java/emu/grasscutter/utils/Language.java index 7c3426384..3789f594a 100644 --- a/src/main/java/emu/grasscutter/utils/Language.java +++ b/src/main/java/emu/grasscutter/utils/Language.java @@ -10,6 +10,8 @@ import java.io.InputStream; import java.util.concurrent.ConcurrentHashMap; import java.util.Map; +import static emu.grasscutter.Configuration.*; + public final class Language { private static final Map cachedLanguages = new ConcurrentHashMap<>(); @@ -27,8 +29,8 @@ public final class Language { return cachedLanguages.get(langCode); } - var fallbackLanguageCode = Utils.getLanguageCode(Grasscutter.getConfig().DefaultLanguage); - var description = getLanguageFileStreamDescripter(langCode, fallbackLanguageCode); + var fallbackLanguageCode = Utils.getLanguageCode(FALLBACK_LANGUAGE); + var description = getLanguageFileDescription(langCode, fallbackLanguageCode); var actualLanguageCode = description.getLanguageCode(); Language languageInst; @@ -111,35 +113,31 @@ public final class Language { * @param languageCode The name of the language code. * @param fallbackLanguageCode The name of the fallback language code. */ - private static LanguageStreamDescription getLanguageFileStreamDescripter(String languageCode, String fallbackLanguageCode) { + private static LanguageStreamDescription getLanguageFileDescription(String languageCode, String fallbackLanguageCode) { var fileName = languageCode + ".json"; var fallback = fallbackLanguageCode + ".json"; - - String actualLanguageCode = languageCode; - if (cachedLanguages.containsKey(actualLanguageCode)) { - return new LanguageStreamDescription(actualLanguageCode, null); - } + String actualLanguageCode = languageCode; InputStream file = Grasscutter.class.getResourceAsStream("/languages/" + fileName); if (file == null) { // Provided fallback language. + Grasscutter.getLogger().warn("Failed to load language file: " + fileName + ", falling back to: " + fallback); actualLanguageCode = fallbackLanguageCode; if (cachedLanguages.containsKey(actualLanguageCode)) { return new LanguageStreamDescription(actualLanguageCode, null); } file = Grasscutter.class.getResourceAsStream("/languages/" + fallback); - Grasscutter.getLogger().warn("Failed to load language file: " + fileName + ", falling back to: " + fallback); } if(file == null) { // Fallback the fallback language. + Grasscutter.getLogger().warn("Failed to load language file: " + fallback + ", falling back to: en-US.json"); actualLanguageCode = "en-US"; if (cachedLanguages.containsKey(actualLanguageCode)) { return new LanguageStreamDescription(actualLanguageCode, null); } file = Grasscutter.class.getResourceAsStream("/languages/en-US.json"); - Grasscutter.getLogger().warn("Failed to load language file: " + fallback + ", falling back to: en-US.json"); } if(file == null) diff --git a/src/main/java/emu/grasscutter/utils/Utils.java b/src/main/java/emu/grasscutter/utils/Utils.java index 764993255..4af62bfb4 100644 --- a/src/main/java/emu/grasscutter/utils/Utils.java +++ b/src/main/java/emu/grasscutter/utils/Utils.java @@ -11,7 +11,6 @@ import java.util.Map; import java.util.Random; import java.util.Locale; -import emu.grasscutter.Config; import emu.grasscutter.Grasscutter; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; @@ -175,12 +174,12 @@ public final class Utils { * Checks for required files and folders before startup. */ public static void startupCheck() { - Config config = Grasscutter.getConfig(); + ConfigContainer config = Grasscutter.getConfig(); Logger logger = Grasscutter.getLogger(); boolean exit = false; - String resourcesFolder = config.RESOURCE_FOLDER; - String dataFolder = config.DATA_FOLDER; + String resourcesFolder = config.folderStructure.resources; + String dataFolder = config.folderStructure.data; // Check for resources folder. if(!fileExists(resourcesFolder)) { diff --git a/src/main/resources/languages/en-US.json b/src/main/resources/languages/en-US.json index 107a12d71..03e96a4e1 100644 --- a/src/main/resources/languages/en-US.json +++ b/src/main/resources/languages/en-US.json @@ -191,6 +191,7 @@ "language": { "current_language": "current language is %s", "language_changed": "language changed to %s", + "language_not_found": "currently, server does not have that language: %s", "description": "display or change current language" }, "list": { diff --git a/src/main/resources/languages/zh-CN.json b/src/main/resources/languages/zh-CN.json index 8d5b35137..2f9663f4f 100644 --- a/src/main/resources/languages/zh-CN.json +++ b/src/main/resources/languages/zh-CN.json @@ -191,6 +191,7 @@ "language": { "current_language": "当前语言是: %s", "language_changed": "语言切换至: %s", + "language_not_found": "目前服务端没有这种语言: %s", "description": "显示或切换当前语言。" }, "list": {