mirror of
https://github.com/Grasscutters/Grasscutter.git
synced 2025-01-25 09:02:59 +08:00
Merge remote-tracking branch 'origin/development' into development
This commit is contained in:
commit
20b2554e39
@ -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 @@
|
|||||||
|
|
||||||
* [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
|
|||||||
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
|
||||||
|
@ -1,49 +1,63 @@
|
|||||||
{
|
{
|
||||||
"$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": {
|
||||||
|
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,25 +1,34 @@
|
|||||||
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;
|
|||||||
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 final Gson gson = new GsonBuilder().setPrettyPrinting().create();
|
private static Language language;
|
||||||
public static final File configFile = new File("./config.json");
|
|
||||||
|
|
||||||
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 int day; // Current day of week.
|
||||||
private static GameServer gameServer;
|
|
||||||
private static PluginManager pluginManager;
|
|
||||||
private static AuthenticationSystem authenticationSystem;
|
|
||||||
private static PermissionHandler permissionHandler;
|
|
||||||
|
|
||||||
public static final Reflections reflector = new Reflections("emu.grasscutter");
|
private static HttpServer httpServer;
|
||||||
public static ConfigContainer config;
|
private static GameServer gameServer;
|
||||||
|
private static PluginManager pluginManager;
|
||||||
static {
|
private static AuthenticationSystem authenticationSystem;
|
||||||
// Declare logback configuration.
|
private static PermissionHandler permissionHandler;
|
||||||
System.setProperty("logback.configurationFile", "src/main/resources/logback.xml");
|
|
||||||
|
|
||||||
// Load server configuration.
|
public static final Reflections reflector = new Reflections("emu.grasscutter");
|
||||||
Grasscutter.loadConfig();
|
public static ConfigContainer config;
|
||||||
// Attempt to update configuration.
|
|
||||||
ConfigContainer.updateConfig();
|
|
||||||
|
|
||||||
// Load translation files.
|
static {
|
||||||
Grasscutter.loadLanguage();
|
// Declare logback configuration.
|
||||||
|
System.setProperty("logback.configurationFile", "src/main/resources/logback.xml");
|
||||||
|
|
||||||
// Check server structure.
|
// Load server configuration.
|
||||||
Utils.startupCheck();
|
Grasscutter.loadConfig();
|
||||||
}
|
// Attempt to update configuration.
|
||||||
|
ConfigContainer.updateConfig();
|
||||||
|
|
||||||
public static void main(String[] args) throws Exception {
|
// Load translation files.
|
||||||
Crypto.loadKeys(); // Load keys from buffers.
|
Grasscutter.loadLanguage();
|
||||||
|
|
||||||
// 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();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// Check server structure.
|
||||||
* Server shutdown event.
|
Utils.startupCheck();
|
||||||
*/
|
}
|
||||||
private static void onShutdown() {
|
|
||||||
// Disable all plugins.
|
|
||||||
pluginManager.disablePlugins();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
public static void main(String[] args) throws Exception {
|
||||||
* Methods for the language system component.
|
Crypto.loadKeys(); // Load keys from buffers.
|
||||||
*/
|
|
||||||
|
|
||||||
public static void loadLanguage() {
|
|
||||||
var locale = config.language.language;
|
|
||||||
language = Language.getLanguage(Utils.getLanguageCode(locale));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Methods for the configuration system component.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
// Parse arguments.
|
||||||
* Attempts to load the configuration from a file.
|
boolean exitEarly = false;
|
||||||
*/
|
for (String arg : args) {
|
||||||
public static void loadConfig() {
|
switch (arg.toLowerCase()) {
|
||||||
// Check if config.json exists. If not, we generate a new config.
|
case "-handbook" -> {
|
||||||
if (!configFile.exists()) {
|
Tools.createGmHandbook();
|
||||||
getLogger().info("config.json could not be found. Generating a default configuration ...");
|
exitEarly = true;
|
||||||
config = new ConfigContainer();
|
}
|
||||||
Grasscutter.saveConfig(config);
|
case "-gachamap" -> {
|
||||||
return;
|
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.
|
// Exit early if argument sets it.
|
||||||
try (FileReader file = new FileReader(configFile)) {
|
if (exitEarly) System.exit(0);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// Initialize server.
|
||||||
* Saves the provided server configuration.
|
Grasscutter.getLogger().info(translate("messages.status.starting"));
|
||||||
* @param config The configuration to save, or null for a new one.
|
Grasscutter.getLogger().info(translate("messages.status.game_version", GameConstants.VERSION));
|
||||||
*/
|
Grasscutter.getLogger().info(translate("messages.status.version", BuildConfig.VERSION, BuildConfig.GIT_HASH));
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
// Load all resources.
|
||||||
* Getters for the various server components.
|
Grasscutter.updateDayOfWeek();
|
||||||
*/
|
ResourceLoader.loadAll();
|
||||||
|
ScriptLoader.init();
|
||||||
public static ConfigContainer getConfig() {
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Language getLanguage() {
|
// Initialize database.
|
||||||
return language;
|
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;
|
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() {
|
|
||||||
return authenticationSystem;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static PermissionHandler getPermissionHandler() {
|
public static AuthenticationSystem getAuthenticationSystem() {
|
||||||
return permissionHandler;
|
return authenticationSystem;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getCurrentDayOfWeek() {
|
public static PermissionHandler getPermissionHandler() {
|
||||||
return day;
|
return permissionHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Utility methods.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public static void updateDayOfWeek() {
|
|
||||||
Calendar calendar = Calendar.getInstance();
|
|
||||||
day = calendar.get(Calendar.DAY_OF_WEEK);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void startConsole() {
|
public static int getCurrentDayOfWeek() {
|
||||||
// Console should not start in dispatch only mode.
|
return day;
|
||||||
if (SERVER.runMode == ServerRunMode.DISPATCH_ONLY) {
|
}
|
||||||
getLogger().info(translate("messages.dispatch.no_commands_error"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
getLogger().info(translate("messages.status.done"));
|
/*
|
||||||
String input = null;
|
* Utility methods.
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
isLastInterrupted = false;
|
public static void updateDayOfWeek() {
|
||||||
try {
|
Calendar calendar = Calendar.getInstance();
|
||||||
CommandMap.getInstance().invoke(null, null, input);
|
day = calendar.get(Calendar.DAY_OF_WEEK);
|
||||||
} catch (Exception e) {
|
}
|
||||||
Grasscutter.getLogger().error(translate("messages.game.command_error"), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
public static void startConsole() {
|
||||||
* Sets the authentication system for the server.
|
// Console should not start in dispatch only mode.
|
||||||
* @param authenticationSystem The authentication system to use.
|
if (SERVER.runMode == ServerRunMode.DISPATCH_ONLY) {
|
||||||
*/
|
getLogger().info(translate("messages.dispatch.no_commands_error"));
|
||||||
public static void setAuthenticationSystem(AuthenticationSystem authenticationSystem) {
|
return;
|
||||||
Grasscutter.authenticationSystem = authenticationSystem;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
getLogger().info(translate("messages.status.done"));
|
||||||
* Sets the permission handler for the server.
|
String input = null;
|
||||||
* @param permissionHandler The permission handler to use.
|
boolean isLastInterrupted = false;
|
||||||
*/
|
while (config.server.game.enableConsole) {
|
||||||
public static void setPermissionHandler(PermissionHandler permissionHandler) {
|
try {
|
||||||
Grasscutter.permissionHandler = permissionHandler;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
isLastInterrupted = false;
|
||||||
* Enums for the configuration.
|
try {
|
||||||
*/
|
CommandMap.getInstance().invoke(null, null, input);
|
||||||
|
} catch (Exception e) {
|
||||||
public enum ServerRunMode {
|
Grasscutter.getLogger().error(translate("messages.game.command_error"), e);
|
||||||
HYBRID, DISPATCH_ONLY, GAME_ONLY
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
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.*;
|
|||||||
@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 {
|
|||||||
// 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 {
|
|||||||
*/
|
*/
|
||||||
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 {
|
|||||||
// 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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
}
|
}
|
||||||
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 {
|
|||||||
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 {
|
|||||||
|
|
||||||
// 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 {
|
|||||||
}
|
}
|
||||||
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 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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 {
|
|||||||
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 {
|
|||||||
}
|
}
|
||||||
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:
|
||||||
|
@ -1,47 +1,44 @@
|
|||||||
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:
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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.setWeather(weatherId, climate);
|
||||||
targetPlayer.getScene().setClimate(climate);
|
climate = targetPlayer.getClimate(); // Might be different to what we set
|
||||||
targetPlayer.getScene().broadcastPacket(new PacketSceneAreaWeatherNotify(targetPlayer));
|
CommandHandler.sendTranslatedMessage(sender, "commands.weather.success", Integer.toString(weatherId), climate.getShortName());
|
||||||
CommandHandler.sendMessage(sender, translate(sender, "commands.weather.success", Integer.toString(weatherId), Integer.toString(climateId)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 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 {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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;
|
||||||
}
|
}
|
||||||
|
26
src/main/java/emu/grasscutter/data/excels/WeatherData.java
Normal file
26
src/main/java/emu/grasscutter/data/excels/WeatherData.java
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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.*;
|
|||||||
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;
|
|||||||
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 {
|
|||||||
@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 {
|
|||||||
@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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
}
|
}
|
||||||
return towerData;
|
return towerData;
|
||||||
}
|
}
|
||||||
|
|
||||||
public QuestManager getQuestManager() {
|
public QuestManager getQuestManager() {
|
||||||
return questManager;
|
return questManager;
|
||||||
}
|
}
|
||||||
@ -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 {
|
|||||||
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 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
|||||||
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 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} 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 {
|
|||||||
}
|
}
|
||||||
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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
}
|
}
|
||||||
//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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
|
|
||||||
// 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 {
|
|||||||
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 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
|||||||
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 {
|
|||||||
|
|
||||||
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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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;
|
||||||
}
|
}
|
||||||
|
@ -1,54 +1,60 @@
|
|||||||
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 {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 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 {
|
|||||||
} 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 {
|
|||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
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> {
|
|||||||
/**
|
/**
|
||||||
* 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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 @@
|
|||||||
"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 @@
|
|||||||
"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 @@
|
|||||||
"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 @@
|
|||||||
"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 @@
|
|||||||
"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",
|
||||||
|
@ -70,16 +70,14 @@
|
|||||||
"itemLevel": "Błędny poziom przedmiotu.",
|
"itemLevel": "Błędny poziom przedmiotu.",
|
||||||
"itemRefinement": "Błędne ulepszenie.",
|
"itemRefinement": "Błędne ulepszenie.",
|
||||||
"playerId": "Błędne playerId.",
|
"playerId": "Błędne playerId.",
|
||||||
"uid": "Błędne UID."
|
"uid": "Błędne UID.",
|
||||||
|
"id": "Błędne ID."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"execution": {
|
"execution": {
|
||||||
"uid_error": "Błędne UID.",
|
|
||||||
"player_exist_error": "Gracz nie znaleziony.",
|
"player_exist_error": "Gracz nie znaleziony.",
|
||||||
"player_offline_error": "Gracz nie jest online.",
|
"player_offline_error": "Gracz nie jest online.",
|
||||||
"item_id_error": "Błędne ID przedmiotu.",
|
|
||||||
"item_player_exist_error": "Błędny przedmiot lub UID.",
|
"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.",
|
"player_exist_offline_error": "Gracz nie znaleziony lub jest offline.",
|
||||||
"argument_error": "Błędne argumenty.",
|
"argument_error": "Błędne argumenty.",
|
||||||
"clear_target": "Cel wyczyszczony.",
|
"clear_target": "Cel wyczyszczony.",
|
||||||
@ -291,9 +289,10 @@
|
|||||||
"success": "Przeteleportowano %s do %s, %s, %s w scenie %s"
|
"success": "Przeteleportowano %s do %s, %s, %s w scenie %s"
|
||||||
},
|
},
|
||||||
"weather": {
|
"weather": {
|
||||||
"usage": "Użycie: weather <climate type(weatherId)> <weather type(climateId)>\nWeather types 0: None, 1: Sunny, 2: Cloudy, 3: Rain, 4: Thunderstorm, 5: Snow, 6: Mist",
|
"description": "Changes the weather.Weather 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.",
|
"usage": "Usage: weather [weatherId] [climateType]\nWeather IDs can be found in WeatherExcelConfigData.json.\nClimate types: sunny, cloudy, rain, thunderstorm, snow, mist.",
|
||||||
"invalid_id": "Błędne ID."
|
"success": "Set weather ID to %s with climate type %s.",
|
||||||
|
"status": "Current weather ID is %s with climate type %s."
|
||||||
},
|
},
|
||||||
"drop": {
|
"drop": {
|
||||||
"command_usage": "Użycie: drop <ID przedmiotu|nazwa przedmiotu> [ilość]",
|
"command_usage": "Użycie: drop <ID przedmiotu|nazwa przedmiotu> [ilość]",
|
||||||
|
@ -76,16 +76,14 @@
|
|||||||
"itemLevel": "Некорректный уровень предмета (itemLevel).",
|
"itemLevel": "Некорректный уровень предмета (itemLevel).",
|
||||||
"itemRefinement": "Некорректный уровень пробуждения предмета (itemRefinement).",
|
"itemRefinement": "Некорректный уровень пробуждения предмета (itemRefinement).",
|
||||||
"playerId": "Некорректный ID игрока.",
|
"playerId": "Некорректный ID игрока.",
|
||||||
"uid": "Некорректный UID."
|
"uid": "Некорректный UID.",
|
||||||
|
"id": "Некорректный ID."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"execution": {
|
"execution": {
|
||||||
"uid_error": "Некорректный UID.",
|
|
||||||
"player_exist_error": "Игрок не найден.",
|
"player_exist_error": "Игрок не найден.",
|
||||||
"player_offline_error": "Игрок не в сети.",
|
"player_offline_error": "Игрок не в сети.",
|
||||||
"item_id_error": "Некорректный ID предмета.",
|
|
||||||
"item_player_exist_error": "Некорректный предмет или UID.",
|
"item_player_exist_error": "Некорректный предмет или UID.",
|
||||||
"entity_id_error": "Некорректный ID сущности.",
|
|
||||||
"player_exist_offline_error": "Игрок не был найден или не в сети.",
|
"player_exist_offline_error": "Игрок не был найден или не в сети.",
|
||||||
"argument_error": "Некорректные аргументы.",
|
"argument_error": "Некорректные аргументы.",
|
||||||
"clear_target": "Цель была удалена.",
|
"clear_target": "Цель была удалена.",
|
||||||
@ -387,10 +385,10 @@
|
|||||||
"description": "Открывает все уровни башни"
|
"description": "Открывает все уровни башни"
|
||||||
},
|
},
|
||||||
"weather": {
|
"weather": {
|
||||||
"usage": "Применение: weather <тип_климата(Id погоды)> <тип_погоды(Id климата)>\nТипы погоды 0: Отсутствует, 1: Солнечная, 2: Пасмурная, 3: Дождливая, 4: Грозовая, 5: Снежная, 6: Туманная",
|
"description": "Изменяет погоду.Weather IDs can be found in WeatherExcelConfigData.json.\nClimate types: sunny, cloudy, rain, thunderstorm, snow, mist.",
|
||||||
"success": "Тип климата был изменен на %s, тип погоды: %s.",
|
"usage": "Usage: weather [weatherId] [climateType]\nWeather IDs can be found in WeatherExcelConfigData.json.\nClimate types: sunny, cloudy, rain, thunderstorm, snow, mist.",
|
||||||
"invalid_id": "Некорректный ID.",
|
"success": "Set weather ID to %s with climate type %s.",
|
||||||
"description": "Изменяет погоду"
|
"status": "Current weather ID is %s with climate type %s."
|
||||||
},
|
},
|
||||||
"ban": {
|
"ban": {
|
||||||
"description": "Банит игрока",
|
"description": "Банит игрока",
|
||||||
|
@ -76,16 +76,14 @@
|
|||||||
"itemLevel": "无效的物品等级。",
|
"itemLevel": "无效的物品等级。",
|
||||||
"itemRefinement": "无效的物品精炼等级。",
|
"itemRefinement": "无效的物品精炼等级。",
|
||||||
"playerId": "无效的玩家ID。",
|
"playerId": "无效的玩家ID。",
|
||||||
"uid": "无效的UID。"
|
"uid": "无效的UID。",
|
||||||
|
"id": "无效的ID。"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"execution": {
|
"execution": {
|
||||||
"uid_error": "无效的UID。",
|
|
||||||
"player_exist_error": "玩家不存在。",
|
"player_exist_error": "玩家不存在。",
|
||||||
"player_offline_error": "玩家已离线。",
|
"player_offline_error": "玩家已离线。",
|
||||||
"item_id_error": "无效的物品ID。",
|
|
||||||
"item_player_exist_error": "无效的物品/玩家UID。",
|
"item_player_exist_error": "无效的物品/玩家UID。",
|
||||||
"entity_id_error": "无效的实体ID。",
|
|
||||||
"player_exist_offline_error": "玩家不存在或已离线。",
|
"player_exist_offline_error": "玩家不存在或已离线。",
|
||||||
"argument_error": "无效的参数。",
|
"argument_error": "无效的参数。",
|
||||||
"clear_target": "目标已清除。",
|
"clear_target": "目标已清除。",
|
||||||
@ -99,17 +97,16 @@
|
|||||||
"status": {
|
"status": {
|
||||||
"enabled": "已启用",
|
"enabled": "已启用",
|
||||||
"disabled": "未启用",
|
"disabled": "未启用",
|
||||||
"help": "帮助",
|
"help": "帮助",
|
||||||
"success": "成功"
|
"success": "成功"
|
||||||
},
|
},
|
||||||
"account": {
|
"account": {
|
||||||
"modify": "修改用户账号",
|
"command_usage": "用法:account <create|delete> <用户名> [UID]",
|
||||||
"invalid": "无效的UID。",
|
"invalid": "无效的UID。",
|
||||||
"exists": "具有此用户名和/或 UID 的账号已存在。",
|
"exists": "具有此用户名和/或 UID 的账号已存在。",
|
||||||
"create": "已创建 UID 为 %s 的账号。",
|
"create": "已创建 UID 为 %s 的账号。",
|
||||||
"delete": "账号已删除。",
|
"delete": "账号已删除。",
|
||||||
"no_account": "账号不存在。",
|
"no_account": "账号不存在。",
|
||||||
"command_usage": "用法:account <create|delete> <用户名> [UID]",
|
|
||||||
"description": "创建或删除账号"
|
"description": "创建或删除账号"
|
||||||
},
|
},
|
||||||
"broadcast": {
|
"broadcast": {
|
||||||
@ -121,7 +118,7 @@
|
|||||||
"usage": "用法:changescene <场景ID>",
|
"usage": "用法:changescene <场景ID>",
|
||||||
"already_in_scene": "你已经在这个场景中了。",
|
"already_in_scene": "你已经在这个场景中了。",
|
||||||
"success": "已切换至场景 %s。",
|
"success": "已切换至场景 %s。",
|
||||||
"exists_error": "此场景不存在。",
|
"exists_error": "场景不存在。",
|
||||||
"description": "切换指定场景"
|
"description": "切换指定场景"
|
||||||
},
|
},
|
||||||
"clear": {
|
"clear": {
|
||||||
@ -148,8 +145,8 @@
|
|||||||
"enter_dungeon": {
|
"enter_dungeon": {
|
||||||
"usage": "用法:enterdungeon <秘境ID>",
|
"usage": "用法:enterdungeon <秘境ID>",
|
||||||
"changed": "已进入秘境 %s。",
|
"changed": "已进入秘境 %s。",
|
||||||
"not_found_error": "此秘境不存在。",
|
"not_found_error": "秘境不存在。",
|
||||||
"in_dungeon_error": "你已经在秘境中了。",
|
"in_dungeon_error": "你已经在这个秘境中了。",
|
||||||
"description": "进入指定秘境"
|
"description": "进入指定秘境"
|
||||||
},
|
},
|
||||||
"giveAll": {
|
"giveAll": {
|
||||||
@ -387,25 +384,25 @@
|
|||||||
"description": "解锁深境螺旋"
|
"description": "解锁深境螺旋"
|
||||||
},
|
},
|
||||||
"weather": {
|
"weather": {
|
||||||
"usage": "用法:weather <气候类型(天气ID)> <天气类型(气候ID)>\n天气类型 0: 无, 1: 晴天, 2: 多云, 3: 雨, 4: 雷雨, 5: 雪, 6: 雾",
|
"usage": "用法:weather [天气ID] [气候类型]\n天气ID可以在 WeatherExcelConfigData.json 中找到。\n气候类型:sunny(晴天), cloudy(多云), rain(雨), thunderstorm(雷雨), snow(雪), mist(雾)",
|
||||||
"success": "已更改气候类型为 %s,天气类型为 %s。",
|
"success": "已设置天气ID 为 %s,气候类型为 %s。",
|
||||||
"invalid_id": "无效的ID。",
|
"status": "当前天气ID 为 %s,气候类型为 %s。",
|
||||||
"description": "更改天气"
|
"description": "更改天气ID和气候类型。天气ID可以在 WeatherExcelConfigData.json 中找到。\n气候类型:sunny(晴天), cloudy(多云), rain(雨), thunderstorm(雷雨), snow(雪), mist(雾)"
|
||||||
},
|
},
|
||||||
"ban": {
|
"ban": {
|
||||||
"description": "封禁玩家",
|
"command_usage": "用法:ban <玩家ID> [时间] [原因]",
|
||||||
"success": "封禁玩家成功。",
|
"success": "成功封禁玩家。",
|
||||||
"failure": "封禁玩家失败,因为无法获取到其账户。",
|
"failure": "封禁玩家失败,因为玩家不存在。",
|
||||||
"invalid_time": "无法解析时间戳。",
|
"invalid_time": "无法解析时间戳。",
|
||||||
"invalid_player_id": "无法解析玩家 ID。",
|
"invalid_player_id": "无法解析玩家ID。",
|
||||||
"command_usage": "用法:ban <玩家ID> [时间] [原因]"
|
"description": "封禁玩家"
|
||||||
},
|
},
|
||||||
"unban": {
|
"unban": {
|
||||||
"description": "取消玩家的封禁",
|
"command_usage": "用法:unban <玩家ID>",
|
||||||
"success": "取消玩家的封禁成功。",
|
"success": "成功取消玩家的封禁。",
|
||||||
"failure": "取消玩家的封禁失败,因为无法获取到其账户。",
|
"failure": "取消玩家的封禁失败,因为玩家不存在。",
|
||||||
"invalid_player_id": "无法解析玩家 ID。",
|
"invalid_player_id": "无法解析玩家ID。",
|
||||||
"command_usage": "用法:unban <玩家ID>"
|
"description": "取消玩家的封禁"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"gacha": {
|
"gacha": {
|
||||||
|
@ -75,16 +75,14 @@
|
|||||||
"itemLevel": "無效的物品等級。",
|
"itemLevel": "無效的物品等級。",
|
||||||
"itemRefinement": "無效的物品精煉度。",
|
"itemRefinement": "無效的物品精煉度。",
|
||||||
"playerId": "無效的玩家ID。",
|
"playerId": "無效的玩家ID。",
|
||||||
"uid": "無效的UID。"
|
"uid": "無效的UID。",
|
||||||
|
"id": "無效的ID。"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"execution": {
|
"execution": {
|
||||||
"uid_error": "無效的UID。",
|
|
||||||
"player_exist_error": "用戶不存在。",
|
"player_exist_error": "用戶不存在。",
|
||||||
"player_offline_error": "玩家已離線。",
|
"player_offline_error": "玩家已離線。",
|
||||||
"item_id_error": "無效的物品ID。.",
|
|
||||||
"item_player_exist_error": "無效的物品/玩家UID。",
|
"item_player_exist_error": "無效的物品/玩家UID。",
|
||||||
"entity_id_error": "無效的實體ID。",
|
|
||||||
"player_exist_offline_error": "玩家不存在或已離線。",
|
"player_exist_offline_error": "玩家不存在或已離線。",
|
||||||
"argument_error": "無效的參數。",
|
"argument_error": "無效的參數。",
|
||||||
"clear_target": "目標已清除.",
|
"clear_target": "目標已清除.",
|
||||||
@ -98,11 +96,10 @@
|
|||||||
"status": {
|
"status": {
|
||||||
"enabled": "已啟用",
|
"enabled": "已啟用",
|
||||||
"disabled": "未啟用",
|
"disabled": "未啟用",
|
||||||
"help": "幫助",
|
"help": "幫助",
|
||||||
"success": "成功"
|
"success": "成功"
|
||||||
},
|
},
|
||||||
"account": {
|
"account": {
|
||||||
"modify": "修改使用者帳號",
|
|
||||||
"invalid": "無效的UID。",
|
"invalid": "無效的UID。",
|
||||||
"exists": "帳號已存在。",
|
"exists": "帳號已存在。",
|
||||||
"create": "已建立帳號,UID 為 %s 。",
|
"create": "已建立帳號,UID 為 %s 。",
|
||||||
@ -390,10 +387,10 @@
|
|||||||
"description": "解鎖所有級別的深境螺旋。"
|
"description": "解鎖所有級別的深境螺旋。"
|
||||||
},
|
},
|
||||||
"weather": {
|
"weather": {
|
||||||
"usage": "用法:weather <氣候型別(weatherId)> <天氣型別(climateId)>\n天氣類型: '0:無、 1:晴天、 2:多雲、 3:雨、 4::雷雨、 5:雪、 6:霧'",
|
"usage": "用法:weather [weatherId] [climateType]\n天氣ID可以在 WeatherExcelConfigData.json 中找到。\n氣候型別:sunny(晴天), cloudy(多雲), rain(雨), thunderstorm(雷雨), snow(雪), mist(霧)",
|
||||||
"success": "已將當前氣候設定為 %s ,天氣則為 %s 。",
|
"success": "已設定天氣ID 為 %s,氣候型別為 %s。",
|
||||||
"invalid_id": "無效的ID。",
|
"status": "當前天氣ID 為 %s,氣候型別為 %s。",
|
||||||
"description": "更改目前的天氣。"
|
"description": "更改天氣ID和氣候型別。天氣ID可以在 WeatherExcelConfigData.json 中找到。\n氣候型別:sunny(晴天), cloudy(多雲), rain(雨), thunderstorm(雷雨), snow(雪), mist(霧)"
|
||||||
},
|
},
|
||||||
"ban": {
|
"ban": {
|
||||||
"description": "停權指定玩家。",
|
"description": "停權指定玩家。",
|
||||||
|
Loading…
Reference in New Issue
Block a user