From 9add61ff745f9d7789a80c500d7ec6e6aa52aa5a Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Tue, 21 Jun 2022 19:06:14 -0400 Subject: [PATCH 01/11] Add `loadAfter` plugin setting --- plugin-schema.json | 106 ++++++++++-------- .../emu/grasscutter/plugin/PluginConfig.java | 2 + 2 files changed, 62 insertions(+), 46 deletions(-) diff --git a/plugin-schema.json b/plugin-schema.json index 4fc772416..fad282da0 100644 --- a/plugin-schema.json +++ b/plugin-schema.json @@ -1,49 +1,63 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "JSON schema for a Grasscutter Plugin", - "type": "object", - "additionalProperties": true, - "definitions": { - "plugin-name": { - "type": "string", - "pattern": "^[A-Za-z\\d_.-]+$" + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "JSON schema for a Grasscutter Plugin", + "type": "object", + "additionalProperties": true, + "definitions": { + "plugin-name": { + "type": "string", + "pattern": "^[A-Za-z\\d_.-]+$" + } + }, + "required": [ + "name", + "description", + "mainClass" + ], + "properties": { + "name": { + "description": "The unique name of plugin.", + "$ref": "#/definitions/plugin-name" + }, + "mainClass": { + "description": "The plugin's initial class file.", + "type": "string", + "pattern": "^(?!org\\.bukkit\\.)([a-zA-Z_$][a-zA-Z\\d_$]*\\.)*[a-zA-Z_$][a-zA-Z\\d_$]*$" + }, + "version": { + "description": "A plugin revision identifier.", + "type": [ + "string", + "number" + ] + }, + "description": { + "description": "Human readable plugin summary.", + "type": "string" + }, + "author": { + "description": "The plugin author.", + "type": "string" + }, + "authors": { + "description": "The plugin contributors.", + "type": "array", + "items": { + "type": "string" + } + }, + "website": { + "title": "Website", + "description": "The URL to the plugin's site", + "type": "string", + "format": "uri" + }, + "loadAfter": { + "description": "Plugins to load before this plugin.", + "type": "array", + "items": { + "type": "string" + } + } } - }, - "required": [ "name", "description", "mainClass" ], - "properties": { - "name": { - "description": "The unique name of plugin.", - "$ref": "#/definitions/plugin-name" - }, - "mainClass": { - "description": "The plugin's initial class file.", - "type": "string", - "pattern": "^(?!org\\.bukkit\\.)([a-zA-Z_$][a-zA-Z\\d_$]*\\.)*[a-zA-Z_$][a-zA-Z\\d_$]*$" - }, - "version": { - "description": "A plugin revision identifier.", - "type": [ "string", "number" ] - }, - "description": { - "description": "Human readable plugin summary.", - "type": "string" - }, - "author": { - "description": "The plugin author.", - "type": "string" - }, - "authors": { - "description": "The plugin contributors.", - "type": "array", - "items": { - "type": "string" - } - }, - "website": { - "title": "Website", - "description": "The URL to the plugin's site", - "type": "string", - "format": "uri" - } - } } \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/plugin/PluginConfig.java b/src/main/java/emu/grasscutter/plugin/PluginConfig.java index 0fb07037c..e80cd6f60 100644 --- a/src/main/java/emu/grasscutter/plugin/PluginConfig.java +++ b/src/main/java/emu/grasscutter/plugin/PluginConfig.java @@ -7,11 +7,13 @@ public final class PluginConfig { public String name, description, version; public String mainClass; public String[] authors; + public String[] loadAfter; /** * Attempts to validate this config instance. * @return True if the config is valid, false otherwise. */ + @SuppressWarnings("BooleanMethodIsAlwaysInverted") public boolean validate() { return name != null && description != null && mainClass != null; } From fe78d2c295a9d0e4fd7b2405678b6f1f4c2a776d Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Tue, 21 Jun 2022 23:15:52 -0400 Subject: [PATCH 02/11] Plugin-specific listeners & dependency loading --- .../emu/grasscutter/plugin/PluginManager.java | 215 ++++++++++++++---- 1 file changed, 166 insertions(+), 49 deletions(-) diff --git a/src/main/java/emu/grasscutter/plugin/PluginManager.java b/src/main/java/emu/grasscutter/plugin/PluginManager.java index f6f1cfbf7..8575d125e 100644 --- a/src/main/java/emu/grasscutter/plugin/PluginManager.java +++ b/src/main/java/emu/grasscutter/plugin/PluginManager.java @@ -1,54 +1,60 @@ package emu.grasscutter.plugin; import emu.grasscutter.Grasscutter; -import emu.grasscutter.server.event.Event; -import emu.grasscutter.server.event.EventHandler; -import emu.grasscutter.server.event.HandlerPriority; +import emu.grasscutter.server.event.*; import emu.grasscutter.utils.Utils; +import lombok.*; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.InputStreamReader; +import javax.annotation.Nullable; +import java.io.*; import java.lang.reflect.Method; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; +import java.net.*; import java.util.*; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; +import java.util.jar.*; -import static emu.grasscutter.Configuration.*; +import static emu.grasscutter.Configuration.PLUGIN; /** * Manages the server's plugins and the event system. */ public final class PluginManager { - private final Map plugins = new HashMap<>(); - private final List> listeners = new LinkedList<>(); - + /* All loaded plugins. */ + private final Map plugins = new LinkedHashMap<>(); + /* All currently registered listeners per plugin. */ + private final Map>> listeners = new LinkedHashMap<>(); + public PluginManager() { this.loadPlugins(); // Load all plugins from the plugins directory. } + /* Data about an unloaded plugin. */ + @AllArgsConstructor @Getter + static class PluginData { + private Plugin plugin; + private PluginIdentifier identifier; + private URLClassLoader classLoader; + private String[] dependencies; + } + /** * Loads plugins from the config-specified directory. */ private void loadPlugins() { File pluginsDir = new File(Utils.toFilePath(PLUGIN())); - if(!pluginsDir.exists() && !pluginsDir.mkdirs()) { + if (!pluginsDir.exists() && !pluginsDir.mkdirs()) { Grasscutter.getLogger().error("Failed to create plugins directory: " + pluginsDir.getAbsolutePath()); return; } - + File[] files = pluginsDir.listFiles(); - if(files == null) { + if (files == null) { // The directory is empty, there aren't any plugins to load. return; } - + List plugins = Arrays.stream(files) - .filter(file -> file.getName().endsWith(".jar")) - .toList(); + .filter(file -> file.getName().endsWith(".jar")) + .toList(); URL[] pluginNames = new URL[plugins.size()]; plugins.forEach(plugin -> { @@ -59,36 +65,59 @@ public final class PluginManager { } }); + // Create a class loader for the plugins. URLClassLoader classLoader = new URLClassLoader(pluginNames); + // Create a list of plugins that require dependencies. + List dependencies = new ArrayList<>(); - plugins.forEach(plugin -> { + // Initialize all plugins. + for(var plugin : plugins) { try { URL url = plugin.toURI().toURL(); try (URLClassLoader loader = new URLClassLoader(new URL[]{url})) { - URL configFile = loader.findResource("plugin.json"); // Find the plugin.json file for each plugin. + // Find the plugin.json file for each plugin. + URL configFile = loader.findResource("plugin.json"); + // Open the config file for reading. InputStreamReader fileReader = new InputStreamReader(configFile.openStream()); + // Create a plugin config instance from the config file. PluginConfig pluginConfig = Grasscutter.getGsonFactory().fromJson(fileReader, PluginConfig.class); - if(!pluginConfig.validate()) { + // 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; } + // Create a JAR file instance from the plugin's URL. JarFile jarFile = new JarFile(plugin); + // Load all class files from the JAR file. Enumeration entries = jarFile.entries(); - while(entries.hasMoreElements()) { + while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); - if(entry.isDirectory() || !entry.getName().endsWith(".class") || entry.getName().contains("module-info")) continue; + if (entry.isDirectory() || !entry.getName().endsWith(".class") || entry.getName().contains("module-info")) + continue; String className = entry.getName().replace(".class", "").replace("/", "."); classLoader.loadClass(className); // Use the same class loader for ALL plugins. } - + + // Create a plugin instance. Class pluginClass = classLoader.loadClass(pluginConfig.mainClass); Plugin pluginInstance = (Plugin) pluginClass.getDeclaredConstructor().newInstance(); + // Close the file reader. + fileReader.close(); + + // Check if the plugin has alternate dependencies. + if(pluginConfig.loadAfter.length > 0) { + // Add the plugin to a "load later" list. + dependencies.add(new PluginData( + pluginInstance, PluginIdentifier.fromPluginConfig(pluginConfig), + loader, pluginConfig.loadAfter)); + continue; + } + + // Load the plugin. this.loadPlugin(pluginInstance, PluginIdentifier.fromPluginConfig(pluginConfig), loader); - - fileReader.close(); // Close the file reader. } catch (ClassNotFoundException ignored) { Grasscutter.getLogger().warn("Plugin " + plugin.getName() + " has an invalid main class."); } catch (FileNotFoundException ignored) { @@ -97,29 +126,58 @@ public final class PluginManager { } catch (Exception exception) { Grasscutter.getLogger().error("Failed to load plugin: " + plugin.getName(), exception); } - }); + } + + // Load plugins with dependencies. + int depth = 0; final int maxDepth = 30; + while(!dependencies.isEmpty() || depth < maxDepth) { + try { + // Get the next plugin to load. + var pluginData = dependencies.get(0); + // Check if the plugin's dependencies are loaded. + if(!this.plugins.keySet().containsAll(List.of(pluginData.getDependencies()))) { + depth++; // Increase depth counter. + continue; // Continue to next plugin. + } + + // Load the plugin. + this.loadPlugin(pluginData.getPlugin(), pluginData.getIdentifier(), pluginData.getClassLoader()); + } catch (Exception exception) { + Grasscutter.getLogger().error("Failed to load a plugin.", exception); + } + } } /** * Load the specified plugin. + * * @param plugin The plugin instance. */ private void loadPlugin(Plugin plugin, PluginIdentifier identifier, URLClassLoader classLoader) { Grasscutter.getLogger().info("Loading plugin: " + identifier.name); - + // Add the plugin's identifier. try { Class pluginClass = Plugin.class; Method method = pluginClass.getDeclaredMethod("initializePlugin", PluginIdentifier.class, URLClassLoader.class); - method.setAccessible(true); method.invoke(plugin, identifier, classLoader); method.setAccessible(false); + method.setAccessible(true); + method.invoke(plugin, identifier, classLoader); + method.setAccessible(false); } catch (Exception ignored) { Grasscutter.getLogger().warn("Failed to add plugin identifier: " + identifier.name); } - + // Add the plugin to the list of loaded plugins. this.plugins.put(identifier.name, plugin); + // Create a collection for the plugin's listeners. + this.listeners.put(plugin, new LinkedList<>()); + // Call the plugin's onLoad method. - plugin.onLoad(); + try { + plugin.onLoad(); + } catch (Exception exception) { + Grasscutter.getLogger().error("Failed to load plugin: " + identifier.name, exception); + } } /** @@ -128,62 +186,121 @@ public final class PluginManager { public void enablePlugins() { this.plugins.forEach((name, plugin) -> { Grasscutter.getLogger().info("Enabling plugin: " + name); - plugin.onEnable(); + try { + plugin.onEnable(); + } catch (Exception exception) { + Grasscutter.getLogger().error("Failed to enable plugin: " + name, exception); + } }); } - + /** * Disables all registered plugins. */ public void disablePlugins() { this.plugins.forEach((name, plugin) -> { Grasscutter.getLogger().info("Disabling plugin: " + name); - plugin.onDisable(); + try { + plugin.onDisable(); + } catch (Exception exception) { + Grasscutter.getLogger().error("Failed to disable plugin: " + name, exception); + } }); } /** * Registers a plugin's event listener. + * + * @param plugin The plugin registering the listener. * @param listener The event listener. */ - public void registerListener(EventHandler listener) { - this.listeners.add(listener); + public void registerListener(Plugin plugin, EventHandler listener) { + this.listeners.get(plugin).add(listener); } - + /** * Invoke the provided event on all registered event listeners. + * * @param event The event to invoke. */ public void invokeEvent(Event event) { EnumSet.allOf(HandlerPriority.class) - .forEach(priority -> this.checkAndFilter(event, priority)); + .forEach(priority -> this.checkAndFilter(event, priority)); } /** * Check an event to handlers for the priority. - * @param event The event being called. + * + * @param event The event being called. * @param priority The priority to call for. */ private void checkAndFilter(Event event, HandlerPriority priority) { - this.listeners.stream() - .filter(handler -> handler.handles().isInstance(event)) - .filter(handler -> handler.getPriority() == priority) - .toList().forEach(handler -> this.invokeHandler(event, handler)); + // Create a collection of listeners. + List> listeners = new LinkedList<>(); + + // Add all listeners from every plugin. + this.listeners.values().forEach(listeners::addAll); + + listeners.stream() + // Filter the listeners by priority. + .filter(handler -> handler.handles().isInstance(event)) + .filter(handler -> handler.getPriority() == priority) + // Invoke the event. + .toList().forEach(handler -> this.invokeHandler(event, handler)); } + /** + * Gets a plugin's instance by its name. + * + * @param name The name of the plugin. + * @return Either null, or the plugin's instance. + */ + @Nullable public Plugin getPlugin(String name) { return this.plugins.get(name); } + /** + * Enables a plugin. + * + * @param plugin The plugin to enable. + */ + public void enablePlugin(Plugin plugin) { + try { + // Call the plugin's onEnable method. + plugin.onEnable(); + } catch (Exception exception) { + Grasscutter.getLogger().error("Failed to enable plugin: " + plugin.getName(), exception); + } + } + + /** + * Disables a plugin. + * + * @param plugin The plugin to disable. + */ + public void disablePlugin(Plugin plugin) { + try { + // Call the plugin's onDisable method. + plugin.onDisable(); + } catch (Exception exception) { + Grasscutter.getLogger().error("Failed to disable plugin: " + plugin.getName(), exception); + } + + // Un-register all listeners. + this.listeners.remove(plugin); + } + /** * Performs logic checks then invokes the provided event handler. - * @param event The event passed through to the handler. + * + * @param event The event passed through to the handler. * @param handler The handler to invoke. */ @SuppressWarnings("unchecked") private void invokeHandler(Event event, EventHandler handler) { - if(!event.isCanceled() || - (event.isCanceled() && handler.ignoresCanceled()) + if (!event.isCanceled() || + (event.isCanceled() && handler.ignoresCanceled()) ) handler.getCallback().consume((T) event); } } From 7308a70222f09e5e55f1c5995c2a2eecace0a725 Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Tue, 21 Jun 2022 23:16:19 -0400 Subject: [PATCH 03/11] Separate aliases from the command map --- .../emu/grasscutter/command/CommandMap.java | 56 ++++++++++++------- 1 file changed, 36 insertions(+), 20 deletions(-) diff --git a/src/main/java/emu/grasscutter/command/CommandMap.java b/src/main/java/emu/grasscutter/command/CommandMap.java index 05c099bcb..bbda2b646 100644 --- a/src/main/java/emu/grasscutter/command/CommandMap.java +++ b/src/main/java/emu/grasscutter/command/CommandMap.java @@ -1,9 +1,7 @@ package emu.grasscutter.command; import emu.grasscutter.Grasscutter; -import emu.grasscutter.game.Account; import emu.grasscutter.game.player.Player; - import org.reflections.Reflections; import java.util.*; @@ -11,6 +9,7 @@ import java.util.*; @SuppressWarnings({"UnusedReturnValue", "unused"}) public final class CommandMap { private final Map commands = new HashMap<>(); + private final Map aliases = new HashMap<>(); private final Map annotations = new HashMap<>(); private final Map targetPlayerIds = new HashMap<>(); private static final String consoleId = "console"; @@ -34,6 +33,7 @@ public final class CommandMap { * @param command The command handler. * @return Instance chaining. */ + @Deprecated(since = "1.2.1-dev") public CommandMap registerCommand(String label, CommandHandler command) { Grasscutter.getLogger().debug("Registered command: " + label); @@ -45,7 +45,7 @@ public final class CommandMap { // Register aliases. if (annotation.aliases().length > 0) { for (String alias : annotation.aliases()) { - this.commands.put(alias, command); + this.aliases.put(alias, command); this.annotations.put(alias, annotation); } } @@ -60,6 +60,7 @@ public final class CommandMap { */ public CommandMap unregisterCommand(String label) { Grasscutter.getLogger().debug("Unregistered command: " + label); + CommandHandler handler = this.commands.get(label); if (handler == null) return this; @@ -70,7 +71,7 @@ public final class CommandMap { // Unregister aliases. if (annotation.aliases().length > 0) { for (String alias : annotation.aliases()) { - this.commands.remove(alias); + this.aliases.remove(alias); this.annotations.remove(alias); } } @@ -78,7 +79,9 @@ public final class CommandMap { return this; } - public List getAnnotationsAsList() { return new LinkedList<>(this.annotations.values()); } + public List getAnnotationsAsList() { + return new LinkedList<>(this.annotations.values()); + } public HashMap getAnnotations() { return new LinkedHashMap<>(this.annotations); @@ -125,7 +128,7 @@ public final class CommandMap { List args = new LinkedList<>(Arrays.asList(split)); String label = args.remove(0); String playerId = (player == null) ? consoleId : player.getAccount().getId(); - + // Check for special cases - currently only target command. String targetUidStr = null; if (label.startsWith("@")) { // @[UID] @@ -142,7 +145,7 @@ public final class CommandMap { } if (targetUidStr != null) { if (targetUidStr.equals("")) { // Clears the default targetPlayer. - targetPlayerIds.remove(playerId); + this.targetPlayerIds.remove(playerId); CommandHandler.sendTranslatedMessage(player, "commands.execution.clear_target"); } else { // Sets default targetPlayer to the UID provided. try { @@ -151,9 +154,9 @@ public final class CommandMap { if (targetPlayer == null) { CommandHandler.sendTranslatedMessage(player, "commands.execution.player_exist_error"); } else { - targetPlayerIds.put(playerId, uid); + this.targetPlayerIds.put(playerId, uid); CommandHandler.sendTranslatedMessage(player, "commands.execution.set_target", targetUidStr); - CommandHandler.sendTranslatedMessage(player, targetPlayer.isOnline()? "commands.execution.set_target_online" : "commands.execution.set_target_offline", targetUidStr); + CommandHandler.sendTranslatedMessage(player, targetPlayer.isOnline() ? "commands.execution.set_target_online" : "commands.execution.set_target_offline", targetUidStr); } } catch (NumberFormatException e) { CommandHandler.sendTranslatedMessage(player, "commands.execution.uid_error"); @@ -164,11 +167,19 @@ public final class CommandMap { // Get command handler. CommandHandler handler = this.commands.get(label); + if(handler == null) + // Try to get the handler by alias. + handler = this.aliases.get(label); + + // Check if the handler is still null. if (handler == null) { CommandHandler.sendTranslatedMessage(player, "commands.generic.unknown_command", label); return; } + // Get the command's annotation. + Command annotation = this.annotations.get(label); + // If any @UID argument is present, override targetPlayer with it. for (int i = 0; i < args.size(); i++) { String arg = args.get(i); @@ -188,11 +199,11 @@ public final class CommandMap { } } } - + // If there's still no targetPlayer at this point, use previously-set target if (targetPlayer == null) { - if (targetPlayerIds.containsKey(playerId)) { - targetPlayer = Grasscutter.getGameServer().getPlayerByUid(targetPlayerIds.get(playerId), true); // We check every time in case the target is deleted after being targeted + if (this.targetPlayerIds.containsKey(playerId)) { + targetPlayer = Grasscutter.getGameServer().getPlayerByUid(this.targetPlayerIds.get(playerId), true); // We check every time in case the target is deleted after being targeted if (targetPlayer == null) { CommandHandler.sendTranslatedMessage(player, "commands.execution.player_exist_error"); return; @@ -204,32 +215,36 @@ public final class CommandMap { } // Check for permissions. - if (!Grasscutter.getPermissionHandler().checkPermission(player, targetPlayer, this.annotations.get(label).permission(), this.annotations.get(label).permissionTargeted())) { + if (!Grasscutter.getPermissionHandler().checkPermission(player, targetPlayer, annotation.permission(), this.annotations.get(label).permissionTargeted())) { return; } // Check if command has unfulfilled constraints on targetPlayer - Command.TargetRequirement targetRequirement = this.annotations.get(label).targetRequirement(); + Command.TargetRequirement targetRequirement = annotation.targetRequirement(); if (targetRequirement != Command.TargetRequirement.NONE) { if (targetPlayer == null) { - CommandHandler.sendTranslatedMessage(player, "commands.execution.need_target"); + CommandHandler.sendTranslatedMessage(null, "commands.execution.need_target"); return; } + if ((targetRequirement == Command.TargetRequirement.ONLINE) && !targetPlayer.isOnline()) { CommandHandler.sendTranslatedMessage(player, "commands.execution.need_target_online"); return; } + if ((targetRequirement == Command.TargetRequirement.OFFLINE) && targetPlayer.isOnline()) { CommandHandler.sendTranslatedMessage(player, "commands.execution.need_target_offline"); return; } } + // Copy player and handler to final properties. + final Player targetPlayerF = targetPlayer; // Is there a better way to do this? + final CommandHandler handlerF = handler; // Is there a better way to do this? + // Invoke execute method for handler. - boolean threading = this.annotations.get(label).threading(); - final Player targetPlayerF = targetPlayer; // Is there a better way to do this? - Runnable runnable = () -> handler.execute(player, targetPlayerF, args); - if(threading) { + Runnable runnable = () -> handlerF.execute(player, targetPlayerF, args); + if (annotation.threading()) { new Thread(runnable).start(); } else { runnable.run(); @@ -242,10 +257,11 @@ public final class CommandMap { private void scan() { Reflections reflector = Grasscutter.reflector; Set> classes = reflector.getTypesAnnotatedWith(Command.class); + classes.forEach(annotated -> { try { Command cmdData = annotated.getAnnotation(Command.class); - Object object = annotated.newInstance(); + Object object = annotated.getDeclaredConstructor().newInstance(); if (object instanceof CommandHandler) this.registerCommand(cmdData.label(), (CommandHandler) object); else Grasscutter.getLogger().error("Class " + annotated.getName() + " is not a CommandHandler!"); From be88c155bd30134d83a677b86e38ee8e0c126aba Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Tue, 21 Jun 2022 23:16:41 -0400 Subject: [PATCH 04/11] Bump server version --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 630646f75..491d60ec8 100644 --- a/build.gradle +++ b/build.gradle @@ -43,7 +43,7 @@ sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 group = 'xyz.grasscutters' -version = '1.2.1-dev' +version = '1.2.2-dev' sourceCompatibility = 17 @@ -116,7 +116,7 @@ jar { from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } } - + duplicatesStrategy = DuplicatesStrategy.INCLUDE from('src/main/java') { From ce8344a545364226e634ded44ceea1bcd7ec99d1 Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Wed, 22 Jun 2022 00:04:45 -0400 Subject: [PATCH 05/11] Fix `filenames` issue --- .../java/emu/grasscutter/data/DataLoader.java | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/main/java/emu/grasscutter/data/DataLoader.java b/src/main/java/emu/grasscutter/data/DataLoader.java index 69a17491c..0fda415e5 100644 --- a/src/main/java/emu/grasscutter/data/DataLoader.java +++ b/src/main/java/emu/grasscutter/data/DataLoader.java @@ -6,11 +6,11 @@ import emu.grasscutter.tools.Tools; import emu.grasscutter.utils.FileUtils; import emu.grasscutter.utils.Utils; -import java.io.*; -import java.nio.file.FileSystems; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; import java.nio.file.Path; import java.util.List; -import java.util.regex.Pattern; import static emu.grasscutter.Configuration.DATA; @@ -18,10 +18,11 @@ public class DataLoader { /** * Load a data file by its name. If the file isn't found within the /data directory then it will fallback to the default within the jar resources - * @see #load(String, boolean) + * * @param resourcePath The path to the data file to be loaded. * @return InputStream of the data file. * @throws FileNotFoundException + * @see #load(String, boolean) */ public static InputStream load(String resourcePath) throws FileNotFoundException { return load(resourcePath, true); @@ -29,17 +30,18 @@ public class DataLoader { /** * Load a data file by its name. + * * @param resourcePath The path to the data file to be loaded. - * @param useFallback If the file does not exist in the /data directory, should it use the default file in the jar? + * @param useFallback If the file does not exist in the /data directory, should it use the default file in the jar? * @return InputStream of the data file. * @throws FileNotFoundException */ public static InputStream load(String resourcePath, boolean useFallback) throws FileNotFoundException { - if(Utils.fileExists(DATA(resourcePath))) { + if (Utils.fileExists(DATA(resourcePath))) { // Data is in the resource directory return new FileInputStream(DATA(resourcePath)); } else { - if(useFallback) { + if (useFallback) { return FileUtils.readResourceAsStream("/defaults/data/" + resourcePath); } } @@ -50,9 +52,9 @@ public class DataLoader { public static void CheckAllFiles() { try { List filenames = FileUtils.getPathsFromResource("/defaults/data/"); - + if (filenames == null) { - Grasscutter.getLogger().error("We were unable to locate your default data files."); + Grasscutter.getLogger().error("We were unable to locate your default data files."); return; } for (Path file : filenames) { @@ -76,12 +78,12 @@ public class DataLoader { String[] path = name.split("/"); String folder = ""; - for(int i = 0; i < (path.length - 1); i++) { + for (int i = 0; i < (path.length - 1); i++) { folder += path[i] + "/"; // Make sure the current folder exists String folderToCreate = Utils.toFilePath(DATA(folder)); - if(!Utils.fileExists(folderToCreate)) { + if (!Utils.fileExists(folderToCreate)) { Grasscutter.getLogger().info("Creating data folder '" + folder + "'"); Utils.createFolder(folderToCreate); } From 0157c72de87344036961bba2c5d5b857cd8d83d9 Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Wed, 22 Jun 2022 00:05:44 -0400 Subject: [PATCH 06/11] Fix `filenames` issue (pt. 2) --- src/main/java/emu/grasscutter/data/DataLoader.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/java/emu/grasscutter/data/DataLoader.java b/src/main/java/emu/grasscutter/data/DataLoader.java index 0fda415e5..d03d3a612 100644 --- a/src/main/java/emu/grasscutter/data/DataLoader.java +++ b/src/main/java/emu/grasscutter/data/DataLoader.java @@ -54,10 +54,8 @@ public class DataLoader { List filenames = FileUtils.getPathsFromResource("/defaults/data/"); if (filenames == null) { - Grasscutter.getLogger().error("We were unable to locate your default data files."); return; - } - - for (Path file : filenames) { + Grasscutter.getLogger().error("We were unable to locate your default data files."); + } else for (Path file : filenames) { String relativePath = String.valueOf(file).split("defaults[\\\\\\/]data[\\\\\\/]")[1]; CheckAndCopyData(relativePath); From 6058e2b254bb2a5de5897f33b57ef6a0f6538a52 Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Wed, 22 Jun 2022 00:09:08 -0400 Subject: [PATCH 07/11] Fix `Player` on this branch --- .../emu/grasscutter/game/player/Player.java | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index 15df38c18..196e19362 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -120,7 +120,7 @@ public class Player { @Transient private MessageHandler messageHandler; @Transient private AbilityManager abilityManager; @Transient private QuestManager questManager; - + @Transient private SotSManager sotsManager; @Transient private InsectCaptureManager insectCaptureManager; @@ -409,7 +409,7 @@ public class Player { public int getWorldLevel() { return this.getProperty(PlayerProperty.PROP_PLAYER_WORLD_LEVEL); } - + public void setWorldLevel(int level) { this.setProperty(PlayerProperty.PROP_PLAYER_WORLD_LEVEL, level); this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_WORLD_LEVEL)); @@ -432,7 +432,7 @@ public class Player { this.setProperty(PlayerProperty.PROP_PLAYER_SCOIN, mora); this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_SCOIN)); } - + public int getCrystals() { return this.getProperty(PlayerProperty.PROP_PLAYER_MCOIN); } @@ -507,11 +507,11 @@ public class Player { public TeamManager getTeamManager() { return this.teamManager; } - + public TowerManager getTowerManager() { return towerManager; } - + public TowerData getTowerData() { if(towerData==null){ // because of mistake, null may be saved as storage at some machine, this if can be removed in future @@ -519,7 +519,7 @@ public class Player { } return towerData; } - + public QuestManager getQuestManager() { return questManager; } @@ -588,7 +588,7 @@ public class Player { public MpSettingType getMpSetting() { return MpSettingType.MP_SETTING_TYPE_ENTER_AFTER_APPLY; // TEMP } - + public Queue getAttackResults() { return this.attackResults; } @@ -783,7 +783,7 @@ public class Player { remainCalendar.add(Calendar.DATE, moonCardDuration); Date theLastDay = remainCalendar.getTime(); Date now = DateHelper.onlyYearMonthDay(new Date()); - return (int) ((theLastDay.getTime() - now.getTime()) / (24 * 60 * 60 * 1000)); // By copilot + return (int) ((theLastDay.getTime() - now.getTime()) / (24 * 60 * 60 * 1000)); // By copilot } public void rechargeMoonCard() { @@ -980,7 +980,7 @@ public class Player { } public Mail getMail(int index) { return this.getMailHandler().getMailById(index); } - + public int getMailId(Mail message) { return this.getMailHandler().getMailIndex(message); } @@ -988,9 +988,9 @@ public class Player { public boolean replaceMailByIndex(int index, Mail message) { return this.getMailHandler().replaceMailByIndex(index, message); } - - public void interactWith(int gadgetEntityId, GadgetInteractReq req) { + + public void interactWith(int gadgetEntityId, GadgetInteractReq opType) { GameEntity entity = getScene().getEntityById(gadgetEntityId); if (entity == null) { return; @@ -1018,13 +1018,13 @@ public class Player { } } } else if (entity instanceof EntityGadget gadget) { - + if (gadget.getContent() == null) { return; } - - boolean shouldDelete = gadget.getContent().onInteract(this, req); - + + boolean shouldDelete = gadget.getContent().onInteract(this, opType); + if (shouldDelete) { entity.getScene().removeEntity(entity); } @@ -1168,7 +1168,7 @@ public class Player { } return showAvatarInfoList; } - + public PlayerWorldLocationInfoOuterClass.PlayerWorldLocationInfo getWorldPlayerLocationInfo() { return PlayerWorldLocationInfoOuterClass.PlayerWorldLocationInfo.newBuilder() .setSceneId(this.getSceneId()) @@ -1211,7 +1211,7 @@ public class Player { public BattlePassManager getBattlePassManager(){ return battlePassManager; } - + public void loadBattlePassManager() { if (this.battlePassManager != null) return; this.battlePassManager = DatabaseHelper.loadBattlePass(this); @@ -1301,7 +1301,7 @@ public class Player { public void save() { DatabaseHelper.savePlayer(this); } - + // Called from tokenrsp public void loadFromDatabase() { // Make sure these exist @@ -1319,7 +1319,7 @@ public class Player { } //Make sure towerManager's player is online player this.getTowerManager().setPlayer(this); - + // Load from db this.getAvatars().loadFromDatabase(); this.getInventory().loadFromDatabase(); @@ -1328,7 +1328,7 @@ public class Player { this.getFriendsList().loadFromDatabase(); this.getMailHandler().loadFromDatabase(); this.getQuestManager().loadFromDatabase(); - + this.loadBattlePassManager(); } @@ -1341,12 +1341,12 @@ public class Player { quest.finish(); } getQuestManager().addQuest(35101); - + this.setSceneId(3); this.getPos().set(GameConstants.START_POSITION); } */ - + // Create world World world = new World(this); world.addPlayer(this); @@ -1383,7 +1383,7 @@ public class Player { // First notify packets sent this.setHasSentAvatarDataNotify(true); - + // Set session state session.setState(SessionState.ACTIVE); @@ -1393,7 +1393,7 @@ public class Player { session.close(); return; } - + // register getServer().registerPlayer(this); getProfile().setPlayer(this); // Set online From 239d44c244b7bef11e64e42aa9e374d8dce924c4 Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Wed, 22 Jun 2022 00:09:31 -0400 Subject: [PATCH 08/11] Fix issue when exiting server when plugin manager hasn't loaded --- .../java/emu/grasscutter/Grasscutter.java | 623 +++++++++--------- 1 file changed, 313 insertions(+), 310 deletions(-) diff --git a/src/main/java/emu/grasscutter/Grasscutter.java b/src/main/java/emu/grasscutter/Grasscutter.java index e0868d60b..f8c4bb4ab 100644 --- a/src/main/java/emu/grasscutter/Grasscutter.java +++ b/src/main/java/emu/grasscutter/Grasscutter.java @@ -1,25 +1,34 @@ package emu.grasscutter; -import java.io.*; -import java.util.Calendar; - +import ch.qos.logback.classic.Logger; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import emu.grasscutter.auth.AuthenticationSystem; import emu.grasscutter.auth.DefaultAuthentication; import emu.grasscutter.command.CommandMap; import emu.grasscutter.command.DefaultPermissionHandler; import emu.grasscutter.command.PermissionHandler; import emu.grasscutter.game.dungeons.challenge.DungeonChallenge; +import emu.grasscutter.data.ResourceLoader; +import emu.grasscutter.database.DatabaseManager; import emu.grasscutter.game.managers.energy.EnergyManager; import emu.grasscutter.game.managers.stamina.StaminaManager; import emu.grasscutter.plugin.PluginManager; import emu.grasscutter.plugin.api.ServerHook; import emu.grasscutter.scripts.ScriptLoader; +import emu.grasscutter.server.game.GameServer; import emu.grasscutter.server.http.HttpServer; import emu.grasscutter.server.http.dispatch.DispatchHandler; -import emu.grasscutter.server.http.handlers.*; import emu.grasscutter.server.http.dispatch.RegionHandler; import emu.grasscutter.server.http.documentation.DocumentationServerHandler; +import emu.grasscutter.server.http.handlers.AnnouncementsHandler; +import emu.grasscutter.server.http.handlers.GachaHandler; +import emu.grasscutter.server.http.handlers.GenericHandler; +import emu.grasscutter.server.http.handlers.LogHandler; +import emu.grasscutter.tools.Tools; import emu.grasscutter.utils.ConfigContainer; +import emu.grasscutter.utils.Crypto; +import emu.grasscutter.utils.Language; import emu.grasscutter.utils.Utils; import org.jline.reader.EndOfFileException; import org.jline.reader.LineReader; @@ -30,349 +39,343 @@ import org.jline.terminal.TerminalBuilder; import org.reflections.Reflections; import org.slf4j.LoggerFactory; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; - -import ch.qos.logback.classic.Logger; -import emu.grasscutter.data.ResourceLoader; -import emu.grasscutter.database.DatabaseManager; -import emu.grasscutter.utils.Language; -import emu.grasscutter.server.game.GameServer; -import emu.grasscutter.tools.Tools; -import emu.grasscutter.utils.Crypto; - import javax.annotation.Nullable; +import java.io.*; +import java.util.Calendar; +import static emu.grasscutter.Configuration.DATA; +import static emu.grasscutter.Configuration.SERVER; 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 Language language; + private static final Logger log = (Logger) LoggerFactory.getLogger(Grasscutter.class); + private static LineReader consoleLineReader = null; - private static final Gson gson = new GsonBuilder().setPrettyPrinting().create(); - public static final File configFile = new File("./config.json"); + private static Language language; - private static int day; // Current day of week. + private static final Gson gson = new GsonBuilder().setPrettyPrinting().create(); + public static final File configFile = new File("./config.json"); - private static HttpServer httpServer; - private static GameServer gameServer; - private static PluginManager pluginManager; - private static AuthenticationSystem authenticationSystem; - private static PermissionHandler permissionHandler; + private static int day; // Current day of week. - public static final Reflections reflector = new Reflections("emu.grasscutter"); - public static ConfigContainer config; - - static { - // Declare logback configuration. - System.setProperty("logback.configurationFile", "src/main/resources/logback.xml"); + private static HttpServer httpServer; + private static GameServer gameServer; + private static PluginManager pluginManager; + private static AuthenticationSystem authenticationSystem; + private static PermissionHandler permissionHandler; - // Load server configuration. - Grasscutter.loadConfig(); - // Attempt to update configuration. - ConfigContainer.updateConfig(); + public static final Reflections reflector = new Reflections("emu.grasscutter"); + public static ConfigContainer config; - // Load translation files. - Grasscutter.loadLanguage(); + static { + // Declare logback configuration. + System.setProperty("logback.configurationFile", "src/main/resources/logback.xml"); - // Check server structure. - Utils.startupCheck(); - } + // Load server configuration. + Grasscutter.loadConfig(); + // Attempt to update configuration. + ConfigContainer.updateConfig(); - public static void main(String[] args) throws Exception { - Crypto.loadKeys(); // Load keys from buffers. - - // Parse arguments. - boolean exitEarly = false; - for (String arg : args) { - switch (arg.toLowerCase()) { - case "-handbook" -> { - Tools.createGmHandbook(); exitEarly = true; - } - case "-gachamap" -> { - Tools.createGachaMapping(DATA("gacha_mappings.js")); exitEarly = true; - } - case "-version" -> { - System.out.println("Grasscutter version: " + BuildConfig.VERSION + "-" + BuildConfig.GIT_HASH); exitEarly = true; - } - } - } - - // Exit early if argument sets it. - if(exitEarly) System.exit(0); - - // Initialize server. - Grasscutter.getLogger().info(translate("messages.status.starting")); - Grasscutter.getLogger().info(translate("messages.status.game_version", GameConstants.VERSION)); - Grasscutter.getLogger().info(translate("messages.status.version", BuildConfig.VERSION, BuildConfig.GIT_HASH)); - - // Load all resources. - Grasscutter.updateDayOfWeek(); - ResourceLoader.loadAll(); - ScriptLoader.init(); - EnergyManager.initialize(); - DungeonChallenge.initialize(); - - // Initialize database. - DatabaseManager.initialize(); - - // Initialize the default systems. - authenticationSystem = new DefaultAuthentication(); - permissionHandler = new DefaultPermissionHandler(); - - // Create server instances. - httpServer = new HttpServer(); - gameServer = new GameServer(); - // Create a server hook instance with both servers. - new ServerHook(gameServer, httpServer); - - // Create plugin manager instance. - pluginManager = new PluginManager(); - // Add HTTP routes after loading plugins. - httpServer.addRouter(HttpServer.UnhandledRequestRouter.class); - httpServer.addRouter(HttpServer.DefaultRequestRouter.class); - httpServer.addRouter(RegionHandler.class); - httpServer.addRouter(LogHandler.class); - httpServer.addRouter(GenericHandler.class); - httpServer.addRouter(AnnouncementsHandler.class); - httpServer.addRouter(DispatchHandler.class); - httpServer.addRouter(GachaHandler.class); - httpServer.addRouter(DocumentationServerHandler.class); - - // TODO: find a better place? - StaminaManager.initialize(); - - // Start servers. - var runMode = SERVER.runMode; - if (runMode == ServerRunMode.HYBRID) { - httpServer.start(); - gameServer.start(); - } else if (runMode == ServerRunMode.DISPATCH_ONLY) { - httpServer.start(); - } else if (runMode == ServerRunMode.GAME_ONLY) { - gameServer.start(); - } else { - 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(); - } + // Load translation files. + Grasscutter.loadLanguage(); - /** - * Server shutdown event. - */ - private static void onShutdown() { - // Disable all plugins. - pluginManager.disablePlugins(); - } + // Check server structure. + Utils.startupCheck(); + } - /* - * Methods for the language system component. - */ - - public static void loadLanguage() { - var locale = config.language.language; - language = Language.getLanguage(Utils.getLanguageCode(locale)); - } - - /* - * Methods for the configuration system component. - */ + public static void main(String[] args) throws Exception { + Crypto.loadKeys(); // Load keys from buffers. - /** - * Attempts to load the configuration from a file. - */ - public static void loadConfig() { - // Check if config.json exists. If not, we generate a new config. - if (!configFile.exists()) { - getLogger().info("config.json could not be found. Generating a default configuration ..."); - config = new ConfigContainer(); - Grasscutter.saveConfig(config); - return; - } + // Parse arguments. + boolean exitEarly = false; + for (String arg : args) { + switch (arg.toLowerCase()) { + case "-handbook" -> { + Tools.createGmHandbook(); + exitEarly = true; + } + case "-gachamap" -> { + Tools.createGachaMapping(DATA("gacha_mappings.js")); + exitEarly = true; + } + case "-version" -> { + System.out.println("Grasscutter version: " + BuildConfig.VERSION + "-" + BuildConfig.GIT_HASH); + exitEarly = true; + } + } + } - // If the file already exists, we attempt to load it. - try (FileReader file = new FileReader(configFile)) { - config = gson.fromJson(file, ConfigContainer.class); - } catch (Exception exception) { - getLogger().error("There was an error while trying to load the configuration from config.json. Please make sure that there are no syntax errors. If you want to start with a default configuration, delete your existing config.json."); - System.exit(1); - } - } + // Exit early if argument sets it. + if (exitEarly) System.exit(0); - /** - * 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.", e); - } - } + // Initialize server. + Grasscutter.getLogger().info(translate("messages.status.starting")); + Grasscutter.getLogger().info(translate("messages.status.game_version", GameConstants.VERSION)); + Grasscutter.getLogger().info(translate("messages.status.version", BuildConfig.VERSION, BuildConfig.GIT_HASH)); - /* - * Getters for the various server components. - */ - - public static ConfigContainer getConfig() { - return config; - } + // Load all resources. + Grasscutter.updateDayOfWeek(); + ResourceLoader.loadAll(); + ScriptLoader.init(); - public static Language getLanguage() { - return language; - } + // Initialize database. + DatabaseManager.initialize(); - public static void setLanguage(Language language) { + // Initialize the default systems. + authenticationSystem = new DefaultAuthentication(); + permissionHandler = new DefaultPermissionHandler(); + + // Create server instances. + httpServer = new HttpServer(); + gameServer = new GameServer(); + // Create a server hook instance with both servers. + new ServerHook(gameServer, httpServer); + + // Create plugin manager instance. + pluginManager = new PluginManager(); + // Add HTTP routes after loading plugins. + httpServer.addRouter(HttpServer.UnhandledRequestRouter.class); + httpServer.addRouter(HttpServer.DefaultRequestRouter.class); + httpServer.addRouter(RegionHandler.class); + httpServer.addRouter(LogHandler.class); + httpServer.addRouter(GenericHandler.class); + httpServer.addRouter(AnnouncementsHandler.class); + httpServer.addRouter(DispatchHandler.class); + httpServer.addRouter(GachaHandler.class); + httpServer.addRouter(DocumentationServerHandler.class); + + // Start servers. + var runMode = SERVER.runMode; + if (runMode == ServerRunMode.HYBRID) { + httpServer.start(); + gameServer.start(); + } else if (runMode == ServerRunMode.DISPATCH_ONLY) { + httpServer.start(); + } else if (runMode == ServerRunMode.GAME_ONLY) { + gameServer.start(); + } else { + 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. + */ + private static void onShutdown() { + // Disable all plugins. + if(pluginManager != null) + pluginManager.disablePlugins(); + } + + /* + * Methods for the language system component. + */ + + public static void loadLanguage() { + var locale = config.language.language; + language = Language.getLanguage(Utils.getLanguageCode(locale)); + } + + /* + * Methods for the configuration system component. + */ + + /** + * Attempts to load the configuration from a file. + */ + public static void loadConfig() { + // Check if config.json exists. If not, we generate a new config. + if (!configFile.exists()) { + getLogger().info("config.json could not be found. Generating a default configuration ..."); + config = new ConfigContainer(); + Grasscutter.saveConfig(config); + return; + } + + // If the file already exists, we attempt to load it. + try (FileReader file = new FileReader(configFile)) { + config = gson.fromJson(file, ConfigContainer.class); + } catch (Exception exception) { + getLogger().error("There was an error while trying to load the configuration from config.json. Please make sure that there are no syntax errors. If you want to start with a default configuration, delete your existing config.json."); + System.exit(1); + } + } + + /** + * 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.", e); + } + } + + /* + * Getters for the various server components. + */ + + public static ConfigContainer getConfig() { + return config; + } + + public static Language getLanguage() { + return language; + } + + public static void setLanguage(Language language) { Grasscutter.language = language; - } + } - public static Language getLanguage(String langCode) { + public static Language getLanguage(String langCode) { return Language.getLanguage(langCode); - } + } - public static Logger getLogger() { - return log; - } + public static Logger getLogger() { + return log; + } - public static LineReader getConsole() { - if (consoleLineReader == null) { - Terminal terminal = null; - try { - terminal = TerminalBuilder.builder().jna(true).build(); - } catch (Exception e) { - try { - // Fallback to a dumb jline terminal. - terminal = TerminalBuilder.builder().dumb(true).build(); - } catch (Exception ignored) { - // When dumb is true, build() never throws. - } - } - consoleLineReader = LineReaderBuilder.builder() - .terminal(terminal) - .build(); - } - return consoleLineReader; - } + public static LineReader getConsole() { + if (consoleLineReader == null) { + Terminal terminal = null; + try { + terminal = TerminalBuilder.builder().jna(true).build(); + } catch (Exception e) { + try { + // Fallback to a dumb jline terminal. + terminal = TerminalBuilder.builder().dumb(true).build(); + } catch (Exception ignored) { + // When dumb is true, build() never throws. + } + } + consoleLineReader = LineReaderBuilder.builder() + .terminal(terminal) + .build(); + } + return consoleLineReader; + } - public static Gson getGsonFactory() { - return gson; - } + public static Gson getGsonFactory() { + return gson; + } - public static HttpServer getHttpServer() { - return httpServer; - } + public static HttpServer getHttpServer() { + return httpServer; + } - public static GameServer getGameServer() { - return gameServer; - } + public static GameServer getGameServer() { + return gameServer; + } - public static PluginManager getPluginManager() { - return pluginManager; - } - - public static AuthenticationSystem getAuthenticationSystem() { - return authenticationSystem; - } + public static PluginManager getPluginManager() { + return pluginManager; + } - public static PermissionHandler getPermissionHandler() { - return permissionHandler; - } + public static AuthenticationSystem getAuthenticationSystem() { + return authenticationSystem; + } - public static int getCurrentDayOfWeek() { - return day; - } - - /* - * Utility methods. - */ - - public static void updateDayOfWeek() { - Calendar calendar = Calendar.getInstance(); - day = calendar.get(Calendar.DAY_OF_WEEK); - } + public static PermissionHandler getPermissionHandler() { + return permissionHandler; + } - public static void startConsole() { - // Console should not start in dispatch only mode. - if (SERVER.runMode == ServerRunMode.DISPATCH_ONLY) { - getLogger().info(translate("messages.dispatch.no_commands_error")); - return; - } + public static int getCurrentDayOfWeek() { + return day; + } - getLogger().info(translate("messages.status.done")); - String input = null; - boolean isLastInterrupted = false; - while (config.server.game.enableConsole) { - try { - input = consoleLineReader.readLine("> "); - } catch (UserInterruptException e) { - if (!isLastInterrupted) { - isLastInterrupted = true; - Grasscutter.getLogger().info("Press Ctrl-C again to shutdown."); - continue; - } else { - Runtime.getRuntime().exit(0); - } - } catch (EndOfFileException e) { - Grasscutter.getLogger().info("EOF detected."); - continue; - } catch (IOError e) { - Grasscutter.getLogger().error("An IO error occurred.", e); - continue; - } + /* + * Utility methods. + */ - isLastInterrupted = false; - try { - CommandMap.getInstance().invoke(null, null, input); - } catch (Exception e) { - Grasscutter.getLogger().error(translate("messages.game.command_error"), e); - } - } - } + public static void updateDayOfWeek() { + Calendar calendar = Calendar.getInstance(); + day = calendar.get(Calendar.DAY_OF_WEEK); + } - /** - * Sets the authentication system for the server. - * @param authenticationSystem The authentication system to use. - */ - public static void setAuthenticationSystem(AuthenticationSystem authenticationSystem) { - Grasscutter.authenticationSystem = authenticationSystem; - } + public static void startConsole() { + // Console should not start in dispatch only mode. + if (SERVER.runMode == ServerRunMode.DISPATCH_ONLY) { + getLogger().info(translate("messages.dispatch.no_commands_error")); + return; + } - /** - * Sets the permission handler for the server. - * @param permissionHandler The permission handler to use. - */ - public static void setPermissionHandler(PermissionHandler permissionHandler) { - Grasscutter.permissionHandler = permissionHandler; - } + getLogger().info(translate("messages.status.done")); + String input = null; + boolean isLastInterrupted = false; + while (config.server.game.enableConsole) { + try { + input = consoleLineReader.readLine("> "); + } catch (UserInterruptException e) { + if (!isLastInterrupted) { + isLastInterrupted = true; + Grasscutter.getLogger().info("Press Ctrl-C again to shutdown."); + continue; + } else { + Runtime.getRuntime().exit(0); + } + } catch (EndOfFileException e) { + Grasscutter.getLogger().info("EOF detected."); + continue; + } catch (IOError e) { + Grasscutter.getLogger().error("An IO error occurred.", e); + continue; + } - /* - * Enums for the configuration. - */ - - public enum ServerRunMode { - HYBRID, DISPATCH_ONLY, GAME_ONLY - } + isLastInterrupted = false; + try { + CommandMap.getInstance().invoke(null, null, input); + } catch (Exception e) { + Grasscutter.getLogger().error(translate("messages.game.command_error"), e); + } + } + } - public enum ServerDebugMode { - ALL, MISSING, NONE - } + /** + * Sets the authentication system for the server. + * + * @param authenticationSystem The authentication system to use. + */ + public static void setAuthenticationSystem(AuthenticationSystem authenticationSystem) { + Grasscutter.authenticationSystem = authenticationSystem; + } + + /** + * Sets the permission handler for the server. + * + * @param permissionHandler The permission handler to use. + */ + public static void setPermissionHandler(PermissionHandler permissionHandler) { + Grasscutter.permissionHandler = permissionHandler; + } + + /* + * Enums for the configuration. + */ + + public enum ServerRunMode { + HYBRID, DISPATCH_ONLY, GAME_ONLY + } + + public enum ServerDebugMode { + ALL, MISSING, NONE + } } From 640f3fee6cca38faa70ae39348ebde52cc6b8014 Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Wed, 22 Jun 2022 00:09:53 -0400 Subject: [PATCH 09/11] Update `EventHandler` with new register syntax --- src/main/java/emu/grasscutter/server/event/EventHandler.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/emu/grasscutter/server/event/EventHandler.java b/src/main/java/emu/grasscutter/server/event/EventHandler.java index 6611b758c..4bd1bf044 100644 --- a/src/main/java/emu/grasscutter/server/event/EventHandler.java +++ b/src/main/java/emu/grasscutter/server/event/EventHandler.java @@ -1,6 +1,7 @@ package emu.grasscutter.server.event; import emu.grasscutter.Grasscutter; +import emu.grasscutter.plugin.Plugin; import emu.grasscutter.utils.EventConsumer; public final class EventHandler { @@ -75,7 +76,7 @@ public final class EventHandler { /** * Registers the handler into the PluginManager. */ - public void register() { - Grasscutter.getPluginManager().registerListener(this); + public void register(Plugin plugin) { + Grasscutter.getPluginManager().registerListener(plugin, this); } } \ No newline at end of file From d511d51ca94cb36ee8b5ff7676497d1d02983ac4 Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Wed, 22 Jun 2022 00:10:56 -0400 Subject: [PATCH 10/11] Fix method of loading dependant plugins --- .../emu/grasscutter/plugin/PluginManager.java | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/main/java/emu/grasscutter/plugin/PluginManager.java b/src/main/java/emu/grasscutter/plugin/PluginManager.java index 8575d125e..4c7b0ac68 100644 --- a/src/main/java/emu/grasscutter/plugin/PluginManager.java +++ b/src/main/java/emu/grasscutter/plugin/PluginManager.java @@ -108,7 +108,7 @@ public final class PluginManager { fileReader.close(); // Check if the plugin has alternate dependencies. - if(pluginConfig.loadAfter.length > 0) { + if(pluginConfig.loadAfter != null && pluginConfig.loadAfter.length > 0) { // Add the plugin to a "load later" list. dependencies.add(new PluginData( pluginInstance, PluginIdentifier.fromPluginConfig(pluginConfig), @@ -130,20 +130,30 @@ public final class PluginManager { // Load plugins with dependencies. int depth = 0; final int maxDepth = 30; - while(!dependencies.isEmpty() || depth < maxDepth) { + while(!dependencies.isEmpty()) { + // Check if the depth is too high. + if(depth >= maxDepth) { + Grasscutter.getLogger().error("Failed to load plugins with dependencies."); + break; + } + try { // Get the next plugin to load. var pluginData = dependencies.get(0); + // Check if the plugin's dependencies are loaded. if(!this.plugins.keySet().containsAll(List.of(pluginData.getDependencies()))) { depth++; // Increase depth counter. continue; // Continue to next plugin. } + // Remove the plugin from the list of dependencies. + dependencies.remove(pluginData); + // Load the plugin. this.loadPlugin(pluginData.getPlugin(), pluginData.getIdentifier(), pluginData.getClassLoader()); } catch (Exception exception) { - Grasscutter.getLogger().error("Failed to load a plugin.", exception); + Grasscutter.getLogger().error("Failed to load a plugin.", exception); depth++; } } } @@ -175,7 +185,7 @@ public final class PluginManager { // Call the plugin's onLoad method. try { plugin.onLoad(); - } catch (Exception exception) { + } catch (Throwable exception) { Grasscutter.getLogger().error("Failed to load plugin: " + identifier.name, exception); } } @@ -188,7 +198,7 @@ public final class PluginManager { Grasscutter.getLogger().info("Enabling plugin: " + name); try { plugin.onEnable(); - } catch (Exception exception) { + } catch (Throwable exception) { Grasscutter.getLogger().error("Failed to enable plugin: " + name, exception); } }); @@ -202,7 +212,7 @@ public final class PluginManager { Grasscutter.getLogger().info("Disabling plugin: " + name); try { plugin.onDisable(); - } catch (Exception exception) { + } catch (Throwable exception) { Grasscutter.getLogger().error("Failed to disable plugin: " + name, exception); } }); From 8f617ddd2fd844ae7e4d0d0d7e841cff34929d37 Mon Sep 17 00:00:00 2001 From: Magix <27646710+KingRainbow44@users.noreply.github.com> Date: Wed, 22 Jun 2022 02:59:45 -0400 Subject: [PATCH 11/11] Revert deprecation of `registerCommand` i was going to make commands plugin specific but then decided against it --- src/main/java/emu/grasscutter/command/CommandMap.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/emu/grasscutter/command/CommandMap.java b/src/main/java/emu/grasscutter/command/CommandMap.java index bbda2b646..330c1402e 100644 --- a/src/main/java/emu/grasscutter/command/CommandMap.java +++ b/src/main/java/emu/grasscutter/command/CommandMap.java @@ -33,7 +33,6 @@ public final class CommandMap { * @param command The command handler. * @return Instance chaining. */ - @Deprecated(since = "1.2.1-dev") public CommandMap registerCommand(String label, CommandHandler command) { Grasscutter.getLogger().debug("Registered command: " + label);