diff --git a/.editorconfig b/.editorconfig index a76a5bd6..dcc46b74 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,10 +5,10 @@ charset = utf-8 end_of_line = lf indent_size = 4 indent_style = space -insert_final_newline = false +insert_final_newline = true max_line_length = 120 tab_width = 4 trim_trailing_whitespace = true [{*.json,*.xml}] -indent_size = 2 \ No newline at end of file +indent_size = 2 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..3175732e --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +*.java text=auto +*.json text=auto +*.md text=auto +*.properties text=auto +*.py text=auto +*.sh text=auto diff --git a/src/main/java/emu/grasscutter/Grasscutter.java b/src/main/java/emu/grasscutter/Grasscutter.java index ba1f85f1..d7d64fb6 100644 --- a/src/main/java/emu/grasscutter/Grasscutter.java +++ b/src/main/java/emu/grasscutter/Grasscutter.java @@ -29,7 +29,10 @@ import emu.grasscutter.server.http.handlers.LogHandler; import emu.grasscutter.tools.Tools; import emu.grasscutter.utils.Crypto; import emu.grasscutter.utils.Language; +import emu.grasscutter.utils.StartupArguments; import emu.grasscutter.utils.Utils; +import lombok.Getter; +import lombok.Setter; import org.jline.reader.EndOfFileException; import org.jline.reader.LineReader; import org.jline.reader.LineReaderBuilder; @@ -57,10 +60,13 @@ public final class Grasscutter { public static final File configFile = new File("./config.json"); private static int day; // Current day of week. + @Getter @Setter private static String preferredLanguage; private static HttpServer httpServer; private static GameServer gameServer; private static PluginManager pluginManager; + @Getter private static CommandMap commandMap; + private static AuthenticationSystem authenticationSystem; private static PermissionHandler permissionHandler; @@ -89,59 +95,16 @@ public final class Grasscutter { public static void main(String[] args) throws Exception { Crypto.loadKeys(); // Load keys from buffers. - Tools.createGmHandbooks(); - // Parse arguments. - boolean exitEarly = false; - for (String arg : args) { - switch (arg.toLowerCase()) { - case "-dumppacketids" -> { - PacketOpcodesUtils.dumpPacketIds(); - exitEarly = true; - } - case "-version" -> { - System.out.println("Grasscutter version: " + BuildConfig.VERSION + "-" + BuildConfig.GIT_HASH); - exitEarly = true; - } - case "-debug" -> { - // Set the logger to debug. - log.setLevel(Level.DEBUG); - log.debug("The logger is now running in debug mode."); - - // Change loggers to debug. - ((Logger) LoggerFactory.getLogger("express")) - .setLevel(Level.INFO); - ((Logger) LoggerFactory.getLogger("org.quartz")) - .setLevel(Level.INFO); - ((Logger) LoggerFactory.getLogger("org.reflections")) - .setLevel(Level.INFO); - ((Logger) LoggerFactory.getLogger("org.eclipse.jetty")) - .setLevel(Level.INFO); - ((Logger) LoggerFactory.getLogger("org.mongodb.driver")) - .setLevel(Level.INFO); - } - case "-debugall" -> { - // Set the logger to debug. - log.setLevel(Level.DEBUG); - log.debug("The logger is now running in debug mode."); - - // Change loggers to debug. - ((Logger) LoggerFactory.getLogger("express")) - .setLevel(Level.DEBUG); - ((Logger) LoggerFactory.getLogger("org.quartz")) - .setLevel(Level.DEBUG); - ((Logger) LoggerFactory.getLogger("org.reflections")) - .setLevel(Level.DEBUG); - ((Logger) LoggerFactory.getLogger("org.eclipse.jetty")) - .setLevel(Level.DEBUG); - ((Logger) LoggerFactory.getLogger("org.mongodb.driver")) - .setLevel(Level.DEBUG); - } - } + // Parse start-up arguments. + if(StartupArguments.parse(args)) { + System.exit(0); // Exit early. } - // Exit early if an argument sets it. - if (exitEarly) System.exit(0); + // Create command map. + commandMap = new CommandMap(true); + // Generate handbooks. + Tools.createGmHandbooks(); // Initialize server. Grasscutter.getLogger().info(translate("messages.status.starting")); diff --git a/src/main/java/emu/grasscutter/command/CommandMap.java b/src/main/java/emu/grasscutter/command/CommandMap.java index c976c5cc..260a69e2 100644 --- a/src/main/java/emu/grasscutter/command/CommandMap.java +++ b/src/main/java/emu/grasscutter/command/CommandMap.java @@ -23,7 +23,7 @@ public final class CommandMap { } public static CommandMap getInstance() { - return Grasscutter.getGameServer().getCommandMap(); + return Grasscutter.getCommandMap(); } /** diff --git a/src/main/java/emu/grasscutter/data/ResourceLoader.java b/src/main/java/emu/grasscutter/data/ResourceLoader.java index 01662e23..985ada05 100644 --- a/src/main/java/emu/grasscutter/data/ResourceLoader.java +++ b/src/main/java/emu/grasscutter/data/ResourceLoader.java @@ -421,7 +421,7 @@ public class ResourceLoader { GameData.getMainQuestDataMap().put(mainQuest.getId(), mainQuest); } - try (Reader reader = new FileReader(new File(RESOURCE("QuestEncryptionKeys.json")))) { + try (Reader reader = new FileReader(RESOURCE("QuestEncryptionKeys.json"))) { List keys = Grasscutter.getGsonFactory().fromJson( reader, TypeToken.getParameterized(List.class, QuestEncryptionKey.class).getType()); diff --git a/src/main/java/emu/grasscutter/plugin/PluginManager.java b/src/main/java/emu/grasscutter/plugin/PluginManager.java index f55e0a6f..d2df184d 100644 --- a/src/main/java/emu/grasscutter/plugin/PluginManager.java +++ b/src/main/java/emu/grasscutter/plugin/PluginManager.java @@ -85,7 +85,6 @@ public final class PluginManager { PluginConfig pluginConfig = Grasscutter.getGsonFactory().fromJson(fileReader, PluginConfig.class); // Check if the plugin config is valid. if (!pluginConfig.validate()) { - Utils.logObject(pluginConfig); Grasscutter.getLogger().warn("Plugin " + plugin.getName() + " has an invalid config file."); return; } @@ -211,11 +210,7 @@ public final class PluginManager { public void disablePlugins() { this.plugins.forEach((name, plugin) -> { Grasscutter.getLogger().info("Disabling plugin: " + name); - try { - plugin.onDisable(); - } catch (Throwable exception) { - Grasscutter.getLogger().error("Failed to disable plugin: " + name, exception); - } + this.disablePlugin(plugin); }); } diff --git a/src/main/java/emu/grasscutter/plugin/api/ServerHook.java b/src/main/java/emu/grasscutter/plugin/api/ServerHook.java index feb22547..a103865e 100644 --- a/src/main/java/emu/grasscutter/plugin/api/ServerHook.java +++ b/src/main/java/emu/grasscutter/plugin/api/ServerHook.java @@ -4,6 +4,7 @@ import emu.grasscutter.Grasscutter; import emu.grasscutter.auth.AuthenticationSystem; import emu.grasscutter.command.Command; import emu.grasscutter.command.CommandHandler; +import emu.grasscutter.command.CommandMap; import emu.grasscutter.command.PermissionHandler; import emu.grasscutter.game.player.Player; import emu.grasscutter.server.game.GameServer; @@ -72,7 +73,7 @@ public final class ServerHook { if(!clazz.isAnnotationPresent(Command.class)) throw new IllegalArgumentException("Command handler must be annotated with @Command."); Command commandData = clazz.getAnnotation(Command.class); - this.gameServer.getCommandMap().registerCommand(commandData.label(), handler); + CommandMap.getInstance().registerCommand(commandData.label(), handler); } /** diff --git a/src/main/java/emu/grasscutter/server/game/GameServer.java b/src/main/java/emu/grasscutter/server/game/GameServer.java index d5be5047..a1e36f74 100644 --- a/src/main/java/emu/grasscutter/server/game/GameServer.java +++ b/src/main/java/emu/grasscutter/server/game/GameServer.java @@ -72,7 +72,6 @@ public final class GameServer extends KcpServer { // Extra private final ServerTaskScheduler scheduler; - private final CommandMap commandMap; private final TaskMap taskMap; private ChatManagerHandler chatManager; @@ -107,7 +106,6 @@ public final class GameServer extends KcpServer { // Extra this.scheduler = new ServerTaskScheduler(); - this.commandMap = new CommandMap(true); this.taskMap = new TaskMap(true); // Create game systems diff --git a/src/main/java/emu/grasscutter/server/http/documentation/HandbookRequestHandler.java b/src/main/java/emu/grasscutter/server/http/documentation/HandbookRequestHandler.java index d2ca07ac..cb260e3a 100644 --- a/src/main/java/emu/grasscutter/server/http/documentation/HandbookRequestHandler.java +++ b/src/main/java/emu/grasscutter/server/http/documentation/HandbookRequestHandler.java @@ -54,7 +54,7 @@ final class HandbookRequestHandler implements DocumentationHandler { sbs.add(new StringBuilder("")); // Commands table - new CommandMap(true).getHandlersAsList().forEach(cmd -> { + CommandMap.getInstance().getHandlersAsList().forEach(cmd -> { String label = cmd.getLabel(); String descKey = cmd.getDescriptionKey(); for (int langIdx = 0; langIdx < NUM_LANGUAGES; langIdx++) diff --git a/src/main/java/emu/grasscutter/tools/Tools.java b/src/main/java/emu/grasscutter/tools/Tools.java index ed9e3550..9b5fe737 100644 --- a/src/main/java/emu/grasscutter/tools/Tools.java +++ b/src/main/java/emu/grasscutter/tools/Tools.java @@ -48,7 +48,7 @@ public final class Tools { .append("// Created " + now + "\n\n") .append("// Commands\n")); // Commands - final List cmdList = new CommandMap(true).getHandlersAsList(); + final List cmdList = CommandMap.getInstance().getHandlersAsList(); final String padCmdLabel = "%" + cmdList.stream().map(CommandHandler::getLabel).map(String::length).max(Integer::compare).get().toString() + "s : "; for (CommandHandler cmd : cmdList) { final String label = padCmdLabel.formatted(cmd.getLabel()); @@ -65,7 +65,7 @@ public final class Tools { final Int2IntMap h = handbookNames[section]; final String s = "\n\n// " + handbookSections[section] + "\n"; handbookBuilders.forEach(b -> b.append(s)); - final String padId = "%" + Integer.toString(Integer.toString(h.keySet().intStream().max().getAsInt()).length()) + "s : "; + final String padId = "%" + Integer.toString(h.keySet().intStream().max().getAsInt()).length() + "s : "; h.keySet().intStream().sorted().forEach(id -> { final String sId = padId.formatted(id); final TextStrings t = textMaps.get(h.get(id)); @@ -76,7 +76,7 @@ public final class Tools { // Scenes - no translations handbookBuilders.forEach(b -> b.append("\n\n// Scenes\n")); final var sceneDataMap = GameData.getSceneDataMap(); - final String padSceneId = "%" + Integer.toString(Integer.toString(sceneDataMap.keySet().intStream().max().getAsInt()).length()) + "d : "; + final String padSceneId = "%" + Integer.toString(sceneDataMap.keySet().intStream().max().getAsInt()).length() + "d : "; sceneDataMap.keySet().intStream().sorted().forEach(id -> { final String sId = padSceneId.formatted(id); final String data = sceneDataMap.get(id).getScriptData(); @@ -85,7 +85,7 @@ public final class Tools { // Quests handbookBuilders.forEach(b -> b.append("\n\n// Quests\n")); final var questDataMap = GameData.getQuestDataMap(); - final String padQuestId = "%" + Integer.toString(Integer.toString(questDataMap.keySet().intStream().max().getAsInt()).length()) + "d : "; + final String padQuestId = "%" + Integer.toString(questDataMap.keySet().intStream().max().getAsInt()).length() + "d : "; questDataMap.keySet().intStream().sorted().forEach(id -> { final String sId = padQuestId.formatted(id); final QuestData data = questDataMap.get(id); @@ -206,7 +206,7 @@ public final class Tools { try (PrintWriter writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream(location), StandardCharsets.UTF_8), false)) { // if the user made choices for language, I assume it's okay to assign his/her selected language to "en-us" // since it's the fallback language and there will be no difference in the gacha record page. - // The enduser can still modify the `gacha/mappings.js` directly to enable multilingual for the gacha record system. + // The end-user can still modify the `gacha/mappings.js` directly to enable multilingual for the gacha record system. writer.println(sb); Grasscutter.getLogger().info("Mappings generated to " + location + " !"); } @@ -220,6 +220,7 @@ public final class Tools { } return availableLangList; } + @Deprecated(forRemoval = true, since = "1.2.3") public static String getLanguageOption() { List availableLangList = getAvailableLanguage(); @@ -248,17 +249,13 @@ public final class Tools { stagedMessage.append(groupedLangList).append("\n"); } - stagedMessage.append("\nYour choice:[EN] "); + stagedMessage.append("\nYour choice: [EN] "); input = Grasscutter.getConsole().readLine(stagedMessage.toString()); if (availableLangList.contains(input.toLowerCase())) { return input.toUpperCase(); } - Grasscutter.getLogger().info("Invalid option. Will use EN(English) as fallback"); - return "EN"; + Grasscutter.getLogger().info("Invalid option. Will use EN (English) as fallback."); return "EN"; } } - -final class ToolsWithLanguageOption { -} diff --git a/src/main/java/emu/grasscutter/utils/StartupArguments.java b/src/main/java/emu/grasscutter/utils/StartupArguments.java new file mode 100644 index 00000000..c42840d0 --- /dev/null +++ b/src/main/java/emu/grasscutter/utils/StartupArguments.java @@ -0,0 +1,99 @@ +package emu.grasscutter.utils; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import emu.grasscutter.BuildConfig; +import emu.grasscutter.Grasscutter; +import emu.grasscutter.net.packet.PacketOpcodesUtils; +import org.slf4j.LoggerFactory; + +import java.util.Map; +import java.util.function.Function; + +/** + * A parser for start-up arguments. + */ +public final class StartupArguments { + private StartupArguments() { + // This class is not meant to be instantiated. + } + + /* A map of parameter -> argument handler. */ + private static final Map> argumentHandlers = Map.of( + "-dumppacketids", parameter -> { + PacketOpcodesUtils.dumpPacketIds(); return true; + }, + "-version", StartupArguments::printVersion, + "-debug", StartupArguments::enableDebug, + "-lang", parameter -> { + Grasscutter.setPreferredLanguage(parameter); return false; + }, + + // Aliases. + "-v", StartupArguments::printVersion, + "-debugall", parameter -> { + StartupArguments.enableDebug("all"); return false; + } + ); + + /** + * Parses the provided start-up arguments. + * @param args The application start-up arguments. + * @return If the application should exit. + */ + public static boolean parse(String[] args) { + boolean exitEarly = false; + + // Parse the arguments. + for(var input : args) { + var containsParameter = input.contains("="); + + var argument = containsParameter ? input.split("=")[0] : input; + var handler = argumentHandlers.get(argument.toLowerCase()); + + if (handler != null) { + exitEarly |= handler.apply(containsParameter ? input.split("=")[1] : null); + } + } + + return exitEarly; + } + + /** + * Prints the server version. + * @param parameter Additional parameters. + * @return True to exit early. + */ + private static boolean printVersion(String parameter) { + System.out.println("Grasscutter version: " + BuildConfig.VERSION + "-" + BuildConfig.GIT_HASH); return true; + } + + /** + * Enables debug logging. + * @param parameter Additional parameters. + * @return False to continue execution. + */ + private static boolean enableDebug(String parameter) { + // Get the level by parameter. + var loggerLevel = parameter != null && parameter.equals("all") + ? Level.DEBUG : Level.INFO; + + // Set the logger to debug. + Grasscutter.getLogger().setLevel(Level.DEBUG); + Grasscutter.getLogger().debug("The logger is now running in debug mode."); + + // Change loggers to debug. + ((Logger) LoggerFactory.getLogger("express")) + .setLevel(loggerLevel); + ((Logger) LoggerFactory.getLogger("org.quartz")) + .setLevel(loggerLevel); + ((Logger) LoggerFactory.getLogger("org.reflections")) + .setLevel(loggerLevel); + ((Logger) LoggerFactory.getLogger("org.eclipse.jetty")) + .setLevel(loggerLevel); + ((Logger) LoggerFactory.getLogger("org.mongodb.driver")) + .setLevel(loggerLevel); + + return false; + } +}