diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 49a9d8b53..d14ebf3e0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -31,4 +31,4 @@ jobs: uses: actions/upload-artifact@v3 with: name: Grasscutter - path: grasscutter-*-dev.jar + path: grasscutter-*.jar diff --git a/.gitignore b/.gitignore index 9c374fa70..35ee889c5 100644 --- a/.gitignore +++ b/.gitignore @@ -67,4 +67,5 @@ mongod.exe /*.sh language/ languages/ -gacha_mappings.js +gacha-mapping.js +data/gacha_mappings.js diff --git a/build.gradle b/build.gradle index 6da62b032..eefef5b48 100644 --- a/build.gradle +++ b/build.gradle @@ -28,7 +28,7 @@ plugins { // Eclipse Support id 'eclipse' - // Intelij Support + // IntelliJ Support id 'idea' // Maven @@ -43,7 +43,7 @@ sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 group = 'xyz.grasscutters' -version = '1.0.3-dev' +version = '1.1.0' sourceCompatibility = 17 targetCompatibility = 17 @@ -228,4 +228,4 @@ javadoc { processResources { dependsOn "generateProto" -} \ No newline at end of file +} diff --git a/data/ExpeditionReward.json b/data/ExpeditionReward.json new file mode 100644 index 000000000..b7c5db45d --- /dev/null +++ b/data/ExpeditionReward.json @@ -0,0 +1,1072 @@ +[ + { + "expId": 101, + "expeditionRewardDataList": [ + { + "hourTime": 4, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 101001, + "minCount": 4, + "maxCount": 5 + }, { + "itemId": 101002, + "minCount": 2, + "maxCount": 3 + } + ] + }, + { + "hourTime": 8, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 101002, + "minCount": 6, + "maxCount": 7 + }, { + "itemId": 101003, + "minCount": 1, + "maxCount": 2 + } + ] + }, + { + "hourTime": 12, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 101002, + "minCount": 6, + "maxCount": 7 + }, { + "itemId": 101003, + "minCount": 2, + "maxCount": 3 + } + ] + }, + { + "hourTime": 20, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 101002, + "minCount": 3, + "maxCount": 4 + }, { + "itemId": 101003, + "minCount": 7, + "maxCount": 8 + } + ] + } + ] + }, + { + "expId": 102, + "expeditionRewardDataList": [ + { + "hourTime": 4, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 100061, + "minCount": 1, + "maxCount": 1 + }, { + "itemId": 100064, + "minCount": 1, + "maxCount": 1 + } + ] + }, + { + "hourTime": 8, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 100061, + "minCount": 2, + "maxCount": 4 + }, { + "itemId": 100064, + "minCount": 2, + "maxCount": 4 + } + ] + }, + { + "hourTime": 12, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 100061, + "minCount": 4, + "maxCount": 6 + }, { + "itemId": 100064, + "minCount": 4, + "maxCount": 6 + } + ] + }, + { + "hourTime": 20, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 100061, + "minCount": 8, + "maxCount": 12 + }, { + "itemId": 100064, + "minCount": 8, + "maxCount": 12 + } + ] + } + ] + }, + { + "expId": 103, + "expeditionRewardDataList": [ + { + "hourTime": 4, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 101001, + "minCount": 4, + "maxCount": 5 + }, { + "itemId": 101002, + "minCount": 2, + "maxCount": 3 + } + ] + }, + { + "hourTime": 8, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 101002, + "minCount": 6, + "maxCount": 7 + }, { + "itemId": 101003, + "minCount": 1, + "maxCount": 2 + } + ] + }, + { + "hourTime": 12, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 101002, + "minCount": 6, + "maxCount": 7 + }, { + "itemId": 101003, + "minCount": 2, + "maxCount": 3 + } + ] + }, + { + "hourTime": 20, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 101002, + "minCount": 3, + "maxCount": 4 + }, { + "itemId": 101003, + "minCount": 7, + "maxCount": 8 + } + ] + } + ] + }, + { + "expId": 104, + "expeditionRewardDataList": [ + { + "hourTime": 4, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 100012, + "minCount": 1, + "maxCount": 1 + }, { + "itemId": 100062, + "minCount": 1, + "maxCount": 1 + } + ] + }, + { + "hourTime": 8, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 100012, + "minCount": 2, + "maxCount": 4 + }, { + "itemId": 100062, + "minCount": 2, + "maxCount": 4 + } + ] + }, + { + "hourTime": 12, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 100012, + "minCount": 4, + "maxCount": 6 + }, { + "itemId": 100062, + "minCount": 4, + "maxCount": 6 + } + ] + }, + { + "hourTime": 20, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 100012, + "minCount": 8, + "maxCount": 12 + }, { + "itemId": 100062, + "minCount": 8, + "maxCount": 12 + } + ] + } + ] + }, + { + "expId": 105, + "expeditionRewardDataList": [ + { + "hourTime": 4, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 100014, + "minCount": 1, + "maxCount": 1 + }, { + "itemId": 100013, + "minCount": 1, + "maxCount": 1 + } + ] + }, + { + "hourTime": 8, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 100014, + "minCount": 2, + "maxCount": 4 + }, { + "itemId": 100013, + "minCount": 2, + "maxCount": 4 + } + ] + }, + { + "hourTime": 12, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 100014, + "minCount": 4, + "maxCount": 6 + }, { + "itemId": 100013, + "minCount": 4, + "maxCount": 6 + } + ] + }, + { + "hourTime": 20, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 100014, + "minCount": 8, + "maxCount": 12 + }, { + "itemId": 100013, + "minCount": 8, + "maxCount": 12 + } + ] + } + ] + }, + { + "expId": 106, + "expeditionRewardDataList": [ + { + "hourTime": 4, + "expeditionRewardData": [ + { + "itemId": 202, + "minCount": 625, + "maxCount": 625 + } + ] + }, + { + "hourTime": 8, + "expeditionRewardData": [ + { + "itemId": 202, + "minCount": 1575, + "maxCount": 1575 + } + ] + }, + { + "hourTime": 12, + "expeditionRewardData": [ + { + "itemId": 202, + "minCount": 2500, + "maxCount": 2500 + } + ] + }, + { + "hourTime": 20, + "expeditionRewardData": [ + { + "itemId": 202, + "minCount": 5000, + "maxCount": 5000 + } + ] + } + ] + }, + { + "expId": 201, + "expeditionRewardDataList": [ + { + "hourTime": 4, + "expeditionRewardData": [ + { + "itemId": 202, + "minCount": 625, + "maxCount": 625 + } + ] + }, + { + "hourTime": 8, + "expeditionRewardData": [ + { + "itemId": 202, + "minCount": 1575, + "maxCount": 1575 + } + ] + }, + { + "hourTime": 12, + "expeditionRewardData": [ + { + "itemId": 202, + "minCount": 2500, + "maxCount": 2500 + } + ] + }, + { + "hourTime": 20, + "expeditionRewardData": [ + { + "itemId": 202, + "minCount": 5000, + "maxCount": 5000 + } + ] + } + ] + }, + { + "expId": 202, + "expeditionRewardDataList": [ + { + "hourTime": 4, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 100026, + "minCount": 1, + "maxCount": 1 + }, { + "itemId": 100063, + "minCount": 1, + "maxCount": 1 + } + ] + }, + { + "hourTime": 8, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 100026, + "minCount": 2, + "maxCount": 4 + }, { + "itemId": 100063, + "minCount": 2, + "maxCount": 4 + } + ] + }, + { + "hourTime": 12, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 100026, + "minCount": 4, + "maxCount": 6 + }, { + "itemId": 100063, + "minCount": 4, + "maxCount": 6 + } + ] + }, + { + "hourTime": 20, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 100026, + "minCount": 8, + "maxCount": 12 + }, { + "itemId": 100063, + "minCount": 8, + "maxCount": 12 + } + ] + } + ] + }, + { + "expId": 203, + "expeditionRewardDataList": [ + { + "hourTime": 4, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 101001, + "minCount": 4, + "maxCount": 5 + }, { + "itemId": 101002, + "minCount": 2, + "maxCount": 3 + } + ] + }, + { + "hourTime": 8, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 101002, + "minCount": 6, + "maxCount": 7 + }, { + "itemId": 101003, + "minCount": 1, + "maxCount": 2 + } + ] + }, + { + "hourTime": 12, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 101002, + "minCount": 6, + "maxCount": 7 + }, { + "itemId": 101003, + "minCount": 2, + "maxCount": 3 + } + ] + }, + { + "hourTime": 20, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 101002, + "minCount": 3, + "maxCount": 4 + }, { + "itemId": 101003, + "minCount": 7, + "maxCount": 8 + } + ] + } + ] + }, + { + "expId": 204, + "expeditionRewardDataList": [ + { + "hourTime": 4, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 100014, + "minCount": 1, + "maxCount": 1 + }, { + "itemId": 100013, + "minCount": 1, + "maxCount": 1 + } + ] + }, + { + "hourTime": 8, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 100014, + "minCount": 2, + "maxCount": 4 + }, { + "itemId": 100013, + "minCount": 2, + "maxCount": 4 + } + ] + }, + { + "hourTime": 12, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 100014, + "minCount": 4, + "maxCount": 6 + }, { + "itemId": 100013, + "minCount": 4, + "maxCount": 6 + } + ] + }, + { + "hourTime": 20, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 100014, + "minCount": 8, + "maxCount": 12 + }, { + "itemId": 100013, + "minCount": 8, + "maxCount": 12 + } + ] + } + ] + }, + { + "expId": 205, + "expeditionRewardDataList": [ + { + "hourTime": 4, + "expeditionRewardData": [ + { + "itemId": 202, + "minCount": 625, + "maxCount": 625 + } + ] + }, + { + "hourTime": 8, + "expeditionRewardData": [ + { + "itemId": 202, + "minCount": 1575, + "maxCount": 1575 + } + ] + }, + { + "hourTime": 12, + "expeditionRewardData": [ + { + "itemId": 202, + "minCount": 2500, + "maxCount": 2500 + } + ] + }, + { + "hourTime": 20, + "expeditionRewardData": [ + { + "itemId": 202, + "minCount": 5000, + "maxCount": 5000 + } + ] + } + ] + }, + { + "expId": 206, + "expeditionRewardDataList": [ + { + "hourTime": 4, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 100026, + "minCount": 1, + "maxCount": 1 + }, { + "itemId": 100063, + "minCount": 1, + "maxCount": 1 + } + ] + }, + { + "hourTime": 8, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 100026, + "minCount": 2, + "maxCount": 4 + }, { + "itemId": 100063, + "minCount": 2, + "maxCount": 4 + } + ] + }, + { + "hourTime": 12, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 100026, + "minCount": 4, + "maxCount": 6 + }, { + "itemId": 100063, + "minCount": 4, + "maxCount": 6 + } + ] + }, + { + "hourTime": 20, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 100026, + "minCount": 8, + "maxCount": 12 + }, { + "itemId": 100063, + "minCount": 8, + "maxCount": 12 + } + ] + } + ] + }, + { + "expId": 301, + "expeditionRewardDataList": [ + { + "hourTime": 4, + "expeditionRewardData": [ + { + "itemId": 202, + "minCount": 625, + "maxCount": 625 + } + ] + }, + { + "hourTime": 8, + "expeditionRewardData": [ + { + "itemId": 202, + "minCount": 1575, + "maxCount": 1575 + } + ] + }, + { + "hourTime": 12, + "expeditionRewardData": [ + { + "itemId": 202, + "minCount": 2500, + "maxCount": 2500 + } + ] + }, + { + "hourTime": 20, + "expeditionRewardData": [ + { + "itemId": 202, + "minCount": 5000, + "maxCount": 5000 + } + ] + } + ] + }, + { + "expId": 302, + "expeditionRewardDataList": [ + { + "hourTime": 4, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 100014, + "minCount": 1, + "maxCount": 1 + }, { + "itemId": 101211, + "minCount": 1, + "maxCount": 1 + } + ] + }, + { + "hourTime": 8, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 100014, + "minCount": 2, + "maxCount": 4 + }, { + "itemId": 101211, + "minCount": 2, + "maxCount": 4 + } + ] + }, + { + "hourTime": 12, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 100014, + "minCount": 4, + "maxCount": 6 + }, { + "itemId": 101211, + "minCount": 4, + "maxCount": 6 + } + ] + }, + { + "hourTime": 20, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 100014, + "minCount": 8, + "maxCount": 12 + }, { + "itemId": 101211, + "minCount": 8, + "maxCount": 12 + } + ] + } + ] + }, + { + "expId": 303, + "expeditionRewardDataList": [ + { + "hourTime": 4, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 100012, + "minCount": 1, + "maxCount": 1 + }, { + "itemId": 100002, + "minCount": 1, + "maxCount": 1 + } + ] + }, + { + "hourTime": 8, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 100012, + "minCount": 2, + "maxCount": 4 + }, { + "itemId": 100002, + "minCount": 2, + "maxCount": 4 + } + ] + }, + { + "hourTime": 12, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 100012, + "minCount": 4, + "maxCount": 6 + }, { + "itemId": 100002, + "minCount": 4, + "maxCount": 6 + } + ] + }, + { + "hourTime": 20, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 100012, + "minCount": 8, + "maxCount": 12 + }, { + "itemId": 100002, + "minCount": 8, + "maxCount": 12 + } + ] + } + ] + }, + { + "expId": 304, + "expeditionRewardDataList": [ + { + "hourTime": 4, + "expeditionRewardData": [ + { + "itemId": 202, + "minCount": 625, + "maxCount": 625 + } + ] + }, + { + "hourTime": 8, + "expeditionRewardData": [ + { + "itemId": 202, + "minCount": 1575, + "maxCount": 1575 + } + ] + }, + { + "hourTime": 12, + "expeditionRewardData": [ + { + "itemId": 202, + "minCount": 2500, + "maxCount": 2500 + } + ] + }, + { + "hourTime": 20, + "expeditionRewardData": [ + { + "itemId": 202, + "minCount": 5000, + "maxCount": 5000 + } + ] + } + ] + }, + { + "expId": 305, + "expeditionRewardDataList": [ + { + "hourTime": 4, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 100064, + "minCount": 1, + "maxCount": 1 + }, { + "itemId": 101210, + "minCount": 1, + "maxCount": 1 + } + ] + }, + { + "hourTime": 8, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 100064, + "minCount": 2, + "maxCount": 4 + }, { + "itemId": 101210, + "minCount": 2, + "maxCount": 4 + } + ] + }, + { + "hourTime": 12, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 100064, + "minCount": 4, + "maxCount": 6 + }, { + "itemId": 101210, + "minCount": 4, + "maxCount": 6 + } + ] + }, + { + "hourTime": 20, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 100064, + "minCount": 8, + "maxCount": 12 + }, { + "itemId": 101210, + "minCount": 8, + "maxCount": 12 + } + ] + } + ] + }, + { + "expId": 306, + "expeditionRewardDataList": [ + { + "hourTime": 4, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 100061, + "minCount": 1, + "maxCount": 1 + }, { + "itemId": 100062, + "minCount": 1, + "maxCount": 1 + } + ] + }, + { + "hourTime": 8, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 100061, + "minCount": 2, + "maxCount": 4 + }, { + "itemId": 100062, + "minCount": 2, + "maxCount": 4 + } + ] + }, + { + "hourTime": 12, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 100061, + "minCount": 4, + "maxCount": 6 + }, { + "itemId": 100062, + "minCount": 4, + "maxCount": 6 + } + ] + }, + { + "hourTime": 20, + "rewardMora": 0, + "expeditionRewardData": [ + { + "itemId": 100061, + "minCount": 8, + "maxCount": 12 + }, { + "itemId": 100062, + "minCount": 8, + "maxCount": 12 + } + ] + } + ] + } +] \ No newline at end of file diff --git a/plugin-schema.json b/plugin-schema.json new file mode 100644 index 000000000..4fc772416 --- /dev/null +++ b/plugin-schema.json @@ -0,0 +1,49 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "JSON schema for a Grasscutter Plugin", + "type": "object", + "additionalProperties": true, + "definitions": { + "plugin-name": { + "type": "string", + "pattern": "^[A-Za-z\\d_.-]+$" + } + }, + "required": [ "name", "description", "mainClass" ], + "properties": { + "name": { + "description": "The unique name of plugin.", + "$ref": "#/definitions/plugin-name" + }, + "mainClass": { + "description": "The plugin's initial class file.", + "type": "string", + "pattern": "^(?!org\\.bukkit\\.)([a-zA-Z_$][a-zA-Z\\d_$]*\\.)*[a-zA-Z_$][a-zA-Z\\d_$]*$" + }, + "version": { + "description": "A plugin revision identifier.", + "type": [ "string", "number" ] + }, + "description": { + "description": "Human readable plugin summary.", + "type": "string" + }, + "author": { + "description": "The plugin author.", + "type": "string" + }, + "authors": { + "description": "The plugin contributors.", + "type": "array", + "items": { + "type": "string" + } + }, + "website": { + "title": "Website", + "description": "The URL to the plugin's site", + "type": "string", + "format": "uri" + } + } +} \ No newline at end of file diff --git a/proxy_config.py b/proxy_config.py index 5025a974e..07bac1a3a 100644 --- a/proxy_config.py +++ b/proxy_config.py @@ -1,4 +1,17 @@ +import os + # This can also be replaced with another IP address. USE_SSL = True -REMOTE_HOST = "127.0.0.1" -REMOTE_PORT = 443 \ No newline at end of file +REMOTE_HOST = "localhost" +REMOTE_PORT = 443 + +if os.getenv('MITM_REMOTE_HOST') != None: + REMOTE_HOST = os.getenv('MITM_REMOTE_HOST') +if os.getenv('MITM_REMOTE_PORT') != None: + REMOTE_PORT = int(os.getenv('MITM_REMOTE_PORT')) +if os.getenv('MITM_USE_SSL') != None: + USE_SSL = bool(os.getenv('MITM_USE_SSL')) + +print('MITM Remote Host: ' + REMOTE_HOST) +print('MITM Remote Port: ' + str(REMOTE_PORT)) +print('MITM Use SSL ' + str(USE_SSL)) diff --git a/src/main/java/emu/grasscutter/Config.java b/src/main/java/emu/grasscutter/Config.java index e7536b588..3982fc46b 100644 --- a/src/main/java/emu/grasscutter/Config.java +++ b/src/main/java/emu/grasscutter/Config.java @@ -6,7 +6,6 @@ import emu.grasscutter.Grasscutter.ServerRunMode; import emu.grasscutter.game.mail.Mail; public final class Config { - public String DatabaseUrl = "mongodb://localhost:27017"; public String DatabaseCollection = "grasscutter"; @@ -24,6 +23,7 @@ public final class Config { public GameServerOptions GameServer = new GameServerOptions(); public DispatchServerOptions DispatchServer = new DispatchServerOptions(); public Locale LocaleLanguage = Locale.getDefault(); + public Locale DefaultLanguage = Locale.ENGLISH; public Boolean OpenStamina = true; public GameServerOptions getGameServerOptions() { @@ -41,6 +41,8 @@ public final class Config { public String KeystorePassword = "123456"; public Boolean UseSSL = true; public Boolean FrontHTTPS = true; + public Boolean CORS = false; + public String[] CORSAllowedOrigins = new String[] { "*" }; public boolean AutomaticallyCreateAccounts = false; public String[] defaultPermissions = new String[] { "" }; @@ -80,6 +82,10 @@ public final class Config { public boolean WatchGacha = false; public String ServerNickname = "Server"; public int ServerAvatarId = 10000007; + public int ServerNameCardId = 210001; + public int ServerLevel = 1; + public int ServerWorldLevel = 1; + public String ServerSignature = "Server Signature"; public int[] WelcomeEmotes = {2007, 1002, 4010}; public String WelcomeMotd = "Welcome to Grasscutter emu"; public String WelcomeMailTitle = "Welcome to Grasscutter!"; diff --git a/src/main/java/emu/grasscutter/GameConstants.java b/src/main/java/emu/grasscutter/GameConstants.java index 39b28b736..dc07c32e1 100644 --- a/src/main/java/emu/grasscutter/GameConstants.java +++ b/src/main/java/emu/grasscutter/GameConstants.java @@ -11,8 +11,6 @@ public final class GameConstants { public static final int MAX_TEAMS = 4; public static final int MAIN_CHARACTER_MALE = 10000005; public static final int MAIN_CHARACTER_FEMALE = 10000007; - public static final String SERVER_AVATAR_NAME = Grasscutter.getConfig().getGameServerOptions().ServerNickname; - public static final int SERVER_AVATAR_ID = Grasscutter.getConfig().getGameServerOptions().ServerAvatarId; public static final Position START_POSITION = new Position(2747, 194, -1719); public static final int MAX_FRIENDS = 45; diff --git a/src/main/java/emu/grasscutter/Grasscutter.java b/src/main/java/emu/grasscutter/Grasscutter.java index a1c8a5c7c..c593f5f13 100644 --- a/src/main/java/emu/grasscutter/Grasscutter.java +++ b/src/main/java/emu/grasscutter/Grasscutter.java @@ -4,9 +4,7 @@ import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOError; -import java.net.InetSocketAddress; import java.util.Calendar; -import java.util.Locale; import emu.grasscutter.command.CommandMap; import emu.grasscutter.plugin.PluginManager; @@ -28,31 +26,32 @@ import com.google.gson.GsonBuilder; import ch.qos.logback.classic.Logger; import emu.grasscutter.data.ResourceLoader; import emu.grasscutter.database.DatabaseManager; -import emu.grasscutter.languages.CNLanguage; -import emu.grasscutter.languages.Language; +import emu.grasscutter.utils.Language; import emu.grasscutter.server.dispatch.DispatchServer; import emu.grasscutter.server.game.GameServer; import emu.grasscutter.tools.Tools; import emu.grasscutter.utils.Crypto; +import static emu.grasscutter.utils.Language.translate; + public final class Grasscutter { private static final Logger log = (Logger) LoggerFactory.getLogger(Grasscutter.class); - private static Config config; private static LineReader consoleLineReader = null; + + private static Config config; private static Language language; - private static CNLanguage cn_language; private static final Gson gson = new GsonBuilder().setPrettyPrinting().create(); private static final File configFile = new File("./config.json"); - private static int day; // Current day of week + private static int day; // Current day of week. private static DispatchServer dispatchServer; private static GameServer gameServer; private static PluginManager pluginManager; public static final Reflections reflector = new Reflections("emu.grasscutter"); - + static { // Declare logback configuration. System.setProperty("logback.configurationFile", "src/main/resources/logback.xml"); @@ -60,46 +59,50 @@ public final class Grasscutter { // Load server configuration. Grasscutter.loadConfig(); - // Load Language + // Load translation files. Grasscutter.loadLanguage(); // Check server structure. Utils.startupCheck(); } - public static void main(String[] args) throws Exception { - Crypto.loadKeys(); + public static void main(String[] args) throws Exception { + Crypto.loadKeys(); // Load keys from buffers. + // Parse arguments. + boolean exitEarly = false; for (String arg : args) { switch (arg.toLowerCase()) { case "-handbook" -> { - Tools.createGmHandbook(); return; + Tools.createGmHandbook(); exitEarly = true; } case "-gachamap" -> { - Tools.createGachaMapping("./gacha_mappings.js"); return; + Tools.createGachaMapping(Grasscutter.getConfig().DATA_FOLDER + "/gacha_mappings.js"); exitEarly = true; } } - } + } + + // Exit early if argument sets it. + if(exitEarly) System.exit(0); // Initialize server. - Grasscutter.getLogger().info(language.Starting_Grasscutter); + Grasscutter.getLogger().info(translate("messages.status.starting")); // Load all resources. Grasscutter.updateDayOfWeek(); ResourceLoader.loadAll(); ScriptLoader.init(); - // Database + // Initialize database. DatabaseManager.initialize(); - // Create plugin manager instance. - pluginManager = new PluginManager(); - // Create server instances. dispatchServer = new DispatchServer(); - gameServer = new GameServer(new InetSocketAddress(getConfig().getGameServerOptions().Ip, getConfig().getGameServerOptions().Port)); + gameServer = new GameServer(); // Create a server hook instance with both servers. new ServerHook(gameServer, dispatchServer); + // Create plugin manager instance. + pluginManager = new PluginManager(); // Start servers. if (getConfig().RunMode == ServerRunMode.HYBRID) { @@ -110,9 +113,9 @@ public final class Grasscutter { } else if (getConfig().RunMode == ServerRunMode.GAME_ONLY) { gameServer.start(); } else { - getLogger().error(language.Invalid_server_run_mode + " " + getConfig().RunMode); - getLogger().error(language.Server_run_mode); - getLogger().error(language.Shutting_down); + getLogger().error(translate("messages.status.run_mode_error", getConfig().RunMode)); + getLogger().error(translate("messages.status.run_mode_help")); + getLogger().error(translate("messages.status.shutdown")); System.exit(1); } @@ -124,7 +127,7 @@ public final class Grasscutter { // Open console. startConsole(); - } + } /** * Server shutdown event. @@ -145,40 +148,14 @@ public final class Grasscutter { } public static void loadLanguage() { - try (FileReader file = new FileReader(String.format("%s%s.json", getConfig().LANGUAGE_FOLDER, Grasscutter.config.LocaleLanguage))) { - language = gson.fromJson(file, Language.class); - } catch (Exception e) { - Grasscutter.language = new Language(); - Grasscutter.cn_language = new CNLanguage(); - Grasscutter.config.LocaleLanguage = Locale.getDefault(); - saveConfig(); - - try { - File folder = new File("./languages"); - if (!folder.exists() && !folder.isDirectory()) { - //noinspection ResultOfMethodCallIgnored - folder.mkdirs(); - } - } catch (Exception ee) { - Grasscutter.getLogger().error("Unable to create language folder."); - } - try (FileWriter file = new FileWriter("./languages/" + Locale.US + ".json")) { - file.write(gson.toJson(language)); - } catch (Exception ee) { - Grasscutter.getLogger().error("Unable to create language file."); - } - try (FileWriter file = new FileWriter("./languages/" + Locale.SIMPLIFIED_CHINESE + ".json")) { - file.write(gson.toJson(cn_language)); - } catch (Exception ee) { - Grasscutter.getLogger().error("无法创建简体中文语言文件。"); - } - - // try again - try (FileReader file = new FileReader(String.format("%s%s.json", getConfig().LANGUAGE_FOLDER, Grasscutter.config.LocaleLanguage))) { - language = gson.fromJson(file, Language.class); - } catch (Exception ee) { - Grasscutter.getLogger().error("Unable to load " + Grasscutter.config.LocaleLanguage + ".json"); - } + var locale = config.LocaleLanguage; + var languageTag = locale.toLanguageTag(); + + if (languageTag.equals("und")) { + Grasscutter.getLogger().error("Illegal locale language, using 'en-US' instead."); + language = Language.getLanguage("en-US"); + } else { + language = Language.getLanguage(languageTag); } } @@ -193,11 +170,11 @@ public final class Grasscutter { public static void startConsole() { // Console should not start in dispatch only mode. if (getConfig().RunMode == ServerRunMode.DISPATCH_ONLY) { - getLogger().info(language.Dispatch_mode_not_support_command); + getLogger().info(translate("messages.dispatch.no_commands_error")); return; } - getLogger().info(language.Start_done); + getLogger().info(translate("messages.status.done")); String input = null; boolean isLastInterrupted = false; while (true) { @@ -223,7 +200,7 @@ public final class Grasscutter { try { CommandMap.getInstance().invoke(null, null, input); } catch (Exception e) { - Grasscutter.getLogger().error(language.Command_error, e); + Grasscutter.getLogger().error(translate("messages.game.command_error"), e); } } } diff --git a/src/main/java/emu/grasscutter/command/Command.java b/src/main/java/emu/grasscutter/command/Command.java index 734f454ea..045f6a51c 100644 --- a/src/main/java/emu/grasscutter/command/Command.java +++ b/src/main/java/emu/grasscutter/command/Command.java @@ -9,7 +9,7 @@ public @interface Command { String usage() default "No usage specified"; - String description() default "No description specified"; + String description() default "commands.generic.no_description_specified"; String[] aliases() default {}; diff --git a/src/main/java/emu/grasscutter/command/CommandHandler.java b/src/main/java/emu/grasscutter/command/CommandHandler.java index ffe21c9be..f4fe12b3f 100644 --- a/src/main/java/emu/grasscutter/command/CommandHandler.java +++ b/src/main/java/emu/grasscutter/command/CommandHandler.java @@ -6,6 +6,7 @@ import emu.grasscutter.game.player.Player; import java.util.List; public interface CommandHandler { + /** * Send a message to the target. * diff --git a/src/main/java/emu/grasscutter/command/CommandMap.java b/src/main/java/emu/grasscutter/command/CommandMap.java index f17fb0443..a183c6ac3 100644 --- a/src/main/java/emu/grasscutter/command/CommandMap.java +++ b/src/main/java/emu/grasscutter/command/CommandMap.java @@ -8,6 +8,8 @@ import org.reflections.Reflections; import java.util.*; +import static emu.grasscutter.utils.Language.translate; + @SuppressWarnings({"UnusedReturnValue", "unused"}) public final class CommandMap { private final Map commands = new HashMap<>(); @@ -77,6 +79,12 @@ public final class CommandMap { return this; } + public List getAnnotationsAsList() { return new LinkedList<>(this.annotations.values()); } + + public HashMap getAnnotations() { + return new LinkedHashMap<>(this.annotations); + } + /** * Returns a list of all registered commands. * @@ -109,7 +117,7 @@ public final class CommandMap { public void invoke(Player player, Player targetPlayer, String rawMessage) { rawMessage = rawMessage.trim(); if (rawMessage.length() == 0) { - CommandHandler.sendMessage(player, Grasscutter.getLanguage().No_command_specified); + CommandHandler.sendMessage(player, translate("commands.generic.not_specified")); return; } @@ -118,11 +126,12 @@ public final class CommandMap { List args = new LinkedList<>(Arrays.asList(split)); String label = args.remove(0); String playerId = (player == null) ? consoleId : player.getAccount().getId(); - // Check for special cases - currently only target command + + // Check for special cases - currently only target command. String targetUidStr = null; - if (label.startsWith("@")) { // @[UID] + if (label.startsWith("@")) { // @[UID] targetUidStr = label.substring(1); - } else if (label.equalsIgnoreCase("target")) { // target [[@]UID] + } else if (label.equalsIgnoreCase("target")) { // target [[@]UID] if (args.size() > 0) { targetUidStr = args.get(0); if (targetUidStr.startsWith("@")) { @@ -133,68 +142,64 @@ public final class CommandMap { } } if (targetUidStr != null) { - if (targetUidStr.equals("")) { // Clears default targetPlayer + if (targetUidStr.equals("")) { // Clears the default targetPlayer. targetPlayerIds.remove(playerId); - CommandHandler.sendMessage(player, Grasscutter.getLanguage().Target_cleared); - return; - } else { // Sets default targetPlayer to the UID given + CommandHandler.sendMessage(player, translate("commands.execution.clear_target")); + } else { // Sets default targetPlayer to the UID provided. try { int uid = Integer.parseInt(targetUidStr); targetPlayer = Grasscutter.getGameServer().getPlayerByUid(uid); if (targetPlayer == null) { - CommandHandler.sendMessage(player, Grasscutter.getLanguage().Player_not_found_or_offline); + CommandHandler.sendMessage(player, translate("commands.generic.execution.player_exist_offline_error")); } else { targetPlayerIds.put(playerId, uid); - CommandHandler.sendMessage(player, Grasscutter.getLanguage().Target_set.replace("{uid}", targetUidStr)); + CommandHandler.sendMessage(player, translate("commands.execution.set_target", targetUidStr)); } } catch (NumberFormatException e) { - CommandHandler.sendMessage(player, Grasscutter.getLanguage().Invalid_UID); + CommandHandler.sendMessage(player, translate("commands.execution.uid_error")); } - return; } + return; } // Get command handler. CommandHandler handler = this.commands.get(label); if (handler == null) { - CommandHandler.sendMessage(player, Grasscutter.getLanguage().Unknown_command + label); + CommandHandler.sendMessage(player, translate("commands.generic.unknown_command", label)); return; } - // 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++) { String arg = args.get(i); - if (!arg.startsWith("@")) { - continue; - } else { + if (arg.startsWith("@")) { arg = args.remove(i).substring(1); try { int uid = Integer.parseInt(arg); targetPlayer = Grasscutter.getGameServer().getPlayerByUid(uid); if (targetPlayer == null) { - CommandHandler.sendMessage(player, Grasscutter.getLanguage().Player_not_found_or_offline); + CommandHandler.sendMessage(player, translate("commands.generic.execution.player_exist_offline_error")); return; } break; } catch (NumberFormatException e) { - CommandHandler.sendMessage(player, Grasscutter.getLanguage().Invalid_UID); + CommandHandler.sendMessage(player, translate("commands.execution.uid_error")); return; } } } + // If there's still no targetPlayer at this point, use previously-set target if (targetPlayer == null) { if (targetPlayerIds.containsKey(playerId)) { targetPlayer = Grasscutter.getGameServer().getPlayerByUid(targetPlayerIds.get(playerId)); // We check every time in case the target goes offline after being targeted if (targetPlayer == null) { - CommandHandler.sendMessage(player, Grasscutter.getLanguage().Player_not_found_or_offline); + CommandHandler.sendMessage(player, translate("commands.generic.execution.player_exist_offline_error")); return; } } else { - // If there's still no targetPlayer at this point, use local player - if (targetPlayer == null) { - targetPlayer = player; - } + // If there's still no targetPlayer at this point, use executor. + targetPlayer = player; } } @@ -205,12 +210,12 @@ public final class CommandMap { Account account = player.getAccount(); if (player != targetPlayer) { // Additional permission required for targeting another player if (!permissionNodeTargeted.isEmpty() && !account.hasPermission(permissionNodeTargeted)) { - CommandHandler.sendMessage(player, Grasscutter.getLanguage().You_not_permission_run_command); + CommandHandler.sendMessage(player, translate("commands.generic.permission_error")); return; } } if (!permissionNode.isEmpty() && !account.hasPermission(permissionNode)) { - CommandHandler.sendMessage(player, Grasscutter.getLanguage().You_not_permission_run_command); + CommandHandler.sendMessage(player, translate("commands.generic.permission_error")); return; } } @@ -220,10 +225,8 @@ public final class CommandMap { final Player targetPlayerF = targetPlayer; // Is there a better way to do this? Runnable runnable = () -> handler.execute(player, targetPlayerF, args); if(threading) { - Thread command = new Thread(runnable); - command.start(); - } - else { + new Thread(runnable).start(); + } else { runnable.run(); } } diff --git a/src/main/java/emu/grasscutter/command/commands/AccountCommand.java b/src/main/java/emu/grasscutter/command/commands/AccountCommand.java index a26e52ff0..4b287afa7 100644 --- a/src/main/java/emu/grasscutter/command/commands/AccountCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/AccountCommand.java @@ -1,6 +1,5 @@ package emu.grasscutter.command.commands; -import emu.grasscutter.Grasscutter; import emu.grasscutter.command.Command; import emu.grasscutter.command.CommandHandler; import emu.grasscutter.database.DatabaseHelper; @@ -8,18 +7,20 @@ import emu.grasscutter.game.player.Player; import java.util.List; -@Command(label = "account", usage = "account [uid]", description = "Modify user accounts") +import static emu.grasscutter.utils.Language.translate; + +@Command(label = "account", usage = "account [uid]", description = "commands.account.description") public final class AccountCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { if (sender != null) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().This_command_can_only_run_from_console); + CommandHandler.sendMessage(sender, translate("commands.generic.console_execute_error")); return; } if (args.size() < 2) { - CommandHandler.sendMessage(null, Grasscutter.getLanguage().Account_command_usage); + CommandHandler.sendMessage(null, translate("commands.account.command_usage")); return; } @@ -28,7 +29,7 @@ public final class AccountCommand implements CommandHandler { switch (action) { default: - CommandHandler.sendMessage(null, Grasscutter.getLanguage().Account_command_usage); + CommandHandler.sendMessage(null, translate("commands.account.command_usage")); return; case "create": int uid = 0; @@ -36,27 +37,27 @@ public final class AccountCommand implements CommandHandler { try { uid = Integer.parseInt(args.get(2)); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(null, Grasscutter.getLanguage().Invalid_UID); + CommandHandler.sendMessage(null, translate("commands.account.invalid")); return; } } emu.grasscutter.game.Account account = DatabaseHelper.createAccountWithId(username, uid); if (account == null) { - CommandHandler.sendMessage(null, Grasscutter.getLanguage().Account_exists); + CommandHandler.sendMessage(null, translate("commands.account.exists")); return; } else { account.addPermission("*"); account.save(); // Save account to database. - CommandHandler.sendMessage(null, Grasscutter.getLanguage().Account_create_UID.replace("{uid}", Integer.toString(account.getPlayerUid()))); + CommandHandler.sendMessage(null, translate("commands.account.create", Integer.toString(account.getPlayerUid()))); } return; case "delete": if (DatabaseHelper.deleteAccount(username)) { - CommandHandler.sendMessage(null, Grasscutter.getLanguage().Account_delete); + CommandHandler.sendMessage(null, translate("commands.account.delete")); } else { - CommandHandler.sendMessage(null, Grasscutter.getLanguage().Account_not_find); + CommandHandler.sendMessage(null, translate("commands.account.no_account")); } } } diff --git a/src/main/java/emu/grasscutter/command/commands/BroadcastCommand.java b/src/main/java/emu/grasscutter/command/commands/BroadcastCommand.java index b6eeac422..95f0c7c05 100644 --- a/src/main/java/emu/grasscutter/command/commands/BroadcastCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/BroadcastCommand.java @@ -7,14 +7,15 @@ import emu.grasscutter.game.player.Player; import java.util.List; -@Command(label = "broadcast", usage = "broadcast ", - description = "Sends a message to all the players", aliases = {"b"}, permission = "server.broadcast") +import static emu.grasscutter.utils.Language.translate; + +@Command(label = "broadcast", usage = "broadcast ", aliases = {"b"}, permission = "server.broadcast", description = "commands.broadcast.description") public final class BroadcastCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { if (args.size() < 1) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Broadcast_command_usage); + CommandHandler.sendMessage(sender, translate("commands.broadcast.command_usage")); return; } @@ -24,6 +25,6 @@ public final class BroadcastCommand implements CommandHandler { CommandHandler.sendMessage(p, message); } - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Broadcast_message_sent); + CommandHandler.sendMessage(sender, translate("commands.broadcast.message_sent")); } } diff --git a/src/main/java/emu/grasscutter/command/commands/ChangeSceneCommand.java b/src/main/java/emu/grasscutter/command/commands/ChangeSceneCommand.java index fb1694325..594eb27c6 100644 --- a/src/main/java/emu/grasscutter/command/commands/ChangeSceneCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/ChangeSceneCommand.java @@ -1,43 +1,43 @@ package emu.grasscutter.command.commands; -import emu.grasscutter.Grasscutter; import emu.grasscutter.command.Command; import emu.grasscutter.command.CommandHandler; import emu.grasscutter.game.player.Player; import java.util.List; -@Command(label = "changescene", usage = "changescene ", - description = "Changes your scene", aliases = {"scene"}, permission = "player.changescene") +import static emu.grasscutter.utils.Language.translate; + +@Command(label = "changescene", usage = "changescene ", aliases = {"scene"}, permission = "player.changescene", description = "commands.changescene.description") public final class ChangeSceneCommand implements CommandHandler { + @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed); + CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); return; } if (args.size() != 1) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Change_screen_usage); + CommandHandler.sendMessage(sender, translate("commands.changescene.usage")); return; } try { int sceneId = Integer.parseInt(args.get(0)); - if (sceneId == targetPlayer.getSceneId()) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Change_screen_you_in_that_screen); + CommandHandler.sendMessage(sender, translate("commands.changescene.already_in_scene")); return; } boolean result = targetPlayer.getWorld().transferPlayerToScene(targetPlayer, sceneId, targetPlayer.getPos()); - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Change_screen + sceneId); + CommandHandler.sendMessage(sender, translate("commands.changescene.result", Integer.toString(sceneId))); if (!result) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Change_screen_not_exist); + CommandHandler.sendMessage(sender, translate("commands.changescene.exists_error")); } } catch (Exception e) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_arguments); + CommandHandler.sendMessage(sender, translate("commands.execution.argument_error")); } } } diff --git a/src/main/java/emu/grasscutter/command/commands/ClearCommand.java b/src/main/java/emu/grasscutter/command/commands/ClearCommand.java index 2d0d0c48a..38f78e638 100644 --- a/src/main/java/emu/grasscutter/command/commands/ClearCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/ClearCommand.java @@ -10,8 +10,10 @@ import emu.grasscutter.game.player.Player; import java.util.List; +import static emu.grasscutter.utils.Language.translate; + @Command(label = "clear", usage = "clear ", //Merged /clearartifacts and /clearweapons to /clear [uid] - description = "Deletes unequipped unlocked items, including yellow rarity ones from your inventory", + description = "commands.clear.description", aliases = {"clear"}, permission = "player.clearinv") public final class ClearCommand implements CommandHandler { @@ -19,11 +21,11 @@ public final class ClearCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed); + CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); return; } if (args.size() < 1) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Clear_usage); + CommandHandler.sendMessage(sender, translate("commands.clear.command_usage")); return; } Inventory playerInventory = targetPlayer.getInventory(); @@ -35,7 +37,7 @@ public final class ClearCommand implements CommandHandler { .filter(item -> item.getItemType() == ItemType.ITEM_WEAPON) .filter(item -> !item.isLocked() && !item.isEquipped()) .toList(); - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Clear_weapons.replace("{name}", targetPlayer.getNickname())); + CommandHandler.sendMessage(sender, translate("commands.clear.weapons", targetPlayer.getNickname())); } case "art" -> { toDelete = playerInventory.getItems().values().stream() @@ -43,7 +45,7 @@ public final class ClearCommand implements CommandHandler { .filter(item -> item.getLevel() == 1 && item.getExp() == 0) .filter(item -> !item.isLocked() && !item.isEquipped()) .toList(); - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Clear_artifacts.replace("{name}", targetPlayer.getNickname())); + CommandHandler.sendMessage(sender, translate("commands.clear.artifacts", targetPlayer.getNickname())); } case "mat" -> { toDelete = playerInventory.getItems().values().stream() @@ -51,7 +53,7 @@ public final class ClearCommand implements CommandHandler { .filter(item -> item.getLevel() == 1 && item.getExp() == 0) .filter(item -> !item.isLocked() && !item.isEquipped()) .toList(); - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Clear_artifacts.replace("{name}", targetPlayer.getNickname())); + CommandHandler.sendMessage(sender, translate("commands.clear.materials", targetPlayer.getNickname())); } case "all" -> { toDelete = playerInventory.getItems().values().stream() @@ -59,34 +61,44 @@ public final class ClearCommand implements CommandHandler { .filter(item1 -> item1.getLevel() == 1 && item1.getExp() == 0) .filter(item1 -> !item1.isLocked() && !item1.isEquipped()) .toList(); - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Clear_artifacts.replace("{name}", targetPlayer.getNickname())); + CommandHandler.sendMessage(sender, translate("commands.clear.artifacts", targetPlayer.getNickname())); + playerInventory.removeItems(toDelete); + toDelete = playerInventory.getItems().values().stream() .filter(item2 -> item2.getItemType() == ItemType.ITEM_MATERIAL) .filter(item2 -> !item2.isLocked() && !item2.isEquipped()) .toList(); - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Clear_artifacts.replace("{name}", targetPlayer.getNickname())); + playerInventory.removeItems(toDelete); + CommandHandler.sendMessage(sender, translate("commands.clear.materials", targetPlayer.getNickname())); + toDelete = playerInventory.getItems().values().stream() .filter(item3 -> item3.getItemType() == ItemType.ITEM_WEAPON) .filter(item3 -> item3.getLevel() == 1 && item3.getExp() == 0) .filter(item3 -> !item3.isLocked() && !item3.isEquipped()) .toList(); - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Clear_artifacts.replace("{name}", targetPlayer.getNickname())); + playerInventory.removeItems(toDelete); + CommandHandler.sendMessage(sender, translate("commands.clear.weapons", targetPlayer.getNickname())); + toDelete = playerInventory.getItems().values().stream() .filter(item4 -> item4.getItemType() == ItemType.ITEM_FURNITURE) .filter(item4 -> !item4.isLocked() && !item4.isEquipped()) .toList(); - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Clear_furniture.replace("{name}", targetPlayer.getNickname())); + playerInventory.removeItems(toDelete); + CommandHandler.sendMessage(sender, translate("commands.clear.furniture", targetPlayer.getNickname())); + toDelete = playerInventory.getItems().values().stream() .filter(item5 -> item5.getItemType() == ItemType.ITEM_DISPLAY) .filter(item5 -> !item5.isLocked() && !item5.isEquipped()) .toList(); - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Clear_displays.replace("{name}", targetPlayer.getNickname())); + playerInventory.removeItems(toDelete); + CommandHandler.sendMessage(sender, translate("commands.clear.displays", targetPlayer.getNickname())); + toDelete = playerInventory.getItems().values().stream() .filter(item6 -> item6.getItemType() == ItemType.ITEM_VIRTUAL) .filter(item6 -> !item6.isLocked() && !item6.isEquipped()) .toList(); - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Clear_virtuals.replace("{name}", targetPlayer.getNickname())); - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Clear_everything.replace("{name}", targetPlayer.getNickname())); + CommandHandler.sendMessage(sender, translate("commands.clear.virtuals", targetPlayer.getNickname())); + CommandHandler.sendMessage(sender, translate("commands.clear.everything", targetPlayer.getNickname())); } } diff --git a/src/main/java/emu/grasscutter/command/commands/CoopCommand.java b/src/main/java/emu/grasscutter/command/commands/CoopCommand.java index fff548d95..cf7d4fc82 100644 --- a/src/main/java/emu/grasscutter/command/commands/CoopCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/CoopCommand.java @@ -7,39 +7,40 @@ import emu.grasscutter.game.player.Player; import java.util.List; -@Command(label = "coop", usage = "coop [host UID]", - description = "Forces someone to join the world of others", permission = "server.coop") +import static emu.grasscutter.utils.Language.translate; + +@Command(label = "coop", usage = "coop [host UID]", permission = "server.coop", description = "commands.coop.description") public final class CoopCommand implements CommandHandler { + @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed); + CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); return; } Player host = sender; switch (args.size()) { case 0: // Summon target to self - if (sender == null) { // Console doesn't have a self to summon to - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Coop_usage); + CommandHandler.sendMessage(sender, translate("commands.coop.usage")); + if (sender == null) // Console doesn't have a self to summon to return; - } break; case 1: // Summon target to argument try { int hostId = Integer.parseInt(args.get(0)); host = Grasscutter.getGameServer().getPlayerByUid(hostId); if (host == null) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Player_is_offline); + CommandHandler.sendMessage(sender, translate("commands.execution.player_offline_error")); return; } break; } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_playerId); + CommandHandler.sendMessage(sender, translate("commands.execution.uid_error")); return; } default: - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Coop_usage); + CommandHandler.sendMessage(sender, translate("commands.coop.usage")); return; } @@ -49,6 +50,6 @@ public final class CoopCommand implements CommandHandler { } host.getServer().getMultiplayerManager().applyEnterMp(targetPlayer, host.getUid()); targetPlayer.getServer().getMultiplayerManager().applyEnterMpReply(host, targetPlayer.getUid(), true); - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Coop_success.replace("{host}", host.getNickname()).replace("{target}", targetPlayer.getNickname())); + CommandHandler.sendMessage(sender, translate("commands.coop.success", targetPlayer.getNickname(), host.getNickname())); } } diff --git a/src/main/java/emu/grasscutter/command/commands/DropCommand.java b/src/main/java/emu/grasscutter/command/commands/DropCommand.java index 29c5f10c6..10c306cad 100644 --- a/src/main/java/emu/grasscutter/command/commands/DropCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/DropCommand.java @@ -11,14 +11,15 @@ import emu.grasscutter.utils.Position; import java.util.List; -@Command(label = "drop", usage = "drop [amount]", - description = "Drops an item near you", aliases = {"d", "dropitem"}, permission = "server.drop") +import static emu.grasscutter.utils.Language.translate; + +@Command(label = "drop", usage = "drop [amount]", aliases = {"d", "dropitem"}, permission = "server.drop", description = "commands.drop.description") public final class DropCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(null, Grasscutter.getLanguage().Target_needed); + CommandHandler.sendMessage(null, translate("commands.execution.need_target")); return; } @@ -30,25 +31,25 @@ public final class DropCommand implements CommandHandler { try { amount = Integer.parseInt(args.get(1)); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_amount); + CommandHandler.sendMessage(sender, translate("commands.generic.invalid.amount")); return; - } // Slightly cheeky here: no break so it falls through to initialize the first argument too + } // Slightly cheeky here: no break, so it falls through to initialize the first argument too case 1: try { item = Integer.parseInt(args.get(0)); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_item_id); + CommandHandler.sendMessage(sender, translate("commands.generic.invalid.itemId")); return; } break; default: - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Drop_usage); + CommandHandler.sendMessage(sender, translate("commands.drop.command_usage")); return; } ItemData itemData = GameData.getItemDataMap().get(item); if (itemData == null) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_item_id); + CommandHandler.sendMessage(sender, translate("commands.generic.invalid.itemId")); return; } if (itemData.isEquip()) { @@ -62,6 +63,6 @@ public final class DropCommand implements CommandHandler { EntityItem entity = new EntityItem(targetPlayer.getScene(), targetPlayer, itemData, targetPlayer.getPos().clone().addY(3f), amount); targetPlayer.getScene().addEntity(entity); } - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Drop_dropped_of.replace("{amount}", Integer.toString(amount)).replace("{item}", Integer.toString(item))); + CommandHandler.sendMessage(sender, translate("commands.drop.success", Integer.toString(amount), Integer.toString(item))); } } \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/command/commands/EnterDungeonCommand.java b/src/main/java/emu/grasscutter/command/commands/EnterDungeonCommand.java index a52787b68..8534c034f 100644 --- a/src/main/java/emu/grasscutter/command/commands/EnterDungeonCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/EnterDungeonCommand.java @@ -1,43 +1,43 @@ package emu.grasscutter.command.commands; -import emu.grasscutter.Grasscutter; import emu.grasscutter.command.Command; import emu.grasscutter.command.CommandHandler; import emu.grasscutter.game.player.Player; import java.util.List; -@Command(label = "enterdungeon", usage = "enterdungeon ", - description = "Enter a dungeon", aliases = {"dungeon"}, permission = "player.enterdungeon") +import static emu.grasscutter.utils.Language.translate; + +@Command(label = "enterdungeon", usage = "enterdungeon ", aliases = {"dungeon"}, permission = "player.enterdungeon", description = "commands.enter_dungeon.description") public final class EnterDungeonCommand implements CommandHandler { + @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(null, Grasscutter.getLanguage().Target_needed); + CommandHandler.sendMessage(null, translate("commands.execution.need_target")); return; } if (args.size() < 1) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().EnterDungeon_usage); + CommandHandler.sendMessage(sender, translate("commands.enter_dungeon.usage")); return; } try { int dungeonId = Integer.parseInt(args.get(0)); - if (dungeonId == targetPlayer.getSceneId()) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().EnterDungeon_you_in_that_dungeon); + CommandHandler.sendMessage(sender, translate("commands.enter_dungeon.in_dungeon_error")); return; } boolean result = targetPlayer.getServer().getDungeonManager().enterDungeon(targetPlayer.getSession().getPlayer(), 0, dungeonId); - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().EnterDungeon_changed_to_dungeon + dungeonId); + CommandHandler.sendMessage(sender, translate("commands.enter_dungeon.changed", dungeonId)); if (!result) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().EnterDungeon_dungeon_not_found); + CommandHandler.sendMessage(sender, translate("commands.enter_dungeon.not_found_error")); } } catch (Exception e) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().EnterDungeon_usage); + CommandHandler.sendMessage(sender, translate("commands.enter_dungeon.usage")); } } } diff --git a/src/main/java/emu/grasscutter/command/commands/GiveAllCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveAllCommand.java index 21352dba5..c94d67129 100644 --- a/src/main/java/emu/grasscutter/command/commands/GiveAllCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/GiveAllCommand.java @@ -13,14 +13,15 @@ import emu.grasscutter.game.player.Player; import java.util.*; -@Command(label = "giveall", usage = "giveall [amount]", - description = "Gives all items", aliases = {"givea"}, permission = "player.giveall", threading = true) +import static emu.grasscutter.utils.Language.translate; + +@Command(label = "giveall", usage = "giveall [amount]", aliases = {"givea"}, permission = "player.giveall", threading = true, description = "commands.giveAll.description") public final class GiveAllCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed); + CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); return; } int amount = 99999; @@ -32,21 +33,21 @@ public final class GiveAllCommand implements CommandHandler { try { amount = Integer.parseInt(args.get(0)); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_amount); + CommandHandler.sendMessage(sender, translate("commands.generic.invalid.amount")); return; } break; default: // invalid - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().GiveAll_usage); + CommandHandler.sendMessage(sender, translate("commands.giveAll.usage")); return; } this.giveAllItems(targetPlayer, amount); - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().GiveAll_done); + CommandHandler.sendMessage(sender, translate("commands.giveAll.success", targetPlayer.getNickname())); } public void giveAllItems(Player player, int amount) { - CommandHandler.sendMessage(player, Grasscutter.getLanguage().GiveAll_item); + CommandHandler.sendMessage(player, translate("commands.giveAll.started")); for (AvatarData avatarData: GameData.getAvatarDataMap().values()) { //Exclude test avatar diff --git a/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java index 4d123dc5a..b87642bb2 100644 --- a/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java @@ -14,17 +14,20 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -@Command(label = "giveart", usage = "giveart [[,]]... [level]", description = "Gives the player a specified artifact", aliases = {"gart"}, permission = "player.giveart") +import static emu.grasscutter.utils.Language.translate; + +@Command(label = "giveart", usage = "giveart [[,]]... [level]", aliases = {"gart"}, permission = "player.giveart", description = "commands.giveArtifact.description") public final class GiveArtifactCommand implements CommandHandler { + @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed); + CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); return; } if (args.size() < 2) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().GiveArtifact_usage); + CommandHandler.sendMessage(sender, translate("commands.giveArtifact.usage")); return; } @@ -32,12 +35,12 @@ public final class GiveArtifactCommand implements CommandHandler { try { itemId = Integer.parseInt(args.remove(0)); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_artifact_id); + CommandHandler.sendMessage(sender, translate("commands.giveArtifact.id_error")); return; } ItemData itemData = GameData.getItemDataMap().get(itemId); if (itemData.getItemType() != ItemType.ITEM_RELIQUARY) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_artifact_id); + CommandHandler.sendMessage(sender, translate("commands.giveArtifact.id_error")); return; } @@ -45,7 +48,7 @@ public final class GiveArtifactCommand implements CommandHandler { try { mainPropId = Integer.parseInt(args.remove(0)); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_arguments); + CommandHandler.sendMessage(sender, translate("commands.generic.execution.argument_error")); return; } @@ -59,7 +62,7 @@ public final class GiveArtifactCommand implements CommandHandler { } catch (NumberFormatException ignored) { // Could be a stat,times string so no need to panic } - ArrayList appendPropIdList = new ArrayList<>(); + List appendPropIdList = new ArrayList<>(); try { args.forEach(it -> { String[] arr; @@ -74,7 +77,7 @@ public final class GiveArtifactCommand implements CommandHandler { appendPropIdList.addAll(Collections.nCopies(n, Integer.parseInt(it))); }); } catch (Exception ignored) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_arguments); + CommandHandler.sendMessage(sender, translate("commands.execution.argument_error")); return; } @@ -85,7 +88,7 @@ public final class GiveArtifactCommand implements CommandHandler { item.getAppendPropIdList().addAll(appendPropIdList); targetPlayer.getInventory().addItem(item, ActionReason.SubfieldDrop); - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().GiveArtifact_given.replace("{itemId}", Integer.toString(itemId)).replace("target", Integer.toString(targetPlayer.getUid()))); + CommandHandler.sendMessage(sender, translate("commands.giveArtifact.success", Integer.toString(itemId), Integer.toString(targetPlayer.getUid()))); } } diff --git a/src/main/java/emu/grasscutter/command/commands/GiveCharCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveCharCommand.java index 6283a8e78..5c6bad0d2 100644 --- a/src/main/java/emu/grasscutter/command/commands/GiveCharCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/GiveCharCommand.java @@ -10,14 +10,15 @@ import emu.grasscutter.game.player.Player; import java.util.List; -@Command(label = "givechar", usage = "givechar [level]", - description = "Gives the player a specified character", aliases = {"givec"}, permission = "player.givechar") +import static emu.grasscutter.utils.Language.translate; + +@Command(label = "givechar", usage = "givechar [level]", aliases = {"givec"}, permission = "player.givechar", description = "commands.giveChar.description") public final class GiveCharCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed); + CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); return; } @@ -30,7 +31,7 @@ public final class GiveCharCommand implements CommandHandler { level = Integer.parseInt(args.get(1)); } catch (NumberFormatException ignored) { // TODO: Parse from avatar name using GM Handbook. - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_avatar_level); + CommandHandler.sendMessage(sender, translate("commands.execution.invalid.avatarLevel")); return; } // Cheeky fall-through to parse first argument too case 1: @@ -38,24 +39,24 @@ public final class GiveCharCommand implements CommandHandler { avatarId = Integer.parseInt(args.get(0)); } catch (NumberFormatException ignored) { // TODO: Parse from avatar name using GM Handbook. - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_avatar_id); + CommandHandler.sendMessage(sender, translate("commands.execution.invalid.avatarId")); return; } break; default: - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().GiveChar_usage); + CommandHandler.sendMessage(sender, translate("commands.giveChar.usage")); return; } AvatarData avatarData = GameData.getAvatarDataMap().get(avatarId); if (avatarData == null) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_avatar_id); + CommandHandler.sendMessage(sender, translate("commands.execution.invalid.avatarId")); return; } // Check level. if (level > 90) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_avatar_level); + CommandHandler.sendMessage(sender, translate("commands.execution.invalid.avatarLevel")); return; } @@ -75,6 +76,6 @@ public final class GiveCharCommand implements CommandHandler { avatar.recalcStats(); targetPlayer.addAvatar(avatar); - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().GiveChar_given.replace("{avatarId}", Integer.toString(avatarId)).replace("{level}", Integer.toString(level)).replace("{target}", Integer.toString(targetPlayer.getUid()))); + CommandHandler.sendMessage(sender, translate("commands.giveChar.given", Integer.toString(avatarId), Integer.toString(level), Integer.toString(targetPlayer.getUid()))); } } diff --git a/src/main/java/emu/grasscutter/command/commands/GiveCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveCommand.java index f3f2adc17..19a9a8d26 100644 --- a/src/main/java/emu/grasscutter/command/commands/GiveCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/GiveCommand.java @@ -1,6 +1,5 @@ package emu.grasscutter.command.commands; -import emu.grasscutter.Grasscutter; import emu.grasscutter.command.Command; import emu.grasscutter.command.CommandHandler; import emu.grasscutter.data.GameData; @@ -12,11 +11,13 @@ import emu.grasscutter.game.props.ActionReason; import java.util.LinkedList; import java.util.List; -import java.util.regex.Pattern; import java.util.regex.Matcher; +import java.util.regex.Pattern; -@Command(label = "give", usage = "give [amount] [level]", description = "Gives an item to you or the specified player", aliases = { - "g", "item", "giveitem"}, permission = "player.give") +import static emu.grasscutter.utils.Language.translate; + +@Command(label = "give", usage = "give [amount] [level]", aliases = { + "g", "item", "giveitem"}, permission = "player.give", description = "commands.give.description") public final class GiveCommand implements CommandHandler { Pattern lvlRegex = Pattern.compile("l(?:vl?)?(\\d+)"); // Java is a joke of a proglang that doesn't have raw string literals Pattern refineRegex = Pattern.compile("r(\\d+)"); @@ -33,7 +34,7 @@ public final class GiveCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed); + CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); return; } int item; @@ -67,21 +68,21 @@ public final class GiveCommand implements CommandHandler { try { refinement = Integer.parseInt(args.get(3)); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_item_refinement); + CommandHandler.sendMessage(sender, translate("commands.generic.invalid.itemRefinement")); return; } // Fallthrough case 3: // [amount] [level] try { lvl = Integer.parseInt(args.get(2)); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_item_level); + CommandHandler.sendMessage(sender, translate("commands.generic.invalid.itemLevel")); return; } // Fallthrough case 2: // [amount] try { amount = Integer.parseInt(args.get(1)); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_amount); + CommandHandler.sendMessage(sender, translate("commands.generic.invalid.amount")); return; } // Fallthrough case 1: // @@ -89,30 +90,28 @@ public final class GiveCommand implements CommandHandler { item = Integer.parseInt(args.get(0)); } catch (NumberFormatException ignored) { // TODO: Parse from item name using GM Handbook. - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_item_id); + CommandHandler.sendMessage(sender, translate("commands.generic.invalid.itemId")); return; } break; default: // *No args* - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Give_usage); + CommandHandler.sendMessage(sender, translate("commands.give.usage")); return; } - - ItemData itemData = GameData.getItemDataMap().get(item); if (itemData == null) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_item_id); + CommandHandler.sendMessage(sender, translate("commands.generic.invalid.itemId")); return; } if (refinement != 0) { if (itemData.getItemType() == ItemType.ITEM_WEAPON) { if (refinement < 1 || refinement > 5) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Give_refinement_must_between_1_and_5); + CommandHandler.sendMessage(sender, translate("commands.give.refinement_must_between_1_and_5")); return; } } else { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Give_refinement_only_applicable_weapons); + CommandHandler.sendMessage(sender, translate("commands.give.refinement_only_applicable_weapons")); return; } } @@ -120,11 +119,11 @@ public final class GiveCommand implements CommandHandler { this.item(targetPlayer, itemData, amount, lvl, refinement); if (!itemData.isEquip()) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Give_given.replace("{amount}", Integer.toString(amount)).replace("{item}", Integer.toString(item)).replace("{target}", Integer.toString(targetPlayer.getUid()))); + CommandHandler.sendMessage(sender, translate("commands.give.given", Integer.toString(amount), Integer.toString(item), Integer.toString(targetPlayer.getUid()))); } else if (itemData.getItemType() == ItemType.ITEM_WEAPON) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Give_given_with_level_and_refinement.replace("{item}", Integer.toString(item)).replace("{lvl}", Integer.toString(lvl)).replace("{refinement}", Integer.toString(refinement)).replace("{amount}", Integer.toString(amount)).replace("{target}", Integer.toString(targetPlayer.getUid()))); + CommandHandler.sendMessage(sender, translate("commands.give.given_with_level_and_refinement", Integer.toString(item), Integer.toString(lvl), Integer.toString(refinement), Integer.toString(amount), Integer.toString(targetPlayer.getUid()))); } else { - CommandHandler.sendMessage(sender,Grasscutter.getLanguage().Give_given_level.replace("{item}", Integer.toString(item)).replace("{lvl}", Integer.toString(lvl)).replace("{amount}", Integer.toString(amount))); + CommandHandler.sendMessage(sender, translate("commands.give.given_level", Integer.toString(item), Integer.toString(lvl), Integer.toString(amount))); } } diff --git a/src/main/java/emu/grasscutter/command/commands/GodModeCommand.java b/src/main/java/emu/grasscutter/command/commands/GodModeCommand.java index 73ec8b032..bf2a00c9f 100644 --- a/src/main/java/emu/grasscutter/command/commands/GodModeCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/GodModeCommand.java @@ -1,20 +1,20 @@ package emu.grasscutter.command.commands; -import emu.grasscutter.Grasscutter; import emu.grasscutter.command.Command; import emu.grasscutter.command.CommandHandler; import emu.grasscutter.game.player.Player; import java.util.List; -@Command(label = "godmode", usage = "godmode [on|off|toggle]", - description = "Prevents you from taking damage. Defaults to toggle.", permission = "player.godmode") +import static emu.grasscutter.utils.Language.translate; + +@Command(label = "godmode", usage = "godmode [on|off|toggle]", permission = "player.godmode", description = "commands.godmode.description") public final class GodModeCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed); + CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); return; } @@ -30,11 +30,11 @@ public final class GodModeCommand implements CommandHandler { case "toggle": break; // Already toggled default: - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Godmode_status); + break; } } targetPlayer.setGodmode(enabled); - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Godmode_status.replace("{status}", (enabled ? Grasscutter.getLanguage().Enabled : Grasscutter.getLanguage().Disabled)).replace("{name}", targetPlayer.getNickname())); + CommandHandler.sendMessage(sender, translate("commands.godmode.success", (enabled ? translate("commands.status.enabled") : translate("commands.status.disabled")), targetPlayer.getNickname())); } } diff --git a/src/main/java/emu/grasscutter/command/commands/HealCommand.java b/src/main/java/emu/grasscutter/command/commands/HealCommand.java index e61cfd98f..440db0a49 100644 --- a/src/main/java/emu/grasscutter/command/commands/HealCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/HealCommand.java @@ -1,6 +1,5 @@ package emu.grasscutter.command.commands; -import emu.grasscutter.Grasscutter; import emu.grasscutter.command.Command; import emu.grasscutter.command.CommandHandler; import emu.grasscutter.game.player.Player; @@ -10,13 +9,15 @@ import emu.grasscutter.server.packet.send.PacketAvatarLifeStateChangeNotify; import java.util.List; -@Command(label = "heal", usage = "heal|h", aliases = {"h"}, - description = "Heal all characters in your current team.", permission = "player.heal") +import static emu.grasscutter.utils.Language.translate; + +@Command(label = "heal", usage = "heal|h", aliases = {"h"}, permission = "player.heal", description = "commands.heal.description") public final class HealCommand implements CommandHandler { + @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed); + CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); return; } @@ -31,6 +32,6 @@ public final class HealCommand implements CommandHandler { entity.getWorld().broadcastPacket(new PacketAvatarLifeStateChangeNotify(entity.getAvatar())); } }); - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Heal_message); + CommandHandler.sendMessage(sender, translate("commands.heal.success")); } } diff --git a/src/main/java/emu/grasscutter/command/commands/HelpCommand.java b/src/main/java/emu/grasscutter/command/commands/HelpCommand.java index 8397eae3e..8a222f7a6 100644 --- a/src/main/java/emu/grasscutter/command/commands/HelpCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/HelpCommand.java @@ -8,8 +8,9 @@ import emu.grasscutter.game.player.Player; import java.util.*; -@Command(label = "help", usage = "help [command]", - description = "Sends the help message or shows information about a specified command") +import static emu.grasscutter.utils.Language.translate; + +@Command(label = "help", usage = "help [command]", description = "commands.help.description") public final class HelpCommand implements CommandHandler { @Override @@ -31,16 +32,16 @@ public final class HelpCommand implements CommandHandler { } else { String command = args.get(0); CommandHandler handler = CommandMap.getInstance().getHandler(command); - StringBuilder builder = new StringBuilder(player == null ? "\n" + Grasscutter.getLanguage().Help + " - " : Grasscutter.getLanguage().Help + " - ").append(command).append(": \n"); + StringBuilder builder = new StringBuilder(player == null ? "\n" + translate("commands.status.help") + " - " : translate("commands.status.help") + " - ").append(command).append(": \n"); if (handler == null) { - builder.append(Grasscutter.getLanguage().No_command_found); + builder.append(translate("commands.generic.command_exist_error")); } else { Command annotation = handler.getClass().getAnnotation(Command.class); - builder.append(" ").append(annotation.description()).append("\n"); - builder.append(Grasscutter.getLanguage().Help_usage).append(annotation.usage()); + builder.append(" ").append(translate(annotation.description())).append("\n"); + builder.append(translate("commands.help.usage")).append(annotation.usage()); if (annotation.aliases().length >= 1) { - builder.append("\n").append(Grasscutter.getLanguage().Help_aliases); + builder.append("\n").append(translate("commands.help.aliases")); for (String alias : annotation.aliases()) { builder.append(alias).append(" "); } @@ -56,13 +57,13 @@ public final class HelpCommand implements CommandHandler { void SendAllHelpMessage(Player player, List annotations) { if (player == null) { - StringBuilder builder = new StringBuilder("\n" + Grasscutter.getLanguage().Help_available_command + "\n"); + StringBuilder builder = new StringBuilder("\n" + translate("commands.help.available_commands") + "\n"); annotations.forEach(annotation -> { builder.append(annotation.label()).append("\n"); - builder.append(" ").append(annotation.description()).append("\n"); - builder.append(Grasscutter.getLanguage().Help_usage).append(annotation.usage()); + builder.append(" ").append(translate(annotation.description())).append("\n"); + builder.append(translate("commands.help.usage")).append(annotation.usage()); if (annotation.aliases().length >= 1) { - builder.append("\n").append(Grasscutter.getLanguage().Help_aliases); + builder.append("\n").append(translate("commands.help.aliases")); for (String alias : annotation.aliases()) { builder.append(alias).append(" "); } @@ -73,13 +74,13 @@ public final class HelpCommand implements CommandHandler { CommandHandler.sendMessage(null, builder.toString()); } else { - CommandHandler.sendMessage(player, Grasscutter.getLanguage().Help_available_command); + CommandHandler.sendMessage(player, translate("commands.help.available_commands")); annotations.forEach(annotation -> { StringBuilder builder = new StringBuilder(annotation.label()).append("\n"); - builder.append(" ").append(annotation.description()).append("\n"); - builder.append(Grasscutter.getLanguage().Help_usage).append(annotation.usage()); + builder.append(" ").append(translate(annotation.description())).append("\n"); + builder.append(translate("commands.help.usage")).append(annotation.usage()); if (annotation.aliases().length >= 1) { - builder.append("\n").append(Grasscutter.getLanguage().Help_aliases); + builder.append("\n").append(translate("commands.help.aliases")); for (String alias : annotation.aliases()) { builder.append(alias).append(" "); } diff --git a/src/main/java/emu/grasscutter/command/commands/KickCommand.java b/src/main/java/emu/grasscutter/command/commands/KickCommand.java index 0e3bb6c2b..9741226e7 100644 --- a/src/main/java/emu/grasscutter/command/commands/KickCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/KickCommand.java @@ -1,27 +1,29 @@ package emu.grasscutter.command.commands; -import emu.grasscutter.Grasscutter; import emu.grasscutter.command.Command; import emu.grasscutter.command.CommandHandler; import emu.grasscutter.game.player.Player; import java.util.List; -@Command(label = "kick", usage = "kick", - description = "Kicks the specified player from the server (WIP)", permission = "server.kick") +import static emu.grasscutter.utils.Language.translate; + +@Command(label = "kick", usage = "kick", permission = "server.kick", description = "commands.kick.description") public final class KickCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed); + CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); return; } if (sender != null) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Kick_player_kick_player.replace("{sendUid}", Integer.toString(sender.getAccount().getPlayerUid())).replace("{sendName}", sender.getAccount().getUsername()).replace("kickUid", Integer.toString(targetPlayer.getUid())).replace("{kickName}", targetPlayer.getAccount().getUsername())); + CommandHandler.sendMessage(sender, translate("commands.kick.player_kick_player", + Integer.toString(sender.getAccount().getPlayerUid()), sender.getAccount().getUsername(), + Integer.toString(targetPlayer.getUid()), targetPlayer.getAccount().getUsername())); } else { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Kick_server_player.replace("{kickUid}", Integer.toString(targetPlayer.getUid())).replace("{kickName}", targetPlayer.getAccount().getUsername())); + CommandHandler.sendMessage(null, translate("commands.kick.server_kick_player", Integer.toString(targetPlayer.getUid()), targetPlayer.getAccount().getUsername())); } targetPlayer.getSession().close(); diff --git a/src/main/java/emu/grasscutter/command/commands/KillAllCommand.java b/src/main/java/emu/grasscutter/command/commands/KillAllCommand.java index 0e3660c1b..da9ac7b5e 100644 --- a/src/main/java/emu/grasscutter/command/commands/KillAllCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/KillAllCommand.java @@ -10,14 +10,15 @@ import emu.grasscutter.game.world.Scene; import java.util.List; -@Command(label = "killall", usage = "killall [sceneId]", - description = "Kill all entities", permission = "server.killall") +import static emu.grasscutter.utils.Language.translate; + +@Command(label = "killall", usage = "killall [sceneId]", permission = "server.killall", description = "commands.kill.description") public final class KillAllCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed); + CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); return; } @@ -30,14 +31,14 @@ public final class KillAllCommand implements CommandHandler { scene = targetPlayer.getWorld().getSceneById(Integer.parseInt(args.get(0))); break; default: - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Kill_usage); + CommandHandler.sendMessage(sender, translate("commands.kill.usage")); return; } } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_arguments); + CommandHandler.sendMessage(sender, translate("commands.execution.argument_error")); } if (scene == null) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Kill_scene_not_found_in_player_world); + CommandHandler.sendMessage(sender, translate("commands.kill.scene_not_found_in_player_world")); return; } @@ -46,7 +47,7 @@ public final class KillAllCommand implements CommandHandler { List toKill = sceneF.getEntities().values().stream() .filter(entity -> entity instanceof EntityMonster) .toList(); - toKill.stream().forEach(entity -> sceneF.killEntity(entity, 0)); - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Kill_kill_monsters_in_scene.replace("{size}", Integer.toString(toKill.size())).replace("{id}", Integer.toString(scene.getId()))); + toKill.forEach(entity -> sceneF.killEntity(entity, 0)); + CommandHandler.sendMessage(sender, translate("commands.kill.kill_monsters_in_scene", Integer.toString(toKill.size()), Integer.toString(scene.getId()))); } } diff --git a/src/main/java/emu/grasscutter/command/commands/KillCharacterCommand.java b/src/main/java/emu/grasscutter/command/commands/KillCharacterCommand.java index 7dc01363d..3eda6f7e7 100644 --- a/src/main/java/emu/grasscutter/command/commands/KillCharacterCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/KillCharacterCommand.java @@ -1,6 +1,5 @@ package emu.grasscutter.command.commands; -import emu.grasscutter.Grasscutter; import emu.grasscutter.command.Command; import emu.grasscutter.command.CommandHandler; import emu.grasscutter.game.entity.EntityAvatar; @@ -12,14 +11,15 @@ import emu.grasscutter.server.packet.send.PacketLifeStateChangeNotify; import java.util.List; -@Command(label = "killcharacter", usage = "killcharacter", aliases = {"suicide", "kill"}, - description = "Kills the players current character", permission = "player.killcharacter") +import static emu.grasscutter.utils.Language.translate; + +@Command(label = "killcharacter", usage = "killcharacter", aliases = {"suicide", "kill"}, permission = "player.killcharacter", description = "commands.list.description") public final class KillCharacterCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed); + CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); return; } @@ -32,6 +32,6 @@ public final class KillCharacterCommand implements CommandHandler { targetPlayer.getScene().removeEntity(entity); entity.onDeath(0); - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().KillCharacter_kill_current_character.replace("{name}", targetPlayer.getNickname())); + CommandHandler.sendMessage(sender, translate("commands.killCharacter.success", targetPlayer.getNickname())); } } diff --git a/src/main/java/emu/grasscutter/command/commands/ListCommand.java b/src/main/java/emu/grasscutter/command/commands/ListCommand.java index 3119f728f..53a274e52 100644 --- a/src/main/java/emu/grasscutter/command/commands/ListCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/ListCommand.java @@ -8,8 +8,9 @@ import emu.grasscutter.game.player.Player; import java.util.List; import java.util.Map; -@Command(label = "list", usage = "list [uid]", - description = "List online players", aliases = {"players"}) +import static emu.grasscutter.utils.Language.translate; + +@Command(label = "list", usage = "list [uid]", aliases = {"players"}, description = "commands.list.description") public final class ListCommand implements CommandHandler { @Override @@ -21,7 +22,7 @@ public final class ListCommand implements CommandHandler { needUID = args.get(0).equals("uid"); } - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().List_message.replace("{size}", Integer.toString(playersMap.size()))); + CommandHandler.sendMessage(sender, translate("commands.list.success", Integer.toString(playersMap.size()))); if (playersMap.size() != 0) { StringBuilder playerSet = new StringBuilder(); diff --git a/src/main/java/emu/grasscutter/command/commands/PermissionCommand.java b/src/main/java/emu/grasscutter/command/commands/PermissionCommand.java index 6e041f338..4b945b3d1 100644 --- a/src/main/java/emu/grasscutter/command/commands/PermissionCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/PermissionCommand.java @@ -8,19 +8,20 @@ import emu.grasscutter.game.player.Player; import java.util.List; -@Command(label = "permission", usage = "permission ", - description = "Grants or removes a permission for a user", permission = "*") +import static emu.grasscutter.utils.Language.translate; + +@Command(label = "permission", usage = "permission ", permission = "*", description = "commands.permission.description") public final class PermissionCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed); + CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); return; } if (args.size() != 2) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Permission_usage); + CommandHandler.sendMessage(sender, translate("commands.permission.usage")); return; } @@ -29,23 +30,23 @@ public final class PermissionCommand implements CommandHandler { Account account = targetPlayer.getAccount(); if (account == null) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Account_not_find); + CommandHandler.sendMessage(sender, translate("commands.permission.account_error")); return; } switch (action) { default: - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Permission_usage); + CommandHandler.sendMessage(sender, translate("commands.permission.usage")); break; case "add": if (account.addPermission(permission)) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Permission_add); - } else CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Permission_have_permission); + CommandHandler.sendMessage(sender, translate("commands.permission.add")); + } else CommandHandler.sendMessage(sender, translate("commands.permission.has_error")); break; case "remove": if (account.removePermission(permission)) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Permission_remove); - } else CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Permission_not_have_permission); + CommandHandler.sendMessage(sender, translate("commands.permission.remove")); + } else CommandHandler.sendMessage(sender, translate("commands.permission.not_have_error")); break; } diff --git a/src/main/java/emu/grasscutter/command/commands/PositionCommand.java b/src/main/java/emu/grasscutter/command/commands/PositionCommand.java index 0e015d178..b5a250af6 100644 --- a/src/main/java/emu/grasscutter/command/commands/PositionCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/PositionCommand.java @@ -1,6 +1,5 @@ package emu.grasscutter.command.commands; -import emu.grasscutter.Grasscutter; import emu.grasscutter.command.Command; import emu.grasscutter.command.CommandHandler; import emu.grasscutter.game.player.Player; @@ -8,18 +7,21 @@ import emu.grasscutter.utils.Position; import java.util.List; -@Command(label = "position", usage = "position", aliases = {"pos"}, - description = "Get coordinates.") +import static emu.grasscutter.utils.Language.translate; + +@Command(label = "position", usage = "position", aliases = {"pos"}, description = "commands.position.description") public final class PositionCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed); + CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); return; } Position pos = targetPlayer.getPos(); - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Position_message.replace("{x}", Float.toString(pos.getX())).replace("{y}", Float.toString(pos.getY())).replace("{z}", Float.toString(pos.getZ())).replace("{id}", Integer.toString(targetPlayer.getSceneId()))); + CommandHandler.sendMessage(sender, translate("commands.position.success", + Float.toString(pos.getX()), Float.toString(pos.getY()), Float.toString(pos.getZ()), + Integer.toString(targetPlayer.getSceneId()))); } } diff --git a/src/main/java/emu/grasscutter/command/commands/ReloadCommand.java b/src/main/java/emu/grasscutter/command/commands/ReloadCommand.java index 0e45c4efc..29eb93d0d 100644 --- a/src/main/java/emu/grasscutter/command/commands/ReloadCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/ReloadCommand.java @@ -7,19 +7,22 @@ import emu.grasscutter.game.player.Player; import java.util.List; -@Command(label = "reload", usage = "reload", - description = "Reload server config", permission = "server.reload") +import static emu.grasscutter.utils.Language.translate; + +@Command(label = "reload", usage = "reload", permission = "server.reload", description = "commands.reload.description") public final class ReloadCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Reload_reload_start); + CommandHandler.sendMessage(sender, translate("commands.reload.reload_start")); + Grasscutter.loadConfig(); Grasscutter.loadLanguage(); Grasscutter.getGameServer().getGachaManager().load(); Grasscutter.getGameServer().getDropManager().load(); Grasscutter.getGameServer().getShopManager().load(); Grasscutter.getDispatchServer().loadQueries(); - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Reload_reload_done); + + CommandHandler.sendMessage(sender, translate("commands.reload.reload_done")); } } diff --git a/src/main/java/emu/grasscutter/command/commands/ResetConstCommand.java b/src/main/java/emu/grasscutter/command/commands/ResetConstCommand.java index 52ed0a55e..3a77cee4d 100644 --- a/src/main/java/emu/grasscutter/command/commands/ResetConstCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/ResetConstCommand.java @@ -1,6 +1,5 @@ package emu.grasscutter.command.commands; -import emu.grasscutter.Grasscutter; import emu.grasscutter.command.Command; import emu.grasscutter.command.CommandHandler; import emu.grasscutter.game.avatar.Avatar; @@ -9,21 +8,22 @@ import emu.grasscutter.game.player.Player; import java.util.List; +import static emu.grasscutter.utils.Language.translate; + @Command(label = "resetconst", usage = "resetconst [all]", - description = "Resets the constellation level on your current active character, will need to relog after using the command to see any changes.", - aliases = {"resetconstellation"}, permission = "player.resetconstellation") + aliases = {"resetconstellation"}, permission = "player.resetconstellation", description = "commands.resetConst.description") public final class ResetConstCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed); + CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); return; } if (args.size() > 0 && args.get(0).equalsIgnoreCase("all")) { targetPlayer.getAvatars().forEach(this::resetConstellation); - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().ResetConst_reset_all); + CommandHandler.sendMessage(sender, translate("commands.resetConst.reset_all")); } else { EntityAvatar entity = targetPlayer.getTeamManager().getCurrentAvatarEntity(); if (entity == null) { @@ -33,7 +33,7 @@ public final class ResetConstCommand implements CommandHandler { Avatar avatar = entity.getAvatar(); this.resetConstellation(avatar); - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().ResetConst_reset_all_done.replace("{name}", avatar.getAvatarData().getName())); + CommandHandler.sendMessage(sender, translate("commands.resetConst.success", avatar.getAvatarData().getName())); } } diff --git a/src/main/java/emu/grasscutter/command/commands/ResetShopLimitCommand.java b/src/main/java/emu/grasscutter/command/commands/ResetShopLimitCommand.java index f2cbf3476..7aa84ff6a 100644 --- a/src/main/java/emu/grasscutter/command/commands/ResetShopLimitCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/ResetShopLimitCommand.java @@ -7,18 +7,20 @@ import emu.grasscutter.game.player.Player; import java.util.List; -@Command(label = "resetshop", usage = "resetshop", - description = "Reset target player's shop refresh time.", permission = "server.resetshop") +import static emu.grasscutter.utils.Language.translate; + +@Command(label = "resetshop", usage = "resetshop", permission = "server.resetshop", description = "commands.status.description") public final class ResetShopLimitCommand implements CommandHandler { + @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed); + CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); return; } targetPlayer.getShopLimit().forEach(x -> x.setNextRefreshTime(0)); targetPlayer.save(); - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Success); + CommandHandler.sendMessage(sender, translate("commands.status.success")); } } diff --git a/src/main/java/emu/grasscutter/command/commands/RestartCommand.java b/src/main/java/emu/grasscutter/command/commands/RestartCommand.java index e3b8b2747..045a49d9e 100644 --- a/src/main/java/emu/grasscutter/command/commands/RestartCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/RestartCommand.java @@ -6,7 +6,9 @@ import emu.grasscutter.game.player.Player; import java.util.List; -@Command(label = "restart", usage = "restart - Restarts the current session") +import static emu.grasscutter.utils.Language.translate; + +@Command(label = "restart", usage = "restart", description = "commands.restart.description") public final class RestartCommand implements CommandHandler { @Override diff --git a/src/main/java/emu/grasscutter/command/commands/SendMailCommand.java b/src/main/java/emu/grasscutter/command/commands/SendMailCommand.java index da0414f79..69aafa20b 100644 --- a/src/main/java/emu/grasscutter/command/commands/SendMailCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SendMailCommand.java @@ -10,8 +10,10 @@ import emu.grasscutter.game.player.Player; import java.util.HashMap; import java.util.List; -@Command(label = "sendmail", usage = "sendmail [templateId]", - description = "Sends mail to the specified user. The usage of this command changes based on it's composition state.", permission = "server.sendmail") +import static emu.grasscutter.utils.Language.translate; + +@SuppressWarnings("ConstantConditions") +@Command(label = "sendmail", usage = "sendmail [templateId]", permission = "server.sendmail", description = "commands.sendMail.description") public final class SendMailCommand implements CommandHandler { // TODO: You should be able to do /sendmail and then just send subsequent messages until you finish @@ -37,7 +39,7 @@ public final class SendMailCommand implements CommandHandler { MailBuilder mailBuilder; switch (args.get(0).toLowerCase()) { case "help" -> { - CommandHandler.sendMessage(sender, this.getClass().getAnnotation(Command.class).description() + "\nUsage: " + this.getClass().getAnnotation(Command.class).usage()); + CommandHandler.sendMessage(sender, translate(this.getClass().getAnnotation(Command.class).description()) + "\nUsage: " + this.getClass().getAnnotation(Command.class).usage()); return; } case "all" -> mailBuilder = new MailBuilder(true, new Mail()); @@ -45,16 +47,16 @@ public final class SendMailCommand implements CommandHandler { if (DatabaseHelper.getPlayerById(Integer.parseInt(args.get(0))) != null) { mailBuilder = new MailBuilder(Integer.parseInt(args.get(0)), new Mail()); } else { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().SendMail_user_not_exist.replace("{id}", args.get(0))); + CommandHandler.sendMessage(sender, translate("commands.sendMail.user_not_exist", args.get(0))); return; } } } mailBeingConstructed.put(senderId, mailBuilder); - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().SendMail_start_composition); + CommandHandler.sendMessage(sender, translate("commands.sendMail.start_composition")); } - case 2 -> CommandHandler.sendMessage(sender, Grasscutter.getLanguage().SendMail_templates); - default -> CommandHandler.sendMessage(sender, Grasscutter.getLanguage().SendMail_invalid_arguments); + case 2 -> CommandHandler.sendMessage(sender, translate("commands.sendMail.templates")); + default -> CommandHandler.sendMessage(sender, translate("commands.sendMail.invalid_arguments")); } } else { MailBuilder mailBuilder = mailBeingConstructed.get(senderId); @@ -63,28 +65,28 @@ public final class SendMailCommand implements CommandHandler { switch (args.get(0).toLowerCase()) { case "stop" -> { mailBeingConstructed.remove(senderId); - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().SendMail_send_cancel); + CommandHandler.sendMessage(sender, translate("commands.sendMail.sendCancel")); return; } case "finish" -> { if (mailBuilder.constructionStage == 3) { if (!mailBuilder.sendToAll) { Grasscutter.getGameServer().getPlayerByUid(mailBuilder.recipient, true).sendMail(mailBuilder.mail); - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().SendMail_send_done.replace("{name}", Integer.toString(mailBuilder.recipient))); + CommandHandler.sendMessage(sender, translate("commands.sendMail.send_done", Integer.toString(mailBuilder.recipient))); } else { for (Player player : DatabaseHelper.getAllPlayers()) { Grasscutter.getGameServer().getPlayerByUid(player.getUid(), true).sendMail(mailBuilder.mail); } - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().SendMail_send_all_done); + CommandHandler.sendMessage(sender, translate("commands.sendMail.send_all_done")); } mailBeingConstructed.remove(senderId); } else { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().SendMail_not_composition_end.replace("{args}", getConstructionArgs(mailBuilder.constructionStage))); + CommandHandler.sendMessage(sender, translate("commands.sendMail.not_composition_end", getConstructionArgs(mailBuilder.constructionStage))); } return; } case "help" -> { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().SendMail_please_use.replace("{args}", getConstructionArgs(mailBuilder.constructionStage))); + CommandHandler.sendMessage(sender, translate("commands.sendMail.please_use", getConstructionArgs(mailBuilder.constructionStage))); return; } default -> { @@ -92,19 +94,19 @@ public final class SendMailCommand implements CommandHandler { case 0 -> { String title = String.join(" ", args.subList(0, args.size())); mailBuilder.mail.mailContent.title = title; - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().SendMail_set_title.replace("{title}", title)); + CommandHandler.sendMessage(sender, translate("commands.sendMail.set_title", title)); mailBuilder.constructionStage++; } case 1 -> { String contents = String.join(" ", args.subList(0, args.size())); mailBuilder.mail.mailContent.content = contents; - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().SendMail_set_contents.replace("{contents}", contents)); + CommandHandler.sendMessage(sender, translate("commands.sendMail.set_contents", contents)); mailBuilder.constructionStage++; } case 2 -> { String msgSender = String.join(" ", args.subList(0, args.size())); mailBuilder.mail.mailContent.sender = msgSender; - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().SendMail_set_message_sender.replace("{send}", msgSender)); + CommandHandler.sendMessage(sender, translate("commands.sendMail.set_message_sender", msgSender)); mailBuilder.constructionStage++; } case 3 -> { @@ -117,21 +119,21 @@ public final class SendMailCommand implements CommandHandler { try { refinement = Integer.parseInt(args.get(3)); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_item_refinement); + CommandHandler.sendMessage(sender, translate("commands.generic.invalid.itemRefinement")); return; } // Fallthrough case 3: // [amount] [level] try { lvl = Integer.parseInt(args.get(2)); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_item_level); + CommandHandler.sendMessage(sender, translate("commands.generic.invalid.itemLevel")); return; } // Fallthrough case 2: // [amount] try { amount = Integer.parseInt(args.get(1)); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_amount); + CommandHandler.sendMessage(sender, translate("commands.generic.invalid.amount")); return; } // Fallthrough case 1: // @@ -139,46 +141,34 @@ public final class SendMailCommand implements CommandHandler { item = Integer.parseInt(args.get(0)); } catch (NumberFormatException ignored) { // TODO: Parse from item name using GM Handbook. - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_item_id); + CommandHandler.sendMessage(sender, translate("commands.generic.invalid.itemId")); return; } break; default: // *No args* - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Give_usage); + CommandHandler.sendMessage(sender, translate("commands.give.usage")); return; } mailBuilder.mail.itemList.add(new Mail.MailItem(item, amount, lvl)); - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().SendMail_send.replace("{amount}", Integer.toString(amount)).replace("{item}", Integer.toString(item)).replace("{lvl}", Integer.toString(lvl))); + CommandHandler.sendMessage(sender, translate("commands.sendMail.send", Integer.toString(amount), Integer.toString(item), Integer.toString(lvl))); } } } } } else { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().SendMail_invalid_arguments_please_use.replace("{args}", getConstructionArgs(mailBuilder.constructionStage))); + CommandHandler.sendMessage(sender, translate("commands.sendMail.invalid_arguments_please_use", getConstructionArgs(mailBuilder.constructionStage))); } } } private String getConstructionArgs(int stage) { - switch (stage) { - case 0 -> { - return Grasscutter.getLanguage().SendMail_title; - } - case 1 -> { - return Grasscutter.getLanguage().SendMail_message; - } - case 2 -> { - return Grasscutter.getLanguage().SendMail_sender; - - } - case 3 -> { - return Grasscutter.getLanguage().SendMail_arguments; - } - default -> { - Thread.dumpStack(); - return Grasscutter.getLanguage().SendMail_error.replace("{stage}", Integer.toString(stage)); - } - } + return switch(stage) { + case 0 -> translate("commands.sendMail.title"); + case 1 -> translate("commands.sendMail.message"); + case 2 -> translate("commands.sendMail.sender"); + case 3 -> translate("commands.sendMail.arguments"); + default -> translate("commands.sendMail.error", Integer.toString(stage)); + }; } public static class MailBuilder { diff --git a/src/main/java/emu/grasscutter/command/commands/SendMessageCommand.java b/src/main/java/emu/grasscutter/command/commands/SendMessageCommand.java index e04d3817e..18d6264db 100644 --- a/src/main/java/emu/grasscutter/command/commands/SendMessageCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SendMessageCommand.java @@ -1,29 +1,30 @@ package emu.grasscutter.command.commands; -import emu.grasscutter.Grasscutter; import emu.grasscutter.command.Command; import emu.grasscutter.command.CommandHandler; import emu.grasscutter.game.player.Player; import java.util.List; -@Command(label = "say", usage = "say ", description = "Sends a message to a player as the server", - aliases = {"sendservmsg", "sendservermessage", "sendmessage"}, permission = "server.sendmessage") +import static emu.grasscutter.utils.Language.translate; + +@Command(label = "say", usage = "say ", + aliases = {"sendservmsg", "sendservermessage", "sendmessage"}, permission = "server.sendmessage", description = "commands.sendMessage.description") public final class SendMessageCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed); + CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); return; } if (args.size() == 0) { - CommandHandler.sendMessage(null, Grasscutter.getLanguage().SendMessage_usage); + CommandHandler.sendMessage(null, translate("commands.sendMessage.usage")); return; } String message = String.join(" ", args); CommandHandler.sendMessage(targetPlayer, message); - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().SenaMessage_message_sent); + CommandHandler.sendMessage(sender, translate("commands.sendMessage.success")); } } \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/command/commands/SetFetterLevelCommand.java b/src/main/java/emu/grasscutter/command/commands/SetFetterLevelCommand.java index f1768c72d..ca5a3cb43 100644 --- a/src/main/java/emu/grasscutter/command/commands/SetFetterLevelCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SetFetterLevelCommand.java @@ -2,7 +2,6 @@ package emu.grasscutter.command.commands; import java.util.List; -import emu.grasscutter.Grasscutter; import emu.grasscutter.command.Command; import emu.grasscutter.command.CommandHandler; import emu.grasscutter.data.GameData; @@ -10,27 +9,28 @@ import emu.grasscutter.game.avatar.Avatar; import emu.grasscutter.game.player.Player; import emu.grasscutter.server.packet.send.PacketAvatarFetterDataNotify; +import static emu.grasscutter.utils.Language.translate; + @Command(label = "setfetterlevel", usage = "setfetterlevel ", - description = "Sets your fetter level for your current active character", - aliases = {"setfetterlvl", "setfriendship"}, permission = "player.setfetterlevel") + aliases = {"setfetterlvl", "setfriendship"}, permission = "player.setfetterlevel", description = "commands.setFetterLevel.description") public final class SetFetterLevelCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed); + CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); return; } if (args.size() != 1) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().SetFetterLevel_usage); + CommandHandler.sendMessage(sender, translate("commands.setFetterLevel.usage")); return; } try { int fetterLevel = Integer.parseInt(args.get(0)); if (fetterLevel < 0 || fetterLevel > 10) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().SetFetterLevel_fetter_level_must_between_0_and_10); + CommandHandler.sendMessage(sender, translate("commands.setFetterLevel.range_error")); return; } Avatar avatar = targetPlayer.getTeamManager().getCurrentAvatarEntity().getAvatar(); @@ -42,9 +42,9 @@ public final class SetFetterLevelCommand implements CommandHandler { avatar.save(); targetPlayer.sendPacket(new PacketAvatarFetterDataNotify(avatar)); - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().SetFetterLevel_fetter_set_level.replace("{level}", Integer.toString(fetterLevel))); + CommandHandler.sendMessage(sender, translate("commands.setFetterLevel.success", fetterLevel)); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().SetFetterLevel_invalid_fetter_level); + CommandHandler.sendMessage(sender, translate("commands.setFetterLevel.level_error")); } } diff --git a/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java b/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java index 48dbe141f..c7ed78a58 100644 --- a/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java @@ -10,13 +10,14 @@ import emu.grasscutter.command.CommandHandler; import emu.grasscutter.game.entity.EntityAvatar; import emu.grasscutter.game.player.Player; import emu.grasscutter.game.props.FightProperty; -import emu.grasscutter.languages.Language; import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; +import emu.grasscutter.utils.Language; -@Command(label = "setstats", usage = "setstats|stats ", - description = "Set fight property for your current active character", aliases = {"stats"}, permission = "player.setstats") +import static emu.grasscutter.utils.Language.translate; + +@Command(label = "setstats", usage = "setstats|stats ", aliases = {"stats"}, permission = "player.setstats", description = "commands.setStats.description") public final class SetStatsCommand implements CommandHandler { - class Stat { + static class Stat { String name; FightProperty prop; boolean percent; @@ -27,44 +28,43 @@ public final class SetStatsCommand implements CommandHandler { this.percent = percent; } } - Map stats; + + Map stats = new HashMap<>(); public SetStatsCommand() { - Language lang = Grasscutter.getLanguage(); - stats = new HashMap(); // Default stats - stats.put("maxhp", new Stat(lang.Stats_FIGHT_PROP_MAX_HP, FightProperty.FIGHT_PROP_MAX_HP, false)); - stats.put("hp", new Stat(lang.Stats_FIGHT_PROP_CUR_HP, FightProperty.FIGHT_PROP_CUR_HP, false)); - stats.put("atk", new Stat(lang.Stats_FIGHT_PROP_CUR_ATTACK, FightProperty.FIGHT_PROP_CUR_ATTACK, false)); - stats.put("atkb", new Stat(lang.Stats_FIGHT_PROP_BASE_ATTACK, FightProperty.FIGHT_PROP_BASE_ATTACK, false)); // This doesn't seem to get used to recalculate ATK, so it's only useful for stuff like Bennett's buff. - stats.put("def", new Stat(lang.Stats_FIGHT_PROP_DEFENSE, FightProperty.FIGHT_PROP_DEFENSE, false)); - stats.put("em", new Stat(lang.Stats_FIGHT_PROP_ELEMENT_MASTERY, FightProperty.FIGHT_PROP_ELEMENT_MASTERY, false)); - stats.put("er", new Stat(lang.Stats_FIGHT_PROP_CHARGE_EFFICIENCY, FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY, true)); - stats.put("crate", new Stat(lang.Stats_FIGHT_PROP_CRITICAL, FightProperty.FIGHT_PROP_CRITICAL, true)); - stats.put("cdmg", new Stat(lang.Stats_FIGHT_PROP_CRITICAL_HURT, FightProperty.FIGHT_PROP_CRITICAL_HURT, true)); - stats.put("dmg", new Stat(lang.Stats_FIGHT_PROP_ADD_HURT, FightProperty.FIGHT_PROP_ADD_HURT, true)); // This seems to get reset after attacks - stats.put("eanemo", new Stat(lang.Stats_FIGHT_PROP_WIND_ADD_HURT, FightProperty.FIGHT_PROP_WIND_ADD_HURT, true)); - stats.put("ecryo", new Stat(lang.Stats_FIGHT_PROP_ICE_ADD_HURT, FightProperty.FIGHT_PROP_ICE_ADD_HURT, true)); - stats.put("edendro", new Stat(lang.Stats_FIGHT_PROP_GRASS_ADD_HURT, FightProperty.FIGHT_PROP_GRASS_ADD_HURT, true)); - stats.put("eelectro", new Stat(lang.Stats_FIGHT_PROP_ELEC_ADD_HURT, FightProperty.FIGHT_PROP_ELEC_ADD_HURT, true)); - stats.put("egeo", new Stat(lang.Stats_FIGHT_PROP_ROCK_ADD_HURT, FightProperty.FIGHT_PROP_ROCK_ADD_HURT, true)); - stats.put("ehydro", new Stat(lang.Stats_FIGHT_PROP_WATER_ADD_HURT, FightProperty.FIGHT_PROP_WATER_ADD_HURT, true)); - stats.put("epyro", new Stat(lang.Stats_FIGHT_PROP_FIRE_ADD_HURT, FightProperty.FIGHT_PROP_FIRE_ADD_HURT, true)); - stats.put("ephys", new Stat(lang.Stats_FIGHT_PROP_PHYSICAL_ADD_HURT, FightProperty.FIGHT_PROP_PHYSICAL_ADD_HURT, true)); - stats.put("resall", new Stat(lang.Stats_FIGHT_PROP_SUB_HURT, FightProperty.FIGHT_PROP_SUB_HURT, true)); // This seems to get reset after attacks - stats.put("resanemo", new Stat(lang.Stats_FIGHT_PROP_WIND_SUB_HURT, FightProperty.FIGHT_PROP_WIND_SUB_HURT, true)); - stats.put("rescryo", new Stat(lang.Stats_FIGHT_PROP_ICE_SUB_HURT, FightProperty.FIGHT_PROP_ICE_SUB_HURT, true)); - stats.put("resdendro", new Stat(lang.Stats_FIGHT_PROP_GRASS_SUB_HURT, FightProperty.FIGHT_PROP_GRASS_SUB_HURT, true)); - stats.put("reselectro", new Stat(lang.Stats_FIGHT_PROP_ELEC_SUB_HURT, FightProperty.FIGHT_PROP_ELEC_SUB_HURT, true)); - stats.put("resgeo", new Stat(lang.Stats_FIGHT_PROP_ROCK_SUB_HURT, FightProperty.FIGHT_PROP_ROCK_SUB_HURT, true)); - stats.put("reshydro", new Stat(lang.Stats_FIGHT_PROP_WATER_SUB_HURT, FightProperty.FIGHT_PROP_WATER_SUB_HURT, true)); - stats.put("respyro", new Stat(lang.Stats_FIGHT_PROP_FIRE_SUB_HURT, FightProperty.FIGHT_PROP_FIRE_SUB_HURT, true)); - stats.put("resphys", new Stat(lang.Stats_FIGHT_PROP_PHYSICAL_SUB_HURT, FightProperty.FIGHT_PROP_PHYSICAL_SUB_HURT, true)); - stats.put("cdr", new Stat(lang.Stats_FIGHT_PROP_SKILL_CD_MINUS_RATIO, FightProperty.FIGHT_PROP_SKILL_CD_MINUS_RATIO, true)); - stats.put("heal", new Stat(lang.Stats_FIGHT_PROP_HEAL_ADD, FightProperty.FIGHT_PROP_HEAL_ADD, true)); - stats.put("heali", new Stat(lang.Stats_FIGHT_PROP_HEALED_ADD, FightProperty.FIGHT_PROP_HEALED_ADD, true)); - stats.put("shield", new Stat(lang.Stats_FIGHT_PROP_SHIELD_COST_MINUS_RATIO, FightProperty.FIGHT_PROP_SHIELD_COST_MINUS_RATIO, true)); - stats.put("defi", new Stat(lang.Stats_FIGHT_PROP_DEFENCE_IGNORE_RATIO, FightProperty.FIGHT_PROP_DEFENCE_IGNORE_RATIO, true)); + stats.put("maxhp", new Stat(FightProperty.FIGHT_PROP_MAX_HP.toString(), FightProperty.FIGHT_PROP_MAX_HP, false)); + stats.put("hp", new Stat(FightProperty.FIGHT_PROP_CUR_HP.toString(), FightProperty.FIGHT_PROP_CUR_HP, false)); + stats.put("atk", new Stat(FightProperty.FIGHT_PROP_CUR_ATTACK.toString(), FightProperty.FIGHT_PROP_CUR_ATTACK, false)); + stats.put("atkb", new Stat(FightProperty.FIGHT_PROP_BASE_ATTACK.toString(), FightProperty.FIGHT_PROP_BASE_ATTACK, false)); // This doesn't seem to get used to recalculate ATK, so it's only useful for stuff like Bennett's buff. + stats.put("def", new Stat(FightProperty.FIGHT_PROP_DEFENSE.toString(), FightProperty.FIGHT_PROP_DEFENSE, false)); + stats.put("em", new Stat(FightProperty.FIGHT_PROP_ELEMENT_MASTERY.toString(), FightProperty.FIGHT_PROP_ELEMENT_MASTERY, false)); + stats.put("er", new Stat(FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY.toString(), FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY, true)); + stats.put("crate", new Stat(FightProperty.FIGHT_PROP_CRITICAL.toString(), FightProperty.FIGHT_PROP_CRITICAL, true)); + stats.put("cdmg", new Stat(FightProperty.FIGHT_PROP_CRITICAL_HURT.toString(), FightProperty.FIGHT_PROP_CRITICAL_HURT, true)); + stats.put("dmg", new Stat(FightProperty.FIGHT_PROP_ADD_HURT.toString(), FightProperty.FIGHT_PROP_ADD_HURT, true)); // This seems to get reset after attacks + stats.put("eanemo", new Stat(FightProperty.FIGHT_PROP_WIND_ADD_HURT.toString(), FightProperty.FIGHT_PROP_WIND_ADD_HURT, true)); + stats.put("ecryo", new Stat(FightProperty.FIGHT_PROP_ICE_ADD_HURT.toString(), FightProperty.FIGHT_PROP_ICE_ADD_HURT, true)); + stats.put("edendro", new Stat(FightProperty.FIGHT_PROP_GRASS_ADD_HURT.toString(), FightProperty.FIGHT_PROP_GRASS_ADD_HURT, true)); + stats.put("eelectro", new Stat(FightProperty.FIGHT_PROP_ELEC_ADD_HURT.toString(), FightProperty.FIGHT_PROP_ELEC_ADD_HURT, true)); + stats.put("egeo", new Stat(FightProperty.FIGHT_PROP_ROCK_ADD_HURT.toString(), FightProperty.FIGHT_PROP_ROCK_ADD_HURT, true)); + stats.put("ehydro", new Stat(FightProperty.FIGHT_PROP_WATER_ADD_HURT.toString(), FightProperty.FIGHT_PROP_WATER_ADD_HURT, true)); + stats.put("epyro", new Stat(FightProperty.FIGHT_PROP_FIRE_ADD_HURT.toString(), FightProperty.FIGHT_PROP_FIRE_ADD_HURT, true)); + stats.put("ephys", new Stat(FightProperty.FIGHT_PROP_PHYSICAL_ADD_HURT.toString(), FightProperty.FIGHT_PROP_PHYSICAL_ADD_HURT, true)); + stats.put("resall", new Stat(FightProperty.FIGHT_PROP_SUB_HURT.toString(), FightProperty.FIGHT_PROP_SUB_HURT, true)); // This seems to get reset after attacks + stats.put("resanemo", new Stat(FightProperty.FIGHT_PROP_WIND_SUB_HURT.toString(), FightProperty.FIGHT_PROP_WIND_SUB_HURT, true)); + stats.put("rescryo", new Stat(FightProperty.FIGHT_PROP_ICE_SUB_HURT.toString(), FightProperty.FIGHT_PROP_ICE_SUB_HURT, true)); + stats.put("resdendro", new Stat(FightProperty.FIGHT_PROP_GRASS_SUB_HURT.toString(), FightProperty.FIGHT_PROP_GRASS_SUB_HURT, true)); + stats.put("reselectro", new Stat(FightProperty.FIGHT_PROP_ELEC_SUB_HURT.toString(), FightProperty.FIGHT_PROP_ELEC_SUB_HURT, true)); + stats.put("resgeo", new Stat(FightProperty.FIGHT_PROP_ROCK_SUB_HURT.toString(), FightProperty.FIGHT_PROP_ROCK_SUB_HURT, true)); + stats.put("reshydro", new Stat(FightProperty.FIGHT_PROP_WATER_SUB_HURT.toString(), FightProperty.FIGHT_PROP_WATER_SUB_HURT, true)); + stats.put("respyro", new Stat(FightProperty.FIGHT_PROP_FIRE_SUB_HURT.toString(), FightProperty.FIGHT_PROP_FIRE_SUB_HURT, true)); + stats.put("resphys", new Stat(FightProperty.FIGHT_PROP_PHYSICAL_SUB_HURT.toString(), FightProperty.FIGHT_PROP_PHYSICAL_SUB_HURT, true)); + stats.put("cdr", new Stat(FightProperty.FIGHT_PROP_SKILL_CD_MINUS_RATIO.toString(), FightProperty.FIGHT_PROP_SKILL_CD_MINUS_RATIO, true)); + stats.put("heal", new Stat(FightProperty.FIGHT_PROP_HEAL_ADD.toString(), FightProperty.FIGHT_PROP_HEAL_ADD, true)); + stats.put("heali", new Stat(FightProperty.FIGHT_PROP_HEALED_ADD.toString(), FightProperty.FIGHT_PROP_HEALED_ADD, true)); + stats.put("shield", new Stat(FightProperty.FIGHT_PROP_SHIELD_COST_MINUS_RATIO.toString(), FightProperty.FIGHT_PROP_SHIELD_COST_MINUS_RATIO, true)); + stats.put("defi", new Stat(FightProperty.FIGHT_PROP_DEFENCE_IGNORE_RATIO.toString(), FightProperty.FIGHT_PROP_DEFENCE_IGNORE_RATIO, true)); // Compatibility aliases stats.put("mhp", stats.get("maxhp")); stats.put("cr", stats.get("crate")); @@ -175,26 +175,23 @@ public final class SetStatsCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { - Language lang = Grasscutter.getLanguage(); - String syntax = sender == null ? lang.SetStats_usage_console : lang.SetStats_usage_console; - String usage = syntax + lang.SetStats_help_message; + String syntax = sender == null ? translate("commands.setStats.usage_console") : translate("commands.setStats.ingame"); + String usage = syntax + translate("commands.setStats.help_message"); String statStr; String valueStr; if (targetPlayer == null) { - CommandHandler.sendMessage(sender, lang.Target_needed); + CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); return; } - switch (args.size()) { - default: - CommandHandler.sendMessage(sender, usage); - return; - case 2: - statStr = args.get(0).toLowerCase(); - valueStr = args.get(1); - break; - }; + if (args.size() == 2) { + statStr = args.get(0).toLowerCase(); + valueStr = args.get(1); + } else { + CommandHandler.sendMessage(sender, usage); + return; + } EntityAvatar entity = targetPlayer.getTeamManager().getCurrentAvatarEntity(); @@ -206,7 +203,7 @@ public final class SetStatsCommand implements CommandHandler { value = Float.parseFloat(valueStr); } } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, lang.SetStats_value_error); + CommandHandler.sendMessage(sender, translate("commands.setStats.value_error")); return; } @@ -220,15 +217,14 @@ public final class SetStatsCommand implements CommandHandler { valueStr = String.format("%.0f", value); } if (targetPlayer == sender) { - CommandHandler.sendMessage(sender, lang.SetStats_set_self.replace("{name}", stat.name).replace("{value}", valueStr)); + CommandHandler.sendMessage(sender, translate("commands.setStats.set_self", stat.name, valueStr)); } else { String uidStr = targetPlayer.getAccount().getId(); - CommandHandler.sendMessage(sender, lang.SetStats_set_for_uid.replace("{name}", stat.name).replace("{uid}", uidStr).replace("{value}", valueStr)); + CommandHandler.sendMessage(sender, translate("commands.setStats.set_self", stat.name, uidStr, valueStr)); } - return; } else { CommandHandler.sendMessage(sender, usage); - return; } + return; } } diff --git a/src/main/java/emu/grasscutter/command/commands/SetWorldLevelCommand.java b/src/main/java/emu/grasscutter/command/commands/SetWorldLevelCommand.java index cd53237ca..41b959336 100644 --- a/src/main/java/emu/grasscutter/command/commands/SetWorldLevelCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SetWorldLevelCommand.java @@ -7,27 +7,28 @@ import emu.grasscutter.game.player.Player; import java.util.List; +import static emu.grasscutter.utils.Language.translate; + @Command(label = "setworldlevel", usage = "setworldlevel ", - description = "Sets your world level (Relog to see proper effects)", - aliases = {"setworldlvl"}, permission = "player.setworldlevel") + aliases = {"setworldlvl"}, permission = "player.setworldlevel", description = "commands.setWorldLevel.description") public final class SetWorldLevelCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed); + CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); return; } if (args.size() < 1) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().SetWorldLevel_usage); + CommandHandler.sendMessage(sender, translate("commands.setWorldLevel.usage")); return; } try { int level = Integer.parseInt(args.get(0)); if (level > 8 || level < 0) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().SetWorldLevel_world_level_must_between_0_and_8); + CommandHandler.sendMessage(sender, translate("commands.setWorldLevel.value_error")); return; } @@ -35,9 +36,9 @@ public final class SetWorldLevelCommand implements CommandHandler { targetPlayer.getWorld().setWorldLevel(level); targetPlayer.setWorldLevel(level); - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().SetWorldLevel_set_world_level.replace("{level}", Integer.toString(level))); + CommandHandler.sendMessage(sender, translate("commands.setWorldLevel.success", Integer.toString(level))); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(null, Grasscutter.getLanguage().SetWorldLevel_invalid_world_level); + CommandHandler.sendMessage(null, translate("commands.setWorldLevel.invalid_world_level")); } } } diff --git a/src/main/java/emu/grasscutter/command/commands/SpawnCommand.java b/src/main/java/emu/grasscutter/command/commands/SpawnCommand.java index 48d6235cb..7f0c704c6 100644 --- a/src/main/java/emu/grasscutter/command/commands/SpawnCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SpawnCommand.java @@ -20,14 +20,15 @@ import javax.swing.text.html.parser.Entity; import java.util.List; import java.util.Random; -@Command(label = "spawn", usage = "spawn [amount] [level(monster only)]", - description = "Spawns an entity near you", permission = "server.spawn") +import static emu.grasscutter.utils.Language.translate; + +@Command(label = "spawn", usage = "spawn [amount] [level(monster only)]", permission = "server.spawn", description = "commands.spawn.description") public final class SpawnCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed); + CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); return; } @@ -39,23 +40,23 @@ public final class SpawnCommand implements CommandHandler { try { level = Integer.parseInt(args.get(2)); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_arguments); + CommandHandler.sendMessage(sender, translate("commands.execution.argument_error")); } // Fallthrough case 2: try { amount = Integer.parseInt(args.get(1)); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_amount); + CommandHandler.sendMessage(sender, translate("commands.generic.error.amount")); } // Fallthrough case 1: try { id = Integer.parseInt(args.get(0)); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_entity_id); + CommandHandler.sendMessage(sender, translate("commands.generic.error.entityId")); } break; default: - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Spawn_usage); + CommandHandler.sendMessage(sender, translate("commands.spawn.usage")); return; } @@ -63,7 +64,7 @@ public final class SpawnCommand implements CommandHandler { GadgetData gadgetData = GameData.getGadgetDataMap().get(id); ItemData itemData = GameData.getItemDataMap().get(id); if (monsterData == null && gadgetData == null && itemData == null) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_entity_id); + CommandHandler.sendMessage(sender, translate("commands.generic.error.entityId")); return; } Scene scene = targetPlayer.getScene(); @@ -99,7 +100,7 @@ public final class SpawnCommand implements CommandHandler { scene.addEntity(entity); } - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Spawn_message.replace("{amount}", Integer.toString(amount)).replace("{id}", Integer.toString(id))); + CommandHandler.sendMessage(sender, translate("commands.spawn.success", Integer.toString(amount), Integer.toString(id))); } private Position GetRandomPositionInCircle(Position origin, double radius){ diff --git a/src/main/java/emu/grasscutter/command/commands/StopCommand.java b/src/main/java/emu/grasscutter/command/commands/StopCommand.java index 4d0f8c1c5..129b27b24 100644 --- a/src/main/java/emu/grasscutter/command/commands/StopCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/StopCommand.java @@ -7,17 +7,18 @@ import emu.grasscutter.game.player.Player; import java.util.List; -@Command(label = "stop", usage = "stop", - description = "Stops the server", permission = "server.stop") +import static emu.grasscutter.utils.Language.translate; + +@Command(label = "stop", usage = "stop", permission = "server.stop", description = "commands.stop.description") public final class StopCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { - CommandHandler.sendMessage(null, Grasscutter.getLanguage().Stop_message); + CommandHandler.sendMessage(null, translate("commands.stop.success")); for (Player p : Grasscutter.getGameServer().getPlayers().values()) { - CommandHandler.sendMessage(p, Grasscutter.getLanguage().Stop_message); + CommandHandler.sendMessage(p, translate("commands.stop.success")); } - System.exit(1); + System.exit(1000); } } diff --git a/src/main/java/emu/grasscutter/command/commands/TalentCommand.java b/src/main/java/emu/grasscutter/command/commands/TalentCommand.java index 559006473..1540a81f8 100644 --- a/src/main/java/emu/grasscutter/command/commands/TalentCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/TalentCommand.java @@ -12,13 +12,14 @@ import emu.grasscutter.server.packet.send.PacketAvatarSkillUpgradeRsp; import java.util.List; -@Command(label = "talent", usage = "talent ", - description = "Set talent level for your current active character", permission = "player.settalent") +import static emu.grasscutter.utils.Language.translate; + +@Command(label = "talent", usage = "talent ", permission = "player.settalent", description = "commands.talent.description") public final class TalentCommand implements CommandHandler { private void setTalentLevel(Player sender, Player player, Avatar avatar, int talentId, int talentLevel) { int oldLevel = avatar.getSkillLevelMap().get(talentId); if (talentLevel < 0 || talentLevel > 15) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Talent_lower_16); + CommandHandler.sendMessage(sender, translate("commands.talent.lower_16")); return; } @@ -30,29 +31,29 @@ public final class TalentCommand implements CommandHandler { player.sendPacket(new PacketAvatarSkillChangeNotify(avatar, talentId, oldLevel, talentLevel)); player.sendPacket(new PacketAvatarSkillUpgradeRsp(avatar, talentId, oldLevel, talentLevel)); - String successMessage = Grasscutter.getLanguage().Talent_set_id.replace("{id}", Integer.toString(talentId)); + String successMessage = "commands.talent.set_id"; AvatarSkillDepotData depot = avatar.getData().getSkillDepot(); if (talentId == depot.getSkills().get(0)) { - successMessage = Grasscutter.getLanguage().Talent_set_atk; + successMessage = "commands.talent.set_atk"; } else if (talentId == depot.getSkills().get(1)) { - successMessage = Grasscutter.getLanguage().Talent_set_e; + successMessage = "commands.talent.set_e"; } else if (talentId == depot.getEnergySkill()) { - successMessage = Grasscutter.getLanguage().Talent_set_q; + successMessage = "commands.talent.set_q"; } - CommandHandler.sendMessage(sender, successMessage.replace("{level}", Integer.toString(talentLevel))); + CommandHandler.sendMessage(sender, translate(successMessage, talentLevel)); } @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed); + CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); return; } if (args.size() < 1){ - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Talent_usage_1); - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Talent_usage_2); - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Talent_usage_3); + CommandHandler.sendMessage(sender, translate("commands.talent.usage_1")); + CommandHandler.sendMessage(sender, translate("commands.talent.usage_2")); + CommandHandler.sendMessage(sender, translate("commands.talent.usage_3")); return; } @@ -60,66 +61,54 @@ public final class TalentCommand implements CommandHandler { Avatar avatar = entity.getAvatar(); String cmdSwitch = args.get(0); switch (cmdSwitch) { - default: - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Talent_usage_1); - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Talent_usage_2); - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Talent_usage_3); - return; - case "set": - if (args.size() < 3){ - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Talent_usage_1); - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Talent_usage_3); + default -> { + CommandHandler.sendMessage(sender, translate("commands.talent.usage_1")); + CommandHandler.sendMessage(sender, translate("commands.talent.usage_2")); + CommandHandler.sendMessage(sender, translate("commands.talent.usage_3")); + return; + } + case "set" -> { + if (args.size() < 3) { + CommandHandler.sendMessage(sender, translate("commands.talent.usage_1")); + CommandHandler.sendMessage(sender, translate("commands.talent.usage_3")); return; } - try { int skillId = Integer.parseInt(args.get(1)); int newLevel = Integer.parseInt(args.get(2)); setTalentLevel(sender, targetPlayer, avatar, skillId, newLevel); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Talent_invalid_skill_id); + CommandHandler.sendMessage(sender, translate("commands.talent.invalid_skill_id")); return; } - break; - case "n": - case "e": - case "q": - if (args.size() < 2){ - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Talent_usage_2); + } + case "n", "e", "q" -> { + if (args.size() < 2) { + CommandHandler.sendMessage(sender, translate("commands.talent.usage_2")); return; } - AvatarSkillDepotData SkillDepot = avatar.getData().getSkillDepot(); - int skillId; - switch (cmdSwitch) { - default: - skillId = SkillDepot.getSkills().get(0); - break; - case "e": - skillId = SkillDepot.getSkills().get(1); - break; - case "q": - skillId = SkillDepot.getEnergySkill(); - break; - } - + int skillId = switch (cmdSwitch) { + default -> SkillDepot.getSkills().get(0); + case "e" -> SkillDepot.getSkills().get(1); + case "q" -> SkillDepot.getEnergySkill(); + }; try { int newLevel = Integer.parseInt(args.get(1)); setTalentLevel(sender, targetPlayer, avatar, skillId, newLevel); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Talent_invalid_talent_level); + CommandHandler.sendMessage(sender, translate("commands.talent.invalid_level")); return; } - break; - case "getid": + } + case "getid" -> { int skillIdNorAtk = avatar.getData().getSkillDepot().getSkills().get(0); int skillIdE = avatar.getData().getSkillDepot().getSkills().get(1); int skillIdQ = avatar.getData().getSkillDepot().getEnergySkill(); - - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Talent_normal_attack_id.replace("{id}", Integer.toString(skillIdNorAtk))); - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Talent_e_skill_id.replace("{id}", Integer.toString(skillIdE))); - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Talent_q_skill_id.replace("{id}", Integer.toString(skillIdQ))); - break; + CommandHandler.sendMessage(sender, translate("commands.talent.normal_attack_id", Integer.toString(skillIdNorAtk))); + CommandHandler.sendMessage(sender, translate("commands.talent.e_skill_id", Integer.toString(skillIdE))); + CommandHandler.sendMessage(sender, translate("commands.talent.q_skill_id", Integer.toString(skillIdQ))); + } } } } diff --git a/src/main/java/emu/grasscutter/command/commands/TeleportAllCommand.java b/src/main/java/emu/grasscutter/command/commands/TeleportAllCommand.java index 4c9f2a9cd..175f69b81 100644 --- a/src/main/java/emu/grasscutter/command/commands/TeleportAllCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/TeleportAllCommand.java @@ -8,18 +8,20 @@ import emu.grasscutter.utils.Position; import java.util.List; -@Command(label = "tpall", usage = "tpall", - description = "Teleports all players in your world to your position", permission = "player.tpall") +import static emu.grasscutter.utils.Language.translate; + +@Command(label = "tpall", usage = "tpall", permission = "player.tpall", description = "commands.teleportAll.description") public final class TeleportAllCommand implements CommandHandler { + @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed); + CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); return; } if (!targetPlayer.getWorld().isMultiplayer()) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().TeleportAll_message); + CommandHandler.sendMessage(sender, translate("commands.teleportAll.error")); return; } @@ -30,5 +32,7 @@ public final class TeleportAllCommand implements CommandHandler { player.getWorld().transferPlayerToScene(player, targetPlayer.getSceneId(), pos); } + + CommandHandler.sendMessage(sender, translate("commands.teleportAll.success")); } } diff --git a/src/main/java/emu/grasscutter/command/commands/TeleportCommand.java b/src/main/java/emu/grasscutter/command/commands/TeleportCommand.java index f5a533bef..0d15b55af 100644 --- a/src/main/java/emu/grasscutter/command/commands/TeleportCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/TeleportCommand.java @@ -8,8 +8,9 @@ import emu.grasscutter.utils.Position; import java.util.List; -@Command(label = "teleport", usage = "teleport [scene id]", aliases = {"tp"}, - description = "Change the player's position.", permission = "player.teleport") +import static emu.grasscutter.utils.Language.translate; + +@Command(label = "teleport", usage = "teleport [scene id]", aliases = {"tp"}, permission = "player.teleport", description = "commands.teleport.description") public final class TeleportCommand implements CommandHandler { private float parseRelative(String input, Float current) { // TODO: Maybe this will be useful elsewhere later @@ -26,7 +27,7 @@ public final class TeleportCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed); + CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); return; } @@ -41,7 +42,7 @@ public final class TeleportCommand implements CommandHandler { try { sceneId = Integer.parseInt(args.get(3)); }catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_arguments); + CommandHandler.sendMessage(sender, translate("commands.execution.argument_error")); } // Fallthrough case 3: try { @@ -49,20 +50,23 @@ public final class TeleportCommand implements CommandHandler { y = parseRelative(args.get(1), y); z = parseRelative(args.get(2), z); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Teleport_invalid_position); + CommandHandler.sendMessage(sender, translate("commands.teleport.invalid_position")); } break; default: - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Teleport_usage); + CommandHandler.sendMessage(sender, translate("commands.teleport.usage")); return; } Position target_pos = new Position(x, y, z); boolean result = targetPlayer.getWorld().transferPlayerToScene(targetPlayer, sceneId, target_pos); if (!result) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Teleport_invalid_position); + CommandHandler.sendMessage(sender, translate("commands.teleport.invalid_position")); } else { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Teleport_message.replace("{name}", targetPlayer.getNickname()).replace("{x}", Float.toString(x)).replace("{y}", Float.toString(y)).replace("{z}", Float.toString(z)).replace("{id}", Integer.toString(sceneId))); + CommandHandler.sendMessage(sender, translate("commands.teleport.success", + targetPlayer.getNickname(), Float.toString(x), Float.toString(y), + Float.toString(z), Integer.toString(sceneId)) + ); } } diff --git a/src/main/java/emu/grasscutter/command/commands/WeatherCommand.java b/src/main/java/emu/grasscutter/command/commands/WeatherCommand.java index eaf73e53a..dd0002790 100644 --- a/src/main/java/emu/grasscutter/command/commands/WeatherCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/WeatherCommand.java @@ -9,14 +9,15 @@ import emu.grasscutter.server.packet.send.PacketSceneAreaWeatherNotify; import java.util.List; -@Command(label = "weather", usage = "weather [climateId]", - description = "Changes the weather.", aliases = {"w"}, permission = "player.weather") +import static emu.grasscutter.utils.Language.translate; + +@Command(label = "weather", usage = "weather [climateId]", aliases = {"w"}, permission = "player.weather", description = "commands.weather.description") public final class WeatherCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed); + CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); return; } @@ -27,17 +28,17 @@ public final class WeatherCommand implements CommandHandler { try { climateId = Integer.parseInt(args.get(1)); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Weather_invalid_id); + CommandHandler.sendMessage(sender, translate("commands.weather.invalid_id")); } case 1: try { weatherId = Integer.parseInt(args.get(0)); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Weather_invalid_id); + CommandHandler.sendMessage(sender, translate("commands.weather.invalid_id")); } break; default: - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Weather_usage); + CommandHandler.sendMessage(sender, translate("commands.weather.usage")); return; } @@ -46,7 +47,6 @@ public final class WeatherCommand implements CommandHandler { targetPlayer.getScene().setWeather(weatherId); targetPlayer.getScene().setClimate(climate); targetPlayer.getScene().broadcastPacket(new PacketSceneAreaWeatherNotify(targetPlayer)); - CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Weather_message.replace("{weatherId}", Integer.toString(weatherId)).replace("{climateId}", Integer.toString(climateId))); - + CommandHandler.sendMessage(sender, translate("commands.weather.success", Integer.toString(weatherId), Integer.toString(climateId))); } } diff --git a/src/main/java/emu/grasscutter/data/ResourceLoader.java b/src/main/java/emu/grasscutter/data/ResourceLoader.java index 5478052d8..b1e3da9ff 100644 --- a/src/main/java/emu/grasscutter/data/ResourceLoader.java +++ b/src/main/java/emu/grasscutter/data/ResourceLoader.java @@ -7,6 +7,7 @@ import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; +import com.google.gson.Gson; import emu.grasscutter.utils.Utils; import org.reflections.Reflections; @@ -120,14 +121,15 @@ public class ResourceLoader { @SuppressWarnings({"rawtypes", "unchecked"}) protected static void loadFromResource(Class c, String fileName, Int2ObjectMap map) throws Exception { - try (FileReader fileReader = new FileReader(Grasscutter.getConfig().RESOURCE_FOLDER + "ExcelBinOutput/" + fileName)) { - List list = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, c).getType()); + FileReader fileReader = new FileReader(Grasscutter.getConfig().RESOURCE_FOLDER + "ExcelBinOutput/" + fileName); + Gson gson = Grasscutter.getGsonFactory(); + List list = gson.fromJson(fileReader, List.class); - for (Object o : list) { - GameResource res = (GameResource) o; - res.onLoad(); - map.put(res.getId(), res); - } + for (Object o : list) { + Map tempMap = Utils.switchPropertiesUpperLowerCase((Map) o, c); + GameResource res = gson.fromJson(gson.toJson(tempMap), TypeToken.get(c).getType()); + res.onLoad(); + map.put(res.getId(), res); } } diff --git a/src/main/java/emu/grasscutter/game/entity/EntityMonster.java b/src/main/java/emu/grasscutter/game/entity/EntityMonster.java index c9d0c0982..0ae6f356b 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityMonster.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityMonster.java @@ -117,6 +117,7 @@ public class EntityMonster extends GameEntity { this.getScene().getDeadSpawnedEntities().add(getSpawnEntry()); } if (getScene().getScriptManager().isInit() && this.getGroupId() > 0) { + getScene().getScriptManager().onMonsterDie(); getScene().getScriptManager().callEvent(EventType.EVENT_ANY_MONSTER_DIE, null); } if (getScene().getChallenge() != null && getScene().getChallenge().getGroup().id == this.getGroupId()) { diff --git a/src/main/java/emu/grasscutter/game/expedition/ExpeditionInfo.java b/src/main/java/emu/grasscutter/game/expedition/ExpeditionInfo.java new file mode 100644 index 000000000..867d692cf --- /dev/null +++ b/src/main/java/emu/grasscutter/game/expedition/ExpeditionInfo.java @@ -0,0 +1,44 @@ +package emu.grasscutter.game.expedition; + +import dev.morphia.annotations.Entity; + +@Entity +public class ExpeditionInfo { + + public int getState() { + return state; + } + + public void setState(int state) { + this.state = state; + } + + public int getExpId() { + return expId; + } + + public void setExpId(int expId) { + this.expId = expId; + } + + public int getHourTime() { + return hourTime; + } + + public void setHourTime(int hourTime) { + this.hourTime = hourTime; + } + + public int getStartTime() { + return startTime; + } + + public void setStartTime(int startTime) { + this.startTime = startTime; + } + + private int state; + private int expId; + private int hourTime; + private int startTime; +} diff --git a/src/main/java/emu/grasscutter/game/expedition/ExpeditionManager.java b/src/main/java/emu/grasscutter/game/expedition/ExpeditionManager.java new file mode 100644 index 000000000..5d1b652e1 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/expedition/ExpeditionManager.java @@ -0,0 +1,46 @@ +package emu.grasscutter.game.expedition; + +import com.google.gson.reflect.TypeToken; +import emu.grasscutter.Grasscutter; +import emu.grasscutter.server.game.GameServer; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; + +import java.io.FileReader; +import java.util.Collection; +import java.util.List; + +public class ExpeditionManager { + public GameServer getGameServer() { + return gameServer; + } + + private final GameServer gameServer; + + public Int2ObjectMap> getExpeditionRewardDataList() { return expeditionRewardData; } + + private final Int2ObjectMap> expeditionRewardData; + + public ExpeditionManager(GameServer gameServer) { + this.gameServer = gameServer; + this.expeditionRewardData = new Int2ObjectOpenHashMap<>(); + this.load(); + } + + public synchronized void load() { + try (FileReader fileReader = new FileReader(Grasscutter.getConfig().DATA_FOLDER + "ExpeditionReward.json")) { + getExpeditionRewardDataList().clear(); + List banners = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, ExpeditionRewardInfo.class).getType()); + if(banners.size() > 0) { + for (ExpeditionRewardInfo di : banners) { + getExpeditionRewardDataList().put(di.getExpId(), di.getExpeditionRewardDataList()); + } + Grasscutter.getLogger().info("Expedition reward successfully loaded."); + } else { + Grasscutter.getLogger().error("Unable to load expedition reward. Expedition reward size is 0."); + } + } catch (Exception e) { + Grasscutter.getLogger().error("Unable to load expedition reward.", e); + } + } +} diff --git a/src/main/java/emu/grasscutter/game/expedition/ExpeditionRewardData.java b/src/main/java/emu/grasscutter/game/expedition/ExpeditionRewardData.java new file mode 100644 index 000000000..ded817a3e --- /dev/null +++ b/src/main/java/emu/grasscutter/game/expedition/ExpeditionRewardData.java @@ -0,0 +1,18 @@ +package emu.grasscutter.game.expedition; + +public class ExpeditionRewardData { + private int itemId; + private int minCount; + private int maxCount; + + public int getItemId() { + return itemId; + } + + public int getMinCount() { return minCount; } + + public int getMaxCount() { + return maxCount; + } + +} diff --git a/src/main/java/emu/grasscutter/game/expedition/ExpeditionRewardDataList.java b/src/main/java/emu/grasscutter/game/expedition/ExpeditionRewardDataList.java new file mode 100644 index 000000000..27a7534e2 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/expedition/ExpeditionRewardDataList.java @@ -0,0 +1,15 @@ +package emu.grasscutter.game.expedition; + +import java.util.List; + +public class ExpeditionRewardDataList { + public int getHourTime() { + return hourTime; + } + public List getExpeditionRewardData() { + return expeditionRewardData; + } + + private int hourTime; + private List expeditionRewardData; +} diff --git a/src/main/java/emu/grasscutter/game/expedition/ExpeditionRewardInfo.java b/src/main/java/emu/grasscutter/game/expedition/ExpeditionRewardInfo.java new file mode 100644 index 000000000..4e0039ff9 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/expedition/ExpeditionRewardInfo.java @@ -0,0 +1,16 @@ +package emu.grasscutter.game.expedition; + +import java.util.List; + +public class ExpeditionRewardInfo { + public int getExpId() { + return expId; + } + + public List getExpeditionRewardDataList() { + return expeditionRewardDataList; + } + + private int expId; + private List expeditionRewardDataList; +} diff --git a/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java b/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java index 2317af38e..b48cb0898 100644 --- a/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java +++ b/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java @@ -96,7 +96,7 @@ public class GachaBanner { return toProto(""); } public GachaInfo toProto(String sessionKey) { - String record = "https://" + String record = "http" + (Grasscutter.getConfig().getDispatchOptions().FrontHTTPS ? "s" : "") + "://" + (Grasscutter.getConfig().getDispatchOptions().PublicIp.isEmpty() ? Grasscutter.getConfig().getDispatchOptions().Ip : Grasscutter.getConfig().getDispatchOptions().PublicIp) diff --git a/src/main/java/emu/grasscutter/game/managers/MovementManager/MovementManager.java b/src/main/java/emu/grasscutter/game/managers/MovementManager/MovementManager.java deleted file mode 100644 index a08280378..000000000 --- a/src/main/java/emu/grasscutter/game/managers/MovementManager/MovementManager.java +++ /dev/null @@ -1,359 +0,0 @@ -package emu.grasscutter.game.managers.MovementManager; - -import emu.grasscutter.Grasscutter; -import emu.grasscutter.game.entity.EntityAvatar; -import emu.grasscutter.game.entity.GameEntity; -import emu.grasscutter.game.player.Player; -import emu.grasscutter.game.props.FightProperty; -import emu.grasscutter.game.props.LifeState; -import emu.grasscutter.game.props.PlayerProperty; -import emu.grasscutter.net.proto.EntityMoveInfoOuterClass; -import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo; -import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState; -import emu.grasscutter.net.proto.PlayerDieTypeOuterClass.PlayerDieType; -import emu.grasscutter.net.proto.VectorOuterClass; -import emu.grasscutter.server.game.GameSession; -import emu.grasscutter.server.packet.send.*; -import emu.grasscutter.utils.Position; -import org.jetbrains.annotations.NotNull; - -import java.lang.Math; -import java.util.*; - -public class MovementManager { - - public HashMap> MotionStatesCategorized = new HashMap<>(); - - private enum Consumption { - None(0), - - // consume - CLIMB_START(-500), - CLIMBING(-150), - CLIMB_JUMP(-2500), - DASH(-1800), - SPRINT(-360), - FLY(-60), - SWIM_DASH_START(-200), - SWIM_DASH(-200), - SWIMMING(-80), - - // restore - STANDBY(500), - RUN(500), - WALK(500), - STANDBY_MOVE(500), - POWERED_FLY(500); - - public final int amount; - Consumption(int amount) { - this.amount = amount; - } - } - - - private MotionState previousState = MotionState.MOTION_STANDBY; - private MotionState currentState = MotionState.MOTION_STANDBY; - private Position previousCoordinates = new Position(0, 0, 0); - private Position currentCoordinates = new Position(0, 0, 0); - - private final Player player; - - private float landSpeed = 0; - private Timer movementManagerTickTimer; - private GameSession cachedSession = null; - private GameEntity cachedEntity = null; - - private int staminaRecoverDelay = 0; - - public MovementManager(Player player) { - previousCoordinates.add(new Position(0,0,0)); - this.player = player; - - MotionStatesCategorized.put("SWIM", new HashSet<>(Arrays.asList( - MotionState.MOTION_SWIM_MOVE, - MotionState.MOTION_SWIM_IDLE, - MotionState.MOTION_SWIM_DASH, - MotionState.MOTION_SWIM_JUMP - ))); - - MotionStatesCategorized.put("STANDBY", new HashSet<>(Arrays.asList( - MotionState.MOTION_STANDBY, - MotionState.MOTION_STANDBY_MOVE, - MotionState.MOTION_DANGER_STANDBY, - MotionState.MOTION_DANGER_STANDBY_MOVE, - MotionState.MOTION_LADDER_TO_STANDBY, - MotionState.MOTION_JUMP_UP_WALL_FOR_STANDBY - ))); - - MotionStatesCategorized.put("CLIMB", new HashSet<>(Arrays.asList( - MotionState.MOTION_CLIMB, - MotionState.MOTION_CLIMB_JUMP, - MotionState.MOTION_STANDBY_TO_CLIMB, - MotionState.MOTION_LADDER_IDLE, - MotionState.MOTION_LADDER_MOVE, - MotionState.MOTION_LADDER_SLIP, - MotionState.MOTION_STANDBY_TO_LADDER - ))); - - MotionStatesCategorized.put("FLY", new HashSet<>(Arrays.asList( - MotionState.MOTION_FLY, - MotionState.MOTION_FLY_IDLE, - MotionState.MOTION_FLY_SLOW, - MotionState.MOTION_FLY_FAST, - MotionState.MOTION_POWERED_FLY - ))); - - MotionStatesCategorized.put("RUN", new HashSet<>(Arrays.asList( - MotionState.MOTION_DASH, - MotionState.MOTION_DANGER_DASH, - MotionState.MOTION_DASH_BEFORE_SHAKE, - MotionState.MOTION_RUN, - MotionState.MOTION_DANGER_RUN, - MotionState.MOTION_WALK, - MotionState.MOTION_DANGER_WALK - ))); - } - - public void handle(GameSession session, EntityMoveInfoOuterClass.EntityMoveInfo moveInfo, GameEntity entity) { - if (movementManagerTickTimer == null) { - movementManagerTickTimer = new Timer(); - movementManagerTickTimer.scheduleAtFixedRate(new MotionManagerTick(), 0, 200); - } - // cache info for later use in tick - cachedSession = session; - cachedEntity = entity; - - MotionInfo motionInfo = moveInfo.getMotionInfo(); - moveEntity(entity, moveInfo); - VectorOuterClass.Vector posVector = motionInfo.getPos(); - Position newPos = new Position(posVector.getX(), - posVector.getY(), posVector.getZ());; - if (newPos.getX() != 0 && newPos.getY() != 0 && newPos.getZ() != 0) { - currentCoordinates = newPos; - } - currentState = motionInfo.getState(); - Grasscutter.getLogger().debug("" + currentState); - handleFallOnGround(motionInfo); - } - - public void resetTimer() { - movementManagerTickTimer.cancel(); - movementManagerTickTimer = null; - } - - private void moveEntity(GameEntity entity, EntityMoveInfoOuterClass.EntityMoveInfo moveInfo) { - entity.getPosition().set(moveInfo.getMotionInfo().getPos()); - entity.getRotation().set(moveInfo.getMotionInfo().getRot()); - entity.setLastMoveSceneTimeMs(moveInfo.getSceneTime()); - entity.setLastMoveReliableSeq(moveInfo.getReliableSeq()); - entity.setMotionState(moveInfo.getMotionInfo().getState()); - } - - private boolean isPlayerMoving() { - float diffX = currentCoordinates.getX() - previousCoordinates.getX(); - float diffY = currentCoordinates.getY() - previousCoordinates.getY(); - float diffZ = currentCoordinates.getZ() - previousCoordinates.getZ(); - // Grasscutter.getLogger().debug("isPlayerMoving: " + previousCoordinates + ", " + currentCoordinates + ", " + diffX + ", " + diffY + ", " + diffZ); - return Math.abs(diffX) > 0.2 || Math.abs(diffY) > 0.1 || Math.abs(diffZ) > 0.2; - } - - private int getCurrentStamina() { - return player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA); - } - - private int getMaximumStamina() { - return player.getProperty(PlayerProperty.PROP_MAX_STAMINA); - } - - - - // Returns new stamina - public int updateStamina(GameSession session, int amount) { - int currentStamina = session.getPlayer().getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA); - if (amount == 0) { - return currentStamina; - } - int playerMaxStamina = session.getPlayer().getProperty(PlayerProperty.PROP_MAX_STAMINA); - int newStamina = currentStamina + amount; - if (newStamina < 0) { - newStamina = 0; - } - if (newStamina > playerMaxStamina) { - newStamina = playerMaxStamina; - } - session.getPlayer().setProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA, newStamina); - return newStamina; - } - - private void handleFallOnGround(@NotNull MotionInfo motionInfo) { - MotionState state = motionInfo.getState(); - // land speed and fall on ground event arrive in different packets - // cache land speed - if (state == MotionState.MOTION_LAND_SPEED) { - landSpeed = motionInfo.getSpeed().getY(); - } - if (state == MotionState.MOTION_FALL_ON_GROUND) { - float currentHP = cachedEntity.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP); - float maxHP = cachedEntity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); - float damage = 0; -// Grasscutter.getLogger().debug("LandSpeed: " + landSpeed); - if (landSpeed < -23.5) { - damage = (float)(maxHP * 0.33); - } - if (landSpeed < -25) { - damage = (float)(maxHP * 0.5); - } - if (landSpeed < -26.5) { - damage = (float)(maxHP * 0.66); - } - if (landSpeed < -28) { - damage = (maxHP * 1); - } - float newHP = currentHP - damage; - if (newHP < 0) { - newHP = 0; - } -// Grasscutter.getLogger().debug("Max: " + maxHP + "\tCurr: " + currentHP + "\tDamage: " + damage + "\tnewHP: " + newHP); - cachedEntity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, newHP); - cachedEntity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(cachedEntity, FightProperty.FIGHT_PROP_CUR_HP)); - if (newHP == 0) { - killAvatar(cachedSession, cachedEntity, PlayerDieType.PLAYER_DIE_FALL); - } - landSpeed = 0; - } - } - - private void handleDrowning() { - int stamina = getCurrentStamina(); - if (stamina < 10) { - boolean isSwimming = MotionStatesCategorized.get("SWIM").contains(currentState); - Grasscutter.getLogger().debug(player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA) + "/" + player.getProperty(PlayerProperty.PROP_MAX_STAMINA) + "\t" + currentState + "\t" + isSwimming); - if (isSwimming && currentState != MotionState.MOTION_SWIM_IDLE) { - killAvatar(cachedSession, cachedEntity, PlayerDieType.PLAYER_DIE_DRAWN); - } - } - } - - public void killAvatar(GameSession session, GameEntity entity, PlayerDieType dieType) { - cachedSession.send(new PacketAvatarLifeStateChangeNotify( - cachedSession.getPlayer().getTeamManager().getCurrentAvatarEntity().getAvatar(), - LifeState.LIFE_DEAD, - dieType - )); - cachedSession.send(new PacketLifeStateChangeNotify( - cachedEntity, - LifeState.LIFE_DEAD, - dieType - )); - cachedEntity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, 0); - cachedEntity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(cachedEntity, FightProperty.FIGHT_PROP_CUR_HP)); - entity.getWorld().broadcastPacket(new PacketLifeStateChangeNotify(0, entity, LifeState.LIFE_DEAD)); - session.getPlayer().getScene().removeEntity(entity); - ((EntityAvatar)entity).onDeath(dieType, 0); - } - - private class MotionManagerTick extends TimerTask - { - public void run() { - if (Grasscutter.getConfig().OpenStamina) { - boolean moving = isPlayerMoving(); - if (moving || (getCurrentStamina() < getMaximumStamina())) { - Grasscutter.getLogger().debug("Player moving: " + moving + ", stamina full: " + (getCurrentStamina() >= getMaximumStamina()) + ", recalculate stamina"); - Consumption consumption = Consumption.None; - - // TODO: refactor these conditions. - if (MotionStatesCategorized.get("CLIMB").contains(currentState)) { - if (currentState == MotionState.MOTION_CLIMB) { - // CLIMB - if (previousState != MotionState.MOTION_CLIMB && previousState != MotionState.MOTION_CLIMB_JUMP) { - consumption = Consumption.CLIMB_START; - } else { - consumption = Consumption.CLIMBING; - } - } - if (currentState == MotionState.MOTION_CLIMB_JUMP) { - if (previousState != MotionState.MOTION_CLIMB_JUMP) { - consumption = Consumption.CLIMB_JUMP; - } - } - if (currentState == MotionState.MOTION_JUMP) { - if (previousState == MotionState.MOTION_CLIMB) { - consumption = Consumption.CLIMB_JUMP; - } - } - } else if (MotionStatesCategorized.get("SWIM").contains((currentState))) { - // SWIM - if (currentState == MotionState.MOTION_SWIM_MOVE) { - consumption = Consumption.SWIMMING; - } - if (currentState == MotionState.MOTION_SWIM_DASH) { - if (previousState != MotionState.MOTION_SWIM_DASH) { - consumption = Consumption.SWIM_DASH_START; - } else { - consumption = Consumption.SWIM_DASH; - } - } - } else if (MotionStatesCategorized.get("RUN").contains(currentState)) { - // RUN, DASH and WALK - // DASH - if (currentState == MotionState.MOTION_DASH) { - if (previousState == MotionState.MOTION_DASH) { - consumption = Consumption.SPRINT; - } else { - // cost more to start dashing - consumption = Consumption.DASH; - } - } - // RUN - if (currentState == MotionState.MOTION_RUN) { - consumption = Consumption.RUN; - } - // WALK - if (currentState == MotionState.MOTION_WALK) { - consumption = Consumption.WALK; - } - } else if (MotionStatesCategorized.get("FLY").contains(currentState)) { - // FLY - consumption = Consumption.FLY; - // POWERED_FLY, e.g. wind tunnel - if (currentState == MotionState.MOTION_POWERED_FLY) { - consumption = Consumption.POWERED_FLY; - } - } else if (MotionStatesCategorized.get("STANDBY").contains(currentState)) { - // STAND - if (currentState == MotionState.MOTION_STANDBY) { - consumption = Consumption.STANDBY; - } - if (currentState == MotionState.MOTION_STANDBY_MOVE) { - consumption = Consumption.STANDBY_MOVE; - } - } - - // tick triggered - handleDrowning(); - - if (cachedSession != null) { - if (consumption.amount < 0) { - staminaRecoverDelay = 0; - } - if (consumption.amount > 0) { - if (staminaRecoverDelay < 5) { - staminaRecoverDelay++; - consumption = Consumption.None; - } - } - int newStamina = updateStamina(cachedSession, consumption.amount); - cachedSession.send(new PacketPlayerPropNotify(player, PlayerProperty.PROP_CUR_PERSIST_STAMINA)); - - Grasscutter.getLogger().debug(player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA) + "/" + player.getProperty(PlayerProperty.PROP_MAX_STAMINA) + "\t" + currentState + "\t" + "isMoving: " + isPlayerMoving() + "\t" + consumption + "(" + consumption.amount + ")"); - } - } - } - - previousState = currentState; - previousCoordinates = new Position(currentCoordinates.getX(), - currentCoordinates.getY(), currentCoordinates.getZ());; - } - } -} diff --git a/src/main/java/emu/grasscutter/game/managers/SotSManager/SotSManager.java b/src/main/java/emu/grasscutter/game/managers/SotSManager.java similarity index 65% rename from src/main/java/emu/grasscutter/game/managers/SotSManager/SotSManager.java rename to src/main/java/emu/grasscutter/game/managers/SotSManager.java index 4edad231e..ed67c6a62 100644 --- a/src/main/java/emu/grasscutter/game/managers/SotSManager/SotSManager.java +++ b/src/main/java/emu/grasscutter/game/managers/SotSManager.java @@ -1,7 +1,8 @@ -package emu.grasscutter.game.managers.SotSManager; +package emu.grasscutter.game.managers; import emu.grasscutter.Grasscutter; import emu.grasscutter.game.avatar.Avatar; +import emu.grasscutter.game.entity.EntityAvatar; import emu.grasscutter.game.player.Player; import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.PlayerProperty; @@ -14,6 +15,8 @@ import emu.grasscutter.server.packet.send.PacketEntityFightPropChangeReasonNotif import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; import java.util.List; +import java.util.Timer; +import java.util.TimerTask; // Statue of the Seven Manager public class SotSManager { @@ -21,6 +24,9 @@ public class SotSManager { // NOTE: Spring volume balance *1 = fight prop HP *100 private final Player player; + private Timer autoRecoverTimer; + + public final static int GlobalMaximumSpringVolume = 8500000; public SotSManager(Player player) { this.player = player; @@ -46,26 +52,44 @@ public class SotSManager { public void autoRevive(GameSession session) { player.getTeamManager().getActiveTeam().forEach(entity -> { boolean isAlive = entity.isAlive(); + float currentHP = entity.getAvatar().getFightProperty(FightProperty.FIGHT_PROP_CUR_HP); + float maxHP = entity.getAvatar().getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); +// Grasscutter.getLogger().debug("" + entity.getAvatar().getAvatarData().getName() + "\t" + currentHP + "/" + maxHP + "\t" + (isAlive ? "ALIVE":"DEAD")); + float newHP = (float)(maxHP * 0.3); + if (currentHP < newHP) { + updateAvatarCurHP(session, entity, newHP); + } if (!isAlive) { - float maxHP = entity.getAvatar().getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); - float newHP = (float)(maxHP * 0.3); - entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, newHP); entity.getWorld().broadcastPacket(new PacketAvatarLifeStateChangeNotify(entity.getAvatar())); } }); } public void scheduleAutoRecover(GameSession session) { - // TODO: play audio effects? possibly client side? - client automatically plays. - // delay 2.5 seconds - new Thread(() -> { - try { - Thread.sleep(2500); - autoRecover(session); - } catch (Exception e) { - Grasscutter.getLogger().error(e.getMessage()); - } - }).start(); + if (autoRecoverTimer == null) { + autoRecoverTimer = new Timer(); + autoRecoverTimer.schedule(new AutoRecoverTimerTick(session), 2500); + } + } + + public void cancelAutoRecover() { + if (autoRecoverTimer != null) { + autoRecoverTimer.cancel(); + autoRecoverTimer = null; + } + } + + private class AutoRecoverTimerTick extends TimerTask + { + private GameSession session; + + public AutoRecoverTimerTick(GameSession session) { + this.session = session; + } + public void run() { + autoRecover(session); + cancelAutoRecover(); + } } public void refillSpringVolume() { @@ -124,24 +148,27 @@ public class SotSManager { float newHP = currentHP + needSV / 100; // convert SV to HP - // TODO: Figure out why client shows current HP instead of added HP. - // Say an avatar had 12000 and now has 14000, it should show "2000". - // The client always show "+14000" which is incorrect. - - entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, newHP); - session.send(new PacketEntityFightPropChangeReasonNotify(entity, FightProperty.FIGHT_PROP_CUR_HP, - newHP, List.of(3), PropChangeReasonOuterClass.PropChangeReason.PROP_CHANGE_STATUE_RECOVER, - ChangeHpReasonOuterClass.ChangeHpReason.ChangeHpAddStatue)); - session.send(new PacketEntityFightPropUpdateNotify(entity, FightProperty.FIGHT_PROP_CUR_HP)); - - Avatar avatar = entity.getAvatar(); - avatar.setCurrentHp(newHP); - session.send(new PacketAvatarFightPropUpdateNotify(avatar, FightProperty.FIGHT_PROP_CUR_HP)); - player.save(); + updateAvatarCurHP(session, entity, newHP); } }); } } + private void updateAvatarCurHP(GameSession session, EntityAvatar entity, float newHP) { + // TODO: Figure out why client shows current HP instead of added HP. + // Say an avatar had 12000 and now has 14000, it should show "2000". + // The client always show "+14000" which is incorrect. + entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, newHP); + session.send(new PacketEntityFightPropChangeReasonNotify(entity, FightProperty.FIGHT_PROP_CUR_HP, + newHP, List.of(3), PropChangeReasonOuterClass.PropChangeReason.PROP_CHANGE_STATUE_RECOVER, + ChangeHpReasonOuterClass.ChangeHpReason.ChangeHpAddStatue)); + session.send(new PacketEntityFightPropUpdateNotify(entity, FightProperty.FIGHT_PROP_CUR_HP)); + + Avatar avatar = entity.getAvatar(); + avatar.setCurrentHp(newHP); + session.send(new PacketAvatarFightPropUpdateNotify(avatar, FightProperty.FIGHT_PROP_CUR_HP)); + player.save(); + } + } diff --git a/src/main/java/emu/grasscutter/game/managers/StaminaManager/AfterUpdateStaminaListener.java b/src/main/java/emu/grasscutter/game/managers/StaminaManager/AfterUpdateStaminaListener.java new file mode 100644 index 000000000..bb4f0b188 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/managers/StaminaManager/AfterUpdateStaminaListener.java @@ -0,0 +1,12 @@ +package emu.grasscutter.game.managers.StaminaManager; + +public interface AfterUpdateStaminaListener { + /** + * onBeforeUpdateStamina() will be called before StaminaManager attempt to update the player's current stamina. + * This gives listeners a chance to intercept this update. + * + * @param reason Why updating stamina. + * @param newStamina New Stamina value. + */ + void onAfterUpdateStamina(String reason, int newStamina); +} diff --git a/src/main/java/emu/grasscutter/game/managers/StaminaManager/BeforeUpdateStaminaListener.java b/src/main/java/emu/grasscutter/game/managers/StaminaManager/BeforeUpdateStaminaListener.java new file mode 100644 index 000000000..02f1f3522 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/managers/StaminaManager/BeforeUpdateStaminaListener.java @@ -0,0 +1,20 @@ +package emu.grasscutter.game.managers.StaminaManager; + +public interface BeforeUpdateStaminaListener { + /** + * onBeforeUpdateStamina() will be called before StaminaManager attempt to update the player's current stamina. + * This gives listeners a chance to intercept this update. + * @param reason Why updating stamina. + * @param newStamina New ABSOLUTE stamina value. + * @return true if you want to cancel this update, otherwise false. + */ + int onBeforeUpdateStamina(String reason, int newStamina); + /** + * onBeforeUpdateStamina() will be called before StaminaManager attempt to update the player's current stamina. + * This gives listeners a chance to intercept this update. + * @param reason Why updating stamina. + * @param consumption ConsumptionType and RELATIVE stamina change amount. + * @return true if you want to cancel this update, otherwise false. + */ + Consumption onBeforeUpdateStamina(String reason, Consumption consumption); +} \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/game/managers/StaminaManager/Consumption.java b/src/main/java/emu/grasscutter/game/managers/StaminaManager/Consumption.java new file mode 100644 index 000000000..23eb44be9 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/managers/StaminaManager/Consumption.java @@ -0,0 +1,15 @@ +package emu.grasscutter.game.managers.StaminaManager; + +public class Consumption { + public ConsumptionType consumptionType; + public int amount; + + public Consumption(ConsumptionType ct, int a) { + consumptionType = ct; + amount = a; + } + + public Consumption(ConsumptionType ct) { + this(ct, ct.amount); + } +} diff --git a/src/main/java/emu/grasscutter/game/managers/StaminaManager/ConsumptionType.java b/src/main/java/emu/grasscutter/game/managers/StaminaManager/ConsumptionType.java new file mode 100644 index 000000000..9afb2171c --- /dev/null +++ b/src/main/java/emu/grasscutter/game/managers/StaminaManager/ConsumptionType.java @@ -0,0 +1,30 @@ +package emu.grasscutter.game.managers.StaminaManager; + +public enum ConsumptionType { + None(0), + + // consume + CLIMB_START(-500), + CLIMBING(-150), + CLIMB_JUMP(-2500), + SPRINT(-1800), + DASH(-360), + FLY(-60), + SWIM_DASH_START(-20), + SWIM_DASH(-204), + SWIMMING(-80), // TODO: Slow swimming is handled per movement, not per second. Movement frequency depends on gender/age/height. + FIGHT(0), // See StaminaManager.getFightConsumption() + + // restore + STANDBY(500), + RUN(500), + WALK(500), + STANDBY_MOVE(500), + POWERED_FLY(500); + + public final int amount; + + ConsumptionType(int amount) { + this.amount = amount; + } +} \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/game/managers/StaminaManager/README.md b/src/main/java/emu/grasscutter/game/managers/StaminaManager/README.md new file mode 100644 index 000000000..39a4e7988 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/managers/StaminaManager/README.md @@ -0,0 +1,73 @@ +# Stamina Manager + +--- +## UpdateStamina +```java +// will use consumption.consumptionType as reason +public int updateStaminaRelative(GameSession session, Consumption consumption); +``` +```java +public int updateStaminaAbsolute(GameSession session, String reason, int newStamina) +``` + +--- +## Pause and Resume +```java +public void startSustainedStaminaHandler() +``` +```java +public void stopSustainedStaminaHandler() +``` + + +--- +## Stamina change listeners and intercepting +### BeforeUpdateStaminaListener +```java + +import emu.grasscutter.game.managers.StaminaManager.BeforeUpdateStaminaListener; + +// Listener sample: plugin disable CLIMB_JUMP stamina cost. +private class MyClass implements BeforeUpdateStaminaListener { + // Make your class implement the listener, and pass in your class as a listener. + + public MyClass() { + getStaminaManager().registerBeforeUpdateStaminaListener("myClass", this); + } + + @Override + public boolean onBeforeUpdateStamina(String reason, int newStamina) { + // do not intercept this update + return false; + } + + @Override + public boolean onBeforeUpdateStamina(String reason, Consumption consumption) { + // Try to intercept if this update is CLIMB_JUMP + if (consumption.consumptionType == ConsumptionType.CLIMB_JUMP) { + return true; + } + // If it is not CLIMB_JUMP, do not intercept. + return false; + } +} +``` +### AfterUpdateStaminaListener +```java + +import emu.grasscutter.game.managers.StaminaManager.AfterUpdateStaminaListener; + +// Listener sample: plugin listens for changes already made. +private class MyClass implements AfterUpdateStaminaListener { + // Make your class implement the listener, and pass in your class as a listener. + + public MyClass() { + registerAfterUpdateStaminaListener("myClass", this); + } + + @Override + public void onAfterUpdateStamina(String reason, int newStamina) { + // ... + } +} +``` \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java b/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java new file mode 100644 index 000000000..72b91c055 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java @@ -0,0 +1,472 @@ +package emu.grasscutter.game.managers.StaminaManager; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.game.entity.EntityAvatar; +import emu.grasscutter.game.entity.GameEntity; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.props.FightProperty; +import emu.grasscutter.game.props.LifeState; +import emu.grasscutter.game.props.PlayerProperty; +import emu.grasscutter.net.proto.EntityMoveInfoOuterClass.EntityMoveInfo; +import emu.grasscutter.net.proto.EvtDoSkillSuccNotifyOuterClass.EvtDoSkillSuccNotify; +import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo; +import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState; +import emu.grasscutter.net.proto.PlayerDieTypeOuterClass.PlayerDieType; +import emu.grasscutter.net.proto.VectorOuterClass.Vector; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.*; +import emu.grasscutter.utils.Position; + +import java.lang.Math; +import java.util.*; + +public class StaminaManager { + private final Player player; + private HashMap> MotionStatesCategorized = new HashMap<>(); + + public final static int GlobalMaximumStamina = 24000; + private Position currentCoordinates = new Position(0, 0, 0); + private Position previousCoordinates = new Position(0, 0, 0); + private MotionState currentState = MotionState.MOTION_STANDBY; + private MotionState previousState = MotionState.MOTION_STANDBY; + private Timer sustainedStaminaHandlerTimer; + private GameSession cachedSession = null; + private GameEntity cachedEntity = null; + private int staminaRecoverDelay = 0; + + private HashMap beforeUpdateStaminaListeners = new HashMap<>(); + private HashMap afterUpdateStaminaListeners = new HashMap<>(); + + public StaminaManager(Player player) { + this.player = player; + + MotionStatesCategorized.put("SWIM", new HashSet<>(Arrays.asList( + MotionState.MOTION_SWIM_MOVE, + MotionState.MOTION_SWIM_IDLE, + MotionState.MOTION_SWIM_DASH, + MotionState.MOTION_SWIM_JUMP + ))); + + MotionStatesCategorized.put("STANDBY", new HashSet<>(Arrays.asList( + MotionState.MOTION_STANDBY, + MotionState.MOTION_STANDBY_MOVE, + MotionState.MOTION_DANGER_STANDBY, + MotionState.MOTION_DANGER_STANDBY_MOVE, + MotionState.MOTION_LADDER_TO_STANDBY, + MotionState.MOTION_JUMP_UP_WALL_FOR_STANDBY + ))); + + MotionStatesCategorized.put("CLIMB", new HashSet<>(Arrays.asList( + MotionState.MOTION_CLIMB, + MotionState.MOTION_CLIMB_JUMP, + MotionState.MOTION_STANDBY_TO_CLIMB, + MotionState.MOTION_LADDER_IDLE, + MotionState.MOTION_LADDER_MOVE, + MotionState.MOTION_LADDER_SLIP, + MotionState.MOTION_STANDBY_TO_LADDER + ))); + + MotionStatesCategorized.put("FLY", new HashSet<>(Arrays.asList( + MotionState.MOTION_FLY, + MotionState.MOTION_FLY_IDLE, + MotionState.MOTION_FLY_SLOW, + MotionState.MOTION_FLY_FAST, + MotionState.MOTION_POWERED_FLY + ))); + + MotionStatesCategorized.put("RUN", new HashSet<>(Arrays.asList( + MotionState.MOTION_DASH, + MotionState.MOTION_DANGER_DASH, + MotionState.MOTION_DASH_BEFORE_SHAKE, + MotionState.MOTION_RUN, + MotionState.MOTION_DANGER_RUN, + MotionState.MOTION_WALK, + MotionState.MOTION_DANGER_WALK + ))); + + MotionStatesCategorized.put("FIGHT", new HashSet<>(Arrays.asList( + MotionState.MOTION_FIGHT + ))); + + MotionStatesCategorized.put("SKIFF", new HashSet<>(Arrays.asList( + MotionState.MOTION_SKIFF_BOARDING, + MotionState.MOTION_SKIFF_NORMAL, + MotionState.MOTION_SKIFF_DASH, + MotionState.MOTION_SKIFF_POWERED_DASH + ))); + } + + // Listeners + + public boolean registerBeforeUpdateStaminaListener(String listenerName, BeforeUpdateStaminaListener listener) { + if (beforeUpdateStaminaListeners.containsKey(listenerName)) { + return false; + } + beforeUpdateStaminaListeners.put(listenerName, listener); + return true; + } + + public boolean unregisterBeforeUpdateStaminaListener(String listenerName) { + if (!beforeUpdateStaminaListeners.containsKey(listenerName)) { + return false; + } + beforeUpdateStaminaListeners.remove(listenerName); + return true; + } + + public boolean registerAfterUpdateStaminaListener(String listenerName, AfterUpdateStaminaListener listener) { + if (afterUpdateStaminaListeners.containsKey(listenerName)) { + return false; + } + afterUpdateStaminaListeners.put(listenerName, listener); + return true; + } + + public boolean unregisterAfterUpdateStaminaListener(String listenerName) { + if (!afterUpdateStaminaListeners.containsKey(listenerName)) { + return false; + } + afterUpdateStaminaListeners.remove(listenerName); + return true; + } + + private boolean isPlayerMoving() { + float diffX = currentCoordinates.getX() - previousCoordinates.getX(); + float diffY = currentCoordinates.getY() - previousCoordinates.getY(); + float diffZ = currentCoordinates.getZ() - previousCoordinates.getZ(); + Grasscutter.getLogger().trace("isPlayerMoving: " + previousCoordinates + ", " + currentCoordinates + + ", " + diffX + ", " + diffY + ", " + diffZ); + return Math.abs(diffX) > 0.3 || Math.abs(diffY) > 0.2 || Math.abs(diffZ) > 0.3; + } + + public int updateStaminaRelative(GameSession session, Consumption consumption) { + int currentStamina = player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA); + if (consumption.amount == 0) { + return currentStamina; + } + // notify will update + for (Map.Entry listener : beforeUpdateStaminaListeners.entrySet()) { + Consumption overriddenConsumption = listener.getValue().onBeforeUpdateStamina(consumption.consumptionType.toString(), consumption); + if ((overriddenConsumption.consumptionType != consumption.consumptionType) && (overriddenConsumption.amount != consumption.amount)) { + Grasscutter.getLogger().debug("[StaminaManager] Stamina update relative(" + + consumption.consumptionType.toString() + ", " + consumption.amount + ") overridden to relative(" + + consumption.consumptionType.toString() + ", " + consumption.amount + ") by: " + listener.getKey()); + return currentStamina; + } + } + int playerMaxStamina = player.getProperty(PlayerProperty.PROP_MAX_STAMINA); + Grasscutter.getLogger().trace(currentStamina + "/" + playerMaxStamina + "\t" + currentState + "\t" + + (isPlayerMoving() ? "moving" : " ") + "\t(" + consumption.consumptionType + "," + + consumption.amount + ")"); + int newStamina = currentStamina + consumption.amount; + if (newStamina < 0) { + newStamina = 0; + } else if (newStamina > playerMaxStamina) { + newStamina = playerMaxStamina; + } + return setStamina(session, consumption.consumptionType.toString(), newStamina); + } + + public int updateStaminaAbsolute(GameSession session, String reason, int newStamina) { + int currentStamina = player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA); + // notify will update + for (Map.Entry listener : beforeUpdateStaminaListeners.entrySet()) { + int overriddenNewStamina = listener.getValue().onBeforeUpdateStamina(reason, newStamina); + if (overriddenNewStamina != newStamina) { + Grasscutter.getLogger().debug("[StaminaManager] Stamina update absolute(" + + reason + ", " + newStamina + ") overridden to absolute(" + + reason + ", " + newStamina + ") by: " + listener.getKey()); + return currentStamina; + } + } + int playerMaxStamina = player.getProperty(PlayerProperty.PROP_MAX_STAMINA); + if (newStamina < 0) { + newStamina = 0; + } else if (newStamina > playerMaxStamina) { + newStamina = playerMaxStamina; + } + return setStamina(session, reason, newStamina); + } + + // Returns new stamina and sends PlayerPropNotify + public int setStamina(GameSession session, String reason, int newStamina) { + // set stamina + player.setProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA, newStamina); + session.send(new PacketPlayerPropNotify(player, PlayerProperty.PROP_CUR_PERSIST_STAMINA)); + // notify updated + for (Map.Entry listener : afterUpdateStaminaListeners.entrySet()) { + listener.getValue().onAfterUpdateStamina(reason, newStamina); + } + return newStamina; + } + + // Kills avatar, removes entity and sends notification. + // TODO: Probably move this to Avatar class? since other components may also need to kill avatar. + public void killAvatar(GameSession session, GameEntity entity, PlayerDieType dieType) { + session.send(new PacketAvatarLifeStateChangeNotify(player.getTeamManager().getCurrentAvatarEntity().getAvatar(), + LifeState.LIFE_DEAD, dieType)); + session.send(new PacketLifeStateChangeNotify(entity, LifeState.LIFE_DEAD, dieType)); + entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, 0); + entity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(entity, FightProperty.FIGHT_PROP_CUR_HP)); + entity.getWorld().broadcastPacket(new PacketLifeStateChangeNotify(0, entity, LifeState.LIFE_DEAD)); + player.getScene().removeEntity(entity); + ((EntityAvatar) entity).onDeath(dieType, 0); + } + + public void startSustainedStaminaHandler() { + if (!player.isPaused() && sustainedStaminaHandlerTimer == null) { + sustainedStaminaHandlerTimer = new Timer(); + sustainedStaminaHandlerTimer.scheduleAtFixedRate(new SustainedStaminaHandler(), 0, 200); + Grasscutter.getLogger().debug("[MovementManager] SustainedStaminaHandlerTimer started"); + } + } + + public void stopSustainedStaminaHandler() { + if (sustainedStaminaHandlerTimer != null) { + sustainedStaminaHandlerTimer.cancel(); + sustainedStaminaHandlerTimer = null; + Grasscutter.getLogger().debug("[MovementManager] SustainedStaminaHandlerTimer stopped"); + } + } + + // Handlers + + // External trigger handler + + public void handleEvtDoSkillSuccNotify(GameSession session, EvtDoSkillSuccNotify notify) { + handleImmediateStamina(session, notify); + } + + public void handleCombatInvocationsNotify(GameSession session, EntityMoveInfo moveInfo, GameEntity entity) { + // cache info for later use in SustainedStaminaHandler tick + cachedSession = session; + cachedEntity = entity; + MotionInfo motionInfo = moveInfo.getMotionInfo(); + MotionState motionState = motionInfo.getState(); + boolean isReliable = moveInfo.getIsReliable(); + Grasscutter.getLogger().trace("" + motionState + "\t" + (isReliable ? "reliable" : "")); + if (isReliable) { + currentState = motionState; + Vector posVector = motionInfo.getPos(); + Position newPos = new Position(posVector.getX(), posVector.getY(), posVector.getZ()); + if (newPos.getX() != 0 && newPos.getY() != 0 && newPos.getZ() != 0) { + currentCoordinates = newPos; + } + } + startSustainedStaminaHandler(); + handleImmediateStamina(session, motionInfo, motionState, entity); + } + + // Internal handler + + private void handleImmediateStamina(GameSession session, MotionInfo motionInfo, MotionState motionState, + GameEntity entity) { + switch (motionState) { + case MOTION_DASH_BEFORE_SHAKE: + if (previousState != MotionState.MOTION_DASH_BEFORE_SHAKE) { + updateStaminaRelative(session, new Consumption(ConsumptionType.SPRINT)); + } + break; + case MOTION_CLIMB_JUMP: + if (previousState != MotionState.MOTION_CLIMB_JUMP) { + updateStaminaRelative(session, new Consumption(ConsumptionType.CLIMB_JUMP)); + } + break; + case MOTION_SWIM_DASH: + if (previousState != MotionState.MOTION_SWIM_DASH) { + updateStaminaRelative(session, new Consumption(ConsumptionType.SWIM_DASH_START)); + } + break; + } + } + + private void handleImmediateStamina(GameSession session, EvtDoSkillSuccNotify notify) { + Consumption consumption = getFightConsumption(notify.getSkillId()); + updateStaminaRelative(session, consumption); + } + + private class SustainedStaminaHandler extends TimerTask { + public void run() { + if (Grasscutter.getConfig().OpenStamina) { + boolean moving = isPlayerMoving(); + int currentStamina = player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA); + int maxStamina = player.getProperty(PlayerProperty.PROP_MAX_STAMINA); + if (moving || (currentStamina < maxStamina)) { + Grasscutter.getLogger().trace("Player moving: " + moving + ", stamina full: " + + (currentStamina >= maxStamina) + ", recalculate stamina"); + + Consumption consumption = new Consumption(ConsumptionType.None); + if (MotionStatesCategorized.get("CLIMB").contains(currentState)) { + consumption = getClimbSustainedConsumption(); + } else if (MotionStatesCategorized.get("SWIM").contains((currentState))) { + consumption = getSwimSustainedConsumptions(); + } else if (MotionStatesCategorized.get("RUN").contains(currentState)) { + consumption = getRunWalkDashSustainedConsumption(); + } else if (MotionStatesCategorized.get("FLY").contains(currentState)) { + consumption = getFlySustainedConsumption(); + } else if (MotionStatesCategorized.get("STANDBY").contains(currentState)) { + consumption = getStandSustainedConsumption(); + } + + /* + TODO: Reductions that apply to all motion types: + Elemental Resonance + Wind: -15% + Skills + Diona E: -10% while shield lasts + Barbara E: -12% while lasts + */ + if (cachedSession != null) { + if (consumption.amount < 0) { + staminaRecoverDelay = 0; + } + if (consumption.amount > 0 && consumption.consumptionType != ConsumptionType.POWERED_FLY) { + // For POWERED_FLY recover immediately - things like Amber's gliding exam may require this. + if (staminaRecoverDelay < 10) { + // For others recover after 2 seconds (10 ticks) - as official server does. + staminaRecoverDelay++; + consumption.amount = 0; + Grasscutter.getLogger().trace("[StaminaManager] Delaying recovery: " + staminaRecoverDelay); + } + } + updateStaminaRelative(cachedSession, consumption); + } + } + } + previousState = currentState; + previousCoordinates = new Position( + currentCoordinates.getX(), + currentCoordinates.getY(), + currentCoordinates.getZ() + ); + } + } + + private void handleDrowning() { + int stamina = player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA); + if (stamina < 10) { + Grasscutter.getLogger().trace(player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA) + "/" + + player.getProperty(PlayerProperty.PROP_MAX_STAMINA) + "\t" + currentState); + if (currentState != MotionState.MOTION_SWIM_IDLE) { + killAvatar(cachedSession, cachedEntity, PlayerDieType.PLAYER_DIE_DRAWN); + } + } + } + + // Consumption Calculators + + // Stamina Consumption Reduction: https://genshin-impact.fandom.com/wiki/Stamina + + private Consumption getFightConsumption(int skillCasting) { + /* TODO: + Instead of handling here, consider call StaminaManager.updateStamina****() with a Consumption object with + type=FIGHT and a modified amount when handling attacks for more accurate attack start/end time and + other info. Handling it here could be very complicated. + Charged attack + Default: + Polearm: (-2500) + Claymore: (-4000 per second, -800 each tick) + Catalyst: (-5000) + Talent: + Ningguang: When Ningguang is in possession of Star Jades, her Charged Attack does not consume Stamina. (Catalyst * 0) + Klee: When Jumpy Dumpty and Normal Attacks deal DMG, Klee has a 50% chance to obtain an Explosive Spark. + This Explosive Spark is consumed by the next Charged Attack, which costs no Stamina. (Catalyst * 0) + Constellations: + Hu Tao: While in a Paramita Papilio state activated by Guide to Afterlife, Hu Tao's Charge Attacks do not consume Stamina. (Polearm * 0) + Character Specific: + Keqing: (-2500) + Diluc: (Claymore * 0.5) + Talent Moving: (Those are skills too) + Ayaka: (-1000 initial) (-1500 per second) When the Cryo application at the end of Kamisato Art: Senho hits an opponent (+1000) + Mona: (-1000 initial) (-1500 per second) + */ + + // TODO: Currently only handling Ayaka and Mona's talent moving initial costs. + Consumption consumption = new Consumption(ConsumptionType.None); + HashMap fightingCost = new HashMap<>() {{ + put(10013, -1000); // Kamisato Ayaka + put(10413, -1000); // Mona + }}; + if (fightingCost.containsKey(skillCasting)) { + consumption = new Consumption(ConsumptionType.FIGHT, fightingCost.get(skillCasting)); + } + return consumption; + } + + private Consumption getClimbSustainedConsumption() { + Consumption consumption = new Consumption(ConsumptionType.None); + if (currentState == MotionState.MOTION_CLIMB && isPlayerMoving()) { + consumption = new Consumption(ConsumptionType.CLIMBING); + if (previousState != MotionState.MOTION_CLIMB && previousState != MotionState.MOTION_CLIMB_JUMP) { + consumption = new Consumption(ConsumptionType.CLIMB_START); + } + } + // TODO: Foods + return consumption; + } + + private Consumption getSwimSustainedConsumptions() { + handleDrowning(); + Consumption consumption = new Consumption(ConsumptionType.None); + if (currentState == MotionState.MOTION_SWIM_MOVE) { + consumption = new Consumption(ConsumptionType.SWIMMING); + } + if (currentState == MotionState.MOTION_SWIM_DASH) { + consumption = new Consumption(ConsumptionType.SWIM_DASH); + } + return consumption; + } + + private Consumption getRunWalkDashSustainedConsumption() { + Consumption consumption = new Consumption(ConsumptionType.None); + if (currentState == MotionState.MOTION_DASH) { + consumption = new Consumption(ConsumptionType.DASH); + // TODO: Foods + } + if (currentState == MotionState.MOTION_RUN) { + consumption = new Consumption(ConsumptionType.RUN); + } + if (currentState == MotionState.MOTION_WALK) { + consumption = new Consumption(ConsumptionType.WALK); + } + return consumption; + } + + private Consumption getFlySustainedConsumption() { + // POWERED_FLY, e.g. wind tunnel + if (currentState == MotionState.MOTION_POWERED_FLY) { + return new Consumption(ConsumptionType.POWERED_FLY); + } + Consumption consumption = new Consumption(ConsumptionType.FLY); + // Talent + HashMap glidingCostReduction = new HashMap<>() {{ + put(212301, 0.8f); // Amber + put(222301, 0.8f); // Venti + }}; + float reduction = 1; + for (EntityAvatar entity : cachedSession.getPlayer().getTeamManager().getActiveTeam()) { + for (int skillId : entity.getAvatar().getProudSkillList()) { + if (glidingCostReduction.containsKey(skillId)) { + float potentialLowerReduction = glidingCostReduction.get(skillId); + if (potentialLowerReduction < reduction) { + reduction = potentialLowerReduction; + } + } + } + } + consumption.amount *= reduction; + // TODO: Foods + return consumption; + } + + private Consumption getStandSustainedConsumption() { + Consumption consumption = new Consumption(ConsumptionType.None); + if (currentState == MotionState.MOTION_STANDBY) { + consumption = new Consumption(ConsumptionType.STANDBY); + } + if (currentState == MotionState.MOTION_STANDBY_MOVE) { + consumption = new Consumption(ConsumptionType.STANDBY_MOVE); + } + return consumption; + } +} diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index dba728453..71ae9d8c6 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -14,6 +14,7 @@ import emu.grasscutter.game.avatar.AvatarStorage; import emu.grasscutter.game.entity.EntityGadget; import emu.grasscutter.game.entity.EntityItem; import emu.grasscutter.game.entity.GameEntity; +import emu.grasscutter.game.expedition.ExpeditionInfo; import emu.grasscutter.game.friends.FriendsList; import emu.grasscutter.game.friends.PlayerProfile; import emu.grasscutter.game.gacha.PlayerGachaInfo; @@ -21,8 +22,8 @@ import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.inventory.Inventory; import emu.grasscutter.game.mail.Mail; import emu.grasscutter.game.mail.MailHandler; -import emu.grasscutter.game.managers.MovementManager.MovementManager; -import emu.grasscutter.game.managers.SotSManager.SotSManager; +import emu.grasscutter.game.managers.StaminaManager.StaminaManager; +import emu.grasscutter.game.managers.SotSManager; import emu.grasscutter.game.props.ActionReason; import emu.grasscutter.game.props.EntityType; import emu.grasscutter.game.props.PlayerProperty; @@ -51,6 +52,7 @@ import emu.grasscutter.server.packet.send.*; import emu.grasscutter.utils.DateHelper; import emu.grasscutter.utils.Position; import emu.grasscutter.utils.MessageHandler; +import emu.grasscutter.utils.Utils; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; @@ -60,9 +62,6 @@ import java.util.concurrent.LinkedBlockingQueue; @Entity(value = "players", useDiscriminator = false) public class Player { - @Transient private static int GlobalMaximumSpringVolume = 8500000; - @Transient private static int GlobalMaximumStamina = 24000; - @Id private int id; @Indexed(options = @IndexOptions(unique = true)) private String accountId; @@ -102,6 +101,7 @@ public class Player { private ArrayList shownAvatars; private Set rewardedLevels; private ArrayList shopLimit; + private Map expeditionInfo; private int sceneId; private int regionId; @@ -129,7 +129,7 @@ public class Player { @Transient private final InvokeHandler clientAbilityInitFinishHandler; private MapMarksManager mapMarksManager; - @Transient private MovementManager movementManager; + @Transient private StaminaManager staminaManager; private long springLastUsed; @@ -141,6 +141,7 @@ public class Player { this.avatars = new AvatarStorage(this); this.friendsList = new FriendsList(this); this.mailHandler = new MailHandler(this); + this.towerManager = new TowerManager(this); this.pos = new Position(); this.rotation = new Position(); this.properties = new HashMap<>(); @@ -171,9 +172,10 @@ public class Player { this.moonCardGetTimes = new HashSet<>(); this.shopLimit = new ArrayList<>(); + this.expeditionInfo = new HashMap<>(); this.messageHandler = null; this.mapMarksManager = new MapMarksManager(); - this.movementManager = new MovementManager(this); + this.staminaManager = new StaminaManager(this); this.sotsManager = new SotSManager(this); } @@ -186,7 +188,6 @@ public class Player { this.nickname = "Traveler"; this.signature = ""; this.teamManager = new TeamManager(this); - this.towerManager = new TowerManager(this); this.birthday = new PlayerBirthday(); this.setProperty(PlayerProperty.PROP_PLAYER_LEVEL, 1); this.setProperty(PlayerProperty.PROP_IS_SPRING_AUTO_USE, 1); @@ -202,7 +203,7 @@ public class Player { this.getRotation().set(0, 307, 0); this.messageHandler = null; this.mapMarksManager = new MapMarksManager(); - this.movementManager = new MovementManager(this); + this.staminaManager = new StaminaManager(this); this.sotsManager = new SotSManager(this); } @@ -674,6 +675,28 @@ public class Player { session.send(new PacketCardProductRewardNotify(getMoonCardRemainDays())); } + public Map getExpeditionInfo() { + return expeditionInfo; + } + + public void addExpeditionInfo(long avaterGuid, int expId, int hourTime, int startTime){ + ExpeditionInfo exp = new ExpeditionInfo(); + exp.setExpId(expId); + exp.setHourTime(hourTime); + exp.setState(1); + exp.setStartTime(startTime); + expeditionInfo.put(avaterGuid, exp); + } + + public void removeExpeditionInfo(long avaterGuid){ + expeditionInfo.remove(avaterGuid); + } + + public ExpeditionInfo getExpeditionInfo(long avaterGuid){ + return expeditionInfo.get(avaterGuid); + } + + public List getShopLimit() { return shopLimit; } @@ -849,11 +872,11 @@ public class Player { } public void onPause() { - + getStaminaManager().stopSustainedStaminaHandler(); } public void onUnpause() { - + getStaminaManager().startSustainedStaminaHandler(); } public void sendPacket(BasePacket packet) { @@ -998,7 +1021,7 @@ public class Player { return mapMarksManager; } - public MovementManager getMovementManager() { return movementManager; } + public StaminaManager getStaminaManager() { return staminaManager; } public SotSManager getSotSManager() { return sotsManager; } @@ -1030,6 +1053,22 @@ public class Player { this.resetSendPlayerLocTime(); } } + // Expedition + var timeNow = Utils.getCurrentSeconds(); + var needNotify = false; + for (Long key : expeditionInfo.keySet()) { + ExpeditionInfo e = expeditionInfo.get(key); + if(e.getState() == 1){ + if(timeNow - e.getStartTime() >= e.getHourTime() * 60 * 60){ + e.setState(2); + needNotify = true; + } + } + } + if(needNotify){ + this.save(); + this.sendPacket(new PacketAvatarExpeditionDataNotify(this)); + } } @@ -1057,9 +1096,6 @@ public class Player { if (this.getProfile().getUid() == 0) { this.getProfile().syncWithCharacter(this); } - if (this.getTowerManager() == null) { - this.towerManager = new TowerManager(this); - } // Check if player object exists in server // TODO - optimize @@ -1112,8 +1148,11 @@ public class Player { } public void onLogout() { + // stop stamina calculation + getStaminaManager().stopSustainedStaminaHandler(); + // force to leave the dungeon - if(getScene().getSceneType() == SceneType.SCENE_DUNGEON){ + if (getScene().getSceneType() == SceneType.SCENE_DUNGEON) { this.getServer().getDungeonManager().exitDungeon(this); } // Leave world @@ -1172,7 +1211,7 @@ public class Player { } else if (prop == PlayerProperty.PROP_LAST_CHANGE_AVATAR_TIME) { // 10001 // TODO: implement sanity check } else if (prop == PlayerProperty.PROP_MAX_SPRING_VOLUME) { // 10002 - if (!(value >= 0 && value <= GlobalMaximumSpringVolume)) { return false; } + if (!(value >= 0 && value <= getSotSManager().GlobalMaximumSpringVolume)) { return false; } } else if (prop == PlayerProperty.PROP_CUR_SPRING_VOLUME) { // 10003 int playerMaximumSpringVolume = getProperty(PlayerProperty.PROP_MAX_SPRING_VOLUME); if (!(value >= 0 && value <= playerMaximumSpringVolume)) { return false; } @@ -1189,7 +1228,7 @@ public class Player { } else if (prop == PlayerProperty.PROP_IS_TRANSFERABLE) { // 10009 if (!(0 <= value && value <= 1)) { return false; } } else if (prop == PlayerProperty.PROP_MAX_STAMINA) { // 10010 - if (!(value >= 0 && value <= GlobalMaximumStamina)) { return false; } + if (!(value >= 0 && value <= getStaminaManager().GlobalMaximumStamina)) { return false; } } else if (prop == PlayerProperty.PROP_CUR_PERSIST_STAMINA) { // 10011 int playerMaximumStamina = getProperty(PlayerProperty.PROP_MAX_STAMINA); if (!(value >= 0 && value <= playerMaximumStamina)) { return false; } @@ -1200,7 +1239,7 @@ public class Player { } else if (prop == PlayerProperty.PROP_PLAYER_EXP) { // 10014 if (!(0 <= value)) { return false; } } else if (prop == PlayerProperty.PROP_PLAYER_HCOIN) { // 10015 - // see 10015 + // see PlayerProperty.PROP_PLAYER_HCOIN comments } else if (prop == PlayerProperty.PROP_PLAYER_SCOIN) { // 10016 // See 10015 } else if (prop == PlayerProperty.PROP_PLAYER_MP_SETTING_TYPE) { // 10017 diff --git a/src/main/java/emu/grasscutter/game/player/TeamManager.java b/src/main/java/emu/grasscutter/game/player/TeamManager.java index 16e8942ad..204af2976 100644 --- a/src/main/java/emu/grasscutter/game/player/TeamManager.java +++ b/src/main/java/emu/grasscutter/game/player/TeamManager.java @@ -557,7 +557,7 @@ public class TeamManager { // return; // } // } - player.getMovementManager().resetTimer(); // prevent drowning immediately after respawn + player.getStaminaManager().stopSustainedStaminaHandler(); // prevent drowning immediately after respawn // Revive all team members for (EntityAvatar entity : getActiveTeam()) { diff --git a/src/main/java/emu/grasscutter/game/shop/ShopManager.java b/src/main/java/emu/grasscutter/game/shop/ShopManager.java index 7222db816..2c5d014f5 100644 --- a/src/main/java/emu/grasscutter/game/shop/ShopManager.java +++ b/src/main/java/emu/grasscutter/game/shop/ShopManager.java @@ -4,13 +4,7 @@ import com.google.gson.reflect.TypeToken; import emu.grasscutter.Grasscutter; import emu.grasscutter.data.GameData; import emu.grasscutter.data.common.ItemParamData; -import emu.grasscutter.data.def.ItemData; import emu.grasscutter.data.def.ShopGoodsData; -import emu.grasscutter.game.inventory.GameItem; -import emu.grasscutter.game.managers.InventoryManager; -import emu.grasscutter.game.props.ActionReason; -import emu.grasscutter.net.proto.ItemParamOuterClass; -import emu.grasscutter.net.proto.ShopGoodsOuterClass; import emu.grasscutter.server.game.GameServer; import emu.grasscutter.utils.Utils; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; @@ -54,9 +48,9 @@ public class ShopManager { public static int getShopNextRefreshTime(ShopInfo shopInfo) { return switch (shopInfo.getShopRefreshType()) { - case SHOP_REFRESH_DAILY -> Utils.GetNextTimestampOfThisHour(REFRESH_HOUR, TIME_ZONE, shopInfo.getShopRefreshParam()); - case SHOP_REFRESH_WEEKLY -> Utils.GetNextTimestampOfThisHourInNextWeek(REFRESH_HOUR, TIME_ZONE, shopInfo.getShopRefreshParam()); - case SHOP_REFRESH_MONTHLY -> Utils.GetNextTimestampOfThisHourInNextMonth(REFRESH_HOUR, TIME_ZONE, shopInfo.getShopRefreshParam()); + case SHOP_REFRESH_DAILY -> Utils.getNextTimestampOfThisHour(REFRESH_HOUR, TIME_ZONE, shopInfo.getShopRefreshParam()); + case SHOP_REFRESH_WEEKLY -> Utils.getNextTimestampOfThisHourInNextWeek(REFRESH_HOUR, TIME_ZONE, shopInfo.getShopRefreshParam()); + case SHOP_REFRESH_MONTHLY -> Utils.getNextTimestampOfThisHourInNextMonth(REFRESH_HOUR, TIME_ZONE, shopInfo.getShopRefreshParam()); default -> 0; }; } diff --git a/src/main/java/emu/grasscutter/game/tower/TowerManager.java b/src/main/java/emu/grasscutter/game/tower/TowerManager.java index 116861dd8..409549a1f 100644 --- a/src/main/java/emu/grasscutter/game/tower/TowerManager.java +++ b/src/main/java/emu/grasscutter/game/tower/TowerManager.java @@ -7,6 +7,7 @@ import emu.grasscutter.data.def.TowerLevelData; import emu.grasscutter.game.dungeons.DungeonSettleListener; import emu.grasscutter.game.dungeons.TowerDungeonSettleListener; import emu.grasscutter.game.player.Player; +import emu.grasscutter.server.packet.send.PacketCanUseSkillNotify; import emu.grasscutter.server.packet.send.PacketTowerCurLevelRecordChangeNotify; import emu.grasscutter.server.packet.send.PacketTowerEnterLevelRsp; @@ -15,9 +16,7 @@ import java.util.List; @Entity public class TowerManager { - - @Transient private final Player player; - + @Transient private Player player; public TowerManager(Player player) { this.player = player; @@ -77,7 +76,8 @@ public class TowerManager { player.getScene().setPrevScenePoint(enterPointId); player.getSession().send(new PacketTowerEnterLevelRsp(currentFloorId, currentLevel)); - + // stop using skill + player.getSession().send(new PacketCanUseSkillNotify(false)); } public void notifyCurLevelRecordChange(){ diff --git a/src/main/java/emu/grasscutter/game/world/Scene.java b/src/main/java/emu/grasscutter/game/world/Scene.java index 97099c9b9..82ce9139f 100644 --- a/src/main/java/emu/grasscutter/game/world/Scene.java +++ b/src/main/java/emu/grasscutter/game/world/Scene.java @@ -105,7 +105,13 @@ public class Scene { public GameEntity getEntityById(int id) { return this.entities.get(id); } - + + public GameEntity getEntityByConfigId(int configId) { + return this.entities.values().stream() + .filter(x -> x.getConfigId() == configId) + .findFirst() + .orElse(null); + } /** * @return the autoCloseTime */ diff --git a/src/main/java/emu/grasscutter/languages/CNLanguage.java b/src/main/java/emu/grasscutter/languages/CNLanguage.java deleted file mode 100644 index ead3b4147..000000000 --- a/src/main/java/emu/grasscutter/languages/CNLanguage.java +++ /dev/null @@ -1,289 +0,0 @@ -package emu.grasscutter.languages; - -public final class CNLanguage { - public String An_error_occurred_during_game_update = "游戏更新时发生了错误."; - public String Starting_Grasscutter = "正在开启Grasscutter..."; - public String Invalid_server_run_mode = "无效的服务器运行模式. "; - public String Server_run_mode = "服务器运行模式必须为以下几种之一: 'HYBRID'(混合模式), 'DISPATCH_ONLY'(仅dispatch模式), 或 'GAME_ONLY'(仅游戏模式). 无法启动Grasscutter..."; - public String Shutting_down = "正在停止...."; - public String Start_done = "加载完成!需要指令帮助请输入 \"help\""; - public String Dispatch_mode_not_support_command = "仅dispatch模式无法使用指令。"; - public String Command_error = "命令错误:"; - public String Error = "出现错误."; - public String Grasscutter_is_free = "Grasscutter是免费软件,如果你是花钱买到的,你大概被骗了。主页: https://github.com/Grasscutters/Grasscutter"; - public String Game_start_port = "游戏服务器已在端口 {port} 上开启。"; - public String Client_connect = "来自 {address} 的客户端已连接。"; - public String Client_disconnect = "来自 {address} 的客户端已断开。"; - public String Client_request = "[Dispatch] 客户端 {ip} 请求: {method} {url}"; - public String Not_load_keystore = "[Dispatch] 无法加载证书,正在尝试默认密码..."; - public String Use_default_keystore = "[Dispatch] 成功使用默认密码加载证书. 请考虑将config.json中的KeystorePassword项改为123456."; - public String Load_keystore_error = "[Dispatch] 加载证书时出现错误!"; - public String Not_find_ssl_cert = "[Dispatch] 未找到SSL证书,正在回滚至HTTP模式。"; - public String Welcome = "欢迎使用Grasscutter"; - public String Potential_unhandled_request = "[Dispatch] 潜在的未处理请求: {method} {url}"; - public String Client_login_token = "[Dispatch] 客户端 {ip} 正在尝试使用token登录..."; - public String Client_token_login_failed = "[Dispatch] 客户端 {ip} 使用token登录失败。"; - public String Client_login_in_token = "[Dispatch] 客户端 {ip} 使用token以 {uid} 的身份登录。"; - public String Game_account_cache_error = "游戏账户缓存出现错误。"; - public String Wrong_session_key = "会话密钥错误。"; - public String Client_exchange_combo_token = "[Dispatch] 客户端 {ip} 成功交换token。"; - public String Client_failed_exchange_combo_token = "[Dispatch] 客户端 {ip} 交换token失败。"; - public String Dispatch_start_server_port = "[Dispatch] Dispatch服务器已在端口 {port} 上开启。"; - public String Client_failed_login_account_create = "[Dispatch] 客户端 {ip} 登录失败: 已创建UID为 {uid} 的账户。"; - public String Client_failed_login_account_create_failed = "[Dispatch] 客户端 {ip} 登录失败: 创建账户失败。"; - public String Client_failed_login_account_no_found = "[Dispatch] 客户端 {ip} 登录失败: 未找到帐户。"; - public String Client_login = "[Dispatch] 客户端 {ip} 以 {uid} 的身份登录。"; - public String Username_not_found = "未找到此用户名."; - public String Username_not_found_create_failed = "未找到此用户名, 创建失败。."; - - // Command - public String No_command_specified = "未指定命令."; - public String Unknown_command = "未知命令: "; - public String You_not_permission_run_command = "你没有权限运行此命令."; - public String This_command_can_only_run_from_console = "此命令只能在控制台运行."; - public String Run_this_command_in_game = "请在游戏内运行此命令."; - public String Invalid_playerId = "无效的玩家ID."; - public String Player_not_found = "未找到此玩家."; - public String Player_is_offline = "此玩家已离线."; - public String Invalid_amount = "无效的数量."; - public String Invalid_arguments = "无效的命令参数."; - public String Invalid_artifact_id = "无效的圣遗物ID."; - public String Invalid_avatar_id = "无效的角色ID."; - public String Invalid_avatar_level = "无效的角色等级."; - public String Invalid_entity_id = "无效的物品ID."; - public String Invalid_item_id = "无效的物品ID."; - public String Invalid_item_level = "无效的物品等级."; - public String Invalid_item_refinement = "无效的精炼等级."; - public String Invalid_UID = "无效的UID."; - public String Enabled = "启用"; - public String Disabled = "禁用"; - public String No_command_found = "未找到命令."; - public String Help = "帮助"; - public String Player_not_found_or_offline = "此玩家不存在或已离线."; - public String Success = "成功"; - public String Target_cleared = "已清除选择目标"; - public String Target_set = "接下来的命令将默认以 @{uid} 为目标。输入命令时不必继续携带UID参数。"; - public String Target_needed = "此命令需要指定一个目标用户. 输入命令时携带 <@UID> 参数或使用 /target @UID 来指定一个默认目标用户."; - - // Help - public String Help_usage = " 用法: "; - public String Help_aliases = " 别名: "; - public String Help_available_command = " 可用命令:"; - - // Account - public String Modify_user_account = "修改用户帐户"; - public String Account_exists = "账户已存在."; - public String Account_create_UID = "UID为 {uid} 的账户已创建."; - public String Account_delete = "已删除账户."; - public String Account_not_find = "账户不存在."; - public String Account_command_usage = "用法: account <用户名> [uid]"; - - // Broadcast - public String Broadcast_command_usage = "用法: broadcast <消息>"; - public String Broadcast_message_sent = "消息已发送."; - - // ChangeScene - public String Change_screen_usage = "用法: changescene <场景id>"; - public String Change_screen_you_in_that_screen = "你已经在此场景中了"; - public String Change_screen = "切换到场景 "; - public String Change_screen_not_exist = "此场景不存在。"; - - // Cleart_or_playerId - public String Clear_weapons = "已清除 {name} 的武器."; - public String Clear_artifacts = "已清除 {name} 的圣遗物 ."; - public String Clear_materials = "已清除 {name} 的材料."; - public String Clear_furniture = "已清除 {name} 的摆设."; - public String Clear_displays = "已清除 {name} 的displays."; - public String Clear_virtuals = "已清除 {name} 的virtuals."; - public String Clear_everything = "已清除 {name} 的所有物品."; - - // Coop - public String Coop_usage = "用法: coop <房主的UID>"; - public String Coop_success = "已将{target}拉进{host}的世界."; - - // Drop - public String Drop_usage = "用法: drop <物品ID|物品名> [数量]"; - public String Drop_dropped_of = "已在地上丢弃 {amount} 个 {item}."; - - // EnterDungeon - public String EnterDungeon_usage = "用法: enterdungeon <副本 id>"; - public String EnterDungeon_changed_to_dungeon = "已进入副本 "; - public String EnterDungeon_dungeon_not_found = "副本不存在"; - public String EnterDungeon_you_in_that_dungeon = "你已经在此副本中了。"; - - // GiveAll - public String GiveAll_usage = "用法: giveall [数量]"; - public String GiveAll_item = "正在给予所有物品..."; - public String GiveAll_done = "完成。"; - - // GiveArtifact - public String GiveArtifact_usage = "用法: giveart|gart [玩家] <圣遗物Id> <主词条Id> [<副词条Id>[,<被强化次数>]]... [等级]"; - public String GiveArtifact_given = "已将 {itemId} 给予 {target}."; - - // GiveChar - public String GiveChar_usage = "用法: givechar <角色Id|角色名> [等级]"; - public String GiveChar_given = "将等级为 {level} 的 {avatarId} 给予 {target}."; - - // Give - public String Give_usage = "用法: give [玩家名] <物品ID|物品名> [数量] [等级] "; - public String Give_refinement_only_applicable_weapons = "精炼只对武器有效。"; - public String Give_refinement_must_between_1_and_5 = "精炼等级必须在1和5之间。"; - public String Give_given = "已将 {amount} 个 {item} 给与 {target}."; - public String Give_given_with_level_and_refinement = "已将 {amount} 个等级为 {lvl}, 精炼 {refinement} 的 {item} 给予 {target}."; - public String Give_given_level = "已将 {amount} 个等级为 {lvl} 的 {item} 给与 {target}."; - - // GodMode - public String Godmode_usage = "用法: godmode [on|off|toggle]"; - public String Godmode_status = "设置 {name} 的无敌模式为: {status} "; - - // Heal - public String Heal_message = "所有角色已被治疗。"; - - // Kick - public String Kick_player_kick_player = "玩家 [{sendUid}:{sendName}] 已踢出 [{kickUid}:{kickName}]"; - public String Kick_server_player = "正在踢出玩家 [{kickUid}:{kickName}]"; - - // Kill - public String Kill_usage = "用法: killall [玩家UID] [场景ID]"; - public String Kill_scene_not_found_in_player_world = "未在玩家世界中找到此场景"; - public String Kill_kill_monsters_in_scene = "已杀死场景 {id} 中的 {size} 只怪物。 "; - - // KillCharacter - public String KillCharacter_usage = "用法: /killcharacter [玩家Id]"; - public String KillCharacter_kill_current_character = "已干掉 {name} 当前的场上角色."; - - // List - public String List_message = "现有 {size} 名玩家在线:"; - - // Permission - public String Permission_usage = "用法: permission <权限名>"; - public String Permission_add = "权限已添加。"; - public String Permission_have_permission = "此玩家已拥有此权限!"; - public String Permission_remove = "权限已移除。"; - public String Permission_not_have_permission = "此玩家未拥有此权限!"; - - // Position - public String Position_message = "坐标: {x},{y},{z}\n场景: {id}"; - - // Reload - public String Reload_reload_start = "正在重新加载配置."; - public String Reload_reload_done = "完成."; - - // ResetConst - public String ResetConst_reset_all = "重置你所有角色的命座。"; - public String ResetConst_reset_all_done = "{name} 的命座已被重置。请重新登录。"; - - // ResetShopLimit - public String ResetShopLimit_usage = "用法: /resetshop <玩家id>"; - - // SendMail - public String SendMail_usage = "用法: give [player] [amount]"; - public String SendMail_user_not_exist = "不存在id为 '{id}' 的用户。"; - public String SendMail_start_composition = "开始编辑邮件的组成部分.\n请使用 `/sendmail <标题>` 以继续.\n你可以在任何时候使用`/sendmail stop` 来停止编辑."; - public String SendMail_templates = "很快就会有邮件模板了......."; - public String SendMail_invalid_arguments = "无效的参数.\n用法: `/sendmail <用户Id|all|help> [模板Id]``"; - public String SendMail_send_cancel = "已取消发送邮件。"; - public String SendMail_send_done = "已向 {name} 发送邮件!"; - public String SendMail_send_all_done = "已向所有玩家发送邮件!"; - public String SendMail_not_composition_end = "邮件组成部分编辑尚未结束.\n请使用 `/sendmail {args}` 或 `/sendmail stop` 来停止编辑"; - public String SendMail_Please_use = "请使用 `/sendmail {args}`"; - public String SendMail_set_title = "邮件标题已设为 '{title}'.\n使用 '/sendmail <邮件正文>' 以继续."; - public String SendMail_set_contents = "邮件的正文如下:\n '{contents}'\n使用 '/sendmail <发送者署名>' 以继续."; - public String SendMail_set_message_sender = "邮件的发送者已设为 '{send}'.\n使用 '/sendmail <物品Id|物品名|finish(结束编辑并发送)> [数量] [等级]"; - public String SendMail_send = "已将 {amount} 个 {item} (等级 {lvl}) 作为邮件附件.\n你可以继续添加附件,也可以使用 `/sendmail finish` 来停止编辑并发送邮件."; - public String SendMail_invalid_arguments_please_use = "无效的参数 \n 请使用 `/sendmail {args}`"; - public String SendMail_title = "<标题>"; - public String SendMail_message = "<正文>"; - public String SendMail_sender = "<发送者>"; - public String SendMail_arguments = "<物品Id|物品名|finish(结束编辑并发送)> [数量] [等级]"; - public String SendMail_error = "错误:无效的编写阶段 {stage}. 需要stacktrace请看服务器命令行."; - - // SendMessage - public String SendMessage_usage = "用法: sendmessage <玩家名> <消息>"; - public String SenaMessage_message_sent = "已发送."; - - // SetFetterLevel - public String SetFetterLevel_usage = "用法: setfetterlevel <等级>"; - public String SetFetterLevel_fetter_level_must_between_0_and_10 = "设置的好感等级必须位于 0 和 10 之间。"; - public String SetFetterLevel_fetter_set_level = "好感等级已设置为 {level}"; - public String SetFetterLevel_invalid_fetter_level = "无效的好感等级。"; - - // SetStats - public String SetStats_usage = "用法: setstats|stats "; - public String SetStats_setstats_help_message = "用法: /setstats|stats <数值> 基本属性(整数)"; - public String SetStats_stats_help_message = "用法: /stats <数值> 元素属性(百分比)"; - public String SetStats_set_max_hp = "最大生命值已设为 {int}."; - public String SetStats_set_max_hp_error = "无效的生命数值."; - public String SetStats_set_hp = "生命设置为 {int}."; - public String SetStats_set_hp_error = "无效的生命数值."; - public String SetStats_set_def = "防御力设置为 {int}."; - public String SetStats_set_def_error = "无效的防御力数值."; - public String SetStats_set_atk = "攻击力设置为 {int}."; - public String SetStats_set_atk_error = "无效的攻击力数值."; - public String SetStats_set_em = "元素精通设置为 {int}."; - public String SetStats_set_em_error = "无效的元素精通数值."; - public String SetStats_set_er = "元素充能设置为 {int}%."; - public String SetStats_set_er_error = "无效的元素充能数值."; - public String SetStats_set_cr = "暴击率设置为 {int}%."; - public String SetStats_set_cr_error = "无效的暴击率数值."; - public String SetStats_set_cd = "暴击伤害设置为 {int}%."; - public String SetStats_set_cd_error = "无效的暴击伤害数值."; - public String SetStats_set_pdb = "火伤设置为 {int}%."; - public String SetStats_set_pdb_error = "无效的火伤数值."; - public String SetStats_set_cdb = "冰伤设置为 {int}%."; - public String SetStats_set_cdb_error = "无效的冰伤数值."; - public String SetStats_set_hdb = "水伤设置为 {int}%."; - public String SetStats_set_hdb_error = "无效的水伤数值."; - public String SetStats_set_adb = "风伤设置为 {int}%."; - public String SetStats_set_adb_error = "无效的风伤数值."; - public String SetStats_set_gdb = "岩伤设置为 {int}%."; - public String SetStats_set_gdb_error = "无效的岩伤数值."; - public String SetStats_set_edb = "雷伤设置为 {int}%."; - public String SetStats_set_edb_error = "无效的雷伤数值."; - public String SetStats_set_physdb = "物伤设置为 {int}%."; - public String SetStats_set_physdb_error = "无效的物伤数值."; - public String SetStats_set_ddb = "草伤设置为 {int}%."; - public String SetStats_set_ddb_error = "无效的草伤数值."; - - // SetWorldLevel - public String SetWorldLevel_usage = "用法: setworldlevel "; - public String SetWorldLevel_world_level_must_between_0_and_8 = "世界等级必须在0-8之间。"; - public String SetWorldLevel_set_world_level = "世界等级已设置为 {level}."; - public String SetWorldLevel_invalid_world_level = "无效的世界等级."; - - // Spawn - public String Spawn_usage = "用法: spawn <实体ID|实体名> [数量] [等级(仅限怪物)]"; - public String Spawn_message = "已生成 {amount} 个 {id}."; - - // Stop - public String Stop_message = "正在关闭服务器..."; - - // Talent - public String Talent_usage_1 = "设置技能等级: /talent set <技能ID> <数值>"; - public String Talent_usage_2 = "另一种方式: /talent <数值>"; - public String Talent_usage_3 = "获取技能ID: /talent getid"; - public String Talent_lower_16 = "技能等级应低于16。"; - public String Talent_set_atk = "设置普通攻击等级为 {level}."; - public String Talent_set_e = "设置元素战技(e技能)等级为 {level}."; - public String Talent_set_q = "设置元素爆发(q技能)等级为 {level}."; - public String Talent_invalid_skill_id = "无效的技能ID。"; - public String Talent_set_this = "技能等级已设为 {level}."; - public String Talent_set_id = "将技能 {id} 的等级设为 {level}."; - public String Talent_invalid_talent_level = "无效的技能等级。"; - public String Talent_normal_attack_id = "普通攻击技能ID {id}."; - public String Talent_e_skill_id = "元素战技(e技能)ID {id}."; - public String Talent_q_skill_id = "元素爆发(q技能)ID {id}."; - - // TeleportAll - public String TeleportAll_message = "此命令仅在多人游戏下可用。"; - - // Teleport - public String Teleport_usage_server = "用法: /tp [场景ID]"; - public String Teleport_invalid_position = "无效的位置。"; - public String Teleport_message = "已将 {name} 传送到场景 {id} ,坐标 {x},{y},{z}"; - - // Weather - public String Weather_usage = "用法: weather <天气ID> [气候ID]"; - public String Weather_message = "已修改天气为 {weatherId} 气候为 {climateId}"; - public String Weather_invalid_id = "无效的ID。"; -} diff --git a/src/main/java/emu/grasscutter/languages/Language.java b/src/main/java/emu/grasscutter/languages/Language.java deleted file mode 100644 index 53e99d24f..000000000 --- a/src/main/java/emu/grasscutter/languages/Language.java +++ /dev/null @@ -1,300 +0,0 @@ -package emu.grasscutter.languages; - -public final class Language { - public String An_error_occurred_during_game_update = "An error occurred during game update."; - public String Starting_Grasscutter = "Starting Grasscutter..."; - public String Invalid_server_run_mode = "Invalid server run mode."; - public String Server_run_mode = "Server run mode must be 'HYBRID', 'DISPATCH_ONLY', or 'GAME_ONLY'. Unable to start Grasscutter..."; - public String Shutting_down = "Shutting down..."; - public String Start_done = "Done! For help, type \"help\""; - public String Dispatch_mode_not_support_command = "Commands are not supported in dispatch only mode."; - public String Command_error = "Command error:"; - public String Error = "An error occurred."; - public String Grasscutter_is_free = "Grasscutter is FREE software. If you have paid for this, you may have been scammed. Homepage: https://github.com/Grasscutters/Grasscutter"; - public String Game_start_port = "Game Server started on port {port}"; - public String Client_connect = "Client connected from {address}"; - public String Client_disconnect = "Client disconnected from {address}"; - public String Client_request = "[Dispatch] Client {ip} {method} request: {url}"; - public String Not_load_keystore = "[Dispatch] Unable to load keystore. Trying default keystore password..."; - public String Use_default_keystore = "[Dispatch] The default keystore password was loaded successfully. Please consider setting the password to 123456 in config.json."; - public String Load_keystore_error = "[Dispatch] Error while loading keystore!"; - public String Not_find_ssl_cert = "[Dispatch] No SSL cert found! Falling back to HTTP server."; - public String Welcome = "Welcome to Grasscutter"; - public String Potential_unhandled_request = "[Dispatch] Potential unhandled {method} request: {url}"; - public String Client_try_login = "[Dispatch] Client {ip} is trying to log in"; - public String Client_login_token = "[Dispatch] Client {ip} is trying to log in via token"; - public String Client_token_login_failed = "[Dispatch] Client {ip} failed to log in via token"; - public String Client_login_in_token = "[Dispatch] Client {ip} logged in via token as {uid}"; - public String Game_account_cache_error = "Game account cache information error"; - public String Wrong_session_key = "Wrong session key."; - public String Client_exchange_combo_token = "[Dispatch] Client {ip} succeed to exchange combo token"; - public String Client_failed_exchange_combo_token = "[Dispatch] Client {ip} failed to exchange combo token"; - public String Dispatch_start_server_port = "[Dispatch] Dispatch server started on port {port}"; - public String Client_failed_login_account_create = "[Dispatch] Client {ip} failed to log in: Account {uid} created"; - public String Client_failed_login_account_create_failed = "[Dispatch] Client {ip} failed to log in: Account create failed"; - public String Client_failed_login_account_no_found = "[Dispatch] Client {ip} failed to log in: Account no found"; - public String Client_login = "[Dispatch] Client {ip} logged in as {uid}"; - public String Username_not_found = "Username not found."; - public String Username_not_found_create_failed = "Username not found, create failed."; - public String Create_resources_folder = "Creating resources folder..."; - public String Place_copy = "Place a copy of 'BinOutput' and 'ExcelBinOutput' in the resources folder."; - - // Command - public String No_command_specified = "No command specified."; - public String Unknown_command = "Unknown command: "; - public String You_not_permission_run_command = "You do not have permission to run this command."; - public String This_command_can_only_run_from_console = "This command can only be run from the console."; - public String Run_this_command_in_game = "Run this command in-game."; - public String Invalid_amount = "Invalid amount."; - public String Invalid_arguments = "Invalid arguments."; - public String Invalid_artifact_id = "Invalid artifact ID."; - public String Invalid_avatar_id = "Invalid avatar id."; - public String Invalid_avatar_level = "Invalid avatar level."; - public String Invalid_entity_id = "Invalid entity id."; - public String Invalid_item_id = "Invalid item id."; - public String Invalid_item_level = "Invalid item level."; - public String Invalid_item_refinement = "Invalid item refinement level."; - public String Invalid_playerId = "Invalid playerId."; - public String Invalid_UID = "Invalid UID."; - public String Player_not_found = "Player not found."; - public String Player_is_offline = "Player is offline."; - public String Enabled = "enabled"; - public String Disabled = "disabled"; - public String No_command_found = "No command found."; - public String Help = "Help"; - public String Player_not_found_or_offline = "Player not found or offline."; - public String Success = "Success"; - public String Target_cleared = "Target cleared."; - public String Target_set = "Subsequent commands will target @{uid} by default."; - public String Target_needed = "This command requires a target UID. Add a <@UID> argument or set a persistent target with /target @UID."; - - // Help - public String Help_usage = " Usage: "; - public String Help_aliases = " Aliases: "; - public String Help_available_command = "Available commands:"; - - // Account - public String Modify_user_account = "Modify user accounts"; - public String Account_exists = "Account already exists."; - public String Account_create_UID = "Account created with UID {uid}."; - public String Account_delete = "Account deleted."; - public String Account_not_find = "Account not found."; - public String Account_command_usage = "Usage: account [uid]"; - - // Broadcast - public String Broadcast_command_usage = "Usage: broadcast "; - public String Broadcast_message_sent = "Message sent."; - - // ChangeScene - public String Change_screen_usage = "Usage: changescene "; - public String Change_screen_you_in_that_screen = "You are already in that scene"; - public String Change_screen = "Changed to scene "; - public String Change_screen_not_exist = "Scene does not exist"; - - // Clear - public String Clear_usage = "Usage: clear "; - public String Clear_weapons = "Cleared weapons for {name} ."; - public String Clear_artifacts = "Cleared artifacts for {name} ."; - public String Clear_materials = "Cleared materials for {name} ."; - public String Clear_furniture = "Cleared furniture for {name} ."; - public String Clear_displays = "Cleared displays for {name} ."; - public String Clear_virtuals = "Cleared virtuals for {name} ."; - public String Clear_everything = "Cleared everything for {name} ."; - - // Coop - public String Coop_usage = "Usage: coop "; - public String Coop_success = "Summoned {target} to {host}'s world."; - - // Drop - public String Drop_usage = "Usage: drop [amount]"; - public String Drop_dropped_of = "Dropped {amount} of {item}."; - - // EnterDungeon - public String EnterDungeon_usage = "Usage: enterdungeon "; - public String EnterDungeon_changed_to_dungeon = "Changed to dungeon "; - public String EnterDungeon_dungeon_not_found = "Dungeon does not exist"; - public String EnterDungeon_you_in_that_dungeon = "You are already in that dungeon"; - - // GiveAll - public String GiveAll_usage = "Usage: giveall [amount]"; - public String GiveAll_item = "Giving all items..."; - public String GiveAll_done = "Giving all items done"; - - // GiveArtifact - public String GiveArtifact_usage = "Usage: giveart|gart [player] [[,]]... [level]"; - public String GiveArtifact_given = "Given {itemId} to {target}."; - - // GiveChar - public String GiveChar_usage = "Usage: givechar [amount]"; - public String GiveChar_given = "Given {avatarId} with level {level} to {target}."; - - // Give - public String Give_usage = "Usage: give [amount] [level]"; - public String Give_refinement_only_applicable_weapons = "Refinement is only applicable to weapons."; - public String Give_refinement_must_between_1_and_5 = "Refinement must be between 1 and 5."; - public String Give_given = "Given {amount} of {item} to {target}."; - public String Give_given_with_level_and_refinement = "Given {item} with level {lvl}, refinement {refinement} {amount} times to {target}"; - public String Give_given_level = "Given {item} with level {lvl} {amount} times to {target}"; - - // GodMode - public String Godmode_usage = "Usage: godmode [on|off|toggle]"; - public String Godmode_status = "Godmode is now {status} for {name}."; - - // Heal - public String Heal_message = "All characters have been healed."; - - // Kick - public String Kick_player_kick_player = "Player [{sendUid}:{sendName}] has kicked player [{kickUid}:{kickName}]"; - public String Kick_server_player = "Kicking player [{kickUid}:{kickName}]"; - - // Kill - public String Kill_usage = "Usage: killall [playerUid] [sceneId]"; - public String Kill_scene_not_found_in_player_world = "Scene not found in player world"; - public String Kill_kill_monsters_in_scene = "Killing {size} monsters in scene {id}"; - - // KillCharacter - public String KillCharacter_usage = "Usage: /killcharacter [playerId]"; - public String KillCharacter_kill_current_character = "Killed {name} current character."; - - // List - public String List_message = "There are {size} player(s) online:"; - - // Permission - public String Permission_usage = "Usage: permission "; - public String Permission_add = "Permission added."; - public String Permission_have_permission = "They already have this permission!"; - public String Permission_remove = "Permission removed."; - public String Permission_not_have_permission = "They don't have this permission!"; - - // Position - public String Position_message = "Coord: {x}, {y}, {z}\nScene id: {id}"; - - // Reload - public String Reload_reload_start = "Reloading config."; - public String Reload_reload_done = "Reload complete."; - - // ResetConst - public String ResetConst_reset_all = "Reset all avatars' constellations."; - public String ResetConst_reset_all_done = "Constellations for {name} have been reset. Please relog to see changes."; - - // ResetShopLimit - public String ResetShopLimit_usage = "Usage: /resetshop "; - - // SendMail - public String SendMail_usage = "Usage: give [player] [amount]"; - public String SendMail_user_not_exist = "The user with an id of '{id}' does not exist"; - public String SendMail_start_composition = "Starting composition of message.\nPlease use `/sendmail ` to continue.\nYou can use `/sendmail stop` at any time"; - public String SendMail_templates = "Mail templates coming soon implemented..."; - public String SendMail_invalid_arguments = "Invalid arguments.\nUsage `/sendmail <userId|all|help> [templateId]`"; - public String SendMail_send_cancel = "Message sending cancelled"; - public String SendMail_send_done = "Message sent to user {name}!"; - public String SendMail_send_all_done = "Message sent to all users!"; - public String SendMail_not_composition_end = "Message composition not at final stage.\nPlease use `/sendmail {args}` or `/sendmail stop` to cancel"; - public String SendMail_please_use = "Please use `/sendmail {args}`"; - public String SendMail_set_title = "Message title set as '{title}'.\nUse '/sendmail <content>' to continue."; - public String SendMail_set_contents = "Message contents set as '{contents}'.\nUse '/sendmail <sender>' to continue."; - public String SendMail_set_message_sender = "Message sender set as '{send}'.\nUse '/sendmail <itemId|itemName|finish> [amount] [level]' to continue."; - public String SendMail_send = "Attached {amount} of {item} (level {lvl}) to the message.\nContinue adding more items or use `/sendmail finish` to send the message."; - public String SendMail_invalid_arguments_please_use = "Invalid arguments \n Please use `/sendmail {args}`"; - public String SendMail_title = "<title>"; - public String SendMail_message = "<message>"; - public String SendMail_sender = "<sender>"; - public String SendMail_arguments = "<itemId|itemName|finish> [amount] [level]"; - public String SendMail_error = "ERROR: invalid construction stage {stage}. Check console for stacktrace."; - - // SendMessage - public String SendMessage_usage = "Usage: sendmessage <player> <message>"; - public String SenaMessage_message_sent = "Message sent."; - - // SetFetterLevel - public String SetFetterLevel_usage = "Usage: setfetterlevel <level>"; - public String SetFetterLevel_fetter_level_must_between_0_and_10 = "Fetter level must be between 0 and 10."; - public String SetFetterLevel_fetter_set_level = "Fetter level set to {level}"; - public String SetFetterLevel_invalid_fetter_level = "Invalid fetter level."; - - // SetStats - public String SetStats_usage_console = "Usage: setstats|stats @<UID> <stat> <value>"; - public String SetStats_usage_ingame = "Usage: setstats|stats [@UID] <stat> <value>"; - public String SetStats_help_message = """ - \n\tValues for <stat>: hp | maxhp | def | atk | em | er | crate | cdmg | cdr | heal | heali | shield | defi - \t(cont.) Elemental DMG Bonus: epyro | ecryo | ehydro | egeo | edendro | eelectro | ephys - \t(cont.) Elemental RES: respyro | rescryo | reshydro | resgeo | resdendro | reselectro | resphys - """; - public String SetStats_value_error = "Invalid stat value."; - public String SetStats_set_self = "{name} set to {value}."; - public String SetStats_set_for_uid = "{name} for {uid} set to {value}."; - public String Stats_FIGHT_PROP_MAX_HP = "Max HP"; - public String Stats_FIGHT_PROP_CUR_HP = "Current HP"; - public String Stats_FIGHT_PROP_CUR_ATTACK = "ATK"; - public String Stats_FIGHT_PROP_BASE_ATTACK = "Base ATK"; - public String Stats_FIGHT_PROP_DEFENSE = "DEF"; - public String Stats_FIGHT_PROP_ELEMENT_MASTERY = "Elemental Mastery"; - public String Stats_FIGHT_PROP_CHARGE_EFFICIENCY = "Energy Recharge"; - public String Stats_FIGHT_PROP_CRITICAL = "Crit Rate"; - public String Stats_FIGHT_PROP_CRITICAL_HURT = "Crit DMG"; - public String Stats_FIGHT_PROP_ADD_HURT = "DMG Bonus"; - public String Stats_FIGHT_PROP_WIND_ADD_HURT = "Anemo DMG Bonus"; - public String Stats_FIGHT_PROP_ICE_ADD_HURT = "Cryo DMG Bonus"; - public String Stats_FIGHT_PROP_GRASS_ADD_HURT = "Dendro DMG Bonus"; - public String Stats_FIGHT_PROP_ELEC_ADD_HURT = "Electro DMG Bonus"; - public String Stats_FIGHT_PROP_ROCK_ADD_HURT = "Geo DMG Bonus"; - public String Stats_FIGHT_PROP_WATER_ADD_HURT = "Hydro DMG Bonus"; - public String Stats_FIGHT_PROP_FIRE_ADD_HURT = "Pyro DMG Bonus"; - public String Stats_FIGHT_PROP_PHYSICAL_ADD_HURT = "Physical DMG Bonus"; - public String Stats_FIGHT_PROP_SUB_HURT = "DMG Reduction"; - public String Stats_FIGHT_PROP_WIND_SUB_HURT = "Anemo RES"; - public String Stats_FIGHT_PROP_ICE_SUB_HURT = "Cryo RES"; - public String Stats_FIGHT_PROP_GRASS_SUB_HURT = "Dendro RES"; - public String Stats_FIGHT_PROP_ELEC_SUB_HURT = "Electro RES"; - public String Stats_FIGHT_PROP_ROCK_SUB_HURT = "Geo RES"; - public String Stats_FIGHT_PROP_WATER_SUB_HURT = "Hydro RES"; - public String Stats_FIGHT_PROP_FIRE_SUB_HURT = "Pyro RES"; - public String Stats_FIGHT_PROP_PHYSICAL_SUB_HURT = "Physical RES"; - public String Stats_FIGHT_PROP_SKILL_CD_MINUS_RATIO = "Cooldown Reduction"; - public String Stats_FIGHT_PROP_HEAL_ADD = "Healing Bonus"; - public String Stats_FIGHT_PROP_HEALED_ADD = "Incoming Healing Bonus"; - public String Stats_FIGHT_PROP_SHIELD_COST_MINUS_RATIO = "Shield Strength"; - public String Stats_FIGHT_PROP_DEFENCE_IGNORE_RATIO = "DEF Ignore"; - - // SetWorldLevel - public String SetWorldLevel_usage = "Usage: setworldlevel <level>"; - public String SetWorldLevel_world_level_must_between_0_and_8 = "World level must be between 0-8"; - public String SetWorldLevel_set_world_level = "World level set to {level}."; - public String SetWorldLevel_invalid_world_level = "Invalid world level."; - - // Spawn - public String Spawn_usage = "Usage: spawn <entityId> [amount] [level(monster only)]"; - public String Spawn_message = "Spawned {amount} of {id}."; - - // Stop - public String Stop_message = "Server shutting down..."; - - // Talent - public String Talent_usage_1 = "To set talent level: /talent set <talentID> <value>"; - public String Talent_usage_2 = "Another way to set talent level: /talent <n or e or q> <value>"; - public String Talent_usage_3 = "To get talent ID: /talent getid"; - public String Talent_lower_16 = "Invalid talent level. Level should be lower than 16"; - public String Talent_set_id = "Set talent {id} to {level}."; - public String Talent_set_atk = "Set talent Normal ATK to {level}."; - public String Talent_set_e = "Set talent E to {level}."; - public String Talent_set_q = "Set talent Q to {level}."; - public String Talent_invalid_skill_id = "Invalid skill ID."; - public String Talent_set_this = "Set this talent to {level}."; - public String Talent_invalid_talent_level = "Invalid talent level."; - public String Talent_normal_attack_id = "Normal Attack ID {id}."; - public String Talent_e_skill_id = "E skill ID {id}."; - public String Talent_q_skill_id = "Q skill ID {id}."; - - // TeleportAll - public String TeleportAll_message = "You only can use this command in MP mode."; - - // Teleport - public String Teleport_usage = "Usage: /tp <x> <y> <z> [scene id]"; - public String Teleport_invalid_position = "Invalid position."; - public String Teleport_message = "Teleported {name} to {x},{y},{z} in scene {id}"; - - // Weather - public String Weather_usage = "Usage: weather <weatherId> [climateId]"; - public String Weather_message = "Changed weather to {weatherId} with climate {climateId}"; - public String Weather_invalid_id = "Invalid ID."; -} diff --git a/src/main/java/emu/grasscutter/plugin/PluginManager.java b/src/main/java/emu/grasscutter/plugin/PluginManager.java index 7a5e0aa00..bc78d12eb 100644 --- a/src/main/java/emu/grasscutter/plugin/PluginManager.java +++ b/src/main/java/emu/grasscutter/plugin/PluginManager.java @@ -4,12 +4,12 @@ import emu.grasscutter.Grasscutter; import emu.grasscutter.server.event.Event; import emu.grasscutter.server.event.EventHandler; import emu.grasscutter.server.event.HandlerPriority; -import emu.grasscutter.utils.EventConsumer; import emu.grasscutter.utils.Utils; import java.io.File; import java.io.InputStreamReader; import java.lang.reflect.Method; +import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.*; @@ -47,12 +47,23 @@ public final class PluginManager { List<File> plugins = Arrays.stream(files) .filter(file -> file.getName().endsWith(".jar")) .toList(); - + + URL[] pluginNames = new URL[plugins.size()]; + plugins.forEach(plugin -> { + try { + pluginNames[plugins.indexOf(plugin)] = plugin.toURI().toURL(); + } catch (MalformedURLException exception) { + Grasscutter.getLogger().warn("Unable to load plugin.", exception); + } + }); + + URLClassLoader classLoader = new URLClassLoader(pluginNames); + plugins.forEach(plugin -> { try { URL url = plugin.toURI().toURL(); try (URLClassLoader loader = new URLClassLoader(new URL[]{url})) { - URL configFile = loader.findResource("plugin.json"); + URL configFile = loader.findResource("plugin.json"); // Find the plugin.json file for each plugin. InputStreamReader fileReader = new InputStreamReader(configFile.openStream()); PluginConfig pluginConfig = Grasscutter.getGsonFactory().fromJson(fileReader, PluginConfig.class); @@ -68,10 +79,10 @@ public final class PluginManager { JarEntry entry = entries.nextElement(); if(entry.isDirectory() || !entry.getName().endsWith(".class") || entry.getName().contains("module-info")) continue; String className = entry.getName().replace(".class", "").replace("/", "."); - loader.loadClass(className); + classLoader.loadClass(className); // Use the same class loader for ALL plugins. } - Class<?> pluginClass = loader.loadClass(pluginConfig.mainClass); + Class<?> pluginClass = classLoader.loadClass(pluginConfig.mainClass); Plugin pluginInstance = (Plugin) pluginClass.getDeclaredConstructor().newInstance(); this.loadPlugin(pluginInstance, PluginIdentifier.fromPluginConfig(pluginConfig), loader); @@ -156,6 +167,10 @@ public final class PluginManager { .toList().forEach(handler -> this.invokeHandler(event, handler)); } + public Plugin getPlugin(String name) { + return this.plugins.get(name); + } + /** * Performs logic checks then invokes the provided event handler. * @param event The event passed through to the handler. @@ -167,4 +182,4 @@ public final class PluginManager { (event.isCanceled() && handler.ignoresCanceled()) ) handler.getCallback().consume((T) event); } -} \ No newline at end of file +} diff --git a/src/main/java/emu/grasscutter/plugin/api/PlayerHook.java b/src/main/java/emu/grasscutter/plugin/api/PlayerHook.java index 3760ea42c..6b68b7622 100644 --- a/src/main/java/emu/grasscutter/plugin/api/PlayerHook.java +++ b/src/main/java/emu/grasscutter/plugin/api/PlayerHook.java @@ -28,6 +28,7 @@ public final class PlayerHook { /** * Kicks a player from the server. + * TODO: Refactor to kick using a packet. */ public void kick() { this.player.getSession().close(); diff --git a/src/main/java/emu/grasscutter/plugin/api/README.md b/src/main/java/emu/grasscutter/plugin/api/README.md deleted file mode 100644 index 73a5a75ee..000000000 --- a/src/main/java/emu/grasscutter/plugin/api/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# Grasscutter Plugin API -**Warning!** As of now, this is a work in progress and isn't completely documented. \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java index 1784c827b..b8ba800a6 100644 --- a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java +++ b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java @@ -1,19 +1,14 @@ package emu.grasscutter.scripts; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import javax.script.Bindings; import javax.script.CompiledScript; import javax.script.ScriptException; -import org.luaj.vm2.LuaTable; import org.luaj.vm2.LuaValue; import org.luaj.vm2.lib.jse.CoerceJavaToLua; @@ -23,12 +18,8 @@ import emu.grasscutter.data.def.MonsterData; import emu.grasscutter.data.def.WorldLevelData; import emu.grasscutter.game.entity.EntityGadget; import emu.grasscutter.game.entity.EntityMonster; -import emu.grasscutter.game.entity.GameEntity; -import emu.grasscutter.game.props.EntityType; import emu.grasscutter.game.world.Scene; import emu.grasscutter.scripts.constants.EventType; -import emu.grasscutter.scripts.constants.ScriptGadgetState; -import emu.grasscutter.scripts.constants.ScriptRegionShape; import emu.grasscutter.scripts.data.SceneBlock; import emu.grasscutter.scripts.data.SceneConfig; import emu.grasscutter.scripts.data.SceneGadget; @@ -56,7 +47,12 @@ public class SceneScriptManager { private final Int2ObjectOpenHashMap<Set<SceneTrigger>> triggers; private final Int2ObjectOpenHashMap<SceneRegion> regions; - + private SceneGroup currentGroup; + private AtomicInteger monsterAlive; + private AtomicInteger monsterTideCount; + private int monsterSceneLimit; + private ConcurrentLinkedQueue<Integer> monsterOrders; + public SceneScriptManager(Scene scene) { this.scene = scene; this.scriptLib = new ScriptLib(this); @@ -166,7 +162,7 @@ public class SceneScriptManager { List<SceneBlock> blocks = ScriptLoader.getSerializer().toList(SceneBlock.class, bindings.get("block_rects")); for (int i = 0; i < blocks.size(); i++) { - SceneBlock block = blocks.get(0); + SceneBlock block = blocks.get(i); block.id = blockIds.get(i); loadBlockFromScript(block); @@ -222,7 +218,8 @@ public class SceneScriptManager { cs.eval(getBindings()); // Set - group.monsters = ScriptLoader.getSerializer().toList(SceneMonster.class, bindings.get("monsters")); + group.monsters = ScriptLoader.getSerializer().toList(SceneMonster.class, bindings.get("monsters")).stream() + .collect(Collectors.toMap(x -> x.config_id, y -> y)); group.gadgets = ScriptLoader.getSerializer().toList(SceneGadget.class, bindings.get("gadgets")); group.triggers = ScriptLoader.getSerializer().toList(SceneTrigger.class, bindings.get("triggers")); group.suites = ScriptLoader.getSerializer().toList(SceneSuite.class, bindings.get("suites")); @@ -235,7 +232,7 @@ public class SceneScriptManager { // Add monsters to suite TODO optimize Int2ObjectMap<Object> map = new Int2ObjectOpenHashMap<>(); - group.monsters.forEach(m -> map.put(m.config_id, m)); + group.monsters.entrySet().forEach(m -> map.put(m.getValue().config_id, m)); group.gadgets.forEach(m -> map.put(m.config_id, m)); for (SceneSuite suite : group.suites) { @@ -323,60 +320,92 @@ public class SceneScriptManager { } public void spawnMonstersInGroup(SceneGroup group, int suiteIndex) { - spawnMonstersInGroup(group, group.getSuiteByIndex(suiteIndex)); + this.currentGroup = group; + this.monsterSceneLimit = 0; + var suite = group.getSuiteByIndex(suiteIndex); + if(suite == null){ + return; + } + suite.sceneMonsters.forEach(mob -> spawnMonstersInGroup(group, mob)); } public void spawnMonstersInGroup(SceneGroup group) { - spawnMonstersInGroup(group, null); + this.currentGroup = group; + this.monsterSceneLimit = 0; + group.monsters.values().forEach(mob -> spawnMonstersInGroup(group, mob)); } - - public void spawnMonstersInGroup(SceneGroup group, SceneSuite suite) { - List<SceneMonster> monsters = group.monsters; - - if (suite != null) { - monsters = suite.sceneMonsters; + public void spawnMonstersInGroup(SceneGroup group,Integer[] ordersConfigId, int tideCount, int sceneLimit) { + this.currentGroup = group; + this.monsterSceneLimit = sceneLimit; + this.monsterTideCount = new AtomicInteger(tideCount); + this.monsterAlive = new AtomicInteger(0); + this.monsterOrders = new ConcurrentLinkedQueue<>(List.of(ordersConfigId)); + + // add the last turn + group.monsters.keySet().stream() + .filter(i -> !this.monsterOrders.contains(i)) + .forEach(this.monsterOrders::add); + for (int i = 0; i < sceneLimit; i++) { + spawnMonstersInGroup(group, group.monsters.get(this.monsterOrders.poll())); + } + } + public void spawnMonstersInGroup(SceneGroup group, SceneMonster monster) { + if(monster == null){ + return; + } + if(this.monsterSceneLimit > 0){ + this.monsterTideCount.decrementAndGet(); + this.monsterAlive.incrementAndGet(); } - List<GameEntity> toAdd = new ArrayList<>(); - - for (SceneMonster monster : monsters) { - MonsterData data = GameData.getMonsterDataMap().get(monster.monster_id); - - if (data == null) { - continue; - } - - // Calculate level - int level = monster.level; - - if (getScene().getDungeonData() != null) { - level = getScene().getDungeonData().getShowLevel(); - } else if (getScene().getWorld().getWorldLevel() > 0) { - WorldLevelData worldLevelData = GameData.getWorldLevelDataMap().get(getScene().getWorld().getWorldLevel()); - - if (worldLevelData != null) { - level = worldLevelData.getMonsterLevel(); - } - } - - // Spawn mob - EntityMonster entity = new EntityMonster(getScene(), data, monster.pos, level); - entity.getRotation().set(monster.rot); - entity.setGroupId(group.id); - entity.setConfigId(monster.config_id); - - toAdd.add(entity); + MonsterData data = GameData.getMonsterDataMap().get(monster.monster_id); + + if (data == null) { + return; } - - if (toAdd.size() > 0) { - getScene().addEntities(toAdd); - - for (GameEntity entity : toAdd) { - callEvent(EventType.EVENT_ANY_MONSTER_LIVE, new ScriptArgs(entity.getConfigId())); + + // Calculate level + int level = monster.level; + + if (getScene().getDungeonData() != null) { + level = getScene().getDungeonData().getShowLevel(); + } else if (getScene().getWorld().getWorldLevel() > 0) { + WorldLevelData worldLevelData = GameData.getWorldLevelDataMap().get(getScene().getWorld().getWorldLevel()); + + if (worldLevelData != null) { + level = worldLevelData.getMonsterLevel(); + } + } + + // Spawn mob + EntityMonster entity = new EntityMonster(getScene(), data, monster.pos, level); + entity.getRotation().set(monster.rot); + entity.setGroupId(group.id); + entity.setConfigId(monster.config_id); + + getScene().addEntity(entity); + + callEvent(EventType.EVENT_ANY_MONSTER_LIVE, new ScriptArgs(entity.getConfigId())); + } + + public void onMonsterDie(){ + if(this.monsterSceneLimit <= 0){ + return; + } + if(this.monsterAlive.decrementAndGet() >= this.monsterSceneLimit) { + // maybe not happen + return; + } + if(this.monsterTideCount.get() > 0){ + // add more + spawnMonstersInGroup(this.currentGroup, this.currentGroup.monsters.get(this.monsterOrders.poll())); + }else if(this.monsterAlive.get() == 0){ + // spawn the last turn of monsters + while(!this.monsterOrders.isEmpty()){ + spawnMonstersInGroup(this.currentGroup, this.currentGroup.monsters.get(this.monsterOrders.poll())); } } } - // Events public void callEvent(int eventType, ScriptArgs params) { @@ -405,4 +434,8 @@ public class SceneScriptManager { } } } + +// public LuaValue safetyCall(){ +// +// } } diff --git a/src/main/java/emu/grasscutter/scripts/ScriptLib.java b/src/main/java/emu/grasscutter/scripts/ScriptLib.java index 941b00b60..1b9badc11 100644 --- a/src/main/java/emu/grasscutter/scripts/ScriptLib.java +++ b/src/main/java/emu/grasscutter/scripts/ScriptLib.java @@ -1,28 +1,24 @@ package emu.grasscutter.scripts; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -import org.luaj.vm2.LuaTable; -import org.luaj.vm2.LuaValue; - -import emu.grasscutter.Grasscutter; -import emu.grasscutter.data.GameData; -import emu.grasscutter.data.def.MonsterData; import emu.grasscutter.game.dungeons.DungeonChallenge; import emu.grasscutter.game.entity.EntityGadget; import emu.grasscutter.game.entity.EntityMonster; import emu.grasscutter.game.entity.GameEntity; -import emu.grasscutter.scripts.constants.EventType; import emu.grasscutter.scripts.data.SceneGroup; -import emu.grasscutter.scripts.data.SceneMonster; import emu.grasscutter.scripts.data.SceneRegion; -import emu.grasscutter.scripts.data.ScriptArgs; +import emu.grasscutter.server.packet.send.PacketCanUseSkillNotify; import emu.grasscutter.server.packet.send.PacketGadgetStateNotify; import emu.grasscutter.server.packet.send.PacketWorktopOptionNotify; +import org.luaj.vm2.LuaTable; +import org.luaj.vm2.LuaValue; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Optional; public class ScriptLib { + public static final Logger logger = LoggerFactory.getLogger(ScriptLib.class); private final SceneScriptManager sceneScriptManager; public ScriptLib(SceneScriptManager sceneScriptManager) { @@ -34,6 +30,8 @@ public class ScriptLib { } public int SetGadgetStateByConfigId(int configId, int gadgetState) { + logger.debug("[LUA] Call SetGadgetStateByConfigId with {},{}", + configId,gadgetState); Optional<GameEntity> entity = getSceneScriptManager().getScene().getEntities().values().stream() .filter(e -> e.getConfigId() == configId).findFirst(); @@ -53,6 +51,8 @@ public class ScriptLib { } public int SetGroupGadgetStateByConfigId(int groupId, int configId, int gadgetState) { + logger.debug("[LUA] Call SetGroupGadgetStateByConfigId with {},{},{}", + groupId,configId,gadgetState); List<GameEntity> list = getSceneScriptManager().getScene().getEntities().values().stream() .filter(e -> e.getGroupId() == groupId).toList(); @@ -71,6 +71,8 @@ public class ScriptLib { } public int SetWorktopOptionsByGroupId(int groupId, int configId, int[] options) { + logger.debug("[LUA] Call SetWorktopOptionsByGroupId with {},{},{}", + groupId,configId,options); Optional<GameEntity> entity = getSceneScriptManager().getScene().getEntities().values().stream() .filter(e -> e.getConfigId() == configId && e.getGroupId() == groupId).findFirst(); @@ -90,6 +92,8 @@ public class ScriptLib { } public int DelWorktopOptionByGroupId(int groupId, int configId, int option) { + logger.debug("[LUA] Call DelWorktopOptionByGroupId with {},{},{}",groupId,configId,option); + Optional<GameEntity> entity = getSceneScriptManager().getScene().getEntities().values().stream() .filter(e -> e.getConfigId() == configId && e.getGroupId() == groupId).findFirst(); @@ -109,20 +113,24 @@ public class ScriptLib { } // Some fields are guessed - public int AutoMonsterTide(int challengeIndex, int groupId, int[] config_ids, int param4, int param5, int param6) { + public int AutoMonsterTide(int challengeIndex, int groupId, Integer[] ordersConfigId, int tideCount, int sceneLimit, int param6) { + logger.debug("[LUA] Call AutoMonsterTide with {},{},{},{},{},{}", + challengeIndex,groupId,ordersConfigId,tideCount,sceneLimit,param6); + SceneGroup group = getSceneScriptManager().getGroupById(groupId); if (group == null || group.monsters == null) { return 1; } - - // TODO just spawn all from group for now - this.getSceneScriptManager().spawnMonstersInGroup(group); + + this.getSceneScriptManager().spawnMonstersInGroup(group, ordersConfigId, tideCount, sceneLimit); return 0; } public int AddExtraGroupSuite(int groupId, int suite) { + logger.debug("[LUA] Call AddExtraGroupSuite with {},{}", + groupId,suite); SceneGroup group = getSceneScriptManager().getGroupById(groupId); if (group == null || group.monsters == null) { @@ -136,8 +144,17 @@ public class ScriptLib { } // param3 (probably time limit for timed dungeons) - public int ActiveChallenge(int challengeId, int challengeIndex, int param3, int groupId, int objectiveKills, int param5) { + public int ActiveChallenge(int challengeId, int challengeIndex, int timeLimitOrGroupId, int groupId, int objectiveKills, int param5) { + logger.debug("[LUA] Call ActiveChallenge with {},{},{},{},{},{}", + challengeId,challengeIndex,timeLimitOrGroupId,groupId,objectiveKills,param5); + SceneGroup group = getSceneScriptManager().getGroupById(groupId); + var objective = objectiveKills; + + if(group == null){ + group = getSceneScriptManager().getGroupById(timeLimitOrGroupId); + objective = groupId; + } if (group == null || group.monsters == null) { return 1; @@ -146,7 +163,7 @@ public class ScriptLib { DungeonChallenge challenge = new DungeonChallenge(getSceneScriptManager().getScene(), group); challenge.setChallengeId(challengeId); challenge.setChallengeIndex(challengeIndex); - challenge.setObjective(objectiveKills); + challenge.setObjective(objective); getSceneScriptManager().getScene().setChallenge(challenge); @@ -155,26 +172,37 @@ public class ScriptLib { } public int GetGroupMonsterCountByGroupId(int groupId) { + logger.debug("[LUA] Call GetGroupMonsterCountByGroupId with {}", + groupId); return (int) getSceneScriptManager().getScene().getEntities().values().stream() .filter(e -> e instanceof EntityMonster && e.getGroupId() == groupId) .count(); } public int GetGroupVariableValue(String var) { + logger.debug("[LUA] Call GetGroupVariableValue with {}", + var); return getSceneScriptManager().getVariables().getOrDefault(var, 0); } public int SetGroupVariableValue(String var, int value) { + logger.debug("[LUA] Call SetGroupVariableValue with {},{}", + var, value); getSceneScriptManager().getVariables().put(var, value); return 0; } public LuaValue ChangeGroupVariableValue(String var, int value) { + logger.debug("[LUA] Call ChangeGroupVariableValue with {},{}", + var, value); + getSceneScriptManager().getVariables().put(var, getSceneScriptManager().getVariables().get(var) + value); return LuaValue.ZERO; } public int RefreshGroup(LuaTable table) { + logger.debug("[LUA] Call RefreshGroup with {}", + table); // Kill and Respawn? int groupId = table.get("group_id").toint(); int suite = table.get("suite").toint(); @@ -192,6 +220,8 @@ public class ScriptLib { } public int GetRegionEntityCount(LuaTable table) { + logger.debug("[LUA] Call GetRegionEntityCount with {}", + table); int regionId = table.get("region_eid").toint(); int entityType = table.get("entity_type").toint(); @@ -205,21 +235,68 @@ public class ScriptLib { } public void PrintContextLog(String msg) { - Grasscutter.getLogger().info("[LUA] " + msg); + logger.info("[LUA] " + msg); } - public int TowerCountTimeStatus(int var1, int var2){ + public int TowerCountTimeStatus(int isDone, int var2){ + logger.debug("[LUA] Call TowerCountTimeStatus with {},{}", + isDone,var2); + // TODO record time return 0; } public int GetGroupMonsterCount(int var1){ - // Maybe... - return GetGroupMonsterCountByGroupId(var1); + logger.debug("[LUA] Call GetGroupMonsterCount with {}", + var1); + + return (int) getSceneScriptManager().getScene().getEntities().values().stream() + .filter(e -> e instanceof EntityMonster) + .count(); } public int SetMonsterBattleByGroup(int var1, int var2, int var3){ + logger.debug("[LUA] Call SetMonsterBattleByGroup with {},{},{}", + var1,var2,var3); + return 0; } public int CauseDungeonFail(int var1){ + logger.debug("[LUA] Call CauseDungeonFail with {}", + var1); + return 0; } + // 8-1 + public int GetGroupVariableValueByGroup(int var1, String var2, int var3){ + logger.debug("[LUA] Call GetGroupVariableValueByGroup with {},{},{}", + var1,var2,var3); + + //TODO + + return getSceneScriptManager().getVariables().getOrDefault(var2, 0); + } + + public int SetIsAllowUseSkill(int canUse, int var2){ + logger.debug("[LUA] Call SetIsAllowUseSkill with {},{}", + canUse,var2); + + getSceneScriptManager().getScene().broadcastPacket(new PacketCanUseSkillNotify(canUse == 1)); + return 0; + } + + public int KillEntityByConfigId(LuaTable table){ + logger.debug("[LUA] Call KillEntityByConfigId with {}", + table); + var configId = table.get("config_id"); + if(configId == LuaValue.NIL){ + return 1; + } + + var entity = getSceneScriptManager().getScene().getEntityByConfigId(configId.toint()); + if(entity == null){ + return 1; + } + getSceneScriptManager().getScene().killEntity(entity, 0); + return 0; + } + } diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java b/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java index a13db7b68..690cd3d0d 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java @@ -1,17 +1,21 @@ package emu.grasscutter.scripts.data; -import java.util.List; - import emu.grasscutter.utils.Position; +import java.util.List; +import java.util.Map; + public class SceneGroup { public transient int block_id; // Not an actual variable in the scripts but we will keep it here for reference public int id; public int refresh_id; public Position pos; - - public List<SceneMonster> monsters; + + /** + * ConfigId - Monster + */ + public Map<Integer,SceneMonster> monsters; public List<SceneGadget> gadgets; public List<SceneTrigger> triggers; public List<SceneRegion> regions; diff --git a/src/main/java/emu/grasscutter/server/dispatch/DispatchHttpJsonHandler.java b/src/main/java/emu/grasscutter/server/dispatch/DispatchHttpJsonHandler.java index e1a07b412..54ee38988 100644 --- a/src/main/java/emu/grasscutter/server/dispatch/DispatchHttpJsonHandler.java +++ b/src/main/java/emu/grasscutter/server/dispatch/DispatchHttpJsonHandler.java @@ -2,6 +2,7 @@ package emu.grasscutter.server.dispatch; import java.io.IOException; import java.util.Arrays; +import java.util.Objects; import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter.ServerDebugMode; @@ -9,6 +10,8 @@ import express.http.HttpContextHandler; import express.http.Request; import express.http.Response; +import static emu.grasscutter.utils.Language.translate; + public final class DispatchHttpJsonHandler implements HttpContextHandler { private final String response; private final String[] missingRoutes = { // TODO: When http requests for theses routes are found please remove it from this list and update the route request type in the DispatchServer @@ -31,8 +34,8 @@ public final class DispatchHttpJsonHandler implements HttpContextHandler { @Override public void handle(Request req, Response res) throws IOException { // Checking for ALL here isn't required as when ALL is enabled enableDevLogging() gets enabled - if(Grasscutter.getConfig().DebugMode == ServerDebugMode.MISSING && Arrays.stream(missingRoutes).anyMatch(x -> x == req.baseUrl())) { - Grasscutter.getLogger().info(Grasscutter.getLanguage().Client_request.replace("{ip}", req.ip()).replace("{method}", req.method()).replace("{url}", req.baseUrl()) + (Grasscutter.getConfig().DebugMode == ServerDebugMode.MISSING ? "(MISSING)" : "")); + if(Grasscutter.getConfig().DebugMode == ServerDebugMode.MISSING && Arrays.stream(missingRoutes).anyMatch(x -> Objects.equals(x, req.baseUrl()))) { + Grasscutter.getLogger().info(translate("messages.dispatch.request", req.ip(), req.method(), req.baseUrl()) + (Grasscutter.getConfig().DebugMode == ServerDebugMode.MISSING ? "(MISSING)" : "")); } res.send(response); } diff --git a/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java b/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java index 85f02a36d..7e4b1655e 100644 --- a/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java +++ b/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java @@ -35,6 +35,8 @@ import java.io.*; import java.net.URLDecoder; import java.util.*; +import static emu.grasscutter.utils.Language.translate; + public final class DispatchServer { public static String query_region_list = ""; public static String query_cur_region = ""; @@ -213,21 +215,21 @@ public final class DispatchServer { sslContextFactory.setKeyStorePassword(Grasscutter.getConfig().getDispatchOptions().KeystorePassword); } catch (Exception e) { e.printStackTrace(); - Grasscutter.getLogger().warn(Grasscutter.getLanguage().Not_load_keystore); + Grasscutter.getLogger().warn(translate("messages.dispatch.keystore.password_error")); try { sslContextFactory.setKeyStorePath(keystoreFile.getPath()); sslContextFactory.setKeyStorePassword("123456"); - Grasscutter.getLogger().warn(Grasscutter.getLanguage().Use_default_keystore); + Grasscutter.getLogger().warn(translate("messages.dispatch.keystore.default_password")); } catch (Exception e2) { - Grasscutter.getLogger().warn(Grasscutter.getLanguage().Load_keystore_error); + Grasscutter.getLogger().warn(translate("messages.dispatch.keystore.general_error")); e2.printStackTrace(); } } serverConnector = new ServerConnector(server, sslContextFactory); } else { - Grasscutter.getLogger().warn(Grasscutter.getLanguage().Not_find_ssl_cert); + Grasscutter.getLogger().warn(translate("messages.dispatch.keystore.no_keystore_error")); Grasscutter.getConfig().getDispatchOptions().UseSSL = false; serverConnector = new ServerConnector(server); @@ -245,13 +247,16 @@ public final class DispatchServer { if(Grasscutter.getConfig().DebugMode == ServerDebugMode.ALL) { config.enableDevLogging(); } + if (Grasscutter.getConfig().getDispatchOptions().CORS){ + if (Grasscutter.getConfig().getDispatchOptions().CORSAllowedOrigins.length > 0) config.enableCorsForOrigin(Grasscutter.getConfig().getDispatchOptions().CORSAllowedOrigins); + else config.enableCorsForAllOrigins(); + } }); - - httpServer.get("/", (req, res) -> res.send(Grasscutter.getLanguage().Welcome)); + httpServer.get("/", (req, res) -> res.send(translate("messages.status.welcome"))); httpServer.raw().error(404, ctx -> { if(Grasscutter.getConfig().DebugMode == ServerDebugMode.MISSING) { - Grasscutter.getLogger().info(Grasscutter.getLanguage().Potential_unhandled_request.replace("{method}", ctx.method()).replace("{url}", ctx.url())); + Grasscutter.getLogger().info(translate("messages.dispatch.unhandled_request_error", ctx.method(), ctx.url())); } ctx.contentType("text/html"); ctx.result("<!doctype html><html lang=\"en\"><body><img src=\"https://http.cat/404\" /></body></html>"); // I'm like 70% sure this won't break anything. @@ -309,7 +314,7 @@ public final class DispatchServer { if (requestData == null) { return; } - Grasscutter.getLogger().info(Grasscutter.getLanguage().Client_try_login.replace("{ip}", req.ip())); + Grasscutter.getLogger().info(translate("messages.dispatch.account.login_attempt", req.ip())); res.send(this.getAuthHandler().handleGameLogin(req, requestData)); }); @@ -329,7 +334,7 @@ public final class DispatchServer { return; } LoginResultJson responseData = new LoginResultJson(); - Grasscutter.getLogger().info(Grasscutter.getLanguage().Client_login_token.replace("{ip}", req.ip())); + Grasscutter.getLogger().info(translate("messages.dispatch.account.login_token_attempt", req.ip())); // Login Account account = DatabaseHelper.getAccountById(requestData.uid); @@ -337,16 +342,16 @@ public final class DispatchServer { // Test if (account == null || !account.getSessionKey().equals(requestData.token)) { responseData.retcode = -111; - responseData.message = Grasscutter.getLanguage().Game_account_cache_error; + responseData.message = translate("messages.dispatch.account.account_cache_error"); - Grasscutter.getLogger().info(Grasscutter.getLanguage().Client_token_login_failed.replace("{ip}", req.ip())); + Grasscutter.getLogger().info(translate("messages.dispatch.account.login_token_error", req.ip())); } else { responseData.message = "OK"; responseData.data.account.uid = requestData.uid; responseData.data.account.token = requestData.token; responseData.data.account.email = account.getEmail(); - Grasscutter.getLogger().info(Grasscutter.getLanguage().Client_login_in_token.replace("{ip}", req.ip()).replace("{uid}", responseData.data.account.uid)); + Grasscutter.getLogger().info(translate("messages.dispatch.account.login_token_success", req.ip(), requestData.uid)); } res.send(responseData); @@ -376,16 +381,16 @@ public final class DispatchServer { // Test if (account == null || !account.getSessionKey().equals(loginData.token)) { responseData.retcode = -201; - responseData.message = Grasscutter.getLanguage().Wrong_session_key; + responseData.message = translate("messages.dispatch.account.session_key_error"); - Grasscutter.getLogger().info(Grasscutter.getLanguage().Client_failed_exchange_combo_token.replace("{ip}", req.ip())); + Grasscutter.getLogger().info(translate("messages.dispatch.account.combo_token_error", req.ip())); } else { responseData.message = "OK"; responseData.data.open_id = loginData.uid; responseData.data.combo_id = "157795300"; responseData.data.combo_token = account.generateLoginToken(); - Grasscutter.getLogger().info(Grasscutter.getLanguage().Client_exchange_combo_token.replace("{ip}", req.ip())); + Grasscutter.getLogger().info(translate("messages.dispatch.account.combo_token_success", req.ip())); } res.send(responseData); @@ -458,7 +463,7 @@ public final class DispatchServer { httpServer.raw().config.precompressStaticFiles = false; // If this isn't set to false, files such as images may appear corrupted when serving static files httpServer.listen(Grasscutter.getConfig().getDispatchOptions().Port); - Grasscutter.getLogger().info(Grasscutter.getLanguage().Dispatch_start_server_port.replace("{port}", Integer.toString(httpServer.raw().port()))); + Grasscutter.getLogger().info(translate("messages.dispatch.port_bind", Integer.toString(httpServer.raw().port()))); } private Map<String, String> parseQueryString(String qs) { diff --git a/src/main/java/emu/grasscutter/server/dispatch/authentication/DefaultAuthenticationHandler.java b/src/main/java/emu/grasscutter/server/dispatch/authentication/DefaultAuthenticationHandler.java index 73c696502..e5a4ca055 100644 --- a/src/main/java/emu/grasscutter/server/dispatch/authentication/DefaultAuthenticationHandler.java +++ b/src/main/java/emu/grasscutter/server/dispatch/authentication/DefaultAuthenticationHandler.java @@ -8,6 +8,8 @@ import emu.grasscutter.server.dispatch.json.LoginResultJson; import express.http.Request; import express.http.Response; +import static emu.grasscutter.utils.Language.translate; + public class DefaultAuthenticationHandler implements AuthenticationHandler { @Override @@ -34,11 +36,9 @@ public class DefaultAuthenticationHandler implements AuthenticationHandler { // Check if account exists, else create a new one. if (account == null) { - // Account doesnt exist, so we can either auto create it if the config value is - // set + // Account doesn't exist, so we can either auto create it if the config value is set. if (Grasscutter.getConfig().getDispatchOptions().AutomaticallyCreateAccounts) { - // This account has been created AUTOMATICALLY. There will be no permissions - // added. + // This account has been created AUTOMATICALLY. There will be no permissions added. account = DatabaseHelper.createAccountWithId(requestData.account, 0); for (String permission : Grasscutter.getConfig().getDispatchOptions().defaultPermissions) { @@ -51,19 +51,18 @@ public class DefaultAuthenticationHandler implements AuthenticationHandler { responseData.data.account.token = account.generateSessionKey(); responseData.data.account.email = account.getEmail(); - Grasscutter.getLogger().info(Grasscutter.getLanguage().Client_failed_login_account_create.replace("{ip}", req.ip()).replace("{uid}", responseData.data.account.uid)); + Grasscutter.getLogger().info(translate("messages.dispatch.account.account_login_create_success", req.ip(), responseData.data.account.uid)); } else { responseData.retcode = -201; - responseData.message = Grasscutter.getLanguage().Username_not_found_create_failed; + responseData.message = translate("messages.dispatch.account.username_create_error"); - Grasscutter.getLogger().info(Grasscutter.getLanguage().Client_failed_login_account_no_found.replace("{ip}", req.ip())); + Grasscutter.getLogger().info(translate("messages.dispatch.account.account_login_create_error", req.ip())); } } else { responseData.retcode = -201; - responseData.message = Grasscutter.getLanguage().Username_not_found; + responseData.message = translate("messages.dispatch.account.username_error"); - Grasscutter.getLogger().info(String - .format(Grasscutter.getLanguage().Client_failed_login_account_no_found, req.ip())); + Grasscutter.getLogger().info(translate("messages.dispatch.account.account_login_exist_error", req.ip())); } } else { // Account was found, log the player in @@ -72,7 +71,7 @@ public class DefaultAuthenticationHandler implements AuthenticationHandler { responseData.data.account.token = account.generateSessionKey(); responseData.data.account.email = account.getEmail(); - Grasscutter.getLogger().info(Grasscutter.getLanguage().Client_login.replace("{ip}", req.ip()).replace("{uid}", responseData.data.account.uid)); + Grasscutter.getLogger().info(translate("messages.dispatch.account.login_success", req.ip(), responseData.data.account.uid)); } return responseData; diff --git a/src/main/java/emu/grasscutter/server/game/GameServer.java b/src/main/java/emu/grasscutter/server/game/GameServer.java index db4a8c32b..7ce8488ef 100644 --- a/src/main/java/emu/grasscutter/server/game/GameServer.java +++ b/src/main/java/emu/grasscutter/server/game/GameServer.java @@ -8,6 +8,7 @@ import emu.grasscutter.game.Account; import emu.grasscutter.game.combine.CombineManger; import emu.grasscutter.game.drop.DropManager; import emu.grasscutter.game.dungeons.DungeonManager; +import emu.grasscutter.game.expedition.ExpeditionManager; import emu.grasscutter.game.gacha.GachaManager; import emu.grasscutter.game.managers.ChatManager; import emu.grasscutter.game.managers.InventoryManager; @@ -28,6 +29,11 @@ import java.net.InetSocketAddress; import java.time.OffsetDateTime; import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import static emu.grasscutter.utils.Language.translate; public final class GameServer extends KcpServer { private final InetSocketAddress address; @@ -42,12 +48,20 @@ public final class GameServer extends KcpServer { private final ShopManager shopManager; private final MultiplayerManager multiplayerManager; private final DungeonManager dungeonManager; + private final ExpeditionManager expeditionManager; private final CommandMap commandMap; private final TaskMap taskMap; private final DropManager dropManager; private final CombineManger combineManger; + public GameServer() { + this(new InetSocketAddress( + Grasscutter.getConfig().getGameServerOptions().Ip, + Grasscutter.getConfig().getGameServerOptions().Port + )); + } + public GameServer(InetSocketAddress address) { super(address); @@ -66,20 +80,8 @@ public final class GameServer extends KcpServer { this.commandMap = new CommandMap(true); this.taskMap = new TaskMap(true); this.dropManager = new DropManager(this); + this.expeditionManager = new ExpeditionManager(this); this.combineManger = new CombineManger(this); - - // Schedule game loop. - Timer gameLoop = new Timer(); - gameLoop.scheduleAtFixedRate(new TimerTask() { - @Override - public void run() { - try { - onTick(); - } catch (Exception e) { - Grasscutter.getLogger().error(Grasscutter.getLanguage().An_error_occurred_during_game_update, e); - } - } - }, new Date(), 1000L); // Hook into shutdown event. Runtime.getRuntime().addShutdownHook(new Thread(this::onServerShutdown)); @@ -124,7 +126,11 @@ public final class GameServer extends KcpServer { public DungeonManager getDungeonManager() { return dungeonManager; } - + + public ExpeditionManager getExpeditionManager() { + return expeditionManager; + } + public CommandMap getCommandMap() { return this.commandMap; } @@ -132,6 +138,7 @@ public final class GameServer extends KcpServer { public CombineManger getCombineManger(){ return this.combineManger; } + public TaskMap getTaskMap() { return this.taskMap; } @@ -212,10 +219,28 @@ public final class GameServer extends KcpServer { } + @Override + public synchronized void start() { + // Schedule game loop. + Timer gameLoop = new Timer(); + gameLoop.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + try { + onTick(); + } catch (Exception e) { + Grasscutter.getLogger().error(translate("messages.game.game_update_error"), e); + } + } + }, new Date(), 1000L); + + super.start(); + } + @Override public void onStartFinish() { - Grasscutter.getLogger().info(Grasscutter.getLanguage().Grasscutter_is_free); - Grasscutter.getLogger().info(Grasscutter.getLanguage().Game_start_port.replace("{port}", Integer.toString(address.getPort()))); + Grasscutter.getLogger().info(translate("messages.status.free_software")); + Grasscutter.getLogger().info(translate("messages.game.port_bind", Integer.toString(address.getPort()))); ServerStartEvent event = new ServerStartEvent(ServerEvent.Type.GAME, OffsetDateTime.now()); event.call(); } diff --git a/src/main/java/emu/grasscutter/server/game/GameServerPacketHandler.java b/src/main/java/emu/grasscutter/server/game/GameServerPacketHandler.java index 35303b639..88e7fa17f 100644 --- a/src/main/java/emu/grasscutter/server/game/GameServerPacketHandler.java +++ b/src/main/java/emu/grasscutter/server/game/GameServerPacketHandler.java @@ -14,6 +14,7 @@ import emu.grasscutter.server.game.GameSession.SessionState; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +@SuppressWarnings("unchecked") public class GameServerPacketHandler { private final Int2ObjectMap<PacketHandler> handlers; diff --git a/src/main/java/emu/grasscutter/server/game/GameSession.java b/src/main/java/emu/grasscutter/server/game/GameSession.java index b984baa0e..d1d7eef01 100644 --- a/src/main/java/emu/grasscutter/server/game/GameSession.java +++ b/src/main/java/emu/grasscutter/server/game/GameSession.java @@ -22,6 +22,8 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; +import static emu.grasscutter.utils.Language.translate; + public class GameSession extends KcpChannel { private GameServer server; @@ -113,21 +115,21 @@ public class GameSession extends KcpChannel { @Override protected void onConnect() { - Grasscutter.getLogger().info(Grasscutter.getLanguage().Client_connect.replace("{address}", getAddress().getHostString().toLowerCase())); + Grasscutter.getLogger().info(translate("messages.game.connect", this.getAddress().getHostString().toLowerCase())); } @Override - protected synchronized void onDisconnect() { // Synchronize so we dont add character at the same time - Grasscutter.getLogger().info(Grasscutter.getLanguage().Client_disconnect.replace("{address}", getAddress().getHostString().toLowerCase())); + protected synchronized void onDisconnect() { // Synchronize so we don't add character at the same time. + Grasscutter.getLogger().info(translate("messages.game.disconnect", this.getAddress().getHostString().toLowerCase())); // Set state so no more packets can be handled this.setState(SessionState.INACTIVE); // Save after disconnecting if (this.isLoggedIn()) { - // Save + // Call logout event. getPlayer().onLogout(); - // Remove from gameserver + // Remove from server. getServer().getPlayers().remove(getPlayer().getUid()); } } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerAvatarExpeditionAllDataReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerAvatarExpeditionAllDataReq.java new file mode 100644 index 000000000..f1a30ab0d --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerAvatarExpeditionAllDataReq.java @@ -0,0 +1,15 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketAvatarExpeditionAllDataRsp; + +@Opcodes(PacketOpcodes.AvatarExpeditionAllDataReq) +public class HandlerAvatarExpeditionAllDataReq extends PacketHandler { + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + session.send(new PacketAvatarExpeditionAllDataRsp(session.getPlayer())); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerAvatarExpeditionCallBackReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerAvatarExpeditionCallBackReq.java new file mode 100644 index 000000000..0ac05ed0b --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerAvatarExpeditionCallBackReq.java @@ -0,0 +1,25 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AvatarExpeditionCallBackReqOuterClass.AvatarExpeditionCallBackReq; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketAvatarExpeditionCallBackRsp; +import emu.grasscutter.server.packet.send.PacketAvatarExpeditionStartRsp; +import emu.grasscutter.utils.Utils; + +@Opcodes(PacketOpcodes.AvatarExpeditionCallBackReq) +public class HandlerAvatarExpeditionCallBackReq extends PacketHandler { + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + AvatarExpeditionCallBackReq req = AvatarExpeditionCallBackReq.parseFrom(payload); + + for (int i = 0; i < req.getAvatarGuidCount(); i++) { + session.getPlayer().removeExpeditionInfo(req.getAvatarGuid(i)); + } + + session.getPlayer().save(); + session.send(new PacketAvatarExpeditionCallBackRsp(session.getPlayer())); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerAvatarExpeditionGetRewardReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerAvatarExpeditionGetRewardReq.java new file mode 100644 index 000000000..3e3bed9f1 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerAvatarExpeditionGetRewardReq.java @@ -0,0 +1,61 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.game.drop.DropData; +import emu.grasscutter.game.expedition.ExpeditionInfo; +import emu.grasscutter.game.expedition.ExpeditionRewardData; +import emu.grasscutter.game.expedition.ExpeditionRewardDataList; +import emu.grasscutter.game.inventory.GameItem; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.props.ActionReason; +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AvatarExpeditionGetRewardReqOuterClass.AvatarExpeditionGetRewardReq; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketAvatarExpeditionCallBackRsp; +import emu.grasscutter.server.packet.send.PacketAvatarExpeditionGetRewardRsp; +import emu.grasscutter.server.packet.send.PacketGadgetInteractRsp; +import emu.grasscutter.server.packet.send.PacketItemAddHintNotify; +import emu.grasscutter.utils.Utils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +@Opcodes(PacketOpcodes.AvatarExpeditionGetRewardReq) +public class HandlerAvatarExpeditionGetRewardReq extends PacketHandler { + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + AvatarExpeditionGetRewardReq req = AvatarExpeditionGetRewardReq.parseFrom(payload); + + ExpeditionInfo expInfo = session.getPlayer().getExpeditionInfo(req.getAvatarGuid()); + + List<GameItem> items = new LinkedList<>(); + + if (session.getServer().getExpeditionManager().getExpeditionRewardDataList().containsKey(expInfo.getExpId())) { + for (ExpeditionRewardDataList RewardDataList : session.getServer().getExpeditionManager().getExpeditionRewardDataList().get(expInfo.getExpId())) { + if(RewardDataList.getHourTime() == expInfo.getHourTime()){ + if(!RewardDataList.getExpeditionRewardData().isEmpty()){ + for (ExpeditionRewardData RewardData :RewardDataList.getExpeditionRewardData()) { + int num = RewardData.getMinCount(); + if(RewardData.getMinCount() != RewardData.getMaxCount()){ + num = Utils.randomRange(RewardData.getMinCount(), RewardData.getMaxCount()); + } + items.add(new GameItem(RewardData.getItemId(), num)); + } + } + } + } + } + + session.getPlayer().getInventory().addItems(items); + session.getPlayer().sendPacket(new PacketItemAddHintNotify(items, ActionReason.ExpeditionReward)); + + session.getPlayer().removeExpeditionInfo(req.getAvatarGuid()); + session.getPlayer().save(); + session.send(new PacketAvatarExpeditionGetRewardRsp(session.getPlayer(), items)); + } +} + diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerAvatarExpeditionStartReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerAvatarExpeditionStartReq.java new file mode 100644 index 000000000..74395e30c --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerAvatarExpeditionStartReq.java @@ -0,0 +1,23 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AvatarExpeditionStartReqOuterClass.AvatarExpeditionStartReq; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketAvatarExpeditionStartRsp; +import emu.grasscutter.utils.Utils; + +@Opcodes(PacketOpcodes.AvatarExpeditionStartReq) +public class HandlerAvatarExpeditionStartReq extends PacketHandler { + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + AvatarExpeditionStartReq req = AvatarExpeditionStartReq.parseFrom(payload); + + int startTime = Utils.getCurrentSeconds(); + session.getPlayer().addExpeditionInfo(req.getAvatarGuid(), req.getExpId(), req.getHourTime(), startTime); + session.getPlayer().save(); + session.send(new PacketAvatarExpeditionStartRsp(session.getPlayer())); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java index 27e4ca6ff..36252f828 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java @@ -1,6 +1,8 @@ package emu.grasscutter.server.packet.recv; +import emu.grasscutter.Grasscutter; import emu.grasscutter.game.entity.GameEntity; +import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.CombatInvocationsNotifyOuterClass.CombatInvocationsNotify; @@ -8,11 +10,19 @@ import emu.grasscutter.net.proto.CombatInvokeEntryOuterClass.CombatInvokeEntry; import emu.grasscutter.net.proto.EntityMoveInfoOuterClass.EntityMoveInfo; import emu.grasscutter.net.proto.EvtBeingHitInfoOuterClass.EvtBeingHitInfo; import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo; +import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState; +import emu.grasscutter.net.proto.PlayerDieTypeOuterClass; import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; @Opcodes(PacketOpcodes.CombatInvocationsNotify) public class HandlerCombatInvocationsNotify extends PacketHandler { + private float cachedLandingSpeed = 0; + private long cachedLandingTimeMillisecond = 0; + private boolean monitorLandingEvent = false; + @Override public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { CombatInvocationsNotify notif = CombatInvocationsNotify.parseFrom(payload); @@ -28,7 +38,34 @@ public class HandlerCombatInvocationsNotify extends PacketHandler { EntityMoveInfo moveInfo = EntityMoveInfo.parseFrom(entry.getCombatData()); GameEntity entity = session.getPlayer().getScene().getEntityById(moveInfo.getEntityId()); if (entity != null) { - session.getPlayer().getMovementManager().handle(session, moveInfo, entity); + // Move player + MotionInfo motionInfo = moveInfo.getMotionInfo(); + entity.getPosition().set(motionInfo.getPos()); + entity.getRotation().set(motionInfo.getRot()); + entity.setLastMoveSceneTimeMs(moveInfo.getSceneTime()); + entity.setLastMoveReliableSeq(moveInfo.getReliableSeq()); + MotionState motionState = motionInfo.getState(); + entity.setMotionState(motionState); + + session.getPlayer().getStaminaManager().handleCombatInvocationsNotify(session, moveInfo, entity); + + // TODO: handle MOTION_FIGHT landing which has a different damage factor + // Also, for plunge attacks, LAND_SPEED is always -30 and is not useful. + // May need the height when starting plunge attack. + + // MOTION_LAND_SPEED and MOTION_FALL_ON_GROUND arrive in different packets. + // Cache land speed for later use. + if (motionState == MotionState.MOTION_LAND_SPEED) { + cachedLandingSpeed = motionInfo.getSpeed().getY(); + cachedLandingTimeMillisecond = System.currentTimeMillis(); + monitorLandingEvent = true; + } + if (monitorLandingEvent) { + if (motionState == MotionState.MOTION_FALL_ON_GROUND) { + monitorLandingEvent = false; + handleFallOnGround(session, entity, motionState); + } + } } break; default: @@ -47,5 +84,48 @@ public class HandlerCombatInvocationsNotify extends PacketHandler { } } - + private void handleFallOnGround(GameSession session, GameEntity entity, MotionState motionState) { + // People have reported that after plunge attack (client sends a FIGHT instead of FALL_ON_GROUND) they will die + // if they talk to an NPC (this is when the client sends a FALL_ON_GROUND) without jumping again. + // A dirty patch: if not received immediately after MOTION_LAND_SPEED, discard this packet. + // 200ms seems to be a reasonable delay. + int maxDelay = 200; + long actualDelay = System.currentTimeMillis() - cachedLandingTimeMillisecond; + Grasscutter.getLogger().trace("MOTION_FALL_ON_GROUND received after " + actualDelay + "/" + maxDelay + "ms." + (actualDelay > maxDelay ? " Discard" : "")); + if (actualDelay > maxDelay) { + return; + } + float currentHP = entity.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP); + float maxHP = entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); + float damageFactor = 0; + if (cachedLandingSpeed < -23.5) { + damageFactor = 0.33f; + } + if (cachedLandingSpeed < -25) { + damageFactor = 0.5f; + } + if (cachedLandingSpeed < -26.5) { + damageFactor = 0.66f; + } + if (cachedLandingSpeed < -28) { + damageFactor = 1f; + } + float damage = maxHP * damageFactor; + float newHP = currentHP - damage; + if (newHP < 0) { + newHP = 0; + } + if (damageFactor > 0) { + Grasscutter.getLogger().debug(currentHP + "/" + maxHP + "\tLandingSpeed: " + cachedLandingSpeed + + "\tDamageFactor: " + damageFactor + "\tDamage: " + damage + "\tNewHP: " + newHP); + } else { + Grasscutter.getLogger().trace(currentHP + "/" + maxHP + "\tLandingSpeed: 0\tNo damage"); + } + entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, newHP); + entity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(entity, FightProperty.FIGHT_PROP_CUR_HP)); + if (newHP == 0) { + session.getPlayer().getStaminaManager().killAvatar(session, entity, PlayerDieTypeOuterClass.PlayerDieType.PLAYER_DIE_FALL); + } + cachedLandingSpeed = 0; + } } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterTransPointRegionNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterTransPointRegionNotify.java index 5591607fe..94c9bfd8b 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterTransPointRegionNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterTransPointRegionNotify.java @@ -1,20 +1,11 @@ package emu.grasscutter.server.packet.recv; -import emu.grasscutter.game.managers.SotSManager.SotSManager; +import emu.grasscutter.game.managers.SotSManager; import emu.grasscutter.game.player.Player; -import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.net.packet.PacketOpcodes; -import emu.grasscutter.net.proto.ChangeHpReasonOuterClass.ChangeHpReason; -import emu.grasscutter.net.proto.PropChangeReasonOuterClass.PropChangeReason; import emu.grasscutter.server.game.GameSession; -import emu.grasscutter.server.packet.send.PacketAvatarFightPropUpdateNotify; -import emu.grasscutter.server.packet.send.PacketAvatarLifeStateChangeNotify; -import emu.grasscutter.server.packet.send.PacketEntityFightPropChangeReasonNotify; -import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; - -import java.util.List; @Opcodes(PacketOpcodes.EnterTransPointRegionNotify) public class HandlerEnterTransPointRegionNotify extends PacketHandler { diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDoSkillSuccNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDoSkillSuccNotify.java index d1db944d2..705341fa0 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDoSkillSuccNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDoSkillSuccNotify.java @@ -1,6 +1,5 @@ package emu.grasscutter.server.packet.recv; -import emu.grasscutter.Grasscutter; import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.net.packet.PacketOpcodes; @@ -15,12 +14,7 @@ public class HandlerEvtDoSkillSuccNotify extends PacketHandler { EvtDoSkillSuccNotify notify = EvtDoSkillSuccNotify.parseFrom(payload); // TODO: Will be used for deducting stamina for charged skills. - int caster = notify.getCasterId(); - int skill = notify.getSkillId(); - - // Grasscutter.getLogger().warn(caster + "\t" + skill); - -// session.getPlayer().getScene().broadcastPacket(new PacketEvtAvatarStandUpNotify(notify)); + session.getPlayer().getStaminaManager().handleEvtDoSkillSuccNotify(session, notify); } } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerExitTransPointRegionNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerExitTransPointRegionNotify.java new file mode 100644 index 000000000..0d35c1762 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerExitTransPointRegionNotify.java @@ -0,0 +1,18 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.game.managers.SotSManager; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.ExitTransPointRegionNotify) +public class HandlerExitTransPointRegionNotify extends PacketHandler { + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception{ + Player player = session.getPlayer(); + SotSManager sotsManager = player.getSotSManager(); + sotsManager.cancelAutoRecover(); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarExpeditionAllDataRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarExpeditionAllDataRsp.java new file mode 100644 index 000000000..e6772f928 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarExpeditionAllDataRsp.java @@ -0,0 +1,33 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.game.expedition.ExpeditionInfo; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AvatarExpeditionAllDataRspOuterClass.AvatarExpeditionAllDataRsp; +import emu.grasscutter.net.proto.AvatarExpeditionInfoOuterClass.AvatarExpeditionInfo; + +import java.util.*; + +public class PacketAvatarExpeditionAllDataRsp extends BasePacket { + public PacketAvatarExpeditionAllDataRsp(Player player) { + super(PacketOpcodes.AvatarExpeditionAllDataRsp); + + List<Integer> openExpeditionList = new ArrayList<>(List.of(306,305,304,303,302,301,206,105,204,104,203,103,202,101,102,201,106,205)); + Map<Long, AvatarExpeditionInfo> avatarExpeditionInfoList = new HashMap<Long, AvatarExpeditionInfo>(); + + var expeditionInfo = player.getExpeditionInfo(); + for (Long key : player.getExpeditionInfo().keySet()) { + ExpeditionInfo e = expeditionInfo.get(key); + avatarExpeditionInfoList.put(key, AvatarExpeditionInfo.newBuilder().setStateValue(e.getState()).setExpId(e.getExpId()).setHourTime(e.getHourTime()).setStartTime(e.getStartTime()).build()); + }; + + AvatarExpeditionAllDataRsp.Builder proto = AvatarExpeditionAllDataRsp.newBuilder() + .addAllOpenExpeditionList(openExpeditionList) + .setExpeditionCountLimit(5) + .putAllExpeditionInfoMap(avatarExpeditionInfoList); + + this.setData(proto.build()); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarExpeditionCallBackRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarExpeditionCallBackRsp.java new file mode 100644 index 000000000..30c927bc9 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarExpeditionCallBackRsp.java @@ -0,0 +1,23 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.expedition.ExpeditionInfo; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AvatarExpeditionCallBackRspOuterClass.AvatarExpeditionCallBackRsp; +import emu.grasscutter.net.proto.AvatarExpeditionInfoOuterClass.AvatarExpeditionInfo; + +public class PacketAvatarExpeditionCallBackRsp extends BasePacket { + public PacketAvatarExpeditionCallBackRsp(Player player) { + super(PacketOpcodes.AvatarExpeditionCallBackRsp); + + AvatarExpeditionCallBackRsp.Builder proto = AvatarExpeditionCallBackRsp.newBuilder(); + var expeditionInfo = player.getExpeditionInfo(); + for (Long key : player.getExpeditionInfo().keySet()) { + ExpeditionInfo e = expeditionInfo.get(key); + proto.putExpeditionInfoMap(key, AvatarExpeditionInfo.newBuilder().setStateValue(e.getState()).setExpId(e.getExpId()).setHourTime(e.getHourTime()).setStartTime(e.getStartTime()).build()); + }; + + this.setData(proto.build()); + } +} \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarExpeditionDataNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarExpeditionDataNotify.java new file mode 100644 index 000000000..1fe6bbee0 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarExpeditionDataNotify.java @@ -0,0 +1,29 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.expedition.ExpeditionInfo; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AvatarExpeditionDataNotifyOuterClass.AvatarExpeditionDataNotify; +import emu.grasscutter.net.proto.AvatarExpeditionInfoOuterClass.AvatarExpeditionInfo; + +import java.util.*; + +public class PacketAvatarExpeditionDataNotify extends BasePacket { + public PacketAvatarExpeditionDataNotify(Player player) { + super(PacketOpcodes.AvatarExpeditionDataNotify); + + Map<Long, AvatarExpeditionInfo> avatarExpeditionInfoList = new HashMap<Long, AvatarExpeditionInfo>(); + + var expeditionInfo = player.getExpeditionInfo(); + for (Long key : player.getExpeditionInfo().keySet()) { + ExpeditionInfo e = expeditionInfo.get(key); + avatarExpeditionInfoList.put(key, AvatarExpeditionInfo.newBuilder().setStateValue(e.getState()).setExpId(e.getExpId()).setHourTime(e.getHourTime()).setStartTime(e.getStartTime()).build()); + }; + + AvatarExpeditionDataNotify.Builder proto = AvatarExpeditionDataNotify.newBuilder() + .putAllExpeditionInfoMap(avatarExpeditionInfoList); + + this.setData(proto.build()); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarExpeditionGetRewardRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarExpeditionGetRewardRsp.java new file mode 100644 index 000000000..34f0ea115 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarExpeditionGetRewardRsp.java @@ -0,0 +1,30 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.expedition.ExpeditionInfo; +import emu.grasscutter.game.inventory.GameItem; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AvatarExpeditionGetRewardRspOuterClass.AvatarExpeditionGetRewardRsp; +import emu.grasscutter.net.proto.AvatarExpeditionInfoOuterClass.AvatarExpeditionInfo; + +import java.util.Collection; + +public class PacketAvatarExpeditionGetRewardRsp extends BasePacket { + public PacketAvatarExpeditionGetRewardRsp(Player player, Collection<GameItem> items) { + super(PacketOpcodes.AvatarExpeditionGetRewardRsp); + + AvatarExpeditionGetRewardRsp.Builder proto = AvatarExpeditionGetRewardRsp.newBuilder(); + var expeditionInfo = player.getExpeditionInfo(); + for (Long key : player.getExpeditionInfo().keySet()) { + ExpeditionInfo e = expeditionInfo.get(key); + proto.putExpeditionInfoMap(key, AvatarExpeditionInfo.newBuilder().setStateValue(e.getState()).setExpId(e.getExpId()).setHourTime(e.getHourTime()).setStartTime(e.getStartTime()).build()); + }; + + for (GameItem item : items) { + proto.addItemList(item.toItemParam()); + } + + this.setData(proto.build()); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarExpeditionStartRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarExpeditionStartRsp.java new file mode 100644 index 000000000..9a279124d --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarExpeditionStartRsp.java @@ -0,0 +1,23 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.expedition.ExpeditionInfo; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AvatarExpeditionInfoOuterClass.AvatarExpeditionInfo; +import emu.grasscutter.net.proto.AvatarExpeditionStartRspOuterClass.AvatarExpeditionStartRsp; + +public class PacketAvatarExpeditionStartRsp extends BasePacket { + public PacketAvatarExpeditionStartRsp(Player player) { + super(PacketOpcodes.AvatarExpeditionStartRsp); + + AvatarExpeditionStartRsp.Builder proto = AvatarExpeditionStartRsp.newBuilder(); + var expeditionInfo = player.getExpeditionInfo(); + for (Long key : player.getExpeditionInfo().keySet()) { + ExpeditionInfo e = expeditionInfo.get(key); + proto.putExpeditionInfoMap(key, AvatarExpeditionInfo.newBuilder().setStateValue(e.getState()).setExpId(e.getExpId()).setHourTime(e.getHourTime()).setStartTime(e.getStartTime()).build()); + }; + + this.setData(proto.build()); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketCanUseSkillNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketCanUseSkillNotify.java new file mode 100644 index 000000000..f8fe1314a --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketCanUseSkillNotify.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.CanUseSkillNotifyOuterClass; + +public class PacketCanUseSkillNotify extends BasePacket { + + public PacketCanUseSkillNotify(boolean canUseSkill) { + super(PacketOpcodes.CanUseSkillNotify); + + CanUseSkillNotifyOuterClass.CanUseSkillNotify proto = CanUseSkillNotifyOuterClass.CanUseSkillNotify.newBuilder() + .setIsCanUseSkill(canUseSkill) + .build(); + + this.setData(proto); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGetPlayerFriendListRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGetPlayerFriendListRsp.java index d7a9427b8..ff373140b 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketGetPlayerFriendListRsp.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGetPlayerFriendListRsp.java @@ -1,6 +1,7 @@ package emu.grasscutter.server.packet.send; import emu.grasscutter.GameConstants; +import emu.grasscutter.Grasscutter; import emu.grasscutter.game.friends.Friendship; import emu.grasscutter.game.player.Player; import emu.grasscutter.net.packet.BasePacket; @@ -18,13 +19,13 @@ public class PacketGetPlayerFriendListRsp extends BasePacket { FriendBrief serverFriend = FriendBrief.newBuilder() .setUid(GameConstants.SERVER_CONSOLE_UID) - .setNickname(GameConstants.SERVER_AVATAR_NAME) - .setLevel(1) - .setProfilePicture(ProfilePicture.newBuilder().setAvatarId(GameConstants.SERVER_AVATAR_ID)) - .setWorldLevel(0) - .setSignature("") + .setNickname(Grasscutter.getConfig().getGameServerOptions().ServerNickname) + .setLevel(Grasscutter.getConfig().getGameServerOptions().ServerLevel) + .setProfilePicture(ProfilePicture.newBuilder().setAvatarId(Grasscutter.getConfig().getGameServerOptions().ServerAvatarId)) + .setWorldLevel(Grasscutter.getConfig().getGameServerOptions().ServerWorldLevel) + .setSignature(Grasscutter.getConfig().getGameServerOptions().ServerSignature) .setLastActiveTime((int) (System.currentTimeMillis() / 1000f)) - .setNameCardId(210001) + .setNameCardId(Grasscutter.getConfig().getGameServerOptions().ServerNameCardId) .setOnlineState(FriendOnlineState.FRIEND_ONLINE) .setParam(1) .setIsGameSource(true) diff --git a/src/main/java/emu/grasscutter/tools/Tools.java b/src/main/java/emu/grasscutter/tools/Tools.java index c4ccd2d2e..f69aafc80 100644 --- a/src/main/java/emu/grasscutter/tools/Tools.java +++ b/src/main/java/emu/grasscutter/tools/Tools.java @@ -1,26 +1,28 @@ package emu.grasscutter.tools; +import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileReader; import java.io.FileWriter; +import java.io.FilenameFilter; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.stream.Collectors; import com.google.gson.reflect.TypeToken; import emu.grasscutter.GameConstants; import emu.grasscutter.Grasscutter; +import emu.grasscutter.command.Command; +import emu.grasscutter.command.CommandHandler; +import emu.grasscutter.command.CommandMap; import emu.grasscutter.data.GameData; import emu.grasscutter.data.ResourceLoader; import emu.grasscutter.data.def.AvatarData; @@ -29,14 +31,77 @@ import emu.grasscutter.data.def.MonsterData; import emu.grasscutter.data.def.SceneData; import emu.grasscutter.utils.Utils; +import static emu.grasscutter.utils.Language.translate; + public final class Tools { - - @SuppressWarnings("deprecation") public static void createGmHandbook() throws Exception { + ToolsWithLanguageOption.createGmHandbook(getLanguageOption()); + } + + public static void createGachaMapping(String location) throws Exception { + ToolsWithLanguageOption.createGachaMapping(location, getLanguageOption()); + } + + public static List<String> getAvailableLanguage() throws Exception { + File textMapFolder = new File(Grasscutter.getConfig().RESOURCE_FOLDER + "TextMap"); + List<String> availableLangList = new ArrayList<String>(); + for (String textMapFileName : textMapFolder.list(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + if (name.startsWith("TextMap") && name.endsWith(".json")){ + return true; + } + return false; + } + })) { + availableLangList.add(textMapFileName.replace("TextMap","").replace(".json","").toLowerCase()); + } + return availableLangList; + } + + public static String getLanguageOption() throws Exception { + List<String> availableLangList = getAvailableLanguage(); + + // Use system out for better format + if (availableLangList.size() == 1) { + return availableLangList.get(0).toUpperCase(); + } + String stagedMessage = ""; + stagedMessage += "The following languages mappings are available, please select one: [default: EN]\n"; + String groupedLangList = ">\t"; + int groupedLangCount = 0; + String input = ""; + for (String availableLanguage: availableLangList){ + groupedLangCount++; + groupedLangList = groupedLangList + "" + availableLanguage + "\t"; + if (groupedLangCount == 6) { + stagedMessage += groupedLangList + "\n"; + groupedLangCount = 0; + groupedLangList = ">\t"; + } + } + if (groupedLangCount > 0) { + stagedMessage += groupedLangList + "\n"; + } + stagedMessage += "\nYour choice:[EN] "; + + input = Grasscutter.getConsole().readLine(stagedMessage); + if (availableLangList.contains(input.toLowerCase())) { + return input.toUpperCase(); + } + Grasscutter.getLogger().info("Invalid option. Will use EN(English) as fallback"); + + return "EN"; + } +} + +final class ToolsWithLanguageOption { + @SuppressWarnings("deprecation") + public static void createGmHandbook(String language) throws Exception { ResourceLoader.loadResources(); Map<Long, String> map; - try (InputStreamReader fileReader = new InputStreamReader(new FileInputStream(Utils.toFilePath(Grasscutter.getConfig().RESOURCE_FOLDER + "TextMap/TextMapEN.json")), StandardCharsets.UTF_8)) { + try (InputStreamReader fileReader = new InputStreamReader(new FileInputStream(Utils.toFilePath(Grasscutter.getConfig().RESOURCE_FOLDER + "TextMap/TextMap"+language+".json")), StandardCharsets.UTF_8)) { map = Grasscutter.getGsonFactory().fromJson(fileReader, new TypeToken<Map<Long, String>>() {}.getType()); } @@ -48,7 +113,20 @@ public final class Tools { writer.println("// Grasscutter " + GameConstants.VERSION + " GM Handbook"); writer.println("// Created " + dtf.format(now) + System.lineSeparator() + System.lineSeparator()); - + + CommandMap cmdMap = new CommandMap(true); + List<Command> cmdList = new ArrayList<>(cmdMap.getAnnotationsAsList()); + + writer.println("// Commands"); + for (Command cmd : cmdList) { + String cmdName = cmd.label(); + while (cmdName.length() <= 15) { + cmdName = " " + cmdName; + } + writer.println(cmdName + " : " + translate(cmd.description())); + } + writer.println(); + list = new ArrayList<>(GameData.getAvatarDataMap().keySet()); Collections.sort(list); @@ -96,11 +174,11 @@ public final class Tools { } @SuppressWarnings("deprecation") - public static void createGachaMapping(String location) throws Exception { + public static void createGachaMapping(String location, String language) throws Exception { ResourceLoader.loadResources(); Map<Long, String> map; - try (InputStreamReader fileReader = new InputStreamReader(new FileInputStream(Utils.toFilePath(Grasscutter.getConfig().RESOURCE_FOLDER + "TextMap/TextMapEN.json")), StandardCharsets.UTF_8)) { + try (InputStreamReader fileReader = new InputStreamReader(new FileInputStream(Utils.toFilePath(Grasscutter.getConfig().RESOURCE_FOLDER + "TextMap/TextMap"+language+".json")), StandardCharsets.UTF_8)) { map = Grasscutter.getGsonFactory().fromJson(fileReader, new TypeToken<Map<Long, String>>() {}.getType()); } @@ -113,6 +191,9 @@ public final class Tools { list = new ArrayList<>(GameData.getAvatarDataMap().keySet()); Collections.sort(list); + // if the user made choices for language, I assume it's okay to assign his/her selected language to "en-us" + // since it's the fallback language and there will be no difference in the gacha record page. + // The enduser can still modify the `gacha_mappings.js` directly to enable multilingual for the gacha record system. writer.println("mappings = {\"en-us\": {"); // Avatars @@ -140,10 +221,10 @@ public final class Tools { default: color = "blue"; } - + // Got the magic number 4233146695 from manually search in the json file writer.println( "\"" + (avatarID % 1000 + 1000) + "\" : [\"" - + map.get(data.getNameTextMapHash()) + "(Avatar)\", \"" + + map.get(data.getNameTextMapHash()) + "(" + map.get(4233146695L)+ ")\", \"" + color + "\"]"); } @@ -173,13 +254,17 @@ public final class Tools { default: continue; // skip unnecessary entries } + + // Got the magic number 4231343903 from manually search in the json file + writer.println(",\"" + data.getId() + "\" : [\"" + map.get(data.getNameTextMapHash()).replaceAll("\"", "") - + "(Weapon)\",\""+ color + "\"]"); + + "("+ map.get(4231343903L)+")\",\""+ color + "\"]"); } - writer.println(",\"200\": \"Standard\", \"301\": \"Avatar Event\", \"302\": \"Weapon event\""); + writer.println(",\"200\": \""+map.get(332935371L)+"\", \"301\": \""+ map.get(2272170627L) + "\", \"302\": \""+map.get(2864268523L)+"\""); writer.println("}\n}"); } - Grasscutter.getLogger().info("Mappings generated!"); + + Grasscutter.getLogger().info("Mappings generated to " + location + " !"); } } diff --git a/src/main/java/emu/grasscutter/utils/Language.java b/src/main/java/emu/grasscutter/utils/Language.java new file mode 100644 index 000000000..cda46e512 --- /dev/null +++ b/src/main/java/emu/grasscutter/utils/Language.java @@ -0,0 +1,97 @@ +package emu.grasscutter.utils; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import emu.grasscutter.Grasscutter; + +import javax.annotation.Nullable; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +public final class Language { + private final JsonObject languageData; + private final Map<String, String> cachedTranslations = new HashMap<>(); + + /** + * Creates a language instance from a code. + * @param langCode The language code. + * @return A language instance. + */ + public static Language getLanguage(String langCode) { + return new Language(langCode + ".json", Grasscutter.getConfig().DefaultLanguage.toLanguageTag()); + } + + /** + * Returns the translated value from the key while substituting arguments. + * @param key The key of the translated value to return. + * @param args The arguments to substitute. + * @return A translated value with arguments substituted. + */ + public static String translate(String key, Object... args) { + String translated = Grasscutter.getLanguage().get(key); + + try { + return translated.formatted(args); + } catch (Exception exception) { + Grasscutter.getLogger().error("Failed to format string: " + key, exception); + return translated; + } + } + + /** + * Reads a file and creates a language instance. + * @param fileName The name of the language file. + */ + private Language(String fileName, String fallback) { + @Nullable JsonObject languageData = null; + + try { + InputStream file = Grasscutter.class.getResourceAsStream("/languages/" + fileName); + String translationContents = Utils.readFromInputStream(file); + if(translationContents.equals("empty")) { + file = Grasscutter.class.getResourceAsStream("/languages/" + fallback); + translationContents = Utils.readFromInputStream(file); + } + + languageData = Grasscutter.getGsonFactory().fromJson(translationContents, JsonObject.class); + } catch (Exception exception) { + Grasscutter.getLogger().warn("Failed to load language file: " + fileName, exception); + } + + this.languageData = languageData; + } + + /** + * Returns the value (as a string) from a nested key. + * @param key The key to look for. + * @return The value (as a string) from a nested key. + */ + public String get(String key) { + if(this.cachedTranslations.containsKey(key)) { + return this.cachedTranslations.get(key); + } + + String[] keys = key.split("\\."); + JsonObject object = this.languageData; + + int index = 0; + String result = "This value does not exist. Please report this to the Discord: " + key; + + while (true) { + if(index == keys.length) break; + + String currentKey = keys[index++]; + if(object.has(currentKey)) { + JsonElement element = object.get(currentKey); + if(element.isJsonObject()) + object = element.getAsJsonObject(); + else { + result = element.getAsString(); break; + } + } else break; + } + + this.cachedTranslations.put(key, result); return result; + } +} diff --git a/src/main/java/emu/grasscutter/utils/Utils.java b/src/main/java/emu/grasscutter/utils/Utils.java index 1b4a00b90..1d79c496e 100644 --- a/src/main/java/emu/grasscutter/utils/Utils.java +++ b/src/main/java/emu/grasscutter/utils/Utils.java @@ -1,10 +1,13 @@ package emu.grasscutter.utils; import java.io.*; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.StandardCopyOption; import java.time.*; import java.time.temporal.TemporalAdjusters; +import java.util.HashMap; +import java.util.Map; import java.util.Random; import emu.grasscutter.Config; @@ -15,6 +18,10 @@ import io.netty.buffer.Unpooled; import org.slf4j.Logger; +import javax.annotation.Nullable; + +import static emu.grasscutter.utils.Language.translate; + @SuppressWarnings({"UnusedReturnValue", "BooleanMethodIsAlwaysInverted"}) public final class Utils { public static final Random random = new Random(); @@ -176,15 +183,15 @@ public final class Utils { // Check for resources folder. if(!fileExists(resourcesFolder)) { - logger.info(Grasscutter.getLanguage().Create_resources_folder); - logger.info(Grasscutter.getLanguage().Place_copy); + logger.info(translate("messages.status.create_resources")); + logger.info(translate("messages.status.resources_error")); createFolder(resourcesFolder); exit = true; } - // Check for BinOutput + ExcelBinOuput. + // Check for BinOutput + ExcelBinOutput. if(!fileExists(resourcesFolder + "BinOutput") || !fileExists(resourcesFolder + "ExcelBinOutput")) { - logger.info(Grasscutter.getLanguage().Place_copy); + logger.info(translate("messages.status.resources_error")); exit = true; } @@ -195,7 +202,11 @@ public final class Utils { if(exit) System.exit(1); } - public static int GetNextTimestampOfThisHour(int hour, String timeZone, int param) { + /** + * Gets the timestamp of the next hour. + * @return The timestamp in UNIX seconds. + */ + public static int getNextTimestampOfThisHour(int hour, String timeZone, int param) { ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneId.of(timeZone)); for (int i = 0; i < param; i ++){ if (zonedDateTime.getHour() < hour) { @@ -204,10 +215,14 @@ public final class Utils { zonedDateTime = zonedDateTime.plusDays(1).withHour(hour).withMinute(0).withSecond(0); } } - return (int)zonedDateTime.toInstant().atZone(ZoneOffset.UTC).toEpochSecond(); + return (int) zonedDateTime.toInstant().atZone(ZoneOffset.UTC).toEpochSecond(); } - public static int GetNextTimestampOfThisHourInNextWeek(int hour, String timeZone, int param) { + /** + * Gets the timestamp of the next hour in a week. + * @return The timestamp in UNIX seconds. + */ + public static int getNextTimestampOfThisHourInNextWeek(int hour, String timeZone, int param) { ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneId.of(timeZone)); for (int i = 0; i < param; i++) { if (zonedDateTime.getDayOfWeek() == DayOfWeek.MONDAY && zonedDateTime.getHour() < hour) { @@ -216,10 +231,14 @@ public final class Utils { zonedDateTime = zonedDateTime.with(TemporalAdjusters.next(DayOfWeek.MONDAY)).withHour(hour).withMinute(0).withSecond(0); } } - return (int)zonedDateTime.toInstant().atZone(ZoneOffset.UTC).toEpochSecond(); + return (int) zonedDateTime.toInstant().atZone(ZoneOffset.UTC).toEpochSecond(); } - public static int GetNextTimestampOfThisHourInNextMonth(int hour, String timeZone, int param) { + /** + * Gets the timestamp of the next hour in a month. + * @return The timestamp in UNIX seconds. + */ + public static int getNextTimestampOfThisHourInNextMonth(int hour, String timeZone, int param) { ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneId.of(timeZone)); for (int i = 0; i < param; i++) { if (zonedDateTime.getDayOfMonth() == 1 && zonedDateTime.getHour() < hour) { @@ -228,6 +247,63 @@ public final class Utils { zonedDateTime = zonedDateTime.with(TemporalAdjusters.firstDayOfNextMonth()).withHour(hour).withMinute(0).withSecond(0); } } - return (int)zonedDateTime.toInstant().atZone(ZoneOffset.UTC).toEpochSecond(); + return (int) zonedDateTime.toInstant().atZone(ZoneOffset.UTC).toEpochSecond(); + } + + /** + * Retrieves a string from an input stream. + * @param stream The input stream. + * @return The string. + */ + public static String readFromInputStream(@Nullable InputStream stream) { + if(stream == null) return "empty"; + + StringBuilder stringBuilder = new StringBuilder(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) { + String line; while ((line = reader.readLine()) != null) { + stringBuilder.append(line); + } stream.close(); + } catch (IOException e) { + Grasscutter.getLogger().warn("Failed to read from input stream."); + } catch (NullPointerException ignored) { + return "empty"; + } return stringBuilder.toString(); + } + + /** + * Switch properties from upper case to lower case? + */ + public static Map<String, Object> switchPropertiesUpperLowerCase(Map<String, Object> objMap, Class<?> cls) { + Map<String, Object> map = new HashMap<>(objMap.size()); + for (String key : objMap.keySet()) { + try { + char c = key.charAt(0); + if (c >= 'a' && c <= 'z') { + try { + cls.getDeclaredField(key); + map.put(key, objMap.get(key)); + } catch (NoSuchFieldException e) { + String s1 = String.valueOf(c).toUpperCase(); + String after = key.length() > 1 ? s1 + key.substring(1) : s1; + cls.getDeclaredField(after); + map.put(after, objMap.get(key)); + } + } else if (c >= 'A' && c <= 'Z') { + try { + cls.getDeclaredField(key); + map.put(key, objMap.get(key)); + } catch (NoSuchFieldException e) { + String s1 = String.valueOf(c).toLowerCase(); + String after = key.length() > 1 ? s1 + key.substring(1) : s1; + cls.getDeclaredField(after); + map.put(after, objMap.get(key)); + } + } + } catch (NoSuchFieldException e) { + map.put(key, objMap.get(key)); + } + } + + return map; } } diff --git a/src/main/resources/languages/en-US.json b/src/main/resources/languages/en-US.json new file mode 100644 index 000000000..9a7485608 --- /dev/null +++ b/src/main/resources/languages/en-US.json @@ -0,0 +1,336 @@ +{ + "messages": { + "game": { + "port_bind": "Game Server started on port %s", + "connect": "Client connected from %s", + "disconnect": "Client disconnected from %s", + "game_update_error": "An error occurred during game update.", + "command_error": "Command error:" + }, + "dispatch": { + "port_bind": "[Dispatch] Dispatch server started on port %s", + "request": "[Dispatch] Client %s %s request: %s", + "keystore": { + "general_error": "[Dispatch] Error while loading keystore!", + "password_error": "[Dispatch] Unable to load keystore. Trying default keystore password...", + "no_keystore_error": "[Dispatch] No SSL cert found! Falling back to HTTP server.", + "default_password": "[Dispatch] The default keystore password was loaded successfully. Please consider setting the password to 123456 in config.json." + }, + "no_commands_error": "Commands are not supported in dispatch only mode.", + "unhandled_request_error": "[Dispatch] Potential unhandled %s request: %s", + "account": { + "login_attempt": "[Dispatch] Client %s is trying to log in", + "login_success": "[Dispatch] Client %s logged in as %s", + "login_token_attempt": "[Dispatch] Client %s is trying to log in via token", + "login_token_error": "[Dispatch] Client %s failed to log in via token", + "login_token_success": "[Dispatch] Client %s logged in via token as %s", + "combo_token_success": "[Dispatch] Client %s succeed to exchange combo token", + "combo_token_error": "[Dispatch] Client %s failed to exchange combo token", + "account_login_create_success": "[Dispatch] Client %s failed to log in: Account %s created", + "account_login_create_error": "[Dispatch] Client %s failed to log in: Account create failed", + "account_login_exist_error": "[Dispatch] Client %s failed to log in: Account no found", + "account_cache_error": "Game account cache information error", + "session_key_error": "Wrong session key.", + "username_error": "Username not found.", + "username_create_error": "Username not found, create failed." + } + }, + "status": { + "free_software": "Grasscutter is FREE software. If you have paid for this, you may have been scammed. Homepage: https://github.com/Grasscutters/Grasscutter", + "starting": "Starting Grasscutter...", + "shutdown": "Shutting down...", + "done": "Done! For help, type \"help\"", + "error": "An error occurred.", + "welcome": "Welcome to Grasscutter", + "run_mode_error": "Invalid server run mode: %s.", + "run_mode_help": "Server run mode must be 'HYBRID', 'DISPATCH_ONLY', or 'GAME_ONLY'. Unable to start Grasscutter...", + "create_resources": "Creating resources folder...", + "resources_error": "Place a copy of 'BinOutput' and 'ExcelBinOutput' in the resources folder." + } + }, + "commands": { + "generic": { + "not_specified": "No command specified.", + "unknown_command": "Unknown command: %s", + "permission_error": "You do not have permission to run this command.", + "console_execute_error": "This command can only be run from the console.", + "player_execute_error": "Run this command in-game.", + "command_exist_error": "No command found.", + "no_description_specified": "No description specified", + "invalid": { + "amount": "Invalid amount.", + "artifactId": "Invalid artifactId.", + "avatarId": "Invalid avatarId.", + "avatarLevel": "Invalid avatarLevel.", + "entityId": "Invalid entityId.", + "itemId": "Invalid itemId.", + "itemLevel": "Invalid itemLevel.", + "itemRefinement": "Invalid itemRefinement.", + "playerId": "Invalid playerId.", + "uid": "Invalid UID." + } + }, + "execution": { + "uid_error": "Invalid UID.", + "player_exist_error": "Player not found.", + "player_offline_error": "Player is not online.", + "item_id_error": "Invalid item ID.", + "item_player_exist_error": "Invalid item or UID.", + "entity_id_error": "Invalid entity ID.", + "player_exist_offline_error": "Player not found or is not online.", + "argument_error": "Invalid arguments.", + "clear_target": "Target cleared.", + "set_target": "Subsequent commands will target @%s by default.", + "need_target": "This command requires a target UID. Add a <@UID> argument or set a persistent target with /target @UID." + }, + "status": { + "enabled": "Enabled", + "disabled": "Disabled", + "help": "Help", + "success": "Success" + }, + "account": { + "modify": "Modify user accounts", + "invalid": "Invalid UID.", + "exists": "Account already exists.", + "create": "Account created with UID %s.", + "delete": "Account deleted.", + "no_account": "Account not found.", + "command_usage": "Usage: account <create|delete> <username> [uid]", + "description": "Modify user accounts" + }, + "broadcast": { + "command_usage": "Usage: broadcast <message>", + "message_sent": "Message sent.", + "description": "Sends a message to all the players" + }, + "changescene": { + "usage": "Usage: changescene <sceneId>", + "already_in_scene": "You are already in that scene.", + "success": "Changed to scene %s.", + "exists_error": "The specified scene does not exist.", + "description": "Changes your scene" + }, + "clear": { + "command_usage": "Usage: clear <all|wp|art|mat>", + "weapons": "Cleared weapons for %s.", + "artifacts": "Cleared artifacts for %s.", + "materials": "Cleared materials for %s.", + "furniture": "Cleared furniture for %s.", + "displays": "Cleared displays for %s.", + "virtuals": "Cleared virtuals for %s.", + "everything": "Cleared everything for %s.", + "description": "Deletes unequipped unlocked items, including yellow rarity ones from your inventory" + }, + "coop": { + "usage": "Usage: coop <playerId> <target playerId>", + "success": "Summoned %s to %s's world.", + "description": "Forces someone to join the world of others" + }, + "enter_dungeon": { + "usage": "Usage: enterdungeon <dungeon id>", + "changed": "Changed to dungeon %s", + "not_found_error": "Dungeon does not exist", + "in_dungeon_error": "You are already in that dungeon", + "description": "Enter a dungeon" + }, + "giveAll": { + "usage": "Usage: giveall [player] [amount]", + "started": "Receiving all items...", + "success": "Successfully gave all items to %s.", + "invalid_amount_or_playerId": "Invalid amount or player ID.", + "description": "Gives all items" + }, + "giveArtifact": { + "usage": "Usage: giveart|gart [player] <artifactId> <mainPropId> [<appendPropId>[,<times>]]... [level]", + "id_error": "Invalid artifact ID.", + "success": "Given %s to %s.", + "description": "Gives the player a specified artifact" + }, + "giveChar": { + "usage": "Usage: givechar <player> <itemId|itemName> [amount]", + "given": "Given %s with level %s to %s.", + "invalid_avatar_id": "Invalid avatar id.", + "invalid_avatar_level": "Invalid avatar level.", + "invalid_avatar_or_player_id": "Invalid avatar or player ID.", + "description": "Gives the player a specified character" + }, + "give": { + "usage": "Usage: give <player> <itemId|itemName> [amount] [level]", + "refinement_only_applicable_weapons": "Refinement is only applicable to weapons.", + "refinement_must_between_1_and_5": "Refinement must be between 1 and 5.", + "given": "Given %s of %s to %s.", + "given_with_level_and_refinement": "Given %s with level %s, refinement %s %s times to %s", + "given_level": "Given %s with level %s %s times to %s", + "description": "Gives an item to you or the specified player" + }, + "godmode": { + "success": "Godmode is now %s for %s.", + "description": "Prevents you from taking damage. Defaults to toggle." + }, + "heal": { + "success": "All characters have been healed.", + "description": "Heal all characters in your current team." + }, + "kick": { + "player_kick_player": "Player [%s:%s] has kicked player [%s:%s]", + "server_kick_player": "Kicking player [%s:%s]", + "description": "Kicks the specified player from the server (WIP)" + }, + "kill": { + "usage": "Usage: killall [playerUid] [sceneId]", + "scene_not_found_in_player_world": "Scene not found in player world", + "kill_monsters_in_scene": "Killing %s monsters in scene %s", + "description": "Kill all entities" + }, + "killCharacter": { + "usage": "Usage: /killcharacter [playerId]", + "success": "Killed %s's current character.", + "description": "Kills the players current character" + }, + "list": { + "success": "There are %s player(s) online:", + "description": "List online players" + }, + "permission": { + "usage": "Usage: permission <add|remove> <username> <permission>", + "add": "Permission added.", + "has_error": "They already have this permission!", + "remove": "Permission removed.", + "not_have_error": "They don't have this permission!", + "account_error": "The account cannot be found.", + "description": "Grants or removes a permission for a user" + }, + "position": { + "success": "Coordinates: %s, %s, %s\nScene id: %s", + "description": "Get coordinates." + }, + "reload": { + "reload_start": "Reloading config.", + "reload_done": "Reload complete.", + "description": "Reload server config" + }, + "resetConst": { + "reset_all": "Reset all avatars' constellations.", + "success": "Constellations for %s have been reset. Please relog to see changes.", + "description": "Resets the constellation level on your current active character, will need to relog after using the command to see any changes." + }, + "resetShopLimit": { + "usage": "Usage: /resetshop <player id>", + "description": "Reset target player's shop refresh time." + }, + "sendMail": { + "usage": "Usage: give [player] <itemId|itemName> [amount]", + "user_not_exist": "The user with an id of '%s' does not exist", + "start_composition": "Starting composition of message.\nPlease use `/sendmail <title>` to continue.\nYou can use `/sendmail stop` at any time", + "templates": "Mail templates coming soon implemented...", + "invalid_arguments": "Invalid arguments.\nUsage `/sendmail <userId|all|help> [templateId]`", + "send_cancel": "Message sending cancelled", + "send_done": "Message sent to user %s!", + "send_all_done": "Message sent to all users!", + "not_composition_end": "Message composition not at final stage.\nPlease use `/sendmail %s` or `/sendmail stop` to cancel", + "please_use": "Please use `/sendmail %s`", + "set_title": "Message title set as '%s'.\nUse '/sendmail <content>' to continue.", + "set_contents": "Message contents set as '%s'.\nUse '/sendmail <sender>' to continue.", + "set_message_sender": "Message sender set as '%s'.\nUse '/sendmail <itemId|itemName|finish> [amount] [level]' to continue.", + "send": "Attached %s of %s (level %s) to the message.\nContinue adding more items or use `/sendmail finish` to send the message.", + "invalid_arguments_please_use": "Invalid arguments \n Please use `/sendmail %s`", + "title": "<title>", + "message": "<message>", + "sender": "<sender>", + "arguments": "<itemId|itemName|finish> [amount] [level]", + "error": "ERROR: invalid construction stage %s. Check console for stacktrace.", + "description": "Sends mail to the specified user. The usage of this command changes based on it's composition state." + }, + "sendMessage": { + "usage": "Usage: sendmessage <player> <message>", + "success": "Message sent.", + "description": "Sends a message to a player as the server" + }, + "setFetterLevel": { + "usage": "Usage: setfetterlevel <level>", + "range_error": "Fetter level must be between 0 and 10.", + "success": "Fetter level set to %s", + "level_error": "Invalid fetter level.", + "description": "Sets your fetter level for your current active character" + }, + "setStats": { + "usage_console": "Usage: setstats|stats @<UID> <stat> <value>", + "usage_ingame": "Usage: setstats|stats [@UID] <stat> <value>", + "help_message": "\n\tValues for <stat>: hp | maxhp | def | atk | em | er | crate | cdmg | cdr | heal | heali | shield | defi\n\t(cont.) Elemental DMG Bonus: epyro | ecryo | ehydro | egeo | edendro | eelectro | ephys\n\t(cont.) Elemental RES: respyro | rescryo | reshydro | resgeo | resdendro | reselectro | resphys\n", + "value_error": "Invalid stat value.", + "uid_error": "Invalid UID.", + "player_error": "Player not found or offline.", + "set_self": "%s set to %s.", + "set_for_uid": "%s for %s set to %s.", + "set_max_hp": "MAX HP set to %s.", + "description": "Set fight property for your current active character" + }, + "setWorldLevel": { + "usage": "Usage: setworldlevel <level>", + "value_error": "World level must be between 0-8", + "success": "World level set to %s.", + "invalid_world_level": "Invalid world level.", + "description": "Sets your world level (Relog to see proper effects)" + }, + "spawn": { + "usage": "Usage: spawn <entityId> [amount] [level(monster only)]", + "success": "Spawned %s of %s.", + "description": "Spawns an entity near you" + }, + "stop": { + "success": "Server shutting down...", + "description": "Stops the server" + }, + "talent": { + "usage_1": "To set talent level: /talent set <talentID> <value>", + "usage_2": "Another way to set talent level: /talent <n or e or q> <value>", + "usage_3": "To get talent ID: /talent getid", + "lower_16": "Invalid talent level. Level should be lower than 16", + "set_id": "Set talent to %s.", + "set_atk": "Set talent Normal ATK to %s.", + "set_e": "Set talent E to %s.", + "set_q": "Set talent Q to %s.", + "invalid_skill_id": "Invalid skill ID.", + "set_this": "Set this talent to %s.", + "invalid_level": "Invalid talent level.", + "normal_attack_id": "Normal Attack ID %s.", + "e_skill_id": "E skill ID %s.", + "q_skill_id": "Q skill ID %s.", + "description": "Set talent level for your current active character" + }, + "teleportAll": { + "success": "Summoned all players to your location.", + "error": "You only can use this command in MP mode.", + "description": "Teleports all players in your world to your position" + }, + "teleport": { + "usage_server": "Usage: /tp @<player id> <x> <y> <z> [scene id]", + "usage": "Usage: /tp [@<player id>] <x> <y> <z> [scene id]", + "specify_player_id": "You must specify a player id.", + "invalid_position": "Invalid position.", + "success": "Teleported %s to %s, %s, %s in scene %s", + "description": "Change the player's position." + }, + "weather": { + "usage": "Usage: weather <weatherId> [climateId]", + "success": "Changed weather to %s with climate %s", + "invalid_id": "Invalid ID.", + "description": "Changes the weather." + }, + "drop": { + "command_usage": "Usage: drop <itemId|itemName> [amount]", + "success": "Dropped %s of %s.", + "description": "Drops an item near you" + }, + "help": { + "usage": "Usage: ", + "aliases": "Aliases: ", + "available_commands": "Available commands: ", + "description": "Sends the help message or shows information about a specified command" + }, + "restart": { + "description": "Restarts the current session" + } + } +} diff --git a/src/main/resources/languages/pl-PL.json b/src/main/resources/languages/pl-PL.json new file mode 100644 index 000000000..e9ff74e25 --- /dev/null +++ b/src/main/resources/languages/pl-PL.json @@ -0,0 +1,298 @@ +{ + "messages": { + "game": { + "port_bind": "Serwer gry uruchomiony na porcie: %s", + "connect": "Klient połączył się z %s", + "disconnect": "Klient rozłączył się z %s", + "game_update_error": "Wystąpił błąd podczas aktualizacji gry.", + "command_error": "Błąd komendy:" + }, + "dispatch": { + "port_bind": "[Dispatch] Serwer dispatch wystartował na porcie %s", + "request": "[Dispatch] Klient %s %s zapytanie: %s", + "keystore": { + "general_error": "[Dispatch] Błąd łądowania keystore!", + "password_error": "[Dispatch] Nie można załadować keystore. Próba z domyślnym hasłem keystore...", + "no_keystore_error": "[Dispatch] Brak certyfikatu SSL! Przejście na serwer HTTP.", + "default_password": "[Dispatch] Domyślne hasło keystore zadziałało. Rozważ ustawienie go na 123456 w pliku config.json." + }, + "no_commands_error": "Komendy nie są wspierane w trybie DISPATCH_ONLY.", + "unhandled_request_error": "[Dispatch] Potencjalnie niepodtrzymane %s zapytanie: %s", + "account": { + "login_attempt": "[Dispatch] Klient %s próbuje się zalogować", + "login_success": "[Dispatch] Klient %s zalogował się jako %s", + "login_token_attempt": "[Dispatch] Klient %s próbuje się zalogować poprzez token", + "login_token_error": "[Dispatch] Klient %s nie mógł się zalogować poprzez token", + "login_token_success": "[Dispatch] Klient %s zalogował się poprzez token jako %s", + "combo_token_success": "[Dispatch] Klient %s pomyślnie wymienił combo token", + "combo_token_error": "[Dispatch] Klient %s nie wymienił combo token'u", + "account_login_create_success": "[Dispatch] Klient %s nie mógł się zalogować: Konto %s stworzone", + "account_login_create_error": "[Dispatch] Klient %s nie mógł się zalogować: Tworzenie konta nie powiodło się", + "account_login_exist_error": "[Dispatch] Klient %s nie mógł się zalogować: Nie znaleziono konta", + "account_cache_error": "Błąd pamięci cache konta gry", + "session_key_error": "Błędny klucz sesji.", + "username_error": "Nazwa użytkownika nie znaleziona.", + "username_create_error": "Nazwa użytkownika nie znaleziona, tworzenie nie powiodło się." + } + }, + "status": { + "free_software": "Grasscutter to DARMOWE oprogramowanie. Jeżeli ktoś Ci je sprzedał, to zostałeś oscamowany. Strona domowa: https://github.com/Grasscutters/Grasscutter", + "starting": "Uruchamianie Grasscutter...", + "shutdown": "Wyłączanie...", + "done": "Gotowe! Wpisz \"help\" aby uzyskać pomoc", + "error": "Wystąpił błąd.", + "welcome": "Witamy w Grasscutter", + "run_mode_error": "Błędny tryb pracy serwera: %s.", + "run_mode_help": "Tryb pracy serwera musi być ustawiony na 'HYBRID', 'DISPATCH_ONLY', lub 'GAME_ONLY'. Nie można wystartować Grasscutter...", + "create_resources": "Tworzenie folderu resources...", + "resources_error": "Umieść kopię 'BinOutput' i 'ExcelBinOutput' w folderze resources." + } + }, + "commands": { + "generic": { + "not_specified": "Nie podano komendy.", + "unknown_command": "Nieznana komenda: %s", + "permission_error": "Nie masz uprawnień do tej komendy.", + "console_execute_error": "Tą komende można wywołać tylko z konsoli.", + "player_execute_error": "Wywołaj tą komendę w grze.", + "command_exist_error": "Nie znaleziono komendy.", + "invalid": { + "amount": "Błędna ilość.", + "artifactId": "Błędne ID artefaktu.", + "avatarId": "Błędne id postaci.", + "avatarLevel": "Błędny poziom postaci.", + "entityId": "Błędne id obiektu.", + "id przedmiotu": "Błędne id przedmiotu.", + "itemLevel": "Błędny poziom przedmiotu.", + "itemRefinement": "Błędne ulepszenie.", + "playerId": "Błędne playerId.", + "uid": "Błędne UID." + } + }, + "execution": { + "uid_error": "Błędne UID.", + "player_exist_error": "Gracz nie znaleziony.", + "player_offline_error": "Gracz nie jest online.", + "item_id_error": "Błędne ID przedmiotu.", + "item_player_exist_error": "Błędny przedmiot lub UID.", + "entity_id_error": "Błędne ID obiektu.", + "player_exist_offline_error": "Gracz nie znaleziony lub jest offline.", + "argument_error": "Błędne argumenty.", + "clear_target": "Cel wyczyszczony.", + "set_target": "Następne komendy będą celować w @%s.", + "need_target": "Ta komenda wymaga docelowego UID. Dodaj argument <@UID> lub ustaw stały cel poleceniem /target @UID." + }, + "status": { + "enabled": "Włączone", + "disabled": "Wyłączone", + "help": "Pomoc", + "success": "Sukces" + }, + "account": { + "modify": "Modyfikuj konta użytkowników", + "invalid": "Błędne UID.", + "exists": "Konto już istnieje.", + "create": "Stworzono konto z UID %s.", + "delete": "Konto usunięte.", + "no_account": "Nie znaleziono konta.", + "command_usage": "Użycie: account <create|delete> <nazwa> [uid]" + }, + "broadcast": { + "command_usage": "Użycie: broadcast <wiadomość>", + "message_sent": "Wiadomość wysłana." + }, + "changescene": { + "usage": "Użycie: changescene <id sceny>", + "already_in_scene": "Już jesteś na tej scenie.", + "success": "Zmieniono scene na %s.", + "exists_error": "Ta scena nie istenieje." + }, + "clear": { + "command_usage": "Użycie: clear <all|wp|art|mat>", + "weapons": "Wyczyszczono bronie dla %s.", + "artifacts": "Wyczyszczono artefakty dla %s.", + "materials": "Wyczyszczono materiały dla %s.", + "furniture": "Wyczyszczono meble dla %s.", + "displays": "Wyczyszczono displays dla %s.", + "virtuals": "Wyczyszczono virtuals dla %s.", + "everything": "Wyczyszczono wszystko dla %s." + }, + "coop": { + "usage": "Użycie: coop <id gracza> <id gracza docelowego>", + "success": "Przyzwano %s do świata %s." + }, + "enter_dungeon": { + "usage": "Użycie: enterdungeon <ID lochu>", + "changed": "Zmieniono loch na %s", + "not_found_error": "Ten loch nie istnieje", + "in_dungeon_error": "Już jesteś w tym lochu" + }, + "giveAll": { + "usage": "Użycie: giveall [gracz] [ilość]", + "started": "Dodawanie wszystkich przedmiotów...", + "success": "Pomyślnie dodano wszystkie przedmioty dla %s.", + "invalid_amount_or_playerId": "Błędna ilość lub ID gracza." + }, + "giveArtifact": { + "usage": "Użycie: giveart|gart [gracz] <id artefaktu> <mainPropId> [<appendPropId>[,<razy>]]... [poziom]", + "id_error": "Błędne ID artefaktu.", + "success": "Dano %s dla %s." + }, + "giveChar": { + "usage": "Użycie: givechar <gracz> <id przedmiotu | nazwa przedmiotu> [ilość]", + "given": "Dano %s z poziomem %s dla %s.", + "invalid_avatar_id": "Błędne ID postaci.", + "invalid_avatar_level": "Błędny poziom postaci.", + "invalid_avatar_or_player_id": "Błędne ID postaci lub gracza." + }, + "give": { + "usage": "Użycie: give <gracz> <id przedmiotu | nazwa przedmiotu> [ilość] [poziom]", + "refinement_only_applicable_weapons": "Ulepszenie można zastosować tylko dla broni.", + "refinement_must_between_1_and_5": "Ulepszenie musi być pomiędzy 1, a 5.", + "given": "Dano %s %s dla %s.", + "given_with_level_and_refinement": "Dano %s z poziomem %s, ulepszeniem %s %s razy dla %s", + "given_level": "Dano %s z poziomem %s %s razy dla %s" + }, + "godmode": { + "success": "Godmode jest teraz %s dla %s." + }, + "heal": { + "success": "Wszystkie postacie zostały wyleczone." + }, + "kick": { + "player_kick_player": "Gracz [%s:%s] wyrzucił gracza [%s:%s]", + "server_kick_player": "Wyrzucono gracza [%s:%s]" + }, + "kill": { + "usage": "Użycie: killall [UID gracza] [ID sceny]", + "scene_not_found_in_player_world": "Scena nie znaleziona w świecie gracza", + "kill_monsters_in_scene": "Zabito %s potworów w scenie %s" + }, + "killCharacter": { + "usage": "Użycie: /killcharacter [ID gracza]", + "success": "Zabito aktualną postać gracza %s." + }, + "list": { + "success": "Teraz jest %s gracz(y) online:" + }, + "permission": { + "usage": "Użycie: permission <add|remove> <nazwa gracza> <uprawnienie>", + "add": "Dodano uprawnienie", + "has_error": "To konto już ma to uprawnienie!", + "remove": "Usunięto uprawnienie.", + "not_have_error": "To konto nie ma tych uprawnień!", + "account_error": "Konto nie może zostać znalezione." + }, + "position": { + "success": "Koordynaty: %.3f, %.3f, %.3f\nID sceny: %d" + }, + "reload": { + "reload_start": "Ponowne ładowanie konfiguracji.", + "reload_done": "Ponowne ładowanie zakończone." + }, + "resetConst": { + "reset_all": "Resetuj konstelacje wszystkich postaci.", + "success": "Konstelacje dla %s zostały zresetowane. Proszę zalogować się ponownie aby zobaczyć zmiany." + }, + "resetShopLimit": { + "usage": "Użycie: /resetshop <ID gracza>" + }, + "sendMail": { + "usage": "Użycie: `/sendmail <ID gracza | all | help> [id szablonu]`", + "user_not_exist": "Gracz o ID '%s' nie istnieje", + "start_composition": "Komponowanie wiadomości.\nProszę użyj `/sendmail <tytuł>` aby kontynuować.\nMożesz użyć `/sendmail stop` w dowolnym momencie", + "templates": "Szablony zostaną zaimplementowane niedługo...", + "invalid_arguments": "Błędne argumenty.\nUżycie `/sendmail <ID gracza | all | help> [id szablonu]`", + "send_cancel": "Anulowano wysyłanie wiadomości", + "send_done": "Wysłano wiadomość do gracza %s!", + "send_all_done": "Wysłano wiadomośc do wszystkich graczy!", + "not_composition_end": "Komponowanie nie jest na ostatnim etapie.\nProszę użyj `/sendmail %s` lub `/sendmail stop` aby anulować", + "please_use": "Proszę użyj `/sendmail %s`", + "set_title": "Tytuł wiadomości to teraz: '%s'.\nUżyj '/sendmail <treść>' aby kontynuować.", + "set_contents": "Treść wiadomości to teraz '%s'.\nUżyj '/sendmail <nadawca>' aby kontynuować.", + "set_message_sender": "Nadawca wiadomości to teraz '%s'.\nUżyj '/sendmail <id przedmiotu | nazwa przedmiotu | zakończ> [ilość] [poziom]' aby kontynuować.", + "send": "Załączono %s %s (poziom %s) do wiadomości.\nDodaj więcej przedmiotów lub użyj `/sendmail finish` aby wysłać wiadomość.", + "invalid_arguments_please_use": "Błędne argumenty \nProszę użyj `/sendmail %s`", + "title": "<tytuł>", + "message": "<wiadomość>", + "sender": "<nadawca>", + "arguments": "<id przedmiotu | nazwa przedmiotu | zakończ> [ilość] [poziom]", + "error": "BŁĄD: niepoprawny etap konstrukcji: %s. Sprawdź konsolę aby dowiedzieć się więcej." + }, + "sendMessage": { + "usage": "Użycie: /sendmessage <player> <message>", + "success": "Wiadomość wysłana." + }, + "setFetterLevel": { + "usage": "Użycie: setfetterlevel <poziom>", + "range_error": "Poziom przyjaźni musi być pomiędzy 0,a 10.", + "success": "Poziom przyjaźni ustawiono na: %s", + "level_error": "Błędny poziom przyjaźni." + }, + "setStats": { + "usage_console": "Użycie: setstats|stats @<UID> <statystyka> <wartość>", + "usage_ingame": "Użycie: setstats|stats [@UID] <statystyka> <wartość>", + "help_message": "\n\tWartości dla Statystyka: hp | maxhp | def | atk | em | er | crate | cdmg | cdr | heal | heali | shield | defi\n\t(cont.) Bonus DMG żywiołu: epyro | ecryo | ehydro | egeo | edendro | eelectro | ephys\n\t(cont.) RES na żywioł: respyro | rescryo | reshydro | resgeo | resdendro | reselectro | resphys\n", + "value_error": "Błędna wartość statystyki.", + "uid_error": "Błędne UID.", + "player_error": "Gracza nie znaleziono lub jest offline.", + "set_self": "%s ustawiono na %s.", + "set_for_uid": "%s dla %s ustawiono na %s.", + "set_max_hp": "Maksymalne HP ustawione na %s." + }, + "setWorldLevel": { + "usage": "Użycie: setworldlevel <poziom>", + "value_error": "Poziom świata musi być pomiędzy 0, a 8", + "success": "Ustawiono poziom świata na: %s.", + "invalid_world_level": "Invalid world level." + }, + "spawn": { + "usage": "Użycie: /spawn <id obiektu> [ilość] [poziom(tylko potwory)]", + "success": "Stworzono %s %s." + }, + "stop": { + "success": "Serwer wyłącza się..." + }, + "talent": { + "usage_1": "Aby ustawić poziom talentu: /talent set <ID talentu> <wartość>", + "usage_2": "Inny sposób na ustawienie poziomu talentu: /talent <n lub e lub q> <wartość>", + "usage_3": "Aby uzyskać ID talentu: /talent getid", + "lower_16": "Błędny poziom talentu. Poziom powinien być mniejszy niż 16", + "set_id": "Ustawiono talent na %s.", + "set_atk": "Ustawiono talent Atak Podstawowy na poziom %s.", + "set_e": "Ustawiono poziom talentu E na %s.", + "set_q": "Ustawiono poziom talentu Q na %s.", + "invalid_skill_id": "Błędne ID umiejętności.", + "set_this": "Ustawiono ten talent na poziom %s.", + "invalid_level": "Błędny poziom talentu.", + "normal_attack_id": "ID podstawowego ataku: %s.", + "e_skill_id": "ID umiejętności E: %s.", + "q_skill_id": "ID umiejętności Q: %s." + }, + "teleportAll": { + "success": "Przyzwano wszystkich graczy do Ciebie.", + "error": "Możesz użyć tej komendy wyłącznie w trybie MP." + }, + "teleport": { + "usage_server": "Użycie: /tp @<ID gracza> <x> <y> <z> [ID sceny]", + "usage": "Użycie: /tp [@<ID gracza>] <x> <y> <z> [ID sceny]", + "specify_player_id": "Musisz określić ID gracza.", + "invalid_position": "Błędna pozycja.", + "success": "Przeteleportowano %s do %s, %s, %s w scenie %s" + }, + "weather": { + "usage": "Użycie: weather <ID pogody> [ID klimatu]", + "success": "Zmieniono pogodę na %s z klimatem %s", + "invalid_id": "Błędne ID." + }, + "drop": { + "command_usage": "Użycie: drop <ID przedmiotu|nazwa przedmiotu> [ilość]", + "success": "Wyrzucono %s of %s." + }, + "help": { + "usage": "Użycie: ", + "aliases": "Aliasy: ", + "available_commands": "Dostępne komendy: " + } + } +} \ No newline at end of file diff --git a/src/main/resources/languages/zh-CN.json b/src/main/resources/languages/zh-CN.json new file mode 100644 index 000000000..4e4929aee --- /dev/null +++ b/src/main/resources/languages/zh-CN.json @@ -0,0 +1,335 @@ +{ + "messages": { + "game": { + "port_bind": "游戏服务器已在端口 %s 上启动", + "connect": "客户端已连接至 %s", + "disconnect": "客户端 %s 已断开连接", + "game_update_error": "游戏更新时发生错误", + "command_error": "命令发生错误:" + }, + "dispatch": { + "port_bind": "[Dispatch] 服务器已在端口 %s 上启动", + "request": "[Dispatch] 客户端 %s 请求: %s %s", + "keystore": { + "general_error": "[Dispatch] 加载 keystore 文件时发生错误!", + "password_error": "[Dispatch] 加载 keystore 失败。正在尝试使用预设的 keystore 密码...", + "no_keystore_error": "[Dispatch] 未找到 SSL 证书!已降级到 HTTP 服务器", + "default_password": "[Dispatch] 默认的 keystore 密码加载成功。请考虑将 config.json 的默认密码设置为 123456" + }, + "no_commands_error": "此命令不适用于 Dispatch-only 模式", + "unhandled_request_error": "[Dispatch] 潜在的未处理请求 %s 请求:%s", + "account": { + "login_attempt": "[Dispatch] 客户端 %s 正在尝试登录", + "login_success": "[Dispatch] 客户端 %s 已登录,UID为 %s", + "login_token_attempt": "[Dispatch] 客户端 %s 正在尝试使用令牌登录", + "login_token_error": "[Dispatch] 客户端 %s 使用令牌登录失败", + "login_token_success": "[Dispatch] 客户端 %s 已通过令牌登录,UID为 %s", + "combo_token_success": "[Dispatch] 客户端 %s 交换令牌成功", + "combo_token_error": "[Dispatch] 客户端 %s 交换令牌失败", + "account_login_create_success": "[Dispatch] 客户端 %s 登录失败: 已注册UID为 %s 的账号", + "account_login_create_error": "[Dispatch] 客户端 %s 登录失败:账号创建失败。", + "account_login_exist_error": "[Dispatch] 客户端 %s 登录失败:账号不存在", + "account_cache_error": "游戏账号缓存信息错误", + "session_key_error": "交换秘钥不符。", + "username_error": "未找到此用户名。", + "username_create_error": "未找到用户名,建立连接失败。" + } + }, + "status": { + "free_software": "Grasscutter 是免费开源软件,遵循Apache-2.0 license。如果您是付费购买的,那您已经被骗了。项目地址:Github:https://github.com/Grasscutters/Grasscutter", + "starting": "正在启动 Grasscutter...", + "shutdown": "正在关闭...", + "done": "加载完成!输入 \"help\" 查看命令列表", + "error": "发生了一个错误。", + "welcome": "欢迎使用 Grasscutter!珍惜这段美妙的旅途吧!", + "run_mode_error": "无效的服务器运行模式: %s。", + "run_mode_help": "服务器运行模式必须为 HYBRID、DISPATCH_ONLY 或 GAME_ONLY。Grasscutter 启动失败...", + "create_resources": "正在创建 resources 目录...", + "resources_error": "请将 BinOutput 和 ExcelBinOutput 复制到 resources 目录。" + } + }, + "commands": { + "generic": { + "not_specified": "没有指定命令。", + "unknown_command": "未知的命令:%s", + "permission_error": "哼哼哼!您没有执行此命令的权限!请联系服务器管理员解决!", + "console_execute_error": "此命令只能在服务器控制台执行呐~", + "player_execute_error": "此命令只能在游戏内执行哦~", + "command_exist_error": "这条命令……好像找不到呢?。", + "invalid": { + "amount": "无效的数量.", + "artifactId": "无效的圣遗物ID。", + "avatarId": "无效的角色ID。", + "avatarLevel": "无效的角色等級。", + "entityId": "无效的实体ID。", + "itemId": "无效的物品ID。", + "itemLevel": "无效的物品等級。", + "itemRefinement": "无效的物品精炼等级。", + "playerId": "无效的玩家ID。", + "uid": "无效的UID。" + } + }, + "execution": { + "uid_error": "无效的UID。", + "player_exist_error": "用户不存在。", + "player_offline_error": "玩家已离线。", + "item_id_error": "无效的物品ID。.", + "item_player_exist_error": "无效的物品/玩家UID。", + "entity_id_error": "无效的实体ID。", + "player_exist_offline_error": "玩家不存在或已离线。", + "argument_error": "无效的参数。", + "clear_target": "目标已清除.", + "set_target": "随后的的命令都会以@%s为预设。", + "need_target": "此命令需要一个目标 UID。添加 <@UID> 参数或使用 /target @UID 来设定持久目标。" + }, + "status": { + "enabled": "已启用", + "disabled": "未启用", + "help": "帮助", + "success": "成功" + }, + "account": { + "modify": "修改使用者账号", + "invalid": "无效的UID。", + "exists": "账号已存在。", + "create": "已建立账号,UID 为 %s 。", + "delete": "账号已刪除。", + "no_account": "账号不存在。", + "command_usage": "用法:account <create|delete> <username> [uid]", + "description": "创建或删除账号。" + }, + "broadcast": { + "command_usage": "用法:broadcast <消息>", + "message_sent": "公告已发送。", + "description": "向所有玩家发送公告。" + }, + "changescene": { + "usage": "用法:changescene <scene id>", + "already_in_scene": "你已经在这个秘境中了。", + "success": "已切换至秘境 %s.", + "exists_error": "此秘境不存在。", + "description": "切换指定秘境。" + }, + "clear": { + "command_usage": "用法: clear <all|wp|art|mat>", + "weapons": "已将 %s 的武器清空。", + "artifacts": "已将 %s 的圣遗物清空。", + "materials": "已将 %s 的材料清空。", + "furniture": "已将 %s 的尘歌壶家具清空。", + "displays": "已清除 %s 的显示。", + "virtuals": "已将 %s 的所有货币和经验值清空。", + "everything": "已将 %s 的所有物品清空。", + "description": "从您的背包中删除所有未装备且已解锁的物品,包括稀有物品。" + }, + "coop": { + "usage": "用法:coop <playerId> <target playerId>", + "success": "已强制召唤 %s 到 %s的世界", + "description": "强制召唤指定用户到他人的世界。" + }, + "enter_dungeon": { + "usage": "用法:enterdungeon <dungeon id>", + "changed": "已进入秘境 %s", + "not_found_error": "此秘境不存在。", + "in_dungeon_error": "你已经在秘境中了。", + "description": "进入指定秘境。" + }, + "giveAll": { + "usage": "用法:giveall [player] [amount]", + "started": "正在给予全部物品...", + "success": "已给予全部物品。", + "invalid_amount_or_playerId": "无效的数量/玩家ID。", + "description": "给予所有物品。" + }, + "giveArtifact": { + "usage": "用法:giveart|gart [player] <artifactId> <mainPropId> [<appendPropId>[,<times>]]... [level]", + "id_error": "无效的圣遗物ID。", + "success": "已将 %s 给予 %s。", + "description": "给予指定圣遗物。" + }, + "giveChar": { + "usage": "用法:givechar <player> <itemId|itemName> [amount]", + "given": "给予角色 %s 等级 %s 向UID %s.", + "invalid_avatar_id": "无效的角色ID。", + "invalid_avatar_level": "无效的角色等級。.", + "invalid_avatar_or_player_id": "无效的角色ID/玩家ID。", + "description": "给予指定角色。" + }, + "give": { + "usage": "用法:give <player> <itemId|itemName> [amount] [level] [refinement]", + "refinement_only_applicable_weapons": "精炼等阶参数仅在给予武器时可用", + "refinement_must_between_1_and_5": "精炼等阶必须在 1 到 5 之间。", + "given": "已将 %s 个 %s 给予 %s。", + "given_with_level_and_refinement": "已将 %s [等級%s, 精炼%s] %s个给予 %s", + "given_level": "已将 %s 等级 %s %s 个给予UID %s", + "description": "给予指定物品。" + }, + "godmode": { + "success": "上帝模式已被设置为 %s 。 [用户:%s]", + "description": "防止你受到伤害。" + }, + "heal": { + "success": "所有角色已被治疗。", + "description": "治疗所选队伍的角色。" + }, + "kick": { + "player_kick_player": "玩家 [%s:%s] 已将 [%s:%s] 踢出", + "server_kick_player": "正在踢出玩家 [%s:%s]", + "description": "从服务器内踢出指定玩家。" + }, + "kill": { + "usage": "用法:killall [playerUid] [sceneId]", + "scene_not_found_in_player_world": "未在玩家世界中找到此场景", + "kill_monsters_in_scene": "已杀死 %s 个怪物。 [场景ID: %s]", + "description": "杀死所有怪物" + }, + "killCharacter": { + "usage": "用法:/killcharacter [playerId]", + "success": "已杀死 %s 目前使用的角色。", + "description": "杀死目前使用的角色" + }, + "list": { + "success": "目前在线人数:%s", + "description": "查看所有玩家" + }, + "permission": { + "usage": "用法:permission <add|remove> <username> <permission>", + "add": "已设置权限。", + "has_error": "此玩家已拥有此权限!", + "remove": "权限已移除。", + "not_have_error": "此玩家未拥有权限!", + "account_error": "账号不存在!", + "description": "给予或移除指定玩家的权限。" + }, + "position": { + "success": "坐标:%.3f, %.3f, %.3f\n场景ID:%d", + "description": "获取所在位置。" + }, + "reload": { + "reload_start": "正在重载配置文件和数据。", + "reload_done": "重载完毕。", + "description": "重载配置文件和数据。" + }, + "resetConst": { + "reset_all": "重置所有角色的命座。", + "success": "已重置 %s 的命座,重新登录后将会生效。", + "description": "重置当前角色的命之座,执行命令后需重新登录以生效。" + }, + "resetShopLimit": { + "usage": "用法:/resetshop <player id>", + "description": "重置所选玩家的商店刷新时间。" + }, + "sendMail": { + "usage": "用法:give [player] <itemId|itemName> [amount]", + "user_not_exist": "ID '%s' 的使用者不存在。", + "start_composition": "发送邮件流程。\n请使用`/send <标题>`前进到下一步。\n你可以在任何时间使用`/sendmail stop`来停止发送。", + "templates": "邮件模板尚未实装...", + "invalid_arguments": "无效的参数。\n指令使用方法 `/sendmail <userId|all|help> [templateId]`", + "send_cancel": "取消发送邮件", + "send_done": "已将邮件发送给 %s!", + "send_all_done": "邮件已发送给所有人!", + "not_composition_end": "现在邮件发送未到最后阶段。\n请使用 `/sendmail %s` 继续发送邮件,或使用 `/sendmail stop` 来停止发送邮件。", + "please_use": "请使用 `/sendmail %s`", + "set_title": "成功将邮件标题设置为 '%s'。\n使用 '/sendmail <content>' 来设置邮件内容。", + "set_contents": "成功将'%s'设置为邮件内容。\n使用 '/sendmail <发件人>' 来设置发件人。", + "set_message_sender": "发件人已设置为 '%s'。\n使用 '/sendmail <itemId|itemName|finish> [amount] [level]' 来添加附件。", + "send": "已添加 %s 个 %s (等級为 %s) 邮件附件。\n如果没有要继续添加的道具请使用 `/sendmail finish` 来完成邮件发送。", + "invalid_arguments_please_use": "错误的参数 \n请使用 `/sendmail %s`", + "title": "<标题>", + "message": "<正文>", + "sender": "<发件人>", + "arguments": "<itemId|itemName|finish> [数量] [等级]", + "error": "错误:无效的编写阶段 %s。需要 StackTrace 请查看服务器控制台。", + "description": "向指定用户发送邮件。 此命令的用法可根据附加的参数而变化。" + }, + "sendMessage": { + "usage": "用法:sendmessage <player> <message>", + "success": "消息已发送。", + "description": "向指定玩家发送消息" + }, + "setFetterLevel": { + "usage": "用法:setfetterlevel <level>", + "range_error": "好感度等级必须在 0 到 10 之间。", + "fetter_set_level": "好感度已设置为 %s 级", + "level_error": "无效的好感度等级。", + "description": "设置当前角色的好感度等级。" + }, + "setStats": { + "usage_console": "用法:setstats|stats @<UID> <stat> <value>", + "usage_ingame": "用法:setstats|stats [@UID] <stat> <value>", + "help_message": "\n\t可使用的数据类型:hp (生命值)| maxhp (最大生命值) | def(防御力) | atk (攻击力)| em (元素精通) | er (元素充能效率) | crate(暴击率) | cdmg (暴击伤害)| cdr (冷却缩减) | heal(治疗加成)| heali (受治疗加成)| shield (护盾强效)| defi (无视防御)\n\t(cont.) 元素伤害:epyro (火) | ecryo (冰) | ehydro (水) | egeo (岩) | edendro (草) | eelectro (雷) | ephys (物理)(cont.) 元素抗性:respyro (火) | rescryo (冰) | reshydro (水) | resgeo (岩) | resdendro (草) | reselectro (雷) | resphys (物理)\n", + "value_error": "无效的数据值。", + "uid_error": "无效的UID。", + "player_error": "玩家不存在或已离线。", + "set_self": "%s 已经设置为 %s。", + "set_for_uid": "%s 的使用者 %s 更改为 %s。", + "set_max_hp": "最大生命值更改为 %s。", + "description": "设置当前角色的属性。" + }, + "setWorldLevel": { + "usage": "用法:setworldlevel <level>", + "value_error": "世界等级必须设置在0-8之间。", + "success": "已将世界等级设为%s。", + "invalid_world_level": "无效的世界等级。", + "description": "设置世界等级,执行命令后需重新登录以生效。" + }, + "spawn": { + "usage": "用法:spawn <entityId> [amount] [level(仅限怪物]", + "success": "已生成 %s 个 %s。", + "description": "在你附近生成一个生物。" + }, + "stop": { + "success": "正在关闭服务器...", + "description": "停止服务器" + }, + "talent": { + "usage_1": "设置天赋等级:/talent set <talentID> <value>", + "usage_2": "另一种设置天赋等级的命令使用方法:/talent <n or e or q> <value>", + "usage_3": "获取天赋ID指令用法:/talent getid", + "lower_16": "无效的天赋等级,天赋等级应小于等于15。", + "set_id": "将天赋等级设为 %s。", + "set_atk": "将普通攻击等级设为 %s。", + "set_e": "设定元素战技等级为 %s。", + "set_q": "设定元素爆发等级为 %s。", + "invalid_skill_id": "无效的技能ID。", + "set_this": "将天赋等级设为 %s。", + "invalid_level": "无效的天赋等级。", + "normal_attack_id": "普通攻击的 ID 为 %s。", + "e_skill_id": "元素战技ID %s。", + "q_skill_id": "元素爆发ID %s。", + "description": "设置当前角色的天赋等级。" + }, + "teleportAll": { + "success": "已将全部玩家传送到你的位置", + "error": "命令仅限处于多人游戏状态下使用。", + "description": "将你世界中的所有玩家传送到你所在的位置。" + }, + "teleport": { + "usage_server": "用法:/tp @<player id> <x> <y> <z> [scene id]", + "usage": "用法:/tp [@<player id>] <x> <y> <z> [scene id]", + "specify_player_id": "你必须指定一个玩家ID。", + "invalid_position": "无效的位置。", + "success": "传送 %s 到坐标 %s,%s,%s,场景为 %s", + "description": "改变指定玩家的位置。" + }, + "weather": { + "usage": "用法:weather <weatherId> [climateId]", + "success": "已将当前天气设定为 %s,气候为 %s。", + "invalid_id": "无效的天气ID。", + "description": "改变天气" + }, + "drop": { + "command_usage": "用法:drop <itemId|itemName> [amount]", + "success": "已将 %s x %s 丟在附近。", + "description": "在你附近丢一个物品。" + }, + "help": { + "usage": "用法:", + "aliases": "別名:", + "available_commands": "可用指令:", + "description": "发送帮助信息或显示指定命令的信息。" + }, + "restart": { + "description": "重新启动服务器。" + } + } +} diff --git a/src/main/resources/languages/zh-TW.json b/src/main/resources/languages/zh-TW.json new file mode 100644 index 000000000..7a7f5c100 --- /dev/null +++ b/src/main/resources/languages/zh-TW.json @@ -0,0 +1,298 @@ +{ + "messages": { + "game": { + "port_bind": "遊戲伺服器已成功啟動。端口號:%s", + "connect": "客戶端已連接至 %s", + "disconnect": "客戶端 %s 已斷開連接。", + "game_update_error": "遊戲更新時發生了錯誤。", + "command_error": "指令發生錯誤:" + }, + "dispatch": { + "port_bind": "[Dispatch] 伺服器已在端口 %s 上開啟。", + "request": "[Dispatch] 客戶端 %s 請求: %s %s", + "keystore": { + "general_error": "[Dispatch] 加載keystore文件時發生錯誤!", + "password_error": "[Dispatch] 加載 keystore 失敗。正在嘗試使用預設 keystore 密碼...", + "no_keystore_error": "[Dispatch] 未找到 SSL 憑證!已後降到 HTTP 伺服器。", + "default_password": "[Dispatch] 默認的 keystore 密碼加載成功。請考慮將 config.json 的憑證密碼設定成 123456。" + }, + "no_commands_error": "此指令不適用於Dispatch-only模式。", + "unhandled_request_error": "[Dispatch] 潛在的未處理請求 %s 請求:%s", + "account": { + "login_attempt": "[Dispatch] 客戶端 %s 正在嘗試登入", + "login_success": "[Dispatch] 客戶端 %s 已登入,UID為 %s", + "login_token_attempt": "[Dispatch] 客戶端 %s 正在嘗試用憑證登入", + "login_token_error": "[Dispatch] 客戶端 %s 使用憑證登入失敗", + "login_token_success": "[Dispatch] 客戶端 %s 已透過憑證登入,UID為 %s", + "combo_token_success": "[Dispatch] 客戶端 %s 交換憑證成功", + "combo_token_error": "[Dispatch] 客戶端 %s 交換憑證失敗", + "account_login_create_success": "[Dispatch] 客戶端 %s 登入失敗: 已註冊UID為 %s 的帳號", + "account_login_create_error": "[Dispatch] 客戶端 %s 登入失敗:帳號建立失敗。", + "account_login_exist_error": "[Dispatch] 客戶端 %s 登入失敗: 帳號不存在", + "account_cache_error": "遊戲帳號緩存資訊錯誤", + "session_key_error": "對話密鑰不符。", + "username_error": "未找到此用戶名。", + "username_create_error": "未找到用戶名,建立失敗。" + } + }, + "status": { + "free_software": "Grasscutter 是免費開源軟體。如果你已經付錢了,那你可能被騙了。主頁:https://github.com/Grasscutters/Grasscutter", + "starting": "正在啟動 Grasscutter...", + "shutdown": "正在關閉...", + "done": "加載完成!需要指令幫助請輸入 \"help\"", + "error": "發生了一個錯誤。", + "welcome": "歡迎使用 Grasscutter", + "run_mode_error": "無效的伺服器運行模式: %s。", + "run_mode_help": "伺服器運行模式必須為 HYBRID 或者 DISPATCH_ONLY 或者 GAME_ONLY。Grasscutter 啟動失敗...", + "create_resources": "正在建立 resources 資料夾...", + "resources_error": "請將 BinOutput 和 ExcelBinOutput 複製到 resources 資料夾。" + } + }, + "commands": { + "generic": { + "not_specified": "沒有指定指令。", + "unknown_command": "未知的指令:%s", + "permission_error": "您沒有執行此指令的權限。", + "console_execute_error": "此指令只能在伺服器的命令提示字元執行。", + "player_execute_error": "請在遊戲裡使用這條指令。", + "command_exist_error": "找不到指令。", + "invalid": { + "amount": "無效的數量。", + "artifactId": "無效的聖遺物ID。", + "avatarId": "無效的角色ID。", + "avatarLevel": "無效的角色等級。", + "entityId": "無效的實體ID。", + "itemId": "無效的物品ID。", + "itemLevel": "無效的物品等級。", + "itemRefinement": "無效的物品精煉度。", + "playerId": "無效的玩家ID。", + "uid": "無效的UID。" + } + }, + "execution": { + "uid_error": "無效的UID。", + "player_exist_error": "用戶不存在。", + "player_offline_error": "玩家已離線。", + "item_id_error": "無效的物品ID。.", + "item_player_exist_error": "無效的物品/玩家UID。", + "entity_id_error": "無效的實體ID。", + "player_exist_offline_error": "玩家不存在或已離線。", + "argument_error": "無效的參數。", + "clear_target": "目標已清除.", + "set_target": "隨後的指令都會以@%s為預設。", + "need_target": "此指令需要一個目標 UID。添加 <@UID> 引數或者使用 /target @UID 來設定持久目標。" + }, + "status": { + "enabled": "已啟用", + "disabled": "未啟用", + "help": "幫助", + "success": "成功" + }, + "account": { + "modify": "修改使用者帳號", + "invalid": "無效的UID。", + "exists": "帳號已存在。", + "create": "已建立帳號,UID 為 %s 。", + "delete": "帳號已刪除。", + "no_account": "帳號不存在。", + "command_usage": "用法:account <create|delete> <username> [uid]" + }, + "broadcast": { + "command_usage": "用法:broadcast <message>", + "message_sent": "公告已發送。" + }, + "changescene": { + "usage": "用法:changescene <scene id>", + "already_in_scene": "你已經在這個場景中了。", + "success": "已切換至場景 %s.", + "exists_error": "此場景不存在。" + }, + "clear": { + "command_usage": "用法: clear <all|wp|art|mat>", + "weapons": "已將 %s 的武器清空。", + "artifacts": "已將 %s 的聖遺物清空。", + "materials": "已將 %s 的材料清空。", + "furniture": "已將 %s 的塵歌壺家具清空。", + "displays": "已清除 %s 的顯示。", + "virtuals": "已將 %s 的所有貨幣和經驗值清空。", + "everything": "已將 %s 的所有物品清空。" + }, + "coop": { + "usage": "用法:coop <playerId> <target playerId>", + "success": "召喚了 %s 到 %s 的世界。" + }, + "enter_dungeon": { + "usage": "用法:enterdungeon <dungeon id>", + "changed": "已進入副本 %s", + "not_found_error": "此副本不存在。", + "in_dungeon_error": "你已經在祕境中了。" + }, + "giveAll": { + "usage": "用法:giveall [player] [amount]", + "started": "正在賦予全部物品...", + "success": "已賦予全部物品。", + "invalid_amount_or_playerId": "無效的數量/玩家ID。" + }, + "giveArtifact": { + "usage": "用法:giveart|gart [player] <artifactId> <mainPropId> [<appendPropId>[,<times>]]... [level]", + "id_error": "無效的聖遺物ID。", + "success": "已把 %s 給予 %s。" + }, + "giveChar": { + "usage": "用法:givechar <player> <itemId|itemName> [amount]", + "given": "已將 %s 等級 %s 給予 %s。", + "invalid_avatar_id": "無效的角色ID。", + "invalid_avatar_level": "無效的角色等級。.", + "invalid_avatar_or_player_id": "無效的角色ID/玩家ID。" + }, + "give": { + "usage": "用法:give <player> <itemId|itemName> [amount] [level]", + "refinement_only_applicable_weapons": "精煉度只能施加在武器上面。", + "refinement_must_between_1_and_5": "精煉度必需在 1 到 5 之間。", + "given": "已經將 %s 個 %s 給予 %s。", + "given_with_level_and_refinement": "已將 %s [等級%s, 精煉%s] %s個給予 %s", + "given_level": "已將 %s 等級 %s %s 個給予 %s" + }, + "godmode": { + "success": "上帝模式設定為 %s 。 [用戶:%s]" + }, + "heal": { + "success": "所有角色已被治療。" + }, + "kick": { + "player_kick_player": "玩家 [%s:%s] 已把 [%s:%s] 踢出", + "server_kick_player": "正在踢出玩家 [%s:%s]" + }, + "kill": { + "usage": "用法:killall [playerUid] [sceneId]", + "scene_not_found_in_player_world": "未在玩家世界中找到此場景", + "kill_monsters_in_scene": "已殺死 %s 個怪物。 [場景ID: %s]" + }, + "killCharacter": { + "usage": "用法:/killcharacter [playerId]", + "success": "已殺死 %s 目前的場上角色。" + }, + "list": { + "success": "目前總線上人數:%s" + }, + "permission": { + "usage": "用法:permission <add|remove> <username> <permission>", + "add": "已指派權限。", + "has_error": "此玩家已擁有權限!", + "remove": "權限已移除。", + "not_have_error": "此玩家未擁有權限!", + "account_error": "The account cannot be found." + }, + "position": { + "success": "坐標:%.3f, %.3f, %.3f\n場景ID:%d" + }, + "reload": { + "reload_start": "正在重新加載設定檔。", + "reload_done": "重新加載已完成。" + }, + "resetConst": { + "reset_all": "重設所有角色的命座。", + "success": "已重設 %s 的命座,重新登入後將會生效。" + }, + "resetShopLimit": { + "usage": "用法:/resetshop <player id>" + }, + "sendMail": { + "usage": "用法:give [player] <itemId|itemName> [amount]", + "user_not_exist": "ID '%s' 的使用者不存在。", + "start_composition": "發送郵件流程。\n請使用`/send <郵件標題>`來進到下一步。\n你可以在任何時間使用`/sendmail stop`來停止發送。", + "templates": "郵件模板尚未實裝...", + "invalid_arguments": "無效的參數。\n指令使用方法 `/sendmail <userId|all|help> [templateId]`", + "send_cancel": "取消傳送信息", + "send_done": "已將消息發送給 %s!", + "send_all_done": "消息已發送給全體用戶!", + "not_composition_end": "現在郵件發送未到最後階段。\n請使用 `/sendmail %s` 繼續發送郵件,或者 `/sendmail stop` 來停止發送郵件。", + "please_use": "請使用 `/sendmail %s`", + "set_title": "成功將郵件標題設定成 '%s'。\n接下來請繼續使用 '/sendmail <content>' 來設定郵件內容。", + "set_contents": "成功將'%s'為郵件內容。\n接下來請打出 '/sendmail <寄件者名稱>' 來設定郵件寄件者名稱。", + "set_message_sender": "郵件寄件者已設為 '%s'。\n使用 '/sendmail <itemId|itemName|finish> [amount] [level]' 以繼續操作。", + "send": "已添加 %s 個 %s (等級為 %s) 到郵件附件。\n如果沒有要繼續添加道具請使用 `/sendmail finish` 來完成郵件發送。", + "invalid_arguments_please_use": "Invalid arguments \n Please use `/sendmail %s`", + "title": "<標題>", + "message": "<正文>", + "sender": "<寄件者>", + "arguments": "<itemId|itemName|finish> [數量] [等級]", + "error": "錯誤:無效的編寫階段 %s。需要 stacktrace 請查看伺服器命令提示字元。" + }, + "sendMessage": { + "usage": "用法:sendmessage <player> <message>", + "success": "訊息已發送。" + }, + "setFetterLevel": { + "usage": "用法:setfetterlevel <level>", + "range_error": "好感度必須在 0 到 10 之間。", + "fetter_set_level": "好感等級已設定為 %s", + "level_error": "無效的好感度。" + }, + "setStats": { + "usage_console": "用法:setstats|stats @<UID> <stat> <value>", + "usage_ingame": "用法:setstats|stats [@UID] <stat> <value>", + "help_message": "\n\t可使用的數據類型:hp (生命值)| maxhp (最大生命值) | def(防禦力) | atk (攻擊力)| em (元素精通) | er (元素充能效率) | crate(暴擊率) | cdmg (暴擊傷害)| cdr (冷卻縮減) | heal(治療加成)| heali (受治療加成)| shield (護盾強效)| defi (無視防禦)\n\t(cont.) 元素增傷類:epyro (火傷) | ecryo (冰傷) | ehydro (水傷) | egeo (岩傷) | edendro (草傷) | eelectro (雷傷) | ephys (物傷)(cont.) 元素減傷類:respyro (火抗) | rescryo (冰抗) | reshydro (水抗) | resgeo (岩抗) | resdendro (草抗) | reselectro (雷抗) | resphys (物抗)\n", + "value_error": "無效的數據值。", + "uid_error": "無效的UID。", + "player_error": "玩家不存在或已離線。", + "set_self": "%s 已經設為 %s。", + "set_for_uid": "%s 的使用者 %s 更改為 %s。", + "set_max_hp": "最大生命值更改為 %s。" + }, + "setWorldLevel": { + "usage": "用法:setworldlevel <level>", + "value_error": "世界等級必須設定在0-8之間。", + "success": "已將世界等級設為%s。", + "invalid_world_level": "無效的世界等級。" + }, + "spawn": { + "usage": "用法:spawn <entityId> [amount] [level(僅限怪物)]", + "success": "已生成 %s 個 %s。" + }, + "stop": { + "success": "正在關閉伺服器..." + }, + "talent": { + "usage_1": "設定天賦等級:/talent set <talentID> <value>", + "usage_2": "另一種設定天賦等級的指令使用方法:/talent <n or e or q> <value>", + "usage_3": "獲取天賦ID指令用法:/talent getid", + "lower_16": "無效的技能等級,技能等級應低於 16。", + "set_id": "將天賦等級設為%s。", + "set_atk": "將普通攻擊等級設為 %s。", + "set_e": "設定天賦E等級至 %s。", + "set_q": "設定天賦Q等級至 %s。", + "invalid_skill_id": "無效的技能ID。", + "set_this": "將天賦等級設為 %s。", + "invalid_level": "無效的天賦等級。", + "normal_attack_id": "普通攻擊的 ID 為 %s。", + "e_skill_id": "E技能ID %s。", + "q_skill_id": "Q技能ID %s。" + }, + "teleportAll": { + "success": "召喚了所有玩家到你的位置上。", + "error": "此指令僅可在多人遊戲下可用。" + }, + "teleport": { + "usage_server": "用法:/tp @<player id> <x> <y> <z> [scene id]", + "usage": "用法:/tp [@<player id>] <x> <y> <z> [scene id]", + "specify_player_id": "你必須指定一個玩家ID。", + "invalid_position": "無效的位置。", + "success": "傳送 %s 到坐標 %s,%s,%s ,場景為 %s" + }, + "weather": { + "usage": "用法:weather <weatherId> [climateId]", + "success": "已將當前天氣設定為 %s ,氣候則為 %s 。", + "invalid_id": "無效的ID。" + }, + "drop": { + "command_usage": "用法:drop <itemId|itemName> [amount]", + "success": "已將 %s x %s 丟在附近。" + }, + "help": { + "usage": "用法:", + "aliases": "別名:", + "available_commands": "可用指令:" + } + } +} diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 91d3f133c..bd0740fca 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -1,4 +1,6 @@ <Configuration> + <variable name="LOG_LEVEL" value="${LOG_LEVEL:-INFO}" /> + <appender name="STDOUT" class="emu.grasscutter.utils.JlineLogbackAppender"> <encoder> <pattern>[%d{HH:mm:ss}] [%highlight(%level)] %msg%n</pattern> @@ -14,7 +16,10 @@ <pattern>%d{yyyy-MM-dd'T'HH:mm:ss'Z'} - %m%n</pattern> </encoder> </appender> - <logger name="org.reflections" level="OFF"/> + + <logger name="org.reflections" level="OFF" /> + <logger name="emu.grasscutter" level="${LOG_LEVEL}" /> + <root level="INFO"> <appender-ref ref="STDOUT" /> <appender-ref ref="FILE" />