From f331afe3397f3e560da2bf6102fc2ee5d9e3abe6 Mon Sep 17 00:00:00 2001 From: Benj Date: Wed, 22 Jun 2022 09:24:11 +0800 Subject: [PATCH 01/17] Refix Javadoc and readd getPlugin comments --- .../emu/grasscutter/auth/ExternalAuthenticator.java | 12 ++++++------ .../java/emu/grasscutter/plugin/PluginManager.java | 5 +++++ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/main/java/emu/grasscutter/auth/ExternalAuthenticator.java b/src/main/java/emu/grasscutter/auth/ExternalAuthenticator.java index 6bf78af6e..16c3f5b9e 100644 --- a/src/main/java/emu/grasscutter/auth/ExternalAuthenticator.java +++ b/src/main/java/emu/grasscutter/auth/ExternalAuthenticator.java @@ -16,18 +16,18 @@ public interface ExternalAuthenticator { /** * Called when an external account creation request is made. * @param request The authentication request. - * - * For developers: Use {@link AuthenticationRequest#getRequest()} to get the request body. - * Use {@link AuthenticationRequest#getResponse()} to get the response body. + * + * For developers: Use AuthenticationRequest#getRequest() to get the request body. + * Use AuthenticationRequest#getResponse() to get the response body. */ void handleAccountCreation(AuthenticationRequest request); /** * Called when an external password reset request is made. * @param request The authentication request. - * - * For developers: Use {@link AuthenticationRequest#getRequest()} to get the request body. - * Use {@link AuthenticationRequest#getResponse()} to get the response body. + * + * For developers: Use AuthenticationRequest#getRequest() to get the request body. + * Use AuthenticationRequest#getResponse() to get the response body. */ void handlePasswordReset(AuthenticationRequest request); } diff --git a/src/main/java/emu/grasscutter/plugin/PluginManager.java b/src/main/java/emu/grasscutter/plugin/PluginManager.java index f6f1cfbf7..756d32a0f 100644 --- a/src/main/java/emu/grasscutter/plugin/PluginManager.java +++ b/src/main/java/emu/grasscutter/plugin/PluginManager.java @@ -171,6 +171,11 @@ public final class PluginManager { .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. + */ public Plugin getPlugin(String name) { return this.plugins.get(name); } From e2cfe94bf03790c4bbd3c832d71f412a7498ba24 Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Tue, 21 Jun 2022 19:06:14 -0400 Subject: [PATCH 02/17] 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 82eefde4c01415f36db80ed7e167698ba1b785a1 Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Tue, 21 Jun 2022 23:15:52 -0400 Subject: [PATCH 03/17] 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 07a97f65f6d273dbcd23ace5fd31266d21b563ea Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Tue, 21 Jun 2022 23:16:19 -0400 Subject: [PATCH 04/17] 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 aa43943025fefcda9739c9fcf242e67f1a7b83b4 Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Tue, 21 Jun 2022 23:16:41 -0400 Subject: [PATCH 05/17] 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 74cbad261d5316414381bf5d898405d00fb73298 Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Wed, 22 Jun 2022 00:04:45 -0400 Subject: [PATCH 06/17] 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 eed59e0d77de5b4e0380223064336aa4a34b018c Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Wed, 22 Jun 2022 00:05:44 -0400 Subject: [PATCH 07/17] 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 91d232d6258a20556f423631d2fd0707fd9e1bd8 Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Wed, 22 Jun 2022 00:09:08 -0400 Subject: [PATCH 08/17] 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 6175e957b5f0a085e8088de6b95e6ede4d35c02d Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Wed, 22 Jun 2022 00:09:31 -0400 Subject: [PATCH 09/17] 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 5a8b76d97b384eab12e837c1d60e90cdac693261 Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Wed, 22 Jun 2022 00:09:53 -0400 Subject: [PATCH 10/17] 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 3c361b432fffccc5e758726971a4172464106a14 Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Wed, 22 Jun 2022 00:10:56 -0400 Subject: [PATCH 11/17] 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 1b58ba254e885b01a83fe85d3993d44e63009b3a 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 12/17] 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); From b5c6b4795eaa58b07528460737c5084d9f7d1b15 Mon Sep 17 00:00:00 2001 From: Benj Date: Wed, 22 Jun 2022 09:24:43 +0800 Subject: [PATCH 13/17] Set up publishing to 4benj-maven for dev builds --- build.gradle | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/build.gradle b/build.gradle index 630646f75..32a3948c7 100644 --- a/build.gradle +++ b/build.gradle @@ -116,7 +116,7 @@ jar { from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } } - + duplicatesStrategy = DuplicatesStrategy.INCLUDE from('src/main/java') { @@ -171,13 +171,23 @@ publishing { } repositories { maven { - // change URLs to point to your repos, e.g. http://my.org/repo - def releasesRepoUrl = 'https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/' - def snapshotsRepoUrl = 'https://s01.oss.sonatype.org/content/repositories/snapshots/' - url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl + if(version.endsWith('-dev')) { + println ("Publishing to 4benj-maven") + url 'https://repo.4benj.com/releases' + name '4benj-maven' + credentials { + username System.getenv('benj_maven_username') + password System.getenv('benj_maven_token') + } + } else { + println ("Publishing to sonatype") + def releasesRepoUrl = 'https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/' + def snapshotsRepoUrl = 'https://s01.oss.sonatype.org/content/repositories/snapshots/' + url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl - name = 'sonatype' - credentials(PasswordCredentials) + name = 'sonatype' + credentials(PasswordCredentials) + } } } } @@ -225,7 +235,9 @@ eclipse { } signing { - sign publishing.publications.mavenJava + if(!version.endsWith('-dev')) { + sign publishing.publications.mavenJava + } } javadoc { From f4ba460de1eb659f536fcf21970ea720e593aab1 Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Thu, 23 Jun 2022 00:28:13 -0400 Subject: [PATCH 14/17] Fix `PlayerCreationEvent` to match expected logic --- .../grasscutter/database/DatabaseHelper.java | 43 +++++++++++-------- .../packet/recv/HandlerGetPlayerTokenReq.java | 25 ++++++----- 2 files changed, 36 insertions(+), 32 deletions(-) diff --git a/src/main/java/emu/grasscutter/database/DatabaseHelper.java b/src/main/java/emu/grasscutter/database/DatabaseHelper.java index cb0731afb..2696860c9 100644 --- a/src/main/java/emu/grasscutter/database/DatabaseHelper.java +++ b/src/main/java/emu/grasscutter/database/DatabaseHelper.java @@ -39,11 +39,11 @@ public final class DatabaseHelper { if (reservedUid == GameConstants.SERVER_CONSOLE_UID) { return null; } - + if (DatabaseHelper.checkIfAccountExists(reservedUid)) { return null; } - + // Make sure no existing player already has this id. if (DatabaseHelper.checkIfPlayerExists(reservedUid)) { return null; @@ -105,11 +105,11 @@ public final class DatabaseHelper { public static Account getAccountByPlayerId(int playerId) { return DatabaseManager.getAccountDatastore().find(Account.class).filter(Filters.eq("reservedPlayerId", playerId)).first(); } - + public static boolean checkIfAccountExists(String name) { return DatabaseManager.getAccountDatastore().find(Account.class).filter(Filters.eq("username", name)).count() > 0; } - + public static boolean checkIfAccountExists(int reservedUid) { return DatabaseManager.getAccountDatastore().find(Account.class).filter(Filters.eq("reservedPlayerId", reservedUid)).count() > 0; } @@ -120,11 +120,11 @@ public final class DatabaseHelper { // database in an inconsistent state, but unfortunately Mongo only supports that when we have a replica set ... Player player = Grasscutter.getGameServer().getPlayerByAccountId(target.getId()); - + if (player != null) { // Close session first player.getSession().close(); - + // Delete data from collections DatabaseManager.getGameDatabase().getCollection("mail").deleteMany(eq("ownerUid", player.getUid())); DatabaseManager.getGameDatabase().getCollection("avatars").deleteMany(eq("ownerId", player.getUid())); @@ -153,11 +153,16 @@ public final class DatabaseHelper { public static Player getPlayerByUid(int id) { return DatabaseManager.getGameDatastore().find(Player.class).filter(Filters.eq("_id", id)).first(); } - + + @Deprecated public static Player getPlayerByAccount(Account account) { return DatabaseManager.getGameDatastore().find(Player.class).filter(Filters.eq("accountId", account.getId())).first(); } - + + public static Player getPlayerByAccount(Account account, Class playerClass) { + return DatabaseManager.getGameDatastore().find(playerClass).filter(Filters.eq("accountId", account.getId())).first(); + } + public static boolean checkIfPlayerExists(int uid) { return DatabaseManager.getGameDatastore().find(Player.class).filter(Filters.eq("_id", uid)).count() > 0; } @@ -218,7 +223,7 @@ public final class DatabaseHelper { public static List getInventoryItems(Player player) { return DatabaseManager.getGameDatastore().find(GameItem.class).filter(Filters.eq("ownerId", player.getUid())).stream().toList(); } - + public static List getFriends(Player player) { return DatabaseManager.getGameDatastore().find(Friendship.class).filter(Filters.eq("ownerId", player.getUid())).stream().toList(); } @@ -272,40 +277,40 @@ public final class DatabaseHelper { public static void saveGachaRecord(GachaRecord gachaRecord){ DatabaseManager.getGameDatastore().save(gachaRecord); } - + public static List getAllMail(Player player) { return DatabaseManager.getGameDatastore().find(Mail.class).filter(Filters.eq("ownerUid", player.getUid())).stream().toList(); } - + public static void saveMail(Mail mail) { DatabaseManager.getGameDatastore().save(mail); } - + public static boolean deleteMail(Mail mail) { DeleteResult result = DatabaseManager.getGameDatastore().delete(mail); return result.wasAcknowledged(); } - + public static List getAllQuests(Player player) { return DatabaseManager.getGameDatastore().find(GameMainQuest.class).filter(Filters.eq("ownerUid", player.getUid())).stream().toList(); } - + public static void saveQuest(GameMainQuest quest) { DatabaseManager.getGameDatastore().save(quest); } - + public static boolean deleteQuest(GameMainQuest quest) { return DatabaseManager.getGameDatastore().delete(quest).wasAcknowledged(); } - + public static GameHome getHomeByUid(int id) { return DatabaseManager.getGameDatastore().find(GameHome.class).filter(Filters.eq("ownerUid", id)).first(); } - + public static void saveHome(GameHome gameHome) { DatabaseManager.getGameDatastore().save(gameHome); } - + public static BattlePassManager loadBattlePass(Player player) { BattlePassManager manager = DatabaseManager.getGameDatastore().find(BattlePassManager.class).filter(Filters.eq("ownerUid", player.getUid())).first(); if (manager == null) { @@ -316,7 +321,7 @@ public final class DatabaseHelper { } return manager; } - + public static void saveBattlePass(BattlePassManager manager) { DatabaseManager.getGameDatastore().save(manager); } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetPlayerTokenReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetPlayerTokenReq.java index 464d7a277..68ac9af9c 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetPlayerTokenReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetPlayerTokenReq.java @@ -11,24 +11,23 @@ import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.GetPlayerTokenReqOuterClass.GetPlayerTokenReq; import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.server.event.game.PlayerCreationEvent; -import emu.grasscutter.server.game.GameServer; import emu.grasscutter.server.game.GameSession; import emu.grasscutter.server.game.GameSession.SessionState; import emu.grasscutter.server.packet.send.PacketGetPlayerTokenRsp; @Opcodes(PacketOpcodes.GetPlayerTokenReq) public class HandlerGetPlayerTokenReq extends PacketHandler { - + @Override public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { GetPlayerTokenReq req = GetPlayerTokenReq.parseFrom(payload); - + // Authenticate Account account = DatabaseHelper.getAccountById(req.getAccountUid()); if (account == null || !account.getToken().equals(req.getAccountToken())) { return; } - + // Set account session.setAccount(account); @@ -58,25 +57,25 @@ public class HandlerGetPlayerTokenReq extends PacketHandler { } } - // Get player - Player player = DatabaseHelper.getPlayerByAccount(account); + // Call creation event. + PlayerCreationEvent event = new PlayerCreationEvent(session, Player.class); event.call(); + + // Get player. + Player player = DatabaseHelper.getPlayerByAccount(account, event.getPlayerClass()); if (player == null) { int nextPlayerUid = DatabaseHelper.getNextPlayerId(session.getAccount().getReservedPlayerUid()); - - // Call creation event. - PlayerCreationEvent event = new PlayerCreationEvent(session, Player.class); event.call(); - + // Create player instance from event. player = event.getPlayerClass().getDeclaredConstructor(GameSession.class).newInstance(session); - + // Save to db DatabaseHelper.generatePlayerUid(player, nextPlayerUid); } // Set player object for session session.setPlayer(player); - + // Checks if the player is banned if (session.getAccount().isBanned()) { session.send(new PacketGetPlayerTokenRsp(session, 21, "FORBID_CHEATING_PLUGINS", session.getAccount().getBanEndTime())); @@ -86,7 +85,7 @@ public class HandlerGetPlayerTokenReq extends PacketHandler { // Load player from database player.loadFromDatabase(); - + // Set session state session.setUseSecretKey(true); session.setState(SessionState.WAITING_FOR_LOGIN); From 1c6c581399dc565401151d9216a035dce6cfac51 Mon Sep 17 00:00:00 2001 From: Akka <104902222+Akka0@users.noreply.github.com> Date: Thu, 23 Jun 2022 13:12:58 +0800 Subject: [PATCH 15/17] fix gadget infinity create --- .../grasscutter/scripts/SceneScriptManager.java | 10 ++++++++++ .../emu/grasscutter/scripts/data/SceneGadget.java | 5 +++++ .../scripts/serializer/LuaSerializer.java | 15 ++++++++------- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java index 4df5a84fd..0c0012dc8 100644 --- a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java +++ b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java @@ -351,6 +351,16 @@ public class SceneScriptManager { } public EntityGadget createGadget(int groupId, int blockId, SceneGadget g) { + if(g.isOneoff){ + var hasEntity = getScene().getEntities().values().stream() + .filter(e -> e instanceof EntityGadget) + .filter(e -> e.getGroupId() == g.group.id) + .filter(e -> e.getConfigId() == g.config_id) + .findFirst(); + if(hasEntity.isPresent()){ + return null; + } + } EntityGadget entity = new EntityGadget(getScene(), g.gadget_id, g.pos); if (entity.getGadgetData() == null){ diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneGadget.java b/src/main/java/emu/grasscutter/scripts/data/SceneGadget.java index 3b9f2bf7c..afbe7d0d5 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneGadget.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneGadget.java @@ -11,4 +11,9 @@ public class SceneGadget extends SceneObject{ public int point_type; public SceneBossChest boss_chest; public int interact_id; + public boolean isOneoff; + + public void setIsOneoff(boolean isOneoff){ + this.isOneoff = isOneoff; + } } diff --git a/src/main/java/emu/grasscutter/scripts/serializer/LuaSerializer.java b/src/main/java/emu/grasscutter/scripts/serializer/LuaSerializer.java index 8e668c48a..6a08bfd04 100644 --- a/src/main/java/emu/grasscutter/scripts/serializer/LuaSerializer.java +++ b/src/main/java/emu/grasscutter/scripts/serializer/LuaSerializer.java @@ -1,8 +1,7 @@ package emu.grasscutter.scripts.serializer; - import com.esotericsoftware.reflectasm.ConstructorAccess; +import com.esotericsoftware.reflectasm.ConstructorAccess; import com.esotericsoftware.reflectasm.MethodAccess; - import emu.grasscutter.Grasscutter; import emu.grasscutter.scripts.ScriptUtils; import lombok.AccessLevel; @@ -12,8 +11,6 @@ import lombok.experimental.FieldDefaults; import org.luaj.vm2.LuaTable; import org.luaj.vm2.LuaValue; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; import java.util.*; import java.util.concurrent.ConcurrentHashMap; @@ -56,10 +53,12 @@ public class LuaSerializer implements Serializer { object = (T) (Float) keyValue.tofloat(); // terrible... } else if (keyValue.isstring()) { object = (T) keyValue.tojstring(); - } else { + } else if (keyValue.isboolean()) { + object = (T) (Boolean) keyValue.toboolean(); + } else { object = (T) keyValue; } - + if (object != null) { list.add(object); } @@ -118,7 +117,9 @@ public class LuaSerializer implements Serializer { methodAccess.invoke(object, fieldMeta.index, keyValue.toint()); } else if (fieldMeta.getType().equals(String.class)) { methodAccess.invoke(object, fieldMeta.index, keyValue.tojstring()); - } else { + } else if (fieldMeta.getType().equals(boolean.class)) { + methodAccess.invoke(object, fieldMeta.index, keyValue.toboolean()); + } else { methodAccess.invoke(object, fieldMeta.index, keyValue.tojstring()); } } catch (Exception ex) { From 67ac0d700de51241987c317fead1202824059146 Mon Sep 17 00:00:00 2001 From: Akka <104902222+Akka0@users.noreply.github.com> Date: Thu, 23 Jun 2022 15:59:17 +0800 Subject: [PATCH 16/17] add region entity --- .../grasscutter/game/entity/EntityRegion.java | 74 +++++++++++ .../grasscutter/game/props/EntityIdType.java | 5 +- .../emu/grasscutter/game/world/Scene.java | 45 +++---- .../scripts/SceneScriptManager.java | 124 +++++++++--------- .../emu/grasscutter/scripts/ScriptLib.java | 10 +- .../grasscutter/scripts/data/SceneGroup.java | 20 ++- .../grasscutter/scripts/data/SceneRegion.java | 54 ++------ .../grasscutter/scripts/data/SceneSuite.java | 7 +- 8 files changed, 194 insertions(+), 145 deletions(-) create mode 100644 src/main/java/emu/grasscutter/game/entity/EntityRegion.java diff --git a/src/main/java/emu/grasscutter/game/entity/EntityRegion.java b/src/main/java/emu/grasscutter/game/entity/EntityRegion.java new file mode 100644 index 000000000..5a09b7442 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/entity/EntityRegion.java @@ -0,0 +1,74 @@ +package emu.grasscutter.game.entity; + +import emu.grasscutter.game.props.EntityIdType; +import emu.grasscutter.game.world.Scene; +import emu.grasscutter.net.proto.SceneEntityInfoOuterClass; +import emu.grasscutter.scripts.data.SceneRegion; +import emu.grasscutter.utils.Position; +import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap; +import lombok.Getter; + +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +@Getter +public class EntityRegion extends GameEntity{ + private final Position position; + private boolean hasNewEntities; + private final Set entities; // Ids of entities inside this region + private final SceneRegion metaRegion; + + public EntityRegion(Scene scene, SceneRegion region) { + super(scene); + this.id = getScene().getWorld().getNextEntityId(EntityIdType.REGION); + setGroupId(region.group.id); + setBlockId(region.group.block_id); + setConfigId(region.config_id); + this.position = region.pos.clone(); + this.entities = ConcurrentHashMap.newKeySet(); + this.metaRegion = region; + } + + public void addEntity(GameEntity entity) { + if (this.getEntities().contains(entity.getId())) { + return; + } + this.getEntities().add(entity.getId()); + this.hasNewEntities = true; + } + + public boolean hasNewEntities() { + return hasNewEntities; + } + + public void resetNewEntities() { + hasNewEntities = false; + } + + public void removeEntity(GameEntity entity) { + this.getEntities().remove(entity.getId()); + } + + @Override + public Int2FloatOpenHashMap getFightProperties() { + return null; + } + + @Override + public Position getPosition() { + return position; + } + + @Override + public Position getRotation() { + return null; + } + + @Override + public SceneEntityInfoOuterClass.SceneEntityInfo toProto() { + /** + * The Region Entity would not be sent to client. + */ + return null; + } +} diff --git a/src/main/java/emu/grasscutter/game/props/EntityIdType.java b/src/main/java/emu/grasscutter/game/props/EntityIdType.java index 09414b65a..9ec6aee86 100644 --- a/src/main/java/emu/grasscutter/game/props/EntityIdType.java +++ b/src/main/java/emu/grasscutter/game/props/EntityIdType.java @@ -5,10 +5,11 @@ public enum EntityIdType { MONSTER (0x02), NPC (0x03), GADGET (0x04), - WEAPON (0x06), + REGION (0x05), + WEAPON (0x06), TEAM (0x09), MPLEVEL (0x0b); - + private final int id; private EntityIdType(int id) { diff --git a/src/main/java/emu/grasscutter/game/world/Scene.java b/src/main/java/emu/grasscutter/game/world/Scene.java index 407edd938..1c40fe35e 100644 --- a/src/main/java/emu/grasscutter/game/world/Scene.java +++ b/src/main/java/emu/grasscutter/game/world/Scene.java @@ -30,24 +30,25 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import org.danilopianini.util.SpatialIndex; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.stream.Collectors; public class Scene { private final World world; private final SceneData sceneData; private final List players; - private final Int2ObjectMap entities; - + private final Map entities; private final Set spawnedEntities; private final Set deadSpawnedEntities; private final Set loadedBlocks; private boolean dontDestroyWhenEmpty; - + private int autoCloseTime; private int time; private ClimateType climate; private int weather; - + private SceneScriptManager scriptManager; private WorldChallenge challenge; private List dungeonSettleListeners; @@ -57,19 +58,19 @@ public class Scene { public Scene(World world, SceneData sceneData) { this.world = world; this.sceneData = sceneData; - this.players = Collections.synchronizedList(new ArrayList<>()); - this.entities = Int2ObjectMaps.synchronize(new Int2ObjectOpenHashMap<>()); + this.players = new CopyOnWriteArrayList<>(); + this.entities = new ConcurrentHashMap<>(); this.time = 8 * 60; this.climate = ClimateType.CLIMATE_SUNNY; this.prevScene = 3; - - this.spawnedEntities = new HashSet<>(); - this.deadSpawnedEntities = new HashSet<>(); - this.loadedBlocks = new HashSet<>(); + + this.spawnedEntities = ConcurrentHashMap.newKeySet(); + this.deadSpawnedEntities = ConcurrentHashMap.newKeySet(); + this.loadedBlocks = ConcurrentHashMap.newKeySet(); this.scriptManager = new SceneScriptManager(this); } - + public int getId() { return sceneData.getId(); } @@ -89,15 +90,15 @@ public class Scene { public List getPlayers() { return players; } - + public int getPlayerCount() { return this.getPlayers().size(); } - public Int2ObjectMap getEntities() { + public Map getEntities() { return entities; } - + public GameEntity getEntityById(int id) { return this.entities.get(id); } @@ -629,15 +630,10 @@ public class Scene { var suiteData = group.getSuiteByIndex(suite); suiteData.sceneTriggers.forEach(getScriptManager()::registerTrigger); - entities.addAll(suiteData.sceneGadgets.stream() - .map(g -> scriptManager.createGadget(group.id, group.block_id, g)) - .filter(Objects::nonNull) - .toList()); - entities.addAll(suiteData.sceneMonsters.stream() - .map(mob -> scriptManager.createMonster(group.id, group.block_id, mob)) - .filter(Objects::nonNull) - .toList()); + entities.addAll(scriptManager.getGadgetsInGroupSuite(group, suiteData)); + entities.addAll(scriptManager.getMonstersInGroupSuite(group, suiteData)); + scriptManager.registerRegionInGroupSuite(group, suiteData); } scriptManager.meetEntities(entities); @@ -654,19 +650,18 @@ public class Scene { toRemove.forEach(this::removeEntityDirectly); this.broadcastPacket(new PacketSceneEntityDisappearNotify(toRemove, VisionType.VISION_TYPE_REMOVE)); } - + for (SceneGroup group : block.groups.values()) { if(group.triggers != null){ group.triggers.values().forEach(getScriptManager()::deregisterTrigger); } if(group.regions != null){ - group.regions.forEach(getScriptManager()::deregisterRegion); + group.regions.values().forEach(getScriptManager()::deregisterRegion); } } scriptManager.getLoadedGroupSetPerBlock().remove(block.id); Grasscutter.getLogger().info("Scene {} Block {} is unloaded.", this.getId(), block.id); } - // Gadgets public void onPlayerCreateGadget(EntityClientGadget gadget) { diff --git a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java index 0c0012dc8..da2323e5c 100644 --- a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java +++ b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java @@ -6,10 +6,7 @@ import emu.grasscutter.Grasscutter; import emu.grasscutter.data.GameData; import emu.grasscutter.data.excels.MonsterData; import emu.grasscutter.data.excels.WorldLevelData; -import emu.grasscutter.game.entity.EntityGadget; -import emu.grasscutter.game.entity.EntityMonster; -import emu.grasscutter.game.entity.EntityNPC; -import emu.grasscutter.game.entity.GameEntity; +import emu.grasscutter.game.entity.*; import emu.grasscutter.game.world.Scene; import emu.grasscutter.net.proto.VisionTypeOuterClass; import emu.grasscutter.scripts.constants.EventType; @@ -17,17 +14,12 @@ import emu.grasscutter.scripts.data.*; import emu.grasscutter.scripts.service.ScriptMonsterSpawnService; import emu.grasscutter.scripts.service.ScriptMonsterTideService; import io.netty.util.concurrent.FastThreadLocalThread; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import org.luaj.vm2.LuaError; import org.luaj.vm2.LuaValue; import org.luaj.vm2.lib.jse.CoerceJavaToLua; import java.util.*; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.LinkedBlockingDeque; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; import java.util.stream.Collectors; public class SceneScriptManager { @@ -38,15 +30,15 @@ public class SceneScriptManager { /** * current triggers controlled by RefreshGroup */ - private final Int2ObjectOpenHashMap> currentTriggers; - private final Int2ObjectOpenHashMap regions; - private Map sceneGroups; + private final Map> currentTriggers; + private final Map regions; // + private final Map sceneGroups; private ScriptMonsterTideService scriptMonsterTideService; private ScriptMonsterSpawnService scriptMonsterSpawnService; /** * blockid - loaded groupSet */ - private Int2ObjectMap> loadedGroupSetPerBlock; + private final Map> loadedGroupSetPerBlock; public static final ExecutorService eventExecutor; static { eventExecutor = new ThreadPoolExecutor(4, 4, @@ -55,23 +47,23 @@ public class SceneScriptManager { } public SceneScriptManager(Scene scene) { this.scene = scene; - this.currentTriggers = new Int2ObjectOpenHashMap<>(); + this.currentTriggers = new ConcurrentHashMap<>(); - this.regions = new Int2ObjectOpenHashMap<>(); - this.variables = new HashMap<>(); - this.sceneGroups = new HashMap<>(); + this.regions = new ConcurrentHashMap<>(); + this.variables = new ConcurrentHashMap<>(); + this.sceneGroups = new ConcurrentHashMap<>(); this.scriptMonsterSpawnService = new ScriptMonsterSpawnService(this); - this.loadedGroupSetPerBlock = new Int2ObjectOpenHashMap<>(); + this.loadedGroupSetPerBlock = new ConcurrentHashMap<>(); // TEMPORARY if (this.getScene().getId() < 10 && !Grasscutter.getConfig().server.game.enableScriptInBigWorld) { return; } - + // Create this.init(); } - + public Scene getScene() { return scene; } @@ -123,19 +115,25 @@ public class SceneScriptManager { spawnMonstersInGroup(group, suite); spawnGadgetsInGroup(group, suite); } - public SceneRegion getRegionById(int id) { + public EntityRegion getRegionById(int id) { return regions.get(id); } - - public void registerRegion(SceneRegion region) { - regions.put(region.config_id, region); + + public void registerRegion(EntityRegion region) { + regions.put(region.getId(), region); } - - public void deregisterRegion(SceneRegion region) { - regions.remove(region.config_id); + public void registerRegionInGroupSuite(SceneGroup group, SceneSuite suite){ + suite.sceneRegions.stream().map(region -> new EntityRegion(this.getScene(), region)) + .forEach(this::registerRegion); + } + public synchronized void deregisterRegion(SceneRegion region) { + var instance = regions.values().stream() + .filter(r -> r.getConfigId() == region.config_id) + .findFirst(); + instance.ifPresent(entityRegion -> regions.remove(entityRegion.getId())); } - public Int2ObjectMap> getLoadedGroupSetPerBlock() { + public Map> getLoadedGroupSetPerBlock() { return loadedGroupSetPerBlock; } @@ -182,53 +180,61 @@ public class SceneScriptManager { } this.sceneGroups.put(group.id, group); - - if(group.regions != null){ - group.regions.forEach(this::registerRegion); - } } - + public void checkRegions() { if (this.regions.size() == 0) { return; } - - for (SceneRegion region : this.regions.values()) { + + for (var region : this.regions.values()) { getScene().getEntities().values() .stream() - .filter(e -> e.getEntityType() <= 2 && region.contains(e.getPosition())) + .filter(e -> e.getEntityType() <= 2 && region.getMetaRegion().contains(e.getPosition())) .forEach(region::addEntity); if (region.hasNewEntities()) { - // This is not how it works, source_eid should be region entity id, but we dont have an entity for regions yet - callEvent(EventType.EVENT_ENTER_REGION, new ScriptArgs(region.config_id).setSourceEntityId(region.config_id)); - + callEvent(EventType.EVENT_ENTER_REGION, new ScriptArgs(region.getConfigId()).setSourceEntityId(region.getId())); + region.resetNewEntities(); } } } + public List getGadgetsInGroupSuite(SceneGroup group, SceneSuite suite){ + return suite.sceneGadgets.stream() + .map(g -> createGadget(group.id, group.block_id, g)) + .filter(Objects::nonNull) + .toList(); + } + public List getMonstersInGroupSuite(SceneGroup group, SceneSuite suite){ + return suite.sceneMonsters.stream() + .map(mob -> createMonster(group.id, group.block_id, mob)) + .filter(Objects::nonNull) + .toList(); + } public void addGroupSuite(SceneGroup group, SceneSuite suite){ - spawnMonstersInGroup(group, suite); - spawnGadgetsInGroup(group, suite); - registerTrigger(suite.sceneTriggers); + // we added trigger first + registerTrigger(suite.sceneTriggers); + + var toCreate = new ArrayList(); + toCreate.addAll(getGadgetsInGroupSuite(group, suite)); + toCreate.addAll(getMonstersInGroupSuite(group, suite)); + addEntities(toCreate); + + registerRegionInGroupSuite(group, suite); } public void removeGroupSuite(SceneGroup group, SceneSuite suite){ + deregisterTrigger(suite.sceneTriggers); removeMonstersInGroup(group, suite); removeGadgetsInGroup(group, suite); - deregisterTrigger(suite.sceneTriggers); + + suite.sceneRegions.forEach(this::deregisterRegion); } - public void spawnGadgetsInGroup(SceneGroup group, int suiteIndex) { - spawnGadgetsInGroup(group, group.getSuiteByIndex(suiteIndex)); - } - - public void spawnGadgetsInGroup(SceneGroup group) { - spawnGadgetsInGroup(group, null); - } - + public void spawnGadgetsInGroup(SceneGroup group, SceneSuite suite) { var gadgets = group.gadgets.values(); - + if (suite != null) { gadgets = suite.sceneGadgets; } @@ -240,13 +246,6 @@ public class SceneScriptManager { this.addEntities(toCreate); } - public void spawnMonstersInGroup(SceneGroup group, int suiteIndex) { - var suite = group.getSuiteByIndex(suiteIndex); - if(suite == null){ - return; - } - spawnMonstersInGroup(group, suite); - } public void spawnMonstersInGroup(SceneGroup group, SceneSuite suite) { if(suite == null || suite.sceneMonsters.size() <= 0){ return; @@ -254,11 +253,6 @@ public class SceneScriptManager { this.addEntities(suite.sceneMonsters.stream() .map(mob -> createMonster(group.id, group.block_id, mob)).toList()); } - - public void spawnMonstersInGroup(SceneGroup group) { - this.addEntities(group.monsters.values().stream() - .map(mob -> createMonster(group.id, group.block_id, mob)).toList()); - } public void startMonsterTideInGroup(SceneGroup group, Integer[] ordersConfigId, int tideCount, int sceneLimit) { this.scriptMonsterTideService = diff --git a/src/main/java/emu/grasscutter/scripts/ScriptLib.java b/src/main/java/emu/grasscutter/scripts/ScriptLib.java index 631250c90..494282299 100644 --- a/src/main/java/emu/grasscutter/scripts/ScriptLib.java +++ b/src/main/java/emu/grasscutter/scripts/ScriptLib.java @@ -309,22 +309,22 @@ public class ScriptLib { return 0; } - + public int GetRegionEntityCount(LuaTable table) { logger.debug("[LUA] Call GetRegionEntityCount with {}", printTable(table)); int regionId = table.get("region_eid").toint(); int entityType = table.get("entity_type").toint(); - SceneRegion region = this.getSceneScriptManager().getRegionById(regionId); - + var region = this.getSceneScriptManager().getRegionById(regionId); + if (region == null) { return 0; } - return (int) region.getEntities().intStream().filter(e -> e >> 24 == entityType).count(); + return (int) region.getEntities().stream().filter(e -> e >> 24 == entityType).count(); } - + public void PrintContextLog(String msg) { logger.info("[LUA] " + msg); } diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java b/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java index e670ddcb5..4455e78dd 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java @@ -34,10 +34,10 @@ public class SceneGroup { public Map gadgets; // public Map triggers; public Map npc; // - public List regions; + public Map regions; public List suites; public List variables; - + public SceneBusiness business; public SceneGarbage garbages; public SceneInitConfig init_config; @@ -115,9 +115,12 @@ public class SceneGroup { triggers.values().forEach(t -> t.currentGroup = this); suites = ScriptLoader.getSerializer().toList(SceneSuite.class, bindings.get("suites")); - regions = ScriptLoader.getSerializer().toList(SceneRegion.class, bindings.get("regions")); + regions = ScriptLoader.getSerializer().toList(SceneRegion.class, bindings.get("regions")).stream() + .collect(Collectors.toMap(x -> x.config_id, y -> y)); + regions.values().forEach(m -> m.group = this); + init_config = ScriptLoader.getSerializer().toObject(SceneInitConfig.class, bindings.get("init_config")); - + // Garbages TODO fix properly later Object garbagesValue = bindings.get("garbages"); if (garbagesValue != null && garbagesValue instanceof LuaValue garbagesTable) { @@ -157,12 +160,19 @@ public class SceneGroup { .map(triggers::get) .toList() ); + + suite.sceneRegions = new ArrayList<>( + suite.regions.stream() + .filter(regions::containsKey) + .map(regions::get) + .toList() + ); } } catch (ScriptException e) { Grasscutter.getLogger().error("Error loading group " + id + " in scene " + sceneId, e); } - + Grasscutter.getLogger().info("group {} in scene {} is loaded successfully.", id, sceneId); return this; } diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneRegion.java b/src/main/java/emu/grasscutter/scripts/data/SceneRegion.java index edc04be15..ecba125eb 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneRegion.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneRegion.java @@ -1,62 +1,34 @@ package emu.grasscutter.scripts.data; -import emu.grasscutter.game.entity.GameEntity; import emu.grasscutter.scripts.constants.ScriptRegionShape; import emu.grasscutter.utils.Position; -import it.unimi.dsi.fastutil.ints.IntOpenHashSet; -import it.unimi.dsi.fastutil.ints.IntSet; -import lombok.Data; import lombok.Setter; -import lombok.ToString; -@ToString + @Setter public class SceneRegion { public int config_id; public int shape; public Position pos; + // for CUBIC public Position size; - - private boolean hasNewEntities; - private final IntSet entities; // Ids of entities inside this region - - public SceneRegion() { - this.entities = new IntOpenHashSet(); - } - - public IntSet getEntities() { - return entities; - } + // for SPHERE + public int radius; - public void addEntity(GameEntity entity) { - if (this.getEntities().contains(entity.getId())) { - return; - } - this.getEntities().add(entity.getId()); - this.hasNewEntities = true; - } - - public void removeEntity(GameEntity entity) { - this.getEntities().remove(entity.getId()); - } - - public boolean contains(Position p) { + public transient SceneGroup group; + public boolean contains(Position position) { switch (shape) { case ScriptRegionShape.CUBIC: - return (Math.abs(pos.getX() - p.getX()) <= size.getX()) && - (Math.abs(pos.getZ() - p.getZ()) <= size.getZ()); + return (Math.abs(pos.getX() - position.getX()) <= size.getX()) && + (Math.abs(pos.getY() - position.getY()) <= size.getY()) && + (Math.abs(pos.getZ() - position.getZ()) <= size.getZ()); case ScriptRegionShape.SPHERE: - return false; + var x = Math.pow(pos.getX() - position.getX(), 2); + var y = Math.pow(pos.getY() - position.getY(), 2); + var z = Math.pow(pos.getZ() - position.getZ(), 2); + return x + y + z <= (radius ^ 2); } - return false; } - public boolean hasNewEntities() { - return hasNewEntities; - } - - public void resetNewEntities() { - hasNewEntities = false; - } } diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneSuite.java b/src/main/java/emu/grasscutter/scripts/data/SceneSuite.java index 7555601ca..6d5f62020 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneSuite.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneSuite.java @@ -11,9 +11,12 @@ public class SceneSuite { public List monsters; public List gadgets; public List triggers; - public int rand_weight; - + public List regions; + public int rand_weight; + public transient List sceneMonsters; public transient List sceneGadgets; public transient List sceneTriggers; + public transient List sceneRegions; + } From 74151c204c5e9484297c6e730e13d27c7b3afd66 Mon Sep 17 00:00:00 2001 From: "@Roly" <90933865+xiao-Roly@users.noreply.github.com> Date: Thu, 23 Jun 2022 18:46:25 +0800 Subject: [PATCH 17/17] Fix chest types --- .../resources/defaults/data/ChestReward.json | 290 +++++++++++++++++- 1 file changed, 289 insertions(+), 1 deletion(-) diff --git a/src/main/resources/defaults/data/ChestReward.json b/src/main/resources/defaults/data/ChestReward.json index a9c847b16..ee4b53eba 100644 --- a/src/main/resources/defaults/data/ChestReward.json +++ b/src/main/resources/defaults/data/ChestReward.json @@ -131,5 +131,293 @@ "count": 1 } ] + }, + { + "objNames": [ + "SceneObj_Chest_Default_Lv3", + "SceneObj_Chest_Locked_Lv3", + "SceneObj_Chest_Bramble_Lv3", + "SceneObj_Chest_Frozen_Lv3" + ], + "advExp": 200, + "resin": 20, + "mora": 8888, + "sigil": 20, + "content": [ + { + "itemId": 104012, + "count": 3 + }, + { + "itemId": 223, + "count": 10 + }, + { + "itemId": 104002, + "count": 1 + } + ], + "randomCount": 4, + "randomContent": [ + { + "itemId": 11509, + "count": 1 + }, + { + "itemId": 13501, + "count": 1 + }, + { + "itemId": 12201, + "count": 1 + }, + { + "itemId": 12301, + "count": 1 + }, + { + "itemId": 13201, + "count": 1 + }, + { + "itemId": 13301, + "count": 1 + }, + { + "itemId": 14201, + "count": 1 + }, + { + "itemId": 14301, + "count": 1 + }, + { + "itemId": 15201, + "count": 1 + }, + { + "itemId": 15301, + "count": 1 + } + ] + }, + { + "objNames": [ + "SceneObj_Chest_Default_Lv4", + "SceneObj_Chest_Locked_Lv4", + "SceneObj_Chest_Bramble_Lv4", + "SceneObj_Chest_Frozen_Lv4" + ], + "advExp": 20000, + "resin": 2, + "mora": 88888, + "sigil": 2, + "content": [ + { + "itemId": 104012, + "count": 3 + }, + { + "itemId": 223, + "count": 50 + }, + { + "itemId": 104002, + "count": 1 + } + ], + "randomCount": 4, + "randomContent": [ + { + "itemId": 13501, + "count": 1 + }, + { + "itemId": 11301, + "count": 1 + }, + { + "itemId": 12201, + "count": 1 + }, + { + "itemId": 12301, + "count": 1 + }, + { + "itemId": 13201, + "count": 1 + }, + { + "itemId": 13301, + "count": 1 + }, + { + "itemId": 14201, + "count": 1 + }, + { + "itemId": 14301, + "count": 1 + }, + { + "itemId": 15201, + "count": 1 + }, + { + "itemId": 15301, + "count": 1 + } + ] + }, + { + "objNames": [ + "SceneObj_Chest_Default_Lv5", + "SceneObj_Chest_Locked_Lv5", + "SceneObj_Chest_Bramble_Lv5", + "SceneObj_Chest_Frozen_Lv5" + ], + "advExp": 20000, + "resin": 2, + "mora": 88888, + "sigil": 2, + "content": [ + { + "itemId": 104012, + "count": 3 + }, + { + "itemId": 223, + "count": 100 + }, + { + "itemId": 104002, + "count": 1 + } + ], + "randomCount": 5, + "randomContent": [ + { + "itemId": 13501, + "count": 1 + }, + { + "itemId": 11301, + "count": 1 + }, + { + "itemId": 13509, + "count": 1 + }, + { + "itemId": 12301, + "count": 1 + }, + { + "itemId": 14509, + "count": 1 + }, + { + "itemId": 15507, + "count": 1 + }, + { + "itemId": 15509, + "count": 1 + }, + { + "itemId": 11509, + "count": 1 + }, + { + "itemId": 11503, + "count": 1 + }, + { + "itemId": 15301, + "count": 1 + } + ] + }, + { + "objNames": [ + "SceneObj_Area_Common_Property_Ani_Prop_MoonlitBox_01" + ], + "advExp": 20000, + "resin": 2, + "mora": 88888, + "sigil": 2, + "content": [ + { + "itemId": 104012, + "count": 3 + }, + { + "itemId": 223, + "count": 100 + }, + { + "itemId": 104002, + "count": 1 + } + ], + "randomCount": 5, + "randomContent": [ + { + "itemId": 13501, + "count": 1 + }, + { + "itemId": 11301, + "count": 1 + }, + { + "itemId": 13509, + "count": 1 + }, + { + "itemId": 12301, + "count": 1 + }, + { + "itemId": 14509, + "count": 1 + }, + { + "itemId": 15507, + "count": 1 + }, + { + "itemId": 15509, + "count": 1 + }, + { + "itemId": 11509, + "count": 1 + }, + { + "itemId": 11503, + "count": 1 + }, + { + "itemId": 15301, + "count": 1 + } + ] + }, + { + "objNames": [ + "SceneObj_Area_Dq_Property_Ani_Prop_JunkChest_01", + "SceneObj_Area_Common_Property_Ani_Prop_JunkChest_02", + "SearchPoint", + "SearchPoint_OnWater" + ], + "mora": 1, + "content": [ + { + "itemId": 201, + "count": 114 + } + ] } -] \ No newline at end of file +]