diff --git a/src/main/java/emu/grasscutter/Grasscutter.java b/src/main/java/emu/grasscutter/Grasscutter.java index 89c4d8a26..51bc3fcc5 100644 --- a/src/main/java/emu/grasscutter/Grasscutter.java +++ b/src/main/java/emu/grasscutter/Grasscutter.java @@ -77,7 +77,7 @@ public final class Grasscutter { Tools.createGmHandbook(); exitEarly = true; } case "-gachamap" -> { - Tools.createGachaMapping("./gacha-mapping.js"); exitEarly = true; + Tools.createGachaMapping(Grasscutter.getConfig().DATA_FOLDER + "/gacha_mappings.js"); exitEarly = true; } } } diff --git a/src/main/java/emu/grasscutter/game/managers/MovementManager/MovementManager.java b/src/main/java/emu/grasscutter/game/managers/MovementManager/MovementManager.java index 18958f355..23b45903a 100644 --- a/src/main/java/emu/grasscutter/game/managers/MovementManager/MovementManager.java +++ b/src/main/java/emu/grasscutter/game/managers/MovementManager/MovementManager.java @@ -24,7 +24,7 @@ public class MovementManager { public HashMap> MotionStatesCategorized = new HashMap<>(); - private enum Consumption { + private enum ConsumptionType { None(0), // consume @@ -37,6 +37,7 @@ public class MovementManager { SWIM_DASH_START(-200), SWIM_DASH(-200), SWIMMING(-80), + FIGHT(0), // restore STANDBY(500), @@ -46,11 +47,22 @@ public class MovementManager { POWERED_FLY(500); public final int amount; - Consumption(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; @@ -64,8 +76,9 @@ public class MovementManager { 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)); @@ -114,6 +127,12 @@ public class MovementManager { 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) { @@ -134,11 +153,12 @@ public class MovementManager { currentCoordinates = newPos; } currentState = motionInfo.getState(); - Grasscutter.getLogger().debug("" + currentState); + Grasscutter.getLogger().debug("" + currentState + "\t" + (moveInfo.getIsReliable() ? "reliable" : "")); handleFallOnGround(motionInfo); } public void resetTimer() { + Grasscutter.getLogger().debug("MovementManager ticker stopped"); movementManagerTickTimer.cancel(); movementManagerTickTimer = null; } @@ -167,8 +187,6 @@ public class MovementManager { 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); @@ -184,6 +202,7 @@ public class MovementManager { newStamina = playerMaxStamina; } session.getPlayer().setProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA, newStamina); + session.send(new PacketPlayerPropNotify(player, PlayerProperty.PROP_CUR_PERSIST_STAMINA)); return newStamina; } @@ -269,95 +288,40 @@ public class MovementManager { boolean moving = isPlayerMoving(); if (moving || (getCurrentStamina() < getMaximumStamina())) { // Grasscutter.getLogger().debug("Player moving: " + moving + ", stamina full: " + (getCurrentStamina() >= getMaximumStamina()) + ", recalculate stamina"); - Consumption consumption = Consumption.None; + Consumption consumption = new Consumption(ConsumptionType.None); // TODO: refactor these conditions. if (MotionStatesCategorized.get("CLIMB").contains(currentState)) { - if (currentState == MotionState.MOTION_CLIMB) { - // CLIMB - if (previousState != MotionState.MOTION_CLIMB && previousState != MotionState.MOTION_CLIMB_JUMP) { - consumption = Consumption.CLIMB_START; - } else { - consumption = Consumption.CLIMBING; - } - } - if (currentState == MotionState.MOTION_CLIMB_JUMP) { - if (previousState != MotionState.MOTION_CLIMB_JUMP) { - consumption = Consumption.CLIMB_JUMP; - } - } - if (currentState == MotionState.MOTION_JUMP) { - if (previousState == MotionState.MOTION_CLIMB) { - consumption = Consumption.CLIMB_JUMP; - } - } + consumption = getClimbConsumption(); } else if (MotionStatesCategorized.get("SWIM").contains((currentState))) { - // SWIM - if (currentState == MotionState.MOTION_SWIM_MOVE) { - consumption = Consumption.SWIMMING; - } - if (currentState == MotionState.MOTION_SWIM_DASH) { - if (previousState != MotionState.MOTION_SWIM_DASH) { - consumption = Consumption.SWIM_DASH_START; - } else { - consumption = Consumption.SWIM_DASH; - } - } + consumption = getSwimConsumptions(); } else if (MotionStatesCategorized.get("RUN").contains(currentState)) { - // RUN, DASH and WALK - // DASH - if (currentState == MotionState.MOTION_DASH_BEFORE_SHAKE) { - consumption = Consumption.DASH; - if (previousState == MotionState.MOTION_DASH_BEFORE_SHAKE) { - // only charge once - consumption = Consumption.SPRINT; - } - } - if (currentState == MotionState.MOTION_DASH) { - consumption = Consumption.SPRINT; - } - // RUN - if (currentState == MotionState.MOTION_RUN) { - consumption = Consumption.RUN; - } - // WALK - if (currentState == MotionState.MOTION_WALK) { - consumption = Consumption.WALK; - } + consumption = getRunWalkDashConsumption(); } else if (MotionStatesCategorized.get("FLY").contains(currentState)) { - // FLY - consumption = Consumption.FLY; - // POWERED_FLY, e.g. wind tunnel - if (currentState == MotionState.MOTION_POWERED_FLY) { - consumption = Consumption.POWERED_FLY; - } + consumption = getFlyConsumption(); } else if (MotionStatesCategorized.get("STANDBY").contains(currentState)) { - // STAND - if (currentState == MotionState.MOTION_STANDBY) { - consumption = Consumption.STANDBY; - } - if (currentState == MotionState.MOTION_STANDBY_MOVE) { - consumption = Consumption.STANDBY_MOVE; - } + consumption = getStandConsumption(); + } else if (MotionStatesCategorized.get("FIGHT").contains(currentState)) { + consumption = getFightConsumption(); } - // tick triggered - handleDrowning(); - + // delay 2 seconds before start recovering - as official server does. if (cachedSession != null) { if (consumption.amount < 0) { staminaRecoverDelay = 0; } - if (consumption.amount > 0) { + if (consumption.amount > 0 && consumption.consumptionType != ConsumptionType.POWERED_FLY) { if (staminaRecoverDelay < 10) { staminaRecoverDelay++; - consumption = Consumption.None; + consumption = new Consumption(ConsumptionType.None); } } - int newStamina = updateStamina(cachedSession, consumption.amount); - cachedSession.send(new PacketPlayerPropNotify(player, PlayerProperty.PROP_CUR_PERSIST_STAMINA)); - Grasscutter.getLogger().debug(player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA) + "/" + player.getProperty(PlayerProperty.PROP_MAX_STAMINA) + "\t" + currentState + "\t" + "isMoving: " + isPlayerMoving() + "\t" + consumption + "(" + consumption.amount + ")"); + // Grasscutter.getLogger().debug(getCurrentStamina() + "/" + getMaximumStamina() + "\t" + currentState + "\t" + "isMoving: " + isPlayerMoving() + "\t(" + consumption.consumptionType + "," + consumption.amount + ")"); + updateStamina(cachedSession, consumption.amount); } + + // tick triggered + handleDrowning(); } } @@ -366,4 +330,113 @@ public class MovementManager { 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/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index 1c14ac09a..1eb5e3526 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -1151,8 +1151,11 @@ public class Player { } public void onLogout() { + // stop stamina calculation + getMovementManager().resetTimer(); + // force to leave the dungeon - if(getScene().getSceneType() == SceneType.SCENE_DUNGEON){ + if (getScene().getSceneType() == SceneType.SCENE_DUNGEON) { this.getServer().getDungeonManager().exitDungeon(this); } // Leave world diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDoSkillSuccNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDoSkillSuccNotify.java index d1db944d2..a57ae9665 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDoSkillSuccNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDoSkillSuccNotify.java @@ -16,11 +16,9 @@ public class HandlerEvtDoSkillSuccNotify extends PacketHandler { // TODO: Will be used for deducting stamina for charged skills. int caster = notify.getCasterId(); - int skill = notify.getSkillId(); + int skillId = notify.getSkillId(); - // Grasscutter.getLogger().warn(caster + "\t" + skill); - -// session.getPlayer().getScene().broadcastPacket(new PacketEvtAvatarStandUpNotify(notify)); + session.getPlayer().getMovementManager().notifySkill(caster, skillId); } } diff --git a/src/main/java/emu/grasscutter/tools/Tools.java b/src/main/java/emu/grasscutter/tools/Tools.java index a2bee91f0..7429c143f 100644 --- a/src/main/java/emu/grasscutter/tools/Tools.java +++ b/src/main/java/emu/grasscutter/tools/Tools.java @@ -64,25 +64,26 @@ public final class Tools { if (availableLangList.size() == 1) { return availableLangList.get(0).toUpperCase(); } - System.out.println("The following languages mappings are available, please select one: [default: EN]"); - String groupedLangList = "> "; + String stagedMessage = ""; + stagedMessage += "The following languages mappings are available, please select one: [default: EN]\n"; + String groupedLangList = ">\t"; int groupedLangCount = 0; String input = ""; for (String availableLanguage: availableLangList){ groupedLangCount++; groupedLangList = groupedLangList + "" + availableLanguage + "\t"; if (groupedLangCount == 6) { - System.out.println(groupedLangList); + stagedMessage += groupedLangList + "\n"; groupedLangCount = 0; - groupedLangList = "> "; + groupedLangList = ">\t"; } } if (groupedLangCount > 0) { - System.out.println(groupedLangList); + stagedMessage += groupedLangList + "\n"; } - System.out.print("\nYour choice:[EN] "); - - input = new BufferedReader(new InputStreamReader(System.in)).readLine(); + stagedMessage += "\nYour choice:[EN] "; + + input = Grasscutter.getConsole().readLine(stagedMessage); if (availableLangList.contains(input.toLowerCase())) { return input.toUpperCase(); } @@ -249,6 +250,6 @@ final class ToolsWithLanguageOption { writer.println("}\n}"); } - Grasscutter.getLogger().info("Mappings generated!"); + Grasscutter.getLogger().info("Mappings generated to " + location + " !"); } } diff --git a/src/main/resources/languages/zh-CN.json b/src/main/resources/languages/zh-CN.json new file mode 100644 index 000000000..b9bc5b22a --- /dev/null +++ b/src/main/resources/languages/zh-CN.json @@ -0,0 +1,298 @@ +{ + "messages": { + "game": { + "port_bind": "游戏服务器已在端口 %s 上启动", + "connect": "客户端已连接至 %s", + "disconnect": "客户端 %s 已断开连接", + "game_update_error": "游戏更新时发生错误", + "command_error": "命令发生错误:" + }, + "dispatch": { + "port_bind": "[Dispatch] 服务器已在端口 %s 上启动", + "request": "[Dispatch] 客户端 %s 请求: %s %s", + "keystore": { + "general_error": "[Dispatch] 加载 keystore 文件时发生错误!", + "password_error": "[Dispatch] 加载 keystore 失败。正在尝试使用预设的 keystore 密码...", + "no_keystore_error": "[Dispatch] 未找到 SSL 证书!已降级到 HTTP 服务器", + "default_password": "[Dispatch] 默认的 keystore 密码加载成功。请考虑将 config.json 的默认密码设置为 123456" + }, + "no_commands_error": "此命令不适用于 Dispatch-only 模式", + "unhandled_request_error": "[Dispatch] 潜在的未处理请求 %s 请求:%s", + "account": { + "login_attempt": "[Dispatch] 客户端 %s 正在尝试登录", + "login_success": "[Dispatch] 客户端 %s 已登录,UID为 %s", + "login_token_attempt": "[Dispatch] 客户端 %s 正在尝试使用令牌登录", + "login_token_error": "[Dispatch] 客户端 %s 使用令牌登录失败", + "login_token_success": "[Dispatch] 客户端 %s 已通过令牌登录,UID为 %s", + "combo_token_success": "[Dispatch] 客户端 %s 交换令牌成功", + "combo_token_error": "[Dispatch] 客户端 %s 交换令牌失败", + "account_login_create_success": "[Dispatch] 客户端 %s 登录失败: 已注册UID为 %s 的账号", + "account_login_create_error": "[Dispatch] 客户端 %s 登录失败:账号创建失败。", + "account_login_exist_error": "[Dispatch] 客户端 %s 登录失败:账号不存在", + "account_cache_error": "游戏账号缓存信息错误", + "session_key_error": "交换秘钥不符。", + "username_error": "未找到此用户名。", + "username_create_error": "未找到用户名,建立连接失败。" + } + }, + "status": { + "free_software": "Grasscutter 是免费开源软件。如果你是付费购买的,那已经被骗了。Github:https://github.com/Grasscutters/Grasscutter", + "starting": "正在启动 Grasscutter...", + "shutdown": "正在关闭...", + "done": "加載完成!输入 \"help\" 查看命令列表", + "error": "发生了一个错误。", + "welcome": "欢迎使用 Grasscutter", + "run_mode_error": "无效的服务器运行模式: %s。", + "run_mode_help": "服务器运行模式必须为 HYBRID、DISPATCH_ONLY 或 GAME_ONLY。Grasscutter 启动失败...", + "create_resources": "正在创建 resources 目录...", + "resources_error": "请将 BinOutput 和 ExcelBinOutput 复制到 resources 目录。" + } + }, + "commands": { + "generic": { + "not_specified": "没有指定命令。", + "unknown_command": "未知的命令:%s", + "permission_error": "您没有执行此命令的权限。", + "console_execute_error": "此命令只能在服务器控制台执行。", + "player_execute_error": "此命令只能在游戏内执行。", + "command_exist_error": "找不到命令。", + "invalid": { + "amount": "无效的 数量.", + "artifactId": "无效的圣遗物ID。", + "avatarId": "无效的角色ID。", + "avatarLevel": "无效的角色等級。", + "entityId": "无效的实体ID。", + "itemId": "无效的物品ID。", + "itemLevel": "无效的物品等級。", + "itemRefinement": "无效的物品精炼等级。", + "playerId": "无效的玩家ID。", + "uid": "无效的UID。" + } + }, + "execution": { + "uid_error": "无效的UID。", + "player_exist_error": "用户不存在。", + "player_offline_error": "玩家已离线。", + "item_id_error": "无效的物品ID。.", + "item_player_exist_error": "无效的物品/玩家UID。", + "entity_id_error": "无效的实体ID。", + "player_exist_offline_error": "玩家不存在或已离线。", + "argument_error": "无效的参数。", + "clear_target": "目标已清除.", + "set_target": "随后的的命令都会以@%s为预设。", + "need_target": "此命令需要一个目标 UID。添加 <@UID> 参数或使用 /target @UID 来设定持久目标。" + }, + "status": { + "enabled": "已启用", + "disabled": "未启用", + "help": "帮助", + "success": "成功" + }, + "account": { + "modify": "修改使用者账号", + "invalid": "无效的UID。", + "exists": "账号已存在。", + "create": "已建立账号,UID 为 %s 。", + "delete": "账号已刪除。", + "no_account": "账号不存在。", + "command_usage": "用法:account [uid]" + }, + "broadcast": { + "command_usage": "用法:broadcast <消息>", + "message_sent": "公告已发送。" + }, + "changescene": { + "usage": "用法:changescene ", + "already_in_scene": "你已经在这个秘境中了。", + "success": "已切换至秘境 %s.", + "exists_error": "此秘境不存在。" + }, + "clear": { + "command_usage": "用法: clear ", + "weapons": "已将 %s 的武器清空。", + "artifacts": "已将 %s 的圣遗物清空。", + "materials": "已将 %s 的材料清空。", + "furniture": "已将 %s 的尘歌壶家具清空。", + "displays": "已清除 %s 的显示。", + "virtuals": "已将 %s 的所有货币和经验值清空。", + "everything": "已将 %s 的所有物品清空。" + }, + "coop": { + "usage": "用法:coop ", + "success": "已召唤 %s 到 %s的世界" + }, + "enter_dungeon": { + "usage": "用法:enterdungeon ", + "changed": "已进入秘境 %s", + "not_found_error": "此秘境不存在。", + "in_dungeon_error": "你已经在秘境中了。" + }, + "giveAll": { + "usage": "用法:giveall [player] [amount]", + "started": "正在给予全部物品...", + "success": "已给予全部物品。", + "invalid_amount_or_playerId": "无效的数量/玩家ID。" + }, + "giveArtifact": { + "usage": "用法:giveart|gart [player] [[,]]... [level]", + "id_error": "无效的圣遗物ID。", + "success": "已将 %s 给予 %s。" + }, + "giveChar": { + "usage": "用法:givechar [amount]", + "given": "Given %s with level %s to %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 之间。", + "given": "已将 %s 个 %s 给予 %s。", + "given_with_level_and_refinement": "已将 %s [等級%s, 精炼%s] %s个给予 %s", + "given_level": "已将 %s 等级 %s %s 个给予 %s" + }, + "godmode": { + "success": "上帝模式被设置为 %s 。 [用户:%s]" + }, + "heal": { + "success": "所有角色已被治疗。" + }, + "kick": { + "player_kick_player": "玩家 [%s:%s] 已将 [%s:%s] 踢出", + "server_kick_player": "正在踢出玩家 [%s:%s]" + }, + "kill": { + "usage": "用法:killall [playerUid] [sceneId]", + "scene_not_found_in_player_world": "未在玩家世界中找到此场景", + "kill_monsters_in_scene": "已杀死 %s 个怪物。 [场景ID: %s]" + }, + "killCharacter": { + "usage": "用法:/killcharacter [playerId]", + "success": "已杀死 %s 目前使用的角色。" + }, + "list": { + "message": "目前在线人数:%s" + }, + "permission": { + "usage": "用法:permission ", + "add": "已设置权限。", + "has_error": "此玩家已拥有此权限!", + "remove": "权限已移除。", + "not_have_error": "此玩家未拥有权限!", + "account_error": "账号不存在!" + }, + "position": { + "success": "坐标:%.3f, %.3f, %.3f\n场景ID:%d" + }, + "reload": { + "reload_start": "正在重载配置文件和数据。", + "reload_done": "重装完毕。" + }, + "resetConst": { + "reset_all": "重置所有角色的命座。", + "success": "已重置 %s 的命座,重新登录后将会生效。" + }, + "resetShopLimit": { + "usage": "用法:/resetshop " + }, + "sendMail": { + "usage": "用法:give [player] [amount]", + "user_not_exist": "ID '%s' 的使用者不存在。", + "start_composition": "发送邮件流程。\n请使用`/send <标题>`前进到下一步。\n你可以在任何时间使用`/sendmail stop`来停止发送。", + "templates": "邮件模板尚未实装...", + "invalid_arguments": "无效的参数。\n指令使用方法 `/sendmail [templateId]`", + "send_cancel": "取消发送邮件", + "send_done": "已将邮件给 %s!", + "send_all_done": "邮件已发送给所有人!", + "not_composition_end": "现在邮件发送未到最后阶段。\n请使用 `/sendmail %s` 继续发送邮件,或使用 `/sendmail stop` 来停止发送邮件。", + "please_use": "请使用 `/sendmail %s`", + "set_title": "成功将邮件标题设置为 '%s'。\n使用 '/sendmail ' 来设置邮件内容。", + "set_contents": "成功将'%s'设置为邮件内容。\n使用 '/sendmail <发件人>' 来设置发件人。", + "set_message_sender": "发件人已设置为 '%s'。\n使用 '/sendmail [amount] [level]' 来添加附件。", + "send": "已添加 %s 个 %s (等級为 %s) 邮件附件。\n如果没有要继续添加的道具请使用 `/sendmail finish` 来完成邮件发送。", + "invalid_arguments_please_use": "错误的参数 \n请使用 `/sendmail %s`", + "title": "<标题>", + "message": "<正文>", + "sender": "<发件人>", + "arguments": " [数量] [等级]", + "error": "错误:无效的编写阶段 %s。需要 StackTrace 请查看服务器控制台。" + }, + "sendMessage": { + "usage": "用法:sendmessage ", + "success": "消息已发送。" + }, + "setFetterLevel": { + "usage": "用法:setfetterlevel ", + "range_error": "好感度等级必须在 0 到 10 之间。", + "fetter_set_level": "好感度已设置为 %s 级", + "level_error": "无效的好感度等级。" + }, + "setStats": { + "usage_console": "用法:setstats|stats @ ", + "usage_ingame": "用法:setstats|stats [@UID] ", + "help_message": "\n\t可使用的数据类型:hp (生命值)| maxhp (最大生命值) | def(防御力) | atk (攻击力)| em (元素精通) | er (元素充能效率) | crate(暴击率) | cdmg (暴击伤害)| cdr (冷却缩减) | heal(治疗加成)| heali (受治疗加成)| shield (护盾强效)| defi (无视防御)\n\t(cont.) 元素伤害:epyro (火) | ecryo (冰) | ehydro (水) | egeo (岩) | edendro (草) | eelectro (雷) | ephys (物理)(cont.) 元素抗性:respyro (火) | rescryo (冰) | reshydro (水) | resgeo (岩) | resdendro (草) | reselectro (雷) | resphys (物理)\n", + "value_error": "无效的数据值。", + "uid_error": "无效的UID。", + "player_error": "玩家不存在或已离线。", + "set_self": "%s 已经设置为 %s。", + "set_for_uid": "%s 的使用者 %s 更改为 %s。", + "set_max_hp": "最大生命值更改为 %s。" + }, + "setWorldLevel": { + "usage": "用法:setworldlevel ", + "value_error": "世界等级必须设置在0-8之间。", + "success": "已将世界等级设为%s。", + "invalid_world_level": "无效的世界等级。" + }, + "spawn": { + "usage": "用法:spawn [amount] [level(仅限怪物]", + "success": "已生成 %s 个 %s。" + }, + "stop": { + "success": "正在关闭服务器..." + }, + "talent": { + "usage_1": "设置天赋等级:/talent set ", + "usage_2": "另一种设置天赋等级的命令使用方法:/talent ", + "usage_3": "获取天赋ID指令用法:/talent getid", + "lower_16": "无效的天赋等级,天赋等级应低于16。", + "set_id": "将天赋等级设为 %s。", + "set_atk": "将普通攻击等级设为 %s。", + "set_e": "设定天赋E等级为 %s。", + "set_q": "设定天赋Q等级为 %s。", + "invalid_skill_id": "无效的技能ID。", + "set_this": "将天赋等级设为 %s。", + "invalid_level": "无效的天赋等级。", + "normal_attack_id": "普通攻击的 ID 为 %s。", + "e_skill_id": "E技能ID %s。", + "q_skill_id": "Q技能ID %s。" + }, + "teleportAll": { + "success": "已将全部玩家传送到你的位置", + "error": "命令仅限多人游戏使用。" + }, + "teleport": { + "usage_server": "用法:/tp @ [scene id]", + "usage": "用法:/tp [@] [scene id]", + "specify_player_id": "你必须指定一个玩家ID。", + "invalid_position": "无效的位置。", + "success": "传送 %s 到坐标 %s,%s,%s,场景为 %s" + }, + "weather": { + "usage": "用法:weather [climateId]", + "success": "已将当前天气设定为 %s,气候则为 %s。", + "invalid_id": "无效的ID。" + }, + "drop": { + "command_usage": "用法:drop [amount]", + "success": "已將 %s x %s 丟在附近。" + }, + "help": { + "usage": "用法:", + "aliases": "別名:", + "available_commands": "可用指令:" + } + } +}