Commit a2ff8c84 authored by KingRainbow44's avatar KingRainbow44
Browse files

Merge `development` into `plugin-auth`

parents 3adf0d44 a751e71d
package emu.grasscutter.server.packet.send;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.WidgetGadgetAllDataNotifyOuterClass.WidgetGadgetAllDataNotify;
public class PacketWidgetGadgetAllDataNotify extends BasePacket {
public PacketWidgetGadgetAllDataNotify() {
super(PacketOpcodes.AllWidgetDataNotify);
WidgetGadgetAllDataNotify proto = WidgetGadgetAllDataNotify.newBuilder().build();
this.setData(proto);
}
}
package emu.grasscutter.server.packet.send;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.WidgetGadgetDataNotifyOuterClass;
import emu.grasscutter.net.proto.WidgetGadgetDataOuterClass;
import java.io.IOException;
import java.util.List;
public class PacketWidgetGadgetDataNotify extends BasePacket {
public PacketWidgetGadgetDataNotify(int gadgetId, List<Integer> gadgetEntityIdList) throws IOException {
super(PacketOpcodes.WidgetGadgetDataNotify);
WidgetGadgetDataNotifyOuterClass.WidgetGadgetDataNotify proto = WidgetGadgetDataNotifyOuterClass.WidgetGadgetDataNotify.newBuilder()
.setWidgetGadgetData(
WidgetGadgetDataOuterClass.WidgetGadgetData.newBuilder()
.setGadgetId(gadgetId)
.addAllGadgetEntityIdList(gadgetEntityIdList)
.build()
)
.build();
this.setData(proto);
}
}
package emu.grasscutter.server.packet.send;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.WidgetSlotChangeNotifyOuterClass;
import emu.grasscutter.net.proto.WidgetSlotDataOuterClass;
import emu.grasscutter.net.proto.WidgetSlotOpOuterClass;
public class PacketWidgetSlotChangeNotify extends BasePacket {
public PacketWidgetSlotChangeNotify(WidgetSlotChangeNotifyOuterClass.WidgetSlotChangeNotify proto) {
super(PacketOpcodes.WidgetSlotChangeNotify);
this.setData(proto);
}
public PacketWidgetSlotChangeNotify(WidgetSlotOpOuterClass.WidgetSlotOp op) {
super(PacketOpcodes.WidgetSlotChangeNotify);
WidgetSlotChangeNotifyOuterClass.WidgetSlotChangeNotify proto = WidgetSlotChangeNotifyOuterClass.WidgetSlotChangeNotify.newBuilder()
.setOp(op)
.setSlot(
WidgetSlotDataOuterClass.WidgetSlotData.newBuilder()
.setIsActive(true)
.build()
)
.build();
this.setData(proto);
}
public PacketWidgetSlotChangeNotify(int materialId) {
super(PacketOpcodes.WidgetSlotChangeNotify);
WidgetSlotChangeNotifyOuterClass.WidgetSlotChangeNotify proto = WidgetSlotChangeNotifyOuterClass.WidgetSlotChangeNotify.newBuilder()
.setSlot(
WidgetSlotDataOuterClass.WidgetSlotData.newBuilder()
.setIsActive(true)
.setMaterialId(materialId)
.build()
)
.build();
this.setData(proto);
}
}
......@@ -19,9 +19,11 @@ import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandMap;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.ResourceLoader;
import emu.grasscutter.data.custom.MainQuestData;
import emu.grasscutter.data.def.AvatarData;
import emu.grasscutter.data.def.ItemData;
import emu.grasscutter.data.def.MonsterData;
import emu.grasscutter.data.def.QuestData;
import emu.grasscutter.data.def.SceneData;
import emu.grasscutter.utils.Utils;
......@@ -88,7 +90,7 @@ public final class Tools {
final class ToolsWithLanguageOption {
@SuppressWarnings("deprecation")
public static void createGmHandbook(String language) throws Exception {
ResourceLoader.loadResources();
ResourceLoader.loadAll();
Map<Long, String> map;
try (InputStreamReader fileReader = new InputStreamReader(new FileInputStream(Utils.toFilePath(RESOURCE("TextMap/TextMap"+language+".json"))), StandardCharsets.UTF_8)) {
......@@ -150,6 +152,18 @@ final class ToolsWithLanguageOption {
writer.println();
writer.println("// Quests");
list = new ArrayList<>(GameData.getQuestDataMap().keySet());
Collections.sort(list);
for (Integer id : list) {
QuestData data = GameData.getQuestDataMap().get(id);
MainQuestData mainQuest = GameData.getMainQuestDataMap().get(data.getMainId());
writer.println(data.getId() + " : " + map.get(mainQuest.getTitleTextMapHash()) + " - " + map.get(data.getDescTextMapHash()));
}
writer.println();
writer.println("// Monsters");
list = new ArrayList<>(GameData.getMonsterDataMap().keySet());
Collections.sort(list);
......
......@@ -11,14 +11,18 @@ import static emu.grasscutter.Configuration.*;
public final class Crypto {
private static final SecureRandom secureRandom = new SecureRandom();
public static final long ENCRYPT_SEED = Long.parseUnsignedLong("11468049314633205968");
public static byte[] ENCRYPT_SEED_BUFFER = new byte[0];
public static byte[] DISPATCH_KEY;
public static byte[] DISPATCH_SEED;
public static byte[] ENCRYPT_KEY;
public static long ENCRYPT_SEED = Long.parseUnsignedLong("11468049314633205968");
public static byte[] ENCRYPT_SEED_BUFFER = new byte[0];
public static void loadKeys() {
DISPATCH_KEY = FileUtils.read(KEYS_FOLDER + "/dispatchKey.bin");
DISPATCH_SEED = FileUtils.read(KEYS_FOLDER + "/dispatchSeed.bin");
ENCRYPT_KEY = FileUtils.read(KEYS_FOLDER + "/secretKey.bin");
ENCRYPT_SEED_BUFFER = FileUtils.read(KEYS_FOLDER + "/secretKeyBuffer.bin");
}
......
......@@ -160,7 +160,9 @@ public final class Language {
JsonObject object = this.languageData;
int index = 0;
String result = "This value does not exist. Please report this to the Discord: " + key;
String valueNotFoundPattern = "This value does not exist. Please report this to the Discord: ";
String result = valueNotFoundPattern + key;
boolean isValueFound = false;
while (true) {
if(index == keys.length) break;
......@@ -171,11 +173,19 @@ public final class Language {
if(element.isJsonObject())
object = element.getAsJsonObject();
else {
isValueFound = true;
result = element.getAsString(); break;
}
} else break;
}
if (!isValueFound && !languageCode.equals("en-US")) {
var englishValue = Grasscutter.getLanguage("en-US").get(key);
if (!englishValue.contains(valueNotFoundPattern)) {
result += "\nhere is english version:\n" + englishValue;
}
}
this.cachedTranslations.put(key, result); return result;
}
......
......@@ -12,6 +12,8 @@ import emu.grasscutter.Grasscutter;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import org.slf4j.Logger;
......@@ -304,6 +306,71 @@ public final class Utils {
return map;
}
/**
* Performs a linear interpolation using a table of fixed points to create an effective piecewise f(x) = y function.
* @param x
* @param xyArray Array of points in [[x0,y0], ... [xN, yN]] format
* @return f(x) = y
*/
public static int lerp(int x, int[][] xyArray) {
try {
if (x <= xyArray[0][0]){ // Clamp to first point
return xyArray[0][1];
} else if (x >= xyArray[xyArray.length-1][0]) { // Clamp to last point
return xyArray[xyArray.length-1][1];
}
// At this point we're guaranteed to have two lerp points, and pity be somewhere between them.
for (int i=0; i < xyArray.length-1; i++) {
if (x == xyArray[i+1][0]) {
return xyArray[i+1][1];
}
if (x < xyArray[i+1][0]) {
// We are between [i] and [i+1], interpolation time!
// Using floats would be slightly cleaner but we can just as easily use ints if we're careful with order of operations.
int position = x - xyArray[i][0];
int fullDist = xyArray[i+1][0] - xyArray[i][0];
int prevValue = xyArray[i][1];
int fullDelta = xyArray[i+1][1] - prevValue;
return prevValue + ( (position * fullDelta) / fullDist );
}
}
} catch (IndexOutOfBoundsException e) {
Grasscutter.getLogger().error("Malformed lerp point array. Must be of form [[x0, y0], ..., [xN, yN]].");
}
return 0;
}
/**
* Checks if an int is in an int[]
* @param key int to look for
* @param array int[] to look in
* @return key in array
*/
public static boolean intInArray(int key, int[] array) {
for (int i : array) {
if (i == key) {
return true;
}
}
return false;
}
/**
* Return a copy of minuend without any elements found in subtrahend.
* @param minuend The array we want elements from
* @param subtrahend The array whose elements we don't want
* @return The array with only the elements we want, in the order that minuend had them
*/
public static int[] setSubtract(int[] minuend, int[] subtrahend) {
IntList temp = new IntArrayList();
for (int i : minuend) {
if (!intInArray(i, subtrahend)) {
temp.add(i);
}
}
return temp.toIntArray();
}
/**
* Gets the language code from a given locale.
* @param locale A locale.
......
......@@ -16,6 +16,9 @@
"no_keystore_error": "[Dispatch] No SSL cert found! Falling back to HTTP server.",
"default_password": "[Dispatch] The default keystore password was loaded successfully. Please consider setting the password to 123456 in config.json."
},
"authentication": {
"default_unable_to_verify": "[Authentication] Something called the verifyUser method which is unavailable in the default authentication handler"
},
"no_commands_error": "Commands are not supported in dispatch only mode.",
"unhandled_request_error": "[Dispatch] Potential unhandled %s request: %s",
"account": {
......@@ -46,7 +49,8 @@
"run_mode_error": "Invalid server run mode: %s.",
"run_mode_help": "Server run mode must be 'HYBRID', 'DISPATCH_ONLY', or 'GAME_ONLY'. Unable to start Grasscutter...",
"create_resources": "Creating resources folder...",
"resources_error": "Place a copy of 'BinOutput' and 'ExcelBinOutput' in the resources folder."
"resources_error": "Place a copy of 'BinOutput' and 'ExcelBinOutput' in the resources folder.",
"version": "Grasscutter version: %s-%s"
}
},
"commands": {
......@@ -173,6 +177,10 @@
"success": "All characters have been healed.",
"description": "Heal all characters in your current team."
},
"join": {
"usage": "Usage: join [AvatarIDs] such as\"join 10000038 10000039\"",
"description": "force join avatar into your team"
},
"kick": {
"player_kick_player": "Player [%s:%s] has kicked player [%s:%s]",
"server_kick_player": "Kicking player [%s:%s]",
......@@ -212,11 +220,24 @@
"success": "Coordinates: %s, %s, %s\nScene id: %s",
"description": "Get coordinates."
},
"quest": {
"description": "Add or finish quests",
"usage": "quest <add|finish> [quest id]",
"added": "Quest %s added",
"finished": "Finished quest %s",
"not_found": "Quest not found",
"invalid_id": "Invalid quest id"
},
"reload": {
"reload_start": "Reloading config.",
"reload_done": "Reload complete.",
"description": "Reload server config"
},
"remove": {
"usage": "Usage: remove [indexOfYourTeams] index start from 1",
"invalid_index": "index start from 1",
"description": "force remove avatar into your team"
},
"resetConst": {
"reset_all": "Reset all avatars' constellations.",
"success": "Constellations for %s have been reset. Please relog to see changes.",
......@@ -349,5 +370,14 @@
"resetshop": {
"description": "reset shop"
}
},
"gacha": {
"details": {
"title": "Banner Details",
"available_five_stars": "Available 5-star Items",
"available_four_stars": "Available 4-star Items",
"available_three_stars": "Available 3-star Items",
"template_missing": "data/gacha_details.html is missing."
}
}
}
......@@ -45,7 +45,8 @@
"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."
"resources_error": "Umieść kopię 'BinOutput' i 'ExcelBinOutput' w folderze resources.",
"version": "Grasscutter versión: %s-%s"
}
},
"commands": {
......@@ -301,5 +302,14 @@
"resetshop": {
"description": "zresetuj sklep"
}
},
"gacha": {
"details": {
"title": "Banner Details",
"available_five_stars": "Available 5-star Items",
"available_four_stars": "Available 4-star Items",
"available_three_stars": "Available 3-star Items",
"template_missing": "data/gacha_details.html is missing."
}
}
}
\ No newline at end of file
......@@ -16,11 +16,14 @@
"no_keystore_error": "[Dispatch] 未找到 SSL 证书!已降级到 HTTP 模式",
"default_password": "[Dispatch] 成功加载 keystore 默认密码。请考虑将 config.json 的默认密码设置为 123456"
},
"authentication": {
"default_unable_to_verify": "[Authentication] 称为 verifyUser 的方法在默认验证程序中不可用"
},
"no_commands_error": "此命令不适用于 Dispatch-only 模式",
"unhandled_request_error": "[Dispatch] 潜在的未处理请求:%s %s",
"account": {
"login_attempt": "[Dispatch] 客户端 %s 正在尝试登录",
"login_success": "[Dispatch] 客户端 %s 已登录,UID为 %s",
"login_success": "[Dispatch] 客户端 %s 已登录,UID 为 %s",
"login_token_attempt": "[Dispatch] 客户端 %s 正在尝试使用 token 登录",
"login_token_error": "[Dispatch] 客户端 %s 使用 token 登录失败",
"login_token_success": "[Dispatch] 客户端 %s 已通过 token 登录,UID 为 %s",
......@@ -45,7 +48,8 @@
"run_mode_error": "无效的服务器运行模式:%s。",
"run_mode_help": "服务器运行模式必须为 HYBRID、DISPATCH_ONLY 或 GAME_ONLY。Grasscutter 启动失败...",
"create_resources": "正在创建 resources 目录...",
"resources_error": "请将 BinOutput 和 ExcelBinOutput 复制到 resources 目录。"
"resources_error": "请将 BinOutput 和 ExcelBinOutput 复制到 resources 目录。",
"version": "Grasscutter 版本: %s-%s"
}
},
"commands": {
......@@ -55,7 +59,7 @@
"permission_error": "哼哼哼!你没有执行此命令的权限!请联系服务器管理员解决!",
"console_execute_error": "此命令只能在服务器控制台执行呐~",
"player_execute_error": "此命令只能在游戏内执行哦~",
"command_exist_error": "这条命令……好像找不到呢?",
"command_exist_error": "这条命令...好像找不到呢?",
"no_description_specified": "没有指定说明",
"invalid": {
"amount": "无效的数量。",
......@@ -80,7 +84,7 @@
"player_exist_offline_error": "玩家不存在或已离线。",
"argument_error": "无效的参数。",
"clear_target": "目标已清除。",
"set_target": "随后的的命令都会以@%s为预设。",
"set_target": "随后的的命令都会以 @%s 为预设。",
"need_target": "此命令需要一个目标 UID。添加 <@UID> 参数或使用 /target @UID 来指定默认目标。"
},
"status": {
......@@ -96,20 +100,20 @@
"create": "已创建账号,UID 为 %s。",
"delete": "账号已删除。",
"no_account": "账号不存在。",
"command_usage": "用法:account <create|delete> <用户名> [uid]",
"description": "创建或删除账号"
"command_usage": "用法:account <create|delete> <用户名> [UID]",
"description": "创建或删除账号"
},
"broadcast": {
"command_usage": "用法:broadcast <消息>",
"message_sent": "公告已发送。",
"description": "向所有玩家发送公告"
"description": "向所有玩家发送公告"
},
"changescene": {
"usage": "用法:changescene <场景ID>",
"already_in_scene": "你已经在这个场景中了。",
"success": "已切换至场景 %s。",
"exists_error": "此场景不存在。",
"description": "切换指定场景"
"description": "切换指定场景"
},
"clear": {
"command_usage": "用法:clear <all|wp|art|mat>\nall: 所有, wp: 武器, art: 圣遗物, mat: 材料",
......@@ -120,83 +124,87 @@
"displays": "已清空 %s 的屏幕。",
"virtuals": "已清除 %s 的所有货币和经验值。",
"everything": "已清除 %s 的所有物品。",
"description": "从你的背包中删除所有未装备且已解锁的物品,包括稀有物品"
"description": "从你的背包中删除所有未装备且已解锁的物品,包括稀有物品"
},
"coop": {
"usage": "用法:coop <玩家ID> <目标玩家ID>",
"success": "已强制传送 %s 到 %s 的世界",
"description": "强制传送指定用户到他人的世界"
"success": "已强制传送 %s 到 %s 的世界",
"description": "强制传送指定用户到他人的世界"
},
"enter_dungeon": {
"usage": "用法:enterdungeon <秘境ID>",
"changed": "已进入秘境 %s",
"changed": "已进入秘境 %s",
"not_found_error": "此秘境不存在。",
"in_dungeon_error": "你已经在秘境中了。",
"description": "进入指定秘境"
"description": "进入指定秘境"
},
"giveAll": {
"usage": "用法:giveall [玩家] [数量]",
"started": "正在给予全部物品...",
"success": "已给予 %s 全部物品。",
"invalid_amount_or_playerId": "无效的数量/玩家ID。",
"description": "给予所有物品"
"description": "给予所有物品"
},
"giveArtifact": {
"usage": "用法:giveart|gart [玩家] <圣遗物ID> <主词条ID> [<副词条ID>[,<强化次数>]]... [等级]",
"id_error": "无效的圣遗物ID。",
"success": "已将 %s 给予 %s。",
"description": "给予指定圣遗物"
"description": "给予指定圣遗物"
},
"giveChar": {
"usage": "用法:givechar <玩家> <角色ID|角色名> [数量]",
"given": "已将角色 %s (等级 %s) 给与 %s。",
"given": "已将角色 %s [等级 %s] 给与 %s。",
"invalid_avatar_id": "无效的角色ID。",
"invalid_avatar_level": "无效的角色等级。",
"invalid_avatar_or_player_id": "无效的角色ID/玩家ID。",
"description": "给予指定角色"
"description": "给予指定角色"
},
"give": {
"usage": "用法:give <玩家> <物品ID|物品名> [数量] [等级] [精炼等级]",
"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",
"description": "给予指定物品"
"given_with_level_and_refinement": "已将 %s [等级 %s, 精炼 %s] %s 个给予 %s",
"given_level": "已将 %s [等级 %s] %s 个给予 %s",
"description": "给予指定物品"
},
"godmode": {
"success": "%s 的无敌模式已被设置为 %s。",
"description": "防止你受到伤害"
"success": "上帝模式已设为 %s。[用户:%s]",
"description": "防止你受到伤害"
},
"heal": {
"success": "已经治疗所有角色。",
"description": "治疗当前队伍的角色。"
"success": "已治疗所有角色。",
"description": "治疗当前队伍的角色"
},
"join": {
"usage": "用法:join <角色IDs> 例如\"join 10000038 10000039\"空格分开",
"description": "强制将角色加入到当前队伍中"
},
"kick": {
"player_kick_player": "玩家 [%s:%s] 已将 [%s:%s] 踢出",
"server_kick_player": "正在踢出玩家 [%s:%s]",
"description": "从服务器内踢出指定玩家"
"player_kick_player": "玩家 [%s:%s] 已将 [%s:%s] 踢出",
"server_kick_player": "正在踢出玩家 [%s:%s]...",
"description": "从服务器内踢出指定玩家"
},
"kill": {
"usage": "用法:killall [玩家UID] [场景ID]",
"scene_not_found_in_player_world": "未在玩家世界中找到此场景",
"scene_not_found_in_player_world": "未在玩家世界中找到此场景",
"kill_monsters_in_scene": "已杀死场景 %s 中的 %s 个怪物。",
"description": "杀死所有怪物"
"description": "杀死所有怪物"
},
"killCharacter": {
"usage": "用法:/killcharacter [玩家ID]",
"success": "已杀死 %s 当前角色。",
"description": "杀死当前角色"
"description": "杀死当前角色"
},
"language": {
"current_language": "当前语言是: %s",
"language_changed": "语言切换至: %s",
"language_not_found": "目前服务端没有这种语言: %s",
"description": "显示或切换当前语言"
"description": "显示或切换当前语言"
},
"list": {
"success": "目前在线人数:%s",
"description": "查看所有玩家"
"description": "查看所有玩家"
},
"permission": {
"usage": "用法:permission <add|remove> <用户名> <权限>",
......@@ -205,25 +213,38 @@
"remove": "权限已移除。",
"not_have_error": "此玩家未拥有权限!",
"account_error": "账号不存在。",
"description": "添加或移除指定玩家的权限"
"description": "添加或移除指定玩家的权限"
},
"position": {
"success": "坐标:%s, %s, %s\n场景ID:%s",
"description": "获取所在位置。"
"description": "获取所在位置"
},
"quest": {
"description": "添加或完成任务",
"usage": "quest <add|finish> [任务ID]",
"added": "已添加任务 %s",
"finished": "已完成任务 %s",
"not_found": "未找到任务",
"invalid_id": "无效的任务ID"
},
"reload": {
"reload_start": "正在重载配置文件和数据。",
"reload_done": "重载完成。",
"description": "重载配置文件和数据。"
"description": "重载配置文件和数据"
},
"remove": {
"usage": "用法: remove [indexOfYourTeams] 从1开始",
"invalid_index": "下标从1开始",
"description": "强制移除队内角色"
},
"resetConst": {
"reset_all": "重置所有角色的命座。",
"success": "已重置 %s 的命座,重新登录后生效。",
"description": "重置当前角色的命之座,执行命令后需重新登录以生效"
"description": "重置当前角色的命之座,执行命令后需重新登录以生效"
},
"resetShopLimit": {
"usage": "用法:/resetshop <玩家ID>",
"description": "重置所选玩家的商店刷新时间"
"description": "重置所选玩家的商店刷新时间"
},
"sendMail": {
"usage": "用法:give [玩家] <物品ID|物品名称> [数量]",
......@@ -246,19 +267,19 @@
"sender": "<发件人>",
"arguments": "<物品ID|物品名称|finish> [数量] [等级]",
"error": "错误:无效的编写阶段 %s。需要 StackTrace 请查看服务器控制台。",
"description": "向指定用户发送邮件。此命令的用法可根据附加的参数而变化"
"description": "向指定用户发送邮件。此命令的用法可根据附加的参数而变化"
},
"sendMessage": {
"usage": "用法:sendmessage <玩家> <消息>",
"success": "消息已发送。",
"description": "向指定玩家发送消息"
"description": "向指定玩家发送消息"
},
"setFetterLevel": {
"usage": "用法:setfetterlevel <好感度等级>",
"range_error": "好感度等级必须在 0 到 10 之间。",
"success": "好感度已设为 %s 级",
"success": "好感度已设为 %s 级",
"level_error": "无效的好感度等级。",
"description": "设置当前角色的好感度等级"
"description": "设置当前角色的好感度等级"
},
"setStats": {
"usage_console": "用法:setstats|stats @<UID> <属性> <数值>",
......@@ -268,29 +289,29 @@
"uid_error": "无效的UID。",
"player_error": "玩家不存在或已离线。",
"set_self": "%s 已设为 %s。",
"set_for_uid": "%s (来自 %s) 设置为 %s。",
"set_for_uid": "%s [来自 %s] 已设为 %s。",
"set_max_hp": "最大生命值已设为 %s。",
"description": "设置当前角色的属性"
"description": "设置当前角色的属性"
},
"setWorldLevel": {
"usage": "用法:setworldlevel <等级>",
"value_error": "世界等级必须设置在0-8之间。",
"success": "已将世界等级设为 %s。",
"success": "世界等级设为 %s。",
"invalid_world_level": "无效的世界等级。",
"description": "设置世界等级,执行命令后需重新登录以生效"
"description": "设置世界等级,执行命令后需重新登录以生效"
},
"spawn": {
"usage": "用法:spawn <实体ID> [数量] [等级(仅怪物)]",
"success": "已生成 %s 个 %s。",
"description": "在你附近生成一个生物"
"description": "在你附近生成一个生物"
},
"stop": {
"success": "正在关闭服务器...",
"description": "停止服务器"
"description": "停止服务器"
},
"talent": {
"usage_1": "设置天赋等级:/talent set <天赋ID> <数值>",
"usage_2": "另一种设置天赋等级的方法:/talent <n (普) | e (元素战技) | q (元素爆发)> <数值>",
"usage_2": "另一种设置天赋等级的方法:/talent <n (普通攻击) | e (元素战技) | q (元素爆发)> <数值>",
"usage_3": "获取天赋ID:/talent getid",
"lower_16": "无效的天赋等级,天赋等级应小于等于15。",
"set_id": "将天赋等级设为 %s。",
......@@ -303,20 +324,20 @@
"normal_attack_id": "普通攻击的 ID 为 %s。",
"e_skill_id": "元素战技ID %s。",
"q_skill_id": "元素爆发ID %s。",
"description": "设置当前角色的天赋等级"
"description": "设置当前角色的天赋等级"
},
"teleportAll": {
"success": "已将所有玩家传送到你的位置",
"success": "已将所有玩家传送到你的位置",
"error": "你只能在多人游戏状态下执行此命令。",
"description": "将你世界中的所有玩家传送到你所在的位置"
"description": "将你世界中的所有玩家传送到你所在的位置"
},
"teleport": {
"usage_server": "用法:/tp @<玩家ID> <x> <y> <z> [场景ID]",
"usage": "用法:/tp [@<玩家ID>] <x> <y> <z> [场景ID]",
"specify_player_id": "你必须指定一个玩家ID。",
"invalid_position": "无效的位置。",
"success": "传送 %s 到坐标 %s,%s,%s,场景为 %s",
"description": "改变指定玩家的位置"
"success": "传送 %s 到坐标 %s,%s,%s,场景为 %s",
"description": "改变指定玩家的位置"
},
"tower": {
"unlock_done": "深境回廊的所有层已全部解锁。"
......@@ -325,28 +346,37 @@
"usage": "用法:weather <天气ID> [气候ID]",
"success": "已更改天气为 %s,气候为 %s。",
"invalid_id": "无效的天气ID。",
"description": "更改天气"
"description": "更改天气"
},
"drop": {
"command_usage": "用法:drop <物品ID|物品名称> [数量]",
"success": "已丢下 %s 个 %s。",
"description": "在你附近丢下一个物品"
"description": "在你附近丢下一个物品"
},
"help": {
"usage": "用法:",
"aliases": "别名:",
"available_commands": "可用命令:",
"description": "发送帮助信息或显示指定命令的信息"
"description": "发送帮助信息或显示指定命令的信息"
},
"restart": {
"description": "重新启动服务器"
"description": "重新启动服务器"
},
"unlocktower": {
"success": "解锁完成。",
"description": "解锁深境螺旋的所有层"
"description": "解锁深境螺旋的所有层"
},
"resetshop": {
"description": "重置商店刷新时间。"
"description": "重置商店刷新时间"
}
},
"gacha": {
"details": {
"title": "祈愿详情",
"available_five_stars": "可获得的5星物品",
"available_four_stars": "可获得的4星物品",
"available_three_stars": "可获得的3星物品",
"template_missing": "缺失文件:data/gacha_details.html"
}
}
}
......@@ -16,6 +16,9 @@
"no_keystore_error": "[Dispatch] 未找到 SSL 憑證!已後降到 HTTP 伺服器。",
"default_password": "[Dispatch] 默認的 keystore 密碼加載成功。請考慮將 config.json 的憑證密碼設定成 123456。"
},
"authentication": {
"default_unable_to_verify": "[驗證系統] 稱為 verifyUser 方法的東西在默認身份驗證程序中不可用。"
},
"no_commands_error": "此指令不適用於Dispatch-only模式。",
"unhandled_request_error": "[Dispatch] 潛在的未處理請求 %s 請求:%s",
"account": {
......@@ -45,7 +48,8 @@
"run_mode_error": "無效的伺服器運行模式: %s。",
"run_mode_help": "伺服器運行模式必須為 HYBRID 或者 DISPATCH_ONLY 或者 GAME_ONLY。Grasscutter 啟動失敗...",
"create_resources": "正在建立 resources 資料夾...",
"resources_error": "請將 BinOutput 和 ExcelBinOutput 複製到 resources 資料夾。"
"resources_error": "請將 BinOutput 和 ExcelBinOutput 複製到 resources 資料夾。",
"version": "Grasscutter版本: %s-%s"
}
},
"commands": {
......@@ -56,6 +60,7 @@
"console_execute_error": "此指令只能在伺服器的命令提示字元執行。",
"player_execute_error": "請在遊戲裡使用這條指令。",
"command_exist_error": "找不到指令。",
"no_description_specified": "没有指定說明。",
"invalid": {
"amount": "無效的數量。",
"artifactId": "無效的聖遺物ID。",
......@@ -95,17 +100,20 @@
"create": "已建立帳號,UID 為 %s 。",
"delete": "帳號已刪除。",
"no_account": "帳號不存在。",
"command_usage": "用法:account <create|delete> <username> [uid]"
"command_usage": "用法:account <create|delete> <username> [uid]",
"description": "建立或刪除帳號。"
},
"broadcast": {
"command_usage": "用法:broadcast <message>",
"message_sent": "公告已發送。"
"message_sent": "公告已發送。",
"description": "向所有玩家發送公告。"
},
"changescene": {
"usage": "用法:changescene <scene id>",
"already_in_scene": "你已經在這個場景中了。",
"success": "已切換至場景 %s.",
"exists_error": "此場景不存在。"
"exists_error": "此場景不存在。",
"description": "切換指定場景。"
},
"clear": {
"command_usage": "用法: clear <all|wp|art|mat>",
......@@ -115,35 +123,41 @@
"furniture": "已將 %s 的塵歌壺家具清空。",
"displays": "已清除 %s 的顯示。",
"virtuals": "已將 %s 的所有貨幣和經驗值清空。",
"everything": "已將 %s 的所有物品清空。"
"everything": "已將 %s 的所有物品清空。",
"description": "從你的背包中刪除所有未裝備且未上鎖的物品,包括稀有物品。"
},
"coop": {
"usage": "用法:coop <playerId> <target playerId>",
"success": "召喚了 %s 到 %s 的世界。"
"success": "召喚了 %s 到 %s 的世界。",
"description": "強制傳送指定用戶到他人的世界。"
},
"enter_dungeon": {
"usage": "用法:enterdungeon <dungeon id>",
"changed": "已進入副本 %s",
"not_found_error": "此副本不存在。",
"in_dungeon_error": "你已經在祕境中了。"
"changed": "已進入祕境 %s",
"not_found_error": "此祕境不存在。",
"in_dungeon_error": "你已經在祕境中了。",
"description": "進入指定祕境。"
},
"giveAll": {
"usage": "用法:giveall [player] [amount]",
"started": "正在賦予全部物品...",
"success": "已賦予全部物品。",
"invalid_amount_or_playerId": "無效的數量/玩家ID。"
"invalid_amount_or_playerId": "無效的數量/玩家ID。",
"description": "賦予所有物品。"
},
"giveArtifact": {
"usage": "用法:giveart|gart [player] <artifactId> <mainPropId> [<appendPropId>[,<times>]]... [level]",
"id_error": "無效的聖遺物ID。",
"success": "已把 %s 給予 %s。"
"success": "已把 %s 給予 %s。",
"description": "給予指定聖遺物。"
},
"giveChar": {
"usage": "用法:givechar <player> <itemId|itemName> [amount]",
"given": "已將 %s 等級 %s 給予 %s。",
"invalid_avatar_id": "無效的角色ID。",
"invalid_avatar_level": "無效的角色等級。.",
"invalid_avatar_or_player_id": "無效的角色ID/玩家ID。"
"invalid_avatar_or_player_id": "無效的角色ID/玩家ID。",
"description": "給予指定角色。"
},
"give": {
"usage": "用法:give <player> <itemId|itemName> [amount] [level]",
......@@ -151,29 +165,42 @@
"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 個給予 %s",
"description": "給予指定物品。"
},
"godmode": {
"success": "上帝模式設定為 %s 。 [用戶:%s]"
"success": "上帝模式設定為 %s 。 [用戶:%s]",
"description": "防止你受到傷害。"
},
"heal": {
"success": "所有角色已被治療。"
"success": "所有角色已被治療。",
"description": "治療當前隊伍的角色。"
},
"kick": {
"player_kick_player": "玩家 [%s:%s] 已把 [%s:%s] 踢出",
"server_kick_player": "正在踢出玩家 [%s:%s]"
"server_kick_player": "正在踢出玩家 [%s:%s]",
"description": "從伺服器內踢出指定玩家。"
},
"kill": {
"usage": "用法:killall [playerUid] [sceneId]",
"scene_not_found_in_player_world": "未在玩家世界中找到此場景",
"kill_monsters_in_scene": "已殺死 %s 個怪物。 [場景ID: %s]"
"kill_monsters_in_scene": "已殺死 %s 個怪物。 [場景ID: %s]",
"description": "殺死所有怪物。"
},
"killCharacter": {
"usage": "用法:/killcharacter [playerId]",
"success": "已殺死 %s 目前的場上角色。"
"success": "已殺死 %s 目前的場上角色。",
"description": "殺死玩家目前使用的場上角色。"
},
"language": {
"current_language": "當前語言是: %s",
"language_changed": "語言切換至: %s",
"language_not_found": "目前客戶端沒有這種語言: %s",
"description": "顯示或切換當前語言。"
},
"list": {
"success": "目前總線上人數:%s"
"success": "目前總線上人數:%s" ,
"description": "查看所有在線玩家"
},
"permission": {
"usage": "用法:permission <add|remove> <username> <permission>",
......@@ -181,21 +208,39 @@
"has_error": "此玩家已擁有權限!",
"remove": "權限已移除。",
"not_have_error": "此玩家未擁有權限!",
"account_error": "The account cannot be found."
"account_error": "帳號不存在。",
"description": "指派或移除指定玩家的權限。"
},
"position": {
"success": "坐標:%s, %s, %s\n場景ID:%s"
"success": "座標:%s, %s, %s\n場景ID:%s",
"description": "獲取目前所在位置的座標。"
},
"quest": {
"description": "添加或完成任務",
"usage": "quest <add|finish> [任務ID]",
"added": "已添加任務 %s",
"finished": "已完成任務 %s",
"not_found": "未找到任務",
"invalid_id": "無效的任務ID"
},
"reload": {
"reload_start": "正在重新加載設定檔。",
"reload_done": "重新加載已完成。"
"reload_done": "重新加載已完成。",
"description": "重新加載設定檔和數據。"
},
"remove": {
"usage": "用法: remove [indexOfYourTeams] 从1开始",
"invalid_index": "下標從1開始",
"description": "强制移除對内角色"
},
"resetConst": {
"reset_all": "重設所有角色的命座。",
"success": "已重設 %s 的命座,重新登入後將會生效。"
"success": "已重設 %s 的命座,重新登入後將會生效。",
"description": "重置當前角色的命之座,重新登入後將會生效。"
},
"resetShopLimit": {
"usage": "用法:/resetshop <player id>"
"usage": "用法:/resetshop <player id>",
"description": "重置所選玩家的商店刷新時間。"
},
"sendMail": {
"usage": "用法:give [player] <itemId|itemName> [amount]",
......@@ -217,17 +262,20 @@
"message": "<正文>",
"sender": "<寄件者>",
"arguments": "<itemId|itemName|finish> [數量] [等級]",
"error": "錯誤:無效的編寫階段 %s。需要 stacktrace 請查看伺服器命令提示字元。"
"error": "錯誤:無效的編寫階段 %s。需要 stacktrace 請查看伺服器命令提示字元。",
"description": "向指定用戶發送郵件。此指令的用法可根據附加的參數而改變。"
},
"sendMessage": {
"usage": "用法:sendmessage <player> <message>",
"success": "訊息已發送。"
"success": "訊息已發送。",
"description": "向指定玩家發送訊息。"
},
"setFetterLevel": {
"usage": "用法:setfetterlevel <level>",
"range_error": "好感度必須在 0 到 10 之間。",
"success": "好感等級已設定為 %s",
"level_error": "無效的好感度。"
"level_error": "無效的好感度。",
"description": "設定當前角色的好感度等級。"
},
"setStats": {
"usage_console": "用法:setstats|stats @<UID> <stat> <value>",
......@@ -238,68 +286,93 @@
"player_error": "玩家不存在或已離線。",
"set_self": "%s 已經設為 %s。",
"set_for_uid": "%s 的使用者 %s 更改為 %s。",
"set_max_hp": "最大生命值更改為 %s。"
"set_max_hp": "最大生命值更改為 %s。",
"description": "設定當前角色的數據類型。"
},
"setWorldLevel": {
"usage": "用法:setworldlevel <level>",
"value_error": "世界等級必須設定在0-8之間。",
"success": "已將世界等級設為%s。",
"invalid_world_level": "無效的世界等級。"
"invalid_world_level": "無效的世界等級。",
"description": "設定世界等級,執行指令後需重新登入後才會生效。"
},
"spawn": {
"usage": "用法:spawn <entityId> [amount] [level(僅限怪物)]",
"success": "已生成 %s 個 %s。"
"success": "已生成 %s 個 %s。",
"description": "在你附近生成一個實體動物。"
},
"stop": {
"success": "正在關閉伺服器..."
"success": "正在關閉伺服器...",
"description": "以正常的方式關閉伺服器。"
},
"talent": {
"usage_1": "設定天賦等級:/talent set <talentID> <value>",
"usage_2": "另一種設定天賦等級的指令使用方法:/talent <n or e or q> <value>",
"usage_3": "獲取天賦ID指令用法:/talent getid",
"lower_16": "無效的技能等級,技能等級應低於 16。",
"lower_16": "無效的天賦等級,技能等級應低於 16。",
"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。",
"description": "設定當前角色的天賦等級"
},
"teleportAll": {
"success": "召喚了所有玩家到你的位置上。",
"error": "此指令僅可在多人遊戲下可用。"
"error": "此指令僅可在多人遊戲下可用。",
"description": "將你世界裡的所有玩家傳送到你目前的所在位置。"
},
"teleport": {
"usage_server": "用法:/tp @<player id> <x> <y> <z> [scene id]",
"usage": "用法:/tp [@<player id>] <x> <y> <z> [scene id]",
"specify_player_id": "你必須指定一個玩家ID。",
"invalid_position": "無效的位置。",
"success": "傳送 %s 到坐標 %s,%s,%s ,場景為 %s"
"invalid_position": "無效的座標。",
"success": "傳送 %s 到座標 %s,%s,%s ,場景為 %s 。",
"description": "將玩家的位置傳送到你所指定的座標。"
},
"tower": {
"unlock_done": "解鎖所有級別的深境螺旋已全部解鎖。"
},
"weather": {
"usage": "用法:weather <weatherId> [climateId]",
"success": "已將當前天氣設定為 %s ,氣候則為 %s 。",
"invalid_id": "無效的ID。"
"invalid_id": "無效的ID。",
"description": "更改目前的天氣。"
},
"drop": {
"command_usage": "用法:drop <itemId|itemName> [amount]",
"success": "已將 %s x %s 丟在附近。"
"success": "已將 %s x %s 丟在附近。",
"description": "在你附近丟下一個物品。"
},
"help": {
"usage": "用法:",
"aliases": "別名:",
"description": "發送幫助信息或顯示特定命令的信息",
"available_commands": "可用指令:"
},
"restart": {
"description": "重新啟動伺服器。"
},
"unlocktower": {
"success": "解鎖完成。",
"description": "解鎖所有級別的深境螺旋"
"description": "解鎖所有級別的深境螺旋"
},
"resetshop": {
"description": "重置商店時間"
"description": "重置商店刷新時間。"
}
},
"gacha": {
"details": {
"title": "祈願詳情",
"available_five_stars": "可獲得的5星物品",
"available_four_stars": "可獲得的4星物品",
"available_three_stars": "可獲得的3星物品",
"template_missing": "data/gacha_details.html 不存在。"
}
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment