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/build.gradle b/build.gradle index 2f19e9c99..dd8fa9fa0 100644 --- a/build.gradle +++ b/build.gradle @@ -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/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/proto/WorldPlayerReviveReq.proto b/proto/WorldPlayerReviveReq.proto index edaeab683..d9ea86c1c 100644 --- a/proto/WorldPlayerReviveReq.proto +++ b/proto/WorldPlayerReviveReq.proto @@ -1,6 +1,6 @@ syntax = "proto3"; -option csharp_namespace = "YSFreedom.Common.Protocol"; +option java_package = "emu.grasscutter.net.proto"; message WorldPlayerReviveReq { enum CmdId { diff --git a/src/main/java/emu/grasscutter/Config.java b/src/main/java/emu/grasscutter/Config.java index 1111ba920..3982fc46b 100644 --- a/src/main/java/emu/grasscutter/Config.java +++ b/src/main/java/emu/grasscutter/Config.java @@ -23,7 +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.US; + public Locale DefaultLanguage = Locale.ENGLISH; public Boolean OpenStamina = true; public GameServerOptions getGameServerOptions() { diff --git a/src/main/java/emu/grasscutter/Grasscutter.java b/src/main/java/emu/grasscutter/Grasscutter.java index 51bc3fcc5..c593f5f13 100644 --- a/src/main/java/emu/grasscutter/Grasscutter.java +++ b/src/main/java/emu/grasscutter/Grasscutter.java @@ -149,9 +149,10 @@ public final class Grasscutter { public static void loadLanguage() { var locale = config.LocaleLanguage; - String languageTag = locale.toLanguageTag(); + var languageTag = locale.toLanguageTag(); + if (languageTag.equals("und")) { - Grasscutter.getLogger().error("Illegal locale language, using en-US instead."); + Grasscutter.getLogger().error("Illegal locale language, using 'en-US' instead."); language = Language.getLanguage("en-US"); } else { language = Language.getLanguage(languageTag); 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 07deb84fd..a183c6ac3 100644 --- a/src/main/java/emu/grasscutter/command/CommandMap.java +++ b/src/main/java/emu/grasscutter/command/CommandMap.java @@ -79,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. * diff --git a/src/main/java/emu/grasscutter/command/commands/AccountCommand.java b/src/main/java/emu/grasscutter/command/commands/AccountCommand.java index 627f4680f..4b287afa7 100644 --- a/src/main/java/emu/grasscutter/command/commands/AccountCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/AccountCommand.java @@ -9,7 +9,7 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "account", usage = "account [uid]", description = "Modify user accounts") +@Command(label = "account", usage = "account [uid]", description = "commands.account.description") public final class AccountCommand implements CommandHandler { @Override diff --git a/src/main/java/emu/grasscutter/command/commands/BroadcastCommand.java b/src/main/java/emu/grasscutter/command/commands/BroadcastCommand.java index 1aa234919..95f0c7c05 100644 --- a/src/main/java/emu/grasscutter/command/commands/BroadcastCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/BroadcastCommand.java @@ -9,8 +9,7 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "broadcast", usage = "broadcast ", - description = "Sends a message to all the players", aliases = {"b"}, permission = "server.broadcast") +@Command(label = "broadcast", usage = "broadcast ", aliases = {"b"}, permission = "server.broadcast", description = "commands.broadcast.description") public final class BroadcastCommand implements CommandHandler { @Override diff --git a/src/main/java/emu/grasscutter/command/commands/ChangeSceneCommand.java b/src/main/java/emu/grasscutter/command/commands/ChangeSceneCommand.java index 1a4e97927..594eb27c6 100644 --- a/src/main/java/emu/grasscutter/command/commands/ChangeSceneCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/ChangeSceneCommand.java @@ -8,9 +8,9 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "changescene", usage = "changescene ", - description = "Changes your scene", aliases = {"scene"}, permission = "player.changescene") +@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) { diff --git a/src/main/java/emu/grasscutter/command/commands/ClearCommand.java b/src/main/java/emu/grasscutter/command/commands/ClearCommand.java index 47d9f2c0d..38f78e638 100644 --- a/src/main/java/emu/grasscutter/command/commands/ClearCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/ClearCommand.java @@ -13,7 +13,7 @@ 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 { diff --git a/src/main/java/emu/grasscutter/command/commands/CoopCommand.java b/src/main/java/emu/grasscutter/command/commands/CoopCommand.java index 96411019b..cf7d4fc82 100644 --- a/src/main/java/emu/grasscutter/command/commands/CoopCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/CoopCommand.java @@ -9,9 +9,9 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "coop", usage = "coop [host UID]", - description = "Forces someone to join the world of others", permission = "server.coop") +@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) { diff --git a/src/main/java/emu/grasscutter/command/commands/DropCommand.java b/src/main/java/emu/grasscutter/command/commands/DropCommand.java index a33a32603..10c306cad 100644 --- a/src/main/java/emu/grasscutter/command/commands/DropCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/DropCommand.java @@ -13,8 +13,7 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "drop", usage = "drop [amount]", - description = "Drops an item near you", aliases = {"d", "dropitem"}, permission = "server.drop") +@Command(label = "drop", usage = "drop [amount]", aliases = {"d", "dropitem"}, permission = "server.drop", description = "commands.drop.description") public final class DropCommand implements CommandHandler { @Override diff --git a/src/main/java/emu/grasscutter/command/commands/EnterDungeonCommand.java b/src/main/java/emu/grasscutter/command/commands/EnterDungeonCommand.java index 434e80c8f..8534c034f 100644 --- a/src/main/java/emu/grasscutter/command/commands/EnterDungeonCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/EnterDungeonCommand.java @@ -8,9 +8,9 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "enterdungeon", usage = "enterdungeon ", - description = "Enter a dungeon", aliases = {"dungeon"}, permission = "player.enterdungeon") +@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) { diff --git a/src/main/java/emu/grasscutter/command/commands/GiveAllCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveAllCommand.java index bb11de3c2..c94d67129 100644 --- a/src/main/java/emu/grasscutter/command/commands/GiveAllCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/GiveAllCommand.java @@ -15,8 +15,7 @@ import java.util.*; import static emu.grasscutter.utils.Language.translate; -@Command(label = "giveall", usage = "giveall [amount]", - description = "Gives all items", aliases = {"givea"}, permission = "player.giveall", threading = true) +@Command(label = "giveall", usage = "giveall [amount]", aliases = {"givea"}, permission = "player.giveall", threading = true, description = "commands.giveAll.description") public final class GiveAllCommand implements CommandHandler { @Override diff --git a/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java index 541cc440e..b87642bb2 100644 --- a/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java @@ -16,8 +16,9 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "giveart", usage = "giveart [[,]]... [level]", description = "Gives the player a specified artifact", aliases = {"gart"}, permission = "player.giveart") +@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) { diff --git a/src/main/java/emu/grasscutter/command/commands/GiveCharCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveCharCommand.java index 87c2d61e2..5c6bad0d2 100644 --- a/src/main/java/emu/grasscutter/command/commands/GiveCharCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/GiveCharCommand.java @@ -12,8 +12,7 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "givechar", usage = "givechar [level]", - description = "Gives the player a specified character", aliases = {"givec"}, permission = "player.givechar") +@Command(label = "givechar", usage = "givechar [level]", aliases = {"givec"}, permission = "player.givechar", description = "commands.giveChar.description") public final class GiveCharCommand implements CommandHandler { @Override @@ -77,6 +76,6 @@ public final class GiveCharCommand implements CommandHandler { avatar.recalcStats(); targetPlayer.addAvatar(avatar); - CommandHandler.sendMessage(sender, translate("commands.execution.giveChar.given", Integer.toString(avatarId), Integer.toString(level), 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 2f020f7b3..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,13 +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; import static emu.grasscutter.utils.Language.translate; -@Command(label = "give", usage = "give [amount] [level]", description = "Gives an item to you or the specified player", aliases = { - "g", "item", "giveitem"}, permission = "player.give") +@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+)"); diff --git a/src/main/java/emu/grasscutter/command/commands/GodModeCommand.java b/src/main/java/emu/grasscutter/command/commands/GodModeCommand.java index 9abebb8db..bf2a00c9f 100644 --- a/src/main/java/emu/grasscutter/command/commands/GodModeCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/GodModeCommand.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; @@ -9,8 +8,7 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "godmode", usage = "godmode [on|off|toggle]", - description = "Prevents you from taking damage. Defaults to toggle.", permission = "player.godmode") +@Command(label = "godmode", usage = "godmode [on|off|toggle]", permission = "player.godmode", description = "commands.godmode.description") public final class GodModeCommand implements CommandHandler { @Override diff --git a/src/main/java/emu/grasscutter/command/commands/HealCommand.java b/src/main/java/emu/grasscutter/command/commands/HealCommand.java index bb0b861b0..440db0a49 100644 --- a/src/main/java/emu/grasscutter/command/commands/HealCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/HealCommand.java @@ -11,9 +11,9 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "heal", usage = "heal|h", aliases = {"h"}, - description = "Heal all characters in your current team.", permission = "player.heal") +@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) { diff --git a/src/main/java/emu/grasscutter/command/commands/HelpCommand.java b/src/main/java/emu/grasscutter/command/commands/HelpCommand.java index 93ac831b3..8a222f7a6 100644 --- a/src/main/java/emu/grasscutter/command/commands/HelpCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/HelpCommand.java @@ -10,8 +10,7 @@ import java.util.*; import static emu.grasscutter.utils.Language.translate; -@Command(label = "help", usage = "help [command]", - description = "Sends the help message or shows information about a specified command") +@Command(label = "help", usage = "help [command]", description = "commands.help.description") public final class HelpCommand implements CommandHandler { @Override @@ -39,7 +38,7 @@ public final class HelpCommand implements CommandHandler { } else { Command annotation = handler.getClass().getAnnotation(Command.class); - builder.append(" ").append(annotation.description()).append("\n"); + 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(translate("commands.help.aliases")); @@ -61,7 +60,7 @@ public final class HelpCommand implements CommandHandler { 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(" ").append(translate(annotation.description())).append("\n"); builder.append(translate("commands.help.usage")).append(annotation.usage()); if (annotation.aliases().length >= 1) { builder.append("\n").append(translate("commands.help.aliases")); @@ -78,7 +77,7 @@ public final class HelpCommand implements CommandHandler { 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(" ").append(translate(annotation.description())).append("\n"); builder.append(translate("commands.help.usage")).append(annotation.usage()); if (annotation.aliases().length >= 1) { builder.append("\n").append(translate("commands.help.aliases")); diff --git a/src/main/java/emu/grasscutter/command/commands/KickCommand.java b/src/main/java/emu/grasscutter/command/commands/KickCommand.java index 270e28150..9741226e7 100644 --- a/src/main/java/emu/grasscutter/command/commands/KickCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/KickCommand.java @@ -8,8 +8,7 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "kick", usage = "kick", - description = "Kicks the specified player from the server (WIP)", permission = "server.kick") +@Command(label = "kick", usage = "kick", permission = "server.kick", description = "commands.kick.description") public final class KickCommand implements CommandHandler { @Override diff --git a/src/main/java/emu/grasscutter/command/commands/KillAllCommand.java b/src/main/java/emu/grasscutter/command/commands/KillAllCommand.java index 423c60bbd..da9ac7b5e 100644 --- a/src/main/java/emu/grasscutter/command/commands/KillAllCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/KillAllCommand.java @@ -12,8 +12,7 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "killall", usage = "killall [sceneId]", - description = "Kill all entities", permission = "server.killall") +@Command(label = "killall", usage = "killall [sceneId]", permission = "server.killall", description = "commands.kill.description") public final class KillAllCommand implements CommandHandler { @Override diff --git a/src/main/java/emu/grasscutter/command/commands/KillCharacterCommand.java b/src/main/java/emu/grasscutter/command/commands/KillCharacterCommand.java index f1e0f0f8c..3eda6f7e7 100644 --- a/src/main/java/emu/grasscutter/command/commands/KillCharacterCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/KillCharacterCommand.java @@ -13,8 +13,7 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "killcharacter", usage = "killcharacter", aliases = {"suicide", "kill"}, - description = "Kills the players current character", permission = "player.killcharacter") +@Command(label = "killcharacter", usage = "killcharacter", aliases = {"suicide", "kill"}, permission = "player.killcharacter", description = "commands.list.description") public final class KillCharacterCommand implements CommandHandler { @Override diff --git a/src/main/java/emu/grasscutter/command/commands/ListCommand.java b/src/main/java/emu/grasscutter/command/commands/ListCommand.java index bc35e65e1..53a274e52 100644 --- a/src/main/java/emu/grasscutter/command/commands/ListCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/ListCommand.java @@ -10,8 +10,7 @@ import java.util.Map; import static emu.grasscutter.utils.Language.translate; -@Command(label = "list", usage = "list [uid]", - description = "List online players", aliases = {"players"}) +@Command(label = "list", usage = "list [uid]", aliases = {"players"}, description = "commands.list.description") public final class ListCommand implements CommandHandler { @Override diff --git a/src/main/java/emu/grasscutter/command/commands/PermissionCommand.java b/src/main/java/emu/grasscutter/command/commands/PermissionCommand.java index 69c8ce899..4b945b3d1 100644 --- a/src/main/java/emu/grasscutter/command/commands/PermissionCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/PermissionCommand.java @@ -10,8 +10,7 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "permission", usage = "permission ", - description = "Grants or removes a permission for a user", permission = "*") +@Command(label = "permission", usage = "permission ", permission = "*", description = "commands.permission.description") public final class PermissionCommand implements CommandHandler { @Override diff --git a/src/main/java/emu/grasscutter/command/commands/PositionCommand.java b/src/main/java/emu/grasscutter/command/commands/PositionCommand.java index 7f6548c5b..b5a250af6 100644 --- a/src/main/java/emu/grasscutter/command/commands/PositionCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/PositionCommand.java @@ -9,8 +9,7 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "position", usage = "position", aliases = {"pos"}, - description = "Get coordinates.") +@Command(label = "position", usage = "position", aliases = {"pos"}, description = "commands.position.description") public final class PositionCommand implements CommandHandler { @Override diff --git a/src/main/java/emu/grasscutter/command/commands/ReloadCommand.java b/src/main/java/emu/grasscutter/command/commands/ReloadCommand.java index 6c85d2024..29eb93d0d 100644 --- a/src/main/java/emu/grasscutter/command/commands/ReloadCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/ReloadCommand.java @@ -9,8 +9,7 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "reload", usage = "reload", - description = "Reload server config", permission = "server.reload") +@Command(label = "reload", usage = "reload", permission = "server.reload", description = "commands.reload.description") public final class ReloadCommand implements CommandHandler { @Override diff --git a/src/main/java/emu/grasscutter/command/commands/ResetConstCommand.java b/src/main/java/emu/grasscutter/command/commands/ResetConstCommand.java index 706fb95e0..3a77cee4d 100644 --- a/src/main/java/emu/grasscutter/command/commands/ResetConstCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/ResetConstCommand.java @@ -11,8 +11,7 @@ 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 diff --git a/src/main/java/emu/grasscutter/command/commands/ResetShopLimitCommand.java b/src/main/java/emu/grasscutter/command/commands/ResetShopLimitCommand.java index aeae0abbf..7aa84ff6a 100644 --- a/src/main/java/emu/grasscutter/command/commands/ResetShopLimitCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/ResetShopLimitCommand.java @@ -9,9 +9,9 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "resetshop", usage = "resetshop", - description = "Reset target player's shop refresh time.", permission = "server.resetshop") +@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) { 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 838bea567..69aafa20b 100644 --- a/src/main/java/emu/grasscutter/command/commands/SendMailCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SendMailCommand.java @@ -13,8 +13,7 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; @SuppressWarnings("ConstantConditions") -@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") +@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 @@ -40,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()); diff --git a/src/main/java/emu/grasscutter/command/commands/SendMessageCommand.java b/src/main/java/emu/grasscutter/command/commands/SendMessageCommand.java index acf63dea0..18d6264db 100644 --- a/src/main/java/emu/grasscutter/command/commands/SendMessageCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SendMessageCommand.java @@ -8,8 +8,8 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "say", usage = "say ", description = "Sends a message to a player as the server", - aliases = {"sendservmsg", "sendservermessage", "sendmessage"}, permission = "server.sendmessage") +@Command(label = "say", usage = "say ", + aliases = {"sendservmsg", "sendservermessage", "sendmessage"}, permission = "server.sendmessage", description = "commands.sendMessage.description") public final class SendMessageCommand implements CommandHandler { @Override diff --git a/src/main/java/emu/grasscutter/command/commands/SetFetterLevelCommand.java b/src/main/java/emu/grasscutter/command/commands/SetFetterLevelCommand.java index 7184c679c..ca5a3cb43 100644 --- a/src/main/java/emu/grasscutter/command/commands/SetFetterLevelCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SetFetterLevelCommand.java @@ -12,8 +12,7 @@ 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 diff --git a/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java b/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java index 233eb4d73..c7ed78a58 100644 --- a/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java @@ -15,8 +15,7 @@ import emu.grasscutter.utils.Language; import static emu.grasscutter.utils.Language.translate; -@Command(label = "setstats", usage = "setstats|stats ", - description = "Set fight property for your current active character", aliases = {"stats"}, permission = "player.setstats") +@Command(label = "setstats", usage = "setstats|stats ", aliases = {"stats"}, permission = "player.setstats", description = "commands.setStats.description") public final class SetStatsCommand implements CommandHandler { static class Stat { String name; diff --git a/src/main/java/emu/grasscutter/command/commands/SetWorldLevelCommand.java b/src/main/java/emu/grasscutter/command/commands/SetWorldLevelCommand.java index 914d8cecc..41b959336 100644 --- a/src/main/java/emu/grasscutter/command/commands/SetWorldLevelCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SetWorldLevelCommand.java @@ -10,8 +10,7 @@ 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 diff --git a/src/main/java/emu/grasscutter/command/commands/SpawnCommand.java b/src/main/java/emu/grasscutter/command/commands/SpawnCommand.java index c66a45b50..7f0c704c6 100644 --- a/src/main/java/emu/grasscutter/command/commands/SpawnCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SpawnCommand.java @@ -22,8 +22,7 @@ import java.util.Random; import static emu.grasscutter.utils.Language.translate; -@Command(label = "spawn", usage = "spawn [amount] [level(monster only)]", - description = "Spawns an entity near you", permission = "server.spawn") +@Command(label = "spawn", usage = "spawn [amount] [level(monster only)]", permission = "server.spawn", description = "commands.spawn.description") public final class SpawnCommand implements CommandHandler { @Override diff --git a/src/main/java/emu/grasscutter/command/commands/StopCommand.java b/src/main/java/emu/grasscutter/command/commands/StopCommand.java index ad4903107..129b27b24 100644 --- a/src/main/java/emu/grasscutter/command/commands/StopCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/StopCommand.java @@ -9,8 +9,7 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "stop", usage = "stop", - description = "Stops the server", permission = "server.stop") +@Command(label = "stop", usage = "stop", permission = "server.stop", description = "commands.stop.description") public final class StopCommand implements CommandHandler { @Override diff --git a/src/main/java/emu/grasscutter/command/commands/TalentCommand.java b/src/main/java/emu/grasscutter/command/commands/TalentCommand.java index c9b2e8931..1540a81f8 100644 --- a/src/main/java/emu/grasscutter/command/commands/TalentCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/TalentCommand.java @@ -14,8 +14,7 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "talent", usage = "talent ", - description = "Set talent level for your current active character", permission = "player.settalent") +@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); diff --git a/src/main/java/emu/grasscutter/command/commands/TeleportAllCommand.java b/src/main/java/emu/grasscutter/command/commands/TeleportAllCommand.java index 54c6101f7..175f69b81 100644 --- a/src/main/java/emu/grasscutter/command/commands/TeleportAllCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/TeleportAllCommand.java @@ -10,9 +10,9 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "tpall", usage = "tpall", - description = "Teleports all players in your world to your position", permission = "player.tpall") +@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) { diff --git a/src/main/java/emu/grasscutter/command/commands/TeleportCommand.java b/src/main/java/emu/grasscutter/command/commands/TeleportCommand.java index 06b669a17..0d15b55af 100644 --- a/src/main/java/emu/grasscutter/command/commands/TeleportCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/TeleportCommand.java @@ -10,8 +10,7 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "teleport", usage = "teleport [scene id]", aliases = {"tp"}, - description = "Change the player's position.", permission = "player.teleport") +@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 diff --git a/src/main/java/emu/grasscutter/command/commands/WeatherCommand.java b/src/main/java/emu/grasscutter/command/commands/WeatherCommand.java index df8a6a01f..dd0002790 100644 --- a/src/main/java/emu/grasscutter/command/commands/WeatherCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/WeatherCommand.java @@ -11,8 +11,7 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "weather", usage = "weather [climateId]", - description = "Changes the weather.", aliases = {"w"}, permission = "player.weather") +@Command(label = "weather", usage = "weather [climateId]", aliases = {"w"}, permission = "player.weather", description = "commands.weather.description") public final class WeatherCommand implements CommandHandler { @Override 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 23b45903a..000000000 --- a/src/main/java/emu/grasscutter/game/managers/MovementManager/MovementManager.java +++ /dev/null @@ -1,442 +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 ConsumptionType { - 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), - FIGHT(0), - - // restore - STANDBY(500), - RUN(500), - WALK(500), - STANDBY_MOVE(500), - POWERED_FLY(500); - - public final int amount; - ConsumptionType(int amount) { - this.amount = amount; - } - } - - private 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); - } - } - - 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 long landTimeMillisecond = 0; - private Timer movementManagerTickTimer; - private GameSession cachedSession = null; - private GameEntity cachedEntity = null; - private int staminaRecoverDelay = 0; - private int skillCaster = 0; - private int skillCasting = 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 - ))); - - MotionStatesCategorized.put("FIGHT", new HashSet<>(Arrays.asList( - MotionState.MOTION_FIGHT - ))); - - - } - - 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 + "\t" + (moveInfo.getIsReliable() ? "reliable" : "")); - handleFallOnGround(motionInfo); - } - - public void resetTimer() { - Grasscutter.getLogger().debug("MovementManager ticker stopped"); - 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); - session.send(new PacketPlayerPropNotify(player, PlayerProperty.PROP_CUR_PERSIST_STAMINA)); - 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(); - landTimeMillisecond = System.currentTimeMillis(); - } - if (state == MotionState.MOTION_FALL_ON_GROUND) { - // if not received immediately after MOTION_LAND_SPEED, discard this packet. - // TODO: Test in high latency. - int maxDelay = 200; - if ((System.currentTimeMillis() - landTimeMillisecond) > maxDelay) { - Grasscutter.getLogger().debug("MOTION_FALL_ON_GROUND received after " + maxDelay + "ms, discard."); - return; - } - 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 = new Consumption(ConsumptionType.None); - - // TODO: refactor these conditions. - if (MotionStatesCategorized.get("CLIMB").contains(currentState)) { - consumption = getClimbConsumption(); - } else if (MotionStatesCategorized.get("SWIM").contains((currentState))) { - consumption = getSwimConsumptions(); - } else if (MotionStatesCategorized.get("RUN").contains(currentState)) { - consumption = getRunWalkDashConsumption(); - } else if (MotionStatesCategorized.get("FLY").contains(currentState)) { - consumption = getFlyConsumption(); - } else if (MotionStatesCategorized.get("STANDBY").contains(currentState)) { - consumption = getStandConsumption(); - } else if (MotionStatesCategorized.get("FIGHT").contains(currentState)) { - consumption = getFightConsumption(); - } - - // delay 2 seconds before start recovering - as official server does. - if (cachedSession != null) { - if (consumption.amount < 0) { - staminaRecoverDelay = 0; - } - if (consumption.amount > 0 && consumption.consumptionType != ConsumptionType.POWERED_FLY) { - if (staminaRecoverDelay < 10) { - staminaRecoverDelay++; - consumption = new Consumption(ConsumptionType.None); - } - } - // Grasscutter.getLogger().debug(getCurrentStamina() + "/" + getMaximumStamina() + "\t" + currentState + "\t" + "isMoving: " + isPlayerMoving() + "\t(" + consumption.consumptionType + "," + consumption.amount + ")"); - updateStamina(cachedSession, consumption.amount); - } - - // tick triggered - handleDrowning(); - } - } - - previousState = currentState; - previousCoordinates = new Position(currentCoordinates.getX(), - currentCoordinates.getY(), currentCoordinates.getZ());; - } - } - - private Consumption getClimbConsumption() { - Consumption consumption = new Consumption(ConsumptionType.None); - if (currentState == MotionState.MOTION_CLIMB) { - consumption = new Consumption(ConsumptionType.CLIMBING); - if (previousState != MotionState.MOTION_CLIMB && previousState != MotionState.MOTION_CLIMB_JUMP) { - consumption = new Consumption(ConsumptionType.CLIMB_START); - } - if (!isPlayerMoving()) { - consumption = new Consumption(ConsumptionType.None); - } - } - if (currentState == MotionState.MOTION_CLIMB_JUMP) { - if (previousState != MotionState.MOTION_CLIMB_JUMP) { - consumption = new Consumption(ConsumptionType.CLIMB_JUMP); - } - } - return consumption; - } - - private Consumption getSwimConsumptions() { - 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_START); - if (previousState == MotionState.MOTION_SWIM_DASH) { - consumption = new Consumption(ConsumptionType.SWIM_DASH); - } - } - return consumption; - } - - private Consumption getRunWalkDashConsumption() { - Consumption consumption = new Consumption(ConsumptionType.None); - if (currentState == MotionState.MOTION_DASH_BEFORE_SHAKE) { - consumption = new Consumption(ConsumptionType.DASH); - if (previousState == MotionState.MOTION_DASH_BEFORE_SHAKE) { - // only charge once - consumption = new Consumption(ConsumptionType.SPRINT); - } - } - if (currentState == MotionState.MOTION_DASH) { - consumption = new Consumption(ConsumptionType.SPRINT); - } - if (currentState == MotionState.MOTION_RUN) { - consumption = new Consumption(ConsumptionType.RUN); - } - if (currentState == MotionState.MOTION_WALK) { - consumption = new Consumption(ConsumptionType.WALK); - } - return consumption; - } - - private Consumption getFlyConsumption() { - Consumption consumption = new Consumption(ConsumptionType.FLY); - 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)) { - reduction = glidingCostReduction.get(skillId); - } - } - } - consumption.amount *= reduction; - - // POWERED_FLY, e.g. wind tunnel - if (currentState == MotionState.MOTION_POWERED_FLY) { - consumption = new Consumption(ConsumptionType.POWERED_FLY); - } - return consumption; - } - - private Consumption getStandConsumption() { - 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; - } - - private Consumption getFightConsumption() { - 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)); - // only handle once, so reset. - skillCasting = 0; - skillCaster = 0; - } - return consumption; - } - - public void notifySkill(int caster, int skillId) { - skillCaster = caster; - skillCasting = skillId; - } -} - 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 95% rename from src/main/java/emu/grasscutter/game/managers/SotSManager/SotSManager.java rename to src/main/java/emu/grasscutter/game/managers/SotSManager.java index 0bfdf9454..ed67c6a62 100644 --- a/src/main/java/emu/grasscutter/game/managers/SotSManager/SotSManager.java +++ b/src/main/java/emu/grasscutter/game/managers/SotSManager.java @@ -1,14 +1,11 @@ -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.entity.GameEntity; -import emu.grasscutter.game.managers.MovementManager.MovementManager; import emu.grasscutter.game.player.Player; import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.PlayerProperty; -import emu.grasscutter.game.world.World; import emu.grasscutter.net.proto.ChangeHpReasonOuterClass; import emu.grasscutter.net.proto.PropChangeReasonOuterClass; import emu.grasscutter.server.game.GameSession; @@ -29,6 +26,8 @@ public class SotSManager { private final Player player; private Timer autoRecoverTimer; + public final static int GlobalMaximumSpringVolume = 8500000; + public SotSManager(Player player) { this.player = player; } 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..9a2d8ae24 --- /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(-200), + SWIM_DASH(-200), + SWIMMING(-80), + FIGHT(0), + + // 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/StaminaManager.java b/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java new file mode 100644 index 000000000..5065b12b3 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java @@ -0,0 +1,357 @@ +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 final Timer sustainedStaminaHandlerTimer = new Timer(); + private final SustainedStaminaHandler handleSustainedStamina = new SustainedStaminaHandler(); + private boolean timerRunning = false; + private GameSession cachedSession = null; + private GameEntity cachedEntity = null; + private int staminaRecoverDelay = 0; + private boolean isInSkillMove = false; + public boolean getIsInSkillMove() { + return isInSkillMove; + } + public void setIsInSkillMove(boolean b) { + isInSkillMove = b; + } + + 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 + ))); + } + + 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.3 || Math.abs(diffY) > 0.2 || Math.abs(diffZ) > 0.3; + } + + // Returns new stamina and sends PlayerPropNotify + public int updateStamina(GameSession session, Consumption consumption) { + int currentStamina = player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA); + if (consumption.amount == 0) { + return currentStamina; + } + int playerMaxStamina = player.getProperty(PlayerProperty.PROP_MAX_STAMINA); + Grasscutter.getLogger().debug(currentStamina + "/" + playerMaxStamina + "\t" + currentState + "\t" + + (isPlayerMoving() ? "moving" : " ") + "\t(" + consumption.consumptionType + "," + + consumption.amount + ")"); + int newStamina = currentStamina + consumption.amount; + if (newStamina < 0) { + newStamina = 0; + } + if (newStamina > playerMaxStamina) { + newStamina = playerMaxStamina; + } + player.setProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA, newStamina); + session.send(new PacketPlayerPropNotify(player, PlayerProperty.PROP_CUR_PERSIST_STAMINA)); + 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() && !timerRunning) { + timerRunning = true; + sustainedStaminaHandlerTimer.scheduleAtFixedRate(handleSustainedStamina, 0, 200); + // Grasscutter.getLogger().debug("[MovementManager] SustainedStaminaHandlerTimer started"); + } + } + + public void stopSustainedStaminaHandler() { + if (timerRunning) { + timerRunning = false; + sustainedStaminaHandlerTimer.cancel(); + // 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) { + updateStamina(session, new Consumption(ConsumptionType.SPRINT)); + } + break; + case MOTION_CLIMB_JUMP: + if (previousState != MotionState.MOTION_CLIMB_JUMP) { + updateStamina(session, new Consumption(ConsumptionType.CLIMB_JUMP)); + } + break; + case MOTION_SWIM_DASH: + if (previousState != MotionState.MOTION_SWIM_DASH) { + updateStamina(session, new Consumption(ConsumptionType.SWIM_DASH_START)); + } + break; + } + } + + private void handleImmediateStamina(GameSession session, EvtDoSkillSuccNotify notify) { + Consumption consumption = getFightConsumption(notify.getSkillId()); + updateStamina(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().debug("Player moving: " + moving + ", stamina full: " + + (currentStamina >= maxStamina) + ", recalculate stamina"); + Consumption consumption = new Consumption(ConsumptionType.None); + if (!isInSkillMove) { + 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(); + } + } + 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 = new Consumption(ConsumptionType.None); + } + } + updateStamina(cachedSession, consumption); + } + handleDrowning(); + } + } + 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) { + 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); + } + } + } + + // Consumption Calculators + + private Consumption getFightConsumption(int skillCasting) { + 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); + } + } + return consumption; + } + + private Consumption getSwimSustainedConsumptions() { + 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); + } + 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() { + Consumption consumption = new Consumption(ConsumptionType.FLY); + 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)) { + reduction = glidingCostReduction.get(skillId); + } + } + } + consumption.amount *= reduction; + // POWERED_FLY, e.g. wind tunnel + if (currentState == MotionState.MOTION_POWERED_FLY) { + consumption = new Consumption(ConsumptionType.POWERED_FLY); + } + 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 1eb5e3526..71ae9d8c6 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -22,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; @@ -62,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; @@ -132,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; @@ -178,7 +175,7 @@ public class Player { 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); } @@ -206,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); } @@ -875,11 +872,11 @@ public class Player { } public void onPause() { - + getStaminaManager().stopSustainedStaminaHandler(); } public void onUnpause() { - + getStaminaManager().startSustainedStaminaHandler(); } public void sendPacket(BasePacket packet) { @@ -1024,7 +1021,7 @@ public class Player { return mapMarksManager; } - public MovementManager getMovementManager() { return movementManager; } + public StaminaManager getStaminaManager() { return staminaManager; } public SotSManager getSotSManager() { return sotsManager; } @@ -1152,7 +1149,7 @@ public class Player { public void onLogout() { // stop stamina calculation - getMovementManager().resetTimer(); + getStaminaManager().stopSustainedStaminaHandler(); // force to leave the dungeon if (getScene().getSceneType() == SceneType.SCENE_DUNGEON) { @@ -1214,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; } @@ -1231,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; } @@ -1242,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/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 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/server/packet/recv/HandlerCombatInvocationsNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java index 27e4ca6ff..cc9e7b345 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,33 @@ 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 + // For plunge attacks, LAND_SPEED is always -30 and is not useful. + // May need the height when starting plunge attack. + + if (monitorLandingEvent) { + if (motionState == MotionState.MOTION_FALL_ON_GROUND) { + monitorLandingEvent = false; + handleFallOnGround(session, entity, motionState); + } + } + if (motionState == MotionState.MOTION_LAND_SPEED) { + // MOTION_LAND_SPEED and MOTION_FALL_ON_GROUND arrive in different packet. Cache land speed for later use. + cachedLandingSpeed = motionInfo.getSpeed().getY(); + cachedLandingTimeMillisecond = System.currentTimeMillis(); + monitorLandingEvent = true; + } } break; default: @@ -47,5 +83,39 @@ public class HandlerCombatInvocationsNotify extends PacketHandler { } } - + private void handleFallOnGround(GameSession session, GameEntity entity, MotionState motionState) { + // If not received immediately after MOTION_LAND_SPEED, discard this packet. + int maxDelay = 200; + long actualDelay = System.currentTimeMillis() - cachedLandingTimeMillisecond; + Grasscutter.getLogger().debug("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 damage = 0; + if (cachedLandingSpeed < -23.5) { + damage = (float) (maxHP * 0.33); + } + if (cachedLandingSpeed < -25) { + damage = (float) (maxHP * 0.5); + } + if (cachedLandingSpeed < -26.5) { + damage = (float) (maxHP * 0.66); + } + if (cachedLandingSpeed < -28) { + damage = (maxHP * 1); + } + float newHP = currentHP - damage; + if (newHP < 0) { + newHP = 0; + } + Grasscutter.getLogger().debug(currentHP + "/" + maxHP + "\t" + "\tDamage: " + damage + "\tnewHP: " + newHP); + 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 a57ae9665..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,10 +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 skillId = notify.getSkillId(); - - session.getPlayer().getMovementManager().notifySkill(caster, skillId); + 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 index 35ec957cb..0d35c1762 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerExitTransPointRegionNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerExitTransPointRegionNotify.java @@ -1,6 +1,6 @@ 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.net.packet.Opcodes; import emu.grasscutter.net.packet.PacketHandler; diff --git a/src/main/java/emu/grasscutter/tools/Tools.java b/src/main/java/emu/grasscutter/tools/Tools.java index 7429c143f..f69aafc80 100644 --- a/src/main/java/emu/grasscutter/tools/Tools.java +++ b/src/main/java/emu/grasscutter/tools/Tools.java @@ -13,16 +13,16 @@ 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; @@ -31,6 +31,8 @@ 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 { public static void createGmHandbook() throws Exception { ToolsWithLanguageOption.createGmHandbook(getLanguageOption()); @@ -111,7 +113,20 @@ final class ToolsWithLanguageOption { writer.println("// Grasscutter " + GameConstants.VERSION + " GM Handbook"); writer.println("// Created " + dtf.format(now) + System.lineSeparator() + System.lineSeparator()); - + + CommandMap cmdMap = new CommandMap(true); + List 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); diff --git a/src/main/java/emu/grasscutter/utils/Language.java b/src/main/java/emu/grasscutter/utils/Language.java index 340d35dc2..cda46e512 100644 --- a/src/main/java/emu/grasscutter/utils/Language.java +++ b/src/main/java/emu/grasscutter/utils/Language.java @@ -19,7 +19,7 @@ public final class Language { * @return A language instance. */ public static Language getLanguage(String langCode) { - return new Language(langCode + ".json"); + return new Language(langCode + ".json", Grasscutter.getConfig().DefaultLanguage.toLanguageTag()); } /** @@ -30,6 +30,7 @@ public final class Language { */ public static String translate(String key, Object... args) { String translated = Grasscutter.getLanguage().get(key); + try { return translated.formatted(args); } catch (Exception exception) { @@ -38,48 +39,27 @@ public final class Language { } } - /** - * creates a language instance. - * @param fileName The name of the language file. - */ - private Language(String fileName) { - @Nullable JsonObject languageData = null; - - languageData = loadLanguage(fileName); - - if (languageData == null) { - Grasscutter.getLogger().info("Now switch to default language"); - languageData = loadDefaultLanguage(); - } - - assert languageData != null : "languageData is null"; - this.languageData = languageData; - } - - /** - * Load default language file and creates a language instance. - * @return language data - */ - private JsonObject loadDefaultLanguage() { - var fileName = Grasscutter.getConfig().DefaultLanguage.toLanguageTag() + ".json"; - return loadLanguage(fileName); - } - /** * Reads a file and creates a language instance. * @param fileName The name of the language file. - * @return language data */ - private JsonObject loadLanguage(String fileName) { + private Language(String fileName, String fallback) { @Nullable JsonObject languageData = null; - + try { InputStream file = Grasscutter.class.getResourceAsStream("/languages/" + fileName); - languageData = Grasscutter.getGsonFactory().fromJson(Utils.readFromInputStream(file), JsonObject.class); + 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); + Grasscutter.getLogger().warn("Failed to load language file: " + fileName, exception); } - return languageData; + + this.languageData = languageData; } /** diff --git a/src/main/java/emu/grasscutter/utils/Utils.java b/src/main/java/emu/grasscutter/utils/Utils.java index 259fc8ad5..1d79c496e 100644 --- a/src/main/java/emu/grasscutter/utils/Utils.java +++ b/src/main/java/emu/grasscutter/utils/Utils.java @@ -1,6 +1,7 @@ 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.*; @@ -17,6 +18,8 @@ import io.netty.buffer.Unpooled; import org.slf4j.Logger; +import javax.annotation.Nullable; + import static emu.grasscutter.utils.Language.translate; @SuppressWarnings({"UnusedReturnValue", "BooleanMethodIsAlwaysInverted"}) @@ -252,14 +255,18 @@ public final class Utils { * @param stream The input stream. * @return The string. */ - public static String readFromInputStream(InputStream stream) { + public static String readFromInputStream(@Nullable InputStream stream) { + if(stream == null) return "empty"; + StringBuilder stringBuilder = new StringBuilder(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream,"UTF-8"))) { + 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(); } diff --git a/src/main/resources/languages/en-US.json b/src/main/resources/languages/en-US.json index 65a8634f7..4ec17a214 100644 --- a/src/main/resources/languages/en-US.json +++ b/src/main/resources/languages/en-US.json @@ -56,6 +56,7 @@ "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.", @@ -95,17 +96,20 @@ "create": "Account created with UID %s.", "delete": "Account deleted.", "no_account": "Account not found.", - "command_usage": "Usage: account [uid]" + "command_usage": "Usage: account [uid]", + "description": "Modify user accounts" }, "broadcast": { "command_usage": "Usage: broadcast ", - "message_sent": "Message sent." + "message_sent": "Message sent.", + "description": "Sends a message to all the players" }, "changescene": { "usage": "Usage: changescene ", "already_in_scene": "You are already in that scene.", "success": "Changed to scene %s.", - "exists_error": "The specified scene does not exist." + "exists_error": "The specified scene does not exist.", + "description": "Changes your scene" }, "clear": { "command_usage": "Usage: clear ", @@ -115,35 +119,41 @@ "furniture": "Cleared furniture for %s.", "displays": "Cleared displays for %s.", "virtuals": "Cleared virtuals for %s.", - "everything": "Cleared everything for %s." + "everything": "Cleared everything for %s.", + "description": "Deletes unequipped unlocked items, including yellow rarity ones from your inventory" }, "coop": { "usage": "Usage: coop ", - "success": "Summoned %s to %s's world." + "success": "Summoned %s to %s's world.", + "description": "Forces someone to join the world of others" }, "enter_dungeon": { "usage": "Usage: enterdungeon ", "changed": "Changed to dungeon %s", "not_found_error": "Dungeon does not exist", - "in_dungeon_error": "You are already in that dungeon" + "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." + "invalid_amount_or_playerId": "Invalid amount or player ID.", + "description": "Gives all items" }, "giveArtifact": { "usage": "Usage: giveart|gart [player] [[,]]... [level]", "id_error": "Invalid artifact ID.", - "success": "Given %s to %s." + "success": "Given %s to %s.", + "description": "Gives the player a specified artifact" }, "giveChar": { "usage": "Usage: givechar [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." + "invalid_avatar_or_player_id": "Invalid avatar or player ID.", + "description": "Gives the player a specified character" }, "give": { "usage": "Usage: give [amount] [level]", @@ -151,29 +161,36 @@ "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" + "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." + "success": "Godmode is now %s for %s.", + "description": "Prevents you from taking damage. Defaults to toggle." }, "heal": { - "success": "All characters have been healed." + "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]" + "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" + "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." + "success": "Killed %s's current character.", + "description": "Kills the players current character" }, "list": { - "success": "There are %s player(s) online:" + "success": "There are %s player(s) online:", + "description": "List online players" }, "permission": { "usage": "Usage: permission ", @@ -181,21 +198,26 @@ "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." + "account_error": "The account cannot be found.", + "description": "Grants or removes a permission for a user" }, "position": { - "success": "Coordinates: %.3f, %.3f, %.3f\nScene id: %d" + "success": "Coordinates: %s, %s, %s\nScene id: %s", + "description": "Get coordinates." }, "reload": { "reload_start": "Reloading config.", - "reload_done": "Reload complete." + "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." + "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 " + "usage": "Usage: /resetshop ", + "description": "Reset target player's shop refresh time." }, "sendMail": { "usage": "Usage: give [player] [amount]", @@ -217,17 +239,20 @@ "message": "", "sender": "", "arguments": " [amount] [level]", - "error": "ERROR: invalid construction stage %s. Check console for stacktrace." + "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 ", - "success": "Message sent." + "success": "Message sent.", + "description": "Sends a message to a player as the server" }, "setFetterLevel": { "usage": "Usage: setfetterlevel ", "range_error": "Fetter level must be between 0 and 10.", "success": "Fetter level set to %s", - "level_error": "Invalid fetter level." + "level_error": "Invalid fetter level.", + "description": "Sets your fetter level for your current active character" }, "setStats": { "usage_console": "Usage: setstats|stats @ ", @@ -238,20 +263,24 @@ "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." + "set_max_hp": "MAX HP set to %s.", + "description": "Set fight property for your current active character" }, "setWorldLevel": { "usage": "Usage: setworldlevel ", "value_error": "World level must be between 0-8", "success": "World level set to %s.", - "invalid_world_level": "Invalid world level." + "invalid_world_level": "Invalid world level.", + "description": "Sets your world level (Relog to see proper effects)" }, "spawn": { "usage": "Usage: spawn [amount] [level(monster only)]", - "success": "Spawned %s of %s." + "success": "Spawned %s of %s.", + "description": "Spawns an entity near you" }, "stop": { - "success": "Server shutting down..." + "success": "Server shutting down...", + "description": "Stops the server" }, "talent": { "usage_1": "To set talent level: /talent set ", @@ -267,18 +296,21 @@ "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." + "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." + "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 @ [scene id]", "usage": "Usage: /tp [@] [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" + "success": "Teleported %s to %s, %s, %s in scene %s", + "description": "Change the player's position." }, "tower": { "unlock_done": "Abyss Corridor's Floors are all unlocked now." @@ -286,16 +318,22 @@ "weather": { "usage": "Usage: weather [climateId]", "success": "Changed weather to %s with climate %s", - "invalid_id": "Invalid ID." + "invalid_id": "Invalid ID.", + "description": "Changes the weather." }, "drop": { "command_usage": "Usage: drop [amount]", - "success": "Dropped %s of %s." + "success": "Dropped %s of %s.", + "description": "Drops an item near you" }, "help": { "usage": "Usage: ", "aliases": "Aliases: ", - "available_commands": "Available commands: " + "available_commands": "Available commands: ", + "description": "Sends the help message or shows information about a specified command" + }, + "restart": { + "description": "Restarts the current session" } } -} \ No newline at end of file +} 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 [uid]" + }, + "broadcast": { + "command_usage": "Użycie: broadcast ", + "message_sent": "Wiadomość wysłana." + }, + "changescene": { + "usage": "Użycie: changescene ", + "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 ", + "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 ", + "success": "Przyzwano %s do świata %s." + }, + "enter_dungeon": { + "usage": "Użycie: enterdungeon ", + "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] [[,]]... [poziom]", + "id_error": "Błędne ID artefaktu.", + "success": "Dano %s dla %s." + }, + "giveChar": { + "usage": "Użycie: givechar [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 [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": "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 " + }, + "sendMail": { + "usage": "Użycie: `/sendmail [id szablonu]`", + "user_not_exist": "Gracz o ID '%s' nie istnieje", + "start_composition": "Komponowanie wiadomości.\nProszę użyj `/sendmail ` 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 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 ' aby kontynuować.", + "set_contents": "Treść wiadomości to teraz '%s'.\nUżyj '/sendmail ' aby kontynuować.", + "set_message_sender": "Nadawca wiadomości to teraz '%s'.\nUżyj '/sendmail [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": "", + "message": "", + "sender": "", + "arguments": " [ilość] [poziom]", + "error": "BŁĄD: niepoprawny etap konstrukcji: %s. Sprawdź konsolę aby dowiedzieć się więcej." + }, + "sendMessage": { + "usage": "Użycie: /sendmessage ", + "success": "Wiadomość wysłana." + }, + "setFetterLevel": { + "usage": "Użycie: setfetterlevel ", + "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 @ ", + "usage_ingame": "Użycie: setstats|stats [@UID] ", + "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 ", + "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 [ilość] [poziom(tylko potwory)]", + "success": "Stworzono %s %s." + }, + "stop": { + "success": "Serwer wyłącza się..." + }, + "talent": { + "usage_1": "Aby ustawić poziom talentu: /talent set ", + "usage_2": "Inny sposób na ustawienie poziomu talentu: /talent ", + "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 sceny]", + "usage": "Użycie: /tp [@] [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 klimatu]", + "success": "Zmieniono pogodę na %s z klimatem %s", + "invalid_id": "Błędne ID." + }, + "drop": { + "command_usage": "Użycie: drop [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 index b9bc5b22a..8faa0e4ae 100644 --- a/src/main/resources/languages/zh-CN.json +++ b/src/main/resources/languages/zh-CN.json @@ -36,12 +36,12 @@ } }, "status": { - "free_software": "Grasscutter 是免费开源软件。如果你是付费购买的,那已经被骗了。Github:https://github.com/Grasscutters/Grasscutter", + "free_software": "Grasscutter 是免费开源软件,遵循Apache-2.0 license。如果您是付费购买的,那您已经被骗了。项目地址:Github:https://github.com/Grasscutters/Grasscutter", "starting": "正在启动 Grasscutter...", "shutdown": "正在关闭...", - "done": "加載完成!输入 \"help\" 查看命令列表", + "done": "加载完成!输入 \"help\" 查看命令列表", "error": "发生了一个错误。", - "welcome": "欢迎使用 Grasscutter", + "welcome": "欢迎使用 Grasscutter!珍惜这段美妙的旅途吧!", "run_mode_error": "无效的服务器运行模式: %s。", "run_mode_help": "服务器运行模式必须为 HYBRID、DISPATCH_ONLY 或 GAME_ONLY。Grasscutter 启动失败...", "create_resources": "正在创建 resources 目录...", @@ -52,12 +52,12 @@ "generic": { "not_specified": "没有指定命令。", "unknown_command": "未知的命令:%s", - "permission_error": "您没有执行此命令的权限。", - "console_execute_error": "此命令只能在服务器控制台执行。", - "player_execute_error": "此命令只能在游戏内执行。", - "command_exist_error": "找不到命令。", + "permission_error": "哼哼哼!您没有执行此命令的权限!请联系服务器管理员解决!", + "console_execute_error": "此命令只能在服务器控制台执行呐~", + "player_execute_error": "此命令只能在游戏内执行哦~", + "command_exist_error": "这条命令……好像找不到呢?。", "invalid": { - "amount": "无效的 数量.", + "amount": "无效的数量.", "artifactId": "无效的圣遗物ID。", "avatarId": "无效的角色ID。", "avatarLevel": "无效的角色等級。", @@ -119,7 +119,7 @@ }, "coop": { "usage": "用法:coop ", - "success": "已召唤 %s 到 %s的世界" + "success": "已强制召唤 %s 到 %s的世界" }, "enter_dungeon": { "usage": "用法:enterdungeon ", @@ -140,21 +140,21 @@ }, "giveChar": { "usage": "用法:givechar [amount]", - "given": "Given %s with level %s to %s.", + "given": "给予角色 %s 等级 %s 向UID %s.", "invalid_avatar_id": "无效的角色ID。", "invalid_avatar_level": "无效的角色等級。.", "invalid_avatar_or_player_id": "无效的角色ID/玩家ID。" }, "give": { "usage": "用法:give [amount] [level] [refinement]", - "refinement_only_applicable_weapons": "精炼等级参数仅在武器上可用", - "refinement_must_between_1_and_5": "精炼等级必须在 1 到 5 之间。", + "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" + "given_level": "已将 %s 等级 %s %s 个给予UID %s" }, "godmode": { - "success": "上帝模式被设置为 %s 。 [用户:%s]" + "success": "上帝模式已被设置为 %s 。 [用户:%s]" }, "heal": { "success": "所有角色已被治疗。" @@ -173,7 +173,7 @@ "success": "已杀死 %s 目前使用的角色。" }, "list": { - "message": "目前在线人数:%s" + "success": "目前在线人数:%s" }, "permission": { "usage": "用法:permission ", @@ -188,7 +188,7 @@ }, "reload": { "reload_start": "正在重载配置文件和数据。", - "reload_done": "重装完毕。" + "reload_done": "重载完毕。" }, "resetConst": { "reset_all": "重置所有角色的命座。", @@ -204,7 +204,7 @@ "templates": "邮件模板尚未实装...", "invalid_arguments": "无效的参数。\n指令使用方法 `/sendmail [templateId]`", "send_cancel": "取消发送邮件", - "send_done": "已将邮件给 %s!", + "send_done": "已将邮件发送给 %s!", "send_all_done": "邮件已发送给所有人!", "not_composition_end": "现在邮件发送未到最后阶段。\n请使用 `/sendmail %s` 继续发送邮件,或使用 `/sendmail stop` 来停止发送邮件。", "please_use": "请使用 `/sendmail %s`", @@ -257,21 +257,21 @@ "usage_1": "设置天赋等级:/talent set ", "usage_2": "另一种设置天赋等级的命令使用方法:/talent ", "usage_3": "获取天赋ID指令用法:/talent getid", - "lower_16": "无效的天赋等级,天赋等级应低于16。", + "lower_16": "无效的天赋等级,天赋等级应小于等于15。", "set_id": "将天赋等级设为 %s。", "set_atk": "将普通攻击等级设为 %s。", - "set_e": "设定天赋E等级为 %s。", - "set_q": "设定天赋Q等级为 %s。", + "set_e": "设定元素战技等级为 %s。", + "set_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。" + "e_skill_id": "元素战技ID %s。", + "q_skill_id": "元素爆发ID %s。" }, "teleportAll": { "success": "已将全部玩家传送到你的位置", - "error": "命令仅限多人游戏使用。" + "error": "命令仅限处于多人游戏状态下使用。" }, "teleport": { "usage_server": "用法:/tp @ [scene id]", @@ -282,12 +282,12 @@ }, "weather": { "usage": "用法:weather [climateId]", - "success": "已将当前天气设定为 %s,气候则为 %s。", - "invalid_id": "无效的ID。" + "success": "已将当前天气设定为 %s,气候为 %s。", + "invalid_id": "无效的天气ID。" }, "drop": { "command_usage": "用法:drop [amount]", - "success": "已將 %s x %s 丟在附近。" + "success": "已将 %s x %s 丟在附近。" }, "help": { "usage": "用法:", diff --git a/src/main/resources/languages/zh-TW.json b/src/main/resources/languages/zh-TW.json index 22d1339c8..7a7f5c100 100644 --- a/src/main/resources/languages/zh-TW.json +++ b/src/main/resources/languages/zh-TW.json @@ -57,7 +57,7 @@ "player_execute_error": "請在遊戲裡使用這條指令。", "command_exist_error": "找不到指令。", "invalid": { - "amount": "無效的 數量.", + "amount": "無效的數量。", "artifactId": "無效的聖遺物ID。", "avatarId": "無效的角色ID。", "avatarLevel": "無效的角色等級。", @@ -119,7 +119,7 @@ }, "coop": { "usage": "用法:coop ", - "success": "Summoned %s to %s's world." + "success": "召喚了 %s 到 %s 的世界。" }, "enter_dungeon": { "usage": "用法:enterdungeon ", @@ -140,7 +140,7 @@ }, "giveChar": { "usage": "用法:givechar [amount]", - "given": "Given %s with level %s to %s.", + "given": "已將 %s 等級 %s 給予 %s。", "invalid_avatar_id": "無效的角色ID。", "invalid_avatar_level": "無效的角色等級。.", "invalid_avatar_or_player_id": "無效的角色ID/玩家ID。" @@ -173,7 +173,7 @@ "success": "已殺死 %s 目前的場上角色。" }, "list": { - "message": "目前總線上人數:%s" + "success": "目前總線上人數:%s" }, "permission": { "usage": "用法:permission ", @@ -270,7 +270,7 @@ "q_skill_id": "Q技能ID %s。" }, "teleportAll": { - "success": "Summoned all players to your location.", + "success": "召喚了所有玩家到你的位置上。", "error": "此指令僅可在多人遊戲下可用。" }, "teleport": { @@ -295,4 +295,4 @@ "available_commands": "可用指令:" } } -} \ No newline at end of file +}