diff --git a/README_zh-CN.md b/README_zh-CN.md index 9ae17ca56..d41529fe8 100644 --- a/README_zh-CN.md +++ b/README_zh-CN.md @@ -30,11 +30,11 @@ * [MongoDB](https://www.mongodb.com/try/download/community) (推荐 4.0+) -* 代理: mitmproxy (推荐 mitmdump), Fiddler Classic 等 +* 代理程序: mitmproxy (推荐 mitmdump), Fiddler Classic 等 ### 运行 -**注意:** 从旧版本升级到新版本, 需要删除 `config.json` 文件 +**注意:** 从旧版本升级到新版本, 需要删除 `config.json` 1. 获取 `grasscutter.jar` - 从 [actions](https://github.com/Grasscutters/Grasscutter/suites/6895963598/artifacts/267483297) 下载 diff --git a/README_zh-TW.md b/README_zh-TW.md index 4f6d1f6ae..a5eb2ae3a 100644 --- a/README_zh-TW.md +++ b/README_zh-TW.md @@ -30,11 +30,11 @@ * [MongoDB](https://www.mongodb.com/try/download/community) (推薦 4.0+) -* 代理: mitmproxy (推薦 mitmdump), Fiddler Classic 等 +* 代理程式: mitmproxy (推薦 mitmdump), Fiddler Classic 等 ### 執行 -**注意:** 從舊版本升級到新版本, 需要刪除 `config.json` 檔案 +**注意:** 從舊版本升級到新版本, 需要刪除 `config.json` 1. 獲取 `grasscutter.jar` - 從 [actions](https://github.com/Grasscutters/Grasscutter/suites/6895963598/artifacts/267483297) 下載 diff --git a/build.gradle b/build.gradle index 32a3948c7..97e0d5056 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 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/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 + } } diff --git a/src/main/java/emu/grasscutter/command/CommandMap.java b/src/main/java/emu/grasscutter/command/CommandMap.java index 05c099bcb..b5159261a 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"; @@ -45,7 +44,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 +59,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 +70,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 +78,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 +127,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 +144,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,12 +153,12 @@ 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"); + CommandHandler.sendTranslatedMessage(player, "commands.generic.invalid.uid"); } } return; @@ -164,11 +166,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); @@ -183,16 +193,16 @@ public final class CommandMap { } break; } catch (NumberFormatException e) { - CommandHandler.sendTranslatedMessage(player, "commands.execution.uid_error"); + CommandHandler.sendTranslatedMessage(player, "commands.generic.invalid.uid"); return; } } } - + // 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 +214,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 +256,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!"); diff --git a/src/main/java/emu/grasscutter/command/commands/CoopCommand.java b/src/main/java/emu/grasscutter/command/commands/CoopCommand.java index c8961340e..86de192bc 100644 --- a/src/main/java/emu/grasscutter/command/commands/CoopCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/CoopCommand.java @@ -31,7 +31,7 @@ public final class CoopCommand implements CommandHandler { } break; } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, translate(sender, "commands.execution.uid_error")); + CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.uid")); return; } default: diff --git a/src/main/java/emu/grasscutter/command/commands/WeatherCommand.java b/src/main/java/emu/grasscutter/command/commands/WeatherCommand.java index 78a284968..c3dff4968 100644 --- a/src/main/java/emu/grasscutter/command/commands/WeatherCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/WeatherCommand.java @@ -1,47 +1,44 @@ package emu.grasscutter.command.commands; -import emu.grasscutter.Grasscutter; import emu.grasscutter.command.Command; import emu.grasscutter.command.CommandHandler; import emu.grasscutter.game.player.Player; import emu.grasscutter.game.props.ClimateType; -import emu.grasscutter.server.packet.send.PacketSceneAreaWeatherNotify; +import emu.grasscutter.game.world.Scene; import java.util.List; -import static emu.grasscutter.utils.Language.translate; - -@Command(label = "weather", usage = "weather ", aliases = {"w"}, permission = "player.weather", permissionTargeted = "player.weather.others", description = "commands.weather.description") +@Command(label = "weather", usage = "weather [weatherId] [climateType]", aliases = {"w"}, permission = "player.weather", permissionTargeted = "player.weather.others", description = "commands.weather.description") public final class WeatherCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { - int weatherId = 0; - int climateId = 1; - switch (args.size()) { - case 2: - try { - climateId = Integer.parseInt(args.get(1)); - } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, translate(sender, "commands.weather.invalid_id")); - } - case 1: - try { - weatherId = Integer.parseInt(args.get(0)); - } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, translate(sender, "commands.weather.invalid_id")); - } - break; - default: - CommandHandler.sendMessage(sender, translate(sender, "commands.weather.usage")); - return; + int weatherId = targetPlayer.getWeatherId(); + ClimateType climate = ClimateType.CLIMATE_NONE; // Sending ClimateType.CLIMATE_NONE to Scene.setWeather will use the default climate for that weather + + if (args.isEmpty()) { + climate = targetPlayer.getClimate(); + CommandHandler.sendTranslatedMessage(sender, "commands.weather.status", Integer.toString(weatherId), climate.getShortName()); + return; } - ClimateType climate = ClimateType.getTypeByValue(climateId); + for (String arg : args) { + ClimateType c = ClimateType.getTypeByShortName(arg.toLowerCase()); + if (c != ClimateType.CLIMATE_NONE) { + climate = c; + } else { + try { + weatherId = Integer.parseInt(arg); + } catch (NumberFormatException ignored) { + CommandHandler.sendTranslatedMessage(sender, "commands.generic.invalid.id"); + CommandHandler.sendTranslatedMessage(sender, "commands.weather.usage"); + return; + } + } + } - targetPlayer.getScene().setWeather(weatherId); - targetPlayer.getScene().setClimate(climate); - targetPlayer.getScene().broadcastPacket(new PacketSceneAreaWeatherNotify(targetPlayer)); - CommandHandler.sendMessage(sender, translate(sender, "commands.weather.success", Integer.toString(weatherId), Integer.toString(climateId))); + targetPlayer.setWeather(weatherId, climate); + climate = targetPlayer.getClimate(); // Might be different to what we set + CommandHandler.sendTranslatedMessage(sender, "commands.weather.success", Integer.toString(weatherId), climate.getShortName()); } } diff --git a/src/main/java/emu/grasscutter/data/DataLoader.java b/src/main/java/emu/grasscutter/data/DataLoader.java index 69a17491c..d03d3a612 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,12 +52,10 @@ 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."); - } - for (Path file : filenames) { + if (filenames == null) { + 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); @@ -76,12 +76,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); } diff --git a/src/main/java/emu/grasscutter/data/GameData.java b/src/main/java/emu/grasscutter/data/GameData.java index 4be0d464f..9ed0390cc 100644 --- a/src/main/java/emu/grasscutter/data/GameData.java +++ b/src/main/java/emu/grasscutter/data/GameData.java @@ -94,6 +94,7 @@ public class GameData { private static final Int2ObjectMap furnitureMakeConfigDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap investigationMonsterDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap cityDataMap = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap weatherDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap battlePassMissionExcelConfigDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap battlePassRewardExcelConfigDataMap = new Int2ObjectOpenHashMap<>(); @@ -414,10 +415,15 @@ public class GameData { public static Int2ObjectMap getInvestigationMonsterDataMap() { return investigationMonsterDataMap; } + public static Int2ObjectMap getCityDataMap() { return cityDataMap; } + public static Int2ObjectMap getWeatherDataMap() { + return weatherDataMap; + } + public static Int2ObjectMap getBattlePassMissionExcelConfigDataMap() { return battlePassMissionExcelConfigDataMap; } diff --git a/src/main/java/emu/grasscutter/data/excels/WeatherData.java b/src/main/java/emu/grasscutter/data/excels/WeatherData.java new file mode 100644 index 000000000..91538851b --- /dev/null +++ b/src/main/java/emu/grasscutter/data/excels/WeatherData.java @@ -0,0 +1,26 @@ +package emu.grasscutter.data.excels; + +import emu.grasscutter.data.GameResource; +import emu.grasscutter.data.ResourceType; +import emu.grasscutter.game.props.ClimateType; +import lombok.Getter; + +@ResourceType(name = "WeatherExcelConfigData.json") +public class WeatherData extends GameResource { + @Getter private int areaID; + @Getter private int weatherAreaId; + @Getter private String maxHeightStr; + @Getter private int gadgetID; + @Getter private boolean isDefaultValid; + @Getter private String templateName; + @Getter private int priority; + @Getter private String profileName; + @Getter private ClimateType defaultClimate; + @Getter private boolean isUseDefault; + @Getter private int sceneID; + + @Override + public int getId() { + return this.areaID; + } +} diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index 15df38c18..f52f6d6be 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -5,6 +5,7 @@ import emu.grasscutter.GameConstants; import emu.grasscutter.Grasscutter; import emu.grasscutter.data.GameData; import emu.grasscutter.data.excels.PlayerLevelData; +import emu.grasscutter.data.excels.WeatherData; import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.game.Account; import emu.grasscutter.game.CoopRequest; @@ -38,6 +39,7 @@ import emu.grasscutter.game.managers.mapmark.*; import emu.grasscutter.game.managers.stamina.StaminaManager; import emu.grasscutter.game.managers.SotSManager; import emu.grasscutter.game.props.ActionReason; +import emu.grasscutter.game.props.ClimateType; import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.game.props.SceneType; import emu.grasscutter.game.quest.QuestManager; @@ -71,6 +73,7 @@ import emu.grasscutter.utils.MessageHandler; import emu.grasscutter.utils.Utils; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import lombok.Getter; import java.util.*; import java.util.concurrent.LinkedBlockingQueue; @@ -112,6 +115,8 @@ public class Player { @Transient private int peerId; @Transient private World world; @Transient private Scene scene; + @Transient @Getter private int weatherId = 0; + @Transient @Getter private ClimateType climate = ClimateType.CLIMATE_SUNNY; @Transient private GameSession session; @Transient private AvatarStorage avatars; @Transient private Inventory inventory; @@ -120,7 +125,7 @@ public class Player { @Transient private MessageHandler messageHandler; @Transient private AbilityManager abilityManager; @Transient private QuestManager questManager; - + @Transient private SotSManager sotsManager; @Transient private InsectCaptureManager insectCaptureManager; @@ -140,8 +145,8 @@ public class Player { private int regionId; private int mainCharacterId; private boolean godmode; - private boolean stamina; + private boolean moonCard; private Date moonCardStartTime; private int moonCardDuration; @@ -324,6 +329,28 @@ public class Player { this.scene = scene; } + synchronized public void setClimate(ClimateType climate) { + this.climate = climate; + this.session.send(new PacketSceneAreaWeatherNotify(this)); + } + + synchronized public void setWeather(int weather) { + this.setWeather(weather, ClimateType.CLIMATE_NONE); + } + + synchronized public void setWeather(int weatherId, ClimateType climate) { + // Lookup default climate for this weather + if (climate == ClimateType.CLIMATE_NONE) { + WeatherData w = GameData.getWeatherDataMap().get(weatherId); + if (w != null) { + climate = w.getDefaultClimate(); + } + } + this.weatherId = weatherId; + this.climate = climate; + this.session.send(new PacketSceneAreaWeatherNotify(this)); + } + public int getGmLevel() { return 1; } @@ -402,6 +429,14 @@ public class Player { return this.getProperty(PlayerProperty.PROP_PLAYER_LEVEL); } + public void setLevel(int level) { + this.setProperty(PlayerProperty.PROP_PLAYER_LEVEL, level); + this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_LEVEL)); + + this.updateWorldLevel(); + this.updateProfile(); + } + public int getExp() { return this.getProperty(PlayerProperty.PROP_PLAYER_EXP); } @@ -409,10 +444,12 @@ 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)); + + this.updateProfile(); } public int getPrimogems() { @@ -432,7 +469,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); } @@ -481,12 +518,7 @@ public class Player { } if (hasLeveledUp) { - // Set level property - this.setProperty(PlayerProperty.PROP_PLAYER_LEVEL, level); - // Update social status - this.updateProfile(); - // Update player with packet - this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_LEVEL)); + this.setLevel(level); } // Set exp @@ -496,6 +528,27 @@ public class Player { this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_EXP)); } + private void updateWorldLevel() { + int currentWorldLevel = this.getWorldLevel(); + int currentLevel = this.getLevel(); + + int newWorldLevel = + (currentLevel >= 55) ? 8 : + (currentLevel >= 50) ? 7 : + (currentLevel >= 45) ? 6 : + (currentLevel >= 40) ? 5 : + (currentLevel >= 35) ? 4 : + (currentLevel >= 30) ? 3 : + (currentLevel >= 25) ? 2 : + (currentLevel >= 20) ? 1 : + 0; + + if (newWorldLevel != currentWorldLevel) { + this.getWorld().setWorldLevel(newWorldLevel); + this.setWorldLevel(newWorldLevel); + } + } + private void updateProfile() { getProfile().syncWithCharacter(this); } @@ -507,11 +560,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 +572,7 @@ public class Player { } return towerData; } - + public QuestManager getQuestManager() { return questManager; } @@ -588,7 +641,7 @@ public class Player { public MpSettingType getMpSetting() { return MpSettingType.MP_SETTING_TYPE_ENTER_AFTER_APPLY; // TEMP } - + public Queue getAttackResults() { return this.attackResults; } @@ -783,7 +836,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 +1033,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 +1041,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 +1071,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 +1221,7 @@ public class Player { } return showAvatarInfoList; } - + public PlayerWorldLocationInfoOuterClass.PlayerWorldLocationInfo getWorldPlayerLocationInfo() { return PlayerWorldLocationInfoOuterClass.PlayerWorldLocationInfo.newBuilder() .setSceneId(this.getSceneId()) @@ -1211,7 +1264,7 @@ public class Player { public BattlePassManager getBattlePassManager(){ return battlePassManager; } - + public void loadBattlePassManager() { if (this.battlePassManager != null) return; this.battlePassManager = DatabaseHelper.loadBattlePass(this); @@ -1301,7 +1354,7 @@ public class Player { public void save() { DatabaseHelper.savePlayer(this); } - + // Called from tokenrsp public void loadFromDatabase() { // Make sure these exist @@ -1319,7 +1372,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 +1381,7 @@ public class Player { this.getFriendsList().loadFromDatabase(); this.getMailHandler().loadFromDatabase(); this.getQuestManager().loadFromDatabase(); - + this.loadBattlePassManager(); } @@ -1341,12 +1394,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 +1436,7 @@ public class Player { // First notify packets sent this.setHasSentAvatarDataNotify(true); - + // Set session state session.setState(SessionState.ACTIVE); @@ -1393,7 +1446,7 @@ public class Player { session.close(); return; } - + // register getServer().registerPlayer(this); getProfile().setPlayer(this); // Set online diff --git a/src/main/java/emu/grasscutter/game/props/ClimateType.java b/src/main/java/emu/grasscutter/game/props/ClimateType.java index 433bf4a76..3ec52ed1c 100644 --- a/src/main/java/emu/grasscutter/game/props/ClimateType.java +++ b/src/main/java/emu/grasscutter/game/props/ClimateType.java @@ -32,7 +32,11 @@ public enum ClimateType { } public int getValue() { - return value; + return this.value; + } + + public String getShortName() { + return this.name().substring(8).toLowerCase(); } public static ClimateType getTypeByValue(int value) { @@ -42,4 +46,9 @@ public enum ClimateType { public static ClimateType getTypeByName(String name) { return stringMap.getOrDefault(name, CLIMATE_NONE); } + + public static ClimateType getTypeByShortName(String shortName) { + String name = "CLIMATE_" + shortName.toUpperCase(); + return stringMap.getOrDefault(name, CLIMATE_NONE); + } } diff --git a/src/main/java/emu/grasscutter/game/world/Scene.java b/src/main/java/emu/grasscutter/game/world/Scene.java index 1c40fe35e..f28568c07 100644 --- a/src/main/java/emu/grasscutter/game/world/Scene.java +++ b/src/main/java/emu/grasscutter/game/world/Scene.java @@ -46,8 +46,6 @@ public class Scene { private int autoCloseTime; private int time; - private ClimateType climate; - private int weather; private SceneScriptManager scriptManager; private WorldChallenge challenge; @@ -62,7 +60,6 @@ public class Scene { this.entities = new ConcurrentHashMap<>(); this.time = 8 * 60; - this.climate = ClimateType.CLIMATE_SUNNY; this.prevScene = 3; this.spawnedEntities = ConcurrentHashMap.newKeySet(); @@ -130,22 +127,6 @@ public class Scene { public void changeTime(int time) { this.time = time % 1440; } - - public ClimateType getClimate() { - return climate; - } - - public int getWeather() { - return weather; - } - - public void setClimate(ClimateType climate) { - this.climate = climate; - } - - public void setWeather(int weather) { - this.weather = weather; - } public int getPrevScene() { return prevScene; 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; } diff --git a/src/main/java/emu/grasscutter/plugin/PluginManager.java b/src/main/java/emu/grasscutter/plugin/PluginManager.java index 756d32a0f..4c7b0ac68 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 != null && 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,68 @@ 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()) { + // 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); depth++; + } + } } /** * 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 (Throwable exception) { + Grasscutter.getLogger().error("Failed to load plugin: " + identifier.name, exception); + } } /** @@ -128,67 +196,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 (Throwable 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 (Throwable 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); } } 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 diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketSceneAreaWeatherNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketSceneAreaWeatherNotify.java index 91112871d..b49df5fef 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketSceneAreaWeatherNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketSceneAreaWeatherNotify.java @@ -12,8 +12,8 @@ public class PacketSceneAreaWeatherNotify extends BasePacket { super(PacketOpcodes.SceneAreaWeatherNotify); SceneAreaWeatherNotify proto = SceneAreaWeatherNotify.newBuilder() - .setWeatherAreaId(player.getScene().getWeather()) - .setClimateType(player.getScene().getClimate().getValue()) + .setWeatherAreaId(player.getWeatherId()) + .setClimateType(player.getClimate().getValue()) .build(); this.setData(proto); diff --git a/src/main/resources/languages/en-US.json b/src/main/resources/languages/en-US.json index ef3dd1628..a5a50ec8e 100644 --- a/src/main/resources/languages/en-US.json +++ b/src/main/resources/languages/en-US.json @@ -76,16 +76,14 @@ "itemLevel": "Invalid itemLevel.", "itemRefinement": "Invalid itemRefinement.", "playerId": "Invalid player ID.", - "uid": "Invalid UID." + "uid": "Invalid UID.", + "id": "Invalid ID." } }, "execution": { - "uid_error": "Invalid UID.", "player_exist_error": "Player not found.", "player_offline_error": "Player is not online.", - "item_id_error": "Invalid item ID.", "item_player_exist_error": "Invalid item or UID.", - "entity_id_error": "Invalid entity ID.", "player_exist_offline_error": "Player not found or is not online.", "argument_error": "Invalid arguments.", "clear_target": "Target cleared.", @@ -99,17 +97,16 @@ "status": { "enabled": "Enabled", "disabled": "Disabled", - "help": "Help", + "help": "Help", "success": "Success" }, "account": { - "modify": "Modify user accounts", + "command_usage": "Usage: account [UID]", "invalid": "Invalid UID.", "exists": "An account with this username and/or UID already exists.", "create": "Account created with UID %s.", "delete": "Account deleted.", "no_account": "Account not found.", - "command_usage": "Usage: account [UID]", "description": "Modify user accounts" }, "broadcast": { @@ -387,25 +384,25 @@ "description": "Unlock all levels of tower" }, "weather": { - "usage": "Usage: weather \nWeather types 0: None, 1: Sunny, 2: Cloudy, 3: Rain, 4: Thunderstorm, 5: Snow, 6: Mist", - "success": "Changed climate type to %s with weather type %s.", - "invalid_id": "Invalid ID.", - "description": "Changes the weather" + "usage": "Usage: weather [weatherId] [climateType]\nWeather IDs can be found in WeatherExcelConfigData.json.\nClimate types: sunny, cloudy, rain, thunderstorm, snow, mist", + "success": "Set weather ID to %s with climate type %s.", + "status": "Current weather ID is %s with climate type %s.", + "description": "Changes weather ID and climate type. Weather IDs can be found in WeatherExcelConfigData.json.\nClimate types: sunny, cloudy, rain, thunderstorm, snow, mist" }, "ban": { - "description": "Ban a player", + "command_usage": "Usage: ban [timestamp] [reason]", "success": "Successful.", "failure": "Failed, player not found.", "invalid_time": "Unable to parse timestamp.", "invalid_player_id": "Unable to parse player ID.", - "command_usage": "Usage: ban [timestamp] [reason]" + "description": "Ban a player" }, "unban": { - "description": "Unban a player", + "command_usage": "Usage: unban ", "success": "Successful.", "failure": "Failed, player not found.", "invalid_player_id": "Unable to parse player ID.", - "command_usage": "Usage: unban " + "description": "Unban a player" } }, "gacha": { diff --git a/src/main/resources/languages/fr-FR.json b/src/main/resources/languages/fr-FR.json index ead31cd3b..955bbe1b4 100644 --- a/src/main/resources/languages/fr-FR.json +++ b/src/main/resources/languages/fr-FR.json @@ -76,16 +76,14 @@ "itemLevel": "Niveau de l'objet invalide.", "itemRefinement": "Raffinement de l'objet invalide.", "playerId": "ID du joueur invalide.", - "uid": "UID invalide." + "uid": "UID invalide.", + "id": "ID invalide." } }, "execution": { - "uid_error": "UID invalide.", "player_exist_error": "Joueur introuvable.", "player_offline_error": "Le joueur n'est pas connecté.", - "item_id_error": "ID de l'objet invalide.", "item_player_exist_error": "UID ou objet invalide.", - "entity_id_error": "ID de l'entité invalide.", "player_exist_offline_error": "Le joueur est introuvable ou n'est pas connecté.", "argument_error": "Arguments invalides.", "clear_target": "Cible réinitialisée.", @@ -387,10 +385,10 @@ "description": "Débloque tous les couloirs de l'abysse" }, "weather": { - "usage": "Usage: weather \nTypes de météo 0: Aucun, 1: Ensoleillé, 2: Nuageux, 3: Pluvieux, 4: Orageux, 5: Neige, 6: Brouillard", - "success": "Le type de climat a été changé à %s avec le type de météo %s.", - "invalid_id": "ID invalide.", - "description": "Change la météo" + "description": "Change la météo. Weather IDs can be found in WeatherExcelConfigData.json.\nClimate types: sunny, cloudy, rain, thunderstorm, snow, mist.", + "usage": "Utilisation: weather [weatherId] [climateType]\nWeather IDs can be found in WeatherExcelConfigData.json.\nClimate types: sunny, cloudy, rain, thunderstorm, snow, mist.", + "success": "Set weather ID to %s with climate type %s.", + "status": "Current weather ID is %s with climate type %s." }, "ban": { "description": "Bannis un joueur", diff --git a/src/main/resources/languages/pl-PL.json b/src/main/resources/languages/pl-PL.json index 7cdde7bae..e2fcfbae8 100644 --- a/src/main/resources/languages/pl-PL.json +++ b/src/main/resources/languages/pl-PL.json @@ -70,16 +70,14 @@ "itemLevel": "Błędny poziom przedmiotu.", "itemRefinement": "Błędne ulepszenie.", "playerId": "Błędne playerId.", - "uid": "Błędne UID." + "uid": "Błędne UID.", + "id": "Błędne ID." } }, "execution": { - "uid_error": "Błędne UID.", "player_exist_error": "Gracz nie znaleziony.", "player_offline_error": "Gracz nie jest online.", - "item_id_error": "Błędne ID przedmiotu.", "item_player_exist_error": "Błędny przedmiot lub UID.", - "entity_id_error": "Błędne ID obiektu.", "player_exist_offline_error": "Gracz nie znaleziony lub jest offline.", "argument_error": "Błędne argumenty.", "clear_target": "Cel wyczyszczony.", @@ -291,9 +289,10 @@ "success": "Przeteleportowano %s do %s, %s, %s w scenie %s" }, "weather": { - "usage": "Użycie: weather \nWeather types 0: None, 1: Sunny, 2: Cloudy, 3: Rain, 4: Thunderstorm, 5: Snow, 6: Mist", - "success": "Changed climate type to %s with weather type %s.", - "invalid_id": "Błędne ID." + "description": "Changes the weather.Weather IDs can be found in WeatherExcelConfigData.json.\nClimate types: sunny, cloudy, rain, thunderstorm, snow, mist.", + "usage": "Usage: weather [weatherId] [climateType]\nWeather IDs can be found in WeatherExcelConfigData.json.\nClimate types: sunny, cloudy, rain, thunderstorm, snow, mist.", + "success": "Set weather ID to %s with climate type %s.", + "status": "Current weather ID is %s with climate type %s." }, "drop": { "command_usage": "Użycie: drop [ilość]", diff --git a/src/main/resources/languages/ru-RU.json b/src/main/resources/languages/ru-RU.json index 014e9e931..eea174c89 100644 --- a/src/main/resources/languages/ru-RU.json +++ b/src/main/resources/languages/ru-RU.json @@ -76,16 +76,14 @@ "itemLevel": "Некорректный уровень предмета (itemLevel).", "itemRefinement": "Некорректный уровень пробуждения предмета (itemRefinement).", "playerId": "Некорректный ID игрока.", - "uid": "Некорректный UID." + "uid": "Некорректный UID.", + "id": "Некорректный ID." } }, "execution": { - "uid_error": "Некорректный UID.", "player_exist_error": "Игрок не найден.", "player_offline_error": "Игрок не в сети.", - "item_id_error": "Некорректный ID предмета.", "item_player_exist_error": "Некорректный предмет или UID.", - "entity_id_error": "Некорректный ID сущности.", "player_exist_offline_error": "Игрок не был найден или не в сети.", "argument_error": "Некорректные аргументы.", "clear_target": "Цель была удалена.", @@ -387,10 +385,10 @@ "description": "Открывает все уровни башни" }, "weather": { - "usage": "Применение: weather <тип_климата(Id погоды)> <тип_погоды(Id климата)>\nТипы погоды 0: Отсутствует, 1: Солнечная, 2: Пасмурная, 3: Дождливая, 4: Грозовая, 5: Снежная, 6: Туманная", - "success": "Тип климата был изменен на %s, тип погоды: %s.", - "invalid_id": "Некорректный ID.", - "description": "Изменяет погоду" + "description": "Изменяет погоду.Weather IDs can be found in WeatherExcelConfigData.json.\nClimate types: sunny, cloudy, rain, thunderstorm, snow, mist.", + "usage": "Usage: weather [weatherId] [climateType]\nWeather IDs can be found in WeatherExcelConfigData.json.\nClimate types: sunny, cloudy, rain, thunderstorm, snow, mist.", + "success": "Set weather ID to %s with climate type %s.", + "status": "Current weather ID is %s with climate type %s." }, "ban": { "description": "Банит игрока", diff --git a/src/main/resources/languages/zh-CN.json b/src/main/resources/languages/zh-CN.json index 849c13cb2..608bd7c7f 100644 --- a/src/main/resources/languages/zh-CN.json +++ b/src/main/resources/languages/zh-CN.json @@ -76,16 +76,14 @@ "itemLevel": "无效的物品等级。", "itemRefinement": "无效的物品精炼等级。", "playerId": "无效的玩家ID。", - "uid": "无效的UID。" + "uid": "无效的UID。", + "id": "无效的ID。" } }, "execution": { - "uid_error": "无效的UID。", "player_exist_error": "玩家不存在。", "player_offline_error": "玩家已离线。", - "item_id_error": "无效的物品ID。", "item_player_exist_error": "无效的物品/玩家UID。", - "entity_id_error": "无效的实体ID。", "player_exist_offline_error": "玩家不存在或已离线。", "argument_error": "无效的参数。", "clear_target": "目标已清除。", @@ -99,17 +97,16 @@ "status": { "enabled": "已启用", "disabled": "未启用", - "help": "帮助", + "help": "帮助", "success": "成功" }, "account": { - "modify": "修改用户账号", + "command_usage": "用法:account <用户名> [UID]", "invalid": "无效的UID。", "exists": "具有此用户名和/或 UID 的账号已存在。", "create": "已创建 UID 为 %s 的账号。", "delete": "账号已删除。", "no_account": "账号不存在。", - "command_usage": "用法:account <用户名> [UID]", "description": "创建或删除账号" }, "broadcast": { @@ -121,7 +118,7 @@ "usage": "用法:changescene <场景ID>", "already_in_scene": "你已经在这个场景中了。", "success": "已切换至场景 %s。", - "exists_error": "此场景不存在。", + "exists_error": "场景不存在。", "description": "切换指定场景" }, "clear": { @@ -148,8 +145,8 @@ "enter_dungeon": { "usage": "用法:enterdungeon <秘境ID>", "changed": "已进入秘境 %s。", - "not_found_error": "此秘境不存在。", - "in_dungeon_error": "你已经在秘境中了。", + "not_found_error": "秘境不存在。", + "in_dungeon_error": "你已经在这个秘境中了。", "description": "进入指定秘境" }, "giveAll": { @@ -387,25 +384,25 @@ "description": "解锁深境螺旋" }, "weather": { - "usage": "用法:weather <气候类型(天气ID)> <天气类型(气候ID)>\n天气类型 0: 无, 1: 晴天, 2: 多云, 3: 雨, 4: 雷雨, 5: 雪, 6: 雾", - "success": "已更改气候类型为 %s,天气类型为 %s。", - "invalid_id": "无效的ID。", - "description": "更改天气" + "usage": "用法:weather [天气ID] [气候类型]\n天气ID可以在 WeatherExcelConfigData.json 中找到。\n气候类型:sunny(晴天), cloudy(多云), rain(雨), thunderstorm(雷雨), snow(雪), mist(雾)", + "success": "已设置天气ID 为 %s,气候类型为 %s。", + "status": "当前天气ID 为 %s,气候类型为 %s。", + "description": "更改天气ID和气候类型。天气ID可以在 WeatherExcelConfigData.json 中找到。\n气候类型:sunny(晴天), cloudy(多云), rain(雨), thunderstorm(雷雨), snow(雪), mist(雾)" }, "ban": { - "description": "封禁玩家", - "success": "封禁玩家成功。", - "failure": "封禁玩家失败,因为无法获取到其账户。", + "command_usage": "用法:ban <玩家ID> [时间] [原因]", + "success": "成功封禁玩家。", + "failure": "封禁玩家失败,因为玩家不存在。", "invalid_time": "无法解析时间戳。", - "invalid_player_id": "无法解析玩家 ID。", - "command_usage": "用法:ban <玩家ID> [时间] [原因]" + "invalid_player_id": "无法解析玩家ID。", + "description": "封禁玩家" }, "unban": { - "description": "取消玩家的封禁", - "success": "取消玩家的封禁成功。", - "failure": "取消玩家的封禁失败,因为无法获取到其账户。", - "invalid_player_id": "无法解析玩家 ID。", - "command_usage": "用法:unban <玩家ID>" + "command_usage": "用法:unban <玩家ID>", + "success": "成功取消玩家的封禁。", + "failure": "取消玩家的封禁失败,因为玩家不存在。", + "invalid_player_id": "无法解析玩家ID。", + "description": "取消玩家的封禁" } }, "gacha": { diff --git a/src/main/resources/languages/zh-TW.json b/src/main/resources/languages/zh-TW.json index 77b103bc0..f834d1669 100644 --- a/src/main/resources/languages/zh-TW.json +++ b/src/main/resources/languages/zh-TW.json @@ -75,16 +75,14 @@ "itemLevel": "無效的物品等級。", "itemRefinement": "無效的物品精煉度。", "playerId": "無效的玩家ID。", - "uid": "無效的UID。" + "uid": "無效的UID。", + "id": "無效的ID。" } }, "execution": { - "uid_error": "無效的UID。", "player_exist_error": "用戶不存在。", "player_offline_error": "玩家已離線。", - "item_id_error": "無效的物品ID。.", "item_player_exist_error": "無效的物品/玩家UID。", - "entity_id_error": "無效的實體ID。", "player_exist_offline_error": "玩家不存在或已離線。", "argument_error": "無效的參數。", "clear_target": "目標已清除.", @@ -98,11 +96,10 @@ "status": { "enabled": "已啟用", "disabled": "未啟用", - "help": "幫助", + "help": "幫助", "success": "成功" }, "account": { - "modify": "修改使用者帳號", "invalid": "無效的UID。", "exists": "帳號已存在。", "create": "已建立帳號,UID 為 %s 。", @@ -390,10 +387,10 @@ "description": "解鎖所有級別的深境螺旋。" }, "weather": { - "usage": "用法:weather <氣候型別(weatherId)> <天氣型別(climateId)>\n天氣類型: '0:無、 1:晴天、 2:多雲、 3:雨、 4::雷雨、 5:雪、 6:霧'", - "success": "已將當前氣候設定為 %s ,天氣則為 %s 。", - "invalid_id": "無效的ID。", - "description": "更改目前的天氣。" + "usage": "用法:weather [weatherId] [climateType]\n天氣ID可以在 WeatherExcelConfigData.json 中找到。\n氣候型別:sunny(晴天), cloudy(多雲), rain(雨), thunderstorm(雷雨), snow(雪), mist(霧)", + "success": "已設定天氣ID 為 %s,氣候型別為 %s。", + "status": "當前天氣ID 為 %s,氣候型別為 %s。", + "description": "更改天氣ID和氣候型別。天氣ID可以在 WeatherExcelConfigData.json 中找到。\n氣候型別:sunny(晴天), cloudy(多雲), rain(雨), thunderstorm(雷雨), snow(雪), mist(霧)" }, "ban": { "description": "停權指定玩家。",