Commit 20b2554e authored by KingRainbow44's avatar KingRainbow44
Browse files

Merge remote-tracking branch 'origin/development' into development

parents a1681255 52c1d2f5
...@@ -30,11 +30,11 @@ ...@@ -30,11 +30,11 @@
* [MongoDB](https://www.mongodb.com/try/download/community) (推荐 4.0+) * [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` 1. 获取 `grasscutter.jar`
-[actions](https://github.com/Grasscutters/Grasscutter/suites/6895963598/artifacts/267483297) 下载 -[actions](https://github.com/Grasscutters/Grasscutter/suites/6895963598/artifacts/267483297) 下载
......
...@@ -30,11 +30,11 @@ ...@@ -30,11 +30,11 @@
* [MongoDB](https://www.mongodb.com/try/download/community) (推薦 4.0+) * [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` 1. 獲取 `grasscutter.jar`
-[actions](https://github.com/Grasscutters/Grasscutter/suites/6895963598/artifacts/267483297) 下載 -[actions](https://github.com/Grasscutters/Grasscutter/suites/6895963598/artifacts/267483297) 下載
......
...@@ -43,7 +43,7 @@ sourceCompatibility = JavaVersion.VERSION_17 ...@@ -43,7 +43,7 @@ sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17
group = 'xyz.grasscutters' group = 'xyz.grasscutters'
version = '1.2.1-dev' version = '1.2.2-dev'
sourceCompatibility = 17 sourceCompatibility = 17
......
{ {
"$schema": "http://json-schema.org/draft-07/schema#", "$schema": "http://json-schema.org/draft-07/schema#",
"title": "JSON schema for a Grasscutter Plugin", "title": "JSON schema for a Grasscutter Plugin",
"type": "object", "type": "object",
"additionalProperties": true, "additionalProperties": true,
"definitions": { "definitions": {
"plugin-name": { "plugin-name": {
"type": "string", "type": "string",
"pattern": "^[A-Za-z\\d_.-]+$" "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": { "required": [
"title": "Website", "name",
"description": "The URL to the plugin's site", "description",
"type": "string", "mainClass"
"format": "uri" ],
"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"
}
}
} }
}
} }
\ No newline at end of file
package emu.grasscutter; package emu.grasscutter;
import java.io.*; import ch.qos.logback.classic.Logger;
import java.util.Calendar; import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import emu.grasscutter.auth.AuthenticationSystem; import emu.grasscutter.auth.AuthenticationSystem;
import emu.grasscutter.auth.DefaultAuthentication; import emu.grasscutter.auth.DefaultAuthentication;
import emu.grasscutter.command.CommandMap; import emu.grasscutter.command.CommandMap;
import emu.grasscutter.command.DefaultPermissionHandler; import emu.grasscutter.command.DefaultPermissionHandler;
import emu.grasscutter.command.PermissionHandler; import emu.grasscutter.command.PermissionHandler;
import emu.grasscutter.game.dungeons.challenge.DungeonChallenge; 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.energy.EnergyManager;
import emu.grasscutter.game.managers.stamina.StaminaManager; import emu.grasscutter.game.managers.stamina.StaminaManager;
import emu.grasscutter.plugin.PluginManager; import emu.grasscutter.plugin.PluginManager;
import emu.grasscutter.plugin.api.ServerHook; import emu.grasscutter.plugin.api.ServerHook;
import emu.grasscutter.scripts.ScriptLoader; import emu.grasscutter.scripts.ScriptLoader;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.http.HttpServer; import emu.grasscutter.server.http.HttpServer;
import emu.grasscutter.server.http.dispatch.DispatchHandler; 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.dispatch.RegionHandler;
import emu.grasscutter.server.http.documentation.DocumentationServerHandler; 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.ConfigContainer;
import emu.grasscutter.utils.Crypto;
import emu.grasscutter.utils.Language;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Utils;
import org.jline.reader.EndOfFileException; import org.jline.reader.EndOfFileException;
import org.jline.reader.LineReader; import org.jline.reader.LineReader;
...@@ -30,349 +39,343 @@ import org.jline.terminal.TerminalBuilder; ...@@ -30,349 +39,343 @@ import org.jline.terminal.TerminalBuilder;
import org.reflections.Reflections; import org.reflections.Reflections;
import org.slf4j.LoggerFactory; 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 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.utils.Language.translate;
import static emu.grasscutter.Configuration.*;
public final class Grasscutter { public final class Grasscutter {
private static final Logger log = (Logger) LoggerFactory.getLogger(Grasscutter.class); private static final Logger log = (Logger) LoggerFactory.getLogger(Grasscutter.class);
private static LineReader consoleLineReader = null; private static LineReader consoleLineReader = null;
private static Language language; private static Language language;
private static final Gson gson = new GsonBuilder().setPrettyPrinting().create(); private static final Gson gson = new GsonBuilder().setPrettyPrinting().create();
public static final File configFile = new File("./config.json"); public static final File configFile = new File("./config.json");
private static int day; // Current day of week. private static int day; // Current day of week.
private static HttpServer httpServer; private static HttpServer httpServer;
private static GameServer gameServer; private static GameServer gameServer;
private static PluginManager pluginManager; private static PluginManager pluginManager;
private static AuthenticationSystem authenticationSystem; private static AuthenticationSystem authenticationSystem;
private static PermissionHandler permissionHandler; private static PermissionHandler permissionHandler;
public static final Reflections reflector = new Reflections("emu.grasscutter"); public static final Reflections reflector = new Reflections("emu.grasscutter");
public static ConfigContainer config; public static ConfigContainer config;
static { static {
// Declare logback configuration. // Declare logback configuration.
System.setProperty("logback.configurationFile", "src/main/resources/logback.xml"); System.setProperty("logback.configurationFile", "src/main/resources/logback.xml");
// Load server configuration. // Load server configuration.
Grasscutter.loadConfig(); Grasscutter.loadConfig();
// Attempt to update configuration. // Attempt to update configuration.
ConfigContainer.updateConfig(); ConfigContainer.updateConfig();
// Load translation files. // Load translation files.
Grasscutter.loadLanguage(); Grasscutter.loadLanguage();
// Check server structure. // Check server structure.
Utils.startupCheck(); Utils.startupCheck();
} }
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
Crypto.loadKeys(); // Load keys from buffers. Crypto.loadKeys(); // Load keys from buffers.
// Parse arguments. // Parse arguments.
boolean exitEarly = false; boolean exitEarly = false;
for (String arg : args) { for (String arg : args) {
switch (arg.toLowerCase()) { switch (arg.toLowerCase()) {
case "-handbook" -> { case "-handbook" -> {
Tools.createGmHandbook(); exitEarly = true; Tools.createGmHandbook();
} exitEarly = true;
case "-gachamap" -> { }
Tools.createGachaMapping(DATA("gacha_mappings.js")); exitEarly = true; case "-gachamap" -> {
} Tools.createGachaMapping(DATA("gacha_mappings.js"));
case "-version" -> { exitEarly = true;
System.out.println("Grasscutter version: " + BuildConfig.VERSION + "-" + BuildConfig.GIT_HASH); 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. // Exit early if argument sets it.
Grasscutter.getLogger().info(translate("messages.status.starting")); if (exitEarly) System.exit(0);
Grasscutter.getLogger().info(translate("messages.status.game_version", GameConstants.VERSION));
Grasscutter.getLogger().info(translate("messages.status.version", BuildConfig.VERSION, BuildConfig.GIT_HASH)); // Initialize server.
Grasscutter.getLogger().info(translate("messages.status.starting"));
// Load all resources. Grasscutter.getLogger().info(translate("messages.status.game_version", GameConstants.VERSION));
Grasscutter.updateDayOfWeek(); Grasscutter.getLogger().info(translate("messages.status.version", BuildConfig.VERSION, BuildConfig.GIT_HASH));
ResourceLoader.loadAll();
ScriptLoader.init(); // Load all resources.
EnergyManager.initialize(); Grasscutter.updateDayOfWeek();
DungeonChallenge.initialize(); ResourceLoader.loadAll();
ScriptLoader.init();
// Initialize database.
DatabaseManager.initialize(); // Initialize database.
DatabaseManager.initialize();
// Initialize the default systems.
authenticationSystem = new DefaultAuthentication(); // Initialize the default systems.
permissionHandler = new DefaultPermissionHandler(); authenticationSystem = new DefaultAuthentication();
permissionHandler = new DefaultPermissionHandler();
// Create server instances.
httpServer = new HttpServer(); // Create server instances.
gameServer = new GameServer(); httpServer = new HttpServer();
// Create a server hook instance with both servers. gameServer = new GameServer();
new ServerHook(gameServer, httpServer); // Create a server hook instance with both servers.
new ServerHook(gameServer, httpServer);
// Create plugin manager instance.
pluginManager = new PluginManager(); // Create plugin manager instance.
// Add HTTP routes after loading plugins. pluginManager = new PluginManager();
httpServer.addRouter(HttpServer.UnhandledRequestRouter.class); // Add HTTP routes after loading plugins.
httpServer.addRouter(HttpServer.DefaultRequestRouter.class); httpServer.addRouter(HttpServer.UnhandledRequestRouter.class);
httpServer.addRouter(RegionHandler.class); httpServer.addRouter(HttpServer.DefaultRequestRouter.class);
httpServer.addRouter(LogHandler.class); httpServer.addRouter(RegionHandler.class);
httpServer.addRouter(GenericHandler.class); httpServer.addRouter(LogHandler.class);
httpServer.addRouter(AnnouncementsHandler.class); httpServer.addRouter(GenericHandler.class);
httpServer.addRouter(DispatchHandler.class); httpServer.addRouter(AnnouncementsHandler.class);
httpServer.addRouter(GachaHandler.class); httpServer.addRouter(DispatchHandler.class);
httpServer.addRouter(DocumentationServerHandler.class); httpServer.addRouter(GachaHandler.class);
httpServer.addRouter(DocumentationServerHandler.class);
// TODO: find a better place?
StaminaManager.initialize(); // Start servers.
var runMode = SERVER.runMode;
// Start servers. if (runMode == ServerRunMode.HYBRID) {
var runMode = SERVER.runMode; httpServer.start();
if (runMode == ServerRunMode.HYBRID) { gameServer.start();
httpServer.start(); } else if (runMode == ServerRunMode.DISPATCH_ONLY) {
gameServer.start(); httpServer.start();
} else if (runMode == ServerRunMode.DISPATCH_ONLY) { } else if (runMode == ServerRunMode.GAME_ONLY) {
httpServer.start(); gameServer.start();
} else if (runMode == ServerRunMode.GAME_ONLY) { } else {
gameServer.start(); getLogger().error(translate("messages.status.run_mode_error", runMode));
} else { getLogger().error(translate("messages.status.run_mode_help"));
getLogger().error(translate("messages.status.run_mode_error", runMode)); getLogger().error(translate("messages.status.shutdown"));
getLogger().error(translate("messages.status.run_mode_help")); System.exit(1);
getLogger().error(translate("messages.status.shutdown")); }
System.exit(1);
} // Enable all plugins.
pluginManager.enablePlugins();
// Enable all plugins.
pluginManager.enablePlugins(); // Hook into shutdown event.
Runtime.getRuntime().addShutdownHook(new Thread(Grasscutter::onShutdown));
// Hook into shutdown event.
Runtime.getRuntime().addShutdownHook(new Thread(Grasscutter::onShutdown)); // Open console.
startConsole();
// Open console. }
startConsole();
} /**
* Server shutdown event.
/** */
* Server shutdown event. private static void onShutdown() {
*/ // Disable all plugins.
private static void onShutdown() { if(pluginManager != null)
// Disable all plugins. pluginManager.disablePlugins();
pluginManager.disablePlugins(); }
}
/*
/* * Methods for the language system component.
* Methods for the language system component. */
*/
public static void loadLanguage() {
public static void loadLanguage() { var locale = config.language.language;
var locale = config.language.language; language = Language.getLanguage(Utils.getLanguageCode(locale));
language = Language.getLanguage(Utils.getLanguageCode(locale)); }
}
/*
/* * Methods for the configuration system component.
* Methods for the configuration system component. */
*/
/**
/** * Attempts to load the configuration from a file.
* Attempts to load the configuration from a file. */
*/ public static void loadConfig() {
public static void loadConfig() { // Check if config.json exists. If not, we generate a new config.
// Check if config.json exists. If not, we generate a new config. if (!configFile.exists()) {
if (!configFile.exists()) { getLogger().info("config.json could not be found. Generating a default configuration ...");
getLogger().info("config.json could not be found. Generating a default configuration ..."); config = new ConfigContainer();
config = new ConfigContainer(); Grasscutter.saveConfig(config);
Grasscutter.saveConfig(config); return;
return; }
}
// If the file already exists, we attempt to load it.
// If the file already exists, we attempt to load it. try (FileReader file = new FileReader(configFile)) {
try (FileReader file = new FileReader(configFile)) { config = gson.fromJson(file, ConfigContainer.class);
config = gson.fromJson(file, ConfigContainer.class); } catch (Exception exception) {
} 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.");
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);
System.exit(1); }
} }
}
/**
/** * Saves the provided server configuration.
* Saves the provided server configuration. *
* @param config The configuration to save, or null for a new one. * @param config The configuration to save, or null for a new one.
*/ */
public static void saveConfig(@Nullable ConfigContainer config) { public static void saveConfig(@Nullable ConfigContainer config) {
if(config == null) config = new ConfigContainer(); if (config == null) config = new ConfigContainer();
try (FileWriter file = new FileWriter(configFile)) { try (FileWriter file = new FileWriter(configFile)) {
file.write(gson.toJson(config)); file.write(gson.toJson(config));
} catch (IOException ignored) { } catch (IOException ignored) {
Grasscutter.getLogger().error("Unable to write to config file."); Grasscutter.getLogger().error("Unable to write to config file.");
} catch (Exception e) { } catch (Exception e) {
Grasscutter.getLogger().error("Unable to save config file.", e); Grasscutter.getLogger().error("Unable to save config file.", e);
} }
} }
/* /*
* Getters for the various server components. * Getters for the various server components.
*/ */
public static ConfigContainer getConfig() { public static ConfigContainer getConfig() {
return config; return config;
} }
public static Language getLanguage() { public static Language getLanguage() {
return language; return language;
} }
public static void setLanguage(Language language) { public static void setLanguage(Language language) {
Grasscutter.language = language; Grasscutter.language = language;
} }
public static Language getLanguage(String langCode) { public static Language getLanguage(String langCode) {
return Language.getLanguage(langCode); return Language.getLanguage(langCode);
} }
public static Logger getLogger() { public static Logger getLogger() {
return log; return log;
} }
public static LineReader getConsole() { public static LineReader getConsole() {
if (consoleLineReader == null) { if (consoleLineReader == null) {
Terminal terminal = null; Terminal terminal = null;
try { try {
terminal = TerminalBuilder.builder().jna(true).build(); terminal = TerminalBuilder.builder().jna(true).build();
} catch (Exception e) { } catch (Exception e) {
try { try {
// Fallback to a dumb jline terminal. // Fallback to a dumb jline terminal.
terminal = TerminalBuilder.builder().dumb(true).build(); terminal = TerminalBuilder.builder().dumb(true).build();
} catch (Exception ignored) { } catch (Exception ignored) {
// When dumb is true, build() never throws. // When dumb is true, build() never throws.
} }
} }
consoleLineReader = LineReaderBuilder.builder() consoleLineReader = LineReaderBuilder.builder()
.terminal(terminal) .terminal(terminal)
.build(); .build();
} }
return consoleLineReader; return consoleLineReader;
} }
public static Gson getGsonFactory() { public static Gson getGsonFactory() {
return gson; return gson;
} }
public static HttpServer getHttpServer() { public static HttpServer getHttpServer() {
return httpServer; return httpServer;
} }
public static GameServer getGameServer() { public static GameServer getGameServer() {
return gameServer; return gameServer;
} }
public static PluginManager getPluginManager() { public static PluginManager getPluginManager() {
return pluginManager; return pluginManager;
} }
public static AuthenticationSystem getAuthenticationSystem() { public static AuthenticationSystem getAuthenticationSystem() {
return authenticationSystem; return authenticationSystem;
} }
public static PermissionHandler getPermissionHandler() { public static PermissionHandler getPermissionHandler() {
return permissionHandler; return permissionHandler;
} }
public static int getCurrentDayOfWeek() { public static int getCurrentDayOfWeek() {
return day; return day;
} }
/* /*
* Utility methods. * Utility methods.
*/ */
public static void updateDayOfWeek() { public static void updateDayOfWeek() {
Calendar calendar = Calendar.getInstance(); Calendar calendar = Calendar.getInstance();
day = calendar.get(Calendar.DAY_OF_WEEK); day = calendar.get(Calendar.DAY_OF_WEEK);
} }
public static void startConsole() { public static void startConsole() {
// Console should not start in dispatch only mode. // Console should not start in dispatch only mode.
if (SERVER.runMode == ServerRunMode.DISPATCH_ONLY) { if (SERVER.runMode == ServerRunMode.DISPATCH_ONLY) {
getLogger().info(translate("messages.dispatch.no_commands_error")); getLogger().info(translate("messages.dispatch.no_commands_error"));
return; return;
} }
getLogger().info(translate("messages.status.done")); getLogger().info(translate("messages.status.done"));
String input = null; String input = null;
boolean isLastInterrupted = false; boolean isLastInterrupted = false;
while (config.server.game.enableConsole) { while (config.server.game.enableConsole) {
try { try {
input = consoleLineReader.readLine("> "); input = consoleLineReader.readLine("> ");
} catch (UserInterruptException e) { } catch (UserInterruptException e) {
if (!isLastInterrupted) { if (!isLastInterrupted) {
isLastInterrupted = true; isLastInterrupted = true;
Grasscutter.getLogger().info("Press Ctrl-C again to shutdown."); Grasscutter.getLogger().info("Press Ctrl-C again to shutdown.");
continue; continue;
} else { } else {
Runtime.getRuntime().exit(0); Runtime.getRuntime().exit(0);
} }
} catch (EndOfFileException e) { } catch (EndOfFileException e) {
Grasscutter.getLogger().info("EOF detected."); Grasscutter.getLogger().info("EOF detected.");
continue; continue;
} catch (IOError e) { } catch (IOError e) {
Grasscutter.getLogger().error("An IO error occurred.", e); Grasscutter.getLogger().error("An IO error occurred.", e);
continue; continue;
} }
isLastInterrupted = false; isLastInterrupted = false;
try { try {
CommandMap.getInstance().invoke(null, null, input); CommandMap.getInstance().invoke(null, null, input);
} catch (Exception e) { } catch (Exception e) {
Grasscutter.getLogger().error(translate("messages.game.command_error"), e); Grasscutter.getLogger().error(translate("messages.game.command_error"), e);
} }
} }
} }
/** /**
* Sets the authentication system for the server. * Sets the authentication system for the server.
* @param authenticationSystem The authentication system to use. *
*/ * @param authenticationSystem The authentication system to use.
public static void setAuthenticationSystem(AuthenticationSystem authenticationSystem) { */
Grasscutter.authenticationSystem = authenticationSystem; public static void setAuthenticationSystem(AuthenticationSystem authenticationSystem) {
} Grasscutter.authenticationSystem = authenticationSystem;
}
/**
* Sets the permission handler for the server. /**
* @param permissionHandler The permission handler to use. * Sets the permission handler for the server.
*/ *
public static void setPermissionHandler(PermissionHandler permissionHandler) { * @param permissionHandler The permission handler to use.
Grasscutter.permissionHandler = permissionHandler; */
} public static void setPermissionHandler(PermissionHandler permissionHandler) {
Grasscutter.permissionHandler = permissionHandler;
/* }
* Enums for the configuration.
*/ /*
* Enums for the configuration.
public enum ServerRunMode { */
HYBRID, DISPATCH_ONLY, GAME_ONLY
} public enum ServerRunMode {
HYBRID, DISPATCH_ONLY, GAME_ONLY
public enum ServerDebugMode { }
ALL, MISSING, NONE
} public enum ServerDebugMode {
ALL, MISSING, NONE
}
} }
package emu.grasscutter.command; package emu.grasscutter.command;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.Account;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import org.reflections.Reflections; import org.reflections.Reflections;
import java.util.*; import java.util.*;
...@@ -11,6 +9,7 @@ import java.util.*; ...@@ -11,6 +9,7 @@ import java.util.*;
@SuppressWarnings({"UnusedReturnValue", "unused"}) @SuppressWarnings({"UnusedReturnValue", "unused"})
public final class CommandMap { public final class CommandMap {
private final Map<String, CommandHandler> commands = new HashMap<>(); private final Map<String, CommandHandler> commands = new HashMap<>();
private final Map<String, CommandHandler> aliases = new HashMap<>();
private final Map<String, Command> annotations = new HashMap<>(); private final Map<String, Command> annotations = new HashMap<>();
private final Map<String, Integer> targetPlayerIds = new HashMap<>(); private final Map<String, Integer> targetPlayerIds = new HashMap<>();
private static final String consoleId = "console"; private static final String consoleId = "console";
...@@ -45,7 +44,7 @@ public final class CommandMap { ...@@ -45,7 +44,7 @@ public final class CommandMap {
// Register aliases. // Register aliases.
if (annotation.aliases().length > 0) { if (annotation.aliases().length > 0) {
for (String alias : annotation.aliases()) { for (String alias : annotation.aliases()) {
this.commands.put(alias, command); this.aliases.put(alias, command);
this.annotations.put(alias, annotation); this.annotations.put(alias, annotation);
} }
} }
...@@ -60,6 +59,7 @@ public final class CommandMap { ...@@ -60,6 +59,7 @@ public final class CommandMap {
*/ */
public CommandMap unregisterCommand(String label) { public CommandMap unregisterCommand(String label) {
Grasscutter.getLogger().debug("Unregistered command: " + label); Grasscutter.getLogger().debug("Unregistered command: " + label);
CommandHandler handler = this.commands.get(label); CommandHandler handler = this.commands.get(label);
if (handler == null) return this; if (handler == null) return this;
...@@ -70,7 +70,7 @@ public final class CommandMap { ...@@ -70,7 +70,7 @@ public final class CommandMap {
// Unregister aliases. // Unregister aliases.
if (annotation.aliases().length > 0) { if (annotation.aliases().length > 0) {
for (String alias : annotation.aliases()) { for (String alias : annotation.aliases()) {
this.commands.remove(alias); this.aliases.remove(alias);
this.annotations.remove(alias); this.annotations.remove(alias);
} }
} }
...@@ -78,7 +78,9 @@ public final class CommandMap { ...@@ -78,7 +78,9 @@ public final class CommandMap {
return this; return this;
} }
public List<Command> getAnnotationsAsList() { return new LinkedList<>(this.annotations.values()); } public List<Command> getAnnotationsAsList() {
return new LinkedList<>(this.annotations.values());
}
public HashMap<String, Command> getAnnotations() { public HashMap<String, Command> getAnnotations() {
return new LinkedHashMap<>(this.annotations); return new LinkedHashMap<>(this.annotations);
...@@ -125,7 +127,7 @@ public final class CommandMap { ...@@ -125,7 +127,7 @@ public final class CommandMap {
List<String> args = new LinkedList<>(Arrays.asList(split)); List<String> args = new LinkedList<>(Arrays.asList(split));
String label = args.remove(0); String label = args.remove(0);
String playerId = (player == null) ? consoleId : player.getAccount().getId(); String playerId = (player == null) ? consoleId : player.getAccount().getId();
// Check for special cases - currently only target command. // Check for special cases - currently only target command.
String targetUidStr = null; String targetUidStr = null;
if (label.startsWith("@")) { // @[UID] if (label.startsWith("@")) { // @[UID]
...@@ -142,7 +144,7 @@ public final class CommandMap { ...@@ -142,7 +144,7 @@ public final class CommandMap {
} }
if (targetUidStr != null) { if (targetUidStr != null) {
if (targetUidStr.equals("")) { // Clears the default targetPlayer. if (targetUidStr.equals("")) { // Clears the default targetPlayer.
targetPlayerIds.remove(playerId); this.targetPlayerIds.remove(playerId);
CommandHandler.sendTranslatedMessage(player, "commands.execution.clear_target"); CommandHandler.sendTranslatedMessage(player, "commands.execution.clear_target");
} else { // Sets default targetPlayer to the UID provided. } else { // Sets default targetPlayer to the UID provided.
try { try {
...@@ -151,12 +153,12 @@ public final class CommandMap { ...@@ -151,12 +153,12 @@ public final class CommandMap {
if (targetPlayer == null) { if (targetPlayer == null) {
CommandHandler.sendTranslatedMessage(player, "commands.execution.player_exist_error"); CommandHandler.sendTranslatedMessage(player, "commands.execution.player_exist_error");
} else { } else {
targetPlayerIds.put(playerId, uid); this.targetPlayerIds.put(playerId, uid);
CommandHandler.sendTranslatedMessage(player, "commands.execution.set_target", targetUidStr); 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) { } catch (NumberFormatException e) {
CommandHandler.sendTranslatedMessage(player, "commands.execution.uid_error"); CommandHandler.sendTranslatedMessage(player, "commands.generic.invalid.uid");
} }
} }
return; return;
...@@ -164,11 +166,19 @@ public final class CommandMap { ...@@ -164,11 +166,19 @@ public final class CommandMap {
// Get command handler. // Get command handler.
CommandHandler handler = this.commands.get(label); 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) { if (handler == null) {
CommandHandler.sendTranslatedMessage(player, "commands.generic.unknown_command", label); CommandHandler.sendTranslatedMessage(player, "commands.generic.unknown_command", label);
return; return;
} }
// Get the command's annotation.
Command annotation = this.annotations.get(label);
// If any @UID argument is present, override targetPlayer with it. // If any @UID argument is present, override targetPlayer with it.
for (int i = 0; i < args.size(); i++) { for (int i = 0; i < args.size(); i++) {
String arg = args.get(i); String arg = args.get(i);
...@@ -183,16 +193,16 @@ public final class CommandMap { ...@@ -183,16 +193,16 @@ public final class CommandMap {
} }
break; break;
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
CommandHandler.sendTranslatedMessage(player, "commands.execution.uid_error"); CommandHandler.sendTranslatedMessage(player, "commands.generic.invalid.uid");
return; return;
} }
} }
} }
// If there's still no targetPlayer at this point, use previously-set target // If there's still no targetPlayer at this point, use previously-set target
if (targetPlayer == null) { if (targetPlayer == null) {
if (targetPlayerIds.containsKey(playerId)) { if (this.targetPlayerIds.containsKey(playerId)) {
targetPlayer = Grasscutter.getGameServer().getPlayerByUid(targetPlayerIds.get(playerId), true); // We check every time in case the target is deleted after being targeted 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) { if (targetPlayer == null) {
CommandHandler.sendTranslatedMessage(player, "commands.execution.player_exist_error"); CommandHandler.sendTranslatedMessage(player, "commands.execution.player_exist_error");
return; return;
...@@ -204,32 +214,36 @@ public final class CommandMap { ...@@ -204,32 +214,36 @@ public final class CommandMap {
} }
// Check for permissions. // 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; return;
} }
// Check if command has unfulfilled constraints on targetPlayer // 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 (targetRequirement != Command.TargetRequirement.NONE) {
if (targetPlayer == null) { if (targetPlayer == null) {
CommandHandler.sendTranslatedMessage(player, "commands.execution.need_target"); CommandHandler.sendTranslatedMessage(null, "commands.execution.need_target");
return; return;
} }
if ((targetRequirement == Command.TargetRequirement.ONLINE) && !targetPlayer.isOnline()) { if ((targetRequirement == Command.TargetRequirement.ONLINE) && !targetPlayer.isOnline()) {
CommandHandler.sendTranslatedMessage(player, "commands.execution.need_target_online"); CommandHandler.sendTranslatedMessage(player, "commands.execution.need_target_online");
return; return;
} }
if ((targetRequirement == Command.TargetRequirement.OFFLINE) && targetPlayer.isOnline()) { if ((targetRequirement == Command.TargetRequirement.OFFLINE) && targetPlayer.isOnline()) {
CommandHandler.sendTranslatedMessage(player, "commands.execution.need_target_offline"); CommandHandler.sendTranslatedMessage(player, "commands.execution.need_target_offline");
return; 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. // Invoke execute method for handler.
boolean threading = this.annotations.get(label).threading(); Runnable runnable = () -> handlerF.execute(player, targetPlayerF, args);
final Player targetPlayerF = targetPlayer; // Is there a better way to do this? if (annotation.threading()) {
Runnable runnable = () -> handler.execute(player, targetPlayerF, args);
if(threading) {
new Thread(runnable).start(); new Thread(runnable).start();
} else { } else {
runnable.run(); runnable.run();
...@@ -242,10 +256,11 @@ public final class CommandMap { ...@@ -242,10 +256,11 @@ public final class CommandMap {
private void scan() { private void scan() {
Reflections reflector = Grasscutter.reflector; Reflections reflector = Grasscutter.reflector;
Set<Class<?>> classes = reflector.getTypesAnnotatedWith(Command.class); Set<Class<?>> classes = reflector.getTypesAnnotatedWith(Command.class);
classes.forEach(annotated -> { classes.forEach(annotated -> {
try { try {
Command cmdData = annotated.getAnnotation(Command.class); Command cmdData = annotated.getAnnotation(Command.class);
Object object = annotated.newInstance(); Object object = annotated.getDeclaredConstructor().newInstance();
if (object instanceof CommandHandler) if (object instanceof CommandHandler)
this.registerCommand(cmdData.label(), (CommandHandler) object); this.registerCommand(cmdData.label(), (CommandHandler) object);
else Grasscutter.getLogger().error("Class " + annotated.getName() + " is not a CommandHandler!"); else Grasscutter.getLogger().error("Class " + annotated.getName() + " is not a CommandHandler!");
......
...@@ -31,7 +31,7 @@ public final class CoopCommand implements CommandHandler { ...@@ -31,7 +31,7 @@ public final class CoopCommand implements CommandHandler {
} }
break; break;
} catch (NumberFormatException ignored) { } catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, translate(sender, "commands.execution.uid_error")); CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.uid"));
return; return;
} }
default: default:
......
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command; import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ClimateType; import emu.grasscutter.game.props.ClimateType;
import emu.grasscutter.server.packet.send.PacketSceneAreaWeatherNotify; import emu.grasscutter.game.world.Scene;
import java.util.List; import java.util.List;
import static emu.grasscutter.utils.Language.translate; @Command(label = "weather", usage = "weather [weatherId] [climateType]", aliases = {"w"}, permission = "player.weather", permissionTargeted = "player.weather.others", description = "commands.weather.description")
@Command(label = "weather", usage = "weather <climate type(weatherId)> <weather type(climateId)>", aliases = {"w"}, permission = "player.weather", permissionTargeted = "player.weather.others", description = "commands.weather.description")
public final class WeatherCommand implements CommandHandler { public final class WeatherCommand implements CommandHandler {
@Override @Override
public void execute(Player sender, Player targetPlayer, List<String> args) { public void execute(Player sender, Player targetPlayer, List<String> args) {
int weatherId = 0; int weatherId = targetPlayer.getWeatherId();
int climateId = 1; ClimateType climate = ClimateType.CLIMATE_NONE; // Sending ClimateType.CLIMATE_NONE to Scene.setWeather will use the default climate for that weather
switch (args.size()) {
case 2: if (args.isEmpty()) {
try { climate = targetPlayer.getClimate();
climateId = Integer.parseInt(args.get(1)); CommandHandler.sendTranslatedMessage(sender, "commands.weather.status", Integer.toString(weatherId), climate.getShortName());
} catch (NumberFormatException ignored) { return;
CommandHandler.sendMessage(sender, translate(sender, "commands.weather.invalid_id")); }
}
case 1: for (String arg : args) {
ClimateType c = ClimateType.getTypeByShortName(arg.toLowerCase());
if (c != ClimateType.CLIMATE_NONE) {
climate = c;
} else {
try { try {
weatherId = Integer.parseInt(args.get(0)); weatherId = Integer.parseInt(arg);
} catch (NumberFormatException ignored) { } catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, translate(sender, "commands.weather.invalid_id")); CommandHandler.sendTranslatedMessage(sender, "commands.generic.invalid.id");
CommandHandler.sendTranslatedMessage(sender, "commands.weather.usage");
return;
} }
break; }
default:
CommandHandler.sendMessage(sender, translate(sender, "commands.weather.usage"));
return;
} }
ClimateType climate = ClimateType.getTypeByValue(climateId); targetPlayer.setWeather(weatherId, climate);
climate = targetPlayer.getClimate(); // Might be different to what we set
targetPlayer.getScene().setWeather(weatherId); CommandHandler.sendTranslatedMessage(sender, "commands.weather.success", Integer.toString(weatherId), climate.getShortName());
targetPlayer.getScene().setClimate(climate);
targetPlayer.getScene().broadcastPacket(new PacketSceneAreaWeatherNotify(targetPlayer));
CommandHandler.sendMessage(sender, translate(sender, "commands.weather.success", Integer.toString(weatherId), Integer.toString(climateId)));
} }
} }
...@@ -6,11 +6,11 @@ import emu.grasscutter.tools.Tools; ...@@ -6,11 +6,11 @@ import emu.grasscutter.tools.Tools;
import emu.grasscutter.utils.FileUtils; import emu.grasscutter.utils.FileUtils;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Utils;
import java.io.*; import java.io.FileInputStream;
import java.nio.file.FileSystems; import java.io.FileNotFoundException;
import java.io.InputStream;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.List; import java.util.List;
import java.util.regex.Pattern;
import static emu.grasscutter.Configuration.DATA; import static emu.grasscutter.Configuration.DATA;
...@@ -18,10 +18,11 @@ public class DataLoader { ...@@ -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 * 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. * @param resourcePath The path to the data file to be loaded.
* @return InputStream of the data file. * @return InputStream of the data file.
* @throws FileNotFoundException * @throws FileNotFoundException
* @see #load(String, boolean)
*/ */
public static InputStream load(String resourcePath) throws FileNotFoundException { public static InputStream load(String resourcePath) throws FileNotFoundException {
return load(resourcePath, true); return load(resourcePath, true);
...@@ -29,17 +30,18 @@ public class DataLoader { ...@@ -29,17 +30,18 @@ public class DataLoader {
/** /**
* Load a data file by its name. * Load a data file by its name.
*
* @param resourcePath The path to the data file to be loaded. * @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. * @return InputStream of the data file.
* @throws FileNotFoundException * @throws FileNotFoundException
*/ */
public static InputStream load(String resourcePath, boolean useFallback) 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 // Data is in the resource directory
return new FileInputStream(DATA(resourcePath)); return new FileInputStream(DATA(resourcePath));
} else { } else {
if(useFallback) { if (useFallback) {
return FileUtils.readResourceAsStream("/defaults/data/" + resourcePath); return FileUtils.readResourceAsStream("/defaults/data/" + resourcePath);
} }
} }
...@@ -50,12 +52,10 @@ public class DataLoader { ...@@ -50,12 +52,10 @@ public class DataLoader {
public static void CheckAllFiles() { public static void CheckAllFiles() {
try { try {
List<Path> filenames = FileUtils.getPathsFromResource("/defaults/data/"); List<Path> 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]; String relativePath = String.valueOf(file).split("defaults[\\\\\\/]data[\\\\\\/]")[1];
CheckAndCopyData(relativePath); CheckAndCopyData(relativePath);
...@@ -76,12 +76,12 @@ public class DataLoader { ...@@ -76,12 +76,12 @@ public class DataLoader {
String[] path = name.split("/"); String[] path = name.split("/");
String folder = ""; String folder = "";
for(int i = 0; i < (path.length - 1); i++) { for (int i = 0; i < (path.length - 1); i++) {
folder += path[i] + "/"; folder += path[i] + "/";
// Make sure the current folder exists // Make sure the current folder exists
String folderToCreate = Utils.toFilePath(DATA(folder)); String folderToCreate = Utils.toFilePath(DATA(folder));
if(!Utils.fileExists(folderToCreate)) { if (!Utils.fileExists(folderToCreate)) {
Grasscutter.getLogger().info("Creating data folder '" + folder + "'"); Grasscutter.getLogger().info("Creating data folder '" + folder + "'");
Utils.createFolder(folderToCreate); Utils.createFolder(folderToCreate);
} }
......
...@@ -94,6 +94,7 @@ public class GameData { ...@@ -94,6 +94,7 @@ public class GameData {
private static final Int2ObjectMap<FurnitureMakeConfigData> furnitureMakeConfigDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<FurnitureMakeConfigData> furnitureMakeConfigDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<InvestigationMonsterData> investigationMonsterDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<InvestigationMonsterData> investigationMonsterDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<CityData> cityDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<CityData> cityDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<WeatherData> weatherDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<BattlePassMissionExcelConfigData> battlePassMissionExcelConfigDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<BattlePassMissionExcelConfigData> battlePassMissionExcelConfigDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<BattlePassRewardExcelConfigData> battlePassRewardExcelConfigDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<BattlePassRewardExcelConfigData> battlePassRewardExcelConfigDataMap = new Int2ObjectOpenHashMap<>();
...@@ -414,10 +415,15 @@ public class GameData { ...@@ -414,10 +415,15 @@ public class GameData {
public static Int2ObjectMap<InvestigationMonsterData> getInvestigationMonsterDataMap() { public static Int2ObjectMap<InvestigationMonsterData> getInvestigationMonsterDataMap() {
return investigationMonsterDataMap; return investigationMonsterDataMap;
} }
public static Int2ObjectMap<CityData> getCityDataMap() { public static Int2ObjectMap<CityData> getCityDataMap() {
return cityDataMap; return cityDataMap;
} }
public static Int2ObjectMap<WeatherData> getWeatherDataMap() {
return weatherDataMap;
}
public static Int2ObjectMap<BattlePassMissionExcelConfigData> getBattlePassMissionExcelConfigDataMap() { public static Int2ObjectMap<BattlePassMissionExcelConfigData> getBattlePassMissionExcelConfigDataMap() {
return battlePassMissionExcelConfigDataMap; return battlePassMissionExcelConfigDataMap;
} }
......
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;
}
}
...@@ -5,6 +5,7 @@ import emu.grasscutter.GameConstants; ...@@ -5,6 +5,7 @@ import emu.grasscutter.GameConstants;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.PlayerLevelData; import emu.grasscutter.data.excels.PlayerLevelData;
import emu.grasscutter.data.excels.WeatherData;
import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.Account; import emu.grasscutter.game.Account;
import emu.grasscutter.game.CoopRequest; import emu.grasscutter.game.CoopRequest;
...@@ -38,6 +39,7 @@ import emu.grasscutter.game.managers.mapmark.*; ...@@ -38,6 +39,7 @@ import emu.grasscutter.game.managers.mapmark.*;
import emu.grasscutter.game.managers.stamina.StaminaManager; import emu.grasscutter.game.managers.stamina.StaminaManager;
import emu.grasscutter.game.managers.SotSManager; import emu.grasscutter.game.managers.SotSManager;
import emu.grasscutter.game.props.ActionReason; import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.props.ClimateType;
import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.props.SceneType; import emu.grasscutter.game.props.SceneType;
import emu.grasscutter.game.quest.QuestManager; import emu.grasscutter.game.quest.QuestManager;
...@@ -71,6 +73,7 @@ import emu.grasscutter.utils.MessageHandler; ...@@ -71,6 +73,7 @@ import emu.grasscutter.utils.MessageHandler;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import lombok.Getter;
import java.util.*; import java.util.*;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
...@@ -112,6 +115,8 @@ public class Player { ...@@ -112,6 +115,8 @@ public class Player {
@Transient private int peerId; @Transient private int peerId;
@Transient private World world; @Transient private World world;
@Transient private Scene scene; @Transient private Scene scene;
@Transient @Getter private int weatherId = 0;
@Transient @Getter private ClimateType climate = ClimateType.CLIMATE_SUNNY;
@Transient private GameSession session; @Transient private GameSession session;
@Transient private AvatarStorage avatars; @Transient private AvatarStorage avatars;
@Transient private Inventory inventory; @Transient private Inventory inventory;
...@@ -120,7 +125,7 @@ public class Player { ...@@ -120,7 +125,7 @@ public class Player {
@Transient private MessageHandler messageHandler; @Transient private MessageHandler messageHandler;
@Transient private AbilityManager abilityManager; @Transient private AbilityManager abilityManager;
@Transient private QuestManager questManager; @Transient private QuestManager questManager;
@Transient private SotSManager sotsManager; @Transient private SotSManager sotsManager;
@Transient private InsectCaptureManager insectCaptureManager; @Transient private InsectCaptureManager insectCaptureManager;
...@@ -140,8 +145,8 @@ public class Player { ...@@ -140,8 +145,8 @@ public class Player {
private int regionId; private int regionId;
private int mainCharacterId; private int mainCharacterId;
private boolean godmode; private boolean godmode;
private boolean stamina; private boolean stamina;
private boolean moonCard; private boolean moonCard;
private Date moonCardStartTime; private Date moonCardStartTime;
private int moonCardDuration; private int moonCardDuration;
...@@ -324,6 +329,28 @@ public class Player { ...@@ -324,6 +329,28 @@ public class Player {
this.scene = scene; 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() { public int getGmLevel() {
return 1; return 1;
} }
...@@ -402,6 +429,14 @@ public class Player { ...@@ -402,6 +429,14 @@ public class Player {
return this.getProperty(PlayerProperty.PROP_PLAYER_LEVEL); 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() { public int getExp() {
return this.getProperty(PlayerProperty.PROP_PLAYER_EXP); return this.getProperty(PlayerProperty.PROP_PLAYER_EXP);
} }
...@@ -409,10 +444,12 @@ public class Player { ...@@ -409,10 +444,12 @@ public class Player {
public int getWorldLevel() { public int getWorldLevel() {
return this.getProperty(PlayerProperty.PROP_PLAYER_WORLD_LEVEL); return this.getProperty(PlayerProperty.PROP_PLAYER_WORLD_LEVEL);
} }
public void setWorldLevel(int level) { public void setWorldLevel(int level) {
this.setProperty(PlayerProperty.PROP_PLAYER_WORLD_LEVEL, level); this.setProperty(PlayerProperty.PROP_PLAYER_WORLD_LEVEL, level);
this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_WORLD_LEVEL)); this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_WORLD_LEVEL));
this.updateProfile();
} }
public int getPrimogems() { public int getPrimogems() {
...@@ -432,7 +469,7 @@ public class Player { ...@@ -432,7 +469,7 @@ public class Player {
this.setProperty(PlayerProperty.PROP_PLAYER_SCOIN, mora); this.setProperty(PlayerProperty.PROP_PLAYER_SCOIN, mora);
this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_SCOIN)); this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_SCOIN));
} }
public int getCrystals() { public int getCrystals() {
return this.getProperty(PlayerProperty.PROP_PLAYER_MCOIN); return this.getProperty(PlayerProperty.PROP_PLAYER_MCOIN);
} }
...@@ -481,12 +518,7 @@ public class Player { ...@@ -481,12 +518,7 @@ public class Player {
} }
if (hasLeveledUp) { if (hasLeveledUp) {
// Set level property this.setLevel(level);
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));
} }
// Set exp // Set exp
...@@ -496,6 +528,27 @@ public class Player { ...@@ -496,6 +528,27 @@ public class Player {
this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_EXP)); 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() { private void updateProfile() {
getProfile().syncWithCharacter(this); getProfile().syncWithCharacter(this);
} }
...@@ -507,11 +560,11 @@ public class Player { ...@@ -507,11 +560,11 @@ public class Player {
public TeamManager getTeamManager() { public TeamManager getTeamManager() {
return this.teamManager; return this.teamManager;
} }
public TowerManager getTowerManager() { public TowerManager getTowerManager() {
return towerManager; return towerManager;
} }
public TowerData getTowerData() { public TowerData getTowerData() {
if(towerData==null){ if(towerData==null){
// because of mistake, null may be saved as storage at some machine, this if can be removed in future // 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 { ...@@ -519,7 +572,7 @@ public class Player {
} }
return towerData; return towerData;
} }
public QuestManager getQuestManager() { public QuestManager getQuestManager() {
return questManager; return questManager;
} }
...@@ -588,7 +641,7 @@ public class Player { ...@@ -588,7 +641,7 @@ public class Player {
public MpSettingType getMpSetting() { public MpSettingType getMpSetting() {
return MpSettingType.MP_SETTING_TYPE_ENTER_AFTER_APPLY; // TEMP return MpSettingType.MP_SETTING_TYPE_ENTER_AFTER_APPLY; // TEMP
} }
public Queue<AttackResult> getAttackResults() { public Queue<AttackResult> getAttackResults() {
return this.attackResults; return this.attackResults;
} }
...@@ -783,7 +836,7 @@ public class Player { ...@@ -783,7 +836,7 @@ public class Player {
remainCalendar.add(Calendar.DATE, moonCardDuration); remainCalendar.add(Calendar.DATE, moonCardDuration);
Date theLastDay = remainCalendar.getTime(); Date theLastDay = remainCalendar.getTime();
Date now = DateHelper.onlyYearMonthDay(new Date()); 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() { public void rechargeMoonCard() {
...@@ -980,7 +1033,7 @@ public class Player { ...@@ -980,7 +1033,7 @@ public class Player {
} }
public Mail getMail(int index) { return this.getMailHandler().getMailById(index); } public Mail getMail(int index) { return this.getMailHandler().getMailById(index); }
public int getMailId(Mail message) { public int getMailId(Mail message) {
return this.getMailHandler().getMailIndex(message); return this.getMailHandler().getMailIndex(message);
} }
...@@ -988,9 +1041,9 @@ public class Player { ...@@ -988,9 +1041,9 @@ public class Player {
public boolean replaceMailByIndex(int index, Mail message) { public boolean replaceMailByIndex(int index, Mail message) {
return this.getMailHandler().replaceMailByIndex(index, 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); GameEntity entity = getScene().getEntityById(gadgetEntityId);
if (entity == null) { if (entity == null) {
return; return;
...@@ -1018,13 +1071,13 @@ public class Player { ...@@ -1018,13 +1071,13 @@ public class Player {
} }
} }
} else if (entity instanceof EntityGadget gadget) { } else if (entity instanceof EntityGadget gadget) {
if (gadget.getContent() == null) { if (gadget.getContent() == null) {
return; return;
} }
boolean shouldDelete = gadget.getContent().onInteract(this, req); boolean shouldDelete = gadget.getContent().onInteract(this, opType);
if (shouldDelete) { if (shouldDelete) {
entity.getScene().removeEntity(entity); entity.getScene().removeEntity(entity);
} }
...@@ -1168,7 +1221,7 @@ public class Player { ...@@ -1168,7 +1221,7 @@ public class Player {
} }
return showAvatarInfoList; return showAvatarInfoList;
} }
public PlayerWorldLocationInfoOuterClass.PlayerWorldLocationInfo getWorldPlayerLocationInfo() { public PlayerWorldLocationInfoOuterClass.PlayerWorldLocationInfo getWorldPlayerLocationInfo() {
return PlayerWorldLocationInfoOuterClass.PlayerWorldLocationInfo.newBuilder() return PlayerWorldLocationInfoOuterClass.PlayerWorldLocationInfo.newBuilder()
.setSceneId(this.getSceneId()) .setSceneId(this.getSceneId())
...@@ -1211,7 +1264,7 @@ public class Player { ...@@ -1211,7 +1264,7 @@ public class Player {
public BattlePassManager getBattlePassManager(){ public BattlePassManager getBattlePassManager(){
return battlePassManager; return battlePassManager;
} }
public void loadBattlePassManager() { public void loadBattlePassManager() {
if (this.battlePassManager != null) return; if (this.battlePassManager != null) return;
this.battlePassManager = DatabaseHelper.loadBattlePass(this); this.battlePassManager = DatabaseHelper.loadBattlePass(this);
...@@ -1301,7 +1354,7 @@ public class Player { ...@@ -1301,7 +1354,7 @@ public class Player {
public void save() { public void save() {
DatabaseHelper.savePlayer(this); DatabaseHelper.savePlayer(this);
} }
// Called from tokenrsp // Called from tokenrsp
public void loadFromDatabase() { public void loadFromDatabase() {
// Make sure these exist // Make sure these exist
...@@ -1319,7 +1372,7 @@ public class Player { ...@@ -1319,7 +1372,7 @@ public class Player {
} }
//Make sure towerManager's player is online player //Make sure towerManager's player is online player
this.getTowerManager().setPlayer(this); this.getTowerManager().setPlayer(this);
// Load from db // Load from db
this.getAvatars().loadFromDatabase(); this.getAvatars().loadFromDatabase();
this.getInventory().loadFromDatabase(); this.getInventory().loadFromDatabase();
...@@ -1328,7 +1381,7 @@ public class Player { ...@@ -1328,7 +1381,7 @@ public class Player {
this.getFriendsList().loadFromDatabase(); this.getFriendsList().loadFromDatabase();
this.getMailHandler().loadFromDatabase(); this.getMailHandler().loadFromDatabase();
this.getQuestManager().loadFromDatabase(); this.getQuestManager().loadFromDatabase();
this.loadBattlePassManager(); this.loadBattlePassManager();
} }
...@@ -1341,12 +1394,12 @@ public class Player { ...@@ -1341,12 +1394,12 @@ public class Player {
quest.finish(); quest.finish();
} }
getQuestManager().addQuest(35101); getQuestManager().addQuest(35101);
this.setSceneId(3); this.setSceneId(3);
this.getPos().set(GameConstants.START_POSITION); this.getPos().set(GameConstants.START_POSITION);
} }
*/ */
// Create world // Create world
World world = new World(this); World world = new World(this);
world.addPlayer(this); world.addPlayer(this);
...@@ -1383,7 +1436,7 @@ public class Player { ...@@ -1383,7 +1436,7 @@ public class Player {
// First notify packets sent // First notify packets sent
this.setHasSentAvatarDataNotify(true); this.setHasSentAvatarDataNotify(true);
// Set session state // Set session state
session.setState(SessionState.ACTIVE); session.setState(SessionState.ACTIVE);
...@@ -1393,7 +1446,7 @@ public class Player { ...@@ -1393,7 +1446,7 @@ public class Player {
session.close(); session.close();
return; return;
} }
// register // register
getServer().registerPlayer(this); getServer().registerPlayer(this);
getProfile().setPlayer(this); // Set online getProfile().setPlayer(this); // Set online
......
...@@ -32,7 +32,11 @@ public enum ClimateType { ...@@ -32,7 +32,11 @@ public enum ClimateType {
} }
public int getValue() { public int getValue() {
return value; return this.value;
}
public String getShortName() {
return this.name().substring(8).toLowerCase();
} }
public static ClimateType getTypeByValue(int value) { public static ClimateType getTypeByValue(int value) {
...@@ -42,4 +46,9 @@ public enum ClimateType { ...@@ -42,4 +46,9 @@ public enum ClimateType {
public static ClimateType getTypeByName(String name) { public static ClimateType getTypeByName(String name) {
return stringMap.getOrDefault(name, CLIMATE_NONE); return stringMap.getOrDefault(name, CLIMATE_NONE);
} }
public static ClimateType getTypeByShortName(String shortName) {
String name = "CLIMATE_" + shortName.toUpperCase();
return stringMap.getOrDefault(name, CLIMATE_NONE);
}
} }
...@@ -46,8 +46,6 @@ public class Scene { ...@@ -46,8 +46,6 @@ public class Scene {
private int autoCloseTime; private int autoCloseTime;
private int time; private int time;
private ClimateType climate;
private int weather;
private SceneScriptManager scriptManager; private SceneScriptManager scriptManager;
private WorldChallenge challenge; private WorldChallenge challenge;
...@@ -62,7 +60,6 @@ public class Scene { ...@@ -62,7 +60,6 @@ public class Scene {
this.entities = new ConcurrentHashMap<>(); this.entities = new ConcurrentHashMap<>();
this.time = 8 * 60; this.time = 8 * 60;
this.climate = ClimateType.CLIMATE_SUNNY;
this.prevScene = 3; this.prevScene = 3;
this.spawnedEntities = ConcurrentHashMap.newKeySet(); this.spawnedEntities = ConcurrentHashMap.newKeySet();
...@@ -130,22 +127,6 @@ public class Scene { ...@@ -130,22 +127,6 @@ public class Scene {
public void changeTime(int time) { public void changeTime(int time) {
this.time = time % 1440; 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() { public int getPrevScene() {
return prevScene; return prevScene;
......
...@@ -7,11 +7,13 @@ public final class PluginConfig { ...@@ -7,11 +7,13 @@ public final class PluginConfig {
public String name, description, version; public String name, description, version;
public String mainClass; public String mainClass;
public String[] authors; public String[] authors;
public String[] loadAfter;
/** /**
* Attempts to validate this config instance. * Attempts to validate this config instance.
* @return True if the config is valid, false otherwise. * @return True if the config is valid, false otherwise.
*/ */
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public boolean validate() { public boolean validate() {
return name != null && description != null && mainClass != null; return name != null && description != null && mainClass != null;
} }
......
package emu.grasscutter.plugin; package emu.grasscutter.plugin;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.server.event.Event; import emu.grasscutter.server.event.*;
import emu.grasscutter.server.event.EventHandler;
import emu.grasscutter.server.event.HandlerPriority;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Utils;
import lombok.*;
import java.io.File; import javax.annotation.Nullable;
import java.io.FileNotFoundException; import java.io.*;
import java.io.InputStreamReader;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.net.MalformedURLException; import java.net.*;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*; import java.util.*;
import java.util.jar.JarEntry; import java.util.jar.*;
import java.util.jar.JarFile;
import static emu.grasscutter.Configuration.*; import static emu.grasscutter.Configuration.PLUGIN;
/** /**
* Manages the server's plugins and the event system. * Manages the server's plugins and the event system.
*/ */
public final class PluginManager { public final class PluginManager {
private final Map<String, Plugin> plugins = new HashMap<>(); /* All loaded plugins. */
private final List<EventHandler<? extends Event>> listeners = new LinkedList<>(); private final Map<String, Plugin> plugins = new LinkedHashMap<>();
/* All currently registered listeners per plugin. */
private final Map<Plugin, List<EventHandler<? extends Event>>> listeners = new LinkedHashMap<>();
public PluginManager() { public PluginManager() {
this.loadPlugins(); // Load all plugins from the plugins directory. 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. * Loads plugins from the config-specified directory.
*/ */
private void loadPlugins() { private void loadPlugins() {
File pluginsDir = new File(Utils.toFilePath(PLUGIN())); 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()); Grasscutter.getLogger().error("Failed to create plugins directory: " + pluginsDir.getAbsolutePath());
return; return;
} }
File[] files = pluginsDir.listFiles(); File[] files = pluginsDir.listFiles();
if(files == null) { if (files == null) {
// The directory is empty, there aren't any plugins to load. // The directory is empty, there aren't any plugins to load.
return; return;
} }
List<File> plugins = Arrays.stream(files) List<File> plugins = Arrays.stream(files)
.filter(file -> file.getName().endsWith(".jar")) .filter(file -> file.getName().endsWith(".jar"))
.toList(); .toList();
URL[] pluginNames = new URL[plugins.size()]; URL[] pluginNames = new URL[plugins.size()];
plugins.forEach(plugin -> { plugins.forEach(plugin -> {
...@@ -59,36 +65,59 @@ public final class PluginManager { ...@@ -59,36 +65,59 @@ public final class PluginManager {
} }
}); });
// Create a class loader for the plugins.
URLClassLoader classLoader = new URLClassLoader(pluginNames); URLClassLoader classLoader = new URLClassLoader(pluginNames);
// Create a list of plugins that require dependencies.
List<PluginData> dependencies = new ArrayList<>();
plugins.forEach(plugin -> { // Initialize all plugins.
for(var plugin : plugins) {
try { try {
URL url = plugin.toURI().toURL(); URL url = plugin.toURI().toURL();
try (URLClassLoader loader = new URLClassLoader(new URL[]{url})) { 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()); InputStreamReader fileReader = new InputStreamReader(configFile.openStream());
// Create a plugin config instance from the config file.
PluginConfig pluginConfig = Grasscutter.getGsonFactory().fromJson(fileReader, PluginConfig.class); PluginConfig pluginConfig = Grasscutter.getGsonFactory().fromJson(fileReader, PluginConfig.class);
if(!pluginConfig.validate()) { // Check if the plugin config is valid.
if (!pluginConfig.validate()) {
Utils.logObject(pluginConfig); Utils.logObject(pluginConfig);
Grasscutter.getLogger().warn("Plugin " + plugin.getName() + " has an invalid config file."); Grasscutter.getLogger().warn("Plugin " + plugin.getName() + " has an invalid config file.");
return; return;
} }
// Create a JAR file instance from the plugin's URL.
JarFile jarFile = new JarFile(plugin); JarFile jarFile = new JarFile(plugin);
// Load all class files from the JAR file.
Enumeration<JarEntry> entries = jarFile.entries(); Enumeration<JarEntry> entries = jarFile.entries();
while(entries.hasMoreElements()) { while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement(); 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("/", "."); String className = entry.getName().replace(".class", "").replace("/", ".");
classLoader.loadClass(className); // Use the same class loader for ALL plugins. classLoader.loadClass(className); // Use the same class loader for ALL plugins.
} }
// Create a plugin instance.
Class<?> pluginClass = classLoader.loadClass(pluginConfig.mainClass); Class<?> pluginClass = classLoader.loadClass(pluginConfig.mainClass);
Plugin pluginInstance = (Plugin) pluginClass.getDeclaredConstructor().newInstance(); 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); this.loadPlugin(pluginInstance, PluginIdentifier.fromPluginConfig(pluginConfig), loader);
fileReader.close(); // Close the file reader.
} catch (ClassNotFoundException ignored) { } catch (ClassNotFoundException ignored) {
Grasscutter.getLogger().warn("Plugin " + plugin.getName() + " has an invalid main class."); Grasscutter.getLogger().warn("Plugin " + plugin.getName() + " has an invalid main class.");
} catch (FileNotFoundException ignored) { } catch (FileNotFoundException ignored) {
...@@ -97,29 +126,68 @@ public final class PluginManager { ...@@ -97,29 +126,68 @@ public final class PluginManager {
} catch (Exception exception) { } catch (Exception exception) {
Grasscutter.getLogger().error("Failed to load plugin: " + plugin.getName(), 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. * Load the specified plugin.
*
* @param plugin The plugin instance. * @param plugin The plugin instance.
*/ */
private void loadPlugin(Plugin plugin, PluginIdentifier identifier, URLClassLoader classLoader) { private void loadPlugin(Plugin plugin, PluginIdentifier identifier, URLClassLoader classLoader) {
Grasscutter.getLogger().info("Loading plugin: " + identifier.name); Grasscutter.getLogger().info("Loading plugin: " + identifier.name);
// Add the plugin's identifier. // Add the plugin's identifier.
try { try {
Class<Plugin> pluginClass = Plugin.class; Class<Plugin> pluginClass = Plugin.class;
Method method = pluginClass.getDeclaredMethod("initializePlugin", PluginIdentifier.class, URLClassLoader.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) { } catch (Exception ignored) {
Grasscutter.getLogger().warn("Failed to add plugin identifier: " + identifier.name); Grasscutter.getLogger().warn("Failed to add plugin identifier: " + identifier.name);
} }
// Add the plugin to the list of loaded plugins. // Add the plugin to the list of loaded plugins.
this.plugins.put(identifier.name, plugin); 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. // 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 { ...@@ -128,67 +196,121 @@ public final class PluginManager {
public void enablePlugins() { public void enablePlugins() {
this.plugins.forEach((name, plugin) -> { this.plugins.forEach((name, plugin) -> {
Grasscutter.getLogger().info("Enabling plugin: " + name); 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. * Disables all registered plugins.
*/ */
public void disablePlugins() { public void disablePlugins() {
this.plugins.forEach((name, plugin) -> { this.plugins.forEach((name, plugin) -> {
Grasscutter.getLogger().info("Disabling plugin: " + name); 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. * Registers a plugin's event listener.
*
* @param plugin The plugin registering the listener.
* @param listener The event listener. * @param listener The event listener.
*/ */
public void registerListener(EventHandler<? extends Event> listener) { public void registerListener(Plugin plugin, EventHandler<? extends Event> listener) {
this.listeners.add(listener); this.listeners.get(plugin).add(listener);
} }
/** /**
* Invoke the provided event on all registered event listeners. * Invoke the provided event on all registered event listeners.
*
* @param event The event to invoke. * @param event The event to invoke.
*/ */
public void invokeEvent(Event event) { public void invokeEvent(Event event) {
EnumSet.allOf(HandlerPriority.class) EnumSet.allOf(HandlerPriority.class)
.forEach(priority -> this.checkAndFilter(event, priority)); .forEach(priority -> this.checkAndFilter(event, priority));
} }
/** /**
* Check an event to handlers for the 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. * @param priority The priority to call for.
*/ */
private void checkAndFilter(Event event, HandlerPriority priority) { private void checkAndFilter(Event event, HandlerPriority priority) {
this.listeners.stream() // Create a collection of listeners.
.filter(handler -> handler.handles().isInstance(event)) List<EventHandler<? extends Event>> listeners = new LinkedList<>();
.filter(handler -> handler.getPriority() == priority)
.toList().forEach(handler -> this.invokeHandler(event, handler)); // 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. * Gets a plugin's instance by its name.
*
* @param name The name of the plugin. * @param name The name of the plugin.
* @return Either null, or the plugin's instance. * @return Either null, or the plugin's instance.
*/ */
@Nullable
public Plugin getPlugin(String name) { public Plugin getPlugin(String name) {
return this.plugins.get(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. * 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. * @param handler The handler to invoke.
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private <T extends Event> void invokeHandler(Event event, EventHandler<T> handler) { private <T extends Event> void invokeHandler(Event event, EventHandler<T> handler) {
if(!event.isCanceled() || if (!event.isCanceled() ||
(event.isCanceled() && handler.ignoresCanceled()) (event.isCanceled() && handler.ignoresCanceled())
) handler.getCallback().consume((T) event); ) handler.getCallback().consume((T) event);
} }
} }
package emu.grasscutter.server.event; package emu.grasscutter.server.event;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.plugin.Plugin;
import emu.grasscutter.utils.EventConsumer; import emu.grasscutter.utils.EventConsumer;
public final class EventHandler<T extends Event> { public final class EventHandler<T extends Event> {
...@@ -75,7 +76,7 @@ public final class EventHandler<T extends Event> { ...@@ -75,7 +76,7 @@ public final class EventHandler<T extends Event> {
/** /**
* Registers the handler into the PluginManager. * Registers the handler into the PluginManager.
*/ */
public void register() { public void register(Plugin plugin) {
Grasscutter.getPluginManager().registerListener(this); Grasscutter.getPluginManager().registerListener(plugin, this);
} }
} }
\ No newline at end of file
...@@ -12,8 +12,8 @@ public class PacketSceneAreaWeatherNotify extends BasePacket { ...@@ -12,8 +12,8 @@ public class PacketSceneAreaWeatherNotify extends BasePacket {
super(PacketOpcodes.SceneAreaWeatherNotify); super(PacketOpcodes.SceneAreaWeatherNotify);
SceneAreaWeatherNotify proto = SceneAreaWeatherNotify.newBuilder() SceneAreaWeatherNotify proto = SceneAreaWeatherNotify.newBuilder()
.setWeatherAreaId(player.getScene().getWeather()) .setWeatherAreaId(player.getWeatherId())
.setClimateType(player.getScene().getClimate().getValue()) .setClimateType(player.getClimate().getValue())
.build(); .build();
this.setData(proto); this.setData(proto);
......
...@@ -76,16 +76,14 @@ ...@@ -76,16 +76,14 @@
"itemLevel": "Invalid itemLevel.", "itemLevel": "Invalid itemLevel.",
"itemRefinement": "Invalid itemRefinement.", "itemRefinement": "Invalid itemRefinement.",
"playerId": "Invalid player ID.", "playerId": "Invalid player ID.",
"uid": "Invalid UID." "uid": "Invalid UID.",
"id": "Invalid ID."
} }
}, },
"execution": { "execution": {
"uid_error": "Invalid UID.",
"player_exist_error": "Player not found.", "player_exist_error": "Player not found.",
"player_offline_error": "Player is not online.", "player_offline_error": "Player is not online.",
"item_id_error": "Invalid item ID.",
"item_player_exist_error": "Invalid item or UID.", "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.", "player_exist_offline_error": "Player not found or is not online.",
"argument_error": "Invalid arguments.", "argument_error": "Invalid arguments.",
"clear_target": "Target cleared.", "clear_target": "Target cleared.",
...@@ -99,17 +97,16 @@ ...@@ -99,17 +97,16 @@
"status": { "status": {
"enabled": "Enabled", "enabled": "Enabled",
"disabled": "Disabled", "disabled": "Disabled",
"help": "Help", "help": "Help",
"success": "Success" "success": "Success"
}, },
"account": { "account": {
"modify": "Modify user accounts", "command_usage": "Usage: account <create|delete> <username> [UID]",
"invalid": "Invalid UID.", "invalid": "Invalid UID.",
"exists": "An account with this username and/or UID already exists.", "exists": "An account with this username and/or UID already exists.",
"create": "Account created with UID %s.", "create": "Account created with UID %s.",
"delete": "Account deleted.", "delete": "Account deleted.",
"no_account": "Account not found.", "no_account": "Account not found.",
"command_usage": "Usage: account <create|delete> <username> [UID]",
"description": "Modify user accounts" "description": "Modify user accounts"
}, },
"broadcast": { "broadcast": {
...@@ -387,25 +384,25 @@ ...@@ -387,25 +384,25 @@
"description": "Unlock all levels of tower" "description": "Unlock all levels of tower"
}, },
"weather": { "weather": {
"usage": "Usage: weather <climate type(weatherId)> <weather type(climateId)>\nWeather types 0: None, 1: Sunny, 2: Cloudy, 3: Rain, 4: Thunderstorm, 5: Snow, 6: Mist", "usage": "Usage: weather [weatherId] [climateType]\nWeather IDs can be found in WeatherExcelConfigData.json.\nClimate types: sunny, cloudy, rain, thunderstorm, snow, mist",
"success": "Changed climate type to %s with weather type %s.", "success": "Set weather ID to %s with climate type %s.",
"invalid_id": "Invalid ID.", "status": "Current weather ID is %s with climate type %s.",
"description": "Changes the weather" "description": "Changes weather ID and climate type. Weather IDs can be found in WeatherExcelConfigData.json.\nClimate types: sunny, cloudy, rain, thunderstorm, snow, mist"
}, },
"ban": { "ban": {
"description": "Ban a player", "command_usage": "Usage: ban <playerId> [timestamp] [reason]",
"success": "Successful.", "success": "Successful.",
"failure": "Failed, player not found.", "failure": "Failed, player not found.",
"invalid_time": "Unable to parse timestamp.", "invalid_time": "Unable to parse timestamp.",
"invalid_player_id": "Unable to parse player ID.", "invalid_player_id": "Unable to parse player ID.",
"command_usage": "Usage: ban <playerId> [timestamp] [reason]" "description": "Ban a player"
}, },
"unban": { "unban": {
"description": "Unban a player", "command_usage": "Usage: unban <playerId>",
"success": "Successful.", "success": "Successful.",
"failure": "Failed, player not found.", "failure": "Failed, player not found.",
"invalid_player_id": "Unable to parse player ID.", "invalid_player_id": "Unable to parse player ID.",
"command_usage": "Usage: unban <playerId>" "description": "Unban a player"
} }
}, },
"gacha": { "gacha": {
......
...@@ -76,16 +76,14 @@ ...@@ -76,16 +76,14 @@
"itemLevel": "Niveau de l'objet invalide.", "itemLevel": "Niveau de l'objet invalide.",
"itemRefinement": "Raffinement de l'objet invalide.", "itemRefinement": "Raffinement de l'objet invalide.",
"playerId": "ID du joueur invalide.", "playerId": "ID du joueur invalide.",
"uid": "UID invalide." "uid": "UID invalide.",
"id": "ID invalide."
} }
}, },
"execution": { "execution": {
"uid_error": "UID invalide.",
"player_exist_error": "Joueur introuvable.", "player_exist_error": "Joueur introuvable.",
"player_offline_error": "Le joueur n'est pas connecté.", "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.", "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é.", "player_exist_offline_error": "Le joueur est introuvable ou n'est pas connecté.",
"argument_error": "Arguments invalides.", "argument_error": "Arguments invalides.",
"clear_target": "Cible réinitialisée.", "clear_target": "Cible réinitialisée.",
...@@ -387,10 +385,10 @@ ...@@ -387,10 +385,10 @@
"description": "Débloque tous les couloirs de l'abysse" "description": "Débloque tous les couloirs de l'abysse"
}, },
"weather": { "weather": {
"usage": "Usage: weather <climate type(weatherId)> <weather type(climateId)>\nTypes de météo 0: Aucun, 1: Ensoleillé, 2: Nuageux, 3: Pluvieux, 4: Orageux, 5: Neige, 6: Brouillard", "description": "Change la météo. Weather IDs can be found in WeatherExcelConfigData.json.\nClimate types: sunny, cloudy, rain, thunderstorm, snow, mist.",
"success": "Le type de climat a été changé à %s avec le type de météo %s.", "usage": "Utilisation: weather [weatherId] [climateType]\nWeather IDs can be found in WeatherExcelConfigData.json.\nClimate types: sunny, cloudy, rain, thunderstorm, snow, mist.",
"invalid_id": "ID invalide.", "success": "Set weather ID to %s with climate type %s.",
"description": "Change la météo" "status": "Current weather ID is %s with climate type %s."
}, },
"ban": { "ban": {
"description": "Bannis un joueur", "description": "Bannis un joueur",
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment