diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 017ccf5..a58de96 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,14 +9,19 @@ on: jobs: build: runs-on: windows-latest + strategy: + matrix: + target_type: + - server + # - client steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - uses: xmake-io/github-action-setup-xmake@v1 # with: # xmake-version: branch@master - - - uses: actions/cache@v4 + + - uses: actions/cache@v6 with: path: | ~/AppData/Local/.xmake @@ -24,25 +29,30 @@ jobs: restore-keys: | xmake- + - name: Setup LLVM + uses: ZhongRuoyu/setup-llvm@v0 + with: + llvm-version: 22 + - run: | xmake repo -u - run: | - xmake f -a x64 -m release -p windows -v -y + xmake f -a x64 -m release -p windows -v --target_type=${{ matrix.target_type }} -y - run: | xmake -v -y - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: - name: ${{ github.event.repository.name }}-windows-x64-${{ github.sha }} + name: ${{ github.event.repository.name }}-${{ github.sha }}-${{ matrix.target_type }}-windows-x64 path: | bin/ # clang-format: - # runs-on: windows-latest + # runs-on: windows-2022 # steps: - # - uses: actions/checkout@v4 + # - uses: actions/checkout@v7 # - run: | # choco install llvm -y --version=17.0.6 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a64b126..5810556 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,19 +6,25 @@ on: jobs: build: runs-on: windows-latest + strategy: + matrix: + target_type: + - server + # - client steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - uses: xmake-io/github-action-setup-xmake@v1 # with: # xmake-version: branch@master - - uses: matbour/setup-sentry-cli@v1 + + - uses: matbour/setup-sentry-cli@v2 with: token: ${{ SECRETS.SENTRY_TOKEN }} organization: ${{ SECRETS.SENTRY_ORG }} project: ${{ SECRETS.SENTRY_PROJECT }} - - - uses: actions/cache@v4 + + - uses: actions/cache@v6 with: path: | ~/AppData/Local/.xmake @@ -26,20 +32,25 @@ jobs: restore-keys: | xmake- + - name: Setup LLVM + uses: ZhongRuoyu/setup-llvm@v0 + with: + llvm-version: 22 + - run: | xmake repo -u - run: | - xmake f -a x64 -m release -p windows -y --publish=y + xmake f -a x64 -m release -p windows --target_type=${{ matrix.target_type }} -y --publish=y - run: | xmake -w -y - run: | sentry-cli debug-files upload --include-sources bin/LK-Stats - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: - name: ${{ github.event.repository.name }}-windows-x64-${{ github.sha }} + name: ${{ github.event.repository.name }}-${{ github.sha }}-${{ matrix.target_type }}-windows-x64 path: | bin/ @@ -48,12 +59,12 @@ jobs: contents: write runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - id: extract-release-notes - uses: ffurrer2/extract-release-notes@v2 + uses: ffurrer2/extract-release-notes@v3 - - uses: softprops/action-gh-release@v1 + - uses: softprops/action-gh-release@v3 with: body: | ${{ steps.extract-release-notes.outputs.release_notes }} @@ -64,23 +75,28 @@ jobs: - update-release-notes permissions: contents: write + strategy: + matrix: + target_type: + - server + # - client runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v8 with: - name: ${{ github.event.repository.name }}-windows-x64-${{ github.sha }} + name: ${{ github.event.repository.name }}-${{ github.sha }}-${{ matrix.target_type }}-windows-x64 path: artifact - run: | cp CHANGELOG.md LICENSE README.md artifact/ - run: | - zip -r ../${{ github.event.repository.name }}-windows-x64.zip * + zip -r ../${{ github.event.repository.name }}-${{ matrix.target_type }}-windows-x64.zip * working-directory: artifact - - uses: softprops/action-gh-release@v1 + - uses: softprops/action-gh-release@v3 with: files: | - ${{ github.event.repository.name }}-windows-x64.zip + ${{ github.event.repository.name }}-${{ matrix.target_type }}-windows-x64.zip \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 95dcf40..df9102e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,12 +7,64 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [未发布] +## [0.11.0] - 2026-09-16 + +## 新增 + +- 适配LeviLamina 26.40 + +## [0.10.1] - 2026-09-13 + +### 修复 + +- 修复骑乘状态判断在 LeviLamina 26.32 中失效,导致载具移动距离无法统计的问题。 +- 修复异常伤害值、无效客户端移动数据或计数溢出可能导致统计数值异常增大的问题。 +- 修复抗性、吸收和盾牌伤害统计的边界计算。 +- 修复花盆交互统计组件获取失败,以及村民交易统计中的空指针问题。 + +## [0.10.0] - 2026-09-12 + +## 新增 + +- 适配LeviLamina 26.32 + +### 更改 + +- 重构语言文件结构:界面、命令、日志、统计分类与通用统计分别使用嵌套键;Minecraft 名称按 `block`、`item`、`entity` 分类。 +- 更新简体中文、繁体中文(香港)与繁体中文(台湾)的 Minecraft 官方译名数据。 + +### 修复 + +- 修复物品/方块混合统计在缺少物品译名时显示 `item.minecraft:`,而不回退到方块译名的问题。 + ## [0.9.0] - 2026-07-05 ### 新增 - 适配LeviLamina 26.20 +## [0.8.3] - 2026-07-14 + +### 新增 + +- GUI 与 RemoteCall 统计查询分页。 +- 在线玩家每 5 分钟异步保存,并支持中断快照恢复。 + +### 优化 + +- 优化玩家统计索引、表单查询与内存占用。 +- 增加排行榜缓存和分页排序,降低大数据量查询开销。 +- 优化玩家移动统计热路径。 + +### 修复 + +- 修复异常崩溃时在线玩家统计可能长期未保存的问题。 +- 修复统计文件写入中断时可能忽略有效临时快照的问题。 +- 修复玩家重新加入后名称等缓存信息未刷新的问题。 +- 修复部分动物繁殖统计错误及潜在崩溃问题。 +- 修复物品转移统计读取错误来源槽位的问题。 +- 修复排行榜类型无效时错误提示不明确的问题。 + ## [0.8.0] - 2026-04-14 ### 新增 @@ -215,8 +267,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 统计玩家与床交互次数 - 统计玩家与部分方块交互次数 -[未发布]: https://github.com/LeafKnife/Stats/compare/v0.9.0...dev -[0.9.0]: https://github.com/LeafKnife/Stats/compare/v0.8.0...0.9.0 +[未发布]: https://github.com/LeafKnife/Stats/compare/v0.11.0...dev +[0.11.0]: https://github.com/LeafKnife/Stats/compare/v0.10.1...v0.11.0 +[0.10.1]: https://github.com/LeafKnife/Stats/compare/v0.10.0...v0.10.1 +[0.10.0]: https://github.com/LeafKnife/Stats/compare/v0.9.0...v0.10.0 +[0.9.0]: https://github.com/LeafKnife/Stats/compare/v0.8.3...v0.9.0 +[0.8.3]: https://github.com/LeafKnife/Stats/compare/v0.8.0...v0.8.3 [0.8.0]: https://github.com/LeafKnife/Stats/compare/v0.7.1...v0.8.0 [0.7.1]: https://github.com/LeafKnife/Stats/compare/v0.7.0...v0.7.1 [0.7.0]: https://github.com/LeafKnife/Stats/compare/v0.6.0...v0.7.0 diff --git a/README.md b/README.md index df6197f..52df49c 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,26 @@ const getStats = ll.import("LK-Stats", "getPlayerStats"); const stats = getStats(uuid, 1); ``` +### Paginated RemoteCall API + +The existing `getPlayerStats` and `getRankStats` exports remain available. For large datasets, use the paginated exports below. Both return a JSON string; `page` starts at `1`, and `pageSize` is limited to `1`-`100` (values less than `1` use `20`). Ranking results may be cached for up to 30 seconds. + +```javascript +const getPlayerStatsPage = ll.import("LK-Stats", "getPlayerStatsPage"); +const statsPage = JSON.parse(getPlayerStatsPage(uuid, 1, 1, 20)); + +// { page, pageSize, total, totalPages, items: [{ key, value }] } +logger.info(JSON.stringify(statsPage)); +``` + +```javascript +const getRankStatsPage = ll.import("LK-Stats", "getRankStatsPage"); +const rankPage = JSON.parse(getRankStatsPage(1, "minecraft:play_time", 1, 20)); + +// { page, pageSize, total, totalPages, items: [{ player, value }] } +logger.info(JSON.stringify(rankPage)); +``` + 获取排行榜信息 - 参数: @@ -79,6 +99,34 @@ const rank3 = getRankData(2,""); // 挖掘排行榜 | ENTITY_KILLED | 与玩家杀死的实体数量相关的统计信息 | minecraft:killed | | ENTITY_KILLED_BY | 与玩家被实体杀死相关的统计信息 | minecraft:killed_by | +### 语言文件对象键 + +统计数据和 RemoteCall API 中的对象键仍为原始命名空间 ID,例如 `minecraft:arrow`。语言文件会按用途及来源嵌套:界面为 `gui`,命令为 `command`,日志为 `log`;统计分类标题为 `stats.category`,通用统计项为 `stats.custom`。方块和物品有时使用相同的 ID,有时不同,因此按来源分为 `block`、`item`、`entity` 三组,避免同一 ID 时互相覆盖: + +```json +{ + "stats": { + "category": { + "used": "物品使用" + }, + "custom": { + "minecraft:jump": "跳跃次数" + } + }, + "block": { + "minecraft:stone": "石头" + }, + "item": { + "minecraft:arrow": "箭" + }, + "entity": { + "minecraft:arrow": "射出的箭" + } +} +``` + +加载时会将嵌套对象展开为内部翻译键。统计存档和 RemoteCall API 的字段不变:`mined` 使用 `block`,`broken` 使用 `item`,`killed` 与 `killed_by` 使用 `entity`;`crafted`、`used`、`picked_up`、`dropped` 会依次查询 `item` 与 `block`;通用统计使用 `stats.custom`。为兼容旧语言文件,未找到新键时仍会回退到原本的裸 `minecraft:` 键。 + ### 统计信息列表 | 统计名称 | 描述 | 命名空间 | diff --git a/benchmarks/StatsQueryBenchmark.cpp b/benchmarks/StatsQueryBenchmark.cpp new file mode 100644 index 0000000..856acb1 --- /dev/null +++ b/benchmarks/StatsQueryBenchmark.cpp @@ -0,0 +1,153 @@ +#include "mod/Stats/StatsQuery.h" +#include "mod/Stats/StatsRecord.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace { + +volatile uint64_t benchmarkChecksum = 0; + +struct PlayerRecord { + std::string name; + stats::StatsDataMap values; +}; + +std::vector makeRecords(std::size_t count, std::size_t entriesPerPlayer) { + std::vector records; + records.reserve(count); + for (std::size_t index = 0; index < count; ++index) { + PlayerRecord record; + record.name = "Player" + std::to_string(index); + record.values.emplace("minecraft:stone", (index * 7919) % 100000); + for (std::size_t entry = 1; entry < entriesPerPlayer; ++entry) { + record.values.emplace("minecraft:key_" + std::to_string(entry), index + entry); + } + records.push_back(std::move(record)); + } + return records; +} + +stats::query::RankData buildWithViews(std::vector const& records) { + std::vector entries; + entries.reserve(records.size()); + for (auto const& record : records) entries.push_back({record.name, &record.values}); + return stats::query::buildRank(entries, "minecraft:stone"); +} + +stats::query::RankData buildDirect(std::vector const& records, std::string const& key) { + stats::query::RankBuilder builder(records.size(), key); + for (auto const& record : records) builder.add(record.name, &record.values); + return std::move(builder).finish(); +} + +stats::query::RankData buildFirstPage(std::vector const& records) { + stats::query::RankBuilder builder(records.size(), "minecraft:stone"); + for (auto const& record : records) builder.add(record.name, &record.values); + return std::move(builder).finishPage(0).entries; +} + +template +int64_t measure(std::vector const& records, int iterations, Builder&& builder) { + uint64_t checksum = 0; + auto const start = std::chrono::steady_clock::now(); + for (int iteration = 0; iteration < iterations; ++iteration) { + auto rank = builder(records); + checksum += rank.empty() ? 0 : rank.front().second; + } + auto const elapsed = std::chrono::steady_clock::now() - start; + benchmarkChecksum = checksum; + return std::chrono::duration_cast(elapsed).count() / iterations; +} + +int64_t median(std::vector samples) { + std::sort(samples.begin(), samples.end()); + return samples[samples.size() / 2]; +} + +void run(std::size_t players, int iterations) { + auto const records = makeRecords(players, 25); + buildWithViews(records); + buildDirect(records, "minecraft:stone"); + + std::vector viewSamples; + std::vector directSamples; + for (int sample = 0; sample < 9; ++sample) { + if (sample % 2 == 0) { + viewSamples.push_back(measure(records, iterations, buildWithViews)); + directSamples.push_back( + measure(records, iterations, [](auto const& data) { return buildDirect(data, "minecraft:stone"); }) + ); + } else { + directSamples.push_back( + measure(records, iterations, [](auto const& data) { return buildDirect(data, "minecraft:stone"); }) + ); + viewSamples.push_back(measure(records, iterations, buildWithViews)); + } + } + auto const totals = measure(records, iterations, [](auto const& data) { return buildDirect(data, ""); }); + auto const page = measure(records, iterations, buildFirstPage); + std::cout << players << " players: views=" << median(std::move(viewSamples)) + << "us direct=" << median(std::move(directSamples)) << "us totals=" << totals + << "us first-page=" << page << "us\n"; +} + +uint64_t privateBytes() { + PROCESS_MEMORY_COUNTERS_EX counters{}; + counters.cb = sizeof(counters); + GetProcessMemoryInfo(GetCurrentProcess(), reinterpret_cast(&counters), sizeof(counters)); + return counters.PrivateUsage; +} + +struct FullPlayerRecord { + stats::PlayerInfo info; + stats::StatsData data; +}; + +void runMemory(std::size_t players, std::size_t entriesPerType) { + auto const before = privateBytes(); + std::vector records; + records.reserve(players); + for (std::size_t player = 0; player < players; ++player) { + FullPlayerRecord record; + record.info = { + "00000000-0000-0000-0000-" + std::to_string(100000000000ULL + player), + std::to_string(1000000000000000ULL + player), + "Player" + std::to_string(player), + }; + for (auto const& descriptor : stats::StatsSchema) { + stats::StatsDataMap values; + values.reserve(entriesPerType); + for (std::size_t entry = 0; entry < entriesPerType; ++entry) { + auto const key = descriptor.type == StatsType::custom && entry < stats::CustomTypeMap.size() + ? std::string{stats::CustomTypeMap[entry].second} + : "minecraft:key_" + std::to_string(entry); + values.emplace(key, player + entry); + } + record.data.loadMap(descriptor.type, std::move(values)); + } + record.data.getMap(StatsType::custom); + records.push_back(std::move(record)); + } + auto const bytes = privateBytes() - before; + std::cout << players << " players x " << entriesPerType << " keys/type: " << bytes / (1024.0 * 1024.0) + << " MiB, " << bytes / static_cast(players) << " bytes/player\n"; +} + +} // namespace + +int main(int argc, char** argv) { + if (argc == 4 && std::string_view{argv[1]} == "--memory") { + runMemory(std::stoull(argv[2]), std::stoull(argv[3])); + return 0; + } + run(1000, 200); + run(10000, 30); +} diff --git a/src/lang/zh_CN.json b/src/lang/zh_CN.json index 2386772..7062553 100644 --- a/src/lang/zh_CN.json +++ b/src/lang/zh_CN.json @@ -1,108 +1,132 @@ { - "gui.title.stats": "统计信息", - "gui.title.rank": "排行榜", - "command.stats.name": "stats", - "command.stats.alias": "lks", - "command.stats.desc": "统计信息", - "command.error.notplayer": "此命令仅限玩家执行", - "command.error.player_name_empty": "玩家名不能为空", - "command.error.find_player": "找不到指定的玩家,请检查玩家名是否正确", - "command.error.rank_type_required": "排行榜类型为custom时,type不能为空", - "log.info.ExistOldPath": "检测到旧数据, 迁移数据中", - "log.warn.moveStats.fail": "数据迁移失败", - "log.info.CreateStatsPath": "未检测到数据目录,正在初始化...", - "log.warn.CreatePath.fail": "数据目录创建失败", - "plugins.load.success": "插件加载成功!", - "plugins.load.fail": "插件加载失败,请检查数据目录是否存在", - "data.parse.fail": "{0} 文件解析失败,请检查文件内容", - "log.info.createData": "未检测到玩家 {0} 数据,正在创建...", - "log.info.savaData": "保存玩家 {0} 数据中...", - "minecraft:custom": "通用", - "minecraft:mined": "方块挖掘", - "minecraft:broken": "物品破损", - "minecraft:crafted": "物品合成", - "minecraft:used": "物品使用", - "minecraft:picked_up": "物品拾取", - "minecraft:dropped": "物品丢弃", - "minecraft:killed": "实体击杀", - "minecraft:killed_by": "被击杀", - "minecraft:animals_bred": "繁殖动物次数", - "minecraft:clean_armor": "清洗盔甲次数", - "minecraft:clean_banner": "清洗旗帜次数", - "minecraft:open_barrel": "木桶打开次数", - "minecraft:bell_ring": "鸣钟次数", - "minecraft:eat_cake_slice": "吃掉的蛋糕片数", - "minecraft:fill_cauldron": "炼药锅装水次数", - "minecraft:open_chest": "箱子打开次数", - "minecraft:damage_absorbed": "吸收的伤害", - "minecraft:damage_blocked_by_shield": "盾牌抵挡的伤害", - "minecraft:damage_dealt": "造成伤害", - "minecraft:damage_dealt_absorbed": "造成伤害(被吸收)", - "minecraft:damage_dealt_resisted": "造成伤害(被抵挡)", - "minecraft:damage_resisted": "抵挡的伤害", - "minecraft:damage_taken": "受到伤害", - "minecraft:inspect_dispenser": "搜查发射器次数", - "minecraft:happy_ghast_one_cm": "骑乘快乐恶魂移动距离(厘米)", - "minecraft:nautilus_one_cm": "骑鹦鹉螺移动距离(厘米)", - "minecraft:boat_one_cm": "坐船移动距离(厘米)", - "minecraft:aviate_one_cm": "鞘翅滑行距离(厘米)", - "minecraft:horse_one_cm": "骑马移动距离(厘米)", - "minecraft:minecart_one_cm": "坐矿车移动距离(厘米)", - "minecraft:pig_one_cm": "骑猪移动距离(厘米)", - "minecraft:strider_one_cm": "骑炽足兽移动距离(厘米)", - "minecraft:climb_one_cm": "已攀爬距离(厘米)", - "minecraft:crouch_one_cm": "潜行距离(厘米)", - "minecraft:fall_one_cm": "摔落高度(厘米)", - "minecraft:fly_one_cm": "飞行距离(厘米)", - "minecraft:sprint_one_cm": "疾跑距离(厘米)", - "minecraft:swim_one_cm": "游泳距离(厘米)", - "minecraft:walk_one_cm": "行走距离(厘米)", - "minecraft:walk_on_water_one_cm": "水面行走距离(厘米)", - "minecraft:walk_under_water_one_cm": "水下行走距离(厘米)", - "minecraft:inspect_dropper": "搜查投掷器次数", - "minecraft:open_enderchest": "末影箱打开次数", - "minecraft:fish_caught": "捕鱼数", - "minecraft:leave_game": "游戏退出次数", - "minecraft:inspect_hopper": "搜查漏斗次数", - "minecraft:interact_with_anvil": "与铁砧交互次数", - "minecraft:interact_with_beacon": "与信标交互次数", - "minecraft:interact_with_blast_furnace": "与高炉交互次数", - "minecraft:interact_with_brewingstand": "与酿造台交互次数", - "minecraft:interact_with_campfire": "与营火交互次数", - "minecraft:interact_with_cartography_table": "与制图台交互次数", - "minecraft:interact_with_crafting_table": "与工作台交互次数", - "minecraft:interact_with_furnace": "与熔炉交互次数", - "minecraft:interact_with_grindstone": "与砂轮交互次数", - "minecraft:interact_with_lectern": "与讲台交互次数", - "minecraft:interact_with_loom": "与织布机交互次数", - "minecraft:interact_with_smithing_table": "与锻造台交互次数", - "minecraft:interact_with_smoker": "与烟熏炉交互次数", - "minecraft:interact_with_stonecutter": "与切石机交互次数", - "minecraft:drop": "物品掉落", - "minecraft:enchant_item": "物品附魔次数", - "minecraft:jump": "跳跃次数", - "minecraft:mob_kills": "生物击杀数", - "minecraft:play_record": "播放唱片数", - "minecraft:play_noteblock": "音符盒播放次数", - "minecraft:tune_noteblock": "音符盒调音次数", - "minecraft:deaths": "死亡次数", - "minecraft:pot_flower": "盆栽种植数", - "minecraft:player_kills": "玩家击杀数", - "minecraft:raid_trigger": "触发袭击次数", - "minecraft:raid_win": "袭击胜利次数", - "minecraft:clean_shulker_box": "潜影盒清洗次数", - "minecraft:open_shulker_box": "潜影盒打开次数", - "minecraft:time_since_death": "自上次死亡(Tick)", - "minecraft:time_since_rest": "自上次入眠(Tick)", - "minecraft:sneak_time": "潜行时间(Tick)", - "minecraft:talked_to_villager": "村民交互次数", - "minecraft:target_hit": "击中标靶次数", - "minecraft:play_time": "游戏时间(Tick)", - "minecraft:total_world_time": "世界打开时间(Tick)", - "minecraft:sleep_in_bed": "躺在床上的次数", - "minecraft:traded_with_villager": "村民交易次数", - "minecraft:trigger_trapped_chest": "陷阱箱触发次数", - "minecraft:use_cauldron": "从炼药锅取水次数", + "gui": { + "title": { + "stats": "统计信息", + "rank": "排行榜" + } + }, + "command": { + "stats": { + "name": "stats", + "alias": "lks", + "desc": "统计信息" + }, + "error": { + "not_player": "此命令只能由玩家执行", + "player_name_empty": "玩家名不能为空", + "player_not_found": "找不到指定玩家,请检查玩家名是否正确", + "invalid_stats_category": "无效的统计分类", + "rank_stat_required": "排行榜分类为“通用”时,必须指定统计项" + } + }, + "log": { + "warning": { + "legacy_data_migration_started": "检测到旧版统计数据,正在迁移...", + "legacy_data_migration_failed": "旧版统计数据迁移失败", + "data_directory_initializing": "未检测到统计数据目录,正在初始化...", + "data_directory_creation_failed": "统计数据目录创建失败", + "plugin_load_failed": "插件加载失败,请检查统计数据目录是否可用", + "data_parse_failed": "无法解析统计数据文件 {0},请检查文件内容" + }, + "info": { + "plugin_loaded": "插件加载成功", + "player_data_creating": "未检测到玩家 {0} 的统计数据,正在创建...", + "player_data_saving": "正在保存玩家 {0} 的统计数据..." + } + }, + "stats": { + "category": { + "custom": "通用", + "mined": "方块挖掘", + "broken": "物品破损", + "crafted": "物品合成", + "used": "物品使用", + "picked_up": "物品拾取", + "dropped": "物品丢弃", + "killed": "实体击杀", + "killed_by": "被击杀" + }, + "custom": { + "minecraft:animals_bred": "繁殖动物次数", + "minecraft:clean_armor": "清洗盔甲次数", + "minecraft:clean_banner": "清洗旗帜次数", + "minecraft:open_barrel": "木桶打开次数", + "minecraft:bell_ring": "鸣钟次数", + "minecraft:eat_cake_slice": "吃掉的蛋糕片数", + "minecraft:fill_cauldron": "炼药锅装水次数", + "minecraft:open_chest": "箱子打开次数", + "minecraft:damage_absorbed": "吸收的伤害", + "minecraft:damage_blocked_by_shield": "盾牌抵挡的伤害", + "minecraft:damage_dealt": "造成伤害", + "minecraft:damage_dealt_absorbed": "造成伤害(被吸收)", + "minecraft:damage_dealt_resisted": "造成伤害(被抵挡)", + "minecraft:damage_resisted": "抵挡的伤害", + "minecraft:damage_taken": "受到伤害", + "minecraft:inspect_dispenser": "搜查发射器次数", + "minecraft:happy_ghast_one_cm": "骑乘快乐恶魂移动距离(厘米)", + "minecraft:nautilus_one_cm": "骑鹦鹉螺移动距离(厘米)", + "minecraft:boat_one_cm": "坐船移动距离(厘米)", + "minecraft:aviate_one_cm": "鞘翅滑行距离(厘米)", + "minecraft:horse_one_cm": "骑马移动距离(厘米)", + "minecraft:minecart_one_cm": "坐矿车移动距离(厘米)", + "minecraft:pig_one_cm": "骑猪移动距离(厘米)", + "minecraft:strider_one_cm": "骑炽足兽移动距离(厘米)", + "minecraft:climb_one_cm": "已攀爬距离(厘米)", + "minecraft:crouch_one_cm": "潜行距离(厘米)", + "minecraft:fall_one_cm": "摔落高度(厘米)", + "minecraft:fly_one_cm": "飞行距离(厘米)", + "minecraft:sprint_one_cm": "疾跑距离(厘米)", + "minecraft:swim_one_cm": "游泳距离(厘米)", + "minecraft:walk_one_cm": "行走距离(厘米)", + "minecraft:walk_on_water_one_cm": "水面行走距离(厘米)", + "minecraft:walk_under_water_one_cm": "水下行走距离(厘米)", + "minecraft:inspect_dropper": "搜查投掷器次数", + "minecraft:open_enderchest": "末影箱打开次数", + "minecraft:fish_caught": "捕鱼数", + "minecraft:leave_game": "游戏退出次数", + "minecraft:inspect_hopper": "搜查漏斗次数", + "minecraft:interact_with_anvil": "与铁砧交互次数", + "minecraft:interact_with_beacon": "与信标交互次数", + "minecraft:interact_with_blast_furnace": "与高炉交互次数", + "minecraft:interact_with_brewingstand": "与酿造台交互次数", + "minecraft:interact_with_campfire": "与营火交互次数", + "minecraft:interact_with_cartography_table": "与制图台交互次数", + "minecraft:interact_with_crafting_table": "与工作台交互次数", + "minecraft:interact_with_furnace": "与熔炉交互次数", + "minecraft:interact_with_grindstone": "与砂轮交互次数", + "minecraft:interact_with_lectern": "与讲台交互次数", + "minecraft:interact_with_loom": "与织布机交互次数", + "minecraft:interact_with_smithing_table": "与锻造台交互次数", + "minecraft:interact_with_smoker": "与烟熏炉交互次数", + "minecraft:interact_with_stonecutter": "与切石机交互次数", + "minecraft:drop": "物品掉落", + "minecraft:enchant_item": "物品附魔次数", + "minecraft:jump": "跳跃次数", + "minecraft:mob_kills": "生物击杀数", + "minecraft:play_record": "播放唱片数", + "minecraft:play_noteblock": "音符盒播放次数", + "minecraft:tune_noteblock": "音符盒调音次数", + "minecraft:deaths": "死亡次数", + "minecraft:pot_flower": "盆栽种植数", + "minecraft:player_kills": "玩家击杀数", + "minecraft:raid_trigger": "触发袭击次数", + "minecraft:raid_win": "袭击胜利次数", + "minecraft:clean_shulker_box": "潜影盒清洗次数", + "minecraft:open_shulker_box": "潜影盒打开次数", + "minecraft:time_since_death": "自上次死亡(Tick)", + "minecraft:time_since_rest": "自上次入眠(Tick)", + "minecraft:sneak_time": "潜行时间(Tick)", + "minecraft:talked_to_villager": "村民交互次数", + "minecraft:target_hit": "击中标靶次数", + "minecraft:play_time": "游戏时间(Tick)", + "minecraft:total_world_time": "世界打开时间(Tick)", + "minecraft:sleep_in_bed": "躺在床上的次数", + "minecraft:traded_with_villager": "村民交易次数", + "minecraft:trigger_trapped_chest": "陷阱箱触发次数", + "minecraft:use_cauldron": "从炼药锅取水次数" + } + }, + "block": { "minecraft:stone": "石头", "minecraft:grass_block": "草方块", "minecraft:dirt": "泥土", @@ -148,7 +172,7 @@ "minecraft:smooth_stone_double_slab": "平滑石头双层台阶", "minecraft:smooth_stone_slab": "平滑石头台阶", "minecraft:brick_block": "红砖块", - "minecraft:tnt": "点燃的TT", + "minecraft:tnt": "TNT", "minecraft:bookshelf": "书架", "minecraft:mossy_cobblestone": "苔石", "minecraft:obsidian": "黑曜石", @@ -185,7 +209,7 @@ "minecraft:snow": "雪块", "minecraft:cactus": "仙人掌", "minecraft:clay": "黏土", - "minecraft:reeds": "甘蔗", + "minecraft:sugar_cane": "甘蔗", "minecraft:jukebox": "唱片机", "minecraft:oak_fence": "橡木栅栏", "minecraft:pumpkin": "南瓜", @@ -198,7 +222,7 @@ "minecraft:unpowered_repeater": "未激活的中继器", "minecraft:powered_repeater": "激活的中继器", "minecraft:invisible_bedrock": "隐形基岩", - "minecraft:trapdoor": "活板门", + "minecraft:trapdoor": "橡木活板门", "minecraft:infested_stone": "虫蚀石头", "minecraft:stone_bricks": "石砖", "minecraft:brown_mushroom_block": "棕色蘑菇方块", @@ -249,8 +273,8 @@ "minecraft:skeleton_skull": "骷髅头颅", "minecraft:anvil": "铁砧", "minecraft:trapped_chest": "陷阱箱", - "minecraft:light_weighted_pressure_plate": "测重压力板 (轻质)", - "minecraft:heavy_weighted_pressure_plate": "测重压力板 (重质)", + "minecraft:light_weighted_pressure_plate": "轻质测重压力板", + "minecraft:heavy_weighted_pressure_plate": "重质测重压力板", "minecraft:unpowered_comparator": "未激活的比较器", "minecraft:powered_comparator": "激活的比较器", "minecraft:daylight_detector": "阳光探测器", @@ -267,7 +291,8 @@ "minecraft:acacia_log": "金合欢原木", "minecraft:acacia_stairs": "金合欢木楼梯", "minecraft:dark_oak_stairs": "深色橡木楼梯", - "minecraft:slime": "史莱姆", + "minecraft:slime": "黏液块", + "minecraft:glow_stick": "荧光棒", "minecraft:iron_trapdoor": "铁活板门", "minecraft:prismarine": "海晶石", "minecraft:sea_lantern": "海晶灯", @@ -299,7 +324,7 @@ "minecraft:jungle_door": "丛林木门", "minecraft:acacia_door": "金合欢木门", "minecraft:dark_oak_door": "深色橡木门", - "minecraft:grass_path": "草径", + "minecraft:grass_path": "土径", "minecraft:frame": "物品展示框", "minecraft:chorus_flower": "紫颂花", "minecraft:purpur_block": "紫珀块", @@ -313,7 +338,7 @@ "minecraft:end_gateway": "末地折跃门", "minecraft:allow": "允许方块", "minecraft:deny": "拒绝方块", - "minecraft:border_block": "边界方块", + "minecraft:border_block": "边界", "minecraft:magma": "岩浆块", "minecraft:nether_wart_block": "下界疣块", "minecraft:red_nether_brick": "红色下界砖块", @@ -346,12 +371,12 @@ "minecraft:camera": "摄像机", "minecraft:podzol": "灰化土", "minecraft:beetroot": "甜菜根", - "minecraft:stonecutter": "切石机", + "minecraft:stonecutter": "切石机(旧版)", "minecraft:glowingobsidian": "发光的黑曜石", "minecraft:netherreactor": "下界反应核", "minecraft:info_update": "数据更新方块(update!)", "minecraft:info_update2": "数据更新方块2(ate!upd)", - "minecraft:moving_block": "移动的活塞", + "minecraft:moving_block": "移动的方块/移动的活塞", "minecraft:observer": "侦测器", "minecraft:structure_block": "结构方块", "minecraft:hard_glass": "强化玻璃", @@ -479,12 +504,12 @@ "minecraft:element_110": "𫟼", "minecraft:element_111": "𬬭", "minecraft:element_112": "鿔", - "minecraft:element_113": "鉨", + "minecraft:element_113": "鿭", "minecraft:element_114": "𫓧", "minecraft:element_115": "镆", "minecraft:element_116": "𫟷", - "minecraft:element_117": "Tennessine", - "minecraft:element_118": "Oganesson", + "minecraft:element_117": "鿬", + "minecraft:element_118": "鿫", "minecraft:seagrass": "海草", "minecraft:tube_coral": "管珊瑚", "minecraft:tube_coral_block": "管珊瑚块", @@ -585,7 +610,7 @@ "minecraft:crimson_fungus": "绯红菌", "minecraft:warped_fungus": "诡异菌", "minecraft:shroomlight": "菌光体", - "minecraft:weeping_vines": "垂泪藤", + "minecraft:weeping_vines_plant": "垂泪藤植株", "minecraft:crimson_nylium": "绯红菌岩", "minecraft:warped_nylium": "诡异菌岩", "minecraft:basalt": "玄武岩", @@ -638,7 +663,7 @@ "minecraft:blackstone_double_slab": "黑石双层台阶", "minecraft:polished_blackstone_brick_slab": "磨制黑石砖台阶", "minecraft:polished_blackstone_brick_double_slab": "磨制黑石砖双层台阶", - "minecraft:chain": "锁链", + "minecraft:iron_chain": "铁链", "minecraft:twisting_vines": "缠怨藤", "minecraft:nether_gold_ore": "下界金矿石", "minecraft:crying_obsidian": "哭泣的黑曜石", @@ -657,7 +682,7 @@ "minecraft:chiseled_nether_bricks": "雕纹下界砖块", "minecraft:cracked_nether_bricks": "裂纹下界砖块", "minecraft:quartz_bricks": "石英砖", - "minecraft:unknown": "未知 [注 3]", + "minecraft:unknown": "未知", "minecraft:powder_snow": "细雪", "minecraft:sculk_sensor": "幽匿感测体", "minecraft:pointed_dripstone": "滴水石锥", @@ -750,7 +775,7 @@ "minecraft:polished_deepslate_double_slab": "磨制深板岩双层台阶", "minecraft:deepslate_tile_double_slab": "深板岩瓦双层台阶", "minecraft:deepslate_brick_double_slab": "深板岩砖双层台阶", - "minecraft:deepslate_lapis_lazuli_ore": "深层青金石矿石", + "minecraft:deepslate_lapis_ore": "深层青金石矿石", "minecraft:deepslate_iron_ore": "深层铁矿石", "minecraft:deepslate_gold_ore": "深层金矿石", "minecraft:deepslate_redstone_ore": "深层红石矿石", @@ -820,6 +845,7 @@ "minecraft:mud": "泥巴", "minecraft:mangrove_propagule": "红树胎生苗", "minecraft:mud_bricks": "泥砖", + "minecraft:mangrove_propagule_hanging": "悬挂的红树胎生苗 (已移除)", "minecraft:packed_mud": "泥坯", "minecraft:mud_brick_slab": "泥砖台阶", "minecraft:mud_brick_double_slab": "泥砖双层台阶", @@ -1290,7 +1316,7 @@ "minecraft:deprecated_purpur_block_2": "平滑紫珀块", "minecraft:chiseled_quartz_block": "雕纹石英块", "minecraft:quartz_pillar": "石英柱", - "minecraft:smooth_quartz": "平滑石英块", + "minecraft:smooth_quartz_block": "平滑石英块", "minecraft:chiseled_red_sandstone": "雕纹红砂岩", "minecraft:cut_red_sandstone": "切制红砂岩", "minecraft:smooth_red_sandstone": "平滑红砂岩", @@ -1320,7 +1346,7 @@ "minecraft:red_sandstone_wall": "红砂岩墙", "minecraft:red_nether_brick_wall": "红色下界砖墙", "minecraft:wet_sponge": "湿海绵", - "minecraft:underwater_tnt": "水下TT", + "minecraft:underwater_tnt": "水下TNT", "minecraft:material_reducer": "材料分解器", "minecraft:element_constructor": "元素构造器", "minecraft:lab_table": "实验台", @@ -1361,37 +1387,189 @@ "minecraft:bush": "灌木丛", "minecraft:wildflowers": "野花簇", "minecraft:firefly_bush": "萤火虫灌木丛", - "minecraft:leaf_litter": "枯叶", - "minecraft:black_bundle": "黑色收纳袋", - "minecraft:blue_bundle": "蓝色收纳袋", - "minecraft:brown_bundle": "棕色收纳袋", - "minecraft:bundle": "收纳袋", - "minecraft:cyan_bundle": "青色收纳袋", - "minecraft:gray_bundle": "灰色收纳袋", - "minecraft:green_bundle": "绿色收纳袋", - "minecraft:light_blue_bundle": "淡蓝色收纳袋", - "minecraft:light_gray_bundle": "淡灰色收纳袋", - "minecraft:lime_bundle": "黄绿色收纳袋", - "minecraft:magenta_bundle": "品红色收纳袋", - "minecraft:orange_bundle": "橙色收纳袋", - "minecraft:pink_bundle": "粉红色收纳袋", - "minecraft:purple_bundle": "紫色收纳袋", - "minecraft:red_bundle": "红色收纳袋", - "minecraft:white_bundle": "白色收纳袋", - "minecraft:yellow_bundle": "黄色收纳袋", - "minecraft:breeze_rod": "旋风棒", - "minecraft:ominous_trial_key": "不祥试炼钥匙", - "minecraft:trial_key": "试炼钥匙", - "minecraft:wind_charge": "风弹", - "minecraft:apple": "苹果", + "minecraft:leaf_litter": "枯叶堆", + "minecraft:dried_ghast": "失水恶魂", + "minecraft:short_dry_grass": "矮枯草丛", + "minecraft:tall_dry_grass": "高枯草丛", + "minecraft:cactus_flower": "仙人掌花", + "minecraft:copper_chest": "铜箱子", + "minecraft:exposed_copper_chest": "斑驳的铜箱子", + "minecraft:weathered_copper_chest": "锈蚀的铜箱子", + "minecraft:oxidized_copper_chest": "氧化的铜箱子", + "minecraft:waxed_copper_chest": "涂蜡的铜箱子", + "minecraft:waxed_exposed_copper_chest": "涂蜡的斑驳铜箱子", + "minecraft:waxed_weathered_copper_chest": "涂蜡的锈蚀铜箱子", + "minecraft:waxed_oxidized_copper_chest": "涂蜡的氧化铜箱子", + "minecraft:copper_golem_statue": "铜傀儡像", + "minecraft:exposed_copper_golem_statue": "斑驳的铜傀儡像", + "minecraft:weathered_copper_golem_statue": "锈蚀的铜傀儡像", + "minecraft:oxidized_copper_golem_statue": "氧化的铜傀儡像", + "minecraft:waxed_copper_golem_statue": "涂蜡的铜傀儡像", + "minecraft:waxed_exposed_copper_golem_statue": "涂蜡的斑驳铜傀儡像", + "minecraft:waxed_weathered_copper_golem_statue": "涂蜡的锈蚀铜傀儡像", + "minecraft:waxed_oxidized_copper_golem_statue": "涂蜡的氧化铜傀儡像", + "minecraft:oak_shelf": "橡木展示架", + "minecraft:spruce_shelf": "云杉木展示架", + "minecraft:birch_shelf": "白桦木展示架", + "minecraft:jungle_shelf": "丛林木展示架", + "minecraft:acacia_shelf": "金合欢木展示架", + "minecraft:dark_oak_shelf": "深色橡木展示架", + "minecraft:mangrove_shelf": "红树木展示架", + "minecraft:cherry_shelf": "樱花木展示架", + "minecraft:pale_oak_shelf": "苍白橡木展示架", + "minecraft:bamboo_shelf": "竹展示架", + "minecraft:crimson_shelf": "绯红木展示架", + "minecraft:warped_shelf": "诡异木展示架", + "minecraft:exposed_lightning_rod": "斑驳的避雷针", + "minecraft:weathered_lightning_rod": "锈蚀的避雷针", + "minecraft:oxidized_lightning_rod": "氧化的避雷针", + "minecraft:waxed_lightning_rod": "涂蜡的避雷针", + "minecraft:waxed_exposed_lightning_rod": "涂蜡的斑驳避雷针", + "minecraft:waxed_weathered_lightning_rod": "涂蜡的锈蚀避雷针", + "minecraft:waxed_oxidized_lightning_rod": "涂蜡的氧化避雷针", + "minecraft:copper_bars": "铜栏杆", + "minecraft:exposed_copper_bars": "斑驳的铜栏杆", + "minecraft:weathered_copper_bars": "锈蚀的铜栏杆", + "minecraft:oxidized_copper_bars": "氧化的铜栏杆", + "minecraft:waxed_copper_bars": "涂蜡的铜栏杆", + "minecraft:waxed_exposed_copper_bars": "涂蜡的斑驳铜栏杆", + "minecraft:waxed_weathered_copper_bars": "涂蜡的锈蚀铜栏杆", + "minecraft:waxed_oxidized_copper_bars": "涂蜡的氧化铜栏杆", + "minecraft:copper_chain": "铜链", + "minecraft:exposed_copper_chain": "斑驳的铜链", + "minecraft:weathered_copper_chain": "锈蚀的铜链", + "minecraft:oxidized_copper_chain": "氧化的铜链", + "minecraft:waxed_copper_chain": "涂蜡的铜链", + "minecraft:waxed_exposed_copper_chain": "涂蜡的斑驳铜链", + "minecraft:waxed_weathered_copper_chain": "涂蜡的锈蚀铜链", + "minecraft:waxed_oxidized_copper_chain": "涂蜡的氧化铜链", + "minecraft:copper_torch": "铜火把", + "minecraft:copper_lantern": "铜灯笼", + "minecraft:exposed_copper_lantern": "斑驳的铜灯笼", + "minecraft:weathered_copper_lantern": "锈蚀的铜灯笼", + "minecraft:oxidized_copper_lantern": "氧化的铜灯笼", + "minecraft:waxed_copper_lantern": "涂蜡的铜灯笼", + "minecraft:waxed_exposed_copper_lantern": "涂蜡的斑驳铜灯笼", + "minecraft:waxed_weathered_copper_lantern": "涂蜡的锈蚀铜灯笼", + "minecraft:waxed_oxidized_copper_lantern": "涂蜡的氧化铜灯笼", + "minecraft:golden_dandelion": "金蒲公英", + "minecraft:sulfur": "硫黄", + "minecraft:sulfur_slab": "硫黄台阶", + "minecraft:sulfur_double_slab": "硫黄双层台阶", + "minecraft:sulfur_stairs": "硫黄楼梯", + "minecraft:sulfur_wall": "硫黄墙", + "minecraft:polished_sulfur": "磨制硫黄", + "minecraft:polished_sulfur_slab": "磨制硫黄台阶", + "minecraft:polished_sulfur_double_slab": "磨制硫黄双层台阶", + "minecraft:polished_sulfur_stairs": "磨制硫黄楼梯", + "minecraft:polished_sulfur_wall": "磨制硫黄墙", + "minecraft:sulfur_bricks": "硫黄砖", + "minecraft:sulfur_brick_slab": "硫黄砖台阶", + "minecraft:sulfur_brick_double_slab": "硫黄砖双层台阶", + "minecraft:sulfur_brick_stairs": "硫黄砖楼梯", + "minecraft:sulfur_brick_wall": "硫黄砖墙", + "minecraft:chiseled_sulfur": "雕纹硫黄", + "minecraft:potent_sulfur": "烈性硫黄", + "minecraft:cinnabar": "朱砂", + "minecraft:cinnabar_slab": "朱砂台阶", + "minecraft:cinnabar_double_slab": "朱砂双层台阶", + "minecraft:cinnabar_stairs": "朱砂楼梯", + "minecraft:cinnabar_wall": "朱砂墙", + "minecraft:polished_cinnabar": "磨制朱砂", + "minecraft:polished_cinnabar_slab": "磨制朱砂台阶", + "minecraft:polished_cinnabar_double_slab": "磨制朱砂双层台阶", + "minecraft:polished_cinnabar_stairs": "磨制朱砂楼梯", + "minecraft:polished_cinnabar_wall": "磨制朱砂墙", + "minecraft:cinnabar_bricks": "朱砂砖", + "minecraft:cinnabar_brick_slab": "朱砂砖台阶", + "minecraft:cinnabar_brick_double_slab": "朱砂砖双层台阶", + "minecraft:cinnabar_brick_stairs": "朱砂砖楼梯", + "minecraft:cinnabar_brick_wall": "朱砂砖墙", + "minecraft:chiseled_cinnabar": "雕纹朱砂", + "minecraft:sulfur_spike": "硫黄尖锥", + "minecraft:poplar_button": "杨木按钮", + "minecraft:poplar_door": "杨木门", + "minecraft:poplar_fence": "杨木栅栏", + "minecraft:poplar_fence_gate": "杨木栅栏门", + "minecraft:poplar_hanging_sign": "悬挂式杨木告示牌", + "minecraft:stripped_poplar_log": "去皮杨木原木", + "minecraft:poplar_log": "杨木原木", + "minecraft:poplar_planks": "杨木木板", + "minecraft:poplar_pressure_plate": "杨木压力板", + "minecraft:poplar_slab": "杨木台阶", + "minecraft:poplar_double_slab": "杨木双层台阶", + "minecraft:poplar_stairs": "杨木楼梯", + "minecraft:poplar_standing_sign": "杨木告示牌", + "minecraft:poplar_trapdoor": "杨木活板门", + "minecraft:poplar_wall_sign": "墙上的杨木告示牌", + "minecraft:stripped_poplar_wood": "去皮杨木", + "minecraft:poplar_wood": "杨木", + "minecraft:poplar_sapling": "杨树树苗", + "minecraft:orange_poplar_leaves": "橙色杨树树叶", + "minecraft:red_poplar_leaves": "红色杨树树叶", + "minecraft:yellow_poplar_leaves": "黄色杨树树叶", + "minecraft:poplar_shelf": "杨木展示架", + "minecraft:red_shrub": "红灌木", + "minecraft:red_wool_stairs": "红色羊毛楼梯", + "minecraft:white_wool_stairs": "白色羊毛楼梯", + "minecraft:blue_wool_stairs": "蓝色羊毛楼梯", + "minecraft:orange_wool_stairs": "橙色羊毛楼梯", + "minecraft:magenta_wool_stairs": "品红色羊毛楼梯", + "minecraft:light_blue_wool_stairs": "淡蓝色羊毛楼梯", + "minecraft:yellow_wool_stairs": "黄色羊毛楼梯", + "minecraft:lime_wool_stairs": "黄绿色羊毛楼梯", + "minecraft:pink_wool_stairs": "粉红色羊毛楼梯", + "minecraft:gray_wool_stairs": "灰色羊毛楼梯", + "minecraft:light_gray_wool_stairs": "淡灰色羊毛楼梯", + "minecraft:cyan_wool_stairs": "青色羊毛楼梯", + "minecraft:purple_wool_stairs": "紫色羊毛楼梯", + "minecraft:green_wool_stairs": "绿色羊毛楼梯", + "minecraft:brown_wool_stairs": "棕色羊毛楼梯", + "minecraft:black_wool_stairs": "黑色羊毛楼梯", + "minecraft:white_wool_slab": "白色羊毛台阶", + "minecraft:light_gray_wool_slab": "淡灰色羊毛台阶", + "minecraft:gray_wool_slab": "灰色羊毛台阶", + "minecraft:black_wool_slab": "黑色羊毛台阶", + "minecraft:brown_wool_slab": "棕色羊毛台阶", + "minecraft:red_wool_slab": "红色羊毛台阶", + "minecraft:orange_wool_slab": "橙色羊毛台阶", + "minecraft:yellow_wool_slab": "黄色羊毛台阶", + "minecraft:lime_wool_slab": "黄绿色羊毛台阶", + "minecraft:green_wool_slab": "绿色羊毛台阶", + "minecraft:cyan_wool_slab": "青色羊毛台阶", + "minecraft:light_blue_wool_slab": "淡蓝色羊毛台阶", + "minecraft:blue_wool_slab": "蓝色羊毛台阶", + "minecraft:purple_wool_slab": "紫色羊毛台阶", + "minecraft:magenta_wool_slab": "品红色羊毛台阶", + "minecraft:pink_wool_slab": "粉红色羊毛台阶", + "minecraft:white_wool_double_slab": "白色羊毛双层台阶", + "minecraft:light_gray_wool_double_slab": "淡灰色羊毛双层台阶", + "minecraft:gray_wool_double_slab": "灰色羊毛双层台阶", + "minecraft:black_wool_double_slab": "黑色羊毛双层台阶", + "minecraft:brown_wool_double_slab": "棕色羊毛双层台阶", + "minecraft:red_wool_double_slab": "红色羊毛双层台阶", + "minecraft:orange_wool_double_slab": "橙色羊毛双层台阶", + "minecraft:yellow_wool_double_slab": "黄色羊毛双层台阶", + "minecraft:lime_wool_double_slab": "黄绿色羊毛双层台阶", + "minecraft:green_wool_double_slab": "绿色羊毛双层台阶", + "minecraft:cyan_wool_double_slab": "青色羊毛双层台阶", + "minecraft:light_blue_wool_double_slab": "淡蓝色羊毛双层台阶", + "minecraft:blue_wool_double_slab": "蓝色羊毛双层台阶", + "minecraft:purple_wool_double_slab": "紫色羊毛双层台阶", + "minecraft:magenta_wool_double_slab": "品红色羊毛双层台阶", + "minecraft:pink_wool_double_slab": "粉红色羊毛双层台阶", + "minecraft:shelf_mushroom": "层孔菇", + "minecraft:straw_bed": "麦秆床" + }, + "item": { "minecraft:golden_apple": "金苹果", "minecraft:enchanted_golden_apple": "附魔金苹果", "minecraft:mushroom_stew": "蘑菇煲", "minecraft:bread": "面包", "minecraft:porkchop": "生猪排", "minecraft:cooked_porkchop": "熟猪排", - "minecraft:cod": "鳕鱼", - "minecraft:salmon": "鲑鱼", + "minecraft:cod": "生鳕鱼", + "minecraft:salmon": "生鲑鱼", "minecraft:tropical_fish": "热带鱼", "minecraft:pufferfish": "河豚", "minecraft:cooked_cod": "熟鳕鱼", @@ -1401,7 +1579,7 @@ "minecraft:melon_slice": "西瓜", "minecraft:beef": "生牛肉", "minecraft:cooked_beef": "牛排", - "minecraft:chicken": "鸡", + "minecraft:chicken": "生鸡肉", "minecraft:cooked_chicken": "熟鸡肉", "minecraft:rotten_flesh": "腐肉", "minecraft:spider_eye": "蜘蛛眼", @@ -1411,14 +1589,16 @@ "minecraft:poisonous_potato": "毒马铃薯", "minecraft:golden_carrot": "金胡萝卜", "minecraft:pumpkin_pie": "南瓜派", + "minecraft:beetroot": "甜菜根", "minecraft:beetroot_soup": "甜菜汤", "minecraft:sweet_berries": "甜浆果", - "minecraft:rabbit": "兔子", + "minecraft:rabbit": "生兔肉", "minecraft:cooked_rabbit": "熟兔肉", "minecraft:rabbit_stew": "兔肉煲", "minecraft:wheat_seeds": "种子", "minecraft:pumpkin_seeds": "南瓜种子", "minecraft:melon_seeds": "西瓜种子", + "minecraft:nether_wart": "下界疣", "minecraft:beetroot_seeds": "甜菜种子", "minecraft:torchflower_seeds": "火把花种子", "minecraft:pitcher_pod": "瓶子草荚果", @@ -1427,7 +1607,7 @@ "minecraft:iron_axe": "铁斧", "minecraft:flint_and_steel": "打火石", "minecraft:bow": "弓", - "minecraft:arrow": "射出的箭", + "minecraft:arrow": "箭", "minecraft:coal": "煤炭", "minecraft:charcoal": "木炭", "minecraft:diamond": "钻石", @@ -1461,6 +1641,7 @@ "minecraft:iron_hoe": "铁锄", "minecraft:diamond_hoe": "钻石锄", "minecraft:golden_hoe": "金锄", + "minecraft:wheat": "小麦", "minecraft:leather_helmet": "皮革帽子", "minecraft:leather_chestplate": "皮革外套", "minecraft:leather_leggings": "皮革裤子", @@ -1485,6 +1666,7 @@ "minecraft:flint": "燧石", "minecraft:painting": "画", "minecraft:oak_sign": "橡木告示牌", + "minecraft:wooden_door": "橡木门", "minecraft:bucket": "铁桶", "minecraft:milk_bucket": "奶桶", "minecraft:water_bucket": "水桶", @@ -1497,8 +1679,9 @@ "minecraft:axolotl_bucket": "美西螈桶", "minecraft:minecart": "矿车", "minecraft:saddle": "鞍", + "minecraft:iron_door": "铁门", "minecraft:redstone": "红石粉", - "minecraft:snowball": "扔出的雪球", + "minecraft:snowball": "雪球", "minecraft:oak_boat": "橡木船", "minecraft:birch_boat": "白桦木船", "minecraft:jungle_boat": "丛林木船", @@ -1506,6 +1689,7 @@ "minecraft:acacia_boat": "金合欢木船", "minecraft:dark_oak_boat": "深色橡木船", "minecraft:leather": "皮革", + "minecraft:kelp": "海带", "minecraft:brick": "红砖", "minecraft:clay_ball": "黏土", "minecraft:sugar_cane": "甘蔗", @@ -1513,7 +1697,7 @@ "minecraft:book": "书", "minecraft:slime_ball": "黏液球", "minecraft:chest_minecart": "运输矿车", - "minecraft:egg": "扔出的鸡蛋", + "minecraft:egg": "鸡蛋", "minecraft:compass": "指南针", "minecraft:fishing_rod": "钓鱼竿", "minecraft:clock": "时钟", @@ -1540,10 +1724,12 @@ "minecraft:lapis_lazuli": "青金石", "minecraft:bone": "骨头", "minecraft:sugar": "糖", + "minecraft:cake": "蛋糕", + "minecraft:bed": "床", "minecraft:repeater": "红石中继器", "minecraft:filled_map": "地图", "minecraft:shears": "剪刀", - "minecraft:ender_pearl": "扔出的末影珍珠", + "minecraft:ender_pearl": "末影珍珠", "minecraft:blaze_rod": "烈焰棒", "minecraft:ghast_tear": "恶魂之泪", "minecraft:gold_nugget": "金粒", @@ -1552,6 +1738,8 @@ "minecraft:fermented_spider_eye": "发酵蛛眼", "minecraft:blaze_powder": "烈焰粉", "minecraft:magma_cream": "岩浆膏", + "minecraft:brewing_stand": "酿造台", + "minecraft:cauldron": "炼药锅", "minecraft:ender_eye": "末影之眼", "minecraft:glistering_melon_slice": "闪烁的西瓜片", "minecraft:chicken_spawn_egg": "鸡刷怪蛋", @@ -1590,7 +1778,7 @@ "minecraft:skeleton_horse_spawn_egg": "骷髅马刷怪蛋", "minecraft:zombie_horse_spawn_egg": "僵尸马刷怪蛋", "minecraft:shulker_spawn_egg": "潜影贝刷怪蛋", - "minecraft:npc_spawn_egg": "PC刷怪蛋", + "minecraft:npc_spawn_egg": "NPC刷怪蛋", "minecraft:elder_guardian_spawn_egg": "远古守卫者刷怪蛋", "minecraft:polar_bear_spawn_egg": "北极熊刷怪蛋", "minecraft:llama_spawn_egg": "羊驼刷怪蛋", @@ -1639,6 +1827,8 @@ "minecraft:writable_book": "书与笔", "minecraft:written_book": "成书", "minecraft:emerald": "绿宝石", + "minecraft:frame": "物品展示框", + "minecraft:flower_pot": "花盆", "minecraft:empty_map": "空地图", "minecraft:carrot_on_a_stick": "胡萝卜钓竿", "minecraft:nether_star": "下界之星", @@ -1648,26 +1838,27 @@ "minecraft:comparator": "红石比较器", "minecraft:netherbrick": "下界砖", "minecraft:quartz": "下界石英", - "minecraft:tnt_minecart": "TT矿车", + "minecraft:tnt_minecart": "TNT矿车", "minecraft:hopper_minecart": "漏斗矿车", + "minecraft:hopper": "漏斗", "minecraft:rabbit_foot": "兔子脚", "minecraft:rabbit_hide": "兔子皮", "minecraft:leather_horse_armor": "皮革马铠", "minecraft:iron_horse_armor": "铁马铠", "minecraft:gold_horse_armor": "金马铠", "minecraft:diamond_horse_armor": "钻石马铠", - "minecraft:music_disc_13": "音乐唱片 (13)", - "minecraft:music_disc_cat": "音乐唱片 (cat)", - "minecraft:music_disc_blocks": "音乐唱片 (blocks)", - "minecraft:music_disc_chirp": "音乐唱片 (chirp)", - "minecraft:music_disc_far": "音乐唱片 (far)", - "minecraft:music_disc_mall": "音乐唱片 (mall)", - "minecraft:music_disc_mellohi": "音乐唱片 (mellohi)", - "minecraft:music_disc_stal": "音乐唱片 (stal)", - "minecraft:music_disc_strad": "音乐唱片 (strad)", - "minecraft:music_disc_ward": "音乐唱片 (ward)", - "minecraft:music_disc_11": "音乐唱片 (11)", - "minecraft:music_disc_wait": "音乐唱片 (wait)", + "minecraft:music_disc_13": "音乐唱片13", + "minecraft:music_disc_cat": "音乐唱片cat", + "minecraft:music_disc_blocks": "音乐唱片blocks", + "minecraft:music_disc_chirp": "音乐唱片chirp", + "minecraft:music_disc_far": "音乐唱片far", + "minecraft:music_disc_mall": "音乐唱片mall", + "minecraft:music_disc_mellohi": "音乐唱片mellohi", + "minecraft:music_disc_stal": "音乐唱片stal", + "minecraft:music_disc_strad": "音乐唱片strad", + "minecraft:music_disc_ward": "音乐唱片ward", + "minecraft:music_disc_11": "音乐唱片11", + "minecraft:music_disc_wait": "音乐唱片wait", "minecraft:trident": "三叉戟", "minecraft:lead": "拴绳", "minecraft:name_tag": "命名牌", @@ -1675,11 +1866,16 @@ "minecraft:mutton": "生羊肉", "minecraft:cooked_mutton": "熟羊肉", "minecraft:armor_stand": "盔甲架", + "minecraft:spruce_door": "云杉木门", + "minecraft:birch_door": "白桦木门", + "minecraft:jungle_door": "丛林木门", + "minecraft:acacia_door": "金合欢木门", + "minecraft:dark_oak_door": "深色橡木门", "minecraft:chorus_fruit": "紫颂果", "minecraft:popped_chorus_fruit": "爆裂紫颂果", "minecraft:dragon_breath": "龙息", - "minecraft:splash_potion": "扔出的喷溅药水", - "minecraft:lingering_potion": "扔出的滞留药水", + "minecraft:splash_potion": "喷溅药水", + "minecraft:lingering_potion": "滞留药水", "minecraft:command_block_minecart": "命令方块矿车", "minecraft:elytra": "鞘翅", "minecraft:prismarine_shard": "海晶碎片", @@ -1689,7 +1885,7 @@ "minecraft:iron_nugget": "铁粒", "minecraft:nautilus_shell": "鹦鹉螺壳", "minecraft:heart_of_the_sea": "海洋之心", - "minecraft:scute": "鳞甲", + "minecraft:scute": "海龟鳞甲", "minecraft:turtle_helmet": "海龟壳", "minecraft:phantom_membrane": "幻翼膜", "minecraft:crossbow": "弩", @@ -1708,11 +1904,13 @@ "minecraft:globe_banner_pattern": "地球旗帜图案", "minecraft:flow_banner_pattern": "涡流旗帜图案", "minecraft:guster_banner_pattern": "旋风旗帜图案", + "minecraft:campfire": "营火", "minecraft:suspicious_stew": "谜之炖菜", "minecraft:honeycomb": "蜜脾", "minecraft:honey_bottle": "蜂蜜瓶", "minecraft:ominous_bottle": "不祥之瓶", "minecraft:board": "黑板", + "minecraft:camera": "摄像机", "minecraft:compound": "化合物", "minecraft:ice_bomb": "冰弹", "minecraft:bleach": "漂白剂", @@ -1735,20 +1933,26 @@ "minecraft:netherite_scrap": "下界合金碎片", "minecraft:crimson_sign": "绯红木告示牌", "minecraft:warped_sign": "诡异木告示牌", + "minecraft:crimson_door": "绯红木门", + "minecraft:warped_door": "诡异木门", "minecraft:warped_fungus_on_a_stick": "诡异菌钓竿", - "minecraft:music_disc_pigstep": "音乐唱片 (Pigstep)", + "minecraft:music_disc_pigstep": "音乐唱片Pigstep", + "minecraft:nether_sprouts": "下界苗", + "minecraft:soul_campfire": "灵魂营火", + "minecraft:glow_frame": "荧光物品展示框", "minecraft:amethyst_shard": "紫水晶碎片", "minecraft:spyglass": "望远镜", - "minecraft:music_disc_otherside": "音乐唱片 (otherside)", + "minecraft:music_disc_otherside": "音乐唱片otherside", "minecraft:goat_horn": "山羊角", "minecraft:frog_spawn_egg": "青蛙刷怪蛋", "minecraft:tadpole_spawn_egg": "蝌蚪刷怪蛋", "minecraft:tadpole_bucket": "蝌蚪桶", "minecraft:allay_spawn_egg": "悦灵刷怪蛋", "minecraft:warden_spawn_egg": "监守者刷怪蛋", + "minecraft:mangrove_door": "红树木门", "minecraft:mangrove_sign": "红树木告示牌", "minecraft:mangrove_boat": "红树木船", - "minecraft:music_disc_5": "音乐唱片 (5)", + "minecraft:music_disc_5": "音乐唱片5", "minecraft:disc_fragment_5": "唱片残片(5)", "minecraft:oak_chest_boat": "橡木运输船", "minecraft:birch_chest_boat": "白桦木运输船", @@ -1767,7 +1971,8 @@ "minecraft:bamboo_sign": "竹告示牌", "minecraft:bamboo_raft": "竹筏", "minecraft:bamboo_chest_raft": "运输竹筏", - "minecraft:camel_spawn_egg": "生成骆驼", + "minecraft:camel_spawn_egg": "骆驼刷怪蛋", + "minecraft:camel_husk_spawn_egg": "骆驼尸壳刷怪蛋", "minecraft:angler_pottery_sherd": "垂钓纹样陶片", "minecraft:archer_pottery_sherd": "弓箭纹样陶片", "minecraft:arms_up_pottery_sherd": "举臂纹样陶片", @@ -1792,26 +1997,26 @@ "minecraft:skull_pottery_sherd": "头颅纹样陶片", "minecraft:snort_pottery_sherd": "嗅探纹样陶片", "minecraft:brush": "刷子", - "minecraft:netherite_upgrade_smithing_template": "下界合金升级锻造模板", - "minecraft:sentry_armor_trim_smithing_template": "哨兵盔甲纹饰锻造模板", - "minecraft:dune_armor_trim_smithing_template": "沙丘盔甲纹饰锻造模板", - "minecraft:coast_armor_trim_smithing_template": "海岸盔甲纹饰锻造模板", - "minecraft:wild_armor_trim_smithing_template": "荒野盔甲纹饰锻造模板", - "minecraft:ward_armor_trim_smithing_template": "监守盔甲纹饰锻造模板", - "minecraft:eye_armor_trim_smithing_template": "眼眸盔甲纹饰锻造模板", - "minecraft:vex_armor_trim_smithing_template": "恼鬼盔甲纹饰锻造模板", - "minecraft:tide_armor_trim_smithing_template": "潮汐盔甲纹饰锻造模板", - "minecraft:snout_armor_trim_smithing_template": "猪鼻盔甲纹饰锻造模板", - "minecraft:rib_armor_trim_smithing_template": "肋骨盔甲纹饰锻造模板", - "minecraft:spire_armor_trim_smithing_template": "尖塔盔甲纹饰锻造模板", - "minecraft:silence_armor_trim_smithing_template": "幽静盔甲纹饰锻造模板", - "minecraft:wayfinder_armor_trim_smithing_template": "向导盔甲纹饰锻造模板", - "minecraft:raiser_armor_trim_smithing_template": "牧民盔甲纹饰锻造模板", - "minecraft:shaper_armor_trim_smithing_template": "塑造盔甲纹饰锻造模板", - "minecraft:host_armor_trim_smithing_template": "雇主盔甲纹饰锻造模板", - "minecraft:flow_armor_trim_smithing_template": "涡流盔甲纹饰锻造模板", - "minecraft:bolt_armor_trim_smithing_template": "镶铆盔甲纹饰锻造模板", - "minecraft:music_disc_relic": "音乐唱片 (Reilc)", + "minecraft:netherite_upgrade_smithing_template": "下界合金升级", + "minecraft:sentry_armor_trim_smithing_template": "哨兵盔甲纹饰", + "minecraft:dune_armor_trim_smithing_template": "沙丘盔甲纹饰", + "minecraft:coast_armor_trim_smithing_template": "海岸盔甲纹饰", + "minecraft:wild_armor_trim_smithing_template": "荒野盔甲纹饰", + "minecraft:ward_armor_trim_smithing_template": "监守盔甲纹饰", + "minecraft:eye_armor_trim_smithing_template": "眼眸盔甲纹饰", + "minecraft:vex_armor_trim_smithing_template": "恼鬼盔甲纹饰", + "minecraft:tide_armor_trim_smithing_template": "潮汐盔甲纹饰", + "minecraft:snout_armor_trim_smithing_template": "猪鼻盔甲纹饰", + "minecraft:rib_armor_trim_smithing_template": "肋骨盔甲纹饰", + "minecraft:spire_armor_trim_smithing_template": "尖塔盔甲纹饰", + "minecraft:silence_armor_trim_smithing_template": "幽静盔甲纹饰", + "minecraft:wayfinder_armor_trim_smithing_template": "向导盔甲纹饰", + "minecraft:raiser_armor_trim_smithing_template": "牧民盔甲纹饰", + "minecraft:shaper_armor_trim_smithing_template": "塑造盔甲纹饰", + "minecraft:host_armor_trim_smithing_template": "雇主盔甲纹饰", + "minecraft:flow_armor_trim_smithing_template": "涡流盔甲纹饰", + "minecraft:bolt_armor_trim_smithing_template": "镶铆盔甲纹饰", + "minecraft:music_disc_relic": "音乐唱片Relic", "minecraft:skull": "生物头颅", "minecraft:stained_hardened_clay": "带釉陶瓦", "minecraft:armadillo_spawn_egg": "犰狳刷怪蛋", @@ -1822,6 +2027,68 @@ "minecraft:pale_oak_sign": "苍白橡木告示牌", "minecraft:creaking_spawn_egg": "嘎枝刷怪蛋", "minecraft:resin_brick": "树脂砖", + "minecraft:blue_egg": "蓝色鸡蛋", + "minecraft:brown_egg": "棕色鸡蛋", + "minecraft:happy_ghast_spawn_egg": "快乐恶魂刷怪蛋", + "minecraft:black_harness": "黑色挽具", + "minecraft:blue_harness": "蓝色挽具", + "minecraft:brown_harness": "棕色挽具", + "minecraft:cyan_harness": "青色挽具", + "minecraft:gray_harness": "灰色挽具", + "minecraft:green_harness": "绿色挽具", + "minecraft:light_blue_harness": "淡蓝色挽具", + "minecraft:light_gray_harness": "淡灰色挽具", + "minecraft:lime_harness": "黄绿色挽具", + "minecraft:magenta_harness": "品红色挽具", + "minecraft:orange_harness": "橙色挽具", + "minecraft:pink_harness": "粉红色挽具", + "minecraft:purple_harness": "紫色挽具", + "minecraft:red_harness": "红色挽具", + "minecraft:white_harness": "白色挽具", + "minecraft:yellow_harness": "黄色挽具", + "minecraft:copper_golem_spawn_egg": "铜傀儡刷怪蛋", + "minecraft:copper_sword": "铜剑", + "minecraft:copper_shovel": "铜锹", + "minecraft:copper_pickaxe": "铜镐", + "minecraft:copper_axe": "铜斧", + "minecraft:copper_hoe": "铜锄", + "minecraft:copper_helmet": "铜头盔", + "minecraft:copper_chestplate": "铜胸甲", + "minecraft:copper_leggings": "铜护腿", + "minecraft:copper_boots": "铜靴子", + "minecraft:copper_nugget": "铜粒", + "minecraft:copper_horse_armor": "铜马铠", + "minecraft:nautilus_spawn_egg": "鹦鹉螺刷怪蛋", + "minecraft:zombie_nautilus_spawn_egg": "僵尸鹦鹉螺刷怪蛋", + "minecraft:parched_spawn_egg": "焦骸刷怪蛋", + "minecraft:copper_nautilus_armor": "铜鹦鹉螺铠", + "minecraft:iron_nautilus_armor": "铁鹦鹉螺铠", + "minecraft:golden_nautilus_armor": "金鹦鹉螺铠", + "minecraft:diamond_nautilus_armor": "钻石鹦鹉螺铠", + "minecraft:netherite_nautilus_armor": "下界合金鹦鹉螺铠", + "minecraft:netherite_horse_armor": "下界合金马铠", + "minecraft:sulfur_cube_spawn_egg": "硫方怪刷怪蛋", + "minecraft:sulfur_cube_bucket": "硫方怪桶", + "minecraft:poplar_boat": "杨木船", + "minecraft:poplar_chest_boat": "杨木运输船", + "minecraft:poplar_sign": "杨木告示牌", + "minecraft:straw_bed": "麦秆床", + "minecraft:black_cushion": "黑色坐垫", + "minecraft:blue_cushion": "蓝色坐垫", + "minecraft:brown_cushion": "棕色坐垫", + "minecraft:cyan_cushion": "青色坐垫", + "minecraft:gray_cushion": "灰色坐垫", + "minecraft:green_cushion": "绿色坐垫", + "minecraft:light_blue_cushion": "淡蓝色坐垫", + "minecraft:light_gray_cushion": "淡灰色坐垫", + "minecraft:lime_cushion": "黄绿色坐垫", + "minecraft:magenta_cushion": "品红色坐垫", + "minecraft:orange_cushion": "橙色坐垫", + "minecraft:pink_cushion": "粉红色坐垫", + "minecraft:purple_cushion": "紫色坐垫", + "minecraft:red_cushion": "红色坐垫", + "minecraft:white_cushion": "白色坐垫", + "minecraft:yellow_cushion": "黄色坐垫", "minecraft:wool": "羊毛", "minecraft:carpet": "地毯", "minecraft:log": "原木", @@ -1840,14 +2107,14 @@ "minecraft:coral_fan_dead": "失活的珊瑚扇", "minecraft:sapling": "树苗", "minecraft:leaves": "树叶", - "minecraft:leaves2": "树叶", + "minecraft:leaves2": "树叶2", "minecraft:wooden_slab": "木质台阶", "minecraft:red_flower": "小型花", "minecraft:double_plant": "大型花", - "minecraft:planks": "Plank", + "minecraft:planks": "木板", "minecraft:coral": "珊瑚", "minecraft:tallgrass": "高草丛", - "minecraft:log2": "原木", + "minecraft:log2": "原木2", "minecraft:monster_egg": "虫蚀方块", "minecraft:concrete": "混凝土", "minecraft:concrete_powder": "混凝土粉末", @@ -1855,9 +2122,12 @@ "minecraft:stained_glass_pane": "染色玻璃板", "minecraft:shulker_box": "染色的潜影盒", "minecraft:wood": "木头", - "minecraft:music_disc_creator": "音乐唱片 (Creator)", - "minecraft:music_disc_creator_music_box": "音乐唱片 (Creator 八音盒)", - "minecraft:music_disc_precipice": "音乐唱片 (Precipice)", + "minecraft:music_disc_creator": "音乐唱片Creator", + "minecraft:music_disc_creator_music_box": "音乐唱片Creator(八音盒)", + "minecraft:music_disc_precipice": "音乐唱片Precipice", + "minecraft:music_disc_tears": "音乐唱片Tears", + "minecraft:music_disc_lava_chicken": "音乐唱片Lava Chicken", + "minecraft:music_disc_bounce": "音乐唱片Bounce", "minecraft:chemistry_table": "化学设备", "minecraft:hard_stained_glass": "染色强化玻璃", "minecraft:hard_stained_glass_pane": "染色强化玻璃板", @@ -1868,9 +2138,43 @@ "minecraft:dye": "染料", "minecraft:banner_pattern": "旗帜图案", "minecraft:spawn_egg": "刷怪蛋", + "minecraft:red_shrub": "红灌木", + "minecraft:shelf_mushroom": "层孔菇", "minecraft:end_crystal": "末地水晶", - "minecraft:glow_berries": "发光浆果", + "minecraft:copper_spear": "铜矛", + "minecraft:diamond_spear": "钻石矛", + "minecraft:golden_spear": "金矛", + "minecraft:iron_spear": "铁矛", + "minecraft:netherite_spear": "下界合金矛", + "minecraft:stone_spear": "石矛", + "minecraft:wooden_spear": "木矛", + "minecraft:black_bundle": "黑色收纳袋", + "minecraft:blue_bundle": "蓝色收纳袋", + "minecraft:brown_bundle": "棕色收纳袋", + "minecraft:bundle": "收纳袋", + "minecraft:cyan_bundle": "青色收纳袋", + "minecraft:gray_bundle": "灰色收纳袋", + "minecraft:green_bundle": "绿色收纳袋", + "minecraft:light_blue_bundle": "淡蓝色收纳袋", + "minecraft:light_gray_bundle": "淡灰色收纳袋", + "minecraft:lime_bundle": "黄绿色收纳袋", + "minecraft:magenta_bundle": "品红色收纳袋", + "minecraft:orange_bundle": "橙色收纳袋", + "minecraft:pink_bundle": "粉红色收纳袋", + "minecraft:purple_bundle": "紫色收纳袋", + "minecraft:red_bundle": "红色收纳袋", + "minecraft:white_bundle": "白色收纳袋", + "minecraft:yellow_bundle": "黄色收纳袋", + "minecraft:breeze_rod": "旋风棒", + "minecraft:ominous_trial_key": "不祥试炼钥匙", + "minecraft:trial_key": "试炼钥匙", + "minecraft:wind_charge": "风弹", + "minecraft:apple": "苹果", + "minecraft:glow_berries": "发光浆果" + }, + "entity": { "minecraft:undefined_test_only": "Undefined Test Only", + "minecraft:chicken": "鸡", "minecraft:cow": "牛", "minecraft:pig": "猪", "minecraft:sheep": "绵羊", @@ -1878,6 +2182,7 @@ "minecraft:villager": "旧版村民", "minecraft:mooshroom": "哞菇", "minecraft:squid": "鱿鱼", + "minecraft:rabbit": "兔子", "minecraft:bat": "蝙蝠", "minecraft:iron_golem": "铁傀儡", "minecraft:snow_golem": "雪傀儡", @@ -1896,20 +2201,21 @@ "minecraft:skeleton": "骷髅", "minecraft:spider": "蜘蛛", "minecraft:zombie_pigman": "僵尸猪灵", + "minecraft:slime": "史莱姆", "minecraft:enderman": "末影人", "minecraft:silverfish": "蠹虫", "minecraft:cave_spider": "洞穴蜘蛛", "minecraft:ghast": "恶魂", "minecraft:magma_cube": "岩浆怪", "minecraft:blaze": "烈焰人", - "minecraft:zombie_villager": "僵尸村民", + "minecraft:zombie_villager": "旧版僵尸村民", "minecraft:witch": "女巫", "minecraft:stray": "流浪者", "minecraft:husk": "尸壳", "minecraft:wither_skeleton": "凋灵骷髅", "minecraft:guardian": "守卫者", "minecraft:elder_guardian": "远古守卫者", - "minecraft:npc": "PC", + "minecraft:npc": "NPC", "minecraft:wither": "凋灵", "minecraft:ender_dragon": "末影龙", "minecraft:shulker": "潜影贝", @@ -1918,10 +2224,13 @@ "minecraft:vindicator": "卫道士", "minecraft:phantom": "幻翼", "minecraft:ravager": "劫掠兽", + "minecraft:armor_stand": "盔甲架", "minecraft:tripod_camera": "摄像机", "minecraft:player": "玩家", "minecraft:item": "掉落物", + "minecraft:tnt": "点燃的TNT", "minecraft:falling_block": "下落的方块", + "minecraft:moving_block": "移动的活塞", "minecraft:xp_bottle": "扔出的附魔之瓶", "minecraft:xp_orb": "经验球", "minecraft:eye_of_ender_signal": "扔出的末影之眼", @@ -1932,24 +2241,44 @@ "minecraft:cat": "流浪猫", "minecraft:shulker_bullet": "潜影弹", "minecraft:fishing_hook": "浮漂", + "minecraft:chalkboard": "黑板", "minecraft:dragon_fireball": "末影龙火球", + "minecraft:arrow": "射出的箭", + "minecraft:snowball": "扔出的雪球", + "minecraft:egg": "扔出的鸡蛋", + "minecraft:painting": "画", + "minecraft:minecart": "矿车", "minecraft:fireball": "恶魂火球", + "minecraft:splash_potion": "扔出的喷溅药水", + "minecraft:ender_pearl": "扔出的末影珍珠", "minecraft:leash_knot": "拴绳结", "minecraft:wither_skull": "凋灵之首", + "minecraft:boat": "船", "minecraft:wither_skull_dangerous": "蓝色凋灵之首", "minecraft:lightning_bolt": "闪电", "minecraft:small_fireball": "小火球", "minecraft:area_effect_cloud": "区域效果云", + "minecraft:hopper_minecart": "漏斗矿车", + "minecraft:tnt_minecart": "TNT矿车", + "minecraft:chest_minecart": "运输矿车", + "minecraft:command_block_minecart": "命令方块矿车", + "minecraft:lingering_potion": "扔出的滞留药水", "minecraft:llama_spit": "羊驼唾沫", "minecraft:evocation_fang": "尖牙", "minecraft:evocation_illager": "唤魔者", "minecraft:vex": "恼鬼", + "minecraft:ice_bomb": "冰弹", + "minecraft:balloon": "气球", + "minecraft:pufferfish": "河豚", + "minecraft:salmon": "鲑鱼", "minecraft:drowned": "溺尸", "minecraft:tropicalfish": "热带鱼", + "minecraft:cod": "鳕鱼", "minecraft:panda": "熊猫", "minecraft:pillager": "掠夺者", "minecraft:villager_v2": "村民", "minecraft:zombie_villager_v2": "僵尸村民", + "minecraft:shield": "盾牌", "minecraft:wandering_trader": "流浪商人", "minecraft:elder_guardian_ghost": "远古守卫者幽灵", "minecraft:fox": "狐狸", @@ -1976,5 +2305,15 @@ "minecraft:bogged": "沼骸", "minecraft:ominous_item_spawner": "不祥之物生成器", "minecraft:creaking": "嘎枝", - "minecraft:trader_llama": "行商羊驼" -} \ No newline at end of file + "minecraft:happy_ghast": "快乐恶魂", + "minecraft:copper_golem": "铜傀儡", + "minecraft:nautilus": "鹦鹉螺", + "minecraft:zombie_nautilus": "僵尸鹦鹉螺", + "minecraft:parched": "焦骸", + "minecraft:camel_husk": "骆驼尸壳", + "minecraft:sulfur_cube": "硫方怪", + "minecraft:cushion": "坐垫", + "minecraft:trader_llama": "行商羊驼", + "minecraft:chest_boat": "运输船" + } +} diff --git a/src/lang/zh_HK.json b/src/lang/zh_HK.json index 1b060a0..e22e714 100644 --- a/src/lang/zh_HK.json +++ b/src/lang/zh_HK.json @@ -1,137 +1,156 @@ { - "gui.title.stats": "统计信息", - "gui.title.rank": "排行榜", - - "command.stats.name":"stats", - "command.stats.alias":"lks", - "command.stats.desc":"统计信息", - "command.error.notplayer":"此命令仅限玩家执行", - "command.error.player_name_empty":"玩家名不能为空", - "command.error.find_player":"找不到指定的玩家,请检查玩家名是否正确", - "command.error.rank_type_required":"排行榜类型为custom时,type不能为空", - - "log.info.ExistOldPath":"检测到旧数据, 迁移数据中", - "log.warn.moveStats.fail":"数据迁移失败", - "log.info.CreateStatsPath":"未检测到数据目录,正在初始化...", - "log.warn.CreatePath.fail":"数据目录创建失败", - "plugins.load.success":"插件加载成功!", - "plugins.load.fail":"插件加载失败,请检查数据目录是否存在", - "data.parse.fail":"{0} 文件解析失败,请检查文件内容", - "log.info.createData":"未检测到玩家 {0} 数据,正在创建...", - "log.info.savaData":"保存玩家 {0} 数据中...", - - "minecraft:custom": "通用", - "minecraft:mined": "方块挖掘", - "minecraft:broken": "物品破损", - "minecraft:crafted": "物品合成", - "minecraft:used": "物品使用", - "minecraft:picked_up": "物品拾取", - "minecraft:dropped": "物品丢弃", - "minecraft:killed": "实体击杀", - "minecraft:killed_by": "被击杀", - - "minecraft:animals_bred": "繁殖动物次数", - "minecraft:clean_armor": "清洗盔甲次数", - "minecraft:clean_banner": "清洗旗帜次数", - "minecraft:open_barrel": "木桶打开次数", - "minecraft:bell_ring": "鸣钟次数", - "minecraft:eat_cake_slice": "吃掉的蛋糕片数", - "minecraft:fill_cauldron": "炼药锅装水次数", - "minecraft:open_chest": "箱子打开次数", - "minecraft:damage_absorbed": "吸收的伤害", - "minecraft:damage_blocked_by_shield": "盾牌抵挡的伤害", - "minecraft:damage_dealt": "造成伤害", - "minecraft:damage_dealt_absorbed": "造成伤害(被吸收)", - "minecraft:damage_dealt_resisted": "造成伤害(被抵挡)", - "minecraft:damage_resisted": "抵挡的伤害", - "minecraft:damage_taken": "受到伤害", - "minecraft:inspect_dispenser": "搜查发射器次数", - "minecraft:happy_ghast_one_cm": "骑乘快乐恶魂移动距离(厘米)", - "minecraft:nautilus_one_cm": "骑鹦鹉螺移动距离(厘米)", - "minecraft:boat_one_cm": "坐船移动距离(厘米)", - "minecraft:aviate_one_cm": "鞘翅滑行距离(厘米)", - "minecraft:horse_one_cm": "骑马移动距离(厘米)", - "minecraft:minecart_one_cm": "坐矿车移动距离(厘米)", - "minecraft:pig_one_cm": "骑猪移动距离(厘米)", - "minecraft:strider_one_cm": "骑炽足兽移动距离(厘米)", - "minecraft:climb_one_cm": "已攀爬距离(厘米)", - "minecraft:crouch_one_cm": "潜行距离(厘米)", - "minecraft:fall_one_cm": "摔落高度(厘米)", - "minecraft:fly_one_cm": "飞行距离(厘米)", - "minecraft:sprint_one_cm": "疾跑距离(厘米)", - "minecraft:swim_one_cm": "游泳距离(厘米)", - "minecraft:walk_one_cm": "行走距离(厘米)", - "minecraft:walk_on_water_one_cm": "水面行走距离(厘米)", - "minecraft:walk_under_water_one_cm": "水下行走距离(厘米)", - "minecraft:inspect_dropper": "搜查投掷器次数", - "minecraft:open_enderchest": "末影箱打开次数", - "minecraft:fish_caught": "捕鱼数", - "minecraft:leave_game": "游戏退出次数", - "minecraft:inspect_hopper": "搜查漏斗次数", - "minecraft:interact_with_anvil": "与铁砧交互次数", - "minecraft:interact_with_beacon": "与信标交互次数", - "minecraft:interact_with_blast_furnace": "与高炉交互次数", - "minecraft:interact_with_brewingstand": "与酿造台交互次数", - "minecraft:interact_with_campfire": "与营火交互次数", - "minecraft:interact_with_cartography_table": "与制图台交互次数", - "minecraft:interact_with_crafting_table": "与工作台交互次数", - "minecraft:interact_with_furnace": "与熔炉交互次数", - "minecraft:interact_with_grindstone": "与砂轮交互次数", - "minecraft:interact_with_lectern": "与讲台交互次数", - "minecraft:interact_with_loom": "与织布机交互次数", - "minecraft:interact_with_smithing_table": "与锻造台交互次数", - "minecraft:interact_with_smoker": "与烟熏炉交互次数", - "minecraft:interact_with_stonecutter": "与切石机交互次数", - "minecraft:drop": "物品掉落", - "minecraft:enchant_item": "物品附魔次数", - "minecraft:jump": "跳跃次数", - "minecraft:mob_kills": "生物击杀数", - "minecraft:play_record": "播放唱片数", - "minecraft:play_noteblock": "音符盒播放次数", - "minecraft:tune_noteblock": "音符盒调音次数", - "minecraft:deaths": "死亡次数", - "minecraft:pot_flower": "盆栽种植数", - "minecraft:player_kills": "玩家击杀数", - "minecraft:raid_trigger": "触发袭击次数", - "minecraft:raid_win": "袭击胜利次数", - "minecraft:clean_shulker_box": "潜影盒清洗次数", - "minecraft:open_shulker_box": "潜影盒打开次数", - "minecraft:time_since_death": "自上次死亡(Tick)", - "minecraft:time_since_rest": "自上次入眠(Tick)", - "minecraft:sneak_time": "潜行时间(Tick)", - "minecraft:talked_to_villager": "村民交互次数", - "minecraft:target_hit": "击中标靶次数", - "minecraft:play_time": "游戏时间(Tick)", - "minecraft:total_world_time": "世界打开时间(Tick)", - "minecraft:sleep_in_bed": "躺在床上的次数", - "minecraft:traded_with_villager": "村民交易次数", - "minecraft:trigger_trapped_chest": "陷阱箱触发次数", - "minecraft:use_cauldron": "从炼药锅取水次数", - + "gui": { + "title": { + "stats": "統計資訊", + "rank": "排行榜" + } + }, + "command": { + "stats": { + "name": "stats", + "alias": "lks", + "desc": "統計資訊" + }, + "error": { + "not_player": "此指令只能由玩家執行", + "player_name_empty": "玩家名稱不能為空", + "player_not_found": "找不到指定玩家,請檢查玩家名稱是否正確", + "invalid_stats_category": "無效的統計分類", + "rank_stat_required": "排行榜分類為「通用」時,必須指定統計項" + } + }, + "log": { + "warning": { + "legacy_data_migration_started": "偵測到舊版統計資料,正在遷移...", + "legacy_data_migration_failed": "舊版統計資料遷移失敗", + "data_directory_initializing": "未偵測到統計資料目錄,正在初始化...", + "data_directory_creation_failed": "統計資料目錄建立失敗", + "plugin_load_failed": "插件載入失敗,請檢查統計資料目錄是否可用", + "data_parse_failed": "無法解析統計資料檔案 {0},請檢查檔案內容" + }, + "info": { + "plugin_loaded": "插件載入成功", + "player_data_creating": "未偵測到玩家 {0} 的統計資料,正在建立...", + "player_data_saving": "正在儲存玩家 {0} 的統計資料..." + } + }, + "stats": { + "category": { + "custom": "通用", + "mined": "方塊挖掘", + "broken": "物品損壞", + "crafted": "物品合成", + "used": "物品使用", + "picked_up": "物品拾取", + "dropped": "物品丟棄", + "killed": "實體擊殺", + "killed_by": "被擊殺" + }, + "custom": { + "minecraft:animals_bred": "繁殖动物次数", + "minecraft:clean_armor": "清洗盔甲次数", + "minecraft:clean_banner": "清洗旗帜次数", + "minecraft:open_barrel": "木桶打开次数", + "minecraft:bell_ring": "鸣钟次数", + "minecraft:eat_cake_slice": "吃掉的蛋糕片数", + "minecraft:fill_cauldron": "炼药锅装水次数", + "minecraft:open_chest": "箱子打开次数", + "minecraft:damage_absorbed": "吸收的伤害", + "minecraft:damage_blocked_by_shield": "盾牌抵挡的伤害", + "minecraft:damage_dealt": "造成伤害", + "minecraft:damage_dealt_absorbed": "造成伤害(被吸收)", + "minecraft:damage_dealt_resisted": "造成伤害(被抵挡)", + "minecraft:damage_resisted": "抵挡的伤害", + "minecraft:damage_taken": "受到伤害", + "minecraft:inspect_dispenser": "搜查发射器次数", + "minecraft:happy_ghast_one_cm": "骑乘快乐恶魂移动距离(厘米)", + "minecraft:nautilus_one_cm": "骑鹦鹉螺移动距离(厘米)", + "minecraft:boat_one_cm": "坐船移动距离(厘米)", + "minecraft:aviate_one_cm": "鞘翅滑行距离(厘米)", + "minecraft:horse_one_cm": "骑马移动距离(厘米)", + "minecraft:minecart_one_cm": "坐矿车移动距离(厘米)", + "minecraft:pig_one_cm": "骑猪移动距离(厘米)", + "minecraft:strider_one_cm": "骑炽足兽移动距离(厘米)", + "minecraft:climb_one_cm": "已攀爬距离(厘米)", + "minecraft:crouch_one_cm": "潜行距离(厘米)", + "minecraft:fall_one_cm": "摔落高度(厘米)", + "minecraft:fly_one_cm": "飞行距离(厘米)", + "minecraft:sprint_one_cm": "疾跑距离(厘米)", + "minecraft:swim_one_cm": "游泳距离(厘米)", + "minecraft:walk_one_cm": "行走距离(厘米)", + "minecraft:walk_on_water_one_cm": "水面行走距离(厘米)", + "minecraft:walk_under_water_one_cm": "水下行走距离(厘米)", + "minecraft:inspect_dropper": "搜查投掷器次数", + "minecraft:open_enderchest": "末影箱打开次数", + "minecraft:fish_caught": "捕鱼数", + "minecraft:leave_game": "游戏退出次数", + "minecraft:inspect_hopper": "搜查漏斗次数", + "minecraft:interact_with_anvil": "与铁砧交互次数", + "minecraft:interact_with_beacon": "与信标交互次数", + "minecraft:interact_with_blast_furnace": "与高炉交互次数", + "minecraft:interact_with_brewingstand": "与酿造台交互次数", + "minecraft:interact_with_campfire": "与营火交互次数", + "minecraft:interact_with_cartography_table": "与制图台交互次数", + "minecraft:interact_with_crafting_table": "与工作台交互次数", + "minecraft:interact_with_furnace": "与熔炉交互次数", + "minecraft:interact_with_grindstone": "与砂轮交互次数", + "minecraft:interact_with_lectern": "与讲台交互次数", + "minecraft:interact_with_loom": "与织布机交互次数", + "minecraft:interact_with_smithing_table": "与锻造台交互次数", + "minecraft:interact_with_smoker": "与烟熏炉交互次数", + "minecraft:interact_with_stonecutter": "与切石机交互次数", + "minecraft:drop": "物品掉落", + "minecraft:enchant_item": "物品附魔次数", + "minecraft:jump": "跳跃次数", + "minecraft:mob_kills": "生物击杀数", + "minecraft:play_record": "播放唱片数", + "minecraft:play_noteblock": "音符盒播放次数", + "minecraft:tune_noteblock": "音符盒调音次数", + "minecraft:deaths": "死亡次数", + "minecraft:pot_flower": "盆栽种植数", + "minecraft:player_kills": "玩家击杀数", + "minecraft:raid_trigger": "触发袭击次数", + "minecraft:raid_win": "袭击胜利次数", + "minecraft:clean_shulker_box": "潜影盒清洗次数", + "minecraft:open_shulker_box": "潜影盒打开次数", + "minecraft:time_since_death": "自上次死亡(Tick)", + "minecraft:time_since_rest": "自上次入眠(Tick)", + "minecraft:sneak_time": "潜行时间(Tick)", + "minecraft:talked_to_villager": "村民交互次数", + "minecraft:target_hit": "击中标靶次数", + "minecraft:play_time": "游戏时间(Tick)", + "minecraft:total_world_time": "世界打开时间(Tick)", + "minecraft:sleep_in_bed": "躺在床上的次数", + "minecraft:traded_with_villager": "村民交易次数", + "minecraft:trigger_trapped_chest": "陷阱箱触发次数", + "minecraft:use_cauldron": "从炼药锅取水次数" + } + }, + "block": { "minecraft:stone": "石頭", - "minecraft:grass_block": "草方塊", + "minecraft:grass_block": "草地", "minecraft:dirt": "泥土", "minecraft:cobblestone": "碎石", "minecraft:oak_planks": "橡木板", - "minecraft:oak_sapling": "橡樹樹苗", - "minecraft:bedrock": "基岩", + "minecraft:oak_sapling": "橡木樹苗", + "minecraft:bedrock": "基巖", "minecraft:flowing_water": "水", "minecraft:water": "靜態水", - "minecraft:flowing_lava": "熔岩", - "minecraft:lava": "靜態熔岩", + "minecraft:flowing_lava": "熔巖", + "minecraft:lava": "靜態熔巖", "minecraft:sand": "沙", "minecraft:gravel": "砂礫", "minecraft:gold_ore": "金礦", "minecraft:iron_ore": "鐵礦", "minecraft:coal_ore": "煤礦", "minecraft:oak_log": "橡木原木", - "minecraft:oak_leaves": "橡樹樹葉", + "minecraft:oak_leaves": "橡木樹葉", "minecraft:sponge": "海綿", "minecraft:glass": "玻璃", "minecraft:lapis_ore": "青金石礦", "minecraft:lapis_block": "青金石磚", "minecraft:dispenser": "發射器", - "minecraft:sandstone": "砂岩", + "minecraft:sandstone": "砂巖", "minecraft:noteblock": "音符盒", "minecraft:bed": "牀", "minecraft:golden_rail": "動力路軌", @@ -150,18 +169,18 @@ "minecraft:red_mushroom": "紅色蘑菇", "minecraft:gold_block": "金磚", "minecraft:iron_block": "鐵磚", - "minecraft:smooth_stone_double_slab": "平滑石頭雙層台階", - "minecraft:smooth_stone_slab": "平滑石頭台階", - "minecraft:brick_block": "紅磚頭塊", - "minecraft:tnt": "點燃的TT", - "minecraft:bookshelf": "書架", + "minecraft:smooth_stone_double_slab": "平滑石頭雙半磚", + "minecraft:smooth_stone_slab": "平滑石頭半磚", + "minecraft:brick_block": "紅磚", + "minecraft:tnt": "TNT", + "minecraft:bookshelf": "書櫃", "minecraft:mossy_cobblestone": "青苔碎石", "minecraft:obsidian": "黑曜石", "minecraft:torch": "火炬", "minecraft:fire": "火", "minecraft:mob_spawner": "生怪籠", "minecraft:oak_stairs": "橡木樓梯", - "minecraft:chest": "箱子", + "minecraft:chest": "儲物箱", "minecraft:redstone_wire": "紅石線", "minecraft:diamond_ore": "鑽石礦", "minecraft:diamond_block": "鑽石磚", @@ -177,51 +196,51 @@ "minecraft:stone_stairs": "碎石樓梯", "minecraft:wall_sign": "牆上的橡木指示牌", "minecraft:lever": "拉桿", - "minecraft:stone_pressure_plate": "石頭壓力板", + "minecraft:stone_pressure_plate": "石壓力板", "minecraft:iron_door": "鐵門", - "minecraft:wooden_pressure_plate": "木質壓力板", + "minecraft:wooden_pressure_plate": "木製壓力板", "minecraft:redstone_ore": "紅石礦", "minecraft:lit_redstone_ore": "紅石礦", "minecraft:unlit_redstone_torch": "熄滅的紅石火炬", "minecraft:redstone_torch": "紅石火炬", - "minecraft:stone_button": "石製按鈕", + "minecraft:stone_button": "石按鈕", "minecraft:snow_layer": "雪", "minecraft:ice": "冰", - "minecraft:snow": "雪", + "minecraft:snow": "雪塊", "minecraft:cactus": "仙人掌", "minecraft:clay": "黏土", - "minecraft:reeds": "蔗", + "minecraft:sugar_cane": "蔗", "minecraft:jukebox": "唱片機", "minecraft:oak_fence": "橡木欄杆", "minecraft:pumpkin": "南瓜", - "minecraft:netherrack": "地獄岩", + "minecraft:netherrack": "地獄石", "minecraft:soul_sand": "靈魂砂", "minecraft:glowstone": "熒光石", "minecraft:portal": "地獄傳送門", "minecraft:lit_pumpkin": "南瓜燈", "minecraft:cake": "蛋糕", - "minecraft:unpowered_repeater": "未激活的中繼器", - "minecraft:powered_repeater": "激活的中繼器", - "minecraft:invisible_bedrock": "隱形基岩", - "minecraft:trapdoor": "地板門", - "minecraft:infested_stone": "蟲蝕石頭", + "minecraft:unpowered_repeater": "未啟動的中繼器", + "minecraft:powered_repeater": "啟動的中繼器", + "minecraft:invisible_bedrock": "隱形基巖", + "minecraft:trapdoor": "橡木地板門", + "minecraft:infested_stone": "蛀蝕的石頭", "minecraft:stone_bricks": "石磚", "minecraft:brown_mushroom_block": "啡色蘑菇方塊", "minecraft:red_mushroom_block": "紅色蘑菇方塊", "minecraft:iron_bars": "鐵欄杆", - "minecraft:glass_pane": "玻璃板", + "minecraft:glass_pane": "玻璃片", "minecraft:melon_block": "西瓜", - "minecraft:pumpkin_stem": "南瓜椗", - "minecraft:melon_stem": "西瓜椗", + "minecraft:pumpkin_stem": "南瓜莖", + "minecraft:melon_stem": "西瓜莖", "minecraft:vine": "藤蔓", "minecraft:oak_fence_gate": "橡木閘門", - "minecraft:brick_stairs": "紅磚頭樓梯", + "minecraft:brick_stairs": "紅磚樓梯", "minecraft:stone_brick_stairs": "石磚樓梯", - "minecraft:mycelium": "菌絲體", - "minecraft:waterlily": "睡蓮", + "minecraft:mycelium": "菌絲土", + "minecraft:waterlily": "荷葉", "minecraft:nether_brick": "地獄磚頭", - "minecraft:nether_brick_fence": "地獄磚頭欄杆", - "minecraft:nether_brick_stairs": "地獄磚頭樓梯", + "minecraft:nether_brick_fence": "地獄磚欄杆", + "minecraft:nether_brick_stairs": "地獄磚樓梯", "minecraft:nether_wart": "地獄孢子", "minecraft:enchanting_table": "附魔台", "minecraft:brewing_stand": "釀造台", @@ -235,106 +254,107 @@ "minecraft:dropper": "投擲器", "minecraft:activator_rail": "觸發路軌", "minecraft:cocoa": "可可果", - "minecraft:sandstone_stairs": "砂岩樓梯", + "minecraft:sandstone_stairs": "砂巖樓梯", "minecraft:emerald_ore": "綠寶石礦", "minecraft:ender_chest": "終界箱", - "minecraft:tripwire_hook": "絆線鈎", - "minecraft:trip_wire": "絆線", + "minecraft:tripwire_hook": "撠線鈎", + "minecraft:trip_wire": "撠線", "minecraft:emerald_block": "綠寶石磚", - "minecraft:spruce_stairs": "雲杉木樓梯", + "minecraft:spruce_stairs": "杉木樓梯", "minecraft:birch_stairs": "樺木樓梯", "minecraft:jungle_stairs": "叢林木樓梯", "minecraft:command_block": "脈衝型命令方塊", - "minecraft:beacon": "信標", + "minecraft:beacon": "燈標", "minecraft:cobblestone_wall": "碎石牆", "minecraft:flower_pot": "花盆", "minecraft:carrots": "紅蘿蔔", "minecraft:potatoes": "薯仔", "minecraft:wooden_button": "橡木按鈕", - "minecraft:skeleton_skull": "骷髏骨頭", + "minecraft:skeleton_skull": "骷髏骨頭顱", "minecraft:anvil": "鐵砧", "minecraft:trapped_chest": "陷阱箱", - "minecraft:light_weighted_pressure_plate": "測重壓力板 (輕質)", - "minecraft:heavy_weighted_pressure_plate": "測重壓力板 (重質)", - "minecraft:unpowered_comparator": "未激活的比較器", - "minecraft:powered_comparator": "激活的比較器", - "minecraft:daylight_detector": "陽光探測器", + "minecraft:light_weighted_pressure_plate": "輕質測重壓力板", + "minecraft:heavy_weighted_pressure_plate": "重質測重壓力板", + "minecraft:unpowered_comparator": "未啟動的比較器", + "minecraft:powered_comparator": "啟動的比較器", + "minecraft:daylight_detector": "日光感測器", "minecraft:redstone_block": "紅石磚", "minecraft:quartz_ore": "地獄石英礦", "minecraft:hopper": "漏斗", "minecraft:quartz_block": "石英磚", "minecraft:quartz_stairs": "石英樓梯", - "minecraft:oak_double_slab": "橡木雙層台階", - "minecraft:oak_slab": "橡木台階", + "minecraft:oak_double_slab": "橡木雙半磚", + "minecraft:oak_slab": "橡木半磚", "minecraft:white_terracotta": "白色陶瓦", "minecraft:white_stained_glass_pane": "白色染色玻璃片", - "minecraft:acacia_leaves": "金合歡樹葉", - "minecraft:acacia_log": "金合歡原木", + "minecraft:acacia_leaves": "相思木樹葉", + "minecraft:acacia_log": "相思木原木", "minecraft:acacia_stairs": "相思木樓梯", "minecraft:dark_oak_stairs": "黑橡木樓梯", - "minecraft:slime": "史萊姆", + "minecraft:slime": "史萊姆方塊", + "minecraft:glow_stick": "熒光棒", "minecraft:iron_trapdoor": "鐵地板門", - "minecraft:prismarine": "海晶石", - "minecraft:sea_lantern": "海晶燈", + "minecraft:prismarine": "海磷石", + "minecraft:sea_lantern": "海燈籠", "minecraft:hay_block": "乾草捆", "minecraft:white_carpet": "白色地氈", "minecraft:hardened_clay": "陶瓦", - "minecraft:coal_block": "煤炭塊", - "minecraft:packed_ice": "浮冰", + "minecraft:coal_block": "煤炭磚", + "minecraft:packed_ice": "冰磚", "minecraft:sunflower": "向日葵", "minecraft:standing_banner": "站立的橫額", "minecraft:wall_banner": "牆上的橫額", - "minecraft:daylight_detector_inverted": "反向陽光探測器", - "minecraft:red_sandstone": "紅砂岩", - "minecraft:red_sandstone_stairs": "紅砂岩樓梯", - "minecraft:red_sandstone_double_slab": "紅砂岩雙層台階", - "minecraft:red_sandstone_slab": "紅砂岩台階", - "minecraft:spruce_fence_gate": "雲杉木閘門", + "minecraft:daylight_detector_inverted": "反向日光感測器", + "minecraft:red_sandstone": "紅砂巖", + "minecraft:red_sandstone_stairs": "紅砂巖樓梯", + "minecraft:red_sandstone_double_slab": "紅砂巖雙半磚", + "minecraft:red_sandstone_slab": "紅砂巖半磚", + "minecraft:spruce_fence_gate": "杉木閘門", "minecraft:birch_fence_gate": "樺木閘門", "minecraft:jungle_fence_gate": "叢林木閘門", "minecraft:dark_oak_fence_gate": "黑橡木閘門", "minecraft:acacia_fence_gate": "相思木閘門", "minecraft:repeating_command_block": "重複型命令方塊", "minecraft:chain_command_block": "連環型命令方塊", - "minecraft:hard_glass_pane": "強化玻璃板", - "minecraft:hard_white_stained_glass_pane": "白色強化玻璃板", + "minecraft:hard_glass_pane": "強化玻璃片", + "minecraft:hard_white_stained_glass_pane": "白色強化玻璃片", "minecraft:chemical_heat": "加熱塊", - "minecraft:spruce_door": "雲杉木門", + "minecraft:spruce_door": "杉木門", "minecraft:birch_door": "樺木門", "minecraft:jungle_door": "叢林木門", "minecraft:acacia_door": "相思木門", "minecraft:dark_oak_door": "黑橡木門", - "minecraft:grass_path": "草徑", + "minecraft:grass_path": "泥徑", "minecraft:frame": "物品展示框", - "minecraft:chorus_flower": "紫頌花", + "minecraft:chorus_flower": "歌萊花", "minecraft:purpur_block": "紫珀磚", "minecraft:colored_torch_red": "紅色火炬", "minecraft:purpur_stairs": "紫珀樓梯", "minecraft:colored_torch_blue": "藍色火炬", - "minecraft:undyed_shulker_box": "未染色的潛影盒", + "minecraft:undyed_shulker_box": "未染色的界伏盒", "minecraft:end_bricks": "終界石磚", "minecraft:frosted_ice": "霜冰", "minecraft:end_rod": "終界燭", "minecraft:end_gateway": "終界折躍門", "minecraft:allow": "允許方塊", "minecraft:deny": "拒絕方塊", - "minecraft:border_block": "邊界方塊", - "minecraft:magma": "岩漿塊", + "minecraft:border_block": "邊界", + "minecraft:magma": "巖漿塊", "minecraft:nether_wart_block": "地獄孢子塊", "minecraft:red_nether_brick": "紅色地獄磚", "minecraft:bone_block": "骨塊", "minecraft:structure_void": "結構空位", - "minecraft:white_shulker_box": "白色潛影盒", + "minecraft:white_shulker_box": "白色界伏盒", "minecraft:purple_glazed_terracotta": "紫色釉陶", "minecraft:white_glazed_terracotta": "白色釉陶", "minecraft:orange_glazed_terracotta": "橙色釉陶", "minecraft:magenta_glazed_terracotta": "紫紅色釉陶", - "minecraft:light_blue_glazed_terracotta": "淡藍色釉陶", + "minecraft:light_blue_glazed_terracotta": "淺藍色釉陶", "minecraft:yellow_glazed_terracotta": "黃色釉陶", - "minecraft:lime_glazed_terracotta": "黃綠色釉陶", + "minecraft:lime_glazed_terracotta": "淺綠色釉陶", "minecraft:pink_glazed_terracotta": "粉紅色釉陶", "minecraft:gray_glazed_terracotta": "灰色釉陶", - "minecraft:silver_glazed_terracotta": "淡灰色釉陶", + "minecraft:silver_glazed_terracotta": "淺灰色釉陶", "minecraft:cyan_glazed_terracotta": "青藍色釉陶", "minecraft:chalkboard": "黑板", "minecraft:blue_glazed_terracotta": "藍色釉陶", @@ -344,31 +364,31 @@ "minecraft:black_glazed_terracotta": "黑色釉陶", "minecraft:white_concrete": "白色混凝土", "minecraft:white_concrete_powder": "白色混凝土粉末", - "minecraft:compound_creator": "化合物創建器", + "minecraft:compound_creator": "化合物建立器", "minecraft:underwater_torch": "水下火炬", - "minecraft:chorus_plant": "紫頌植株", + "minecraft:chorus_plant": "歌萊枝", "minecraft:white_stained_glass": "白色染色玻璃", "minecraft:camera": "攝像機", - "minecraft:podzol": "灰化土", + "minecraft:podzol": "灰壤", "minecraft:beetroot": "紅菜頭", - "minecraft:stonecutter": "切石機", + "minecraft:stonecutter": "切石機(舊版)", "minecraft:glowingobsidian": "發光的黑曜石", "minecraft:netherreactor": "地獄反應核", "minecraft:info_update": "數據更新方塊(update!)", "minecraft:info_update2": "數據更新方塊2(ate!upd)", - "minecraft:moving_block": "移動的活塞", + "minecraft:moving_block": "移動的方塊/移動的活塞", "minecraft:observer": "偵測器", "minecraft:structure_block": "結構方塊", "minecraft:hard_glass": "強化玻璃", "minecraft:hard_white_stained_glass": "白色強化玻璃", "minecraft:reserved6": "reserved6", - "minecraft:prismarine_stairs": "海晶石頭樓梯", - "minecraft:dark_prismarine_stairs": "暗海晶石頭樓梯", - "minecraft:prismarine_bricks_stairs": "海晶石磚樓梯", - "minecraft:stripped_spruce_log": "剝皮雲杉原木", - "minecraft:stripped_birch_log": "剝皮白樺原木", - "minecraft:stripped_jungle_log": "剝皮叢林原木", - "minecraft:stripped_acacia_log": "剝皮金合歡原木", + "minecraft:prismarine_stairs": "海磷石樓梯", + "minecraft:dark_prismarine_stairs": "暗海磷石樓梯", + "minecraft:prismarine_bricks_stairs": "海磷石磚樓梯", + "minecraft:stripped_spruce_log": "剝皮杉木原木", + "minecraft:stripped_birch_log": "剝皮樺木原木", + "minecraft:stripped_jungle_log": "剝皮叢林木原木", + "minecraft:stripped_acacia_log": "剝皮相思木原木", "minecraft:stripped_dark_oak_log": "剝皮黑橡木原木", "minecraft:stripped_oak_log": "剝皮橡木原木", "minecraft:blue_ice": "藍冰", @@ -488,64 +508,64 @@ "minecraft:element_114": "鈇", "minecraft:element_115": "鏌", "minecraft:element_116": "鉝", - "minecraft:element_117": "Tennessine", - "minecraft:element_118": "Oganesson", + "minecraft:element_117": "鿬", + "minecraft:element_118": "鿫", "minecraft:seagrass": "海草", "minecraft:tube_coral": "管珊瑚", - "minecraft:tube_coral_block": "管珊瑚塊", - "minecraft:tube_coral_fan": "管珊瑚扇", - "minecraft:dead_tube_coral_fan": "死亡管珊瑚扇", - "minecraft:tube_coral_wall_fan": "牆上的管珊瑚扇", - "minecraft:bubble_coral_wall_fan": "牆上的氣泡珊瑚扇", - "minecraft:horn_coral_wall_fan": "牆上的角珊瑚扇", + "minecraft:tube_coral_block": "管珊瑚方塊", + "minecraft:tube_coral_fan": "扇狀管珊瑚", + "minecraft:dead_tube_coral_fan": "死亡的扇狀管珊瑚", + "minecraft:tube_coral_wall_fan": "牆上的扇狀管珊瑚", + "minecraft:bubble_coral_wall_fan": "牆上的扇狀氣泡珊瑚", + "minecraft:horn_coral_wall_fan": "牆上的扇狀角珊瑚", "minecraft:kelp": "海帶", "minecraft:dried_kelp_block": "乾海帶捆", "minecraft:acacia_button": "相思木按鈕", "minecraft:birch_button": "樺木按鈕", "minecraft:dark_oak_button": "黑橡木按鈕", "minecraft:jungle_button": "叢林木按鈕", - "minecraft:spruce_button": "雲杉木按鈕", + "minecraft:spruce_button": "杉木按鈕", "minecraft:acacia_trapdoor": "相思木地板門", "minecraft:birch_trapdoor": "樺木地板門", "minecraft:dark_oak_trapdoor": "黑橡木地板門", "minecraft:jungle_trapdoor": "叢林木地板門", - "minecraft:spruce_trapdoor": "雲杉木地板門", + "minecraft:spruce_trapdoor": "杉木地板門", "minecraft:acacia_pressure_plate": "相思木壓力板", "minecraft:birch_pressure_plate": "樺木壓力板", "minecraft:dark_oak_pressure_plate": "黑橡木壓力板", "minecraft:jungle_pressure_plate": "叢林木壓力板", - "minecraft:spruce_pressure_plate": "雲杉木壓力板", + "minecraft:spruce_pressure_plate": "杉木壓力板", "minecraft:carved_pumpkin": "雕刻南瓜", - "minecraft:sea_pickle": "海泡菜", - "minecraft:conduit": "潮湧核心", + "minecraft:sea_pickle": "海鞘", + "minecraft:conduit": "海靈核心", "minecraft:air": "空氣", "minecraft:turtle_egg": "海龜蛋", "minecraft:bubble_column": "氣泡柱", - "minecraft:barrier": "障幕", - "minecraft:end_stone_brick_slab": "終界石磚台階", + "minecraft:barrier": "屏障", + "minecraft:end_stone_brick_slab": "終界石磚半磚", "minecraft:bamboo": "竹", "minecraft:bamboo_sapling": "竹筍", - "minecraft:scaffolding": "竹棚", - "minecraft:mossy_stone_brick_slab": "青苔碎石磚台階", - "minecraft:end_stone_brick_double_slab": "終界石磚雙層台階", - "minecraft:mossy_stone_brick_double_slab": "青苔碎石磚雙層台階", - "minecraft:granite_stairs": "花崗岩樓梯", - "minecraft:diorite_stairs": "閃長岩樓梯", - "minecraft:andesite_stairs": "安山岩樓梯", - "minecraft:polished_granite_stairs": "磨製花崗岩樓梯", - "minecraft:polished_diorite_stairs": "磨製閃長岩樓梯", - "minecraft:polished_andesite_stairs": "磨製安山岩樓梯", - "minecraft:mossy_stone_brick_stairs": "青苔碎石磚樓梯", - "minecraft:smooth_red_sandstone_stairs": "平滑紅砂岩樓梯", - "minecraft:smooth_sandstone_stairs": "平滑砂岩樓梯", + "minecraft:scaffolding": "棚架", + "minecraft:mossy_stone_brick_slab": "青苔石磚半磚", + "minecraft:end_stone_brick_double_slab": "終界石磚雙半磚", + "minecraft:mossy_stone_brick_double_slab": "青苔石磚雙半磚", + "minecraft:granite_stairs": "花崗巖樓梯", + "minecraft:diorite_stairs": "閃長巖樓梯", + "minecraft:andesite_stairs": "安山巖樓梯", + "minecraft:polished_granite_stairs": "拋光花崗巖樓梯", + "minecraft:polished_diorite_stairs": "拋光閃長巖樓梯", + "minecraft:polished_andesite_stairs": "拋光安山巖樓梯", + "minecraft:mossy_stone_brick_stairs": "青苔石磚樓梯", + "minecraft:smooth_red_sandstone_stairs": "平滑紅砂巖樓梯", + "minecraft:smooth_sandstone_stairs": "平滑砂巖樓梯", "minecraft:end_brick_stairs": "終界石磚樓梯", "minecraft:mossy_cobblestone_stairs": "青苔碎石樓梯", "minecraft:normal_stone_stairs": "石頭樓梯", - "minecraft:spruce_standing_sign": "雲杉木指示牌", - "minecraft:spruce_wall_sign": "牆上的雲杉木指示牌", + "minecraft:spruce_standing_sign": "杉木指示牌", + "minecraft:spruce_wall_sign": "牆上的杉木指示牌", "minecraft:smooth_stone": "平滑石頭", "minecraft:red_nether_brick_stairs": "紅色地獄磚樓梯", - "minecraft:smooth_quartz_stairs": "平滑石英磚樓梯", + "minecraft:smooth_quartz_stairs": "平滑石英樓梯", "minecraft:birch_standing_sign": "樺木指示牌", "minecraft:birch_wall_sign": "牆上的樺木指示牌", "minecraft:jungle_standing_sign": "叢林木指示牌", @@ -558,24 +578,24 @@ "minecraft:grindstone": "砂輪", "minecraft:blast_furnace": "高爐", "minecraft:stonecutter_block": "切石機", - "minecraft:smoker": "煙熏爐", - "minecraft:lit_smoker": "燃燒中的煙熏爐", + "minecraft:smoker": "煙燻爐", + "minecraft:lit_smoker": "燃燒中的煙燻爐", "minecraft:cartography_table": "製圖台", - "minecraft:fletching_table": "制箭台", + "minecraft:fletching_table": "製箭台", "minecraft:smithing_table": "鍛造台", "minecraft:barrel": "木桶", "minecraft:loom": "織布機", - "minecraft:bell": "鍾", - "minecraft:sweet_berry_bush": "甜漿果叢", + "minecraft:bell": "鐘", + "minecraft:sweet_berry_bush": "甜莓灌木叢", "minecraft:lantern": "燈籠", "minecraft:campfire": "營火", - "minecraft:lava_cauldron": "裝有熔岩的鍋(已移除)", + "minecraft:lava_cauldron": "裝有熔巖的鍋(已移除)", "minecraft:jigsaw": "拼圖方塊", "minecraft:oak_wood": "橡木", "minecraft:composter": "堆肥桶", "minecraft:lit_blast_furnace": "燃燒中的高爐", - "minecraft:light_block_0": "光源方塊 (亮度0)", - "minecraft:wither_rose": "凋靈玫瑰", + "minecraft:light_block_0": "光 (亮度0)", + "minecraft:wither_rose": "凋零玫瑰", "minecraft:sticky_piston_arm_collision": "黏性活塞臂", "minecraft:bee_nest": "蜂巢", "minecraft:beehive": "蜂箱", @@ -584,23 +604,23 @@ "minecraft:lodestone": "磁石", "minecraft:crimson_roots": "猩紅菌根", "minecraft:warped_roots": "迷離菌根", - "minecraft:crimson_stem": "猩紅菌椗", - "minecraft:warped_stem": "迷離菌椗", + "minecraft:crimson_stem": "猩紅菌柄", + "minecraft:warped_stem": "迷離菌柄", "minecraft:warped_wart_block": "迷離孢子塊", "minecraft:crimson_fungus": "猩紅菌菇", "minecraft:warped_fungus": "迷離菌菇", "minecraft:shroomlight": "菌光體", - "minecraft:weeping_vines": "哭泣藤", - "minecraft:crimson_nylium": "猩紅菌菇岩", - "minecraft:warped_nylium": "迷離菌菇岩", - "minecraft:basalt": "玄武岩", - "minecraft:polished_basalt": "磨製玄武岩", + "minecraft:weeping_vines_plant": "哭泣藤植株", + "minecraft:crimson_nylium": "猩紅菌絲石", + "minecraft:warped_nylium": "迷離菌絲石", + "minecraft:basalt": "玄武巖", + "minecraft:polished_basalt": "拋光玄武巖", "minecraft:soul_soil": "靈魂土", "minecraft:soul_fire": "靈魂火", - "minecraft:nether_sprouts": "地獄苗", + "minecraft:nether_sprouts": "地獄芽", "minecraft:target": "標靶", - "minecraft:stripped_crimson_stem": "剝皮猩紅菌椗", - "minecraft:stripped_warped_stem": "剝皮迷離菌椗", + "minecraft:stripped_crimson_stem": "剝皮猩紅菌柄", + "minecraft:stripped_warped_stem": "剝皮迷離菌柄", "minecraft:crimson_planks": "猩紅菌木板", "minecraft:warped_planks": "迷離菌木板", "minecraft:crimson_door": "猩紅菌木門", @@ -621,51 +641,51 @@ "minecraft:warped_button": "迷離菌木按鈕", "minecraft:crimson_pressure_plate": "猩紅菌木壓力板", "minecraft:warped_pressure_plate": "迷離菌木壓力板", - "minecraft:crimson_slab": "猩紅菌木台階", - "minecraft:warped_slab": "迷離菌木台階", - "minecraft:crimson_double_slab": "猩紅菌木雙層台階", - "minecraft:warped_double_slab": "迷離菌木雙層台階", + "minecraft:crimson_slab": "猩紅菌木半磚", + "minecraft:warped_slab": "迷離菌木半磚", + "minecraft:crimson_double_slab": "猩紅菌木雙半磚", + "minecraft:warped_double_slab": "迷離菌木雙半磚", "minecraft:soul_torch": "靈魂火炬", "minecraft:soul_lantern": "靈魂燈籠", "minecraft:netherite_block": "地獄合金磚", "minecraft:ancient_debris": "遠古殘骸", "minecraft:respawn_anchor": "重生錨", "minecraft:blackstone": "黑石", - "minecraft:polished_blackstone_bricks": "磨製黑石磚", - "minecraft:polished_blackstone_brick_stairs": "磨製黑石磚樓梯", - "minecraft:blackstone_stairs": "黑石頭樓梯", + "minecraft:polished_blackstone_bricks": "拋光黑石磚", + "minecraft:polished_blackstone_brick_stairs": "拋光黑石磚樓梯", + "minecraft:blackstone_stairs": "黑石樓梯", "minecraft:blackstone_wall": "黑石牆", - "minecraft:polished_blackstone_brick_wall": "磨製黑石磚牆", - "minecraft:chiseled_polished_blackstone": "雕紋磨製黑石", - "minecraft:cracked_polished_blackstone_bricks": "裂紋磨製黑石磚", + "minecraft:polished_blackstone_brick_wall": "拋光黑石磚牆", + "minecraft:chiseled_polished_blackstone": "浮雕拋光黑石", + "minecraft:cracked_polished_blackstone_bricks": "裂紋拋光黑石磚", "minecraft:gilded_blackstone": "鑲金黑石", - "minecraft:blackstone_slab": "黑石頭半磚", - "minecraft:blackstone_double_slab": "黑石雙層台階", - "minecraft:polished_blackstone_brick_slab": "磨製黑石磚台階", - "minecraft:polished_blackstone_brick_double_slab": "磨製黑石磚雙層台階", - "minecraft:chain": "鐵鏈", + "minecraft:blackstone_slab": "黑石半磚", + "minecraft:blackstone_double_slab": "黑石雙半磚", + "minecraft:polished_blackstone_brick_slab": "拋光黑石磚半磚", + "minecraft:polished_blackstone_brick_double_slab": "拋光黑石磚雙半磚", + "minecraft:iron_chain": "鐵鏈", "minecraft:twisting_vines": "扭曲藤", "minecraft:nether_gold_ore": "地獄金礦", "minecraft:crying_obsidian": "哭曜石", "minecraft:soul_campfire": "靈魂營火", - "minecraft:polished_blackstone": "磨製黑石", - "minecraft:polished_blackstone_stairs": "磨製黑石頭樓梯", - "minecraft:polished_blackstone_slab": "磨製黑石頭半磚", - "minecraft:polished_blackstone_double_slab": "磨製黑石雙層台階", - "minecraft:polished_blackstone_pressure_plate": "磨製黑石壓力板", - "minecraft:polished_blackstone_button": "磨製黑石按鈕", - "minecraft:polished_blackstone_wall": "磨製黑石牆", + "minecraft:polished_blackstone": "拋光黑石", + "minecraft:polished_blackstone_stairs": "拋光黑石樓梯", + "minecraft:polished_blackstone_slab": "拋光黑石半磚", + "minecraft:polished_blackstone_double_slab": "拋光黑石雙半磚", + "minecraft:polished_blackstone_pressure_plate": "拋光黑石壓力板", + "minecraft:polished_blackstone_button": "拋光黑石按鈕", + "minecraft:polished_blackstone_wall": "拋光黑石牆", "minecraft:warped_hyphae": "迷離菌絲體", "minecraft:crimson_hyphae": "猩紅菌絲體", "minecraft:stripped_crimson_hyphae": "剝皮猩紅菌絲體", "minecraft:stripped_warped_hyphae": "剝皮迷離菌絲體", - "minecraft:chiseled_nether_bricks": "雕紋地獄磚", + "minecraft:chiseled_nether_bricks": "浮雕地獄磚", "minecraft:cracked_nether_bricks": "裂紋地獄磚", "minecraft:quartz_bricks": "磚紋石英磚", - "minecraft:unknown": "未知 [注 3]", + "minecraft:unknown": "未知", "minecraft:powder_snow": "幼雪", "minecraft:sculk_sensor": "沉靈傳感器", - "minecraft:pointed_dripstone": "滴水石錐", + "minecraft:pointed_dripstone": "滴水石柱", "minecraft:copper_ore": "銅礦", "minecraft:lightning_rod": "避雷針", "minecraft:crafter": "合成器", @@ -673,7 +693,7 @@ "minecraft:trial_spawner": "試煉生怪籠", "minecraft:heavy_core": "沉重核心", "minecraft:dripstone_block": "滴水石", - "minecraft:dirt_with_roots": "纏根泥土", + "minecraft:dirt_with_roots": "扎根泥", "minecraft:hanging_roots": "懸根", "minecraft:moss_block": "青苔方塊", "minecraft:spore_blossom": "孢子花", @@ -683,18 +703,18 @@ "minecraft:azalea_leaves_flowered": "盛開的杜鵑樹葉", "minecraft:calcite": "方解石", "minecraft:amethyst_block": "紫水晶磚", - "minecraft:budding_amethyst": "紫水晶母岩", + "minecraft:budding_amethyst": "紫水晶母巖", "minecraft:amethyst_cluster": "紫水晶簇", - "minecraft:large_amethyst_bud": "大型紫晶芽", - "minecraft:medium_amethyst_bud": "中型紫晶芽", - "minecraft:small_amethyst_bud": "小型紫晶芽", - "minecraft:tuff": "凝灰岩", + "minecraft:large_amethyst_bud": "大型紫水晶芽", + "minecraft:medium_amethyst_bud": "中型紫水晶芽", + "minecraft:small_amethyst_bud": "小型紫水晶芽", + "minecraft:tuff": "凝灰巖", "minecraft:tinted_glass": "遮光玻璃", - "minecraft:moss_carpet": "覆地苔蘚", + "minecraft:moss_carpet": "青苔", "minecraft:small_dripleaf": "細懸葉草", - "minecraft:azalea": "杜鵑花叢", - "minecraft:flowering_azalea": "盛開的杜鵑花叢", - "minecraft:glow_frame": "螢光物品展示框", + "minecraft:azalea": "杜鵑叢", + "minecraft:flowering_azalea": "盛開的杜鵑叢", + "minecraft:glow_frame": "熒光物品展示框", "minecraft:copper_block": "銅磚", "minecraft:exposed_copper": "斑駁的銅磚", "minecraft:weathered_copper": "生鏽的銅磚", @@ -702,60 +722,60 @@ "minecraft:waxed_copper": "打蠟的銅磚", "minecraft:waxed_exposed_copper": "打蠟的斑駁銅磚", "minecraft:waxed_weathered_copper": "打蠟的生鏽銅磚", - "minecraft:cut_copper": "切制銅磚", + "minecraft:cut_copper": "切製銅磚", "minecraft:exposed_cut_copper": "斑駁的切製銅磚", "minecraft:weathered_cut_copper": "生鏽的切製銅磚", "minecraft:oxidized_cut_copper": "氧化的切製銅磚", - "minecraft:waxed_cut_copper": "打蠟的切制銅磚", - "minecraft:waxed_exposed_cut_copper": "打蠟的斑駁切制銅磚", - "minecraft:waxed_weathered_cut_copper": "打蠟的生鏽切制銅磚", - "minecraft:cut_copper_stairs": "切制銅樓梯", + "minecraft:waxed_cut_copper": "打蠟的切製銅磚", + "minecraft:waxed_exposed_cut_copper": "打蠟的斑駁切製銅磚", + "minecraft:waxed_weathered_cut_copper": "打蠟的生鏽切製銅磚", + "minecraft:cut_copper_stairs": "切製銅樓梯", "minecraft:exposed_cut_copper_stairs": "斑駁的切製銅樓梯", "minecraft:weathered_cut_copper_stairs": "生鏽的切製銅樓梯", "minecraft:oxidized_cut_copper_stairs": "氧化的切製銅樓梯", - "minecraft:waxed_cut_copper_stairs": "打蠟的切制銅樓梯", - "minecraft:waxed_exposed_cut_copper_stairs": "打蠟的斑駁切制銅樓梯", - "minecraft:waxed_weathered_cut_copper_stairs": "打蠟的生鏽切制銅樓梯", - "minecraft:cut_copper_slab": "切制銅台階", - "minecraft:exposed_cut_copper_slab": "斑駁的切製銅台階", - "minecraft:weathered_cut_copper_slab": "生鏽的切製銅台階", - "minecraft:oxidized_cut_copper_slab": "氧化的切製銅台階", - "minecraft:waxed_cut_copper_slab": "打蠟的切制銅台階", - "minecraft:waxed_exposed_cut_copper_slab": "打蠟的斑駁切制銅台階", - "minecraft:waxed_weathered_cut_copper_slab": "打蠟的生鏽切制銅台階", - "minecraft:double_cut_copper_slab": "切制銅雙層台階", - "minecraft:exposed_double_cut_copper_slab": "斑駁的切製銅雙層台階", - "minecraft:weathered_double_cut_copper_slab": "生鏽的切製銅雙層台階", - "minecraft:oxidized_double_cut_copper_slab": "氧化的切製銅雙層台階", - "minecraft:waxed_double_cut_copper_slab": "打蠟的切制銅雙層台階", - "minecraft:waxed_exposed_double_cut_copper_slab": "打蠟的斑駁切制銅雙層台階", - "minecraft:waxed_weathered_double_cut_copper_slab": "打蠟的生鏽切制銅雙層台階", + "minecraft:waxed_cut_copper_stairs": "打蠟的切製銅樓梯", + "minecraft:waxed_exposed_cut_copper_stairs": "打蠟的斑駁切製銅樓梯", + "minecraft:waxed_weathered_cut_copper_stairs": "打蠟的生鏽切製銅樓梯", + "minecraft:cut_copper_slab": "切製銅半磚", + "minecraft:exposed_cut_copper_slab": "斑駁的切製銅半磚", + "minecraft:weathered_cut_copper_slab": "生鏽的切製銅半磚", + "minecraft:oxidized_cut_copper_slab": "氧化的切製銅半磚", + "minecraft:waxed_cut_copper_slab": "打蠟的切製銅半磚", + "minecraft:waxed_exposed_cut_copper_slab": "打蠟的斑駁切製銅半磚", + "minecraft:waxed_weathered_cut_copper_slab": "打蠟的生鏽切製銅半磚", + "minecraft:double_cut_copper_slab": "切製銅雙半磚", + "minecraft:exposed_double_cut_copper_slab": "斑駁的切製銅雙半磚", + "minecraft:weathered_double_cut_copper_slab": "生鏽的切製銅雙半磚", + "minecraft:oxidized_double_cut_copper_slab": "氧化的切製銅雙半磚", + "minecraft:waxed_double_cut_copper_slab": "打蠟的切製銅雙半磚", + "minecraft:waxed_exposed_double_cut_copper_slab": "打蠟的斑駁切製銅雙半磚", + "minecraft:waxed_weathered_double_cut_copper_slab": "打蠟的生鏽切製銅雙半磚", "minecraft:cave_vines_body_berries": "洞穴藤蔓(帶漿果)", "minecraft:cave_vines_head_berries": "洞穴藤蔓首部(帶漿果)", - "minecraft:smooth_basalt": "平滑玄武岩", - "minecraft:deepslate": "深板岩", - "minecraft:cobbled_deepslate": "深板岩碎石", - "minecraft:cobbled_deepslate_slab": "深板岩碎石台階", - "minecraft:cobbled_deepslate_stairs": "深板岩碎石樓梯", - "minecraft:cobbled_deepslate_wall": "深板岩碎石牆", - "minecraft:polished_deepslate": "磨製深板岩", - "minecraft:polished_deepslate_slab": "磨製深板岩台階", - "minecraft:polished_deepslate_stairs": "磨製深板岩樓梯", - "minecraft:polished_deepslate_wall": "磨製深板岩牆", - "minecraft:deepslate_tiles": "深板岩瓦", - "minecraft:deepslate_tile_slab": "深板岩瓦台階", - "minecraft:deepslate_tile_stairs": "深板岩瓦樓梯", - "minecraft:deepslate_tile_wall": "深板岩瓦牆", - "minecraft:deepslate_bricks": "深板岩磚", - "minecraft:deepslate_brick_slab": "深板岩磚台階", - "minecraft:deepslate_brick_stairs": "深板岩磚樓梯", - "minecraft:deepslate_brick_wall": "深板岩磚牆", - "minecraft:chiseled_deepslate": "雕紋深板岩", - "minecraft:cobbled_deepslate_double_slab": "深板岩碎石雙層台階", - "minecraft:polished_deepslate_double_slab": "磨製深板岩雙層台階", - "minecraft:deepslate_tile_double_slab": "深板岩瓦雙層台階", - "minecraft:deepslate_brick_double_slab": "深板岩磚雙層台階", - "minecraft:deepslate_lapis_lazuli_ore": "深層青金石礦", + "minecraft:smooth_basalt": "平滑玄武巖", + "minecraft:deepslate": "深板巖", + "minecraft:cobbled_deepslate": "深板巖碎石", + "minecraft:cobbled_deepslate_slab": "深板巖碎石半磚", + "minecraft:cobbled_deepslate_stairs": "深板巖碎石樓梯", + "minecraft:cobbled_deepslate_wall": "深板巖碎石牆", + "minecraft:polished_deepslate": "拋光深板巖", + "minecraft:polished_deepslate_slab": "拋光深板巖半磚", + "minecraft:polished_deepslate_stairs": "拋光深板巖樓梯", + "minecraft:polished_deepslate_wall": "拋光深板巖牆", + "minecraft:deepslate_tiles": "深板巖瓦", + "minecraft:deepslate_tile_slab": "深板巖瓦半磚", + "minecraft:deepslate_tile_stairs": "深板巖瓦樓梯", + "minecraft:deepslate_tile_wall": "深板巖瓦牆", + "minecraft:deepslate_bricks": "深板巖磚", + "minecraft:deepslate_brick_slab": "深板巖磚半磚", + "minecraft:deepslate_brick_stairs": "深板巖磚樓梯", + "minecraft:deepslate_brick_wall": "深板巖磚牆", + "minecraft:chiseled_deepslate": "浮雕深板巖", + "minecraft:cobbled_deepslate_double_slab": "深板巖碎石雙半磚", + "minecraft:polished_deepslate_double_slab": "拋光深板巖雙半磚", + "minecraft:deepslate_tile_double_slab": "深板巖瓦雙半磚", + "minecraft:deepslate_brick_double_slab": "深板巖磚雙半磚", + "minecraft:deepslate_lapis_ore": "深層青金石礦", "minecraft:deepslate_iron_ore": "深層鐵礦", "minecraft:deepslate_gold_ore": "深層金礦", "minecraft:deepslate_redstone_ore": "深層紅石礦", @@ -764,19 +784,19 @@ "minecraft:deepslate_coal_ore": "深層煤礦", "minecraft:deepslate_emerald_ore": "深層綠寶石礦", "minecraft:deepslate_copper_ore": "深層銅礦", - "minecraft:cracked_deepslate_tiles": "裂紋深板岩瓦", - "minecraft:cracked_deepslate_bricks": "裂紋深板岩磚", + "minecraft:cracked_deepslate_tiles": "裂紋深板巖瓦", + "minecraft:cracked_deepslate_bricks": "裂紋深板巖磚", "minecraft:glow_lichen": "發光地衣", "minecraft:candle": "蠟燭", "minecraft:white_candle": "白色蠟燭", "minecraft:orange_candle": "橙色蠟燭", "minecraft:magenta_candle": "紫紅色蠟燭", - "minecraft:light_blue_candle": "淡藍色蠟燭", + "minecraft:light_blue_candle": "淺藍色蠟燭", "minecraft:yellow_candle": "黃色蠟燭", - "minecraft:lime_candle": "黃綠色蠟燭", + "minecraft:lime_candle": "淺綠色蠟燭", "minecraft:pink_candle": "粉紅色蠟燭", "minecraft:gray_candle": "灰色蠟燭", - "minecraft:light_gray_candle": "淡灰色蠟燭", + "minecraft:light_gray_candle": "淺灰色蠟燭", "minecraft:cyan_candle": "青藍色蠟燭", "minecraft:purple_candle": "紫色蠟燭", "minecraft:blue_candle": "藍色蠟燭", @@ -788,12 +808,12 @@ "minecraft:white_candle_cake": "插上白色蠟燭的蛋糕", "minecraft:orange_candle_cake": "插上橙色蠟燭的蛋糕", "minecraft:magenta_candle_cake": "插上紫紅色蠟燭的蛋糕", - "minecraft:light_blue_candle_cake": "插上淡藍色蠟燭的蛋糕", + "minecraft:light_blue_candle_cake": "插上淺藍色蠟燭的蛋糕", "minecraft:yellow_candle_cake": "插上黃色蠟燭的蛋糕", - "minecraft:lime_candle_cake": "插上黃綠色蠟燭的蛋糕", + "minecraft:lime_candle_cake": "插上淺綠色蠟燭的蛋糕", "minecraft:pink_candle_cake": "插上粉紅色蠟燭的蛋糕", "minecraft:gray_candle_cake": "插上灰色蠟燭的蛋糕", - "minecraft:light_gray_candle_cake": "插上淡灰色蠟燭的蛋糕", + "minecraft:light_gray_candle_cake": "插上淺灰色蠟燭的蛋糕", "minecraft:cyan_candle_cake": "插上青藍色蠟燭的蛋糕", "minecraft:purple_candle_cake": "插上紫色蠟燭的蛋糕", "minecraft:blue_candle_cake": "插上藍色蠟燭的蛋糕", @@ -802,32 +822,33 @@ "minecraft:red_candle_cake": "插上紅色蠟燭的蛋糕", "minecraft:black_candle_cake": "插上黑色蠟燭的蛋糕", "minecraft:waxed_oxidized_copper": "打蠟的氧化銅磚", - "minecraft:waxed_oxidized_cut_copper": "打蠟的氧化切制銅磚", - "minecraft:waxed_oxidized_cut_copper_stairs": "打蠟的氧化切制銅樓梯", - "minecraft:waxed_oxidized_cut_copper_slab": "打蠟的氧化切制銅台階", - "minecraft:waxed_oxidized_double_cut_copper_slab": "打蠟的氧化切制銅雙層台階", + "minecraft:waxed_oxidized_cut_copper": "打蠟的氧化切製銅磚", + "minecraft:waxed_oxidized_cut_copper_stairs": "打蠟的氧化切製銅樓梯", + "minecraft:waxed_oxidized_cut_copper_slab": "打蠟的氧化切製銅半磚", + "minecraft:waxed_oxidized_double_cut_copper_slab": "打蠟的氧化切製銅雙半磚", "minecraft:raw_iron_block": "粗鐵磚", "minecraft:raw_copper_block": "粗銅磚", "minecraft:raw_gold_block": "粗金磚", - "minecraft:infested_deepslate": "蟲蝕深板岩", + "minecraft:infested_deepslate": "蛀蝕的深板巖", "minecraft:sculk": "沉靈方塊", "minecraft:sculk_vein": "沉靈脈絡", "minecraft:sculk_catalyst": "沉靈催發器", "minecraft:sculk_shrieker": "沉靈尖嘯器", - "minecraft:client_request_placeholder_block": "客户端請求佔位符方塊", - "minecraft:reinforced_deepslate": "強化深板岩", - "minecraft:mysterious_frame_slot": "帶插槽的神祕框架(已移除)", + "minecraft:client_request_placeholder_block": "客户端請求占位方塊", + "minecraft:reinforced_deepslate": "強化深板巖", + "minecraft:mysterious_frame_slot": "帶插欄的神祕框架(已移除)", "minecraft:frog_spawn": "青蛙卵", "minecraft:pearlescent_froglight": "珠光蛙明燈", "minecraft:verdant_froglight": "青翠蛙明燈", "minecraft:ochre_froglight": "赭黃蛙明燈", "minecraft:mangrove_leaves": "紅樹樹葉", - "minecraft:mud": "泥巴", + "minecraft:mud": "泥", "minecraft:mangrove_propagule": "紅樹胎生苗", "minecraft:mud_bricks": "泥磚", + "minecraft:mangrove_propagule_hanging": "懸掛的紅樹胎生苗 (已移除)", "minecraft:packed_mud": "泥坯", - "minecraft:mud_brick_slab": "泥磚台階", - "minecraft:mud_brick_double_slab": "泥磚雙層台階", + "minecraft:mud_brick_slab": "泥磚半磚", + "minecraft:mud_brick_double_slab": "泥磚雙半磚", "minecraft:mud_brick_stairs": "泥磚樓梯", "minecraft:mud_brick_wall": "泥磚牆", "minecraft:mangrove_roots": "紅樹根", @@ -837,7 +858,7 @@ "minecraft:mangrove_planks": "紅樹木板", "minecraft:mangrove_button": "紅樹木按鈕", "minecraft:mangrove_stairs": "紅樹木樓梯", - "minecraft:mangrove_slab": "紅樹木台階", + "minecraft:mangrove_slab": "紅樹木半磚", "minecraft:mangrove_pressure_plate": "紅樹木壓力板", "minecraft:mangrove_fence": "紅樹木欄杆", "minecraft:mangrove_fence_gate": "紅樹木閘門", @@ -847,7 +868,7 @@ "minecraft:mangrove_trapdoor": "紅樹木地板門", "minecraft:mangrove_wood": "紅樹木", "minecraft:stripped_mangrove_wood": "剝皮紅樹木", - "minecraft:mangrove_double_slab": "紅樹木雙層台階", + "minecraft:mangrove_double_slab": "紅樹木雙半磚", "minecraft:oak_hanging_sign": "橡木吊牌", "minecraft:spruce_hanging_sign": "杉木吊牌", "minecraft:birch_hanging_sign": "樺木吊牌", @@ -856,12 +877,12 @@ "minecraft:dark_oak_hanging_sign": "黑橡木吊牌", "minecraft:crimson_hanging_sign": "猩紅菌木吊牌", "minecraft:warped_hanging_sign": "迷離菌木吊牌", - "minecraft:mangrove_hanging_sign": "紅木吊牌", - "minecraft:bamboo_mosaic": "竹馬賽克", + "minecraft:mangrove_hanging_sign": "紅樹木吊牌", + "minecraft:bamboo_mosaic": "竹拼塊", "minecraft:bamboo_planks": "竹板", "minecraft:bamboo_button": "竹按鈕", "minecraft:bamboo_stairs": "竹樓梯", - "minecraft:bamboo_slab": "竹台階", + "minecraft:bamboo_slab": "竹半磚", "minecraft:bamboo_pressure_plate": "竹壓力板", "minecraft:bamboo_fence": "竹欄杆", "minecraft:bamboo_fence_gate": "竹閘門", @@ -869,12 +890,12 @@ "minecraft:bamboo_standing_sign": "竹指示牌", "minecraft:bamboo_wall_sign": "牆上的竹指示牌", "minecraft:bamboo_trapdoor": "竹地板門", - "minecraft:bamboo_double_slab": "竹雙層台階", + "minecraft:bamboo_double_slab": "竹雙半磚", "minecraft:bamboo_hanging_sign": "竹吊牌", "minecraft:bamboo_mosaic_stairs": "竹拼樓梯", - "minecraft:bamboo_mosaic_slab": "竹馬賽克台階", - "minecraft:bamboo_mosaic_double_slab": "竹馬賽克雙層台階", - "minecraft:chiseled_bookshelf": "雕紋書架", + "minecraft:bamboo_mosaic_slab": "竹拼半磚", + "minecraft:bamboo_mosaic_double_slab": "竹拼塊雙半磚", + "minecraft:chiseled_bookshelf": "浮雕書櫃", "minecraft:bamboo_block": "竹塊", "minecraft:stripped_bamboo_block": "剝皮竹塊", "minecraft:suspicious_sand": "可疑的沙", @@ -887,8 +908,8 @@ "minecraft:cherry_log": "櫻花原木", "minecraft:cherry_planks": "櫻花木板", "minecraft:cherry_pressure_plate": "櫻花木壓力板", - "minecraft:cherry_slab": "櫻花木台階", - "minecraft:cherry_double_slab": "櫻花木雙層台階", + "minecraft:cherry_slab": "櫻花木半磚", + "minecraft:cherry_double_slab": "櫻花木雙半磚", "minecraft:cherry_stairs": "櫻花木樓梯", "minecraft:cherry_standing_sign": "櫻花木指示牌", "minecraft:cherry_trapdoor": "櫻花木地板門", @@ -899,26 +920,26 @@ "minecraft:cherry_leaves": "櫻花樹葉", "minecraft:pink_petals": "粉紅色花簇", "minecraft:decorated_pot": "裝飾陶罐", - "minecraft:light_gray_wool": "淡灰色羊毛", + "minecraft:light_gray_wool": "淺灰色羊毛", "minecraft:gray_wool": "灰色羊毛", "minecraft:black_wool": "黑色羊毛", "minecraft:brown_wool": "啡色羊毛", "minecraft:red_wool": "紅色羊毛", "minecraft:orange_wool": "橙色羊毛", "minecraft:yellow_wool": "黃色羊毛", - "minecraft:lime_wool": "黃綠色羊毛", + "minecraft:lime_wool": "淺綠色羊毛", "minecraft:green_wool": "綠色羊毛", "minecraft:cyan_wool": "青藍色羊毛", - "minecraft:light_blue_wool": "淡藍色羊毛", + "minecraft:light_blue_wool": "淺藍色羊毛", "minecraft:blue_wool": "藍色羊毛", "minecraft:purple_wool": "紫色羊毛", "minecraft:magenta_wool": "紫紅色羊毛", "minecraft:pink_wool": "粉紅色羊毛", "minecraft:torchflower_crop": "火炬花植株", "minecraft:torchflower": "火炬花", - "minecraft:spruce_log": "雲杉原木", - "minecraft:birch_log": "白樺原木", - "minecraft:jungle_log": "叢林原木", + "minecraft:spruce_log": "杉木原木", + "minecraft:birch_log": "樺木原木", + "minecraft:jungle_log": "叢林木原木", "minecraft:dark_oak_log": "黑橡木原木", "minecraft:suspicious_gravel": "可疑的砂礫", "minecraft:pitcher_crop": "樽草植株", @@ -926,32 +947,32 @@ "minecraft:birch_fence": "樺木欄杆", "minecraft:dark_oak_fence": "黑橡木欄杆", "minecraft:jungle_fence": "叢林木欄杆", - "minecraft:spruce_fence": "雲杉木欄杆", + "minecraft:spruce_fence": "杉木欄杆", "minecraft:calibrated_sculk_sensor": "校準沉靈傳感器", - "minecraft:brain_coral": "腦紋珊瑚", + "minecraft:brain_coral": "腦珊瑚", "minecraft:bubble_coral": "氣泡珊瑚", "minecraft:fire_coral": "火珊瑚", "minecraft:horn_coral": "角珊瑚", - "minecraft:dead_tube_coral": "死亡管珊瑚", - "minecraft:dead_brain_coral": "死亡腦紋珊瑚", - "minecraft:dead_bubble_coral": "死亡氣泡珊瑚", - "minecraft:dead_fire_coral": "死亡火珊瑚", - "minecraft:dead_horn_coral": "死亡角珊瑚", - "minecraft:granite": "花崗岩", - "minecraft:polished_granite": "磨製花崗岩", - "minecraft:diorite": "閃長岩", - "minecraft:polished_diorite": "磨製閃長岩", - "minecraft:andesite": "安山岩", - "minecraft:polished_andesite": "磨製安山岩", + "minecraft:dead_tube_coral": "死亡的管珊瑚", + "minecraft:dead_brain_coral": "死亡的腦珊瑚", + "minecraft:dead_bubble_coral": "死亡的氣泡珊瑚", + "minecraft:dead_fire_coral": "死亡的火珊瑚", + "minecraft:dead_horn_coral": "死亡的角珊瑚", + "minecraft:granite": "花崗巖", + "minecraft:polished_granite": "拋光花崗巖", + "minecraft:diorite": "閃長巖", + "minecraft:polished_diorite": "拋光閃長巖", + "minecraft:andesite": "安山巖", + "minecraft:polished_andesite": "拋光安山巖", "minecraft:sniffer_egg": "嗅探獸蛋", "minecraft:orange_carpet": "橙色地氈", "minecraft:magenta_carpet": "紫紅色地氈", - "minecraft:light_blue_carpet": "淡藍色地氈", + "minecraft:light_blue_carpet": "淺藍色地氈", "minecraft:yellow_carpet": "黃色地氈", - "minecraft:lime_carpet": "黃綠色地氈", + "minecraft:lime_carpet": "淺綠色地氈", "minecraft:pink_carpet": "粉紅色地氈", "minecraft:gray_carpet": "灰色地氈", - "minecraft:light_gray_carpet": "淡灰色地氈", + "minecraft:light_gray_carpet": "淺灰色地氈", "minecraft:cyan_carpet": "青藍色地氈", "minecraft:purple_carpet": "紫色地氈", "minecraft:blue_carpet": "藍色地氈", @@ -960,29 +981,29 @@ "minecraft:red_carpet": "紅色地氈", "minecraft:black_carpet": "黑色地氈", "minecraft:pitcher_plant": "樽草", - "minecraft:orange_shulker_box": "橙色潛影盒", - "minecraft:magenta_shulker_box": "紫紅色潛影盒", - "minecraft:light_blue_shulker_box": "淡藍色潛影盒", - "minecraft:yellow_shulker_box": "黃色潛影盒", - "minecraft:lime_shulker_box": "黃綠色潛影盒", - "minecraft:pink_shulker_box": "粉紅色潛影盒", - "minecraft:gray_shulker_box": "灰色潛影盒", - "minecraft:light_gray_shulker_box": "淡灰色潛影盒", - "minecraft:cyan_shulker_box": "青藍色潛影盒", - "minecraft:purple_shulker_box": "紫色潛影盒", - "minecraft:blue_shulker_box": "藍色潛影盒", - "minecraft:brown_shulker_box": "啡色潛影盒", - "minecraft:green_shulker_box": "綠色潛影盒", - "minecraft:red_shulker_box": "紅色潛影盒", - "minecraft:black_shulker_box": "黑色潛影盒", + "minecraft:orange_shulker_box": "橙色界伏盒", + "minecraft:magenta_shulker_box": "紫紅色界伏盒", + "minecraft:light_blue_shulker_box": "淺藍色界伏盒", + "minecraft:yellow_shulker_box": "黃色界伏盒", + "minecraft:lime_shulker_box": "淺綠色界伏盒", + "minecraft:pink_shulker_box": "粉紅色界伏盒", + "minecraft:gray_shulker_box": "灰色界伏盒", + "minecraft:light_gray_shulker_box": "淺灰色界伏盒", + "minecraft:cyan_shulker_box": "青藍色界伏盒", + "minecraft:purple_shulker_box": "紫色界伏盒", + "minecraft:blue_shulker_box": "藍色界伏盒", + "minecraft:brown_shulker_box": "啡色界伏盒", + "minecraft:green_shulker_box": "綠色界伏盒", + "minecraft:red_shulker_box": "紅色界伏盒", + "minecraft:black_shulker_box": "黑色界伏盒", "minecraft:orange_concrete": "橙色混凝土", "minecraft:magenta_concrete": "紫紅色混凝土", - "minecraft:light_blue_concrete": "淡藍色混凝土", + "minecraft:light_blue_concrete": "淺藍色混凝土", "minecraft:yellow_concrete": "黃色混凝土", - "minecraft:lime_concrete": "黃綠色混凝土", + "minecraft:lime_concrete": "淺綠色混凝土", "minecraft:pink_concrete": "粉紅色混凝土", "minecraft:gray_concrete": "灰色混凝土", - "minecraft:light_gray_concrete": "淡灰色混凝土", + "minecraft:light_gray_concrete": "淺灰色混凝土", "minecraft:cyan_concrete": "青藍色混凝土", "minecraft:purple_concrete": "紫色混凝土", "minecraft:blue_concrete": "藍色混凝土", @@ -992,12 +1013,12 @@ "minecraft:black_concrete": "黑色混凝土", "minecraft:orange_stained_glass_pane": "橙色染色玻璃片", "minecraft:magenta_stained_glass_pane": "紫紅色染色玻璃片", - "minecraft:light_blue_stained_glass_pane": "淡藍色染色玻璃片", + "minecraft:light_blue_stained_glass_pane": "淺藍色染色玻璃片", "minecraft:yellow_stained_glass_pane": "黃色染色玻璃片", - "minecraft:lime_stained_glass_pane": "黃綠色染色玻璃片", + "minecraft:lime_stained_glass_pane": "淺綠色染色玻璃片", "minecraft:pink_stained_glass_pane": "粉紅色染色玻璃片", "minecraft:gray_stained_glass_pane": "灰色染色玻璃片", - "minecraft:light_gray_stained_glass_pane": "淡灰色染色玻璃片", + "minecraft:light_gray_stained_glass_pane": "淺灰色染色玻璃片", "minecraft:cyan_stained_glass_pane": "青藍色染色玻璃片", "minecraft:purple_stained_glass_pane": "紫色染色玻璃片", "minecraft:blue_stained_glass_pane": "藍色染色玻璃片", @@ -1005,29 +1026,29 @@ "minecraft:green_stained_glass_pane": "綠色染色玻璃片", "minecraft:red_stained_glass_pane": "紅色染色玻璃片", "minecraft:black_stained_glass_pane": "黑色染色玻璃片", - "minecraft:hard_orange_stained_glass_pane": "橙色強化玻璃板", - "minecraft:hard_magenta_stained_glass_pane": "紫紅色強化玻璃板", - "minecraft:hard_light_blue_stained_glass_pane": "淡藍色強化玻璃板", - "minecraft:hard_yellow_stained_glass_pane": "黃色強化玻璃板", - "minecraft:hard_lime_stained_glass_pane": "黃綠色強化玻璃板", - "minecraft:hard_pink_stained_glass_pane": "粉紅色強化玻璃板", - "minecraft:hard_gray_stained_glass_pane": "灰色強化玻璃板", - "minecraft:hard_light_gray_stained_glass_pane": "淡灰色強化玻璃板", - "minecraft:hard_cyan_stained_glass_pane": "青藍色強化玻璃板", - "minecraft:hard_purple_stained_glass_pane": "紫色強化玻璃板", - "minecraft:hard_blue_stained_glass_pane": "藍色強化玻璃板", - "minecraft:hard_brown_stained_glass_pane": "啡色強化玻璃板", - "minecraft:hard_green_stained_glass_pane": "綠色強化玻璃板", - "minecraft:hard_red_stained_glass_pane": "紅色強化玻璃板", - "minecraft:hard_black_stained_glass_pane": "黑色強化玻璃板", + "minecraft:hard_orange_stained_glass_pane": "橙色強化玻璃片", + "minecraft:hard_magenta_stained_glass_pane": "紫紅色強化玻璃片", + "minecraft:hard_light_blue_stained_glass_pane": "淺藍色強化玻璃片", + "minecraft:hard_yellow_stained_glass_pane": "黃色強化玻璃片", + "minecraft:hard_lime_stained_glass_pane": "淺綠色強化玻璃片", + "minecraft:hard_pink_stained_glass_pane": "粉紅色強化玻璃片", + "minecraft:hard_gray_stained_glass_pane": "灰色強化玻璃片", + "minecraft:hard_light_gray_stained_glass_pane": "淺灰色強化玻璃片", + "minecraft:hard_cyan_stained_glass_pane": "青藍色強化玻璃片", + "minecraft:hard_purple_stained_glass_pane": "紫色強化玻璃片", + "minecraft:hard_blue_stained_glass_pane": "藍色強化玻璃片", + "minecraft:hard_brown_stained_glass_pane": "啡色強化玻璃片", + "minecraft:hard_green_stained_glass_pane": "綠色強化玻璃片", + "minecraft:hard_red_stained_glass_pane": "紅色強化玻璃片", + "minecraft:hard_black_stained_glass_pane": "黑色強化玻璃片", "minecraft:orange_stained_glass": "橙色染色玻璃", "minecraft:magenta_stained_glass": "紫紅色染色玻璃", - "minecraft:light_blue_stained_glass": "淡藍色染色玻璃", + "minecraft:light_blue_stained_glass": "淺藍色染色玻璃", "minecraft:yellow_stained_glass": "黃色染色玻璃", - "minecraft:lime_stained_glass": "黃綠色染色玻璃", + "minecraft:lime_stained_glass": "淺綠色染色玻璃", "minecraft:pink_stained_glass": "粉紅色染色玻璃", "minecraft:gray_stained_glass": "灰色染色玻璃", - "minecraft:light_gray_stained_glass": "淡灰色染色玻璃", + "minecraft:light_gray_stained_glass": "淺灰色染色玻璃", "minecraft:cyan_stained_glass": "青藍色染色玻璃", "minecraft:purple_stained_glass": "紫色染色玻璃", "minecraft:blue_stained_glass": "藍色染色玻璃", @@ -1037,12 +1058,12 @@ "minecraft:black_stained_glass": "黑色染色玻璃", "minecraft:hard_orange_stained_glass": "橙色強化玻璃", "minecraft:hard_magenta_stained_glass": "紫紅色強化玻璃", - "minecraft:hard_light_blue_stained_glass": "淡藍色強化玻璃", + "minecraft:hard_light_blue_stained_glass": "淺藍色強化玻璃", "minecraft:hard_yellow_stained_glass": "黃色強化玻璃", - "minecraft:hard_lime_stained_glass": "黃綠色強化玻璃", + "minecraft:hard_lime_stained_glass": "淺綠色強化玻璃", "minecraft:hard_pink_stained_glass": "粉紅色強化玻璃", "minecraft:hard_gray_stained_glass": "灰色強化玻璃", - "minecraft:hard_light_gray_stained_glass": "淡灰色強化玻璃", + "minecraft:hard_light_gray_stained_glass": "淺灰色強化玻璃", "minecraft:hard_cyan_stained_glass": "青藍色強化玻璃", "minecraft:hard_purple_stained_glass": "紫色強化玻璃", "minecraft:hard_blue_stained_glass": "藍色強化玻璃", @@ -1052,12 +1073,12 @@ "minecraft:hard_black_stained_glass": "黑色強化玻璃", "minecraft:orange_concrete_powder": "橙色混凝土粉末", "minecraft:magenta_concrete_powder": "紫紅色混凝土粉末", - "minecraft:light_blue_concrete_powder": "淡藍色混凝土粉末", + "minecraft:light_blue_concrete_powder": "淺藍色混凝土粉末", "minecraft:yellow_concrete_powder": "黃色混凝土粉末", - "minecraft:lime_concrete_powder": "黃綠色混凝土粉末", + "minecraft:lime_concrete_powder": "淺綠色混凝土粉末", "minecraft:pink_concrete_powder": "粉紅色混凝土粉末", "minecraft:gray_concrete_powder": "灰色混凝土粉末", - "minecraft:light_gray_concrete_powder": "淡灰色混凝土粉末", + "minecraft:light_gray_concrete_powder": "淺灰色混凝土粉末", "minecraft:cyan_concrete_powder": "青藍色混凝土粉末", "minecraft:purple_concrete_powder": "紫色混凝土粉末", "minecraft:blue_concrete_powder": "藍色混凝土粉末", @@ -1067,12 +1088,12 @@ "minecraft:black_concrete_powder": "黑色混凝土粉末", "minecraft:orange_terracotta": "橙色陶瓦", "minecraft:magenta_terracotta": "紫紅色陶瓦", - "minecraft:light_blue_terracotta": "淡藍色陶瓦", + "minecraft:light_blue_terracotta": "淺藍色陶瓦", "minecraft:yellow_terracotta": "黃色陶瓦", - "minecraft:lime_terracotta": "黃綠色陶瓦", + "minecraft:lime_terracotta": "淺綠色陶瓦", "minecraft:pink_terracotta": "粉紅色陶瓦", "minecraft:gray_terracotta": "灰色陶瓦", - "minecraft:light_gray_terracotta": "淡灰色陶瓦", + "minecraft:light_gray_terracotta": "淺灰色陶瓦", "minecraft:cyan_terracotta": "青藍色陶瓦", "minecraft:purple_terracotta": "紫色陶瓦", "minecraft:blue_terracotta": "藍色陶瓦", @@ -1085,38 +1106,38 @@ "minecraft:jungle_planks": "叢林木板", "minecraft:acacia_planks": "相思木板", "minecraft:dark_oak_planks": "黑橡木板", - "minecraft:tuff_slab": "凝灰岩台階", - "minecraft:tuff_double_slab": "凝灰岩雙層台階", - "minecraft:tuff_stairs": "凝灰岩樓梯", - "minecraft:tuff_wall": "凝灰岩牆", - "minecraft:polished_tuff": "磨製凝灰岩", - "minecraft:polished_tuff_slab": "磨製凝灰岩台階", - "minecraft:polished_tuff_double_slab": "磨製凝灰岩雙層台階", - "minecraft:polished_tuff_stairs": "磨製凝灰岩樓梯", - "minecraft:polished_tuff_wall": "磨製凝灰岩牆", - "minecraft:chiseled_tuff": "雕紋凝灰岩", - "minecraft:tuff_bricks": "凝灰岩磚", - "minecraft:tuff_brick_slab": "凝灰岩磚台階", - "minecraft:tuff_brick_double_slab": "凝灰岩磚雙層台階", - "minecraft:tuff_brick_stairs": "凝灰岩磚樓梯", - "minecraft:tuff_brick_wall": "凝灰岩磚牆", - "minecraft:chiseled_tuff_bricks": "雕紋凝灰岩磚", - "minecraft:chiseled_copper": "雕紋銅磚", - "minecraft:exposed_chiseled_copper": "斑駁的雕紋銅磚", - "minecraft:weathered_chiseled_copper": "生鏽的雕紋銅磚", - "minecraft:oxidized_chiseled_copper": "氧化的雕紋銅磚", - "minecraft:waxed_chiseled_copper": "打蠟的雕紋銅磚", - "minecraft:waxed_exposed_chiseled_copper": "打蠟的斑駁雕紋銅磚", - "minecraft:waxed_weathered_chiseled_copper": "打蠟的生鏽雕紋銅磚", - "minecraft:waxed_oxidized_chiseled_copper": "打蠟的氧化雕紋銅磚", - "minecraft:copper_grate": "銅格柵", - "minecraft:exposed_copper_grate": "斑駁的銅格柵", - "minecraft:weathered_copper_grate": "生鏽的銅格柵", - "minecraft:oxidized_copper_grate": "氧化的銅格柵", - "minecraft:waxed_copper_grate": "打蠟的銅格柵", - "minecraft:waxed_exposed_copper_grate": "打蠟的斑駁銅格柵", - "minecraft:waxed_weathered_copper_grate": "打蠟的生鏽銅格柵", - "minecraft:waxed_oxidized_copper_grate": "打蠟的氧化銅格柵", + "minecraft:tuff_slab": "凝灰巖半磚", + "minecraft:tuff_double_slab": "凝灰巖雙半磚", + "minecraft:tuff_stairs": "凝灰巖樓梯", + "minecraft:tuff_wall": "凝灰巖牆", + "minecraft:polished_tuff": "拋光凝灰巖", + "minecraft:polished_tuff_slab": "拋光凝灰巖半磚", + "minecraft:polished_tuff_double_slab": "拋光凝灰巖雙半磚", + "minecraft:polished_tuff_stairs": "拋光凝灰巖樓梯", + "minecraft:polished_tuff_wall": "拋光凝灰巖牆", + "minecraft:chiseled_tuff": "浮雕凝灰巖", + "minecraft:tuff_bricks": "凝灰巖磚", + "minecraft:tuff_brick_slab": "凝灰巖磚半磚", + "minecraft:tuff_brick_double_slab": "凝灰巖磚雙半磚", + "minecraft:tuff_brick_stairs": "凝灰巖磚樓梯", + "minecraft:tuff_brick_wall": "凝灰巖磚牆", + "minecraft:chiseled_tuff_bricks": "浮雕凝灰巖磚", + "minecraft:chiseled_copper": "浮雕銅磚", + "minecraft:exposed_chiseled_copper": "斑駁的浮雕銅磚", + "minecraft:weathered_chiseled_copper": "生鏽的浮雕銅磚", + "minecraft:oxidized_chiseled_copper": "氧化的浮雕銅磚", + "minecraft:waxed_chiseled_copper": "打蠟的浮雕銅磚", + "minecraft:waxed_exposed_chiseled_copper": "打蠟的斑駁浮雕銅磚", + "minecraft:waxed_weathered_chiseled_copper": "打蠟的生鏽浮雕銅磚", + "minecraft:waxed_oxidized_chiseled_copper": "打蠟的氧化浮雕銅磚", + "minecraft:copper_grate": "銅網格", + "minecraft:exposed_copper_grate": "斑駁的銅網格", + "minecraft:weathered_copper_grate": "生鏽的銅網格", + "minecraft:oxidized_copper_grate": "氧化的銅網格", + "minecraft:waxed_copper_grate": "打蠟的銅網格", + "minecraft:waxed_exposed_copper_grate": "打蠟的斑駁銅網格", + "minecraft:waxed_weathered_copper_grate": "打蠟的生鏽銅網格", + "minecraft:waxed_oxidized_copper_grate": "打蠟的氧化銅網格", "minecraft:copper_bulb": "銅燈", "minecraft:exposed_copper_bulb": "斑駁的銅燈", "minecraft:weathered_copper_bulb": "生鏽的銅燈", @@ -1141,191 +1162,191 @@ "minecraft:waxed_exposed_copper_trapdoor": "打蠟的斑駁銅地板門", "minecraft:waxed_weathered_copper_trapdoor": "打蠟的生鏽銅地板門", "minecraft:waxed_oxidized_copper_trapdoor": "打蠟的氧化銅地板門", - "minecraft:spruce_leaves": "雲杉樹葉", - "minecraft:birch_leaves": "白樺樹葉", - "minecraft:jungle_leaves": "叢林樹葉", - "minecraft:dark_oak_leaves": "深色橡樹樹葉", - "minecraft:spruce_slab": "雲杉木台階", - "minecraft:birch_slab": "樺木台階", - "minecraft:jungle_slab": "叢林木台階", - "minecraft:acacia_slab": "相思木台階", - "minecraft:dark_oak_slab": "黑橡木台階", - "minecraft:spruce_double_slab": "雲杉木雙層台階", - "minecraft:birch_double_slab": "樺木雙層台階", - "minecraft:jungle_double_slab": "叢林木雙層台階", - "minecraft:acacia_double_slab": "相思木雙層台階", - "minecraft:dark_oak_double_slab": "黑橡木雙層台階", + "minecraft:spruce_leaves": "杉木樹葉", + "minecraft:birch_leaves": "樺木樹葉", + "minecraft:jungle_leaves": "叢林木樹葉", + "minecraft:dark_oak_leaves": "黑橡木樹葉", + "minecraft:spruce_slab": "杉木半磚", + "minecraft:birch_slab": "樺木半磚", + "minecraft:jungle_slab": "叢林木半磚", + "minecraft:acacia_slab": "相思木半磚", + "minecraft:dark_oak_slab": "黑橡木半磚", + "minecraft:spruce_double_slab": "杉木雙半磚", + "minecraft:birch_double_slab": "樺木雙半磚", + "minecraft:jungle_double_slab": "叢林木雙半磚", + "minecraft:acacia_double_slab": "相思木雙半磚", + "minecraft:dark_oak_double_slab": "黑橡木雙半磚", "minecraft:spruce_wood": "杉木", "minecraft:birch_wood": "樺木", "minecraft:jungle_wood": "叢林木", "minecraft:acacia_wood": "相思木", "minecraft:dark_oak_wood": "黑橡木", "minecraft:stripped_oak_wood": "剝皮橡木", - "minecraft:stripped_spruce_wood": "剝皮雲杉木", + "minecraft:stripped_spruce_wood": "剝皮杉木", "minecraft:stripped_birch_wood": "剝皮樺木", "minecraft:stripped_jungle_wood": "剝皮叢林木", "minecraft:stripped_acacia_wood": "剝皮相思木", "minecraft:stripped_dark_oak_wood": "剝皮黑橡木", - "minecraft:spruce_sapling": "雲杉樹苗", - "minecraft:birch_sapling": "白樺樹苗", - "minecraft:jungle_sapling": "叢林樹苗", - "minecraft:acacia_sapling": "金合歡樹苗", - "minecraft:dark_oak_sapling": "深色橡樹樹苗", + "minecraft:spruce_sapling": "杉木樹苗", + "minecraft:birch_sapling": "樺木樹苗", + "minecraft:jungle_sapling": "叢林木樹苗", + "minecraft:acacia_sapling": "相思木樹苗", + "minecraft:dark_oak_sapling": "黑橡木樹苗", "minecraft:blue_orchid": "蘭花", - "minecraft:allium": "絨球蔥", + "minecraft:allium": "絨球葱", "minecraft:azure_bluet": "藍花美耳草", "minecraft:red_tulip": "紅色鬱金香", "minecraft:orange_tulip": "橙色鬱金香", "minecraft:white_tulip": "白色鬱金香", "minecraft:pink_tulip": "粉紅色鬱金香", - "minecraft:oxeye_daisy": "濱菊", + "minecraft:oxeye_daisy": "雛菊", "minecraft:cornflower": "矢車菊", "minecraft:lily_of_the_valley": "鈴蘭", - "minecraft:brain_coral_fan": "腦紋珊瑚扇", - "minecraft:bubble_coral_fan": "氣泡珊瑚扇", - "minecraft:fire_coral_fan": "火珊瑚扇", - "minecraft:horn_coral_fan": "角珊瑚扇", - "minecraft:dead_brain_coral_fan": "死亡腦紋珊瑚扇", - "minecraft:dead_bubble_coral_fan": "死亡氣泡珊瑚扇", - "minecraft:dead_fire_coral_fan": "死亡火珊瑚扇", - "minecraft:dead_horn_coral_fan": "死亡角珊瑚扇", + "minecraft:brain_coral_fan": "扇狀腦珊瑚", + "minecraft:bubble_coral_fan": "扇狀氣泡珊瑚", + "minecraft:fire_coral_fan": "扇狀火珊瑚", + "minecraft:horn_coral_fan": "扇狀角珊瑚", + "minecraft:dead_brain_coral_fan": "死亡的扇狀腦珊瑚", + "minecraft:dead_bubble_coral_fan": "死亡的扇狀氣泡珊瑚", + "minecraft:dead_fire_coral_fan": "死亡的扇狀火珊瑚", + "minecraft:dead_horn_coral_fan": "死亡的扇狀角珊瑚", "minecraft:fern": "蕨", - "minecraft:brain_coral_block": "腦紋珊瑚塊", - "minecraft:bubble_coral_block": "氣泡珊瑚塊", - "minecraft:fire_coral_block": "火珊瑚塊", - "minecraft:horn_coral_block": "角珊瑚塊", - "minecraft:dead_tube_coral_block": "死亡管珊瑚塊", - "minecraft:dead_brain_coral_block": "死亡腦紋珊瑚塊", - "minecraft:dead_bubble_coral_block": "死亡氣泡珊瑚塊", - "minecraft:dead_fire_coral_block": "死亡火珊瑚塊", - "minecraft:dead_horn_coral_block": "死亡角珊瑚塊", - "minecraft:infested_cobblestone": "蟲蝕碎石", - "minecraft:infested_stone_bricks": "蟲蝕石磚", - "minecraft:infested_mossy_stone_bricks": "蟲蝕青苔碎石磚", - "minecraft:infested_cracked_stone_bricks": "蟲蝕裂紋石磚", - "minecraft:infested_chiseled_stone_bricks": "蟲蝕雕紋石磚", - "minecraft:lilac": "丁香", + "minecraft:brain_coral_block": "腦珊瑚方塊", + "minecraft:bubble_coral_block": "氣泡珊瑚方塊", + "minecraft:fire_coral_block": "火珊瑚方塊", + "minecraft:horn_coral_block": "角珊瑚方塊", + "minecraft:dead_tube_coral_block": "死亡的管珊瑚方塊", + "minecraft:dead_brain_coral_block": "死亡的腦珊瑚方塊", + "minecraft:dead_bubble_coral_block": "死亡的氣泡珊瑚方塊", + "minecraft:dead_fire_coral_block": "死亡的火珊瑚方塊", + "minecraft:dead_horn_coral_block": "死亡的角珊瑚方塊", + "minecraft:infested_cobblestone": "蛀蝕的碎石", + "minecraft:infested_stone_bricks": "蛀蝕的石磚", + "minecraft:infested_mossy_stone_bricks": "蛀蝕的青苔石磚", + "minecraft:infested_cracked_stone_bricks": "蛀蝕的裂紋石磚", + "minecraft:infested_chiseled_stone_bricks": "蛀蝕的浮雕石磚", + "minecraft:lilac": "紫丁香", "minecraft:tall_grass": "長草叢", "minecraft:large_fern": "大型蕨", "minecraft:rose_bush": "玫瑰叢", - "minecraft:peony": "牡丹", - "minecraft:mossy_stone_bricks": "青苔碎石磚", + "minecraft:peony": "牡丹花", + "minecraft:mossy_stone_bricks": "青苔石磚", "minecraft:cracked_stone_bricks": "裂紋石磚", - "minecraft:chiseled_stone_bricks": "雕紋石磚", - "minecraft:sandstone_slab": "砂岩台階", - "minecraft:cobblestone_slab": "碎石台階", - "minecraft:brick_slab": "紅磚頭台階", - "minecraft:stone_brick_slab": "石磚台階", - "minecraft:quartz_slab": "石英台階", - "minecraft:nether_brick_slab": "地獄磚頭台階", - "minecraft:sandstone_double_slab": "砂岩雙層台階", - "minecraft:cobblestone_double_slab": "碎石雙層台階", - "minecraft:brick_double_slab": "紅磚頭雙層台階", - "minecraft:stone_brick_double_slab": "石磚雙層台階", - "minecraft:quartz_double_slab": "石英雙層台階", - "minecraft:nether_brick_double_slab": "地獄磚頭雙層台階", - "minecraft:purpur_slab": "紫珀台階", - "minecraft:prismarine_slab": "海晶石頭半磚", - "minecraft:dark_prismarine_slab": "暗海晶石頭半磚", - "minecraft:prismarine_brick_slab": "海晶石磚台階", - "minecraft:mossy_cobblestone_slab": "青苔碎石台階", - "minecraft:smooth_sandstone_slab": "平滑砂岩台階", - "minecraft:red_nether_brick_slab": "紅色地獄磚台階", - "minecraft:smooth_red_sandstone_slab": "平滑紅砂岩台階", - "minecraft:polished_andesite_slab": "磨製安山岩台階", - "minecraft:andesite_slab": "安山岩台階", - "minecraft:diorite_slab": "閃長岩台階", - "minecraft:polished_diorite_slab": "磨製閃長岩台階", - "minecraft:granite_slab": "花崗岩台階", - "minecraft:polished_granite_slab": "磨製花崗岩台階", - "minecraft:smooth_quartz_slab": "平滑石英磚台階", - "minecraft:normal_stone_slab": "石頭台階", - "minecraft:cut_sandstone_slab": "切制砂岩台階", - "minecraft:cut_red_sandstone_slab": "切制紅砂岩台階", - "minecraft:petrified_slab": "石化橡木台階", - "minecraft:petrified_double_slab": "石化橡木雙層台階", - "minecraft:brain_coral_wall_fan": "牆上的腦紋珊瑚扇", - "minecraft:dead_tube_coral_wall_fan": "牆上的死亡管珊瑚扇", - "minecraft:dead_brain_coral_wall_fan": "牆上的死亡腦紋珊瑚扇", - "minecraft:fire_coral_wall_fan": "牆上的火珊瑚扇", - "minecraft:dead_bubble_coral_wall_fan": "牆上的死亡氣泡珊瑚扇", - "minecraft:dead_fire_coral_wall_fan": "牆上的死亡火珊瑚扇", - "minecraft:dead_horn_coral_wall_fan": "牆上的死亡角珊瑚扇", - "minecraft:purpur_double_slab": "紫珀雙層台階", - "minecraft:prismarine_double_slab": "海晶石雙層台階", - "minecraft:dark_prismarine_double_slab": "暗海晶石雙層台階", - "minecraft:prismarine_brick_double_slab": "海晶石磚雙層台階", - "minecraft:mossy_cobblestone_double_slab": "青苔碎石雙層台階", - "minecraft:smooth_sandstone_double_slab": "平滑砂岩雙層台階", - "minecraft:red_nether_brick_double_slab": "紅色地獄磚雙層台階", - "minecraft:smooth_red_sandstone_double_slab": "平滑紅砂岩雙層台階", - "minecraft:polished_andesite_double_slab": "磨製安山岩雙層台階", - "minecraft:andesite_double_slab": "安山岩雙層台階", - "minecraft:diorite_double_slab": "閃長岩雙層台階", - "minecraft:polished_diorite_double_slab": "磨製閃長岩雙層台階", - "minecraft:granite_double_slab": "花崗岩雙層台階", - "minecraft:polished_granite_double_slab": "磨製花崗岩雙層台階", - "minecraft:smooth_quartz_double_slab": "平滑石英磚雙層台階", - "minecraft:normal_stone_double_slab": "石頭雙層台階", - "minecraft:cut_sandstone_double_slab": "切制砂岩雙層台階", - "minecraft:cut_red_sandstone_double_slab": "切制紅砂岩雙層台階", - "minecraft:light_block_1": "光源方塊 (亮度1)", - "minecraft:light_block_2": "光源方塊 (亮度2)", - "minecraft:light_block_3": "光源方塊 (亮度3)", - "minecraft:light_block_4": "光源方塊 (亮度4)", - "minecraft:light_block_5": "光源方塊 (亮度5)", - "minecraft:light_block_6": "光源方塊 (亮度6)", - "minecraft:light_block_7": "光源方塊 (亮度7)", - "minecraft:light_block_8": "光源方塊 (亮度8)", - "minecraft:light_block_9": "光源方塊 (亮度9)", - "minecraft:light_block_10": "光源方塊 (亮度10)", - "minecraft:light_block_11": "光源方塊 (亮度11)", - "minecraft:light_block_12": "光源方塊 (亮度12)", - "minecraft:light_block_13": "光源方塊 (亮度13)", - "minecraft:light_block_14": "光源方塊 (亮度14)", - "minecraft:light_block_15": "光源方塊 (亮度15)", - "minecraft:chiseled_sandstone": "雕紋砂岩", - "minecraft:cut_sandstone": "切制砂岩", - "minecraft:smooth_sandstone": "平滑砂岩", - "minecraft:dark_prismarine": "暗海晶石", - "minecraft:prismarine_bricks": "海晶石磚", + "minecraft:chiseled_stone_bricks": "浮雕石磚", + "minecraft:sandstone_slab": "砂巖半磚", + "minecraft:cobblestone_slab": "碎石半磚", + "minecraft:brick_slab": "紅磚半磚", + "minecraft:stone_brick_slab": "石磚半磚", + "minecraft:quartz_slab": "石英半磚", + "minecraft:nether_brick_slab": "地獄磚半磚", + "minecraft:sandstone_double_slab": "砂巖雙半磚", + "minecraft:cobblestone_double_slab": "碎石雙半磚", + "minecraft:brick_double_slab": "紅磚頭雙半磚", + "minecraft:stone_brick_double_slab": "石磚雙半磚", + "minecraft:quartz_double_slab": "石英雙半磚", + "minecraft:nether_brick_double_slab": "地獄磚頭雙半磚", + "minecraft:purpur_slab": "紫珀半磚", + "minecraft:prismarine_slab": "海磷石半磚", + "minecraft:dark_prismarine_slab": "暗海磷石半磚", + "minecraft:prismarine_brick_slab": "海磷石磚半磚", + "minecraft:mossy_cobblestone_slab": "青苔碎石半磚", + "minecraft:smooth_sandstone_slab": "平滑砂巖半磚", + "minecraft:red_nether_brick_slab": "紅色地獄磚半磚", + "minecraft:smooth_red_sandstone_slab": "平滑紅砂巖半磚", + "minecraft:polished_andesite_slab": "拋光安山巖半磚", + "minecraft:andesite_slab": "安山巖半磚", + "minecraft:diorite_slab": "閃長巖半磚", + "minecraft:polished_diorite_slab": "拋光閃長巖半磚", + "minecraft:granite_slab": "花崗巖半磚", + "minecraft:polished_granite_slab": "拋光花崗巖半磚", + "minecraft:smooth_quartz_slab": "平滑石英半磚", + "minecraft:normal_stone_slab": "石頭半磚", + "minecraft:cut_sandstone_slab": "切製砂巖半磚", + "minecraft:cut_red_sandstone_slab": "切製紅砂巖半磚", + "minecraft:petrified_slab": "石化橡木半磚", + "minecraft:petrified_double_slab": "石化橡木雙半磚", + "minecraft:brain_coral_wall_fan": "牆上的扇狀腦珊瑚", + "minecraft:dead_tube_coral_wall_fan": "牆上死亡的扇狀管珊瑚", + "minecraft:dead_brain_coral_wall_fan": "牆上死亡的扇狀腦珊瑚", + "minecraft:fire_coral_wall_fan": "牆上的扇狀火珊瑚", + "minecraft:dead_bubble_coral_wall_fan": "牆上死亡的扇狀氣泡珊瑚", + "minecraft:dead_fire_coral_wall_fan": "牆上死亡的扇狀火珊瑚", + "minecraft:dead_horn_coral_wall_fan": "牆上死亡的扇狀角珊瑚", + "minecraft:purpur_double_slab": "紫珀雙半磚", + "minecraft:prismarine_double_slab": "海磷石雙半磚", + "minecraft:dark_prismarine_double_slab": "暗海磷石雙半磚", + "minecraft:prismarine_brick_double_slab": "海磷石磚雙半磚", + "minecraft:mossy_cobblestone_double_slab": "青苔碎石雙半磚", + "minecraft:smooth_sandstone_double_slab": "平滑砂巖雙半磚", + "minecraft:red_nether_brick_double_slab": "紅色地獄磚雙半磚", + "minecraft:smooth_red_sandstone_double_slab": "平滑紅砂巖雙半磚", + "minecraft:polished_andesite_double_slab": "拋光安山巖雙半磚", + "minecraft:andesite_double_slab": "安山巖雙半磚", + "minecraft:diorite_double_slab": "閃長巖雙半磚", + "minecraft:polished_diorite_double_slab": "拋光閃長巖雙半磚", + "minecraft:granite_double_slab": "花崗巖雙半磚", + "minecraft:polished_granite_double_slab": "拋光花崗巖雙半磚", + "minecraft:smooth_quartz_double_slab": "平滑石英雙半磚", + "minecraft:normal_stone_double_slab": "石頭雙半磚", + "minecraft:cut_sandstone_double_slab": "切製砂巖雙半磚", + "minecraft:cut_red_sandstone_double_slab": "切製紅砂巖雙半磚", + "minecraft:light_block_1": "光 (亮度1)", + "minecraft:light_block_2": "光 (亮度2)", + "minecraft:light_block_3": "光 (亮度3)", + "minecraft:light_block_4": "光 (亮度4)", + "minecraft:light_block_5": "光 (亮度5)", + "minecraft:light_block_6": "光 (亮度6)", + "minecraft:light_block_7": "光 (亮度7)", + "minecraft:light_block_8": "光 (亮度8)", + "minecraft:light_block_9": "光 (亮度9)", + "minecraft:light_block_10": "光 (亮度10)", + "minecraft:light_block_11": "光 (亮度11)", + "minecraft:light_block_12": "光 (亮度12)", + "minecraft:light_block_13": "光 (亮度13)", + "minecraft:light_block_14": "光 (亮度14)", + "minecraft:light_block_15": "光 (亮度15)", + "minecraft:chiseled_sandstone": "浮雕砂巖", + "minecraft:cut_sandstone": "切製砂巖", + "minecraft:smooth_sandstone": "平滑砂巖", + "minecraft:dark_prismarine": "暗海磷石", + "minecraft:prismarine_bricks": "海磷石磚", "minecraft:red_sand": "紅沙", - "minecraft:deprecated_purpur_block_1": "雕紋紫珀磚", + "minecraft:deprecated_purpur_block_1": "浮雕紫珀磚", "minecraft:purpur_pillar": "紫珀柱", "minecraft:deprecated_purpur_block_2": "平滑紫珀磚", - "minecraft:chiseled_quartz_block": "雕紋石英磚", + "minecraft:chiseled_quartz_block": "浮雕石英磚", "minecraft:quartz_pillar": "石英柱", - "minecraft:smooth_quartz": "平滑石英磚塊", - "minecraft:chiseled_red_sandstone": "雕紋紅砂岩", - "minecraft:cut_red_sandstone": "切制紅砂岩", - "minecraft:smooth_red_sandstone": "平滑紅砂岩", - "minecraft:chipped_anvil": "微損的鐵砧", - "minecraft:damaged_anvil": "耗損的鐵砧", + "minecraft:smooth_quartz_block": "平滑石英磚", + "minecraft:chiseled_red_sandstone": "浮雕紅砂巖", + "minecraft:cut_red_sandstone": "切製紅砂巖", + "minecraft:smooth_red_sandstone": "平滑紅砂巖", + "minecraft:chipped_anvil": "開裂的鐵砧", + "minecraft:damaged_anvil": "破損的鐵砧", "minecraft:deprecated_anvil": "破碎的鐵砧", - "minecraft:coarse_dirt": "砂土", + "minecraft:coarse_dirt": "粗泥", "minecraft:colored_torch_green": "綠色火炬", "minecraft:colored_torch_purple": "紫色火炬", - "minecraft:wither_skeleton_skull": "凋零骷髏骨頭", + "minecraft:wither_skeleton_skull": "凋零骷髏骨頭顱", "minecraft:zombie_head": "喪屍頭顱", "minecraft:player_head": "玩家頭顱", "minecraft:creeper_head": "Creeper頭顱", "minecraft:dragon_head": "龍頭", - "minecraft:piglin_head": "豬人的頭", + "minecraft:piglin_head": "豬人頭顱", "minecraft:mossy_cobblestone_wall": "青苔碎石牆", - "minecraft:granite_wall": "花崗岩牆", - "minecraft:diorite_wall": "閃長岩牆", - "minecraft:andesite_wall": "安山岩牆", - "minecraft:sandstone_wall": "砂岩牆", - "minecraft:brick_wall": "紅磚頭牆", + "minecraft:granite_wall": "花崗巖牆", + "minecraft:diorite_wall": "閃長巖牆", + "minecraft:andesite_wall": "安山巖牆", + "minecraft:sandstone_wall": "砂巖牆", + "minecraft:brick_wall": "紅磚牆", "minecraft:stone_brick_wall": "石磚牆", - "minecraft:mossy_stone_brick_wall": "青苔碎石磚牆", - "minecraft:nether_brick_wall": "地獄磚頭牆", + "minecraft:mossy_stone_brick_wall": "青苔石磚牆", + "minecraft:nether_brick_wall": "地獄磚牆", "minecraft:end_stone_brick_wall": "終界石磚牆", - "minecraft:prismarine_wall": "海晶石牆", - "minecraft:red_sandstone_wall": "紅砂岩牆", + "minecraft:prismarine_wall": "海磷石牆", + "minecraft:red_sandstone_wall": "紅砂巖牆", "minecraft:red_nether_brick_wall": "紅色地獄磚牆", "minecraft:wet_sponge": "濕海綿", - "minecraft:underwater_tnt": "水下TT", + "minecraft:underwater_tnt": "水下TNT", "minecraft:material_reducer": "材料分解器", "minecraft:element_constructor": "元素構造器", "minecraft:lab_table": "實驗台", @@ -1333,80 +1354,232 @@ "minecraft:pale_oak_door": "蒼白橡木門", "minecraft:pale_oak_fence": "蒼白橡木欄杆", "minecraft:pale_oak_fence_gate": "蒼白橡木閘門", - "minecraft:pale_oak_hanging_sign": "懸掛式蒼白橡木指示牌", + "minecraft:pale_oak_hanging_sign": "蒼白橡木吊牌", "minecraft:stripped_pale_oak_log": "剝皮蒼白橡木原木", "minecraft:pale_oak_log": "蒼白橡木原木", "minecraft:pale_oak_planks": "蒼白橡木板", "minecraft:pale_oak_pressure_plate": "蒼白橡木壓力板", - "minecraft:pale_oak_slab": "蒼白橡木台階", - "minecraft:pale_oak_double_slab": "蒼白橡木雙層台階", + "minecraft:pale_oak_slab": "蒼白橡木半磚", + "minecraft:pale_oak_double_slab": "蒼白橡木雙半磚", "minecraft:pale_oak_stairs": "蒼白橡木樓梯", "minecraft:pale_oak_standing_sign": "蒼白橡木指示牌", "minecraft:pale_oak_trapdoor": "蒼白橡木地板門", "minecraft:pale_oak_wall_sign": "牆上的蒼白橡木指示牌", "minecraft:stripped_pale_oak_wood": "剝皮蒼白橡木", "minecraft:pale_oak_wood": "蒼白橡木", - "minecraft:pale_oak_sapling": "蒼白橡樹樹苗", - "minecraft:pale_oak_leaves": "蒼白橡樹樹葉", - "minecraft:mushroom_stem": "蘑菇椗", + "minecraft:pale_oak_sapling": "蒼白橡木樹苗", + "minecraft:pale_oak_leaves": "蒼白橡木樹葉", + "minecraft:mushroom_stem": "蘑菇柄", "minecraft:pale_moss_block": "蒼白青苔方塊", - "minecraft:pale_moss_carpet": "蒼白覆地苔蘚", + "minecraft:pale_moss_carpet": "蒼白青苔", "minecraft:pale_hanging_moss": "蒼白垂鬚", - "minecraft:creaking_heart": "異響樹妖之心", - "minecraft:resin_bricks": "樹脂磚塊", - "minecraft:resin_brick_slab": "樹脂磚台階", - "minecraft:resin_brick_double_slab": "樹脂磚雙層台階", + "minecraft:creaking_heart": "異響之心", + "minecraft:resin_bricks": "樹脂磚", + "minecraft:resin_brick_slab": "樹脂磚半磚", + "minecraft:resin_brick_double_slab": "樹脂磚頭雙半磚", "minecraft:resin_brick_stairs": "樹脂磚樓梯", "minecraft:resin_brick_wall": "樹脂磚牆", "minecraft:open_eyeblossom": "張開的眼眸花", "minecraft:closed_eyeblossom": "閉合的眼眸花", - "minecraft:chiseled_resin_bricks": "雕紋樹脂磚塊", + "minecraft:chiseled_resin_bricks": "浮雕樹脂磚", "minecraft:resin_block": "樹脂塊", "minecraft:resin_clump": "樹脂團", - "minecraft:bush": "ush", + "minecraft:bush": "灌木叢", "minecraft:wildflowers": "野花簇", - "minecraft:firefly_bush": "Firefly ush", + "minecraft:firefly_bush": "螢火蟲灌木叢", "minecraft:leaf_litter": "枯葉", - "minecraft:black_bundle": "黑色收納袋", - "minecraft:blue_bundle": "藍色收納袋", - "minecraft:brown_bundle": "啡色收納袋", - "minecraft:bundle": "收納袋", - "minecraft:cyan_bundle": "青藍色收納袋", - "minecraft:gray_bundle": "灰色收納袋", - "minecraft:green_bundle": "綠色收納袋", - "minecraft:light_blue_bundle": "淡藍色收納袋", - "minecraft:light_gray_bundle": "淡灰色收納袋", - "minecraft:lime_bundle": "黃綠色收納袋", - "minecraft:magenta_bundle": "紫紅色收納袋", - "minecraft:orange_bundle": "橙色收納袋", - "minecraft:pink_bundle": "粉紅色收納袋", - "minecraft:purple_bundle": "紫色收納袋", - "minecraft:red_bundle": "紅色收納袋", - "minecraft:white_bundle": "白色收納袋", - "minecraft:yellow_bundle": "黃色收納袋", - "minecraft:breeze_rod": "旋風棒", - "minecraft:ominous_trial_key": "不祥試煉鎖匙", - "minecraft:trial_key": "試煉鎖匙", - "minecraft:wind_charge": "風彈", - "minecraft:apple": "蘋果", + "minecraft:dried_ghast": "脱水幽靈", + "minecraft:short_dry_grass": "矮枯草", + "minecraft:tall_dry_grass": "高枯草", + "minecraft:cactus_flower": "仙人掌花", + "minecraft:copper_chest": "銅儲物箱", + "minecraft:exposed_copper_chest": "斑駁的銅儲物箱", + "minecraft:weathered_copper_chest": "生鏽的銅儲物箱", + "minecraft:oxidized_copper_chest": "氧化的銅儲物箱", + "minecraft:waxed_copper_chest": "打蠟的銅儲物箱", + "minecraft:waxed_exposed_copper_chest": "打蠟的斑駁銅儲物箱", + "minecraft:waxed_weathered_copper_chest": "打蠟的生鏽銅儲物箱", + "minecraft:waxed_oxidized_copper_chest": "打蠟的氧化銅儲物箱", + "minecraft:copper_golem_statue": "銅人雕像", + "minecraft:exposed_copper_golem_statue": "斑駁的銅人雕像", + "minecraft:weathered_copper_golem_statue": "生鏽的銅人雕像", + "minecraft:oxidized_copper_golem_statue": "氧化的銅人雕像", + "minecraft:waxed_copper_golem_statue": "打蠟的銅人雕像", + "minecraft:waxed_exposed_copper_golem_statue": "打蠟的斑駁銅人雕像", + "minecraft:waxed_weathered_copper_golem_statue": "打蠟的生鏽銅人雕像", + "minecraft:waxed_oxidized_copper_golem_statue": "打蠟的氧化銅人雕像", + "minecraft:oak_shelf": "橡木置物架", + "minecraft:spruce_shelf": "杉木置物架", + "minecraft:birch_shelf": "樺木置物架", + "minecraft:jungle_shelf": "叢林木置物架", + "minecraft:acacia_shelf": "相思木置物架", + "minecraft:dark_oak_shelf": "黑橡木置物架", + "minecraft:mangrove_shelf": "紅樹木置物架", + "minecraft:cherry_shelf": "櫻花木置物架", + "minecraft:pale_oak_shelf": "蒼白橡木置物架", + "minecraft:bamboo_shelf": "竹置物架", + "minecraft:crimson_shelf": "猩紅菌木置物架", + "minecraft:warped_shelf": "迷離菌木置物架", + "minecraft:exposed_lightning_rod": "斑駁的避雷針", + "minecraft:weathered_lightning_rod": "生鏽的避雷針", + "minecraft:oxidized_lightning_rod": "氧化的避雷針", + "minecraft:waxed_lightning_rod": "打蠟的避雷針", + "minecraft:waxed_exposed_lightning_rod": "打蠟的斑駁避雷針", + "minecraft:waxed_weathered_lightning_rod": "打蠟的生鏽避雷針", + "minecraft:waxed_oxidized_lightning_rod": "打蠟的氧化避雷針", + "minecraft:copper_bars": "銅欄杆", + "minecraft:exposed_copper_bars": "斑駁的銅欄杆", + "minecraft:weathered_copper_bars": "生鏽的銅欄杆", + "minecraft:oxidized_copper_bars": "氧化的銅欄杆", + "minecraft:waxed_copper_bars": "打蠟的銅欄杆", + "minecraft:waxed_exposed_copper_bars": "打蠟的斑駁銅欄杆", + "minecraft:waxed_weathered_copper_bars": "打蠟的生鏽銅欄杆", + "minecraft:waxed_oxidized_copper_bars": "打蠟的氧化銅欄杆", + "minecraft:copper_chain": "銅鏈", + "minecraft:exposed_copper_chain": "斑駁的銅鏈", + "minecraft:weathered_copper_chain": "生鏽的銅鏈", + "minecraft:oxidized_copper_chain": "氧化的銅鏈", + "minecraft:waxed_copper_chain": "打蠟的銅鏈", + "minecraft:waxed_exposed_copper_chain": "打蠟的斑駁銅鏈", + "minecraft:waxed_weathered_copper_chain": "打蠟的生鏽銅鏈", + "minecraft:waxed_oxidized_copper_chain": "打蠟的氧化銅鏈", + "minecraft:copper_torch": "銅火炬", + "minecraft:copper_lantern": "銅燈籠", + "minecraft:exposed_copper_lantern": "斑駁的銅燈籠", + "minecraft:weathered_copper_lantern": "生鏽的銅燈籠", + "minecraft:oxidized_copper_lantern": "氧化的銅燈籠", + "minecraft:waxed_copper_lantern": "打蠟的銅燈籠", + "minecraft:waxed_exposed_copper_lantern": "打蠟的斑駁銅燈籠", + "minecraft:waxed_weathered_copper_lantern": "打蠟的生鏽銅燈籠", + "minecraft:waxed_oxidized_copper_lantern": "打蠟的氧化銅燈籠", + "minecraft:golden_dandelion": "金蒲公英", + "minecraft:sulfur": "硫磺", + "minecraft:sulfur_slab": "硫磺半磚", + "minecraft:sulfur_double_slab": "硫磺雙半磚", + "minecraft:sulfur_stairs": "硫磺樓梯", + "minecraft:sulfur_wall": "硫磺牆", + "minecraft:polished_sulfur": "拋光硫磺", + "minecraft:polished_sulfur_slab": "拋光硫磺半磚", + "minecraft:polished_sulfur_double_slab": "拋光硫磺雙半磚", + "minecraft:polished_sulfur_stairs": "拋光硫磺樓梯", + "minecraft:polished_sulfur_wall": "拋光硫磺牆", + "minecraft:sulfur_bricks": "硫磺磚", + "minecraft:sulfur_brick_slab": "硫磺磚半磚", + "minecraft:sulfur_brick_double_slab": "硫磺磚雙半磚", + "minecraft:sulfur_brick_stairs": "硫磺磚樓梯", + "minecraft:sulfur_brick_wall": "硫磺磚牆", + "minecraft:chiseled_sulfur": "浮雕硫磺", + "minecraft:potent_sulfur": "烈性硫磺", + "minecraft:cinnabar": "硃砂", + "minecraft:cinnabar_slab": "硃砂半磚", + "minecraft:cinnabar_double_slab": "硃砂雙半磚", + "minecraft:cinnabar_stairs": "硃砂樓梯", + "minecraft:cinnabar_wall": "硃砂牆", + "minecraft:polished_cinnabar": "拋光硃砂", + "minecraft:polished_cinnabar_slab": "拋光硃砂半磚", + "minecraft:polished_cinnabar_double_slab": "拋光硃砂雙半磚", + "minecraft:polished_cinnabar_stairs": "拋光硃砂樓梯", + "minecraft:polished_cinnabar_wall": "拋光硃砂牆", + "minecraft:cinnabar_bricks": "硃砂磚", + "minecraft:cinnabar_brick_slab": "硃砂磚半磚", + "minecraft:cinnabar_brick_double_slab": "硃砂磚雙半磚", + "minecraft:cinnabar_brick_stairs": "硃砂磚樓梯", + "minecraft:cinnabar_brick_wall": "硃砂磚牆", + "minecraft:chiseled_cinnabar": "浮雕硃砂", + "minecraft:sulfur_spike": "硫磺尖刺", + "minecraft:poplar_button": "楊木按鈕", + "minecraft:poplar_door": "楊木門", + "minecraft:poplar_fence": "楊木欄杆", + "minecraft:poplar_fence_gate": "楊木閘門", + "minecraft:poplar_hanging_sign": "楊木吊牌", + "minecraft:stripped_poplar_log": "剝皮楊木原木", + "minecraft:poplar_log": "楊木原木", + "minecraft:poplar_planks": "楊木板", + "minecraft:poplar_pressure_plate": "楊木壓力板", + "minecraft:poplar_slab": "楊木半磚", + "minecraft:poplar_double_slab": "楊木雙半磚", + "minecraft:poplar_stairs": "楊木樓梯", + "minecraft:poplar_standing_sign": "楊木指示牌", + "minecraft:poplar_trapdoor": "楊木地板門", + "minecraft:poplar_wall_sign": "牆上的楊木指示牌", + "minecraft:stripped_poplar_wood": "剝皮楊木", + "minecraft:poplar_wood": "楊木", + "minecraft:poplar_sapling": "楊木樹苗", + "minecraft:orange_poplar_leaves": "橙色楊木樹葉", + "minecraft:red_poplar_leaves": "紅色楊木樹葉", + "minecraft:yellow_poplar_leaves": "黃色楊木樹葉", + "minecraft:poplar_shelf": "楊木置物架", + "minecraft:red_shrub": "紅灌木", + "minecraft:red_wool_stairs": "紅色羊毛樓梯", + "minecraft:white_wool_stairs": "白色羊毛樓梯", + "minecraft:blue_wool_stairs": "藍色羊毛樓梯", + "minecraft:orange_wool_stairs": "橙色羊毛樓梯", + "minecraft:magenta_wool_stairs": "紫紅色羊毛樓梯", + "minecraft:light_blue_wool_stairs": "淺藍色羊毛樓梯", + "minecraft:yellow_wool_stairs": "黃色羊毛樓梯", + "minecraft:lime_wool_stairs": "淺綠色羊毛樓梯", + "minecraft:pink_wool_stairs": "粉紅色羊毛樓梯", + "minecraft:gray_wool_stairs": "灰色羊毛樓梯", + "minecraft:light_gray_wool_stairs": "淺灰色羊毛樓梯", + "minecraft:cyan_wool_stairs": "青藍色羊毛樓梯", + "minecraft:purple_wool_stairs": "紫色羊毛樓梯", + "minecraft:green_wool_stairs": "綠色羊毛樓梯", + "minecraft:brown_wool_stairs": "啡色羊毛樓梯", + "minecraft:black_wool_stairs": "黑色羊毛樓梯", + "minecraft:white_wool_slab": "白色羊毛半磚", + "minecraft:light_gray_wool_slab": "淺灰色羊毛半磚", + "minecraft:gray_wool_slab": "灰色羊毛半磚", + "minecraft:black_wool_slab": "黑色羊毛半磚", + "minecraft:brown_wool_slab": "啡色羊毛半磚", + "minecraft:red_wool_slab": "紅色羊毛半磚", + "minecraft:orange_wool_slab": "橙色羊毛半磚", + "minecraft:yellow_wool_slab": "黃色羊毛半磚", + "minecraft:lime_wool_slab": "淺綠色羊毛半磚", + "minecraft:green_wool_slab": "綠色羊毛半磚", + "minecraft:cyan_wool_slab": "青藍色羊毛半磚", + "minecraft:light_blue_wool_slab": "淺藍色羊毛半磚", + "minecraft:blue_wool_slab": "藍色羊毛半磚", + "minecraft:purple_wool_slab": "紫色羊毛半磚", + "minecraft:magenta_wool_slab": "紫紅色羊毛半磚", + "minecraft:pink_wool_slab": "粉紅色羊毛半磚", + "minecraft:white_wool_double_slab": "白色羊毛雙半磚", + "minecraft:light_gray_wool_double_slab": "淺灰色羊毛雙半磚", + "minecraft:gray_wool_double_slab": "灰色羊毛雙半磚", + "minecraft:black_wool_double_slab": "黑色羊毛雙半磚", + "minecraft:brown_wool_double_slab": "啡色羊毛雙半磚", + "minecraft:red_wool_double_slab": "紅色羊毛雙半磚", + "minecraft:orange_wool_double_slab": "橙色羊毛雙半磚", + "minecraft:yellow_wool_double_slab": "黃色羊毛雙半磚", + "minecraft:lime_wool_double_slab": "淺綠色羊毛雙半磚", + "minecraft:green_wool_double_slab": "綠色羊毛雙半磚", + "minecraft:cyan_wool_double_slab": "青藍色羊毛雙半磚", + "minecraft:light_blue_wool_double_slab": "淺藍色羊毛雙半磚", + "minecraft:blue_wool_double_slab": "藍色羊毛雙半磚", + "minecraft:purple_wool_double_slab": "紫色羊毛雙半磚", + "minecraft:magenta_wool_double_slab": "紫紅色羊毛雙半磚", + "minecraft:pink_wool_double_slab": "粉紅色羊毛雙半磚", + "minecraft:shelf_mushroom": "層架蘑菇", + "minecraft:straw_bed": "禾稈牀" + }, + "item": { "minecraft:golden_apple": "金蘋果", "minecraft:enchanted_golden_apple": "附魔金蘋果", "minecraft:mushroom_stew": "蘑菇湯", "minecraft:bread": "麪包", "minecraft:porkchop": "生豬扒", "minecraft:cooked_porkchop": "熟豬扒", - "minecraft:cod": "鱈魚", - "minecraft:salmon": "三文魚", + "minecraft:cod": "生鱈魚", + "minecraft:salmon": "生三文魚", "minecraft:tropical_fish": "熱帶魚", - "minecraft:pufferfish": "雞泡魚", + "minecraft:pufferfish": "河豚", "minecraft:cooked_cod": "熟鱈魚", "minecraft:cooked_salmon": "熟三文魚", - "minecraft:dried_kelp": "干海帶", + "minecraft:dried_kelp": "乾海帶", "minecraft:cookie": "曲奇", "minecraft:melon_slice": "西瓜", "minecraft:beef": "生牛肉", "minecraft:cooked_beef": "牛扒", - "minecraft:chicken": "雞", + "minecraft:chicken": "生雞肉", "minecraft:cooked_chicken": "熟雞肉", "minecraft:rotten_flesh": "腐肉", "minecraft:spider_eye": "蜘蛛眼", @@ -1416,23 +1589,25 @@ "minecraft:poisonous_potato": "毒薯仔", "minecraft:golden_carrot": "金紅蘿蔔", "minecraft:pumpkin_pie": "南瓜批", - "minecraft:beetroot_soup": "甜菜湯", - "minecraft:sweet_berries": "甜漿果", - "minecraft:rabbit": "兔", + "minecraft:beetroot": "紅菜頭", + "minecraft:beetroot_soup": "紅菜頭湯", + "minecraft:sweet_berries": "甜莓", + "minecraft:rabbit": "生兔肉", "minecraft:cooked_rabbit": "熟兔肉", "minecraft:rabbit_stew": "兔肉湯", "minecraft:wheat_seeds": "種子", "minecraft:pumpkin_seeds": "南瓜種子", "minecraft:melon_seeds": "西瓜種子", - "minecraft:beetroot_seeds": "甜菜種子", + "minecraft:nether_wart": "地獄孢子", + "minecraft:beetroot_seeds": "紅菜頭種子", "minecraft:torchflower_seeds": "火炬花種子", "minecraft:pitcher_pod": "樽草莢果", - "minecraft:iron_shovel": "鐵鍬", + "minecraft:iron_shovel": "鐵鏟", "minecraft:iron_pickaxe": "鐵鎬", "minecraft:iron_axe": "鐵斧頭", "minecraft:flint_and_steel": "打火石", "minecraft:bow": "弓", - "minecraft:arrow": "射出的箭", + "minecraft:arrow": "箭", "minecraft:coal": "煤炭", "minecraft:charcoal": "木炭", "minecraft:diamond": "鑽石", @@ -1440,22 +1615,22 @@ "minecraft:gold_ingot": "金錠", "minecraft:iron_sword": "鐵劍", "minecraft:wooden_sword": "木劍", - "minecraft:wooden_shovel": "木鍬", + "minecraft:wooden_shovel": "木鏟", "minecraft:wooden_pickaxe": "木鎬", "minecraft:wooden_axe": "木斧頭", "minecraft:stone_sword": "石劍", - "minecraft:stone_shovel": "石鍬", + "minecraft:stone_shovel": "石鏟", "minecraft:stone_pickaxe": "石鎬", "minecraft:stone_axe": "石斧頭", "minecraft:diamond_sword": "鑽石劍", - "minecraft:diamond_shovel": "鑽石鍬", + "minecraft:diamond_shovel": "鑽石鏟", "minecraft:diamond_pickaxe": "鑽石鎬", "minecraft:diamond_axe": "鑽石斧頭", "minecraft:mace": "重錘", "minecraft:stick": "木棍", "minecraft:bowl": "碗", "minecraft:golden_sword": "金劍", - "minecraft:golden_shovel": "金鍬", + "minecraft:golden_shovel": "金鏟", "minecraft:golden_pickaxe": "金鎬", "minecraft:golden_axe": "金斧頭", "minecraft:string": "線", @@ -1466,14 +1641,15 @@ "minecraft:iron_hoe": "鐵鋤頭", "minecraft:diamond_hoe": "鑽石鋤頭", "minecraft:golden_hoe": "金鋤頭", + "minecraft:wheat": "小麥", "minecraft:leather_helmet": "皮革帽", "minecraft:leather_chestplate": "皮革衫", "minecraft:leather_leggings": "皮革褲", "minecraft:leather_boots": "皮革靴", - "minecraft:chainmail_helmet": "鐵鏈頭盔", - "minecraft:chainmail_chestplate": "鐵鏈胸甲", - "minecraft:chainmail_leggings": "鐵鏈護腳", - "minecraft:chainmail_boots": "鐵鏈靴", + "minecraft:chainmail_helmet": "鎖鏈頭盔", + "minecraft:chainmail_chestplate": "鎖鏈胸甲", + "minecraft:chainmail_leggings": "鎖鏈護腳", + "minecraft:chainmail_boots": "鎖鏈靴", "minecraft:iron_helmet": "鐵頭盔", "minecraft:iron_chestplate": "鐵胸甲", "minecraft:iron_leggings": "鐵護腳", @@ -1490,35 +1666,38 @@ "minecraft:flint": "燧石", "minecraft:painting": "畫", "minecraft:oak_sign": "橡木指示牌", + "minecraft:wooden_door": "橡木門", "minecraft:bucket": "鐵桶", "minecraft:milk_bucket": "鮮奶桶", "minecraft:water_bucket": "水桶", - "minecraft:lava_bucket": "熔岩桶", + "minecraft:lava_bucket": "熔巖桶", "minecraft:cod_bucket": "鱈魚桶", "minecraft:salmon_bucket": "三文魚桶", "minecraft:tropical_fish_bucket": "熱帶魚桶", - "minecraft:pufferfish_bucket": "雞泡魚桶", + "minecraft:pufferfish_bucket": "河豚桶", "minecraft:powder_snow_bucket": "幼雪桶", "minecraft:axolotl_bucket": "墨西哥蠑螈桶", "minecraft:minecart": "礦車", "minecraft:saddle": "鞍", + "minecraft:iron_door": "鐵門", "minecraft:redstone": "紅石粉", - "minecraft:snowball": "掟出的雪球", + "minecraft:snowball": "雪球", "minecraft:oak_boat": "橡木船", "minecraft:birch_boat": "樺木船", "minecraft:jungle_boat": "叢林木船", - "minecraft:spruce_boat": "雲杉木船", + "minecraft:spruce_boat": "杉木船", "minecraft:acacia_boat": "相思木船", "minecraft:dark_oak_boat": "黑橡木船", "minecraft:leather": "皮革", + "minecraft:kelp": "海帶", "minecraft:brick": "紅磚頭", "minecraft:clay_ball": "黏土", "minecraft:sugar_cane": "蔗", "minecraft:paper": "紙", "minecraft:book": "書", - "minecraft:slime_ball": "黏液球", - "minecraft:chest_minecart": "運輸礦車", - "minecraft:egg": "掟出的雞蛋", + "minecraft:slime_ball": "史萊姆球", + "minecraft:chest_minecart": "儲物箱礦車", + "minecraft:egg": "雞蛋", "minecraft:compass": "指南針", "minecraft:fishing_rod": "魚竿", "minecraft:clock": "時鐘", @@ -1530,12 +1709,12 @@ "minecraft:blue_dye": "藍色染料", "minecraft:purple_dye": "紫色染料", "minecraft:cyan_dye": "青藍色染料", - "minecraft:light_gray_dye": "淡灰色染料", + "minecraft:light_gray_dye": "淺灰色染料", "minecraft:gray_dye": "灰色染料", "minecraft:pink_dye": "粉紅色染料", - "minecraft:lime_dye": "黃綠色染料", + "minecraft:lime_dye": "淺綠色染料", "minecraft:yellow_dye": "黃色染料", - "minecraft:light_blue_dye": "淡藍色染料", + "minecraft:light_blue_dye": "淺藍色染料", "minecraft:magenta_dye": "紫紅色染料", "minecraft:orange_dye": "橙色染料", "minecraft:white_dye": "白色染料", @@ -1545,106 +1724,112 @@ "minecraft:lapis_lazuli": "青金石", "minecraft:bone": "骨頭", "minecraft:sugar": "糖", + "minecraft:cake": "蛋糕", + "minecraft:bed": "牀", "minecraft:repeater": "紅石中繼器", "minecraft:filled_map": "地圖", - "minecraft:shears": "鉸剪", - "minecraft:ender_pearl": "掟出的終界珍珠", + "minecraft:shears": "剪刀", + "minecraft:ender_pearl": "終界珍珠", "minecraft:blaze_rod": "烈焰棒", - "minecraft:ghast_tear": "惡魂之淚", + "minecraft:ghast_tear": "幽靈之淚", "minecraft:gold_nugget": "金粒", "minecraft:potion": "藥水", "minecraft:glass_bottle": "玻璃樽", - "minecraft:fermented_spider_eye": "發酵蛛眼", + "minecraft:fermented_spider_eye": "發酵蜘蛛眼", "minecraft:blaze_powder": "烈焰粉", - "minecraft:magma_cream": "岩漿膏", + "minecraft:magma_cream": "巖漿球", + "minecraft:brewing_stand": "釀造台", + "minecraft:cauldron": "鍋", "minecraft:ender_eye": "終界之眼", - "minecraft:glistering_melon_slice": "閃爍的西瓜片", - "minecraft:chicken_spawn_egg": "雞 生成蛋", - "minecraft:cow_spawn_egg": "牛 生成蛋", - "minecraft:pig_spawn_egg": "豬 生成蛋", - "minecraft:sheep_spawn_egg": "綿羊 生成蛋", - "minecraft:wolf_spawn_egg": "狼 生成蛋", - "minecraft:mooshroom_spawn_egg": "蘑菇牛 生成蛋", + "minecraft:glistering_melon_slice": "鑲金西瓜片", + "minecraft:chicken_spawn_egg": "雞生成蛋", + "minecraft:cow_spawn_egg": "牛生成蛋", + "minecraft:pig_spawn_egg": "豬生成蛋", + "minecraft:sheep_spawn_egg": "綿羊生成蛋", + "minecraft:wolf_spawn_egg": "狼生成蛋", + "minecraft:mooshroom_spawn_egg": "蘑菇牛生成蛋", "minecraft:creeper_spawn_egg": "Creeper 生成蛋", - "minecraft:enderman_spawn_egg": "終界使者 生成蛋", - "minecraft:silverfish_spawn_egg": "蠹魚 生成蛋", - "minecraft:skeleton_spawn_egg": "骷髏骨 生成蛋", - "minecraft:slime_spawn_egg": "史萊姆 生成蛋", - "minecraft:spider_spawn_egg": "蜘蛛 生成蛋", - "minecraft:zombie_spawn_egg": "喪屍 生成蛋", - "minecraft:zombie_pigman_spawn_egg": "喪屍豬人 生成蛋", - "minecraft:villager_spawn_egg": "村民 生成蛋", - "minecraft:squid_spawn_egg": "墨魚 生成蛋", - "minecraft:ocelot_spawn_egg": "豹貓 生成蛋", - "minecraft:witch_spawn_egg": "女巫 生成蛋", - "minecraft:bat_spawn_egg": "蝙蝠 生成蛋", - "minecraft:ghast_spawn_egg": "地獄幽靈 生成蛋", - "minecraft:magma_cube_spawn_egg": "岩漿史萊姆 生成蛋", - "minecraft:blaze_spawn_egg": "烈焰使者 生成蛋", - "minecraft:cave_spider_spawn_egg": "洞穴蜘蛛 生成蛋", - "minecraft:horse_spawn_egg": "馬 生成蛋", - "minecraft:rabbit_spawn_egg": "兔 生成蛋", - "minecraft:endermite_spawn_egg": "終界蟎 生成蛋", - "minecraft:guardian_spawn_egg": "深海守衞 生成蛋", - "minecraft:stray_spawn_egg": "流浪者 生成蛋", - "minecraft:bogged_spawn_egg": "沼骨 生成蛋", - "minecraft:husk_spawn_egg": "屍殼 生成蛋", - "minecraft:wither_skeleton_spawn_egg": "凋零骷髏骨 生成蛋", - "minecraft:donkey_spawn_egg": "驢 生成蛋", - "minecraft:mule_spawn_egg": "騾 生成蛋", - "minecraft:skeleton_horse_spawn_egg": "骷髏骨馬 生成蛋", - "minecraft:zombie_horse_spawn_egg": "喪屍馬 生成蛋", - "minecraft:shulker_spawn_egg": "界伏蚌 生成蛋", - "minecraft:npc_spawn_egg": "PC生成蛋", - "minecraft:elder_guardian_spawn_egg": "遠古深海守衞 生成蛋", - "minecraft:polar_bear_spawn_egg": "北極熊 生成蛋", - "minecraft:llama_spawn_egg": "羊駝 生成蛋", - "minecraft:vindicator_spawn_egg": "衞道士 生成蛋", - "minecraft:evoker_spawn_egg": "喚魔者 生成蛋", - "minecraft:vex_spawn_egg": "惱鬼 生成蛋", - "minecraft:zombie_villager_spawn_egg": "喪屍村民 生成蛋", - "minecraft:parrot_spawn_egg": "鸚鵡 生成蛋", - "minecraft:tropical_fish_spawn_egg": "熱帶魚 生成蛋", - "minecraft:cod_spawn_egg": "鱈魚 生成蛋", - "minecraft:pufferfish_spawn_egg": "雞泡魚 生成蛋", - "minecraft:salmon_spawn_egg": "三文魚 生成蛋", - "minecraft:drowned_spawn_egg": "沉屍 生成蛋", - "minecraft:dolphin_spawn_egg": "海豚 生成蛋", - "minecraft:turtle_spawn_egg": "海龜 生成蛋", - "minecraft:phantom_spawn_egg": "夜魅 生成蛋", + "minecraft:enderman_spawn_egg": "終界使者生成蛋", + "minecraft:silverfish_spawn_egg": "蠹魚生成蛋", + "minecraft:skeleton_spawn_egg": "骷髏骨生成蛋", + "minecraft:slime_spawn_egg": "史萊姆生成蛋", + "minecraft:spider_spawn_egg": "蜘蛛生成蛋", + "minecraft:zombie_spawn_egg": "喪屍生成蛋", + "minecraft:zombie_pigman_spawn_egg": "喪屍豬人生成蛋", + "minecraft:villager_spawn_egg": "村民生成蛋", + "minecraft:squid_spawn_egg": "墨魚生成蛋", + "minecraft:ocelot_spawn_egg": "豹貓生成蛋", + "minecraft:witch_spawn_egg": "女巫生成蛋", + "minecraft:bat_spawn_egg": "蝙蝠生成蛋", + "minecraft:ghast_spawn_egg": "地獄幽靈生成蛋", + "minecraft:magma_cube_spawn_egg": "巖漿史萊姆生成蛋", + "minecraft:blaze_spawn_egg": "烈焰使者生成蛋", + "minecraft:cave_spider_spawn_egg": "洞穴蜘蛛生成蛋", + "minecraft:horse_spawn_egg": "馬生成蛋", + "minecraft:rabbit_spawn_egg": "兔生成蛋", + "minecraft:endermite_spawn_egg": "終界蟎生成蛋", + "minecraft:guardian_spawn_egg": "深海守衞生成蛋", + "minecraft:stray_spawn_egg": "流浪者生成蛋", + "minecraft:bogged_spawn_egg": "沼骨生成蛋", + "minecraft:husk_spawn_egg": "屍殼生成蛋", + "minecraft:wither_skeleton_spawn_egg": "凋零骷髏骨生成蛋", + "minecraft:donkey_spawn_egg": "驢生成蛋", + "minecraft:mule_spawn_egg": "騾生成蛋", + "minecraft:skeleton_horse_spawn_egg": "骷髏骨馬生成蛋", + "minecraft:zombie_horse_spawn_egg": "喪屍馬生成蛋", + "minecraft:shulker_spawn_egg": "界伏蚌生成蛋", + "minecraft:npc_spawn_egg": "NPC生成蛋", + "minecraft:elder_guardian_spawn_egg": "遠古深海守衞生成蛋", + "minecraft:polar_bear_spawn_egg": "北極熊生成蛋", + "minecraft:llama_spawn_egg": "羊駝生成蛋", + "minecraft:vindicator_spawn_egg": "衞道士生成蛋", + "minecraft:evoker_spawn_egg": "喚魔者生成蛋", + "minecraft:vex_spawn_egg": "惱鬼生成蛋", + "minecraft:zombie_villager_spawn_egg": "喪屍村民生成蛋", + "minecraft:parrot_spawn_egg": "鸚鵡生成蛋", + "minecraft:tropical_fish_spawn_egg": "熱帶魚生成蛋", + "minecraft:cod_spawn_egg": "鱈魚生成蛋", + "minecraft:pufferfish_spawn_egg": "河豚生成蛋", + "minecraft:salmon_spawn_egg": "三文魚生成蛋", + "minecraft:drowned_spawn_egg": "沉屍生成蛋", + "minecraft:dolphin_spawn_egg": "海豚生成蛋", + "minecraft:turtle_spawn_egg": "海龜生成蛋", + "minecraft:phantom_spawn_egg": "夜魅生成蛋", "minecraft:agent_spawn_egg": "智能體生成蛋", - "minecraft:cat_spawn_egg": "貓 生成蛋", - "minecraft:panda_spawn_egg": "熊貓 生成蛋", - "minecraft:fox_spawn_egg": "狐狸 生成蛋", - "minecraft:pillager_spawn_egg": "掠奪者 生成蛋", - "minecraft:wandering_trader_spawn_egg": "流浪商人 生成蛋", - "minecraft:ravager_spawn_egg": "劫毀獸 生成蛋", - "minecraft:bee_spawn_egg": "蜜蜂 生成蛋", - "minecraft:strider_spawn_egg": "熾足獸 生成蛋", - "minecraft:hoglin_spawn_egg": "野豬獸 生成蛋", - "minecraft:piglin_spawn_egg": "豬人 生成蛋", - "minecraft:zoglin_spawn_egg": "豬屍獸 生怪蛋", - "minecraft:piglin_brute_spawn_egg": "殘暴豬人 生成蛋", - "minecraft:sniffer_spawn_egg": "嗅探獸 生成蛋", - "minecraft:breeze_spawn_egg": "旋風使者 生成蛋", - "minecraft:axolotl_spawn_egg": "墨西哥蠑螈 生成蛋", - "minecraft:goat_spawn_egg": "山羊 生成蛋", - "minecraft:glow_squid_spawn_egg": "螢光墨魚 生成蛋", - "minecraft:iron_golem_spawn_egg": "鐵人 生成蛋", - "minecraft:snow_golem_spawn_egg": "雪人 生成蛋", - "minecraft:ender_dragon_spawn_egg": "終界龍 生成蛋", - "minecraft:wither_spawn_egg": "凋零怪 生成蛋", - "minecraft:glow_ink_sac": "螢光墨囊", + "minecraft:cat_spawn_egg": "貓生成蛋", + "minecraft:panda_spawn_egg": "熊貓生成蛋", + "minecraft:fox_spawn_egg": "狐狸生成蛋", + "minecraft:pillager_spawn_egg": "掠奪者生成蛋", + "minecraft:wandering_trader_spawn_egg": "流浪商人生成蛋", + "minecraft:ravager_spawn_egg": "劫毀獸生成蛋", + "minecraft:bee_spawn_egg": "蜜蜂生成蛋", + "minecraft:strider_spawn_egg": "熾足獸生成蛋", + "minecraft:hoglin_spawn_egg": "野豬獸生成蛋", + "minecraft:piglin_spawn_egg": "豬人生成蛋", + "minecraft:zoglin_spawn_egg": "豬屍獸生成蛋", + "minecraft:piglin_brute_spawn_egg": "殘暴豬人生成蛋", + "minecraft:sniffer_spawn_egg": "嗅探獸生成蛋", + "minecraft:breeze_spawn_egg": "旋風使者生成蛋", + "minecraft:axolotl_spawn_egg": "墨西哥蠑螈生成蛋", + "minecraft:goat_spawn_egg": "山羊生成蛋", + "minecraft:glow_squid_spawn_egg": "熒光墨魚生成蛋", + "minecraft:iron_golem_spawn_egg": "鐵人生成蛋", + "minecraft:snow_golem_spawn_egg": "雪人生成蛋", + "minecraft:ender_dragon_spawn_egg": "終界龍生成蛋", + "minecraft:wither_spawn_egg": "凋零怪生成蛋", + "minecraft:glow_ink_sac": "熒光墨囊", "minecraft:copper_ingot": "銅錠", "minecraft:raw_iron": "粗鐵", "minecraft:raw_gold": "粗金", "minecraft:raw_copper": "粗銅", "minecraft:experience_bottle": "附魔之瓶", "minecraft:fire_charge": "火焰彈", - "minecraft:writable_book": "書與筆", + "minecraft:writable_book": "書與羽毛筆", "minecraft:written_book": "寫好的書", "minecraft:emerald": "綠寶石", - "minecraft:empty_map": "空地圖", + "minecraft:frame": "物品展示框", + "minecraft:flower_pot": "花盆", + "minecraft:empty_map": "空白地圖", "minecraft:carrot_on_a_stick": "紅蘿蔔魚竿", "minecraft:nether_star": "地獄之星", "minecraft:firework_rocket": "煙花", @@ -1653,71 +1838,79 @@ "minecraft:comparator": "紅石比較器", "minecraft:netherbrick": "地獄磚頭", "minecraft:quartz": "地獄石英", - "minecraft:tnt_minecart": "TT礦車", + "minecraft:tnt_minecart": "TNT 礦車", "minecraft:hopper_minecart": "漏斗礦車", + "minecraft:hopper": "漏斗", "minecraft:rabbit_foot": "兔腳", "minecraft:rabbit_hide": "兔皮", "minecraft:leather_horse_armor": "皮革馬甲", "minecraft:iron_horse_armor": "鐵馬甲", "minecraft:gold_horse_armor": "金馬甲", "minecraft:diamond_horse_armor": "鑽石馬甲", - "minecraft:music_disc_13": "音樂唱片 (13)", - "minecraft:music_disc_cat": "音樂唱片 (cat)", - "minecraft:music_disc_blocks": "音樂唱片 (blocks)", - "minecraft:music_disc_chirp": "音樂唱片 (chirp)", - "minecraft:music_disc_far": "音樂唱片 (far)", - "minecraft:music_disc_mall": "音樂唱片 (mall)", - "minecraft:music_disc_mellohi": "音樂唱片 (mellohi)", - "minecraft:music_disc_stal": "音樂唱片 (stal)", - "minecraft:music_disc_strad": "音樂唱片 (strad)", - "minecraft:music_disc_ward": "音樂唱片 (ward)", - "minecraft:music_disc_11": "音樂唱片 (11)", - "minecraft:music_disc_wait": "音樂唱片 (wait)", + "minecraft:music_disc_13": "唱片13", + "minecraft:music_disc_cat": "唱片cat", + "minecraft:music_disc_blocks": "唱片blocks", + "minecraft:music_disc_chirp": "唱片chirp", + "minecraft:music_disc_far": "唱片far", + "minecraft:music_disc_mall": "唱片mall", + "minecraft:music_disc_mellohi": "唱片mellohi", + "minecraft:music_disc_stal": "唱片stal", + "minecraft:music_disc_strad": "唱片strad", + "minecraft:music_disc_ward": "唱片ward", + "minecraft:music_disc_11": "唱片11", + "minecraft:music_disc_wait": "唱片wait", "minecraft:trident": "三叉戟", "minecraft:lead": "牽繩", "minecraft:name_tag": "命名牌", - "minecraft:prismarine_crystals": "海晶砂粒", + "minecraft:prismarine_crystals": "海磷晶體", "minecraft:mutton": "生羊肉", "minecraft:cooked_mutton": "熟羊肉", - "minecraft:armor_stand": "盔甲架", - "minecraft:chorus_fruit": "紫頌果", - "minecraft:popped_chorus_fruit": "爆開嘅歌萊果", - "minecraft:dragon_breath": "龍息", - "minecraft:splash_potion": "掟出的噴濺藥水", - "minecraft:lingering_potion": "掟出的滯留藥水", + "minecraft:armor_stand": "盔甲座", + "minecraft:spruce_door": "杉木門", + "minecraft:birch_door": "樺木門", + "minecraft:jungle_door": "叢林木門", + "minecraft:acacia_door": "相思木門", + "minecraft:dark_oak_door": "黑橡木門", + "minecraft:chorus_fruit": "歌萊果", + "minecraft:popped_chorus_fruit": "爆開的歌萊果", + "minecraft:dragon_breath": "龍之吐息", + "minecraft:splash_potion": "飛濺藥水", + "minecraft:lingering_potion": "滯留藥水", "minecraft:command_block_minecart": "命令方塊礦車", "minecraft:elytra": "鞘翅", - "minecraft:prismarine_shard": "海晶碎片", - "minecraft:shulker_shell": "潛影殼", + "minecraft:prismarine_shard": "海磷碎片", + "minecraft:shulker_shell": "界伏殼", "minecraft:banner": "橫額", "minecraft:totem_of_undying": "不死圖騰", "minecraft:iron_nugget": "鐵粒", "minecraft:nautilus_shell": "鸚鵡螺殼", "minecraft:heart_of_the_sea": "海洋之心", - "minecraft:scute": "鱗甲", + "minecraft:scute": "海龜鱗甲", "minecraft:turtle_helmet": "海龜殼", "minecraft:phantom_membrane": "夜魅膜", "minecraft:crossbow": "弩", - "minecraft:spruce_sign": "雲杉木指示牌", + "minecraft:spruce_sign": "杉木指示牌", "minecraft:birch_sign": "樺木指示牌", "minecraft:jungle_sign": "叢林木指示牌", "minecraft:acacia_sign": "相思木指示牌", "minecraft:dark_oak_sign": "黑橡木指示牌", - "minecraft:flower_banner_pattern": "花朵圖案橫額圖案", + "minecraft:flower_banner_pattern": "花朵橫額圖案", "minecraft:creeper_banner_pattern": "Creeper 橫額圖案", - "minecraft:skull_banner_pattern": "頭顱圖案橫額圖案", + "minecraft:skull_banner_pattern": "骷髏骨橫額圖案", "minecraft:mojang_banner_pattern": "Mojang 標誌橫額圖案", - "minecraft:field_masoned_banner_pattern": "磚紋橫額圖案", - "minecraft:bordure_indented_banner_pattern": "波紋邊橫額圖案", + "minecraft:field_masoned_banner_pattern": "磚牆花紋橫額圖案", + "minecraft:bordure_indented_banner_pattern": "鋸齒邊橫額圖案", "minecraft:piglin_banner_pattern": "豬鼻橫額圖案", "minecraft:globe_banner_pattern": "地球橫額圖案", "minecraft:flow_banner_pattern": "渦流橫額圖案", "minecraft:guster_banner_pattern": "旋風橫額圖案", - "minecraft:suspicious_stew": "可疑嘅燉湯", + "minecraft:campfire": "營火", + "minecraft:suspicious_stew": "可疑的燉湯", "minecraft:honeycomb": "蜂巢蜜", "minecraft:honey_bottle": "蜜糖樽", "minecraft:ominous_bottle": "不祥之瓶", "minecraft:board": "黑板", + "minecraft:camera": "攝像機", "minecraft:compound": "化合物", "minecraft:ice_bomb": "冰彈", "minecraft:bleach": "漂白劑", @@ -1728,7 +1921,7 @@ "minecraft:glow_stick": "熒光棒", "minecraft:lodestone_compass": "磁石指南針", "minecraft:netherite_sword": "地獄合金劍", - "minecraft:netherite_shovel": "地獄合金鍬", + "minecraft:netherite_shovel": "地獄合金鏟", "minecraft:netherite_pickaxe": "地獄合金鎬", "minecraft:netherite_axe": "地獄合金斧頭", "minecraft:netherite_hoe": "地獄合金鋤頭", @@ -1740,39 +1933,46 @@ "minecraft:netherite_scrap": "地獄合金碎片", "minecraft:crimson_sign": "猩紅菌木指示牌", "minecraft:warped_sign": "迷離菌木指示牌", + "minecraft:crimson_door": "猩紅菌木門", + "minecraft:warped_door": "迷離菌木門", "minecraft:warped_fungus_on_a_stick": "迷離菌菇魚竿", - "minecraft:music_disc_pigstep": "音樂唱片 (Pigstep)", + "minecraft:music_disc_pigstep": "唱片Pigstep", + "minecraft:nether_sprouts": "地獄芽", + "minecraft:soul_campfire": "靈魂營火", + "minecraft:glow_frame": "熒光物品展示框", "minecraft:amethyst_shard": "紫水晶碎片", "minecraft:spyglass": "望遠鏡", - "minecraft:music_disc_otherside": "音樂唱片 (otherside)", + "minecraft:music_disc_otherside": "唱片otherside", "minecraft:goat_horn": "山羊角", - "minecraft:frog_spawn_egg": "青蛙 生成蛋", - "minecraft:tadpole_spawn_egg": "蝌蚪 生成蛋", + "minecraft:frog_spawn_egg": "青蛙生成蛋", + "minecraft:tadpole_spawn_egg": "蝌蚪生成蛋", "minecraft:tadpole_bucket": "蝌蚪桶", - "minecraft:allay_spawn_egg": "悦靈 生成蛋", - "minecraft:warden_spawn_egg": "沉靈守衞 生成蛋", + "minecraft:allay_spawn_egg": "悦靈生成蛋", + "minecraft:warden_spawn_egg": "沉靈守衞生成蛋", + "minecraft:mangrove_door": "紅樹木門", "minecraft:mangrove_sign": "紅樹木指示牌", "minecraft:mangrove_boat": "紅樹木船", - "minecraft:music_disc_5": "音樂唱片 (5)", + "minecraft:music_disc_5": "唱片5", "minecraft:disc_fragment_5": "唱片殘片(5)", - "minecraft:oak_chest_boat": "橡木運輸船", - "minecraft:birch_chest_boat": "樺木運輸船", - "minecraft:jungle_chest_boat": "叢林木運輸船", - "minecraft:spruce_chest_boat": "雲杉木運輸船", - "minecraft:acacia_chest_boat": "相思木運輸船", - "minecraft:dark_oak_chest_boat": "黑橡木運輸船", - "minecraft:mangrove_chest_boat": "紅樹木運輸船", - "minecraft:chest_boat": "運輸船", - "minecraft:recovery_compass": "追溯指針", + "minecraft:oak_chest_boat": "儲物箱橡木船", + "minecraft:birch_chest_boat": "儲物箱樺木船", + "minecraft:jungle_chest_boat": "儲物箱叢林木船", + "minecraft:spruce_chest_boat": "儲物箱杉木船", + "minecraft:acacia_chest_boat": "儲物箱相思木船", + "minecraft:dark_oak_chest_boat": "儲物箱黑橡木船", + "minecraft:mangrove_chest_boat": "儲物箱紅樹木船", + "minecraft:chest_boat": "儲物箱船", + "minecraft:recovery_compass": "追溯指南針", "minecraft:echo_shard": "迴響碎片", - "minecraft:trader_llama_spawn_egg": "商隊羊駝 生成蛋", + "minecraft:trader_llama_spawn_egg": "商隊羊駝生成蛋", "minecraft:cherry_boat": "櫻花木船", - "minecraft:cherry_chest_boat": "櫻花木運輸船", + "minecraft:cherry_chest_boat": "儲物箱櫻花木船", "minecraft:cherry_sign": "櫻花木指示牌", "minecraft:bamboo_sign": "竹指示牌", "minecraft:bamboo_raft": "竹筏", - "minecraft:bamboo_chest_raft": "運輸竹筏", - "minecraft:camel_spawn_egg": "生成駱駝", + "minecraft:bamboo_chest_raft": "儲物箱竹筏", + "minecraft:camel_spawn_egg": "駱駝生成蛋", + "minecraft:camel_husk_spawn_egg": "駱駝屍殼生成蛋", "minecraft:angler_pottery_sherd": "垂釣陶罐碎片", "minecraft:archer_pottery_sherd": "弓箭陶罐碎片", "minecraft:arms_up_pottery_sherd": "舉臂陶罐碎片", @@ -1796,86 +1996,185 @@ "minecraft:shelter_pottery_sherd": "樹蔭陶罐碎片", "minecraft:skull_pottery_sherd": "頭顱陶罐碎片", "minecraft:snort_pottery_sherd": "嗅探陶罐碎片", - "minecraft:brush": "刷子", - "minecraft:netherite_upgrade_smithing_template": "地獄合金升級鍛造模板", - "minecraft:sentry_armor_trim_smithing_template": "哨兵盔甲紋飾鍛造模板", - "minecraft:dune_armor_trim_smithing_template": "沙丘盔甲紋飾鍛造模板", - "minecraft:coast_armor_trim_smithing_template": "海岸盔甲紋飾鍛造模板", - "minecraft:wild_armor_trim_smithing_template": "荒野盔甲紋飾鍛造模板", - "minecraft:ward_armor_trim_smithing_template": "守衞盔甲紋飾鍛造模板", - "minecraft:eye_armor_trim_smithing_template": "眼眸盔甲紋飾鍛造模板", - "minecraft:vex_armor_trim_smithing_template": "惱鬼盔甲紋飾鍛造模板", - "minecraft:tide_armor_trim_smithing_template": "潮汐盔甲紋飾鍛造模板", - "minecraft:snout_armor_trim_smithing_template": "豬鼻盔甲紋飾鍛造模板", - "minecraft:rib_armor_trim_smithing_template": "肋骨盔甲紋飾鍛造模板", - "minecraft:spire_armor_trim_smithing_template": "尖塔盔甲紋飾鍛造模板", - "minecraft:silence_armor_trim_smithing_template": "沉靜盔甲紋飾鍛造模板", - "minecraft:wayfinder_armor_trim_smithing_template": "嚮導盔甲紋飾鍛造模板", - "minecraft:raiser_armor_trim_smithing_template": "牧民盔甲紋飾鍛造模板", - "minecraft:shaper_armor_trim_smithing_template": "塑造盔甲紋飾鍛造模板", - "minecraft:host_armor_trim_smithing_template": "僱主盔甲紋飾鍛造模板", - "minecraft:flow_armor_trim_smithing_template": "渦流盔甲紋飾鍛造模板", - "minecraft:bolt_armor_trim_smithing_template": "緊固盔甲紋飾鍛造模板", - "minecraft:music_disc_relic": "音樂唱片 (Reilc)", + "minecraft:brush": "毛刷", + "minecraft:netherite_upgrade_smithing_template": "地獄合金升級", + "minecraft:sentry_armor_trim_smithing_template": "哨兵盔甲紋飾", + "minecraft:dune_armor_trim_smithing_template": "沙丘盔甲紋飾", + "minecraft:coast_armor_trim_smithing_template": "海岸盔甲紋飾", + "minecraft:wild_armor_trim_smithing_template": "荒野盔甲紋飾", + "minecraft:ward_armor_trim_smithing_template": "守衞盔甲紋飾", + "minecraft:eye_armor_trim_smithing_template": "眼眸盔甲紋飾", + "minecraft:vex_armor_trim_smithing_template": "惱鬼盔甲紋飾", + "minecraft:tide_armor_trim_smithing_template": "潮汐盔甲紋飾", + "minecraft:snout_armor_trim_smithing_template": "豬鼻盔甲紋飾", + "minecraft:rib_armor_trim_smithing_template": "肋骨盔甲紋飾", + "minecraft:spire_armor_trim_smithing_template": "尖塔盔甲紋飾", + "minecraft:silence_armor_trim_smithing_template": "沉靜盔甲紋飾", + "minecraft:wayfinder_armor_trim_smithing_template": "嚮導盔甲紋飾", + "minecraft:raiser_armor_trim_smithing_template": "牧民盔甲紋飾", + "minecraft:shaper_armor_trim_smithing_template": "塑造盔甲紋飾", + "minecraft:host_armor_trim_smithing_template": "僱主盔甲紋飾", + "minecraft:flow_armor_trim_smithing_template": "渦流盔甲紋飾", + "minecraft:bolt_armor_trim_smithing_template": "緊固盔甲紋飾", + "minecraft:music_disc_relic": "唱片Relic", "minecraft:skull": "生物頭顱", "minecraft:stained_hardened_clay": "釉陶", - "minecraft:armadillo_spawn_egg": "犰狳 生成蛋", + "minecraft:armadillo_spawn_egg": "犰狳生成蛋", "minecraft:armadillo_scute": "犰狳鱗甲", "minecraft:wolf_armor": "狼甲", "minecraft:pale_oak_boat": "蒼白橡木船", - "minecraft:pale_oak_chest_boat": "蒼白橡木運輸船", + "minecraft:pale_oak_chest_boat": "儲物箱蒼白橡木船", "minecraft:pale_oak_sign": "蒼白橡木指示牌", - "minecraft:creaking_spawn_egg": "異響樹妖 生成蛋", - "minecraft:resin_brick": "樹脂磚", + "minecraft:creaking_spawn_egg": "異響樹妖生成蛋", + "minecraft:resin_brick": "樹脂磚頭", + "minecraft:blue_egg": "藍色雞蛋", + "minecraft:brown_egg": "啡色雞蛋", + "minecraft:happy_ghast_spawn_egg": "快樂幽靈生成蛋", + "minecraft:black_harness": "黑色牽引器具", + "minecraft:blue_harness": "藍色牽引器具", + "minecraft:brown_harness": "啡色牽引器具", + "minecraft:cyan_harness": "青藍色牽引器具", + "minecraft:gray_harness": "灰色牽引器具", + "minecraft:green_harness": "綠色牽引器具", + "minecraft:light_blue_harness": "淺藍色牽引器具", + "minecraft:light_gray_harness": "淺灰色牽引器具", + "minecraft:lime_harness": "淺綠色牽引器具", + "minecraft:magenta_harness": "紫紅色牽引器具", + "minecraft:orange_harness": "橙色牽引器具", + "minecraft:pink_harness": "粉紅色牽引器具", + "minecraft:purple_harness": "紫色牽引器具", + "minecraft:red_harness": "紅色牽引器具", + "minecraft:white_harness": "白色牽引器具", + "minecraft:yellow_harness": "黃色牽引器具", + "minecraft:copper_golem_spawn_egg": "銅人生成蛋", + "minecraft:copper_sword": "銅劍", + "minecraft:copper_shovel": "銅鏟", + "minecraft:copper_pickaxe": "銅鎬", + "minecraft:copper_axe": "銅斧頭", + "minecraft:copper_hoe": "銅鋤頭", + "minecraft:copper_helmet": "銅頭盔", + "minecraft:copper_chestplate": "銅胸甲", + "minecraft:copper_leggings": "銅護腳", + "minecraft:copper_boots": "銅靴", + "minecraft:copper_nugget": "銅粒", + "minecraft:copper_horse_armor": "銅馬甲", + "minecraft:nautilus_spawn_egg": "鸚鵡螺生成蛋", + "minecraft:zombie_nautilus_spawn_egg": "喪屍鸚鵡螺生成蛋", + "minecraft:parched_spawn_egg": "旱骨生成蛋", + "minecraft:copper_nautilus_armor": "銅鸚鵡螺甲", + "minecraft:iron_nautilus_armor": "鐵鸚鵡螺甲", + "minecraft:golden_nautilus_armor": "金鸚鵡螺甲", + "minecraft:diamond_nautilus_armor": "鑽石鸚鵡螺甲", + "minecraft:netherite_nautilus_armor": "地獄合金鸚鵡螺甲", + "minecraft:netherite_horse_armor": "地獄合金馬甲", + "minecraft:sulfur_cube_spawn_egg": "硫磺史萊姆生成蛋", + "minecraft:sulfur_cube_bucket": "硫磺史萊姆桶", + "minecraft:poplar_boat": "楊木船", + "minecraft:poplar_chest_boat": "儲物箱楊木船", + "minecraft:poplar_sign": "楊木指示牌", + "minecraft:straw_bed": "禾稈牀", + "minecraft:black_cushion": "黑色咕臣", + "minecraft:blue_cushion": "藍色咕臣", + "minecraft:brown_cushion": "啡色咕臣", + "minecraft:cyan_cushion": "青藍色咕臣", + "minecraft:gray_cushion": "灰色咕臣", + "minecraft:green_cushion": "綠色咕臣", + "minecraft:light_blue_cushion": "淺藍色咕臣", + "minecraft:light_gray_cushion": "淺灰色咕臣", + "minecraft:lime_cushion": "淺綠色咕臣", + "minecraft:magenta_cushion": "紫紅色咕臣", + "minecraft:orange_cushion": "橙色咕臣", + "minecraft:pink_cushion": "粉紅色咕臣", + "minecraft:purple_cushion": "紫色咕臣", + "minecraft:red_cushion": "紅色咕臣", + "minecraft:white_cushion": "白色咕臣", + "minecraft:yellow_cushion": "黃色咕臣", "minecraft:wool": "羊毛", "minecraft:carpet": "地氈", "minecraft:log": "原木", "minecraft:fence": "欄杆", "minecraft:stonebrick": "石磚", - "minecraft:coral_block": "珊瑚塊", - "minecraft:stone_block_slab": "石頭半磚", - "minecraft:stone_block_slab2": "石頭半磚2", - "minecraft:stone_block_slab3": "石頭半磚3", - "minecraft:stone_block_slab4": "石頭半磚4", - "minecraft:double_stone_block_slab": "雙石頭半磚", - "minecraft:double_stone_block_slab2": "雙石頭半磚2", - "minecraft:double_stone_block_slab3": "雙石頭半磚3", - "minecraft:double_stone_block_slab4": "雙石頭半磚4", - "minecraft:coral_fan": "珊瑚扇", - "minecraft:coral_fan_dead": "死亡珊瑚扇", + "minecraft:coral_block": "珊瑚方塊", + "minecraft:stone_block_slab": "石半磚", + "minecraft:stone_block_slab2": "石半磚2", + "minecraft:stone_block_slab3": "石半磚3", + "minecraft:stone_block_slab4": "石半磚4", + "minecraft:double_stone_block_slab": "雙石半磚", + "minecraft:double_stone_block_slab2": "雙石半磚2", + "minecraft:double_stone_block_slab3": "雙石半磚3", + "minecraft:double_stone_block_slab4": "雙石半磚4", + "minecraft:coral_fan": "扇狀珊瑚", + "minecraft:coral_fan_dead": "死亡的扇狀珊瑚", "minecraft:sapling": "樹苗", "minecraft:leaves": "樹葉", - "minecraft:leaves2": "樹葉", - "minecraft:wooden_slab": "木質台階", + "minecraft:leaves2": "樹葉2", + "minecraft:wooden_slab": "木製半磚", "minecraft:red_flower": "小型花", "minecraft:double_plant": "大型花", - "minecraft:planks": "Plank", + "minecraft:planks": "木板", "minecraft:coral": "珊瑚", "minecraft:tallgrass": "長草叢", - "minecraft:log2": "原木", - "minecraft:monster_egg": "蟲蝕方塊", + "minecraft:log2": "原木2", + "minecraft:monster_egg": "蛀蝕的方塊", "minecraft:concrete": "混凝土", "minecraft:concrete_powder": "混凝土粉末", "minecraft:stained_glass": "染色玻璃", "minecraft:stained_glass_pane": "染色玻璃片", - "minecraft:shulker_box": "染色的潛影盒", + "minecraft:shulker_box": "染色的界伏盒", "minecraft:wood": "木頭", - "minecraft:music_disc_creator": "音樂唱片 (Creator)", - "minecraft:music_disc_creator_music_box": "音樂唱片 (Creator 八音盒)", - "minecraft:music_disc_precipice": "音樂唱片 (Precipice)", - "minecraft:chemistry_table": "化學設備", + "minecraft:music_disc_creator": "唱片Creator", + "minecraft:music_disc_creator_music_box": "唱片Creator(音樂盒)", + "minecraft:music_disc_precipice": "唱片Precipice", + "minecraft:music_disc_tears": "唱片Tears", + "minecraft:music_disc_lava_chicken": "唱片Lava Chicken", + "minecraft:music_disc_bounce": "唱片Bounce", + "minecraft:chemistry_table": "化學裝置", "minecraft:hard_stained_glass": "染色強化玻璃", - "minecraft:hard_stained_glass_pane": "染色強化玻璃板", + "minecraft:hard_stained_glass_pane": "染色強化玻璃片", "minecraft:colored_torch_rg": "紅色和綠色火炬", "minecraft:colored_torch_bp": "藍色和紫色火炬", - "minecraft:light_block": "光源方塊", + "minecraft:light_block": "光", "minecraft:boat": "船", "minecraft:dye": "染料", "minecraft:banner_pattern": "橫額圖案", "minecraft:spawn_egg": "生成蛋", + "minecraft:red_shrub": "紅灌木", + "minecraft:shelf_mushroom": "層架蘑菇", "minecraft:end_crystal": "終界水晶", - "minecraft:glow_berries": "發光漿果", + "minecraft:copper_spear": "銅矛", + "minecraft:diamond_spear": "鑽石矛", + "minecraft:golden_spear": "金矛", + "minecraft:iron_spear": "鐵矛", + "minecraft:netherite_spear": "地獄合金矛", + "minecraft:stone_spear": "石矛", + "minecraft:wooden_spear": "木矛", + "minecraft:black_bundle": "黑色收納袋", + "minecraft:blue_bundle": "藍色收納袋", + "minecraft:brown_bundle": "啡色收納袋", + "minecraft:bundle": "收納袋", + "minecraft:cyan_bundle": "青藍色收納袋", + "minecraft:gray_bundle": "灰色收納袋", + "minecraft:green_bundle": "綠色收納袋", + "minecraft:light_blue_bundle": "淺藍色收納袋", + "minecraft:light_gray_bundle": "淺灰色收納袋", + "minecraft:lime_bundle": "淺綠色收納袋", + "minecraft:magenta_bundle": "紫紅色收納袋", + "minecraft:orange_bundle": "橙色收納袋", + "minecraft:pink_bundle": "粉紅色收納袋", + "minecraft:purple_bundle": "紫色收納袋", + "minecraft:red_bundle": "紅色收納袋", + "minecraft:white_bundle": "白色收納袋", + "minecraft:yellow_bundle": "黃色收納袋", + "minecraft:breeze_rod": "旋風棒", + "minecraft:ominous_trial_key": "不祥試煉鎖匙", + "minecraft:trial_key": "試煉鎖匙", + "minecraft:wind_charge": "風彈", + "minecraft:apple": "蘋果", + "minecraft:glow_berries": "熒光莓" + }, + "entity": { "minecraft:undefined_test_only": "Undefined Test Only", + "minecraft:chicken": "雞", "minecraft:cow": "牛", "minecraft:pig": "豬", "minecraft:sheep": "綿羊", @@ -1883,9 +2182,10 @@ "minecraft:villager": "舊版村民", "minecraft:mooshroom": "蘑菇牛", "minecraft:squid": "墨魚", + "minecraft:rabbit": "兔", "minecraft:bat": "蝙蝠", "minecraft:iron_golem": "鐵人", - "minecraft:snow_golem": "雪傀儡", + "minecraft:snow_golem": "雪人", "minecraft:ocelot": "豹貓", "minecraft:horse": "馬", "minecraft:donkey": "驢", @@ -1901,35 +2201,39 @@ "minecraft:skeleton": "骷髏骨", "minecraft:spider": "蜘蛛", "minecraft:zombie_pigman": "喪屍豬人", + "minecraft:slime": "史萊姆", "minecraft:enderman": "終界使者", - "minecraft:silverfish": "蠹蟲", + "minecraft:silverfish": "蠹魚", "minecraft:cave_spider": "洞穴蜘蛛", - "minecraft:ghast": "惡魂", - "minecraft:magma_cube": "岩漿史萊姆", - "minecraft:blaze": "烈焰人", - "minecraft:zombie_villager": "喪屍村民", + "minecraft:ghast": "地獄幽靈", + "minecraft:magma_cube": "巖漿史萊姆", + "minecraft:blaze": "烈焰使者", + "minecraft:zombie_villager": "舊版喪屍村民", "minecraft:witch": "女巫", "minecraft:stray": "流浪者", "minecraft:husk": "屍殼", "minecraft:wither_skeleton": "凋零骷髏骨", - "minecraft:guardian": "守衞者", - "minecraft:elder_guardian": "遠古守衞者", - "minecraft:npc": "PC", - "minecraft:wither": "凋靈", + "minecraft:guardian": "深海守衞", + "minecraft:elder_guardian": "遠古深海守衞", + "minecraft:npc": "NPC", + "minecraft:wither": "凋零怪", "minecraft:ender_dragon": "終界龍", - "minecraft:shulker": "潛影貝", + "minecraft:shulker": "界伏蚌", "minecraft:endermite": "終界蟎", "minecraft:agent": "智能體", "minecraft:vindicator": "衞道士", "minecraft:phantom": "夜魅", - "minecraft:ravager": "劫掠獸", + "minecraft:ravager": "劫毀獸", + "minecraft:armor_stand": "盔甲座", "minecraft:tripod_camera": "攝像機", "minecraft:player": "玩家", "minecraft:item": "跌落物", - "minecraft:falling_block": "下落的方塊", - "minecraft:xp_bottle": "掟出的附魔之瓶", + "minecraft:tnt": "點燃的TNT", + "minecraft:falling_block": "跌落的方塊", + "minecraft:moving_block": "移動的活塞", + "minecraft:xp_bottle": "扔出的附魔之瓶", "minecraft:xp_orb": "經驗球", - "minecraft:eye_of_ender_signal": "掟出的終界之眼", + "minecraft:eye_of_ender_signal": "扔出的終界之眼", "minecraft:ender_crystal": "終界水晶", "minecraft:fireworks_rocket": "煙花", "minecraft:thrown_trident": "三叉戟", @@ -1937,26 +2241,46 @@ "minecraft:cat": "流浪貓", "minecraft:shulker_bullet": "界伏蚌飛彈", "minecraft:fishing_hook": "浮標", + "minecraft:chalkboard": "黑板", "minecraft:dragon_fireball": "終界龍火球", - "minecraft:fireball": "惡魂火球", + "minecraft:arrow": "射出的箭", + "minecraft:snowball": "扔出的雪球", + "minecraft:egg": "扔出的雞蛋", + "minecraft:painting": "畫", + "minecraft:minecart": "礦車", + "minecraft:fireball": "地獄幽靈火球", + "minecraft:splash_potion": "扔出的飛濺藥水", + "minecraft:ender_pearl": "扔出的終界珍珠", "minecraft:leash_knot": "牽繩結", - "minecraft:wither_skull": "凋靈之首", - "minecraft:wither_skull_dangerous": "藍色凋靈之首", + "minecraft:wither_skull": "凋零頭顱", + "minecraft:boat": "船", + "minecraft:wither_skull_dangerous": "藍色凋零怪之首", "minecraft:lightning_bolt": "閃電", "minecraft:small_fireball": "小火球", - "minecraft:area_effect_cloud": "區域效果雲", + "minecraft:area_effect_cloud": "藥水效果雲", + "minecraft:hopper_minecart": "漏斗礦車", + "minecraft:tnt_minecart": "TNT礦車", + "minecraft:chest_minecart": "儲物箱礦車", + "minecraft:command_block_minecart": "命令方塊礦車", + "minecraft:lingering_potion": "扔出的滯留藥水", "minecraft:llama_spit": "羊駝口水", "minecraft:evocation_fang": "尖牙", "minecraft:evocation_illager": "喚魔者", "minecraft:vex": "惱鬼", - "minecraft:drowned": "溺屍", + "minecraft:ice_bomb": "冰彈", + "minecraft:balloon": "氣球", + "minecraft:pufferfish": "河豚", + "minecraft:salmon": "三文魚", + "minecraft:drowned": "沉屍", "minecraft:tropicalfish": "熱帶魚", + "minecraft:cod": "鱈魚", "minecraft:panda": "熊貓", "minecraft:pillager": "掠奪者", "minecraft:villager_v2": "村民", "minecraft:zombie_villager_v2": "喪屍村民", + "minecraft:shield": "盾牌", "minecraft:wandering_trader": "流浪商人", - "minecraft:elder_guardian_ghost": "遠古守衞者幽靈", + "minecraft:elder_guardian_ghost": "遠古深海守衞幽靈", "minecraft:fox": "狐狸", "minecraft:bee": "蜜蜂", "minecraft:piglin": "豬人", @@ -1965,7 +2289,7 @@ "minecraft:zoglin": "豬屍獸", "minecraft:piglin_brute": "殘暴豬人", "minecraft:goat": "山羊", - "minecraft:glow_squid": "螢光墨魚", + "minecraft:glow_squid": "熒光墨魚", "minecraft:axolotl": "墨西哥蠑螈", "minecraft:warden": "沉靈守衞", "minecraft:frog": "青蛙", @@ -1974,12 +2298,22 @@ "minecraft:firefly": "螢火蟲(已移除)", "minecraft:camel": "駱駝", "minecraft:sniffer": "嗅探獸", - "minecraft:breeze": "旋風人", - "minecraft:breeze_wind_charge_projectile": "旋風人風彈", + "minecraft:breeze": "旋風使者", + "minecraft:breeze_wind_charge_projectile": "旋風使者風彈", "minecraft:armadillo": "犰狳", "minecraft:wind_charge_projectile": "風彈物品風彈", "minecraft:bogged": "沼骨", - "minecraft:ominous_item_spawner": "不祥之物生成器", + "minecraft:ominous_item_spawner": "不祥物品生成器", "minecraft:creaking": "異響樹妖", - "minecraft:trader_llama": "商隊羊駝" -} \ No newline at end of file + "minecraft:happy_ghast": "快樂幽靈", + "minecraft:copper_golem": "銅人", + "minecraft:nautilus": "鸚鵡螺", + "minecraft:zombie_nautilus": "喪屍鸚鵡螺", + "minecraft:parched": "旱骨", + "minecraft:camel_husk": "駱駝屍殼", + "minecraft:sulfur_cube": "硫磺史萊姆", + "minecraft:cushion": "咕臣", + "minecraft:trader_llama": "商隊羊駝", + "minecraft:chest_boat": "儲物箱船" + } +} diff --git a/src/lang/zh_TW.json b/src/lang/zh_TW.json index 6e0cb9c..394bdb3 100644 --- a/src/lang/zh_TW.json +++ b/src/lang/zh_TW.json @@ -1,108 +1,132 @@ { - "gui.title.stats": "统计信息", - "gui.title.rank": "排行榜", - "command.stats.name": "stats", - "command.stats.alias": "lks", - "command.stats.desc": "统计信息", - "command.error.notplayer": "此命令仅限玩家执行", - "command.error.player_name_empty": "玩家名不能为空", - "command.error.find_player": "找不到指定的玩家,请检查玩家名是否正确", - "command.error.rank_type_required": "排行榜类型为custom时,type不能为空", - "log.info.ExistOldPath": "检测到旧数据, 迁移数据中", - "log.warn.moveStats.fail": "数据迁移失败", - "log.info.CreateStatsPath": "未检测到数据目录,正在初始化...", - "log.warn.CreatePath.fail": "数据目录创建失败", - "plugins.load.success": "插件加载成功!", - "plugins.load.fail": "插件加载失败,请检查数据目录是否存在", - "data.parse.fail": "{0} 文件解析失败,请检查文件内容", - "log.info.createData": "未检测到玩家 {0} 数据,正在创建...", - "log.info.savaData": "保存玩家 {0} 数据中...", - "minecraft:custom": "通用", - "minecraft:mined": "方块挖掘", - "minecraft:broken": "物品破损", - "minecraft:crafted": "物品合成", - "minecraft:used": "物品使用", - "minecraft:picked_up": "物品拾取", - "minecraft:dropped": "物品丢弃", - "minecraft:killed": "实体击杀", - "minecraft:killed_by": "被击杀", - "minecraft:animals_bred": "繁殖动物次数", - "minecraft:clean_armor": "清洗盔甲次数", - "minecraft:clean_banner": "清洗旗帜次数", - "minecraft:open_barrel": "木桶打开次数", - "minecraft:bell_ring": "鸣钟次数", - "minecraft:eat_cake_slice": "吃掉的蛋糕片数", - "minecraft:fill_cauldron": "炼药锅装水次数", - "minecraft:open_chest": "箱子打开次数", - "minecraft:damage_absorbed": "吸收的伤害", - "minecraft:damage_blocked_by_shield": "盾牌抵挡的伤害", - "minecraft:damage_dealt": "造成伤害", - "minecraft:damage_dealt_absorbed": "造成伤害(被吸收)", - "minecraft:damage_dealt_resisted": "造成伤害(被抵挡)", - "minecraft:damage_resisted": "抵挡的伤害", - "minecraft:damage_taken": "受到伤害", - "minecraft:inspect_dispenser": "搜查发射器次数", - "minecraft:happy_ghast_one_cm": "骑乘快乐恶魂移动距离(厘米)", - "minecraft:nautilus_one_cm": "骑鹦鹉螺移动距离(厘米)", - "minecraft:boat_one_cm": "坐船移动距离(厘米)", - "minecraft:aviate_one_cm": "鞘翅滑行距离(厘米)", - "minecraft:horse_one_cm": "骑马移动距离(厘米)", - "minecraft:minecart_one_cm": "坐矿车移动距离(厘米)", - "minecraft:pig_one_cm": "骑猪移动距离(厘米)", - "minecraft:strider_one_cm": "骑炽足兽移动距离(厘米)", - "minecraft:climb_one_cm": "已攀爬距离(厘米)", - "minecraft:crouch_one_cm": "潜行距离(厘米)", - "minecraft:fall_one_cm": "摔落高度(厘米)", - "minecraft:fly_one_cm": "飞行距离(厘米)", - "minecraft:sprint_one_cm": "疾跑距离(厘米)", - "minecraft:swim_one_cm": "游泳距离(厘米)", - "minecraft:walk_one_cm": "行走距离(厘米)", - "minecraft:walk_on_water_one_cm": "水面行走距离(厘米)", - "minecraft:walk_under_water_one_cm": "水下行走距离(厘米)", - "minecraft:inspect_dropper": "搜查投掷器次数", - "minecraft:open_enderchest": "末影箱打开次数", - "minecraft:fish_caught": "捕鱼数", - "minecraft:leave_game": "游戏退出次数", - "minecraft:inspect_hopper": "搜查漏斗次数", - "minecraft:interact_with_anvil": "与铁砧交互次数", - "minecraft:interact_with_beacon": "与信标交互次数", - "minecraft:interact_with_blast_furnace": "与高炉交互次数", - "minecraft:interact_with_brewingstand": "与酿造台交互次数", - "minecraft:interact_with_campfire": "与营火交互次数", - "minecraft:interact_with_cartography_table": "与制图台交互次数", - "minecraft:interact_with_crafting_table": "与工作台交互次数", - "minecraft:interact_with_furnace": "与熔炉交互次数", - "minecraft:interact_with_grindstone": "与砂轮交互次数", - "minecraft:interact_with_lectern": "与讲台交互次数", - "minecraft:interact_with_loom": "与织布机交互次数", - "minecraft:interact_with_smithing_table": "与锻造台交互次数", - "minecraft:interact_with_smoker": "与烟熏炉交互次数", - "minecraft:interact_with_stonecutter": "与切石机交互次数", - "minecraft:drop": "物品掉落", - "minecraft:enchant_item": "物品附魔次数", - "minecraft:jump": "跳跃次数", - "minecraft:mob_kills": "生物击杀数", - "minecraft:play_record": "播放唱片数", - "minecraft:play_noteblock": "音符盒播放次数", - "minecraft:tune_noteblock": "音符盒调音次数", - "minecraft:deaths": "死亡次数", - "minecraft:pot_flower": "盆栽种植数", - "minecraft:player_kills": "玩家击杀数", - "minecraft:raid_trigger": "触发袭击次数", - "minecraft:raid_win": "袭击胜利次数", - "minecraft:clean_shulker_box": "潜影盒清洗次数", - "minecraft:open_shulker_box": "潜影盒打开次数", - "minecraft:time_since_death": "自上次死亡(Tick)", - "minecraft:time_since_rest": "自上次入眠(Tick)", - "minecraft:sneak_time": "潜行时间(Tick)", - "minecraft:talked_to_villager": "村民交互次数", - "minecraft:target_hit": "击中标靶次数", - "minecraft:play_time": "游戏时间(Tick)", - "minecraft:total_world_time": "世界打开时间(Tick)", - "minecraft:sleep_in_bed": "躺在床上的次数", - "minecraft:traded_with_villager": "村民交易次数", - "minecraft:trigger_trapped_chest": "陷阱箱触发次数", - "minecraft:use_cauldron": "从炼药锅取水次数", + "gui": { + "title": { + "stats": "統計資訊", + "rank": "排行榜" + } + }, + "command": { + "stats": { + "name": "stats", + "alias": "lks", + "desc": "統計資訊" + }, + "error": { + "not_player": "此指令只能由玩家執行", + "player_name_empty": "玩家名稱不能為空", + "player_not_found": "找不到指定玩家,請檢查玩家名稱是否正確", + "invalid_stats_category": "無效的統計分類", + "rank_stat_required": "排行榜分類為「通用」時,必須指定統計項" + } + }, + "log": { + "warning": { + "legacy_data_migration_started": "偵測到舊版統計資料,正在遷移...", + "legacy_data_migration_failed": "舊版統計資料遷移失敗", + "data_directory_initializing": "未偵測到統計資料目錄,正在初始化...", + "data_directory_creation_failed": "統計資料目錄建立失敗", + "plugin_load_failed": "插件載入失敗,請檢查統計資料目錄是否可用", + "data_parse_failed": "無法解析統計資料檔案 {0},請檢查檔案內容" + }, + "info": { + "plugin_loaded": "插件載入成功", + "player_data_creating": "未偵測到玩家 {0} 的統計資料,正在建立...", + "player_data_saving": "正在儲存玩家 {0} 的統計資料..." + } + }, + "stats": { + "category": { + "custom": "通用", + "mined": "方塊挖掘", + "broken": "物品損壞", + "crafted": "物品合成", + "used": "物品使用", + "picked_up": "物品拾取", + "dropped": "物品丟棄", + "killed": "實體擊殺", + "killed_by": "被擊殺" + }, + "custom": { + "minecraft:animals_bred": "繁殖动物次数", + "minecraft:clean_armor": "清洗盔甲次数", + "minecraft:clean_banner": "清洗旗帜次数", + "minecraft:open_barrel": "木桶打开次数", + "minecraft:bell_ring": "鸣钟次数", + "minecraft:eat_cake_slice": "吃掉的蛋糕片数", + "minecraft:fill_cauldron": "炼药锅装水次数", + "minecraft:open_chest": "箱子打开次数", + "minecraft:damage_absorbed": "吸收的伤害", + "minecraft:damage_blocked_by_shield": "盾牌抵挡的伤害", + "minecraft:damage_dealt": "造成伤害", + "minecraft:damage_dealt_absorbed": "造成伤害(被吸收)", + "minecraft:damage_dealt_resisted": "造成伤害(被抵挡)", + "minecraft:damage_resisted": "抵挡的伤害", + "minecraft:damage_taken": "受到伤害", + "minecraft:inspect_dispenser": "搜查发射器次数", + "minecraft:happy_ghast_one_cm": "骑乘快乐恶魂移动距离(厘米)", + "minecraft:nautilus_one_cm": "骑鹦鹉螺移动距离(厘米)", + "minecraft:boat_one_cm": "坐船移动距离(厘米)", + "minecraft:aviate_one_cm": "鞘翅滑行距离(厘米)", + "minecraft:horse_one_cm": "骑马移动距离(厘米)", + "minecraft:minecart_one_cm": "坐矿车移动距离(厘米)", + "minecraft:pig_one_cm": "骑猪移动距离(厘米)", + "minecraft:strider_one_cm": "骑炽足兽移动距离(厘米)", + "minecraft:climb_one_cm": "已攀爬距离(厘米)", + "minecraft:crouch_one_cm": "潜行距离(厘米)", + "minecraft:fall_one_cm": "摔落高度(厘米)", + "minecraft:fly_one_cm": "飞行距离(厘米)", + "minecraft:sprint_one_cm": "疾跑距离(厘米)", + "minecraft:swim_one_cm": "游泳距离(厘米)", + "minecraft:walk_one_cm": "行走距离(厘米)", + "minecraft:walk_on_water_one_cm": "水面行走距离(厘米)", + "minecraft:walk_under_water_one_cm": "水下行走距离(厘米)", + "minecraft:inspect_dropper": "搜查投掷器次数", + "minecraft:open_enderchest": "末影箱打开次数", + "minecraft:fish_caught": "捕鱼数", + "minecraft:leave_game": "游戏退出次数", + "minecraft:inspect_hopper": "搜查漏斗次数", + "minecraft:interact_with_anvil": "与铁砧交互次数", + "minecraft:interact_with_beacon": "与信标交互次数", + "minecraft:interact_with_blast_furnace": "与高炉交互次数", + "minecraft:interact_with_brewingstand": "与酿造台交互次数", + "minecraft:interact_with_campfire": "与营火交互次数", + "minecraft:interact_with_cartography_table": "与制图台交互次数", + "minecraft:interact_with_crafting_table": "与工作台交互次数", + "minecraft:interact_with_furnace": "与熔炉交互次数", + "minecraft:interact_with_grindstone": "与砂轮交互次数", + "minecraft:interact_with_lectern": "与讲台交互次数", + "minecraft:interact_with_loom": "与织布机交互次数", + "minecraft:interact_with_smithing_table": "与锻造台交互次数", + "minecraft:interact_with_smoker": "与烟熏炉交互次数", + "minecraft:interact_with_stonecutter": "与切石机交互次数", + "minecraft:drop": "物品掉落", + "minecraft:enchant_item": "物品附魔次数", + "minecraft:jump": "跳跃次数", + "minecraft:mob_kills": "生物击杀数", + "minecraft:play_record": "播放唱片数", + "minecraft:play_noteblock": "音符盒播放次数", + "minecraft:tune_noteblock": "音符盒调音次数", + "minecraft:deaths": "死亡次数", + "minecraft:pot_flower": "盆栽种植数", + "minecraft:player_kills": "玩家击杀数", + "minecraft:raid_trigger": "触发袭击次数", + "minecraft:raid_win": "袭击胜利次数", + "minecraft:clean_shulker_box": "潜影盒清洗次数", + "minecraft:open_shulker_box": "潜影盒打开次数", + "minecraft:time_since_death": "自上次死亡(Tick)", + "minecraft:time_since_rest": "自上次入眠(Tick)", + "minecraft:sneak_time": "潜行时间(Tick)", + "minecraft:talked_to_villager": "村民交互次数", + "minecraft:target_hit": "击中标靶次数", + "minecraft:play_time": "游戏时间(Tick)", + "minecraft:total_world_time": "世界打开时间(Tick)", + "minecraft:sleep_in_bed": "躺在床上的次数", + "minecraft:traded_with_villager": "村民交易次数", + "minecraft:trigger_trapped_chest": "陷阱箱触发次数", + "minecraft:use_cauldron": "从炼药锅取水次数" + } + }, + "block": { "minecraft:stone": "石頭", "minecraft:grass_block": "草地", "minecraft:dirt": "泥土", @@ -140,7 +164,7 @@ "minecraft:white_wool": "白色羊毛", "minecraft:element_0": "未知元素(???)", "minecraft:dandelion": "蒲公英", - "minecraft:poppy": "罌粟", + "minecraft:poppy": "罌粟花", "minecraft:brown_mushroom": "棕色蘑菇", "minecraft:red_mushroom": "紅色蘑菇", "minecraft:gold_block": "黃金方塊", @@ -148,7 +172,7 @@ "minecraft:smooth_stone_double_slab": "平滑石頭雙半磚", "minecraft:smooth_stone_slab": "平滑石半磚", "minecraft:brick_block": "紅磚", - "minecraft:tnt": "點燃的TT", + "minecraft:tnt": "TNT", "minecraft:bookshelf": "書櫃", "minecraft:mossy_cobblestone": "青苔鵝卵石", "minecraft:obsidian": "黑曜石", @@ -185,7 +209,7 @@ "minecraft:snow": "雪塊", "minecraft:cactus": "仙人掌", "minecraft:clay": "黏土", - "minecraft:reeds": "甘蔗", + "minecraft:sugar_cane": "甘蔗", "minecraft:jukebox": "唱片機", "minecraft:oak_fence": "橡木柵欄", "minecraft:pumpkin": "南瓜", @@ -198,7 +222,7 @@ "minecraft:unpowered_repeater": "未啟動的中繼器", "minecraft:powered_repeater": "啟動的中繼器", "minecraft:invisible_bedrock": "隱形基岩", - "minecraft:trapdoor": "地板門", + "minecraft:trapdoor": "橡木地板門", "minecraft:infested_stone": "蛀蝕的石頭", "minecraft:stone_bricks": "石磚", "minecraft:brown_mushroom_block": "棕色蘑菇方塊", @@ -249,8 +273,8 @@ "minecraft:skeleton_skull": "骷髏頭顱", "minecraft:anvil": "鐵砧", "minecraft:trapped_chest": "陷阱儲物箱", - "minecraft:light_weighted_pressure_plate": "測重壓力板 (輕質)", - "minecraft:heavy_weighted_pressure_plate": "測重壓力板 (重質)", + "minecraft:light_weighted_pressure_plate": "輕質測重壓力板", + "minecraft:heavy_weighted_pressure_plate": "重質測重壓力板", "minecraft:unpowered_comparator": "未啟動的比較器", "minecraft:powered_comparator": "啟動的比較器", "minecraft:daylight_detector": "日光感測器", @@ -267,7 +291,8 @@ "minecraft:acacia_log": "相思木原木", "minecraft:acacia_stairs": "相思木階梯", "minecraft:dark_oak_stairs": "黑橡木階梯", - "minecraft:slime": "史萊姆", + "minecraft:slime": "史萊姆方塊", + "minecraft:glow_stick": "螢光棒", "minecraft:iron_trapdoor": "鐵地板門", "minecraft:prismarine": "海磷石", "minecraft:sea_lantern": "海燈籠", @@ -299,7 +324,7 @@ "minecraft:jungle_door": "叢林木門", "minecraft:acacia_door": "相思木門", "minecraft:dark_oak_door": "黑橡木門", - "minecraft:grass_path": "草徑", + "minecraft:grass_path": "土徑", "minecraft:frame": "物品展示框", "minecraft:chorus_flower": "歌萊花", "minecraft:purpur_block": "紫珀方塊", @@ -313,7 +338,7 @@ "minecraft:end_gateway": "終界折躍門", "minecraft:allow": "允許方塊", "minecraft:deny": "拒絕方塊", - "minecraft:border_block": "邊界方塊", + "minecraft:border_block": "邊界", "minecraft:magma": "岩漿塊", "minecraft:nether_wart_block": "地獄疙瘩塊", "minecraft:red_nether_brick": "紅地獄磚", @@ -346,12 +371,12 @@ "minecraft:camera": "攝像機", "minecraft:podzol": "灰壤", "minecraft:beetroot": "甜菜根", - "minecraft:stonecutter": "切石機", + "minecraft:stonecutter": "切石機(舊版)", "minecraft:glowingobsidian": "發光的黑曜石", "minecraft:netherreactor": "地獄反應核", "minecraft:info_update": "資料更新方塊(update!)", "minecraft:info_update2": "資料更新方塊2(ate!upd)", - "minecraft:moving_block": "移動中的活塞", + "minecraft:moving_block": "移動的方塊/移動中的活塞", "minecraft:observer": "偵測器", "minecraft:structure_block": "結構方塊", "minecraft:hard_glass": "強化玻璃", @@ -437,7 +462,7 @@ "minecraft:element_68": "鉺", "minecraft:element_69": "銩", "minecraft:element_70": "鐿", - "minecraft:element_71": "鑥", + "minecraft:element_71": "鎦", "minecraft:element_72": "鉿", "minecraft:element_73": "鉭", "minecraft:element_74": "鎢", @@ -451,7 +476,7 @@ "minecraft:element_82": "鉛", "minecraft:element_83": "鉍", "minecraft:element_84": "釙", - "minecraft:element_85": "砹", + "minecraft:element_85": "砈", "minecraft:element_86": "氡", "minecraft:element_87": "鍅", "minecraft:element_88": "鐳", @@ -483,8 +508,8 @@ "minecraft:element_114": "鈇", "minecraft:element_115": "鏌", "minecraft:element_116": "鉝", - "minecraft:element_117": "Tennessine", - "minecraft:element_118": "Oganesson", + "minecraft:element_117": "鿬", + "minecraft:element_118": "鿫", "minecraft:seagrass": "海草", "minecraft:tube_coral": "管珊瑚", "minecraft:tube_coral_block": "管珊瑚方塊", @@ -585,7 +610,7 @@ "minecraft:crimson_fungus": "緋紅蕈菇", "minecraft:warped_fungus": "扭曲蕈菇", "minecraft:shroomlight": "蕈光體", - "minecraft:weeping_vines": "垂泣藤", + "minecraft:weeping_vines_plant": "垂泣藤植株", "minecraft:crimson_nylium": "緋紅菌絲石", "minecraft:warped_nylium": "扭曲菌絲石", "minecraft:basalt": "玄武岩", @@ -638,7 +663,7 @@ "minecraft:blackstone_double_slab": "黑石雙半磚", "minecraft:polished_blackstone_brick_slab": "拋光黑石磚半磚", "minecraft:polished_blackstone_brick_double_slab": "拋光黑石磚雙半磚", - "minecraft:chain": "鎖鏈", + "minecraft:iron_chain": "鐵鏈", "minecraft:twisting_vines": "扭曲藤", "minecraft:nether_gold_ore": "地獄金礦", "minecraft:crying_obsidian": "哭泣的黑曜石", @@ -657,7 +682,7 @@ "minecraft:chiseled_nether_bricks": "浮雕地獄磚", "minecraft:cracked_nether_bricks": "裂紋地獄磚", "minecraft:quartz_bricks": "石英磚", - "minecraft:unknown": "未知 [注 3]", + "minecraft:unknown": "未知", "minecraft:powder_snow": "粉雪", "minecraft:sculk_sensor": "伏聆振測器", "minecraft:pointed_dripstone": "鐘乳石", @@ -750,7 +775,7 @@ "minecraft:polished_deepslate_double_slab": "拋光深板岩雙半磚", "minecraft:deepslate_tile_double_slab": "深板岩磚瓦雙半磚", "minecraft:deepslate_brick_double_slab": "深板岩磚雙半磚", - "minecraft:deepslate_lapis_lazuli_ore": "深板岩青金石礦", + "minecraft:deepslate_lapis_ore": "深板岩青金石礦", "minecraft:deepslate_iron_ore": "深板岩鐵礦", "minecraft:deepslate_gold_ore": "深板岩金礦", "minecraft:deepslate_redstone_ore": "深板岩紅石礦", @@ -820,6 +845,7 @@ "minecraft:mud": "泥巴", "minecraft:mangrove_propagule": "紅樹林木胎生苗", "minecraft:mud_bricks": "泥磚", + "minecraft:mangrove_propagule_hanging": "懸掛的紅樹林木胎生苗 (已移除)", "minecraft:packed_mud": "泥坯", "minecraft:mud_brick_slab": "泥磚半磚", "minecraft:mud_brick_double_slab": "泥磚雙半磚", @@ -872,7 +898,7 @@ "minecraft:chiseled_bookshelf": "浮雕書櫃", "minecraft:bamboo_block": "竹方塊", "minecraft:stripped_bamboo_block": "剝皮竹方塊", - "minecraft:suspicious_sand": "可疑的沙子", + "minecraft:suspicious_sand": "可疑的沙", "minecraft:cherry_button": "櫻花木按鈕", "minecraft:cherry_door": "櫻花木門", "minecraft:cherry_fence": "櫻花木柵欄", @@ -1290,7 +1316,7 @@ "minecraft:deprecated_purpur_block_2": "平滑紫珀方塊", "minecraft:chiseled_quartz_block": "浮雕石英方塊", "minecraft:quartz_pillar": "石英柱", - "minecraft:smooth_quartz": "平滑石英方塊", + "minecraft:smooth_quartz_block": "平滑石英方塊", "minecraft:chiseled_red_sandstone": "浮雕紅砂岩", "minecraft:cut_red_sandstone": "切製紅砂岩", "minecraft:smooth_red_sandstone": "平滑紅砂岩", @@ -1320,7 +1346,7 @@ "minecraft:red_sandstone_wall": "紅砂岩牆", "minecraft:red_nether_brick_wall": "紅地獄磚牆", "minecraft:wet_sponge": "濕海綿", - "minecraft:underwater_tnt": "水下TT", + "minecraft:underwater_tnt": "水下TNT", "minecraft:material_reducer": "材料分解器", "minecraft:element_constructor": "元素構造器", "minecraft:lab_table": "實驗台", @@ -1358,40 +1384,192 @@ "minecraft:chiseled_resin_bricks": "浮雕樹脂磚", "minecraft:resin_block": "樹脂方塊", "minecraft:resin_clump": "樹脂團", - "minecraft:bush": "ush", - "minecraft:wildflowers": "Wildflowers", - "minecraft:firefly_bush": "Firefly ush", - "minecraft:leaf_litter": "Leaf Litter", - "minecraft:black_bundle": "黑色束口袋", - "minecraft:blue_bundle": "藍色束口袋", - "minecraft:brown_bundle": "棕色束口袋", - "minecraft:bundle": "束口袋", - "minecraft:cyan_bundle": "青色束口袋", - "minecraft:gray_bundle": "灰色束口袋", - "minecraft:green_bundle": "綠色束口袋", - "minecraft:light_blue_bundle": "淺藍色束口袋", - "minecraft:light_gray_bundle": "淺灰色束口袋", - "minecraft:lime_bundle": "淺綠色束口袋", - "minecraft:magenta_bundle": "洋紅色束口袋", - "minecraft:orange_bundle": "橙色束口袋", - "minecraft:pink_bundle": "粉紅色束口袋", - "minecraft:purple_bundle": "紫色束口袋", - "minecraft:red_bundle": "紅色束口袋", - "minecraft:white_bundle": "白色束口袋", - "minecraft:yellow_bundle": "黃色束口袋", - "minecraft:breeze_rod": "旋風桿", - "minecraft:ominous_trial_key": "不祥試煉鑰匙", - "minecraft:trial_key": "試煉鑰匙", - "minecraft:wind_charge": "風彈", - "minecraft:apple": "蘋果", + "minecraft:bush": "灌木叢", + "minecraft:wildflowers": "野花簇", + "minecraft:firefly_bush": "螢火蟲灌木叢", + "minecraft:leaf_litter": "枯葉", + "minecraft:dried_ghast": "乾癟幽靈", + "minecraft:short_dry_grass": "矮枯草", + "minecraft:tall_dry_grass": "高枯草", + "minecraft:cactus_flower": "仙人掌花", + "minecraft:copper_chest": "銅儲物箱", + "minecraft:exposed_copper_chest": "斑駁的銅儲物箱", + "minecraft:weathered_copper_chest": "風化的銅儲物箱", + "minecraft:oxidized_copper_chest": "氧化的銅儲物箱", + "minecraft:waxed_copper_chest": "上蠟的銅儲物箱", + "minecraft:waxed_exposed_copper_chest": "上蠟的斑駁銅儲物箱", + "minecraft:waxed_weathered_copper_chest": "上蠟的風化銅儲物箱", + "minecraft:waxed_oxidized_copper_chest": "上蠟的氧化銅儲物箱", + "minecraft:copper_golem_statue": "銅像", + "minecraft:exposed_copper_golem_statue": "斑駁的銅像", + "minecraft:weathered_copper_golem_statue": "風化的銅像", + "minecraft:oxidized_copper_golem_statue": "氧化的銅像", + "minecraft:waxed_copper_golem_statue": "上蠟的銅像", + "minecraft:waxed_exposed_copper_golem_statue": "上蠟的斑駁銅像", + "minecraft:waxed_weathered_copper_golem_statue": "上蠟的風化銅像", + "minecraft:waxed_oxidized_copper_golem_statue": "上蠟的氧化銅像", + "minecraft:oak_shelf": "橡木展示架", + "minecraft:spruce_shelf": "杉木展示架", + "minecraft:birch_shelf": "樺木展示架", + "minecraft:jungle_shelf": "叢林木展示架", + "minecraft:acacia_shelf": "相思木展示架", + "minecraft:dark_oak_shelf": "黑橡木展示架", + "minecraft:mangrove_shelf": "紅樹林木展示架", + "minecraft:cherry_shelf": "櫻花木展示架", + "minecraft:pale_oak_shelf": "蒼白橡木展示架", + "minecraft:bamboo_shelf": "竹展示架", + "minecraft:crimson_shelf": "緋紅蕈木展示架", + "minecraft:warped_shelf": "扭曲蕈木展示架", + "minecraft:exposed_lightning_rod": "斑駁的避雷針", + "minecraft:weathered_lightning_rod": "風化的避雷針", + "minecraft:oxidized_lightning_rod": "氧化的避雷針", + "minecraft:waxed_lightning_rod": "上蠟的避雷針", + "minecraft:waxed_exposed_lightning_rod": "上蠟的斑駁避雷針", + "minecraft:waxed_weathered_lightning_rod": "上蠟的風化避雷針", + "minecraft:waxed_oxidized_lightning_rod": "上蠟的氧化避雷針", + "minecraft:copper_bars": "銅柵欄", + "minecraft:exposed_copper_bars": "斑駁的銅柵欄", + "minecraft:weathered_copper_bars": "風化的銅柵欄", + "minecraft:oxidized_copper_bars": "氧化的銅柵欄", + "minecraft:waxed_copper_bars": "上蠟的銅柵欄", + "minecraft:waxed_exposed_copper_bars": "上蠟的斑駁銅柵欄", + "minecraft:waxed_weathered_copper_bars": "上蠟的風化銅柵欄", + "minecraft:waxed_oxidized_copper_bars": "上蠟的氧化銅柵欄", + "minecraft:copper_chain": "銅鏈", + "minecraft:exposed_copper_chain": "斑駁的銅鏈", + "minecraft:weathered_copper_chain": "風化的銅鏈", + "minecraft:oxidized_copper_chain": "氧化的銅鏈", + "minecraft:waxed_copper_chain": "上蠟的銅鏈", + "minecraft:waxed_exposed_copper_chain": "上蠟的斑駁銅鏈", + "minecraft:waxed_weathered_copper_chain": "上蠟的風化銅鏈", + "minecraft:waxed_oxidized_copper_chain": "上蠟的氧化銅鏈", + "minecraft:copper_torch": "銅火把", + "minecraft:copper_lantern": "銅燈籠", + "minecraft:exposed_copper_lantern": "斑駁的銅燈籠", + "minecraft:weathered_copper_lantern": "風化的銅燈籠", + "minecraft:oxidized_copper_lantern": "氧化的銅燈籠", + "minecraft:waxed_copper_lantern": "上蠟的銅燈籠", + "minecraft:waxed_exposed_copper_lantern": "上蠟的斑駁銅燈籠", + "minecraft:waxed_weathered_copper_lantern": "上蠟的風化銅燈籠", + "minecraft:waxed_oxidized_copper_lantern": "上蠟的氧化銅燈籠", + "minecraft:golden_dandelion": "金蒲公英", + "minecraft:sulfur": "硫磺", + "minecraft:sulfur_slab": "硫磺半磚", + "minecraft:sulfur_double_slab": "硫磺雙半磚", + "minecraft:sulfur_stairs": "硫磺階梯", + "minecraft:sulfur_wall": "硫磺牆", + "minecraft:polished_sulfur": "拋光硫磺", + "minecraft:polished_sulfur_slab": "拋光硫磺半磚", + "minecraft:polished_sulfur_double_slab": "拋光硫磺雙半磚", + "minecraft:polished_sulfur_stairs": "拋光硫磺階梯", + "minecraft:polished_sulfur_wall": "拋光硫磺牆", + "minecraft:sulfur_bricks": "硫磺磚", + "minecraft:sulfur_brick_slab": "硫磺磚半磚", + "minecraft:sulfur_brick_double_slab": "硫磺磚雙半磚", + "minecraft:sulfur_brick_stairs": "硫磺磚階梯", + "minecraft:sulfur_brick_wall": "硫磺磚牆", + "minecraft:chiseled_sulfur": "浮雕硫磺", + "minecraft:potent_sulfur": "烈性硫磺", + "minecraft:cinnabar": "硃砂", + "minecraft:cinnabar_slab": "硃砂半磚", + "minecraft:cinnabar_double_slab": "硃砂雙半磚", + "minecraft:cinnabar_stairs": "硃砂階梯", + "minecraft:cinnabar_wall": "硃砂牆", + "minecraft:polished_cinnabar": "拋光硃砂", + "minecraft:polished_cinnabar_slab": "拋光硃砂半磚", + "minecraft:polished_cinnabar_double_slab": "拋光硃砂雙半磚", + "minecraft:polished_cinnabar_stairs": "拋光硃砂階梯", + "minecraft:polished_cinnabar_wall": "拋光硃砂牆", + "minecraft:cinnabar_bricks": "硃砂磚", + "minecraft:cinnabar_brick_slab": "硃砂磚半磚", + "minecraft:cinnabar_brick_double_slab": "硃砂磚雙半磚", + "minecraft:cinnabar_brick_stairs": "硃砂磚階梯", + "minecraft:cinnabar_brick_wall": "硃砂磚牆", + "minecraft:chiseled_cinnabar": "浮雕硃砂", + "minecraft:sulfur_spike": "硫磺尖錐", + "minecraft:poplar_button": "楊木按鈕", + "minecraft:poplar_door": "楊木門", + "minecraft:poplar_fence": "楊木柵欄", + "minecraft:poplar_fence_gate": "楊木柵欄門", + "minecraft:poplar_hanging_sign": "懸掛式楊木告示牌", + "minecraft:stripped_poplar_log": "剝皮楊木原木", + "minecraft:poplar_log": "楊木原木", + "minecraft:poplar_planks": "楊木材", + "minecraft:poplar_pressure_plate": "楊木壓力板", + "minecraft:poplar_slab": "楊木半磚", + "minecraft:poplar_double_slab": "楊木雙半磚", + "minecraft:poplar_stairs": "楊木階梯", + "minecraft:poplar_standing_sign": "楊木告示牌", + "minecraft:poplar_trapdoor": "楊木地板門", + "minecraft:poplar_wall_sign": "牆上的楊木告示牌", + "minecraft:stripped_poplar_wood": "剝皮楊木塊", + "minecraft:poplar_wood": "楊木塊", + "minecraft:poplar_sapling": "楊木樹苗", + "minecraft:orange_poplar_leaves": "橙色楊木樹葉", + "minecraft:red_poplar_leaves": "紅色楊木樹葉", + "minecraft:yellow_poplar_leaves": "黃色楊木樹葉", + "minecraft:poplar_shelf": "楊木展示架", + "minecraft:red_shrub": "紅灌木", + "minecraft:red_wool_stairs": "紅色羊毛階梯", + "minecraft:white_wool_stairs": "白色羊毛階梯", + "minecraft:blue_wool_stairs": "藍色羊毛階梯", + "minecraft:orange_wool_stairs": "橙色羊毛階梯", + "minecraft:magenta_wool_stairs": "洋紅色羊毛階梯", + "minecraft:light_blue_wool_stairs": "淺藍色羊毛階梯", + "minecraft:yellow_wool_stairs": "黃色羊毛階梯", + "minecraft:lime_wool_stairs": "淺綠色羊毛階梯", + "minecraft:pink_wool_stairs": "粉紅色羊毛階梯", + "minecraft:gray_wool_stairs": "灰色羊毛階梯", + "minecraft:light_gray_wool_stairs": "淺灰色羊毛階梯", + "minecraft:cyan_wool_stairs": "青色羊毛階梯", + "minecraft:purple_wool_stairs": "紫色羊毛階梯", + "minecraft:green_wool_stairs": "綠色羊毛階梯", + "minecraft:brown_wool_stairs": "棕色羊毛階梯", + "minecraft:black_wool_stairs": "黑色羊毛階梯", + "minecraft:white_wool_slab": "白色羊毛半磚", + "minecraft:light_gray_wool_slab": "淺灰色羊毛半磚", + "minecraft:gray_wool_slab": "灰色羊毛半磚", + "minecraft:black_wool_slab": "黑色羊毛半磚", + "minecraft:brown_wool_slab": "棕色羊毛半磚", + "minecraft:red_wool_slab": "紅色羊毛半磚", + "minecraft:orange_wool_slab": "橙色羊毛半磚", + "minecraft:yellow_wool_slab": "黃色羊毛半磚", + "minecraft:lime_wool_slab": "淺綠色羊毛半磚", + "minecraft:green_wool_slab": "綠色羊毛半磚", + "minecraft:cyan_wool_slab": "青色羊毛半磚", + "minecraft:light_blue_wool_slab": "淺藍色羊毛半磚", + "minecraft:blue_wool_slab": "藍色羊毛半磚", + "minecraft:purple_wool_slab": "紫色羊毛半磚", + "minecraft:magenta_wool_slab": "洋紅色羊毛半磚", + "minecraft:pink_wool_slab": "粉紅色羊毛半磚", + "minecraft:white_wool_double_slab": "白色羊毛雙半磚", + "minecraft:light_gray_wool_double_slab": "淺灰色羊毛雙半磚", + "minecraft:gray_wool_double_slab": "灰色羊毛雙半磚", + "minecraft:black_wool_double_slab": "黑色羊毛雙半磚", + "minecraft:brown_wool_double_slab": "棕色羊毛雙半磚", + "minecraft:red_wool_double_slab": "紅色羊毛雙半磚", + "minecraft:orange_wool_double_slab": "橙色羊毛雙半磚", + "minecraft:yellow_wool_double_slab": "黃色羊毛雙半磚", + "minecraft:lime_wool_double_slab": "淺綠色羊毛雙半磚", + "minecraft:green_wool_double_slab": "綠色羊毛雙半磚", + "minecraft:cyan_wool_double_slab": "青色羊毛雙半磚", + "minecraft:light_blue_wool_double_slab": "淺藍色羊毛雙半磚", + "minecraft:blue_wool_double_slab": "藍色羊毛雙半磚", + "minecraft:purple_wool_double_slab": "紫色羊毛雙半磚", + "minecraft:magenta_wool_double_slab": "洋紅色羊毛雙半磚", + "minecraft:pink_wool_double_slab": "粉紅色羊毛雙半磚", + "minecraft:shelf_mushroom": "層孔菇", + "minecraft:straw_bed": "Straw Bed" + }, + "item": { "minecraft:golden_apple": "金蘋果", "minecraft:enchanted_golden_apple": "附魔金蘋果", "minecraft:mushroom_stew": "蘑菇湯", "minecraft:bread": "麵包", "minecraft:porkchop": "生豬肉", "minecraft:cooked_porkchop": "烤豬肉", - "minecraft:cod": "鱈魚", - "minecraft:salmon": "鮭魚", + "minecraft:cod": "生鱈魚", + "minecraft:salmon": "生鮭魚", "minecraft:tropical_fish": "熱帶魚", "minecraft:pufferfish": "河豚", "minecraft:cooked_cod": "烤鱈魚", @@ -1401,7 +1579,7 @@ "minecraft:melon_slice": "西瓜", "minecraft:beef": "生牛肉", "minecraft:cooked_beef": "牛排", - "minecraft:chicken": "雞", + "minecraft:chicken": "生雞肉", "minecraft:cooked_chicken": "烤雞", "minecraft:rotten_flesh": "腐肉", "minecraft:spider_eye": "蜘蛛眼", @@ -1411,14 +1589,16 @@ "minecraft:poisonous_potato": "毒馬鈴薯", "minecraft:golden_carrot": "金胡蘿蔔", "minecraft:pumpkin_pie": "南瓜派", + "minecraft:beetroot": "甜菜根", "minecraft:beetroot_soup": "甜菜湯", "minecraft:sweet_berries": "甜莓", - "minecraft:rabbit": "兔子", + "minecraft:rabbit": "生兔肉", "minecraft:cooked_rabbit": "烤兔肉", "minecraft:rabbit_stew": "兔肉湯", "minecraft:wheat_seeds": "種子", "minecraft:pumpkin_seeds": "南瓜種子", "minecraft:melon_seeds": "西瓜種子", + "minecraft:nether_wart": "地獄疙瘩", "minecraft:beetroot_seeds": "甜菜種子", "minecraft:torchflower_seeds": "火把花種子", "minecraft:pitcher_pod": "瓶子草豆莢", @@ -1427,7 +1607,7 @@ "minecraft:iron_axe": "鐵斧", "minecraft:flint_and_steel": "打火石", "minecraft:bow": "弓", - "minecraft:arrow": "射出的箭", + "minecraft:arrow": "箭矢", "minecraft:coal": "煤炭", "minecraft:charcoal": "木炭", "minecraft:diamond": "鑽石", @@ -1461,6 +1641,7 @@ "minecraft:iron_hoe": "鐵鋤", "minecraft:diamond_hoe": "鑽石鋤", "minecraft:golden_hoe": "金鋤", + "minecraft:wheat": "小麥", "minecraft:leather_helmet": "皮革帽子", "minecraft:leather_chestplate": "皮革上衣", "minecraft:leather_leggings": "皮革褲子", @@ -1483,8 +1664,9 @@ "minecraft:golden_boots": "黃金靴子", "minecraft:shield": "盾牌", "minecraft:flint": "燧石", - "minecraft:painting": "畫", + "minecraft:painting": "繪畫", "minecraft:oak_sign": "橡木告示牌", + "minecraft:wooden_door": "橡木門", "minecraft:bucket": "鐵桶", "minecraft:milk_bucket": "鮮奶桶", "minecraft:water_bucket": "水桶", @@ -1497,8 +1679,9 @@ "minecraft:axolotl_bucket": "六角恐龍桶", "minecraft:minecart": "礦車", "minecraft:saddle": "鞍", + "minecraft:iron_door": "鐵門", "minecraft:redstone": "紅石粉", - "minecraft:snowball": "扔出的雪球", + "minecraft:snowball": "雪球", "minecraft:oak_boat": "橡木船", "minecraft:birch_boat": "樺木船", "minecraft:jungle_boat": "叢林木船", @@ -1506,6 +1689,7 @@ "minecraft:acacia_boat": "相思木船", "minecraft:dark_oak_boat": "黑橡木船", "minecraft:leather": "皮革", + "minecraft:kelp": "海帶", "minecraft:brick": "紅磚頭", "minecraft:clay_ball": "黏土", "minecraft:sugar_cane": "甘蔗", @@ -1513,7 +1697,7 @@ "minecraft:book": "書", "minecraft:slime_ball": "史萊姆球", "minecraft:chest_minecart": "儲物箱礦車", - "minecraft:egg": "扔出的雞蛋", + "minecraft:egg": "雞蛋", "minecraft:compass": "羅盤", "minecraft:fishing_rod": "釣竿", "minecraft:clock": "時鐘", @@ -1540,10 +1724,12 @@ "minecraft:lapis_lazuli": "青金石", "minecraft:bone": "骨頭", "minecraft:sugar": "糖", + "minecraft:cake": "蛋糕", + "minecraft:bed": "床", "minecraft:repeater": "紅石中繼器", "minecraft:filled_map": "地圖", "minecraft:shears": "剪刀", - "minecraft:ender_pearl": "扔出的終界珍珠", + "minecraft:ender_pearl": "終界珍珠", "minecraft:blaze_rod": "烈焰桿", "minecraft:ghast_tear": "幽靈之淚", "minecraft:gold_nugget": "金粒", @@ -1552,83 +1738,85 @@ "minecraft:fermented_spider_eye": "發酵蜘蛛眼", "minecraft:blaze_powder": "烈焰粉", "minecraft:magma_cream": "岩漿球", + "minecraft:brewing_stand": "釀造台", + "minecraft:cauldron": "鍋釜", "minecraft:ender_eye": "終界之眼", "minecraft:glistering_melon_slice": "鑲金西瓜片", - "minecraft:chicken_spawn_egg": "雞 生怪蛋", - "minecraft:cow_spawn_egg": "牛 生怪蛋", - "minecraft:pig_spawn_egg": "豬 生怪蛋", - "minecraft:sheep_spawn_egg": "綿羊 生怪蛋", - "minecraft:wolf_spawn_egg": "狼 生怪蛋", - "minecraft:mooshroom_spawn_egg": "哞菇 生怪蛋", - "minecraft:creeper_spawn_egg": "苦力怕 生怪蛋", - "minecraft:enderman_spawn_egg": "終界使者 生怪蛋", - "minecraft:silverfish_spawn_egg": "蠹魚 生怪蛋", - "minecraft:skeleton_spawn_egg": "骷髏 生怪蛋", - "minecraft:slime_spawn_egg": "史萊姆 生怪蛋", - "minecraft:spider_spawn_egg": "蜘蛛 生怪蛋", - "minecraft:zombie_spawn_egg": "殭屍 生怪蛋", - "minecraft:zombie_pigman_spawn_egg": "殭屍化豬布林 生怪蛋", - "minecraft:villager_spawn_egg": "村民 生怪蛋", - "minecraft:squid_spawn_egg": "魷魚 生怪蛋", - "minecraft:ocelot_spawn_egg": "山貓 生怪蛋", - "minecraft:witch_spawn_egg": "女巫 生怪蛋", - "minecraft:bat_spawn_egg": "蝙蝠 生怪蛋", - "minecraft:ghast_spawn_egg": "地獄幽靈 生怪蛋", - "minecraft:magma_cube_spawn_egg": "岩漿立方怪 生怪蛋", - "minecraft:blaze_spawn_egg": "烈焰使者 生怪蛋", - "minecraft:cave_spider_spawn_egg": "洞穴蜘蛛 生怪蛋", - "minecraft:horse_spawn_egg": "馬 生怪蛋", - "minecraft:rabbit_spawn_egg": "兔子 生怪蛋", - "minecraft:endermite_spawn_egg": "終界蟎 生怪蛋", - "minecraft:guardian_spawn_egg": "深海守衛 生怪蛋", - "minecraft:stray_spawn_egg": "流髑 生怪蛋", - "minecraft:bogged_spawn_egg": "沼骸 生怪蛋", - "minecraft:husk_spawn_egg": "屍殼 生怪蛋", - "minecraft:wither_skeleton_spawn_egg": "凋零骷髏 生怪蛋", - "minecraft:donkey_spawn_egg": "驢子 生怪蛋", - "minecraft:mule_spawn_egg": "騾子 生怪蛋", - "minecraft:skeleton_horse_spawn_egg": "骷髏馬 生怪蛋", - "minecraft:zombie_horse_spawn_egg": "殭屍馬 生怪蛋", - "minecraft:shulker_spawn_egg": "界伏蚌 生怪蛋", - "minecraft:npc_spawn_egg": "PC生怪蛋", - "minecraft:elder_guardian_spawn_egg": "遠古深海守衛 生怪蛋", - "minecraft:polar_bear_spawn_egg": "北極熊 生怪蛋", - "minecraft:llama_spawn_egg": "駱馬 生怪蛋", - "minecraft:vindicator_spawn_egg": "衛道士 生怪蛋", - "minecraft:evoker_spawn_egg": "喚魔者 生怪蛋", - "minecraft:vex_spawn_egg": "惱鬼 生怪蛋", - "minecraft:zombie_villager_spawn_egg": "殭屍村民 生怪蛋", - "minecraft:parrot_spawn_egg": "鸚鵡 生怪蛋", - "minecraft:tropical_fish_spawn_egg": "熱帶魚 生怪蛋", - "minecraft:cod_spawn_egg": "鱈魚 生怪蛋", - "minecraft:pufferfish_spawn_egg": "河豚 生怪蛋", - "minecraft:salmon_spawn_egg": "鮭魚 生怪蛋", - "minecraft:drowned_spawn_egg": "沉屍 生怪蛋", - "minecraft:dolphin_spawn_egg": "海豚 生怪蛋", - "minecraft:turtle_spawn_egg": "海龜 生怪蛋", - "minecraft:phantom_spawn_egg": "夜魅 生怪蛋", + "minecraft:chicken_spawn_egg": "雞生怪蛋", + "minecraft:cow_spawn_egg": "牛生怪蛋", + "minecraft:pig_spawn_egg": "豬生怪蛋", + "minecraft:sheep_spawn_egg": "綿羊生怪蛋", + "minecraft:wolf_spawn_egg": "狼生怪蛋", + "minecraft:mooshroom_spawn_egg": "哞菇生怪蛋", + "minecraft:creeper_spawn_egg": "苦力怕生怪蛋", + "minecraft:enderman_spawn_egg": "終界使者生怪蛋", + "minecraft:silverfish_spawn_egg": "蠹魚生怪蛋", + "minecraft:skeleton_spawn_egg": "骷髏生怪蛋", + "minecraft:slime_spawn_egg": "史萊姆生怪蛋", + "minecraft:spider_spawn_egg": "蜘蛛生怪蛋", + "minecraft:zombie_spawn_egg": "殭屍生怪蛋", + "minecraft:zombie_pigman_spawn_egg": "殭屍化豬布林生怪蛋", + "minecraft:villager_spawn_egg": "村民生怪蛋", + "minecraft:squid_spawn_egg": "魷魚生怪蛋", + "minecraft:ocelot_spawn_egg": "山貓生怪蛋", + "minecraft:witch_spawn_egg": "女巫生怪蛋", + "minecraft:bat_spawn_egg": "蝙蝠生怪蛋", + "minecraft:ghast_spawn_egg": "地獄幽靈生怪蛋", + "minecraft:magma_cube_spawn_egg": "岩漿立方怪生怪蛋", + "minecraft:blaze_spawn_egg": "烈焰使者生怪蛋", + "minecraft:cave_spider_spawn_egg": "洞穴蜘蛛生怪蛋", + "minecraft:horse_spawn_egg": "馬生怪蛋", + "minecraft:rabbit_spawn_egg": "兔子生怪蛋", + "minecraft:endermite_spawn_egg": "終界蟎生怪蛋", + "minecraft:guardian_spawn_egg": "深海守衛生怪蛋", + "minecraft:stray_spawn_egg": "流髑生怪蛋", + "minecraft:bogged_spawn_egg": "沼骸生怪蛋", + "minecraft:husk_spawn_egg": "屍殼生怪蛋", + "minecraft:wither_skeleton_spawn_egg": "凋零骷髏生怪蛋", + "minecraft:donkey_spawn_egg": "驢子生怪蛋", + "minecraft:mule_spawn_egg": "騾子生怪蛋", + "minecraft:skeleton_horse_spawn_egg": "骷髏馬生怪蛋", + "minecraft:zombie_horse_spawn_egg": "殭屍馬生怪蛋", + "minecraft:shulker_spawn_egg": "界伏蚌生怪蛋", + "minecraft:npc_spawn_egg": "NPC生怪蛋", + "minecraft:elder_guardian_spawn_egg": "遠古深海守衛生怪蛋", + "minecraft:polar_bear_spawn_egg": "北極熊生怪蛋", + "minecraft:llama_spawn_egg": "駱馬生怪蛋", + "minecraft:vindicator_spawn_egg": "衛道士生怪蛋", + "minecraft:evoker_spawn_egg": "喚魔者生怪蛋", + "minecraft:vex_spawn_egg": "惱鬼生怪蛋", + "minecraft:zombie_villager_spawn_egg": "殭屍村民生怪蛋", + "minecraft:parrot_spawn_egg": "鸚鵡生怪蛋", + "minecraft:tropical_fish_spawn_egg": "熱帶魚生怪蛋", + "minecraft:cod_spawn_egg": "鱈魚生怪蛋", + "minecraft:pufferfish_spawn_egg": "河豚生怪蛋", + "minecraft:salmon_spawn_egg": "鮭魚生怪蛋", + "minecraft:drowned_spawn_egg": "沉屍生怪蛋", + "minecraft:dolphin_spawn_egg": "海豚生怪蛋", + "minecraft:turtle_spawn_egg": "海龜生怪蛋", + "minecraft:phantom_spawn_egg": "夜魅生怪蛋", "minecraft:agent_spawn_egg": "智慧體生怪蛋", - "minecraft:cat_spawn_egg": "貓 生怪蛋", - "minecraft:panda_spawn_egg": "貓熊 生怪蛋", - "minecraft:fox_spawn_egg": "狐狸 生怪蛋", - "minecraft:pillager_spawn_egg": "掠奪者 生怪蛋", - "minecraft:wandering_trader_spawn_egg": "流浪商人 生怪蛋", - "minecraft:ravager_spawn_egg": "劫毀獸 生怪蛋", - "minecraft:bee_spawn_egg": "蜜蜂 生怪蛋", - "minecraft:strider_spawn_egg": "熾足獸 生怪蛋", - "minecraft:hoglin_spawn_egg": "豬布獸 生怪蛋", - "minecraft:piglin_spawn_egg": "豬布林 生怪蛋", - "minecraft:zoglin_spawn_egg": "豬屍獸 生怪蛋", - "minecraft:piglin_brute_spawn_egg": "豬布林蠻兵 生怪蛋", - "minecraft:sniffer_spawn_egg": "嗅探獸 生怪蛋", - "minecraft:breeze_spawn_egg": "旋風使者 生怪蛋", - "minecraft:axolotl_spawn_egg": "六角恐龍 生怪蛋", - "minecraft:goat_spawn_egg": "山羊 生怪蛋", - "minecraft:glow_squid_spawn_egg": "螢光魷魚 生怪蛋", - "minecraft:iron_golem_spawn_egg": "鐵魔像 生怪蛋", - "minecraft:snow_golem_spawn_egg": "雪人 生怪蛋", - "minecraft:ender_dragon_spawn_egg": "終界龍 生怪蛋", - "minecraft:wither_spawn_egg": "凋零怪 生怪蛋", + "minecraft:cat_spawn_egg": "貓生怪蛋", + "minecraft:panda_spawn_egg": "貓熊生怪蛋", + "minecraft:fox_spawn_egg": "狐狸生怪蛋", + "minecraft:pillager_spawn_egg": "掠奪者生怪蛋", + "minecraft:wandering_trader_spawn_egg": "流浪商人生怪蛋", + "minecraft:ravager_spawn_egg": "劫毀獸生怪蛋", + "minecraft:bee_spawn_egg": "蜜蜂生怪蛋", + "minecraft:strider_spawn_egg": "熾足獸生怪蛋", + "minecraft:hoglin_spawn_egg": "豬布獸生怪蛋", + "minecraft:piglin_spawn_egg": "豬布林生怪蛋", + "minecraft:zoglin_spawn_egg": "豬屍獸生怪蛋", + "minecraft:piglin_brute_spawn_egg": "豬布林蠻兵生怪蛋", + "minecraft:sniffer_spawn_egg": "嗅探獸生怪蛋", + "minecraft:breeze_spawn_egg": "旋風使者生怪蛋", + "minecraft:axolotl_spawn_egg": "六角恐龍生怪蛋", + "minecraft:goat_spawn_egg": "山羊生怪蛋", + "minecraft:glow_squid_spawn_egg": "螢光魷魚生怪蛋", + "minecraft:iron_golem_spawn_egg": "鐵魔像生怪蛋", + "minecraft:snow_golem_spawn_egg": "雪人生怪蛋", + "minecraft:ender_dragon_spawn_egg": "終界龍生怪蛋", + "minecraft:wither_spawn_egg": "凋零怪生怪蛋", "minecraft:glow_ink_sac": "螢光墨囊", "minecraft:copper_ingot": "銅錠", "minecraft:raw_iron": "鐵原礦", @@ -1639,6 +1827,8 @@ "minecraft:writable_book": "書和羽毛筆", "minecraft:written_book": "完成的書", "minecraft:emerald": "綠寶石", + "minecraft:frame": "物品展示框", + "minecraft:flower_pot": "花盆", "minecraft:empty_map": "空白地圖", "minecraft:carrot_on_a_stick": "胡蘿蔔釣竿", "minecraft:nether_star": "地獄之星", @@ -1648,26 +1838,27 @@ "minecraft:comparator": "紅石比較器", "minecraft:netherbrick": "地獄磚頭", "minecraft:quartz": "地獄石英", - "minecraft:tnt_minecart": "TT礦車", + "minecraft:tnt_minecart": "TNT 礦車", "minecraft:hopper_minecart": "漏斗礦車", + "minecraft:hopper": "漏斗", "minecraft:rabbit_foot": "兔子腳", "minecraft:rabbit_hide": "兔子皮", "minecraft:leather_horse_armor": "皮革製馬鎧", "minecraft:iron_horse_armor": "鐵製馬鎧", "minecraft:gold_horse_armor": "黃金製馬鎧", "minecraft:diamond_horse_armor": "鑽石製馬鎧", - "minecraft:music_disc_13": "唱片 (13)", - "minecraft:music_disc_cat": "唱片 (cat)", - "minecraft:music_disc_blocks": "唱片 (blocks)", - "minecraft:music_disc_chirp": "唱片 (chirp)", - "minecraft:music_disc_far": "唱片 (far)", - "minecraft:music_disc_mall": "唱片 (mall)", - "minecraft:music_disc_mellohi": "唱片 (mellohi)", - "minecraft:music_disc_stal": "唱片 (stal)", - "minecraft:music_disc_strad": "唱片 (strad)", - "minecraft:music_disc_ward": "唱片 (ward)", - "minecraft:music_disc_11": "唱片 (11)", - "minecraft:music_disc_wait": "唱片 (wait)", + "minecraft:music_disc_13": "唱片13", + "minecraft:music_disc_cat": "唱片cat", + "minecraft:music_disc_blocks": "唱片blocks", + "minecraft:music_disc_chirp": "唱片chirp", + "minecraft:music_disc_far": "唱片far", + "minecraft:music_disc_mall": "唱片mall", + "minecraft:music_disc_mellohi": "唱片mellohi", + "minecraft:music_disc_stal": "唱片stal", + "minecraft:music_disc_strad": "唱片strad", + "minecraft:music_disc_ward": "唱片ward", + "minecraft:music_disc_11": "唱片11", + "minecraft:music_disc_wait": "唱片wait", "minecraft:trident": "三叉戟", "minecraft:lead": "拴繩", "minecraft:name_tag": "命名牌", @@ -1675,11 +1866,16 @@ "minecraft:mutton": "生羊肉", "minecraft:cooked_mutton": "烤羊肉", "minecraft:armor_stand": "盔甲座", + "minecraft:spruce_door": "杉木門", + "minecraft:birch_door": "樺木門", + "minecraft:jungle_door": "叢林木門", + "minecraft:acacia_door": "相思木門", + "minecraft:dark_oak_door": "黑橡木門", "minecraft:chorus_fruit": "歌萊果", "minecraft:popped_chorus_fruit": "爆開的歌萊果", "minecraft:dragon_breath": "龍之吐息", - "minecraft:splash_potion": "扔出的飛濺藥水", - "minecraft:lingering_potion": "扔出的滯留藥水", + "minecraft:splash_potion": "飛濺藥水", + "minecraft:lingering_potion": "滯留藥水", "minecraft:command_block_minecart": "指令方塊礦車", "minecraft:elytra": "鞘翅", "minecraft:prismarine_shard": "海磷碎片", @@ -1689,7 +1885,7 @@ "minecraft:iron_nugget": "鐵粒", "minecraft:nautilus_shell": "鸚鵡螺殼", "minecraft:heart_of_the_sea": "海洋之心", - "minecraft:scute": "鱗甲", + "minecraft:scute": "海龜鱗甲", "minecraft:turtle_helmet": "海龜殼", "minecraft:phantom_membrane": "夜魅皮膜", "minecraft:crossbow": "弩", @@ -1708,11 +1904,13 @@ "minecraft:globe_banner_pattern": "地球旗幟圖案", "minecraft:flow_banner_pattern": "渦流旗幟圖案", "minecraft:guster_banner_pattern": "狂風旗幟圖案", + "minecraft:campfire": "營火", "minecraft:suspicious_stew": "可疑的燉湯", "minecraft:honeycomb": "蜂巢", "minecraft:honey_bottle": "蜂蜜瓶", "minecraft:ominous_bottle": "不祥之瓶", "minecraft:board": "黑板", + "minecraft:camera": "攝像機", "minecraft:compound": "化合物", "minecraft:ice_bomb": "冰彈", "minecraft:bleach": "漂白劑", @@ -1735,20 +1933,26 @@ "minecraft:netherite_scrap": "獄髓碎片", "minecraft:crimson_sign": "緋紅蕈木告示牌", "minecraft:warped_sign": "扭曲蕈木告示牌", + "minecraft:crimson_door": "緋紅蕈木門", + "minecraft:warped_door": "扭曲蕈木門", "minecraft:warped_fungus_on_a_stick": "扭曲蕈菇釣竿", - "minecraft:music_disc_pigstep": "唱片 (Pigstep)", + "minecraft:music_disc_pigstep": "唱片Pigstep", + "minecraft:nether_sprouts": "地獄芽", + "minecraft:soul_campfire": "靈魂營火", + "minecraft:glow_frame": "螢光物品展示框", "minecraft:amethyst_shard": "紫水晶碎片", "minecraft:spyglass": "望遠鏡", - "minecraft:music_disc_otherside": "唱片 (otherside)", + "minecraft:music_disc_otherside": "唱片otherside", "minecraft:goat_horn": "山羊角", - "minecraft:frog_spawn_egg": "青蛙 生怪蛋", - "minecraft:tadpole_spawn_egg": "蝌蚪 生怪蛋", + "minecraft:frog_spawn_egg": "青蛙生怪蛋", + "minecraft:tadpole_spawn_egg": "蝌蚪生怪蛋", "minecraft:tadpole_bucket": "蝌蚪桶", - "minecraft:allay_spawn_egg": "悅靈 生怪蛋", - "minecraft:warden_spawn_egg": "伏守者 生怪蛋", + "minecraft:allay_spawn_egg": "悅靈生怪蛋", + "minecraft:warden_spawn_egg": "伏守者生怪蛋", + "minecraft:mangrove_door": "紅樹林木門", "minecraft:mangrove_sign": "紅樹林木告示牌", "minecraft:mangrove_boat": "紅樹林木船", - "minecraft:music_disc_5": "唱片 (5)", + "minecraft:music_disc_5": "唱片5", "minecraft:disc_fragment_5": "唱片碎片(5)", "minecraft:oak_chest_boat": "儲物箱橡木船", "minecraft:birch_chest_boat": "儲物箱樺木船", @@ -1760,14 +1964,15 @@ "minecraft:chest_boat": "儲物箱船", "minecraft:recovery_compass": "回生羅盤", "minecraft:echo_shard": "回聲碎片", - "minecraft:trader_llama_spawn_egg": "商駝 生怪蛋", + "minecraft:trader_llama_spawn_egg": "商駝生怪蛋", "minecraft:cherry_boat": "櫻花木船", "minecraft:cherry_chest_boat": "儲物箱櫻花木船", "minecraft:cherry_sign": "櫻花木告示牌", "minecraft:bamboo_sign": "竹告示牌", "minecraft:bamboo_raft": "竹筏", "minecraft:bamboo_chest_raft": "儲物箱竹筏", - "minecraft:camel_spawn_egg": "生成駱駝", + "minecraft:camel_spawn_egg": "駱駝生怪蛋", + "minecraft:camel_husk_spawn_egg": "駱駝屍殼生怪蛋", "minecraft:angler_pottery_sherd": "垂釣陶器碎片", "minecraft:archer_pottery_sherd": "弓箭陶器碎片", "minecraft:arms_up_pottery_sherd": "人形陶器碎片", @@ -1792,36 +1997,98 @@ "minecraft:skull_pottery_sherd": "頭顱陶器碎片", "minecraft:snort_pottery_sherd": "嗅探陶器碎片", "minecraft:brush": "刷子", - "minecraft:netherite_upgrade_smithing_template": "獄髓升級鍛造模板", - "minecraft:sentry_armor_trim_smithing_template": "哨兵盔甲紋樣鍛造模板", - "minecraft:dune_armor_trim_smithing_template": "沙丘盔甲紋樣鍛造模板", - "minecraft:coast_armor_trim_smithing_template": "海岸盔甲紋樣鍛造模板", - "minecraft:wild_armor_trim_smithing_template": "荒野盔甲紋樣鍛造模板", - "minecraft:ward_armor_trim_smithing_template": "伏守盔甲紋樣鍛造模板", - "minecraft:eye_armor_trim_smithing_template": "眼眸盔甲紋樣鍛造模板", - "minecraft:vex_armor_trim_smithing_template": "惱鬼盔甲紋樣鍛造模板", - "minecraft:tide_armor_trim_smithing_template": "潮汐盔甲紋樣鍛造模板", - "minecraft:snout_armor_trim_smithing_template": "豬鼻盔甲紋樣鍛造模板", - "minecraft:rib_armor_trim_smithing_template": "肋骨盔甲紋樣鍛造模板", - "minecraft:spire_armor_trim_smithing_template": "旋塔盔甲紋樣鍛造模板", - "minecraft:silence_armor_trim_smithing_template": "寂靜盔甲紋樣鍛造模板", - "minecraft:wayfinder_armor_trim_smithing_template": "嚮導盔甲紋樣鍛造模板", - "minecraft:raiser_armor_trim_smithing_template": "牧者盔甲紋樣鍛造模板", - "minecraft:shaper_armor_trim_smithing_template": "工匠盔甲紋樣鍛造模板", - "minecraft:host_armor_trim_smithing_template": "主人盔甲紋樣鍛造模板", - "minecraft:flow_armor_trim_smithing_template": "渦流盔甲紋樣鍛造模板", - "minecraft:bolt_armor_trim_smithing_template": "鑲鉚盔甲紋樣鍛造模板", - "minecraft:music_disc_relic": "唱片 (Reilc)", + "minecraft:netherite_upgrade_smithing_template": "獄髓升級", + "minecraft:sentry_armor_trim_smithing_template": "哨兵盔甲紋樣", + "minecraft:dune_armor_trim_smithing_template": "沙丘盔甲紋樣", + "minecraft:coast_armor_trim_smithing_template": "海岸盔甲紋樣", + "minecraft:wild_armor_trim_smithing_template": "荒野盔甲紋樣", + "minecraft:ward_armor_trim_smithing_template": "伏守盔甲紋樣", + "minecraft:eye_armor_trim_smithing_template": "眼眸盔甲紋樣", + "minecraft:vex_armor_trim_smithing_template": "惱鬼盔甲紋樣", + "minecraft:tide_armor_trim_smithing_template": "潮汐盔甲紋樣", + "minecraft:snout_armor_trim_smithing_template": "豬鼻盔甲紋樣", + "minecraft:rib_armor_trim_smithing_template": "肋骨盔甲紋樣", + "minecraft:spire_armor_trim_smithing_template": "旋塔盔甲紋樣", + "minecraft:silence_armor_trim_smithing_template": "寂靜盔甲紋樣", + "minecraft:wayfinder_armor_trim_smithing_template": "嚮導盔甲紋樣", + "minecraft:raiser_armor_trim_smithing_template": "牧者盔甲紋樣", + "minecraft:shaper_armor_trim_smithing_template": "工匠盔甲紋樣", + "minecraft:host_armor_trim_smithing_template": "主人盔甲紋樣", + "minecraft:flow_armor_trim_smithing_template": "渦流盔甲紋樣", + "minecraft:bolt_armor_trim_smithing_template": "鑲鉚盔甲紋樣", + "minecraft:music_disc_relic": "唱片Relic", "minecraft:skull": "生物頭顱", "minecraft:stained_hardened_clay": "釉陶", - "minecraft:armadillo_spawn_egg": "犰狳 生怪蛋", + "minecraft:armadillo_spawn_egg": "犰狳生怪蛋", "minecraft:armadillo_scute": "犰狳鱗甲", "minecraft:wolf_armor": "狼鎧", "minecraft:pale_oak_boat": "蒼白橡木船", "minecraft:pale_oak_chest_boat": "儲物箱蒼白橡木船", "minecraft:pale_oak_sign": "蒼白橡木告示牌", - "minecraft:creaking_spawn_egg": "嘎枝 生怪蛋", + "minecraft:creaking_spawn_egg": "嘎枝生怪蛋", "minecraft:resin_brick": "樹脂磚頭", + "minecraft:blue_egg": "藍色雞蛋", + "minecraft:brown_egg": "棕色雞蛋", + "minecraft:happy_ghast_spawn_egg": "快樂幽靈生怪蛋", + "minecraft:black_harness": "黑色帽鞍", + "minecraft:blue_harness": "藍色帽鞍", + "minecraft:brown_harness": "棕色帽鞍", + "minecraft:cyan_harness": "青色帽鞍", + "minecraft:gray_harness": "灰色帽鞍", + "minecraft:green_harness": "綠色帽鞍", + "minecraft:light_blue_harness": "淺藍色帽鞍", + "minecraft:light_gray_harness": "淺灰色帽鞍", + "minecraft:lime_harness": "淺綠色帽鞍", + "minecraft:magenta_harness": "洋紅色帽鞍", + "minecraft:orange_harness": "橙色帽鞍", + "minecraft:pink_harness": "粉紅色帽鞍", + "minecraft:purple_harness": "紫色帽鞍", + "minecraft:red_harness": "紅色帽鞍", + "minecraft:white_harness": "白色帽鞍", + "minecraft:yellow_harness": "黃色帽鞍", + "minecraft:copper_golem_spawn_egg": "銅魔像生怪蛋", + "minecraft:copper_sword": "銅劍", + "minecraft:copper_shovel": "銅鏟", + "minecraft:copper_pickaxe": "銅鎬", + "minecraft:copper_axe": "銅斧", + "minecraft:copper_hoe": "銅鋤", + "minecraft:copper_helmet": "銅製頭盔", + "minecraft:copper_chestplate": "銅製胸甲", + "minecraft:copper_leggings": "銅製護腿", + "minecraft:copper_boots": "銅製靴子", + "minecraft:copper_nugget": "銅粒", + "minecraft:copper_horse_armor": "銅製馬鎧", + "minecraft:nautilus_spawn_egg": "鸚鵡螺生怪蛋", + "minecraft:zombie_nautilus_spawn_egg": "殭屍鸚鵡螺生怪蛋", + "minecraft:parched_spawn_egg": "枯骸生怪蛋", + "minecraft:copper_nautilus_armor": "銅製鸚鵡螺鎧", + "minecraft:iron_nautilus_armor": "鐵製鸚鵡螺鎧", + "minecraft:golden_nautilus_armor": "黃金製鸚鵡螺鎧", + "minecraft:diamond_nautilus_armor": "鑽石製鸚鵡螺鎧", + "minecraft:netherite_nautilus_armor": "獄髓製鸚鵡螺鎧", + "minecraft:netherite_horse_armor": "獄髓製馬鎧", + "minecraft:sulfur_cube_spawn_egg": "硫磺立方怪生怪蛋", + "minecraft:sulfur_cube_bucket": "硫磺立方怪桶", + "minecraft:poplar_boat": "楊木船", + "minecraft:poplar_chest_boat": "儲物箱楊木船", + "minecraft:poplar_sign": "楊木告示牌", + "minecraft:straw_bed": "Straw Bed", + "minecraft:black_cushion": "黑色坐墊", + "minecraft:blue_cushion": "藍色坐墊", + "minecraft:brown_cushion": "棕色坐墊", + "minecraft:cyan_cushion": "青色坐墊", + "minecraft:gray_cushion": "灰色坐墊", + "minecraft:green_cushion": "綠色坐墊", + "minecraft:light_blue_cushion": "淺藍色坐墊", + "minecraft:light_gray_cushion": "淺灰色坐墊", + "minecraft:lime_cushion": "淺綠色坐墊", + "minecraft:magenta_cushion": "洋紅色坐墊", + "minecraft:orange_cushion": "橙色坐墊", + "minecraft:pink_cushion": "粉紅色坐墊", + "minecraft:purple_cushion": "紫色坐墊", + "minecraft:red_cushion": "紅色坐墊", + "minecraft:white_cushion": "白色坐墊", + "minecraft:yellow_cushion": "黃色坐墊", "minecraft:wool": "羊毛", "minecraft:carpet": "地毯", "minecraft:log": "原木", @@ -1840,14 +2107,14 @@ "minecraft:coral_fan_dead": "死亡的扇狀珊瑚", "minecraft:sapling": "樹苗", "minecraft:leaves": "樹葉", - "minecraft:leaves2": "樹葉", + "minecraft:leaves2": "樹葉2", "minecraft:wooden_slab": "木製半磚", "minecraft:red_flower": "小型花", "minecraft:double_plant": "大型花", - "minecraft:planks": "Plank", + "minecraft:planks": "木材", "minecraft:coral": "珊瑚", "minecraft:tallgrass": "芒草", - "minecraft:log2": "原木", + "minecraft:log2": "原木2", "minecraft:monster_egg": "蛀蝕的方塊", "minecraft:concrete": "混凝土", "minecraft:concrete_powder": "混凝土粉末", @@ -1855,9 +2122,12 @@ "minecraft:stained_glass_pane": "染色玻璃片", "minecraft:shulker_box": "染色的界伏盒", "minecraft:wood": "木塊", - "minecraft:music_disc_creator": "唱片 (Creator)", - "minecraft:music_disc_creator_music_box": "唱片 (Creator 音樂盒)", - "minecraft:music_disc_precipice": "唱片 (Precipice)", + "minecraft:music_disc_creator": "唱片Creator", + "minecraft:music_disc_creator_music_box": "唱片Creator(音樂盒)", + "minecraft:music_disc_precipice": "唱片Precipice", + "minecraft:music_disc_tears": "唱片Tears", + "minecraft:music_disc_lava_chicken": "唱片Lava Chicken", + "minecraft:music_disc_bounce": "唱片Bounce", "minecraft:chemistry_table": "化學裝置", "minecraft:hard_stained_glass": "染色強化玻璃", "minecraft:hard_stained_glass_pane": "染色強化玻璃片", @@ -1868,9 +2138,43 @@ "minecraft:dye": "染料", "minecraft:banner_pattern": "旗幟圖案", "minecraft:spawn_egg": "生怪蛋", + "minecraft:red_shrub": "紅灌木", + "minecraft:shelf_mushroom": "層孔菇", "minecraft:end_crystal": "終界水晶", - "minecraft:glow_berries": "螢光莓", + "minecraft:copper_spear": "銅矛", + "minecraft:diamond_spear": "鑽石矛", + "minecraft:golden_spear": "金矛", + "minecraft:iron_spear": "鐵矛", + "minecraft:netherite_spear": "獄髓矛", + "minecraft:stone_spear": "石矛", + "minecraft:wooden_spear": "木矛", + "minecraft:black_bundle": "黑色束口袋", + "minecraft:blue_bundle": "藍色束口袋", + "minecraft:brown_bundle": "棕色束口袋", + "minecraft:bundle": "束口袋", + "minecraft:cyan_bundle": "青色束口袋", + "minecraft:gray_bundle": "灰色束口袋", + "minecraft:green_bundle": "綠色束口袋", + "minecraft:light_blue_bundle": "淺藍色束口袋", + "minecraft:light_gray_bundle": "淺灰色束口袋", + "minecraft:lime_bundle": "淺綠色束口袋", + "minecraft:magenta_bundle": "洋紅色束口袋", + "minecraft:orange_bundle": "橙色束口袋", + "minecraft:pink_bundle": "粉紅色束口袋", + "minecraft:purple_bundle": "紫色束口袋", + "minecraft:red_bundle": "紅色束口袋", + "minecraft:white_bundle": "白色束口袋", + "minecraft:yellow_bundle": "黃色束口袋", + "minecraft:breeze_rod": "旋風桿", + "minecraft:ominous_trial_key": "不祥試煉鑰匙", + "minecraft:trial_key": "試煉鑰匙", + "minecraft:wind_charge": "風彈", + "minecraft:apple": "蘋果", + "minecraft:glow_berries": "螢光莓" + }, + "entity": { "minecraft:undefined_test_only": "Undefined Test Only", + "minecraft:chicken": "雞", "minecraft:cow": "牛", "minecraft:pig": "豬", "minecraft:sheep": "綿羊", @@ -1878,6 +2182,7 @@ "minecraft:villager": "舊版村民", "minecraft:mooshroom": "哞菇", "minecraft:squid": "魷魚", + "minecraft:rabbit": "兔子", "minecraft:bat": "蝙蝠", "minecraft:iron_golem": "鐵魔像", "minecraft:snow_golem": "雪人", @@ -1896,20 +2201,21 @@ "minecraft:skeleton": "骷髏", "minecraft:spider": "蜘蛛", "minecraft:zombie_pigman": "殭屍化豬布林", + "minecraft:slime": "史萊姆", "minecraft:enderman": "終界使者", "minecraft:silverfish": "蠹魚", "minecraft:cave_spider": "洞穴蜘蛛", "minecraft:ghast": "地獄幽靈", "minecraft:magma_cube": "岩漿立方怪", "minecraft:blaze": "烈焰使者", - "minecraft:zombie_villager": "殭屍村民", + "minecraft:zombie_villager": "舊版殭屍村民", "minecraft:witch": "女巫", "minecraft:stray": "流髑", "minecraft:husk": "屍殼", "minecraft:wither_skeleton": "凋零骷髏", "minecraft:guardian": "深海守衛", "minecraft:elder_guardian": "遠古深海守衛", - "minecraft:npc": "PC", + "minecraft:npc": "NPC", "minecraft:wither": "凋零怪", "minecraft:ender_dragon": "終界龍", "minecraft:shulker": "界伏蚌", @@ -1918,10 +2224,13 @@ "minecraft:vindicator": "衛道士", "minecraft:phantom": "夜魅", "minecraft:ravager": "劫毀獸", + "minecraft:armor_stand": "盔甲座", "minecraft:tripod_camera": "攝像機", "minecraft:player": "玩家", "minecraft:item": "掉落物", + "minecraft:tnt": "點燃的TNT", "minecraft:falling_block": "掉落的方塊", + "minecraft:moving_block": "移動中的活塞", "minecraft:xp_bottle": "扔出的經驗瓶", "minecraft:xp_orb": "經驗球", "minecraft:eye_of_ender_signal": "扔出的終界之眼", @@ -1932,24 +2241,44 @@ "minecraft:cat": "流浪貓", "minecraft:shulker_bullet": "界伏彈", "minecraft:fishing_hook": "浮標", + "minecraft:chalkboard": "黑板", "minecraft:dragon_fireball": "終界龍火球", + "minecraft:arrow": "射出的箭", + "minecraft:snowball": "扔出的雪球", + "minecraft:egg": "扔出的雞蛋", + "minecraft:painting": "畫", + "minecraft:minecart": "礦車", "minecraft:fireball": "地獄幽靈火球", + "minecraft:splash_potion": "扔出的飛濺藥水", + "minecraft:ender_pearl": "扔出的終界珍珠", "minecraft:leash_knot": "拴繩結", "minecraft:wither_skull": "凋零頭顱", + "minecraft:boat": "船", "minecraft:wither_skull_dangerous": "藍色凋零怪之首", "minecraft:lightning_bolt": "閃電", "minecraft:small_fireball": "小火球", "minecraft:area_effect_cloud": "藥水效果雲", + "minecraft:hopper_minecart": "漏斗礦車", + "minecraft:tnt_minecart": "TNT礦車", + "minecraft:chest_minecart": "儲物箱礦車", + "minecraft:command_block_minecart": "指令方塊礦車", + "minecraft:lingering_potion": "扔出的滯留藥水", "minecraft:llama_spit": "駱馬唾液", "minecraft:evocation_fang": "尖牙", "minecraft:evocation_illager": "喚魔者", "minecraft:vex": "惱鬼", + "minecraft:ice_bomb": "冰彈", + "minecraft:balloon": "氣球", + "minecraft:pufferfish": "河豚", + "minecraft:salmon": "鮭魚", "minecraft:drowned": "沉屍", "minecraft:tropicalfish": "熱帶魚", + "minecraft:cod": "鱈魚", "minecraft:panda": "貓熊", "minecraft:pillager": "掠奪者", "minecraft:villager_v2": "村民", "minecraft:zombie_villager_v2": "殭屍村民", + "minecraft:shield": "盾牌", "minecraft:wandering_trader": "流浪商人", "minecraft:elder_guardian_ghost": "遠古深海守衛幽靈", "minecraft:fox": "狐狸", @@ -1976,5 +2305,15 @@ "minecraft:bogged": "沼骸", "minecraft:ominous_item_spawner": "不祥物品生成器", "minecraft:creaking": "嘎枝", - "minecraft:trader_llama": "商駝" -} \ No newline at end of file + "minecraft:happy_ghast": "快樂幽靈", + "minecraft:copper_golem": "銅魔像", + "minecraft:nautilus": "鸚鵡螺", + "minecraft:zombie_nautilus": "殭屍鸚鵡螺", + "minecraft:parched": "枯骸", + "minecraft:camel_husk": "駱駝屍殼", + "minecraft:sulfur_cube": "硫磺立方怪", + "minecraft:cushion": "坐墊", + "minecraft:trader_llama": "商駝", + "minecraft:chest_boat": "儲物箱船" + } +} diff --git a/src/mod/Events/BlockEventHandle.cpp b/src/mod/Events/BlockEventHandle.cpp index 2a51b42..f1b5bf8 100644 --- a/src/mod/Events/BlockEventHandle.cpp +++ b/src/mod/Events/BlockEventHandle.cpp @@ -1,5 +1,6 @@ #include "mod/Events/BlockEventHandle.h" +#include #include #include #include @@ -12,9 +13,6 @@ namespace stats { namespace event { namespace block { -namespace { -auto& playerStatsMap = getPlayerStatsMap(); -} const Block& getBlockByBlockPos(BlockPos const& pos, DimensionType id) { auto dimension = ll::service::getLevel()->getDimension(id).lock(); @@ -25,34 +23,28 @@ const Block& getBlockByBlockPos(BlockPos const& pos, DimensionType id) { void onBlockDestroyedByPlayer(BlockPos const& pos, Player& player) { if (player.isSimulatedPlayer()) return; if (player.isCreative()) return; - auto uuid = player.getUuid(); - auto& bl = getBlockByBlockPos(pos, player.getDimensionId()); - auto findPlayer = playerStatsMap.find(uuid); - if (findPlayer == playerStatsMap.end()) return; - auto playerStats = findPlayer->second; + auto uuid = player.getUuid(); + auto& bl = getBlockByBlockPos(pos, player.getDimensionId()); + auto* playerStats = findPlayerStats(uuid); if (!playerStats) return; playerStats->addStats(StatsType::mined, bl.getTypeName()); } void onBlockPlacedByPlayer(BlockPos const& pos, Player& player) { if (player.isSimulatedPlayer()) return; - auto& bl = getBlockByBlockPos(pos, player.getDimensionId()); - auto uuid = player.getUuid(); - auto findPlayer = playerStatsMap.find(uuid); - if (findPlayer == playerStatsMap.end()) return; - auto playerStats = findPlayer->second; + auto& bl = getBlockByBlockPos(pos, player.getDimensionId()); + auto uuid = player.getUuid(); + auto* playerStats = findPlayerStats(uuid); if (!playerStats) return; playerStats->addStats(StatsType::used, bl.getTypeName()); } void onBlockUsed(BlockPos const& pos, Player& player) { if (player.isSimulatedPlayer()) return; - auto& block = getBlockByBlockPos(pos, player.getDimensionId()); - auto blockType = block.getTypeName(); - auto uuid = player.getUuid(); - auto findPlayer = playerStatsMap.find(uuid); - if (findPlayer == playerStatsMap.end()) return; - auto playerStats = findPlayer->second; + auto& block = getBlockByBlockPos(pos, player.getDimensionId()); + auto blockType = block.getTypeName(); + auto uuid = player.getUuid(); + auto* playerStats = findPlayerStats(uuid); if (!playerStats) return; auto it = CustomInteractBlockMap.find(blockType); if (it == CustomInteractBlockMap.end()) return; @@ -62,10 +54,8 @@ void onBlockUsed(BlockPos const& pos, Player& player) { void onNoteBlockAttacked(Player* player) { if (player->isSimulatedPlayer()) return; - auto uuid = player->getUuid(); - auto findPlayer = playerStatsMap.find(uuid); - if (findPlayer == playerStatsMap.end()) return; - auto playerStats = findPlayer->second; + auto uuid = player->getUuid(); + auto* playerStats = findPlayerStats(uuid); if (!playerStats) return; playerStats->addCustomStats(CustomType::play_noteblock); return; @@ -73,21 +63,17 @@ void onNoteBlockAttacked(Player* player) { void onCakeBlockRemovedSlice(Player& player) { if (player.isSimulatedPlayer()) return; - auto uuid = player.getUuid(); - auto findPlayer = playerStatsMap.find(uuid); - if (findPlayer == playerStatsMap.end()) return; - auto playerStats = findPlayer->second; + auto uuid = player.getUuid(); + auto* playerStats = findPlayerStats(uuid); if (!playerStats) return; playerStats->addCustomStats(CustomType::eat_cake_slice); return; } -void onCauldronBlockUseInventory(Player& player, ItemStack& currentItem, ItemStack& replaceItem, int useCount) { +void onCauldronBlockUseInventory(Player& player, ItemStack& currentItem) { if (player.isSimulatedPlayer()) return; - auto uuid = player.getUuid(); - auto findPlayer = playerStatsMap.find(uuid); - if (findPlayer == playerStatsMap.end()) return; - auto playerStats = findPlayer->second; + auto uuid = player.getUuid(); + auto* playerStats = findPlayerStats(uuid); if (!playerStats) return; // JE原版装"水"(用桶装水 岩浆 细雪) 取"水"(桶、空瓶 水瓶交互) // 此处后续待修改 @@ -106,10 +92,8 @@ void onCauldronBlockClean( ) { if (player.isSimulatedPlayer()) return; if (interactionType != ::MinecraftEventing::POIBlockInteractionType::ClearItem) return; - auto uuid = player.getUuid(); - auto findPlayer = playerStatsMap.find(uuid); - if (findPlayer == playerStatsMap.end()) return; - auto playerStats = findPlayer->second; + auto uuid = player.getUuid(); + auto* playerStats = findPlayerStats(uuid); if (!playerStats) return; // 潜影盒 if ((itemId >= -627 && itemId <= -613) || itemId == 218) @@ -122,10 +106,8 @@ void onCauldronBlockClean( void onFlowerPotBlockPlaceFlower(Player& player) { if (player.isSimulatedPlayer()) return; - auto uuid = player.getUuid(); - auto findPlayer = playerStatsMap.find(uuid); - if (findPlayer == playerStatsMap.end()) return; - auto playerStats = findPlayer->second; + auto uuid = player.getUuid(); + auto* playerStats = findPlayerStats(uuid); if (!playerStats) return; playerStats->addCustomStats(CustomType::pot_flower); return; @@ -133,29 +115,28 @@ void onFlowerPotBlockPlaceFlower(Player& player) { void onProjectileHitTargetBlock(Actor const& projectile) { auto mob = projectile.getOwner(); - if (!mob||!mob->isType(::ActorType::Player)) return; + if (!mob || !mob->isType(::ActorType::Player)) return; Player* player = mob->getEntityContext().getWeakRef().tryUnwrap(); if (!player) return; - auto uuid = player->getUuid(); - auto findPlayer = playerStatsMap.find(uuid); - if (findPlayer == playerStatsMap.end()) return; - auto playerStats = findPlayer->second; + auto uuid = player->getUuid(); + auto* playerStats = findPlayerStats(uuid); if (!playerStats) return; playerStats->addCustomStats(CustomType::target_hit); return; } void onFallOn(Actor& actor, float fallDistance) { if (!actor.isType(::ActorType::Player)) return; + if (!std::isfinite(fallDistance) || fallDistance < 1.0f) return; Player* player = actor.getEntityContext().getWeakRef().tryUnwrap(); if (!player) return; - auto uuid = player->getUuid(); - auto findPlayer = playerStatsMap.find(uuid); - if (findPlayer == playerStatsMap.end()) return; - auto playerStats = findPlayer->second; + auto uuid = player->getUuid(); + auto* playerStats = findPlayerStats(uuid); if (!playerStats) return; - uint64_t value = static_cast(fallDistance * 100); + auto const centimeters = static_cast(fallDistance) * 100.0; + if (centimeters >= 18446744073709551616.0) return; + auto const value = static_cast(std::floor(centimeters)); playerStats->addCustomStats(CustomType::fall_one_cm, value); } } // namespace block } // namespace event -} // namespace stats \ No newline at end of file +} // namespace stats diff --git a/src/mod/Events/BlockEventHandle.h b/src/mod/Events/BlockEventHandle.h index 52031e8..df5950d 100644 --- a/src/mod/Events/BlockEventHandle.h +++ b/src/mod/Events/BlockEventHandle.h @@ -14,7 +14,7 @@ void onBlockPlacedByPlayer(BlockPos const& pos,Player& player); void onBlockUsed(BlockPos const& pos, Player& player); void onNoteBlockAttacked(Player* player); void onCakeBlockRemovedSlice(Player& player); -void onCauldronBlockUseInventory(Player& player, ItemStack& currentItem, ItemStack& replaceItem, int useCount); +void onCauldronBlockUseInventory(Player& player, ItemStack& currentItem); void onCauldronBlockClean( Player const& player, short itemId, @@ -25,4 +25,4 @@ void onProjectileHitTargetBlock(Actor const& projectile); void onFallOn(Actor& actor,float fallDistance); } // namespace block } // namespace event -} // namespace Stats \ No newline at end of file +} // namespace Stats diff --git a/src/mod/Events/Events.cpp b/src/mod/Events/Events.cpp index cd3b559..5f7b0da 100644 --- a/src/mod/Events/Events.cpp +++ b/src/mod/Events/Events.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -12,12 +13,18 @@ #include #include #include +#include #include +#include +#include +#include +#include +#include #include #include "mod/Events/BlockEventHandle.h" -#include "mod/Events/PlayerEventHandle.h" - +#include "mod/Stats/Handlers/PlayerStatsHandlers.h" +#include "mod/Stats/Stats.h" namespace stats { namespace event { @@ -32,7 +39,7 @@ ll::event::ListenerPtr playerSneakingListener; ll::event::ListenerPtr playerSneakedListener; ll::event::ListenerPtr playerSprintingListener; ll::event::ListenerPtr playerSprintedListener; -ll::event::ListenerPtr playerPlacedBlockListener; +ll::event::ListenerPtr actorHurtListener; ll::event::ListenerPtr mobDieListener; } // namespace @@ -42,13 +49,13 @@ void listenEvents() { // PlayerJoin playerJoinListener = eventBus.emplaceListener([](ll::event::PlayerJoinEvent& event) { - player::onJoin(event.self()); + handler::onPlayerJoin(event.self()); }); // PlayerDisconnect playerDisconnectListener = eventBus.emplaceListener([](ll::event::PlayerDisconnectEvent& event) { - player::onLeft(event.self()); + handler::onPlayerDisconnect(event.self()); }); // PlayerDestroyBlock @@ -61,42 +68,74 @@ void listenEvents() { // PlayerPickUpItem playerPickUpItemListener = eventBus.emplaceListener( [](ll::event::player::PlayerPickUpItemEvent& event) { - player::onTakeItem(event.self(), event.itemActor().item()); + handler::onPlayerPickUpItem(event.self(), event.itemActor().item()); } ); // PlayerDie playerDieListener = eventBus.emplaceListener([](ll::event::player::PlayerDieEvent& event) { - player::onDied(event.self(), event.source()); + handler::onPlayerDied(event.self(), event.source()); }); // PlayerJump playerJumpListener = eventBus.emplaceListener([](ll::event::player::PlayerJumpEvent& event) { - player::onJump(event.self()); + handler::onPlayerJump(event.self()); }); playerSneakingListener = eventBus.emplaceListener([](ll::event::PlayerSneakingEvent& event) { - player::onSneaking(event.self()); + handler::onPlayerStartSneaking(event.self()); }); playerSneakedListener = eventBus.emplaceListener([](ll::event::PlayerSneakedEvent& event) { - player::onSneaked(event.self()); + handler::onPlayerStopSneaking(event.self()); }); playerSprintingListener = eventBus.emplaceListener([](ll::event::PlayerSprintingEvent& event) { - player::onSprinting(event.self()); + handler::onPlayerStartSprinting(event.self()); }); playerSprintedListener = eventBus.emplaceListener([](ll::event::PlayerSprintedEvent& event) { - player::onSprinted(event.self()); + handler::onPlayerStopSprinting(event.self()); }); + actorHurtListener = eventBus.emplaceListener( + [](ll::event::entity::ActorHurtEvent& event) { + if (event.isCancelled()) return; + + auto& victim = event.self(); + auto const& source = event.source(); + auto const damage = event.damage(); + + if (victim.isType(ActorType::Player)) { + if (auto* player = victim.getEntityContext().getWeakRef().tryUnwrap().as_ptr()) { + handler::onPlayerTakenDamage(player, damage); + } + } + + // Preserve the previous Hook's scope: direct player damage only. + if (!source.isEntitySource() || source.isChildEntitySource()) return; + + auto level = ll::service::getLevel(); + if (!level) return; + + auto* attacker = level->fetchEntity(source.getDamagingEntityUniqueID(), false); + if (!attacker || !attacker->isType(ActorType::Player)) return; + + auto* player = attacker->getEntityContext().getWeakRef().tryUnwrap().as_ptr(); + auto* mob = victim.getEntityContext().getWeakRef().tryUnwrap().as_ptr(); + if (!player || !mob) return; + + handler::onPlayerDealtDamage(mob, player, damage); + }, + ll::event::EventPriority::Lowest + ); + mobDieListener = eventBus.emplaceListener([](ll::event::entity::MobDieEvent& event) { auto& mob = event.self(); @@ -108,9 +147,9 @@ void listenEvents() { if (source.isChildEntitySource()) actor = actor->getOwner(); } if (!actor) return; - if (actor->getTypeName() != "minecraft:player") return; + if (!actor->isType(ActorType::Player)) return; auto& player = *static_cast<::Player*>(actor); - player::onKillMob(player, mob); + handler::onPlayerKillMob(player, mob); }); } @@ -124,11 +163,11 @@ void removeEvents() { eventBus.removeListener(playerSneakedListener); eventBus.removeListener(playerSprintingListener); eventBus.removeListener(playerSprintedListener); + eventBus.removeListener(actorHurtListener); eventBus.removeListener(playerDieListener); eventBus.removeListener(playerJumpListener); - eventBus.removeListener(playerPlacedBlockListener); eventBus.removeListener(mobDieListener); } } // namespace event -}; // namespace stats \ No newline at end of file +}; // namespace stats diff --git a/src/mod/Events/PlayerEventHandle.cpp b/src/mod/Events/PlayerEventHandle.cpp deleted file mode 100644 index 1617d9d..0000000 --- a/src/mod/Events/PlayerEventHandle.cpp +++ /dev/null @@ -1,455 +0,0 @@ -#include "mod/Events/PlayerEventHandle.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "mod/Stats/Stats.h" - -namespace stats { -namespace event { -namespace player { -namespace { -auto& playerStatsMap = getPlayerStatsMap(); -} - -void onJoin(Player& player) { - if (player.isSimulatedPlayer()) return; - // PlayerStats* playerStats = new PlayerStats(player); - auto playerStats = std::make_shared(player); - playerStatsMap.emplace(playerStats->getUuid(), playerStats); -} - -void onLeft(ServerPlayer& player) { - auto uuid = player.getUuid(); - auto findPlayer = playerStatsMap.find(uuid); - if (findPlayer == playerStatsMap.end()) return; - auto playerStats = findPlayer->second; - if (!playerStats) return; - playerStats->addCustomStats(CustomType::play_time, player.mTickCount); - playerStats->addCustomStats(CustomType::leave_game); - playerStats->saveData(); - // delete playerStats; - playerStatsMap.erase(uuid); -} - -void onSneaking(Player& player) { - auto uuid = player.getUuid(); - auto findPlayer = playerStatsMap.find(uuid); - if (findPlayer == playerStatsMap.end()) return; - auto playerStats = findPlayer->second; - if (!playerStats) return; - playerStats->startSneaking(); -} - -void onSneaked(Player& player) { - auto uuid = player.getUuid(); - auto findPlayer = playerStatsMap.find(uuid); - if (findPlayer == playerStatsMap.end()) return; - auto playerStats = findPlayer->second; - if (!playerStats) return; - playerStats->stopSneaking(); -} - -void onSprinting(Player& player) { - auto uuid = player.getUuid(); - auto findPlayer = playerStatsMap.find(uuid); - if (findPlayer == playerStatsMap.end()) return; - auto playerStats = findPlayer->second; - if (!playerStats) return; - playerStats->mDistanceCache.isSprinting = true; -} -void onSprinted(Player& player) { - auto uuid = player.getUuid(); - auto findPlayer = playerStatsMap.find(uuid); - if (findPlayer == playerStatsMap.end()) return; - auto playerStats = findPlayer->second; - if (!playerStats) return; - playerStats->mDistanceCache.isSprinting = false; -} - -void onStartRiding(mce::UUID uuid, Actor& vehicle, bool forceRiding) { - auto findPlayer = playerStatsMap.find(uuid); - if (findPlayer == playerStatsMap.end()) return; - auto playerStats = findPlayer->second; - if (!playerStats) return; - playerStats->mDistanceCache.ride = 0; -} -void onStopRiding(mce::UUID uuid, Actor* vehicle) { - auto findPlayer = playerStatsMap.find(uuid); - if (findPlayer == playerStatsMap.end()) return; - auto playerStats = findPlayer->second; - if (!playerStats) return; - auto value = playerStats->mDistanceCache.ride; - if (!vehicle) { - playerStats->mDistanceCache.ride = 0; - return; - } - if (vehicle->hasCategory(::ActorCategory::BoatRideable)) { - playerStats->addCustomStats(CustomType::boat_one_cm, value); - } else if (vehicle->hasCategory(::ActorCategory::MinecartRidable)) { - playerStats->addCustomStats(CustomType::minecart_one_cm, value); - } else if (vehicle->isType(::ActorType::Horse)) { - playerStats->addCustomStats(CustomType::horse_one_cm, value); - } else if (vehicle->isType(::ActorType::Pig)) { - playerStats->addCustomStats(CustomType::pig_one_cm, value); - } else if (vehicle->isType(::ActorType::Strider)) { - playerStats->addCustomStats(CustomType::strider_one_cm, value); - } else if (vehicle->isType(ActorType::HappyGhast)) { - playerStats->addCustomStats(CustomType::happy_ghast_one_cm, value); - } else if (vehicle->isType(::ActorType::Nautilus)) { - playerStats->addCustomStats(CustomType::nautilus_one_cm, value); - } - playerStats->mDistanceCache.ride = 0; -} - -void onAuthInput(ServerPlayer& player, PlayerAuthInputPacket const& packet) { - auto uuid = player.getUuid(); - auto findPlayer = playerStatsMap.find(uuid); - if (findPlayer == playerStatsMap.end()) return; - auto playerStats = findPlayer->second; - if (!playerStats) return; - auto pos = const_cast(player.getPosition()); - auto dimId = player.getDimensionId().mValue; - if (playerStats->mLastDimensionId != dimId) { - playerStats->mLastDimensionId = dimId; - playerStats->mLastPos = pos; - return; - } - // packet.mPosDelta - - if (player.isRiding()) { - auto value = static_cast(std::floor(player.getPosition().distanceTo(playerStats->mLastPos) * 100)); - playerStats->mDistanceCache.ride += value; - playerStats->mLastPos = pos; - } else { - // 部分情况下可能检测不到玩家停止骑行 - playerStats->mDistanceCache.ride = 0; - - if (player.isInWaterOrRain()) { - auto value = static_cast(std::floor(packet.mPosDelta->length() * 100)); - if (player.isSwimming()) { - playerStats->addCustomStats(CustomType::swim_one_cm, value); - } else if (player._isHeadInWater()) { - playerStats->addCustomStats(CustomType::walk_on_water_one_cm, value); - } else { - playerStats->addCustomStats(CustomType::walk_under_water_one_cm, value); - } - } else if (player.isFlying()) { - auto value = static_cast(std::floor(packet.mPosDelta->length() * 100)); - playerStats->addCustomStats(CustomType::fly_one_cm, value); - } else if (player.isOnGround()) { - auto posOffset = Vec3{0, -0.0784, 0}; - auto value = static_cast(std::floor(packet.mPosDelta->distanceTo(posOffset) * 100)); - if (playerStats->mDistanceCache.isSneaking) { - playerStats->mDistanceCache.sneak += value; - } else if (playerStats->mDistanceCache.isSprinting) { - playerStats->addCustomStats(CustomType::sprint_one_cm, value); - } else { - playerStats->addCustomStats(CustomType::walk_one_cm, value); - } - } else if (player.onClimbableBlock()) { - auto valueY = packet.mPosDelta->y + 0.0784; - auto value2 = static_cast(std::floor(valueY > 0 ? valueY * 100 : 0)); - playerStats->addCustomStats(CustomType::climb_one_cm, value2); - } else if (playerStats->mDistanceCache.isGliding) { - auto value = static_cast(std::floor(packet.mPosDelta->length() * 100)); - playerStats->addCustomStats(CustomType::aviate_one_cm, value); - } - } - if (packet.mInputData->test((size_t)PlayerAuthInputPacket::InputData::StartGliding)) { - playerStats->mDistanceCache.isGliding = true; - } else if (packet.mInputData->test((size_t)PlayerAuthInputPacket::InputData::StopGliding)) { - playerStats->mDistanceCache.isGliding = false; - } -} - -void onTakeItem(Player& player, ItemStack& item) { - auto uuid = player.getUuid(); - auto findPlayer = playerStatsMap.find(uuid); - if (findPlayer == playerStatsMap.end()) return; - auto playerStats = findPlayer->second; - if (!playerStats) return; - playerStats->addStats(StatsType::picked_up, item.getTypeName(), item.mCount); -} - -void onDropItem(Player* player, ItemStack const& item) { - auto uuid = player->getUuid(); - auto findPlayer = playerStatsMap.find(uuid); - if (findPlayer == playerStatsMap.end()) return; - auto playerStats = findPlayer->second; - if (!playerStats) return; - playerStats->addCustomStats(CustomType::drop); - playerStats->addStats(StatsType::dropped, item.getTypeName(), item.mCount); -} - -void onDied(Player& player, ActorDamageSource const& source) { - auto uuid = player.getUuid(); - auto findPlayer = playerStatsMap.find(uuid); - if (findPlayer == playerStatsMap.end()) return; - auto playerStats = findPlayer->second; - if (!playerStats) return; - playerStats->addCustomStats(CustomType::deaths); - // resetCustomSinceTime TODO - if (!source.isEntitySource()) return; - Actor* actor = nullptr; - actor = ll::service::getLevel()->fetchEntity(source.getDamagingEntityUniqueID(), false); - if (!actor) return; - if (source.isChildEntitySource()) { - actor = actor->getOwner(); - } - if (!actor) return; - playerStats->addStats(StatsType::killed_by, actor->getTypeName()); -} - -void onKillMob(Player& player, Mob& mob) { - auto uuid = player.getUuid(); - auto findPlayer = playerStatsMap.find(uuid); - if (findPlayer == playerStatsMap.end()) return; - auto playerStats = findPlayer->second; - if (!playerStats) return; - if (mob.isType(::ActorType::Player)) { - playerStats->addCustomStats(CustomType::player_kills); - } else { - playerStats->addCustomStats(CustomType::mob_kills); - } - playerStats->addStats(StatsType::killed, mob.getTypeName()); -} - -void onTakenDamage(Player* player, float damage, float afterDamage) { - auto uuid = player->getUuid(); - auto findPlayer = playerStatsMap.find(uuid); - if (findPlayer == playerStatsMap.end()) return; - auto playerStats = findPlayer->second; - if (!playerStats) return; - - auto heath = player->getHealth(); - auto absorption = player->getAttribute(SharedAttributes::ABSORPTION()).mPtr->mCurrentValue; - float damage_taken = afterDamage > 0 ? afterDamage : -afterDamage; - float damage_absobed = 0; - if (absorption > 0) { - if (damage_taken >= absorption) { - damage_taken = damage_taken - absorption; - damage_absobed = absorption; - } else { - damage_taken = 0; - damage_absobed = damage_taken; - } - } - float resistanceDamage = damage - afterDamage; - resistanceDamage = resistanceDamage > 0 ? resistanceDamage : -resistanceDamage; - damage_taken = damage_taken < heath ? damage_taken : heath; - playerStats->addCustomStats(CustomType::damage_resisted, static_cast(resistanceDamage * 10)); - playerStats->addCustomStats(CustomType::damage_absorbed, static_cast(damage_absobed * 10)); - playerStats->addCustomStats(CustomType::damage_taken, static_cast(damage_taken * 10)); -} - -void onDealtDamage(Mob* mob, Player* player, float damage, float afterDamage) { - auto uuid = player->getUuid(); - auto findPlayer = playerStatsMap.find(uuid); - if (findPlayer == playerStatsMap.end()) return; - auto playerStats = findPlayer->second; - if (!playerStats) return; - - auto heath = mob->getHealth(); - auto absorption = mob->getAttribute(SharedAttributes::ABSORPTION()).mPtr->mCurrentValue; - float damage_taken = afterDamage > 0 ? afterDamage : -afterDamage; - float damage_absobed = 0; - if (absorption > 0) { - if (damage_taken >= absorption) { - damage_taken = damage_taken - absorption; - damage_absobed = absorption; - } else { - damage_taken = 0; - damage_absobed = damage_taken; - } - } - float resistanceDamage = damage - afterDamage; - resistanceDamage = resistanceDamage > 0 ? resistanceDamage : -resistanceDamage; - damage_taken = damage_taken < heath ? damage_taken : heath; - playerStats->addCustomStats(CustomType::damage_dealt_resisted, static_cast(resistanceDamage * 10)); - playerStats->addCustomStats(CustomType::damage_dealt_absorbed, static_cast(damage_absobed * 10)); - playerStats->addCustomStats(CustomType::damage_dealt, static_cast(damage_taken * 10)); -} - -void onCraftedItem(mce::UUID uuid, std::string itemType, int amount, SharedTypes::Legacy::ContainerType type) { - auto findPlayer = playerStatsMap.find(uuid); - if (findPlayer == playerStatsMap.end()) return; - auto playerStats = findPlayer->second; - if (!playerStats) return; - switch (type) { - case SharedTypes::Legacy::ContainerType::Trade: - playerStats->addStats(StatsType::crafted, itemType, amount); - playerStats->addCustomStats(CustomType::traded_with_villager); - break; - case SharedTypes::Legacy::ContainerType::Enchantment: - playerStats->addCustomStats(CustomType::enchant_item); - break; - case SharedTypes::Legacy::ContainerType::Workbench: - case SharedTypes::Legacy::ContainerType::Inventory: - playerStats->addStats(StatsType::crafted, itemType, amount); - break; - default: - break; - } -} - -void onItemHurtAndBroken(Player* player, ItemStackBase* item, int deltaDamage) { - auto uuid = player->getUuid(); - auto findPlayer = playerStatsMap.find(uuid); - if (findPlayer == playerStatsMap.end()) return; - auto playerStats = findPlayer->second; - if (!playerStats) return; - if (!item->isDamageableItem()) return; - if (!item->isArmorItem()) playerStats->addStats(StatsType::used, item->getTypeName()); - auto maxDamage = item->getMaxDamage(); - auto damage = item->getDamageValue(); - if (damage + deltaDamage > maxDamage) { - playerStats->addStats(StatsType::broken, item->getTypeName()); - } -} - -void onUsedItem(Player* player, ItemStackBase& instance, ItemUseMethod itemUseMethod, bool consumeItem) { - // 奶桶 空桶 钓鱼竿 弓 工具/武器 书与笔 空地图监听不到需要单独处理 - // 玩家的统计数据在生物身上使用物品时不会增加 - // - 无论是命名、驯服、喂养、繁殖、上鞍、拴住、剪毛、染色、挤奶还是收集炖菜 - // - 当盔甲按使用键装备时,当皮革盔甲在炼药锅中清洗时,以及上面提到的例子。 - if (!consumeItem) return; - auto uuid = player->getUuid(); - auto findPlayer = playerStatsMap.find(uuid); - if (findPlayer == playerStatsMap.end()) return; - auto playerStats = findPlayer->second; - if (!playerStats) return; - switch (itemUseMethod) { - case ItemUseMethod::Eat: - case ItemUseMethod::Consume: - case ItemUseMethod::Throw: - case ItemUseMethod::FillBottle: - case ItemUseMethod::PourBucket: - playerStats->addStats(StatsType::used, instance.getTypeName()); - break; - case ItemUseMethod::Place: - playerStats->addStats(StatsType::used, instance.getTypeName()); - if (instance.getItem()->isMusicDisk()) { - playerStats->addCustomStats(CustomType::play_record); - } - break; - case ItemUseMethod::Interact: - // 交互只记录骨粉,其他与实体交互均不记录 - if (instance.getTypeName() == "minecraft:bone_meal") - playerStats->addStats(StatsType::used, instance.getTypeName()); - break; - default: - break; - } -}; - - -void onEffectAdded(Player* player, MobEffectInstance const& effect) { - auto effectId = effect.mId; - auto durationValue = effect.mDuration->mValue; - auto uuid = player->getUuid(); - auto findPlayer = playerStatsMap.find(uuid); - if (findPlayer == playerStatsMap.end()) return; - auto playerStats = findPlayer->second; - if (effectId == 29 && durationValue == 40 * 60 * 20) { - playerStats->addCustomStats(CustomType::raid_win); - } else if (effectId == 36 && durationValue == 30 * 20) { - playerStats->addCustomStats(CustomType::raid_trigger); - } -} - -void onStartSleep(Player* player) { - auto uuid = player->getUuid(); - auto findPlayer = playerStatsMap.find(uuid); - if (findPlayer == playerStatsMap.end()) return; - auto playerStats = findPlayer->second; - if (!playerStats) return; - playerStats->addCustomStats(CustomType::sleep_in_bed); -} - -void onBlockUsingShield(Player* player, float damage) { - auto uuid = player->getUuid(); - auto findPlayer = playerStatsMap.find(uuid); - if (findPlayer == playerStatsMap.end()) return; - auto playerStats = findPlayer->second; - if (!playerStats) return; - playerStats->addCustomStats(CustomType::damage_blocked_by_shield, static_cast(damage * 10)); - return; -} - -void onOpenTrading(Player* player) { - auto uuid = player->getUuid(); - auto findPlayer = playerStatsMap.find(uuid); - if (findPlayer == playerStatsMap.end()) return; - auto playerStats = findPlayer->second; - if (!playerStats) return; - playerStats->addCustomStats(CustomType::talked_to_villager); -} - -void onJump(Player& player) { - auto uuid = player.getUuid(); - auto findPlayer = playerStatsMap.find(uuid); - if (findPlayer == playerStatsMap.end()) return; - auto playerStats = findPlayer->second; - if (!playerStats) return; - playerStats->addCustomStats(CustomType::jump); -} - -void onMove(); - -void onChangeContainerWith( - Player& player, - std::string blockType, - int slot, - ItemStack const& oldItem, - ItemStack const& newItem -) { - auto uuid = player.getUuid(); - auto findPlayer = playerStatsMap.find(uuid); - if (findPlayer == playerStatsMap.end()) return; - auto playerStats = findPlayer->second; - if (!playerStats) return; - if (blockType == "minecraft:furnace" || blockType == "minecraft:lit_furnace" || blockType == "minecraft:smoker") { - if (slot != 2) return; - if (oldItem.isNull()) return; - if (oldItem.mCount > newItem.mCount) { - playerStats->addStats(StatsType::crafted, oldItem.getTypeName()); - } - } - return; -} - -void onBreedAnimal(mce::UUID uuid) { - auto findPlayer = playerStatsMap.find(uuid); - if (findPlayer == playerStatsMap.end()) return; - auto playerStats = findPlayer->second; - if (!playerStats) return; - playerStats->addCustomStats(CustomType::animals_bred); -} -void onFishCaught(mce::UUID uuid) { - auto findPlayer = playerStatsMap.find(uuid); - if (findPlayer == playerStatsMap.end()) return; - auto playerStats = findPlayer->second; - if (!playerStats) return; - playerStats->addCustomStats(CustomType::fish_caught); -} -} // namespace player -} // namespace event -} // namespace stats \ No newline at end of file diff --git a/src/mod/Events/PlayerEventHandle.h b/src/mod/Events/PlayerEventHandle.h deleted file mode 100644 index c0404b5..0000000 --- a/src/mod/Events/PlayerEventHandle.h +++ /dev/null @@ -1,50 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace stats { -namespace event { -namespace player { -void onJoin(Player& player); -void onLeft(ServerPlayer& player); -void onSneaking(Player& player); -void onSneaked(Player& player); -void onSprinting(Player& player); -void onSprinted(Player& player); -void onStartRiding(mce::UUID uuid, Actor& vehicle, bool forceRiding); -void onStopRiding(mce::UUID uuid, Actor* vehicle); -void onAuthInput(ServerPlayer& player, PlayerAuthInputPacket const& packet); -void onTakeItem(Player& player, ItemStack& item); -void onDropItem(Player* player, ItemStack const& item); -void onDied(Player& player, ActorDamageSource const& source); -void onKillMob(Player& player, Mob& mob); -void onTakenDamage(Player* player, float damage, float afterDamage); -void onDealtDamage(Mob* mob, Player* player, float damage, float afterDamage); -void onCraftedItem(mce::UUID uuid, std::string itemType, int amount, SharedTypes::Legacy::ContainerType type); -void onItemHurtAndBroken(Player* player, ItemStackBase* item, int deltaDamage); -void onUsedItem(Player* player, ItemStackBase& instance, ItemUseMethod itemUseMethod, bool consumeItem); -void onEffectAdded(Player* player, MobEffectInstance const& effect); -void onStartSleep(Player* player); -void onBlockUsingShield(Player* player, float damage); -void onOpenTrading(Player* player); -void onJump(Player& player); -void onMove(); -void onChangeContainerWith( - Player& player, - std::string blockType, - int slot, - ItemStack const& oldItem, - ItemStack const& newItem -); -void onBreedAnimal(mce::UUID uuid); -void onFishCaught(mce::UUID uuid); -} // namespace player -} // namespace event -} // namespace stats \ No newline at end of file diff --git a/src/mod/Hook/Block/Block.cpp b/src/mod/Hook/Block/Block.cpp index eac51a2..bffd725 100644 --- a/src/mod/Hook/Block/Block.cpp +++ b/src/mod/Hook/Block/Block.cpp @@ -6,7 +6,9 @@ #include #include #include +#include #include +#include #include #include @@ -30,23 +32,45 @@ LL_TYPE_INSTANCE_HOOK( return r; } +// LL_TYPE_INSTANCE_HOOK( +// BlockOnFallOnHook, +// HookPriority::Normal, +// Block, +// &Block::onFallOn, +// void, +// ::BlockSource& region, +// ::BlockPos const& pos, +// ::Actor& entity, +// float fallDistance +// ) { + +// if (fallDistance < 1.0f) return origin(region, pos, entity, fallDistance); +// event::block::onFallOn(entity, fallDistance); +// return origin(region, pos, entity, fallDistance); +// } + LL_TYPE_INSTANCE_HOOK( - BlockOnFallOnHook, + BlockOnFallOnBaseHook, HookPriority::Normal, - Block, - &Block::onFallOn, + BlockType, + &BlockType::onFallOnBase, void, - ::BlockSource& region, - ::BlockPos const& pos, - ::Actor& entity, - float fallDistance + ::BlockEvents::BlockEntityFallOnEvent& eventData ) { - - if (fallDistance < 1.0f) return origin(region, pos, entity, fallDistance); - event::block::onFallOn(entity, fallDistance); - return origin(region, pos, entity, fallDistance); + if (eventData.mFallDistance < 1.0f) return origin(eventData); + event::block::onFallOn(eventData.mEntity, eventData.mFallDistance); + return origin(eventData); } + void hookBlockUse() { BlockUseHook::hook(); } -void hookBlockOnFallOn() { BlockOnFallOnHook::hook(); } -} // namespace stats::hook::block \ No newline at end of file +void unhookBlockUse() { BlockUseHook::unhook(); } +void hookBlockOnFallOn() { + // BlockOnFallOnHook::hook(); + BlockOnFallOnBaseHook::hook(); +} +void unhookBlockOnFallOn() { + // BlockOnFallOnHook::unhook(); + BlockOnFallOnBaseHook::unhook(); +} +} // namespace stats::hook::block diff --git a/src/mod/Hook/Block/CakeBlock.cpp b/src/mod/Hook/Block/CakeBlock.cpp index f5afd03..ef503c6 100644 --- a/src/mod/Hook/Block/CakeBlock.cpp +++ b/src/mod/Hook/Block/CakeBlock.cpp @@ -22,5 +22,6 @@ LL_TYPE_STATIC_HOOK( } void hookCakeRemoveSlice() { CakeRemoveSliceHook::hook(); } +void unhookCakeRemoveSlice() { CakeRemoveSliceHook::unhook(); } -} // namespace stats::hook::block \ No newline at end of file +} // namespace stats::hook::block diff --git a/src/mod/Hook/Block/CauldronBlock.cpp b/src/mod/Hook/Block/CauldronBlock.cpp index 097e7dc..92da250 100644 --- a/src/mod/Hook/Block/CauldronBlock.cpp +++ b/src/mod/Hook/Block/CauldronBlock.cpp @@ -6,7 +6,7 @@ namespace stats::hook::block { -LL_TYPE_INSTANCE_HOOK( +LL_TYPE_STATIC_HOOK( CauldronBlockUseInventoryHook, HookPriority::Normal, CauldronBlock, @@ -17,11 +17,11 @@ LL_TYPE_INSTANCE_HOOK( class ItemStack& replaceWith, int useCount ) { - event::block::onCauldronBlockUseInventory(player, current, replaceWith, useCount); + event::block::onCauldronBlockUseInventory(player, current); return origin(player, current, replaceWith, useCount); } -LL_TYPE_INSTANCE_HOOK( +LL_TYPE_STATIC_HOOK( CauldronBlockCleanHook, HookPriority::Normal, CauldronBlock, @@ -36,6 +36,8 @@ LL_TYPE_INSTANCE_HOOK( } void hookCauldronBlockUseInventory() { CauldronBlockUseInventoryHook::hook(); } +void unhookCauldronBlockUseInventory() { CauldronBlockUseInventoryHook::unhook(); } void hookCauldronBlockClean() { CauldronBlockCleanHook::hook(); } -} // namespace stats::hook::block \ No newline at end of file +void unhookCauldronBlockClean() { CauldronBlockCleanHook::unhook(); } +} // namespace stats::hook::block diff --git a/src/mod/Hook/Block/FlowerPotBlock.cpp b/src/mod/Hook/Block/FlowerPotBlock.cpp index 5fd013b..8de769e 100644 --- a/src/mod/Hook/Block/FlowerPotBlock.cpp +++ b/src/mod/Hook/Block/FlowerPotBlock.cpp @@ -4,26 +4,47 @@ #include "mod/Events/BlockEventHandle.h" +#include #include +#include +#include namespace stats::hook::block { +namespace { + +Block const* getPottedPlant(BlockEvents::BlockPlayerInteractEvent const& eventData) { + auto const* blockActor = eventData.getBlockSource().getBlockEntity(eventData.mPos); + if (!blockActor) return nullptr; + + return static_cast(blockActor)->mPlant; +} + +} // namespace + LL_TYPE_INSTANCE_HOOK( FlowerPotBlockTryPlaceFlowerHook, HookPriority::Normal, FlowerPotBlock, - &FlowerPotBlock::_tryPlaceFlower, - bool, - ::Player& player, - ::BlockPos const& blockPos + &FlowerPotBlock::use, + void, + ::BlockEvents::BlockPlayerInteractEvent& eventData ) { - auto res = origin(player, blockPos); - if (!res) return res; - event::block::onFlowerPotBlockPlaceFlower(player); - return res; + auto const* plantBefore = getPottedPlant(eventData); + + origin(eventData); + + auto const* plantAfter = getPottedPlant(eventData); + if (!eventData.mSuccessful->value_or(false) || plantBefore || !plantAfter) return; + + event::block::onFlowerPotBlockPlaceFlower(eventData.mPlayer); + return; } -void hookFlowerPotBlockPlaceFlower(){ +void hookFlowerPotBlockPlaceFlower() { FlowerPotBlockTryPlaceFlowerHook::hook(); } +void unhookFlowerPotBlockPlaceFlower() { + FlowerPotBlockTryPlaceFlowerHook::unhook(); +} -} // namespace stats::hook::block \ No newline at end of file +} // namespace stats::hook::block diff --git a/src/mod/Hook/Block/NoteBlock.cpp b/src/mod/Hook/Block/NoteBlock.cpp index e7cf599..e50826d 100644 --- a/src/mod/Hook/Block/NoteBlock.cpp +++ b/src/mod/Hook/Block/NoteBlock.cpp @@ -23,5 +23,6 @@ LL_TYPE_INSTANCE_HOOK( } void hookNoteBlockAttack() { NoteBlockAttackHook::hook(); } +void unhookNoteBlockAttack() { NoteBlockAttackHook::unhook(); } -} // namespace stats::hook::block \ No newline at end of file +} // namespace stats::hook::block diff --git a/src/mod/Hook/Block/TargetBlock.cpp b/src/mod/Hook/Block/TargetBlock.cpp index c0f7f9c..e7545f7 100644 --- a/src/mod/Hook/Block/TargetBlock.cpp +++ b/src/mod/Hook/Block/TargetBlock.cpp @@ -24,5 +24,6 @@ LL_TYPE_INSTANCE_HOOK( void hookProjectileHitTargetBlock(){ TargetBlockOnProjectileHitHook::hook(); } +void unhookProjectileHitTargetBlock() { TargetBlockOnProjectileHitHook::unhook(); } -} // namespace stats::hook::block \ No newline at end of file +} // namespace stats::hook::block diff --git a/src/mod/Hook/Hook.cpp b/src/mod/Hook/Hook.cpp index 9896cb6..3a4064f 100644 --- a/src/mod/Hook/Hook.cpp +++ b/src/mod/Hook/Hook.cpp @@ -1,37 +1,74 @@ #include "mod/Hook/Hook.h" +#include +#include + namespace stats::hook { +namespace { +using HookCallback = void (*)(); + +struct HookBinding { + HookCallback install; + HookCallback remove; +}; + +constexpr std::array bindings{ + HookBinding{player::hookPlayerAddEffect, player::unhookPlayerAddEffect}, + HookBinding{player::hookPlayerBlockUsingShield, player::unhookPlayerBlockUsingShield}, + HookBinding{player::hookPlayerDropItem, player::unhookPlayerDropItem}, + HookBinding{player::hookPlayerStartSleep, player::unhookPlayerStartSleep}, + HookBinding{player::hookServerPlayerOpenTrading, player::unhookServerPlayerOpenTrading}, + HookBinding{player::hookPlayerUseItem, player::unhookPlayerUseItem}, + HookBinding{player::hookPlayerInteractActor, player::unhookPlayerInteractActor}, + HookBinding{player::hookPlayerBreedAnimal, player::unhookPlayerBreedAnimal}, + HookBinding{player::hookPlayerUseBucketItemOnFish, player::unhookPlayerUseBucketItemOnFish}, + HookBinding{player::hookPlayerRiding, player::unhookPlayerRiding}, + HookBinding{player::hookPlayerAuthInput, player::unhookPlayerAuthInput}, + HookBinding{mob::hookMobGetDamageAfterEnchantReduction, mob::unhookMobGetDamageAfterEnchantReduction}, + HookBinding{block::hookBlockUse, block::unhookBlockUse}, + HookBinding{block::hookBlockOnFallOn, block::unhookBlockOnFallOn}, + HookBinding{block::hookCakeRemoveSlice, block::unhookCakeRemoveSlice}, + HookBinding{block::hookCauldronBlockUseInventory, block::unhookCauldronBlockUseInventory}, + HookBinding{block::hookCauldronBlockClean, block::unhookCauldronBlockClean}, + HookBinding{block::hookFlowerPotBlockPlaceFlower, block::unhookFlowerPotBlockPlaceFlower}, + HookBinding{block::hookNoteBlockAttack, block::unhookNoteBlockAttack}, + HookBinding{block::hookProjectileHitTargetBlock, block::unhookProjectileHitTargetBlock}, + HookBinding{item::hookItemStackBaseHurtAndBroken, item::unhookItemStackBaseHurtAndBroken}, + HookBinding{container::hookLevelContainerChanged, container::unhookLevelContainerChanged}, + HookBinding{ + inventory::hookItemStackRequestActionHandlerTransfer, + inventory::unhookItemStackRequestActionHandlerTransfer + }, +}; + +class HookSession { +public: + HookSession() { + for (auto const& binding : bindings) { + binding.install(); + ++mInstalledCount; + } + } + + ~HookSession() { + while (mInstalledCount > 0) { + bindings[--mInstalledCount].remove(); + } + } + + HookSession(HookSession const&) = delete; + HookSession& operator=(HookSession const&) = delete; + +private: + size_t mInstalledCount{0}; +}; + +std::unique_ptr activeSession; +} // namespace + void hook() { - player::hookPlayerAddEffect(); - player::hookPlayerBlockUsingShield(); - player::hookPlayerDropItem(); - player::hookPlayerStartSleep(); - player::hookServerPlayerOpenTrading(); - player::hookPlayerUseItem(); - player::hookPlayerInteractActor(); - player::hookPlayerBreedAnimal(); - player::hookPlayerUseBucketItemOnFish(); - player::hookPlayerRiding(); - player::hookPlayerAuthInput(); - - mob::hookMobGetDamageAfterResistanceEffect(); - - block::hookBlockUse(); - block::hookBlockOnFallOn(); - block::hookCakeRemoveSlice(); - block::hookCauldronBlockUseInventory(); - block::hookCauldronBlockClean(); - block::hookFlowerPotBlockPlaceFlower(); - block::hookNoteBlockAttack(); - block::hookProjectileHitTargetBlock(); - - // item::hookFishingRodUsed(); - item::hookItemStackBaseHurtAndBroken(); - - container::hookLevelContainerChanged(); - - inventory::hookItemStackRequestActionHandlerTransfer(); + if (!activeSession) activeSession = std::make_unique(); } -void unhook() {} -} // namespace stats::hook \ No newline at end of file +void unhook() { activeSession.reset(); } +} // namespace stats::hook diff --git a/src/mod/Hook/Hook.h b/src/mod/Hook/Hook.h index 45c02c0..eb9e463 100644 --- a/src/mod/Hook/Hook.h +++ b/src/mod/Hook/Hook.h @@ -7,45 +7,68 @@ void unhook(); namespace player { void hookPlayerAddEffect(); +void unhookPlayerAddEffect(); void hookPlayerBlockUsingShield(); +void unhookPlayerBlockUsingShield(); void hookPlayerDropItem(); +void unhookPlayerDropItem(); void hookPlayerStartSleep(); +void unhookPlayerStartSleep(); void hookServerPlayerOpenTrading(); +void unhookServerPlayerOpenTrading(); void hookPlayerUseItem(); +void unhookPlayerUseItem(); void hookPlayerInteractActor(); +void unhookPlayerInteractActor(); void hookPlayerBreedAnimal(); +void unhookPlayerBreedAnimal(); void hookPlayerUseBucketItemOnFish(); +void unhookPlayerUseBucketItemOnFish(); void hookPlayerRiding(); +void unhookPlayerRiding(); void hookPlayerAuthInput(); +void unhookPlayerAuthInput(); } // namespace player namespace mob { -void hookMobGetDamageAfterResistanceEffect(); -} +void hookMobGetDamageAfterEnchantReduction(); +void unhookMobGetDamageAfterEnchantReduction(); +} // namespace mob namespace block { void hookBlockUse(); +void unhookBlockUse(); void hookBlockOnFallOn(); +void unhookBlockOnFallOn(); void hookCakeRemoveSlice(); +void unhookCakeRemoveSlice(); void hookCauldronBlockUseInventory(); +void unhookCauldronBlockUseInventory(); void hookCauldronBlockClean(); +void unhookCauldronBlockClean(); void hookFlowerPotBlockPlaceFlower(); +void unhookFlowerPotBlockPlaceFlower(); void hookNoteBlockAttack(); +void unhookNoteBlockAttack(); void hookProjectileHitTargetBlock(); +void unhookProjectileHitTargetBlock(); } // namespace block namespace item { // void hookFishingRodUsed(); void hookItemStackBaseHurtAndBroken(); +void unhookItemStackBaseHurtAndBroken(); } // namespace item namespace container { void hookLevelContainerChanged(); +void unhookLevelContainerChanged(); } namespace inventory { void hookItemStackRequestActionHandlerTransfer(); +void unhookItemStackRequestActionHandlerTransfer(); } } // namespace hook -} // namespace stats \ No newline at end of file +} // namespace stats diff --git a/src/mod/Hook/Item/ItemStack.cpp b/src/mod/Hook/Item/ItemStack.cpp index 3477de9..f76d902 100644 --- a/src/mod/Hook/Item/ItemStack.cpp +++ b/src/mod/Hook/Item/ItemStack.cpp @@ -5,7 +5,6 @@ // #include "mc/world/item/FishingRodItem.h" // #include "mc/world/item/ItemStackBase.h" -// #include "mod/Events/PlayerEventHandle.h" // namespace stats::hook::item { // LL_TYPE_INSTANCE_HOOK( @@ -22,4 +21,4 @@ // } // void hookItemStackBaseHurtAndBroken() { ItemStackBaseHurtAndBreak::hook(); } -// } // namespace stats::hook::item \ No newline at end of file +// } // namespace stats::hook::item diff --git a/src/mod/Hook/Item/ItemStackBase.cpp b/src/mod/Hook/Item/ItemStackBase.cpp index cc595b2..156dd8d 100644 --- a/src/mod/Hook/Item/ItemStackBase.cpp +++ b/src/mod/Hook/Item/ItemStackBase.cpp @@ -2,9 +2,12 @@ #include #include +#include +#include +#include #include -#include "mod/Events/PlayerEventHandle.h" +#include "mod/Stats/Handlers/PlayerStatsHandlers.h" namespace stats::hook::item { LL_TYPE_INSTANCE_HOOK( @@ -22,9 +25,10 @@ LL_TYPE_INSTANCE_HOOK( if (!res) return res; Player* player = owner->getEntityContext().getWeakRef().tryUnwrap(); if (!player) return res; - event::player::onItemHurtAndBroken(player, item, deltaDamage); + handler::onPlayerItemHurtAndBroken(player, item, deltaDamage); return res; } void hookItemStackBaseHurtAndBroken() { ItemStackBaseHurtAndBreak::hook(); } -} // namespace stats::hook::item \ No newline at end of file +void unhookItemStackBaseHurtAndBroken() { ItemStackBaseHurtAndBreak::unhook(); } +} // namespace stats::hook::item diff --git a/src/mod/Hook/ItemStackRequestActionHandler.cpp b/src/mod/Hook/ItemStackRequestActionHandler.cpp index 77a7240..56aa2eb 100644 --- a/src/mod/Hook/ItemStackRequestActionHandler.cpp +++ b/src/mod/Hook/ItemStackRequestActionHandler.cpp @@ -18,7 +18,7 @@ #include -#include "mod/Events/PlayerEventHandle.h" +#include "mod/Stats/Handlers/PlayerStatsHandlers.h" namespace stats::hook::inventory { @@ -45,8 +45,9 @@ LL_TYPE_INSTANCE_HOOK( auto itemType = srcItem.getTypeName(); auto r = origin(requestAction, isSwap, isSrcHintSlot, isDstHintSlot); if (r != ItemStackNetResult::Success) return r; - event::player::onCraftedItem(mPlayer.getUuid(), itemType, requestAction.mAmount, screenType); + handler::onPlayerCraftedItem(mPlayer.getUuid(), itemType, requestAction.mAmount, screenType); return r; } void hookItemStackRequestActionHandlerTransfer() { trasferHandlerHook::hook(); }; -} // namespace stats::hook::inventory \ No newline at end of file +void unhookItemStackRequestActionHandlerTransfer() { trasferHandlerHook::unhook(); } +} // namespace stats::hook::inventory diff --git a/src/mod/Hook/LevelContainerModel.cpp b/src/mod/Hook/LevelContainerModel.cpp index 0e77f92..fc1da6a 100644 --- a/src/mod/Hook/LevelContainerModel.cpp +++ b/src/mod/Hook/LevelContainerModel.cpp @@ -1,9 +1,11 @@ #include "mod/Events/BlockEventHandle.h" -#include "mod/Events/PlayerEventHandle.h" +#include "mod/Stats/Handlers/PlayerStatsHandlers.h" #include "mod/Hook/Hook.h" #include +#include #include #include +#include namespace stats::hook::container { @@ -25,9 +27,10 @@ LL_TYPE_INSTANCE_HOOK( auto slot = slotNumber + this->_getContainerOffset(); auto& block = event::block::getBlockByBlockPos(blockPos, player.getDimensionId()); auto blockType = block.getTypeName(); - event::player::onChangeContainerWith(player, blockType, slot, oldItem, newItem); + handler::onPlayerChangeContainerWith(player, blockType, slot, oldItem, newItem); origin(slotNumber, oldItem, newItem); } void hookLevelContainerChanged() { LevelContainerChangeHook::hook(); } -} // namespace stats::hook::container \ No newline at end of file +void unhookLevelContainerChanged() { LevelContainerChangeHook::unhook(); } +} // namespace stats::hook::container diff --git a/src/mod/Hook/MobDamageReduction.cpp b/src/mod/Hook/MobDamageReduction.cpp new file mode 100644 index 0000000..1880d18 --- /dev/null +++ b/src/mod/Hook/MobDamageReduction.cpp @@ -0,0 +1,78 @@ +#include "mc/world/effect/MobEffectIds.h" +#include "mod/Hook/Hook.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mod/Stats/Handlers/PlayerStatsHandlers.h" +#include "mod/Stats/Stats.h" + + +namespace stats::hook::mob { +namespace { + +float getResistanceDamage(float damageBeforeResistance, int amplifier) { + if (!std::isfinite(damageBeforeResistance) || damageBeforeResistance <= 0.0f || amplifier < 0) return 0.0f; + if (amplifier >= 4) return damageBeforeResistance; + return damageBeforeResistance * 0.2f * static_cast(amplifier + 1); +} + +void recordResistanceDamage(Mob& victim, ActorDamageSource const& source, float resistanceDamage) { + if (!std::isfinite(resistanceDamage) || resistanceDamage <= 0.0f) return; + + if (victim.isType(::ActorType::Player)) { + if (auto* player = victim.getEntityContext().getWeakRef().tryUnwrap().as_ptr()) { + handler::onPlayerResistedDamage(player, resistanceDamage); + } + } + + // Preserve the previous scope: only direct player attacks count as dealt damage. + if (!source.isEntitySource() || source.isChildEntitySource()) return; + + auto level = ll::service::getLevel(); + if (!level) return; + + auto* attacker = level->fetchEntity(source.getDamagingEntityUniqueID(), false); + if (!attacker || !attacker->isType(::ActorType::Player)) return; + + if (auto* player = attacker->getEntityContext().getWeakRef().tryUnwrap().as_ptr()) { + handler::onPlayerDealtResistedDamage(player, resistanceDamage); + } +} + +} // namespace + +LL_TYPE_INSTANCE_HOOK( + MobGetDamageAfterEnchantReductionHook, + HookPriority::Normal, + Mob, + &Mob::$getDamageAfterEnchantReduction, + float, + ::ActorDamageSource const& source, + float damage +) { + auto const afterEnchantReduction = origin(source, damage); + auto const* resistance = this->getEffect((uint)MobEffectIds::DamageResistance); + + if (resistance && source.isReducedByResistanceEffect()) { + auto const resistanceDamage = getResistanceDamage(afterEnchantReduction, resistance->mAmplifier); + recordResistanceDamage(*thisFor(), source, resistanceDamage); + } + + return afterEnchantReduction; +} + +void hookMobGetDamageAfterEnchantReduction() { MobGetDamageAfterEnchantReductionHook::hook(); } +void unhookMobGetDamageAfterEnchantReduction() { MobGetDamageAfterEnchantReductionHook::unhook(); } + +} // namespace stats::hook::mob diff --git a/src/mod/Hook/MobHurt.cpp b/src/mod/Hook/MobHurt.cpp deleted file mode 100644 index 584a6c6..0000000 --- a/src/mod/Hook/MobHurt.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include "mod/Hook/Hook.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "mod/Events/PlayerEventHandle.h" - -namespace stats::hook::mob { - -void onMobHurt(Mob* mob, ActorDamageSource const& source, float damage, float afterDamage) { - if (mob->isType(::ActorType::Player)) { - Player* player = mob->getEntityContext().getWeakRef().tryUnwrap(); - if (!player) return; - event::player::onTakenDamage(player, damage, afterDamage); - } - if (source.isEntitySource()) { - Actor* damageSource = nullptr; - // 玩家造成的伤害总量,单位为1(♥)的1⁄10,只统计近战攻击造成的伤害。 - if (source.isChildEntitySource()) return; - damageSource = ll::service::getLevel()->fetchEntity(source.getDamagingEntityUniqueID(), false); - if (damageSource->isType(::ActorType::Player)) { - Player* player = damageSource->getEntityContext().getWeakRef().tryUnwrap(); - if (!player) return; - event::player::onDealtDamage(mob, player, damage, afterDamage); - } - } -} - -LL_TYPE_INSTANCE_HOOK( - MobGetDamageAfterResistanceEffectHook, - HookPriority::Normal, - Mob, - &Mob::getDamageAfterResistanceEffect, - float, - ::ActorDamageSource const& source, - float damage -) { - auto afterDamage = origin(source, damage); - Mob* mob = this; - onMobHurt(mob, source, damage, afterDamage); - return afterDamage; -} - -void hookMobGetDamageAfterResistanceEffect() { MobGetDamageAfterResistanceEffectHook::hook(); } -} // namespace stats::hook::mob diff --git a/src/mod/Hook/Player/PlayerAddEffect.cpp b/src/mod/Hook/Player/PlayerAddEffect.cpp index be36d1b..5cbdaef 100644 --- a/src/mod/Hook/Player/PlayerAddEffect.cpp +++ b/src/mod/Hook/Player/PlayerAddEffect.cpp @@ -2,8 +2,12 @@ #include #include +#include +#include +#include +#include -#include "mod/Events/PlayerEventHandle.h" +#include "mod/Stats/Handlers/PlayerStatsHandlers.h" namespace stats::hook::player { @@ -18,9 +22,10 @@ LL_TYPE_INSTANCE_HOOK( if (!this->isType(::ActorType::Player)) return origin(effect); Player* player = this->getEntityContext().getWeakRef().tryUnwrap(); if (!player) return origin(effect); - event::player::onEffectAdded(player, effect); + handler::onPlayerEffectAdded(player, effect); return origin(effect); } void hookPlayerAddEffect() { ActorAddEffectHook::hook(); } -} // namespace stats::hook::player \ No newline at end of file +void unhookPlayerAddEffect() { ActorAddEffectHook::unhook(); } +} // namespace stats::hook::player diff --git a/src/mod/Hook/Player/PlayerAuthInputHook.cpp b/src/mod/Hook/Player/PlayerAuthInputHook.cpp index fe91a66..06a0d21 100644 --- a/src/mod/Hook/Player/PlayerAuthInputHook.cpp +++ b/src/mod/Hook/Player/PlayerAuthInputHook.cpp @@ -8,13 +8,13 @@ #include #include -#include "mod/Events/PlayerEventHandle.h" +#include "mod/Stats/Handlers/PlayerStatsHandlers.h" namespace stats::hook::player { LL_TYPE_INSTANCE_HOOK( PlayerAuthInputHook, - HookPriority::Normal, + HookPriority::Low, ServerNetworkHandler, &ServerNetworkHandler::$handle, void, @@ -25,10 +25,11 @@ LL_TYPE_INSTANCE_HOOK( auto handle = thisFor(); auto player = handle->_getServerPlayer(source, packet.mSenderSubId); if (!player) return; - event::player::onAuthInput(*player, packet); + handler::onPlayerAuthInput(*player, packet); return; } void hookPlayerAuthInput() { PlayerAuthInputHook::hook(); } +void unhookPlayerAuthInput() { PlayerAuthInputHook::unhook(); } -} // namespace stats::hook::player \ No newline at end of file +} // namespace stats::hook::player diff --git a/src/mod/Hook/Player/PlayerBlockUsingShield.cpp b/src/mod/Hook/Player/PlayerBlockUsingShield.cpp index 51a0531..c2807b1 100644 --- a/src/mod/Hook/Player/PlayerBlockUsingShield.cpp +++ b/src/mod/Hook/Player/PlayerBlockUsingShield.cpp @@ -1,27 +1,39 @@ #include "mod/Hook/Hook.h" #include +#include +#include +#include +#include -#include "mod/Events/PlayerEventHandle.h" +#include "mod/Stats/Handlers/PlayerStatsHandlers.h" namespace stats::hook::player { LL_TYPE_INSTANCE_HOOK( - PlayerBlockUsingShieldHook, + PlayerHurtShieldHook, HookPriority::Normal, Player, - &Player::_blockUsingShield, - bool, + &Player::$_hurt, + ::ActorHurtResult, ::ActorDamageSource const& source, - float damage + float damage, + ::HurtParameters const& hurtParameters ) { - auto res = origin(source, damage); - Player* player = this; - if (!res) return res; - event::player::onBlockUsingShield(player, damage); - return res; + auto& player = *thisFor(); + + // _blockUsingShield was inlined into Player::_hurt. This is its original guard. + auto const shieldBlockSucceeded = player.isDamageBlocked(source) && !player.getCurrentActiveShield().isNull(); + if (shieldBlockSucceeded) { + handler::onPlayerBlockUsingShield(&player, damage); + } + + return origin(source, damage, hurtParameters); } -void hookPlayerBlockUsingShield(){ - PlayerBlockUsingShieldHook::hook(); +void hookPlayerBlockUsingShield() { + PlayerHurtShieldHook::hook(); +} +void unhookPlayerBlockUsingShield() { + PlayerHurtShieldHook::unhook(); } -} // namespace stats::hook::player \ No newline at end of file +} // namespace stats::hook::player diff --git a/src/mod/Hook/Player/PlayerDropItem.cpp b/src/mod/Hook/Player/PlayerDropItem.cpp index 81d8a42..0179133 100644 --- a/src/mod/Hook/Player/PlayerDropItem.cpp +++ b/src/mod/Hook/Player/PlayerDropItem.cpp @@ -1,8 +1,10 @@ #include "mod/Hook/Hook.h" #include +#include +#include -#include "mod/Events/PlayerEventHandle.h" +#include "mod/Stats/Handlers/PlayerStatsHandlers.h" namespace stats::hook::player { LL_TYPE_INSTANCE_HOOK( @@ -17,9 +19,10 @@ LL_TYPE_INSTANCE_HOOK( auto res = origin(item, randomly); if (!res || randomly) return res; Player* player = this; - event::player::onDropItem(player, item); + handler::onPlayerDropItem(player, item); return res; } void hookPlayerDropItem() { PlayerDropItemHook::hook(); } -} // namespace stats::hook::player \ No newline at end of file +void unhookPlayerDropItem() { PlayerDropItemHook::unhook(); } +} // namespace stats::hook::player diff --git a/src/mod/Hook/Player/PlayerInteractActor.cpp b/src/mod/Hook/Player/PlayerInteractActor.cpp index 443739f..56a48f6 100644 --- a/src/mod/Hook/Player/PlayerInteractActor.cpp +++ b/src/mod/Hook/Player/PlayerInteractActor.cpp @@ -2,28 +2,73 @@ #include "mod/Hook/Hook.h" #include +#include #include #include +#include #include -#include #include -#include -#include #include #include #include +#include #include -#include "mod/Events/PlayerEventHandle.h" +#include "mod/Stats/Handlers/PlayerStatsHandlers.h" +#include +#include #include -#include +#include namespace stats::hook::player { namespace { -std::unordered_map breedCacheMap; -std::unordered_set fishCaughtSet; +using PendingCatchClock = std::chrono::steady_clock; + +constexpr auto PendingCatchLifetime = std::chrono::seconds{30}; +constexpr std::size_t MaxPendingCatches = 512; + +std::unordered_map pendingFishCatches; + +void prunePendingFishCatches(PendingCatchClock::time_point now) { + for (auto entry = pendingFishCatches.begin(); entry != pendingFishCatches.end();) { + if (entry->second <= now) { + entry = pendingFishCatches.erase(entry); + } else { + ++entry; + } + } + if (pendingFishCatches.size() < MaxPendingCatches) return; + + auto oldest = pendingFishCatches.begin(); + for (auto entry = std::next(pendingFishCatches.begin()); entry != pendingFishCatches.end(); ++entry) { + if (entry->second < oldest->second) oldest = entry; + } + pendingFishCatches.erase(oldest); +} + +void rememberPendingFishCatch(ActorUniqueID uniqueId) { + auto const now = PendingCatchClock::now(); + prunePendingFishCatches(now); + pendingFishCatches.insert_or_assign(uniqueId, now + PendingCatchLifetime); +} + +bool consumePendingFishCatch(ActorUniqueID uniqueId) { + auto const pending = pendingFishCatches.find(uniqueId); + if (pending == pendingFishCatches.end()) return false; + + auto const valid = pending->second > PendingCatchClock::now(); + pendingFishCatches.erase(pending); + return valid; +} + +Player* resolveLoveCausePlayer(ActorUniqueID const& loveCause) { + if (loveCause == ActorUniqueID::INVALID_ID()) return nullptr; + + auto* level = ll::service::getLevel().as_ptr(); + return level ? level->getPlayer(loveCause) : nullptr; +} } // namespace LL_TYPE_INSTANCE_HOOK( InteractEntityHook, @@ -34,47 +79,36 @@ LL_TYPE_INSTANCE_HOOK( Actor& actor, Vec3 const& location ) { - auto uniqueId = actor.getOrCreateUniqueID().rawID; - auto text = getInteractText(); + auto uniqueId = actor.getOrCreateUniqueID(); auto uuid = getUuid(); auto r = origin(actor, location); if (!r.mSuccess) return r; //后续可能还需要修改 if (actor.hasCategory(::ActorCategory::WaterAnimal) || actor.isType(::ActorType::Axolotl)) { - auto it = fishCaughtSet.find(uniqueId); - if (it != fishCaughtSet.end()) { - event::player::onFishCaught(uuid); - fishCaughtSet.erase(it); + if (consumePendingFishCatch(uniqueId)) { + handler::onPlayerFishCaught(uuid); } } - if (text != "Feed") return r; - breedCacheMap[uniqueId] = uuid; return r; } -LL_TYPE_INSTANCE_HOOK(BreedGoalStopHook, HookPriority::Normal, BreedGoal, &BreedGoal::$stop, void) { - Mob& mob = mOwner; - Actor* partner = mPartner->tryUnwrap<>(); - if (!partner) return origin(); - auto mob1UniqueId = mob.getOrCreateUniqueID().rawID; - auto mob2UniqueId = partner->getOrCreateUniqueID().rawID; - auto find1 = breedCacheMap.find(mob1UniqueId); - auto find2 = breedCacheMap.find(mob2UniqueId); - if (find1 == breedCacheMap.end() && find2 == breedCacheMap.end()) return origin(); - if (find1->second == find2->second) { - event::player::onBreedAnimal(find1->second); - breedCacheMap.erase(find1); - breedCacheMap.erase(find2); - } else { - if (find1 != breedCacheMap.end()) { - event::player::onBreedAnimal(find1->second); - breedCacheMap.erase(find1); - } - if (find2 != breedCacheMap.end()) { - event::player::onBreedAnimal(find1->second); - breedCacheMap.erase(find2); - } - } - origin(); +LL_TYPE_INSTANCE_HOOK( + PlayerBredAnimalsHook, + HookPriority::Normal, + BreedableComponent, + &BreedableComponent::mate, + std::vector, + Actor& owner, + Actor& partner +) { + auto const loveCause = mLoveCause.get(); + auto result = origin(owner, partner); + if (result.empty()) return result; + + auto* player = resolveLoveCausePlayer(loveCause); + if (!player) return result; + + handler::onPlayerBreedAnimal(player->getUuid()); + return result; } LL_TYPE_INSTANCE_HOOK( @@ -92,16 +126,22 @@ LL_TYPE_INSTANCE_HOOK( auto typeId = static_cast(entity.getEntityTypeId()); auto canFill = (typeId >= 9068 && typeId <= 9072) || typeId == 4994 || typeId == 9093; if (!canFill) return origin(instance, entity, pos, face, clickPos); - auto uniqueId = entity.getOrCreateUniqueID().rawID; + auto uniqueId = entity.getOrCreateUniqueID(); auto r = origin(instance, entity, pos, face, clickPos); if (r.mSwing) { - fishCaughtSet.insert(uniqueId); + rememberPendingFishCatch(uniqueId); } return r; } void hookPlayerInteractActor() { InteractEntityHook::hook(); } -void hookPlayerBreedAnimal() { BreedGoalStopHook::hook(); } +void unhookPlayerInteractActor() { InteractEntityHook::unhook(); } +void hookPlayerBreedAnimal() { PlayerBredAnimalsHook::hook(); } +void unhookPlayerBreedAnimal() { PlayerBredAnimalsHook::unhook(); } void hookPlayerUseBucketItemOnFish() { BucketItemUseOnHook::hook(); } +void unhookPlayerUseBucketItemOnFish() { + BucketItemUseOnHook::unhook(); + pendingFishCatches.clear(); +} -} // namespace stats::hook::player \ No newline at end of file +} // namespace stats::hook::player diff --git a/src/mod/Hook/Player/PlayerRideHook.cpp b/src/mod/Hook/Player/PlayerRideHook.cpp index c6c8db3..bf611b3 100644 --- a/src/mod/Hook/Player/PlayerRideHook.cpp +++ b/src/mod/Hook/Player/PlayerRideHook.cpp @@ -3,8 +3,10 @@ #include #include #include +#include +#include -#include "mod/Events/PlayerEventHandle.h" +#include "mod/Stats/Handlers/PlayerStatsHandlers.h" namespace stats::hook::player { @@ -19,7 +21,7 @@ LL_TYPE_INSTANCE_HOOK( ) { auto r = origin(vehicle, forceRiding); if (!r) return r; - event::player::onStartRiding(getUuid(), vehicle, forceRiding); + handler::onPlayerStartRiding(*thisFor()); return r; } @@ -38,7 +40,7 @@ LL_TYPE_INSTANCE_HOOK( return origin(exitFromPassenger, actorIsBeingDestroyed, switchingVehicles, isBeingTeleported); Player* player = getEntityContext().getWeakRef().tryUnwrap(); if (!player) return origin(exitFromPassenger, actorIsBeingDestroyed, switchingVehicles, isBeingTeleported); - event::player::onStopRiding(player->getUuid(), player->getVehicle()); + handler::onPlayerStopRiding(player->getUuid(), player->getVehicle()); origin(exitFromPassenger, actorIsBeingDestroyed, switchingVehicles, isBeingTeleported); } @@ -46,4 +48,8 @@ void hookPlayerRiding() { PlayerStartRiding::hook(); ActorStopRidingHook::hook(); } -} // namespace stats::hook::player \ No newline at end of file +void unhookPlayerRiding() { + ActorStopRidingHook::unhook(); + PlayerStartRiding::unhook(); +} +} // namespace stats::hook::player diff --git a/src/mod/Hook/Player/PlayerStartSleep.cpp b/src/mod/Hook/Player/PlayerStartSleep.cpp index 7123c8e..a04f787 100644 --- a/src/mod/Hook/Player/PlayerStartSleep.cpp +++ b/src/mod/Hook/Player/PlayerStartSleep.cpp @@ -1,8 +1,11 @@ #include "mod/Hook/Hook.h" #include +#include +#include +#include -#include "mod/Events/PlayerEventHandle.h" +#include "mod/Stats/Handlers/PlayerStatsHandlers.h" namespace stats::hook::player { LL_TYPE_INSTANCE_HOOK( @@ -11,12 +14,15 @@ LL_TYPE_INSTANCE_HOOK( Player, &Player::$startSleepInBed, BedSleepingResult, - BlockPos const& pos + ::BlockPos const& bedPos, + bool setsRespawn, + float sleepOffset ) { - auto res = origin(pos); + auto res = origin(bedPos, setsRespawn, sleepOffset); Player* player = this; - if (res == BedSleepingResult::Ok) event::player::onStartSleep(player); + if (res == BedSleepingResult::Ok) handler::onPlayerStartSleep(player); return res; } void hookPlayerStartSleep() { PlayerStartSleepHook::hook(); } -} // namespace stats::hook::player \ No newline at end of file +void unhookPlayerStartSleep() { PlayerStartSleepHook::unhook(); } +} // namespace stats::hook::player diff --git a/src/mod/Hook/Player/PlayerUseItem.cpp b/src/mod/Hook/Player/PlayerUseItem.cpp index c3a509c..442d808 100644 --- a/src/mod/Hook/Player/PlayerUseItem.cpp +++ b/src/mod/Hook/Player/PlayerUseItem.cpp @@ -1,8 +1,11 @@ #include "mod/Hook/Hook.h" #include +#include +#include +#include -#include "mod/Events/PlayerEventHandle.h" +#include "mod/Stats/Handlers/PlayerStatsHandlers.h" namespace stats::hook::player { @@ -18,12 +21,13 @@ LL_TYPE_INSTANCE_HOOK( ) { Player* player = this; - event::player::onUsedItem(player, instance, itemUseMethod, consumeItem); + handler::onPlayerUsedItem(player, instance, itemUseMethod, consumeItem); return origin(instance, itemUseMethod, consumeItem); } void hookPlayerUseItem(){ PlayerUseItemHook::hook(); } +void unhookPlayerUseItem() { PlayerUseItemHook::unhook(); } //PlayerEat BlockPlace 也可以通过hook玩家使用物品 -} // namespace stats::hook::player \ No newline at end of file +} // namespace stats::hook::player diff --git a/src/mod/Hook/Player/ServerPlayerOpenTrading.cpp b/src/mod/Hook/Player/ServerPlayerOpenTrading.cpp index fffda9f..9d4c53b 100644 --- a/src/mod/Hook/Player/ServerPlayerOpenTrading.cpp +++ b/src/mod/Hook/Player/ServerPlayerOpenTrading.cpp @@ -6,7 +6,7 @@ #include #include -#include "mod/Events/PlayerEventHandle.h" +#include "mod/Stats/Handlers/PlayerStatsHandlers.h" namespace stats::hook::player { LL_TYPE_INSTANCE_HOOK( @@ -20,11 +20,14 @@ LL_TYPE_INSTANCE_HOOK( ) { origin(uniqueID, useNewScreen); Player* player = this; - auto actor = ll::service::getLevel()->fetchEntity(uniqueID, false); + auto level = ll::service::getLevel(); + if (!level) return; + auto actor = level->fetchEntity(uniqueID, false); - if (!actor->isType(::ActorType::VillagerV2)) return; - event::player::onOpenTrading(player); + if (!actor || !actor->isType(::ActorType::VillagerV2)) return; + handler::onPlayerOpenTrading(player); } void hookServerPlayerOpenTrading() { ServerPlayerOpenTradingHook::hook(); } -} // namespace stats::hook::player \ No newline at end of file +void unhookServerPlayerOpenTrading() { ServerPlayerOpenTradingHook::unhook(); } +} // namespace stats::hook::player diff --git a/src/mod/LKStats.rc b/src/mod/LKStats.rc new file mode 100644 index 0000000..d12a66a --- /dev/null +++ b/src/mod/LKStats.rc @@ -0,0 +1,32 @@ +#include "mod/Version.h" +#include "winres.h" + +#pragma code_page(65001) + +VS_VERSION_INFO VERSIONINFO + FILEVERSION LK_STATS_VERSION_MAJOR, LK_STATS_VERSION_MINOR, LK_STATS_VERSION_PATCH, 0 + PRODUCTVERSION LK_STATS_VERSION_MAJOR, LK_STATS_VERSION_MINOR, LK_STATS_VERSION_PATCH, 0 + FILEFLAGSMASK 0x3fL + FILEFLAGS 0x0L + FILEOS 0x40004L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "080404b0" + BEGIN + VALUE "CompanyName", "LWenK" + VALUE "FileDescription", "Player statistics plugin for LeviLamina" + VALUE "FileVersion", LK_STATS_VERSION_STRING + VALUE "InternalName", "LK-Stats.dll" + VALUE "OriginalFilename", "LK-Stats.dll" + VALUE "ProductName", "LK-Stats" + VALUE "ProductVersion", LK_STATS_VERSION_STRING + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x804, 1200 + END +END diff --git a/src/mod/MyMod.cpp b/src/mod/MyMod.cpp index c138245..fd0c4f7 100644 --- a/src/mod/MyMod.cpp +++ b/src/mod/MyMod.cpp @@ -1,8 +1,47 @@ #include "mod/MyMod.h" #include "mod/Stats/Stats.h" + +#include +#include +#include #include #include +#include + +namespace { + +void loadTranslationNode( + nlohmann::json const& data, + std::string const& locale, + std::string const& prefix, + ll::i18n::I18n& i18n +) { + for (auto const& [name, value] : data.items()) { + auto const key = prefix.empty() ? name : prefix + "." + name; + if (value.is_string()) { + i18n.set(locale, key, value.get()); + } else if (value.is_object()) { + loadTranslationNode(value, locale, key, i18n); + } + } +} + +bool loadTranslations(std::filesystem::path const& langDir, ll::i18n::I18n& i18n) { + std::error_code error; + for (std::filesystem::directory_iterator it(langDir, error), end; !error && it != end; it.increment(error)) { + if (!it->is_regular_file(error) || it->path().extension() != ".json") continue; + + std::ifstream input(it->path()); + auto data = nlohmann::json::parse(input, nullptr, false); + if (!input || data.is_discarded() || !data.is_object()) return false; + + loadTranslationNode(data, it->path().stem().string(), {}, i18n); + } + return !error; +} + +} // namespace namespace lk { @@ -13,7 +52,9 @@ MyMod& MyMod::getInstance() { bool MyMod::load() { getSelf().getLogger().debug("Loading..."); - auto res = ll::i18n::getInstance().load(getSelf().getLangDir()); + if (!loadTranslations(getSelf().getLangDir(), ll::i18n::getInstance())) { + getSelf().getLogger().error("Failed to load translations"); + } stats::printLogo(); // Code for loading the mod goes here. return true; diff --git a/src/mod/Stats/Command/RegisterCommand.cpp b/src/mod/Stats/Command/RegisterCommand.cpp index f3c9c48..f02551d 100644 --- a/src/mod/Stats/Command/RegisterCommand.cpp +++ b/src/mod/Stats/Command/RegisterCommand.cpp @@ -1,6 +1,5 @@ #include "mod/Stats/Command/RegisterCommand.h" -#include #include #include @@ -15,7 +14,6 @@ #include #include - #include "mod/Stats/Form/Form.h" #include "mod/Stats/Stats.h" #include "mod/Stats/StatsData.h" @@ -24,13 +22,13 @@ using namespace ll::i18n_literals; namespace stats::command { struct StatsGui { - StatsType StatsType; + StatsType category; std::string playerName; }; struct StatsRank { - StatsType StatsType; - std::string type; + StatsType category; + std::string stat; }; void registerCommand() { @@ -43,101 +41,61 @@ void registerCommand() { .alias("command.stats.alias"_tr()); cmd.overload() .text("gui") - .optional("StatsType") + .optional("category") .execute([&](CommandOrigin const& origin, CommandOutput& output, StatsGui const& param) { auto* entity = origin.getEntity(); if (entity == nullptr || !entity->isType(::ActorType::Player)) { - output.error("command.error.notplayer"_tr()); + return output.error("command.error.not_player"_tr()); } - Player* player = (Player*)entity; - // lk::MyMod::getInstance().getSelf().getLogger().info("cmd {} {}", player->getRealName(), param.statsType); - switch (param.StatsType) { - case StatsType::custom: - case StatsType::mined: - case StatsType::broken: - case StatsType::crafted: - case StatsType::used: - case StatsType::picked_up: - case StatsType::dropped: - case StatsType::killed: - case StatsType::killed_by: - form::sendStatsGui(*player, param.StatsType); - break; - default: + auto* player = static_cast(entity); + // lk::MyMod::getInstance().getSelf().getLogger().info("cmd {} {}", player->getRealName(), param.category); + if (isValidStatsType(param.category)) { + form::sendStatsGui(*player, param.category); + } else { form::sendMainGui(*player); - break; } }); cmd.overload() .text("player") .required("playerName") - .required("StatsType") + .required("category") .execute([&](CommandOrigin const& origin, CommandOutput& output, StatsGui const& param) { auto* entity = origin.getEntity(); if (entity == nullptr || !entity->isType(::ActorType::Player)) { - output.error("command.error.notplayer"_tr()); + return output.error("command.error.not_player"_tr()); } - Player* player = (Player*)entity; + auto* player = static_cast(entity); if (param.playerName.empty()) { return output.error("command.error.player_name_empty"_tr()); } - // auto playerInfo = ll::service::PlayerInfo::getInstance().fromName(param.playerName); - // if (!playerInfo.has_value()) return output.error("Error"); - // auto uuid = playerInfo->uuid; - auto const& cache = getStatsCache(); - std::string uuid; - for (auto& it : cache) { - if (it.first.name == param.playerName) { - uuid = it.first.uuid; - break; - } - } - std::optional content = form::renderStatsContent(mce::UUID(uuid), param.StatsType); - if (!content.has_value()) return output.error("command.error.find_player"_tr()); - auto fm = ll::form::SimpleForm(); - auto typeString = StatsTypeMap.at(param.StatsType); - fm.setTitle( - "gui.title.stats"_tr() + " | " + std::string(ll::i18n::getInstance().get(typeString, {})) + " | " - + param.playerName - ) - .setContent(content.value()) - .sendTo(*player); + auto const* cached = findCachedStatsByName(param.playerName); + if (!cached) return output.error("command.error.player_not_found"_tr()); + form::sendStatsGui( + *player, + mce::UUID(cached->first.uuid), + param.playerName, + param.category + ); }); cmd.overload() .text("rank") - .required("StatsType") - .optional("type") + .required("category") + .optional("stat") .execute([&](CommandOrigin const& origin, CommandOutput& output, StatsRank const& param) { auto* entity = origin.getEntity(); if (entity == nullptr || !entity->isType(::ActorType::Player)) { - output.error("command.error.notplayer"_tr()); + return output.error("command.error.not_player"_tr()); + } + auto* player = static_cast(entity); + if (!isValidStatsType(param.category)) { + return output.error("command.error.invalid_stats_category"_tr()); } - Player* player = (Player*)entity; - switch (param.StatsType) { - case StatsType::custom: - if (param.type.empty()) { - output.error("command.error.rank_type_required"_tr()); - } else { - form::sendRankGui(*player, StatsType::custom, param.type); - } - break; - case StatsType::mined: - case StatsType::broken: - case StatsType::crafted: - case StatsType::used: - case StatsType::picked_up: - case StatsType::dropped: - case StatsType::killed: - case StatsType::killed_by: - form::sendRankGui(*player, param.StatsType, param.type); - break; - default: - // form::sendRankGuiMain(*player); - output.error("TODO"); - break; + if (param.category == StatsType::custom && param.stat.empty()) { + return output.error("command.error.rank_stat_required"_tr()); } + form::sendRankGui(*player, param.category, param.stat); }); } -} // namespace stats::command \ No newline at end of file +} // namespace stats::command diff --git a/src/mod/Stats/Form/Form.cpp b/src/mod/Stats/Form/Form.cpp index 73e7d0a..f05a523 100644 --- a/src/mod/Stats/Form/Form.cpp +++ b/src/mod/Stats/Form/Form.cpp @@ -3,13 +3,12 @@ #include "mc/platform/UUID.h" #include "mod/Stats/Stats.h" +#include #include #include #include #include #include -#include -#include #include #include #include @@ -20,131 +19,170 @@ using namespace ll::i18n_literals; namespace stats::form { +std::string translateStatsCategory(StatsType type); + void sendMainGui(Player& player) { auto fm = ll::form::SimpleForm(); fm.setTitle("gui.title.stats"_tr()) - .appendButton("minecraft:custom"_tr(), [](Player& pl) { sendStatsGui(pl, StatsType::custom); }) - .appendButton("minecraft:mined"_tr(), [](Player& pl) { sendStatsGui(pl, StatsType::mined); }) - .appendButton("minecraft:broken"_tr(), [](Player& pl) { sendStatsGui(pl, StatsType::broken); }) - .appendButton("minecraft:crafted"_tr(), [](Player& pl) { sendStatsGui(pl, StatsType::crafted); }) - .appendButton("minecraft:used"_tr(), [](Player& pl) { sendStatsGui(pl, StatsType::used); }) - .appendButton("minecraft:dropped"_tr(), [](Player& pl) { sendStatsGui(pl, StatsType::dropped); }) - .appendButton("minecraft:picked_up"_tr(), [](Player& pl) { sendStatsGui(pl, StatsType::picked_up); }) - .appendButton("minecraft:killed"_tr(), [](Player& pl) { sendStatsGui(pl, StatsType::killed); }) - .appendButton("minecraft:killed_by"_tr(), [](Player& pl) { sendStatsGui(pl, StatsType::killed_by); }) + .appendButton(translateStatsCategory(StatsType::custom), [](Player& pl) { sendStatsGui(pl, StatsType::custom); }) + .appendButton(translateStatsCategory(StatsType::mined), [](Player& pl) { sendStatsGui(pl, StatsType::mined); }) + .appendButton(translateStatsCategory(StatsType::broken), [](Player& pl) { sendStatsGui(pl, StatsType::broken); }) + .appendButton(translateStatsCategory(StatsType::crafted), [](Player& pl) { sendStatsGui(pl, StatsType::crafted); }) + .appendButton(translateStatsCategory(StatsType::used), [](Player& pl) { sendStatsGui(pl, StatsType::used); }) + .appendButton(translateStatsCategory(StatsType::dropped), [](Player& pl) { sendStatsGui(pl, StatsType::dropped); }) + .appendButton(translateStatsCategory(StatsType::picked_up), [](Player& pl) { sendStatsGui(pl, StatsType::picked_up); }) + .appendButton(translateStatsCategory(StatsType::killed), [](Player& pl) { sendStatsGui(pl, StatsType::killed); }) + .appendButton(translateStatsCategory(StatsType::killed_by), [](Player& pl) { sendStatsGui(pl, StatsType::killed_by); }) .sendTo(player); } -void sendStatsGui(Player& player, StatsType type) { - auto content = renderStatsContent(player.getUuid(), type, player.mTickCount); - if (content.has_value()) { - auto fm = ll::form::SimpleForm(); - auto typeString = StatsTypeMap.at(type); - fm.setTitle("gui.title.stats"_tr() + " - " + std::string(ll::i18n::getInstance().get(typeString, {}))) - .setContent(content.value()) - .sendTo(player); - } -}; - inline void renderContent(std::string& content, std::vector const& data) { + content.reserve(content.size() + data.size() * 32); for (auto it = data.begin(); it != data.end(); ++it) { std::string str = "§6" + it->first + "§r : §a" + std::to_string(it->second) + "§r\n"; content += str; } } -inline std::optional getStatsDataMap(mce::UUID uuid, StatsType type) { - auto& playerStatsMap = getPlayerStatsMap(); - auto findPlayer = playerStatsMap.find(uuid); - if (findPlayer != playerStatsMap.end()) { - auto playerStats = findPlayer->second; - if (!playerStats) return std::nullopt; - auto j = playerStats->getJson(); - auto typeString = StatsTypeMap.at(type); - auto dataMap = j[typeString].get(); - return std::make_optional(dataMap); - } else { - auto& cache = getStatsCache(); - for (auto it : cache) { - if (mce::UUID(it.first.uuid) != uuid) continue; - if (auto r = it.second->getMap(type); r) { - return std::make_optional(*r); - } else { - return std::nullopt; - } - } - return std::nullopt; - } +struct StatsView { + StatsDataMap const* data; + uint64_t playTimeDelta; +}; + +std::string translateStatsCategory(StatsType type) { + auto& i18n = ll::i18n::getInstance(); + auto const translationKey = getStatsCategoryTranslationKey(type); + auto const translation = i18n.get(translationKey, {}); + if (hasTranslation(translation, translationKey)) return std::string(translation); + + // Keep external language files written for the former flat key layout working. + auto const legacyKey = getStatsTypeKey(type); + auto const legacyTranslation = i18n.get(legacyKey, {}); + return hasTranslation(legacyTranslation, legacyKey) ? std::string(legacyTranslation) : std::string(legacyKey); } -std::optional renderStatsContent(mce::UUID uuid, StatsType type, uint64_t tick) { - auto data = getStatsDataMap(uuid, type); +std::string translateStatsEntry(StatsType type, std::string_view typeName) { + auto& i18n = ll::i18n::getInstance(); + auto translate = [&i18n](std::string const& key) -> std::optional { + auto const translation = i18n.get(key, {}); + if (!hasTranslation(translation, key)) return std::nullopt; + return std::string(translation); + }; - if (!data.has_value()) return std::nullopt; + if (isBlockOrItemStatsCategory(type)) { + if (auto translation = translate("item." + std::string(typeName))) return *translation; + if (auto translation = translate("block." + std::string(typeName))) return *translation; + } else if (auto translation = translate(getStatsEntryTranslationKey(type, typeName))) { + return *translation; + } - auto& dataMap = data.value(); + // Older custom language files use the bare Minecraft type name. + auto const legacyTranslation = i18n.get(typeName, {}); + return hasTranslation(legacyTranslation, typeName) ? std::string(legacyTranslation) : std::string(typeName); +} - std::vector dataVector; - for (const auto& pair : dataMap) { - dataVector.push_back(std::make_pair(std::string(ll::i18n::getInstance().get(pair.first, {})), pair.second)); +inline StatsView getStatsView(mce::UUID uuid, StatsType type) { + if (auto* playerStats = findPlayerStats(uuid)) { + auto const level = ll::service::getLevel(); + auto const tick = level ? level->getCurrentTick().tickID : 0; + return {playerStats->getStatsMap(type), playerStats->getPendingPlayTime(tick)}; } + + auto const* cached = findCachedStats(uuid); + return {cached && cached->second ? cached->second->getMap(type) : nullptr, 0}; +} + +std::optional buildStatsEntries(mce::UUID uuid, StatsType type) { + auto const view = getStatsView(uuid, type); + if (!view.data) return std::nullopt; + + auto dataVector = query::buildDisplayEntries(*view.data, type, view.playTimeDelta); if (type == StatsType::custom) { - dataMap["minecraft:play_time"] += tick; - auto levelTick = - std::make_pair("minecraft:total_world_time"_tr(), ll::service::getLevel()->getCurrentServerTick().tickID); - dataVector.push_back(levelTick); - std::sort(dataVector.begin(), dataVector.end(), [](const StatsPair& a, const StatsPair& b) { - return a.first < b.first; - }); - } else { - std::sort(dataVector.begin(), dataVector.end(), [](const StatsPair& a, const StatsPair& b) { - return a.second > b.second; - }); + auto const level = ll::service::getLevel(); + if (level) { + dataVector.emplace_back( + "minecraft:total_world_time", + level->getCurrentServerTick().tickID + ); + } } + for (auto& pair : dataVector) { + pair.first = translateStatsEntry(type, pair.first); + } + query::sortStatsEntries(dataVector, type == StatsType::custom); + return dataVector; +} + +std::string pageSuffix(query::StatsPage const& page) { + auto const current = page.totalPages == 0 ? 0 : page.pageIndex + 1; + return " [" + std::to_string(current) + "/" + std::to_string(page.totalPages) + "]"; +} + +std::optional renderStatsContent(mce::UUID uuid, StatsType type) { + auto entries = buildStatsEntries(uuid, type); + if (!entries) return std::nullopt; - std::string content = ""; - renderContent(content, dataVector); + std::string content; + renderContent(content, *entries); return content; } -inline uint64_t getStatsDataMapValue(StatsDataMap const& map, std::string type) { - uint64_t value; - if (type.empty()) { - value = - std::accumulate(map.begin(), map.end(), 0, [](int total, const StatsPair& p) { return total + p.second; }); - } else { - auto mapValue = map.find(type); - if (mapValue != map.end()) { - value = mapValue->second; - } else { - value = 0; - } - } - return value; +void sendStatsGui(Player& player, StatsType type, std::size_t pageIndex) { + sendStatsGui(player, player.getUuid(), {}, type, pageIndex); } -inline void getRankData(std::vector& data, StatsType statsType, std::string type) { - auto& cache = getStatsCache(); - for (auto it : cache) { - auto name = it.first.name; - auto map = it.second->getMap(statsType); - auto value = getStatsDataMapValue(*map, type); - data.push_back(std::make_pair(name, value)); - }; - std::sort(data.begin(), data.end(), [](const StatsPair& a, const StatsPair& b) { return a.second > b.second; }); +void sendStatsGui( + Player& player, + mce::UUID targetUuid, + std::string targetName, + StatsType type, + std::size_t pageIndex +) { + auto entries = buildStatsEntries(targetUuid, type); + if (!entries) return; + + auto page = query::paginate(std::move(*entries), pageIndex); + auto title = "gui.title.stats"_tr() + " | " + translateStatsCategory(type); + if (!targetName.empty()) title += " | " + targetName; + title += pageSuffix(page); + + std::string content; + renderContent(content, page.entries); + auto fm = ll::form::SimpleForm(title, content); + if (page.pageIndex > 0) { + fm.appendButton("<", [targetUuid, targetName, type, pageIndex = page.pageIndex - 1](Player& viewer) { + sendStatsGui(viewer, targetUuid, targetName, type, pageIndex); + }); + } + if (page.pageIndex + 1 < page.totalPages) { + fm.appendButton(">", [targetUuid, targetName, type, pageIndex = page.pageIndex + 1](Player& viewer) { + sendStatsGui(viewer, targetUuid, targetName, type, pageIndex); + }); + } + fm.sendTo(player); } -void sendRankGui(Player& player, StatsType statsType, std::string type) { - std::vector data; - getRankData(data, statsType, type); - std::string title = type.empty() ? "" : " | " + std::string(ll::i18n::getInstance().get(type, {})); - std::string content = ""; - renderContent(content, data); - auto typeString = StatsTypeMap.at(statsType); - auto fm = ll::form::SimpleForm(); - fm.setTitle("gui.title.rank"_tr() + " | " + std::string(ll::i18n::getInstance().get(typeString, {})) + title) - .setContent(content) - .sendTo(player); +void sendRankGui(Player& player, StatsType statsType, std::string type, std::size_t pageIndex) { + auto page = getStatsRankPage(statsType, type, pageIndex); + std::string title = type.empty() ? "" : " | " + translateStatsEntry(statsType, type); + std::string content; + renderContent(content, page.entries); + auto fm = ll::form::SimpleForm(); + fm.setTitle( + "gui.title.rank"_tr() + " | " + translateStatsCategory(statsType) + title + + pageSuffix(page) + ).setContent(content); + if (page.pageIndex > 0) { + fm.appendButton("<", [statsType, type, pageIndex = page.pageIndex - 1](Player& viewer) { + sendRankGui(viewer, statsType, type, pageIndex); + }); + } + if (page.pageIndex + 1 < page.totalPages) { + fm.appendButton(">", [statsType, type, pageIndex = page.pageIndex + 1](Player& viewer) { + sendRankGui(viewer, statsType, type, pageIndex); + }); + } + fm.sendTo(player); } -} // namespace stats::form \ No newline at end of file +} // namespace stats::form diff --git a/src/mod/Stats/Form/Form.h b/src/mod/Stats/Form/Form.h index 5d018cd..beb872f 100644 --- a/src/mod/Stats/Form/Form.h +++ b/src/mod/Stats/Form/Form.h @@ -1,16 +1,26 @@ #pragma once #include "mod/Stats/StatsData.h" +#include #include #include +#include +#include namespace stats::form { using StatsPair = std::pair; void sendMainGui(Player& player); -void sendStatsGui(Player& player, StatsType type); -void sendRankGui(Player& player, StatsType statsType, std::string type); +void sendStatsGui(Player& player, StatsType type, std::size_t pageIndex = 0); +void sendStatsGui( + Player& player, + mce::UUID targetUuid, + std::string targetName, + StatsType type, + std::size_t pageIndex = 0 +); +void sendRankGui(Player& player, StatsType statsType, std::string type, std::size_t pageIndex = 0); -std::optional renderStatsContent(mce::UUID uuid, StatsType type, uint64_t tick = 0); +std::optional renderStatsContent(mce::UUID uuid, StatsType type); -} // namespace stats::form \ No newline at end of file +} // namespace stats::form diff --git a/src/mod/Stats/Handlers/PlayerCombatStatsHandler.cpp b/src/mod/Stats/Handlers/PlayerCombatStatsHandler.cpp new file mode 100644 index 0000000..8a33c9e --- /dev/null +++ b/src/mod/Stats/Handlers/PlayerCombatStatsHandler.cpp @@ -0,0 +1,145 @@ +#include "mod/Stats/Handlers/PlayerStatsHandlers.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mod/Stats/Stats.h" + +namespace stats::handler { +namespace { + +constexpr double MaxUint64Exclusive = 18446744073709551616.0; + +float positiveFinite(float value) { + return std::isfinite(value) && value > 0.0f ? value : 0.0f; +} + +uint64_t toDamageStatValue(float damage) { + auto const scaled = static_cast(positiveFinite(damage)) * 10.0; + if (!std::isfinite(scaled) || scaled >= MaxUint64Exclusive) return 0; + return static_cast(scaled); +} + +void addDamageStat(PlayerStats& stats, CustomType type, float damage) { + stats.addCustomStats(type, toDamageStatValue(damage)); +} + +} // namespace + +void onPlayerDied(Player& player, ActorDamageSource const& source) { + auto uuid = player.getUuid(); + auto* playerStats = findPlayerStats(uuid); + if (!playerStats) return; + playerStats->addCustomStats(CustomType::deaths); + if (!source.isEntitySource()) return; + + auto* actor = ll::service::getLevel()->fetchEntity(source.getDamagingEntityUniqueID(), false); + if (!actor) return; + if (source.isChildEntitySource()) actor = actor->getOwner(); + if (!actor) return; + playerStats->addStats(StatsType::killed_by, actor->getTypeName()); +} + +void onPlayerKillMob(Player& player, Mob& mob) { + auto uuid = player.getUuid(); + auto* playerStats = findPlayerStats(uuid); + if (!playerStats) return; + if (mob.isType(ActorType::Player)) { + playerStats->addCustomStats(CustomType::player_kills); + } else { + playerStats->addCustomStats(CustomType::mob_kills); + } + playerStats->addStats(StatsType::killed, mob.getTypeName()); +} + +void onPlayerTakenDamage(Player* player, float finalDamage) { + auto uuid = player->getUuid(); + auto* playerStats = findPlayerStats(uuid); + if (!playerStats) return; + + auto health = positiveFinite(player->getHealth()); + auto absorptionAttribute = player->getAttribute(SharedAttributes::ABSORPTION()); + auto absorption = absorptionAttribute.mPtr ? positiveFinite(absorptionAttribute.mPtr->mCurrentValue) : 0.0f; + float damageTaken = positiveFinite(finalDamage); + if (damageTaken == 0.0f) return; + float damageAbsorbed = 0; + if (absorption > 0) { + damageAbsorbed = std::min(damageTaken, absorption); + damageTaken -= damageAbsorbed; + } + damageTaken = std::min(damageTaken, health); + addDamageStat(*playerStats, CustomType::damage_absorbed, damageAbsorbed); + addDamageStat(*playerStats, CustomType::damage_taken, damageTaken); +} + +void onPlayerDealtDamage(Mob* mob, Player* player, float finalDamage) { + auto uuid = player->getUuid(); + auto* playerStats = findPlayerStats(uuid); + if (!playerStats) return; + + auto health = positiveFinite(mob->getHealth()); + auto absorptionAttribute = mob->getAttribute(SharedAttributes::ABSORPTION()); + auto absorption = absorptionAttribute.mPtr ? positiveFinite(absorptionAttribute.mPtr->mCurrentValue) : 0.0f; + float damageTaken = positiveFinite(finalDamage); + if (damageTaken == 0.0f) return; + float damageAbsorbed = 0; + if (absorption > 0) { + damageAbsorbed = std::min(damageTaken, absorption); + damageTaken -= damageAbsorbed; + } + damageTaken = std::min(damageTaken, health); + addDamageStat(*playerStats, CustomType::damage_dealt_absorbed, damageAbsorbed); + addDamageStat(*playerStats, CustomType::damage_dealt, damageTaken); +} + +void onPlayerResistedDamage(Player* player, float resistanceDamage) { + auto* playerStats = findPlayerStats(player->getUuid()); + if (!playerStats) return; + + addDamageStat(*playerStats, CustomType::damage_resisted, resistanceDamage); +} + +void onPlayerDealtResistedDamage(Player* player, float resistanceDamage) { + auto* playerStats = findPlayerStats(player->getUuid()); + if (!playerStats) return; + + addDamageStat(*playerStats, CustomType::damage_dealt_resisted, resistanceDamage); +} + +void onPlayerEffectAdded(Player* player, MobEffectInstance const& effect) { + auto effectId = effect.mId; + auto durationValue = effect.mDuration->mValue; + auto uuid = player->getUuid(); + auto* playerStats = findPlayerStats(uuid); + if (!playerStats) return; + if (effectId == 29 && durationValue == 40 * 60 * 20) { + playerStats->addCustomStats(CustomType::raid_win); + } else if (effectId == 36 && durationValue == 30 * 20) { + playerStats->addCustomStats(CustomType::raid_trigger); + } +} + +void onPlayerBlockUsingShield(Player* player, float damage) { + auto uuid = player->getUuid(); + auto* playerStats = findPlayerStats(uuid); + if (!playerStats) return; + addDamageStat(*playerStats, CustomType::damage_blocked_by_shield, damage); +} + +} // namespace stats::handler diff --git a/src/mod/Stats/Handlers/PlayerInteractionStatsHandler.cpp b/src/mod/Stats/Handlers/PlayerInteractionStatsHandler.cpp new file mode 100644 index 0000000..b3ca288 --- /dev/null +++ b/src/mod/Stats/Handlers/PlayerInteractionStatsHandler.cpp @@ -0,0 +1,36 @@ +#include "mod/Stats/Handlers/PlayerStatsHandlers.h" + +#include +#include + +#include "mod/Stats/Stats.h" + +namespace stats::handler { + +void onPlayerStartSleep(Player* player) { + auto uuid = player->getUuid(); + auto* playerStats = findPlayerStats(uuid); + if (!playerStats) return; + playerStats->addCustomStats(CustomType::sleep_in_bed); +} + +void onPlayerOpenTrading(Player* player) { + auto uuid = player->getUuid(); + auto* playerStats = findPlayerStats(uuid); + if (!playerStats) return; + playerStats->addCustomStats(CustomType::talked_to_villager); +} + +void onPlayerBreedAnimal(mce::UUID uuid) { + auto* playerStats = findPlayerStats(uuid); + if (!playerStats) return; + playerStats->addCustomStats(CustomType::animals_bred); +} + +void onPlayerFishCaught(mce::UUID uuid) { + auto* playerStats = findPlayerStats(uuid); + if (!playerStats) return; + playerStats->addCustomStats(CustomType::fish_caught); +} + +} // namespace stats::handler diff --git a/src/mod/Stats/Handlers/PlayerItemStatsHandler.cpp b/src/mod/Stats/Handlers/PlayerItemStatsHandler.cpp new file mode 100644 index 0000000..e0e4d21 --- /dev/null +++ b/src/mod/Stats/Handlers/PlayerItemStatsHandler.cpp @@ -0,0 +1,113 @@ +#include "mod/Stats/Handlers/PlayerStatsHandlers.h" + +#include +#include +#include +#include +#include +#include + +#include "mod/Stats/Stats.h" + +namespace stats::handler { + +void onPlayerPickUpItem(Player& player, ItemStack& item) { + auto uuid = player.getUuid(); + auto* playerStats = findPlayerStats(uuid); + if (!playerStats) return; + playerStats->addStats(StatsType::picked_up, item.getTypeName(), item.mCount); +} + +void onPlayerDropItem(Player* player, ItemStack const& item) { + auto uuid = player->getUuid(); + auto* playerStats = findPlayerStats(uuid); + if (!playerStats) return; + playerStats->addCustomStats(CustomType::drop); + playerStats->addStats(StatsType::dropped, item.getTypeName(), item.mCount); +} + +void onPlayerCraftedItem(mce::UUID uuid, std::string itemType, int amount, SharedTypes::Legacy::ContainerType type) { + auto* playerStats = findPlayerStats(uuid); + if (!playerStats) return; + switch (type) { + case SharedTypes::Legacy::ContainerType::Trade: + playerStats->addStats(StatsType::crafted, itemType, amount); + playerStats->addCustomStats(CustomType::traded_with_villager); + break; + case SharedTypes::Legacy::ContainerType::Enchantment: + playerStats->addCustomStats(CustomType::enchant_item); + break; + case SharedTypes::Legacy::ContainerType::Workbench: + case SharedTypes::Legacy::ContainerType::Inventory: + playerStats->addStats(StatsType::crafted, itemType, amount); + break; + default: + break; + } +} + +void onPlayerItemHurtAndBroken(Player* player, ItemStackBase* item, int deltaDamage) { + auto uuid = player->getUuid(); + auto* playerStats = findPlayerStats(uuid); + if (!playerStats || !item->isDamageableItem()) return; + if (!item->isArmorItem()) playerStats->addStats(StatsType::used, item->getTypeName()); + + auto const maxDamage = item->mItem->getMaxDamage(); + auto const damage = item->getDamageValue(); + if (damage + deltaDamage > maxDamage) { + playerStats->addStats(StatsType::broken, item->getTypeName()); + } +} + +void onPlayerUsedItem(Player* player, ItemStackBase& instance, ItemUseMethod itemUseMethod, bool consumeItem) { + // Several entity interactions and special items require dedicated hooks. + if (!consumeItem) return; + auto uuid = player->getUuid(); + auto* playerStats = findPlayerStats(uuid); + if (!playerStats) return; + switch (itemUseMethod) { + case ItemUseMethod::Eat: + case ItemUseMethod::Consume: + case ItemUseMethod::Throw: + case ItemUseMethod::FillBottle: + case ItemUseMethod::PourBucket: + playerStats->addStats(StatsType::used, instance.getTypeName()); + break; + case ItemUseMethod::Place: + playerStats->addStats(StatsType::used, instance.getTypeName()); + if (instance.mItem->isMusicDisk()) { + playerStats->addCustomStats(CustomType::play_record); + } + break; + case ItemUseMethod::Interact: + if (instance.getTypeName() == "minecraft:bone_meal") { + playerStats->addStats(StatsType::used, instance.getTypeName()); + } + break; + default: + break; + } +} + +void onPlayerChangeContainerWith( + Player& player, + std::string blockType, + int slot, + ItemStack const& oldItem, + ItemStack const& newItem +) { + auto uuid = player.getUuid(); + auto* playerStats = findPlayerStats(uuid); + if (!playerStats) return; + if (blockType != "minecraft:furnace" && blockType != "minecraft:lit_furnace" + && blockType != "minecraft:smoker") { + return; + } + if (slot != 2 || oldItem.isNull()) return; + if (oldItem.mCount > newItem.mCount) { + auto const extracted = static_cast(oldItem.mCount) - static_cast(newItem.mCount); + playerStats->addStats(StatsType::crafted, oldItem.getTypeName(), extracted); + } +} + +} // namespace stats::handler diff --git a/src/mod/Stats/Handlers/PlayerMovementStatsHandler.cpp b/src/mod/Stats/Handlers/PlayerMovementStatsHandler.cpp new file mode 100644 index 0000000..7663e93 --- /dev/null +++ b/src/mod/Stats/Handlers/PlayerMovementStatsHandler.cpp @@ -0,0 +1,178 @@ +#include "mod/Stats/Handlers/PlayerStatsHandlers.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mod/Stats/Stats.h" + +namespace stats::handler { +namespace { + +// A player normally moves well below this in a single auth-input packet. Larger +// deltas are teleports or invalid client input and must not become statistics. +constexpr float MaxDistancePerAuthInput = 10.0f; + +std::optional toCentimeters(float distance) { + if (!std::isfinite(distance) || distance <= 0.0f || distance > MaxDistancePerAuthInput) return std::nullopt; + return static_cast(std::floor(static_cast(distance) * 100.0)); +} + +uint64_t saturatingAdd(uint64_t current, uint64_t value) { + return value > std::numeric_limits::max() - current ? std::numeric_limits::max() + : current + value; +} + +} // namespace + +void onPlayerStartSneaking(Player& player) { + auto uuid = player.getUuid(); + auto* playerStats = findPlayerStats(uuid); + if (!playerStats) return; + playerStats->startSneaking(ll::service::getLevel()->getCurrentTick().tickID); +} + +void onPlayerStopSneaking(Player& player) { + auto uuid = player.getUuid(); + auto* playerStats = findPlayerStats(uuid); + if (!playerStats) return; + playerStats->stopSneaking(ll::service::getLevel()->getCurrentTick().tickID); +} + +void onPlayerStartSprinting(Player& player) { + auto uuid = player.getUuid(); + auto* playerStats = findPlayerStats(uuid); + if (!playerStats) return; + playerStats->mDistanceCache.isSprinting = true; +} + +void onPlayerStopSprinting(Player& player) { + auto uuid = player.getUuid(); + auto* playerStats = findPlayerStats(uuid); + if (!playerStats) return; + playerStats->mDistanceCache.isSprinting = false; +} + +void onPlayerStartRiding(Player& player) { + auto* playerStats = findPlayerStats(player.getUuid()); + if (!playerStats) return; + playerStats->mDistanceCache.ride = 0; + playerStats->mLastPos = player.getPosition(); +} + +void onPlayerStopRiding(mce::UUID uuid, Actor* vehicle) { + auto* playerStats = findPlayerStats(uuid); + if (!playerStats) return; + + auto const value = playerStats->mDistanceCache.ride; + if (!vehicle) { + playerStats->mDistanceCache.ride = 0; + return; + } + if (vehicle->hasCategory(ActorCategory::BoatRideable)) { + playerStats->addCustomStats(CustomType::boat_one_cm, value); + } else if (vehicle->hasCategory(ActorCategory::MinecartRidable)) { + playerStats->addCustomStats(CustomType::minecart_one_cm, value); + } else if (vehicle->isType(ActorType::Horse)) { + playerStats->addCustomStats(CustomType::horse_one_cm, value); + } else if (vehicle->isType(ActorType::Pig)) { + playerStats->addCustomStats(CustomType::pig_one_cm, value); + } else if (vehicle->isType(ActorType::Strider)) { + playerStats->addCustomStats(CustomType::strider_one_cm, value); + } else if (vehicle->isType(ActorType::HappyGhast)) { + playerStats->addCustomStats(CustomType::happy_ghast_one_cm, value); + } else if (vehicle->isType(ActorType::Nautilus)) { + playerStats->addCustomStats(CustomType::nautilus_one_cm, value); + } + playerStats->mDistanceCache.ride = 0; +} + +void onPlayerAuthInput(ServerPlayer& player, PlayerAuthInputPacket const& packet) { + auto const uuid = player.getUuid(); + auto* playerStats = findPlayerStats(uuid); + if (!playerStats) return; + + auto const& position = player.getPosition(); + auto const& posDelta = *packet.mPosDelta; + auto const& inputData = *packet.mInputData; + auto const dimId = player.getDimensionId().mValue; + + auto updateGlidingState = [&] { + if (inputData.test(static_cast(PlayerAuthInputPacket::InputData::StartGliding))) { + playerStats->mDistanceCache.isGliding = true; + } else if (inputData.test(static_cast(PlayerAuthInputPacket::InputData::StopGliding))) { + playerStats->mDistanceCache.isGliding = false; + } + }; + + if (playerStats->mLastDimensionId != dimId) { + playerStats->mLastDimensionId = dimId; + playerStats->mLastPos = position; + updateGlidingState(); + return; + } + if (player.getVehicle() != nullptr) { + if (auto const value = toCentimeters(position.distanceTo(playerStats->mLastPos))) { + playerStats->mDistanceCache.ride = saturatingAdd(playerStats->mDistanceCache.ride, *value); + } + playerStats->mLastPos = position; + } else { + // Some dismount paths may not emit the stop-riding callback. + playerStats->mDistanceCache.ride = 0; + + if (player.isInWaterOrRain()) { + if (auto const value = toCentimeters(posDelta.length()); value && player.isSwimming()) { + playerStats->addCustomStats(CustomType::swim_one_cm, *value); + } else if (value && player.isImmersedInWater()) { + playerStats->addCustomStats(CustomType::walk_on_water_one_cm, *value); + } else if (value) { + playerStats->addCustomStats(CustomType::walk_under_water_one_cm, *value); + } + } else if (player.isFlying()) { + if (auto const value = toCentimeters(posDelta.length())) { + playerStats->addCustomStats(CustomType::fly_one_cm, *value); + } + } else if (player.isOnGround()) { + auto const posOffset = Vec3{0, -0.0784, 0}; + if (auto const value = toCentimeters(posDelta.distanceTo(posOffset))) { + if (playerStats->mDistanceCache.isSneaking) { + playerStats->mDistanceCache.sneak = saturatingAdd(playerStats->mDistanceCache.sneak, *value); + } else if (playerStats->mDistanceCache.isSprinting) { + playerStats->addCustomStats(CustomType::sprint_one_cm, *value); + } else { + playerStats->addCustomStats(CustomType::walk_one_cm, *value); + } + } + } else if (player.onClimbableBlock()) { + auto const valueY = posDelta.y + 0.0784; + if (auto const value = toCentimeters(valueY)) { + playerStats->addCustomStats(CustomType::climb_one_cm, *value); + } + } else if (playerStats->mDistanceCache.isGliding) { + if (auto const value = toCentimeters(posDelta.length())) { + playerStats->addCustomStats(CustomType::aviate_one_cm, *value); + } + } + } + updateGlidingState(); +} + +void onPlayerJump(Player& player) { + auto uuid = player.getUuid(); + auto* playerStats = findPlayerStats(uuid); + if (!playerStats) return; + playerStats->addCustomStats(CustomType::jump); +} + +} // namespace stats::handler diff --git a/src/mod/Stats/Handlers/PlayerSessionStatsHandler.cpp b/src/mod/Stats/Handlers/PlayerSessionStatsHandler.cpp new file mode 100644 index 0000000..ce204ad --- /dev/null +++ b/src/mod/Stats/Handlers/PlayerSessionStatsHandler.cpp @@ -0,0 +1,28 @@ +#include "mod/Stats/Handlers/PlayerStatsHandlers.h" + +#include +#include +#include +#include + +#include "mod/Stats/Stats.h" + +namespace stats::handler { + +void onPlayerJoin(Player& player) { + if (player.isSimulatedPlayer()) return; + addPlayerStats(player); +} + +void onPlayerDisconnect(ServerPlayer& player) { + auto uuid = player.getUuid(); + auto* playerStats = findPlayerStats(uuid); + if (!playerStats) return; + + playerStats->checkpoint(ll::service::getLevel()->getCurrentTick().tickID); + playerStats->addCustomStats(CustomType::leave_game); + savePlayerStats(*playerStats); + removePlayerStats(uuid); +} + +} // namespace stats::handler diff --git a/src/mod/Stats/Handlers/PlayerStatsHandlers.h b/src/mod/Stats/Handlers/PlayerStatsHandlers.h new file mode 100644 index 0000000..e48ac1a --- /dev/null +++ b/src/mod/Stats/Handlers/PlayerStatsHandlers.h @@ -0,0 +1,57 @@ +#pragma once + +#include + +#include +#include + +class Actor; +class ActorDamageSource; +class ItemStack; +class ItemStackBase; +class Mob; +class MobEffectInstance; +class Player; +class PlayerAuthInputPacket; +class ServerPlayer; + +namespace mce { +class UUID; +} + +namespace stats::handler { +void onPlayerJoin(Player& player); +void onPlayerDisconnect(ServerPlayer& player); +void onPlayerStartSneaking(Player& player); +void onPlayerStopSneaking(Player& player); +void onPlayerStartSprinting(Player& player); +void onPlayerStopSprinting(Player& player); +void onPlayerStartRiding(Player& player); +void onPlayerStopRiding(mce::UUID uuid, Actor* vehicle); +void onPlayerAuthInput(ServerPlayer& player, PlayerAuthInputPacket const& packet); +void onPlayerPickUpItem(Player& player, ItemStack& item); +void onPlayerDropItem(Player* player, ItemStack const& item); +void onPlayerDied(Player& player, ActorDamageSource const& source); +void onPlayerKillMob(Player& player, Mob& mob); +void onPlayerTakenDamage(Player* player, float finalDamage); +void onPlayerDealtDamage(Mob* mob, Player* player, float finalDamage); +void onPlayerResistedDamage(Player* player, float resistanceDamage); +void onPlayerDealtResistedDamage(Player* player, float resistanceDamage); +void onPlayerCraftedItem(mce::UUID uuid, std::string itemType, int amount, SharedTypes::Legacy::ContainerType type); +void onPlayerItemHurtAndBroken(Player* player, ItemStackBase* item, int deltaDamage); +void onPlayerUsedItem(Player* player, ItemStackBase& instance, ItemUseMethod itemUseMethod, bool consumeItem); +void onPlayerEffectAdded(Player* player, MobEffectInstance const& effect); +void onPlayerStartSleep(Player* player); +void onPlayerBlockUsingShield(Player* player, float damage); +void onPlayerOpenTrading(Player* player); +void onPlayerJump(Player& player); +void onPlayerChangeContainerWith( + Player& player, + std::string blockType, + int slot, + ItemStack const& oldItem, + ItemStack const& newItem +); +void onPlayerBreedAnimal(mce::UUID uuid); +void onPlayerFishCaught(mce::UUID uuid); +} // namespace stats::handler diff --git a/src/mod/Stats/PlayerStats.cpp b/src/mod/Stats/PlayerStats.cpp index bdc6c78..90cd883 100644 --- a/src/mod/Stats/PlayerStats.cpp +++ b/src/mod/Stats/PlayerStats.cpp @@ -1,127 +1,74 @@ #include "mod/Stats/PlayerStats.h" #include -#include -#include -#include -#include -#include - -#include "mod/Stats/Stats.h" - -#include #include -#include #include - -using namespace ll::i18n_literals; +#include namespace stats { -PlayerStats::PlayerStats(Player const& player) { - mUuid = player.getUuid(); - mXuid = player.getXuid(); - mName = player.getRealName(); - mSneakingStartTick = 0; - mLastPos = const_cast(player.getPosition()); - mLastDimensionId = player.getDimensionId().mValue; - - auto cache = getStatsCache(); - auto uuid = mUuid.asString(); - StatsCache::iterator it = std::find_if(cache.begin(), cache.end(), [&uuid](const StatsCacheData& data) { - return data.first.uuid.compare(uuid) == 0; - }); - if (it != cache.end()) { - mData = it->second; - } else { - getLogger().debug("log.info.createData"_tr(mName)); - mData = std::make_shared(); - PlayerInfo info = {mUuid.asString(), mXuid, mName}; - cache.push_back(std::make_pair(info, mData)); - saveData(); - } -}; -mce::UUID PlayerStats::getUuid() { return mUuid; }; -std::filesystem::path PlayerStats::getPath() { return getStatsPath().concat("/" + mUuid.asString() + ".json"); }; -nlohmann::json PlayerStats::getJson() { - nlohmann::json j = { - {"playerInfo", {{"uuid", mUuid.asString()}, {"xuid", mXuid}, {"name", mName}}}, - {"minecraft:custom", mData->custom }, - {"minecraft:mined", mData->mined }, - {"minecraft:broken", mData->broken }, - {"minecraft:crafted", mData->crafted }, - {"minecraft:used", mData->used }, - {"minecraft:picked_up", mData->picked_up }, - {"minecraft:dropped", mData->dropped }, - {"minecraft:killed", mData->killed }, - {"minecraft:killed_by", mData->killed_by }, - }; - return j; +PlayerStats::PlayerStats(PlayerSessionInit init, std::shared_ptr data) +: mData(std::move(data)), + mUuid(init.uuid), + mXuid(std::move(init.xuid)), + mName(std::move(init.name)), + mSneakingStartTick(0), + mLastCheckpointTick(init.currentTick), + mLastPos(init.position), + mLastDimensionId(init.dimensionId) {} +mce::UUID PlayerStats::getUuid() const { return mUuid; }; +PlayerInfo PlayerStats::getInfo() const { return {mUuid.asString(), mXuid, mName}; } +StatsData const& PlayerStats::getData() const { return *mData; } +StatsDataMap const* PlayerStats::getStatsMap(StatsType type) const { + return static_cast(*mData).getMap(type); } -bool PlayerStats::saveData() { - getLogger().debug("log.info.savaData"_tr(mName)); - auto j = getJson(); - try { - return ll::file_utils::writeFile(getPath(), j.dump()); - } catch (std::exception& exception) { - getLogger().error(exception.what()); - return false; - } -}; -void PlayerStats::addStats(StatsType type, std::string key, uint64_t value) { +void PlayerStats::addStats(StatsType type, std::string const& key, uint64_t value) { if (value <= 0) return; - switch (type) { - case StatsType::custom: - mData->custom[key] += value; - break; - case StatsType::mined: - mData->mined[key] += value; - break; - case StatsType::broken: - mData->broken[key] += value; - break; - case StatsType::crafted: - mData->crafted[key] += value; - break; - case StatsType::used: - mData->used[key] += value; - break; - case StatsType::picked_up: - mData->picked_up[key] += value; - break; - case StatsType::dropped: - mData->dropped[key] += value; - break; - case StatsType::killed: - mData->killed[key] += value; - break; - case StatsType::killed_by: - mData->killed_by[key] += value; - break; - } - auto typeName = StatsTypeMap.at(type); - // getLogger().debug("AddStats {} {} key:{} value:{}", mName, typeName, key, value); + mData->add(type, key, value); }; void PlayerStats::addCustomStats(CustomType type, uint64_t value) { if (value <= 0) return; - auto key = CustomTypeMap.at(type); - // getLogger().debug("AddCustomStats {} key:{} value:{}", mName, key, value); - - mData->custom[key] += value; + mData->custom.add(type, value); }; -void PlayerStats::startSneaking() { - mSneakingStartTick = ll::service::getLevel()->getCurrentTick().tickID; +void PlayerStats::startSneaking(uint64_t currentTick) { + mSneakingStartTick = currentTick; mDistanceCache.isSneaking = true; mDistanceCache.sneak = 0; }; -void PlayerStats::stopSneaking() { +void PlayerStats::stopSneaking(uint64_t currentTick) { if (mSneakingStartTick == 0) return; - auto record = ll::service::getLevel()->getCurrentTick().tickID - mSneakingStartTick; + if (currentTick < mSneakingStartTick) { + mSneakingStartTick = 0; + mDistanceCache.isSneaking = false; + mDistanceCache.sneak = 0; + return; + } + auto record = currentTick - mSneakingStartTick; addCustomStats(CustomType::sneak_time, record); addCustomStats(CustomType::crouch_one_cm, mDistanceCache.sneak); mSneakingStartTick = 0; mDistanceCache.isSneaking = false; mDistanceCache.sneak = 0; }; -} // namespace stats \ No newline at end of file + +void PlayerStats::checkpoint(uint64_t currentTick) { + if (currentTick < mLastCheckpointTick) { + mLastCheckpointTick = currentTick; + return; + } + + addCustomStats(CustomType::play_time, currentTick - mLastCheckpointTick); + mLastCheckpointTick = currentTick; + + if (!mDistanceCache.isSneaking || mSneakingStartTick == 0) return; + addCustomStats(CustomType::sneak_time, currentTick - mSneakingStartTick); + addCustomStats(CustomType::crouch_one_cm, mDistanceCache.sneak); + mSneakingStartTick = currentTick; + mDistanceCache.sneak = 0; +} + +uint64_t PlayerStats::getPendingPlayTime(uint64_t currentTick) const { + return currentTick < mLastCheckpointTick ? 0 : currentTick - mLastCheckpointTick; +} +} // namespace stats diff --git a/src/mod/Stats/PlayerStats.h b/src/mod/Stats/PlayerStats.h index 6341549..f13dcd1 100644 --- a/src/mod/Stats/PlayerStats.h +++ b/src/mod/Stats/PlayerStats.h @@ -2,16 +2,26 @@ #include "mod/Stats/StatsCustom.h" #include "mod/Stats/StatsData.h" +#include "mod/Stats/StatsRecord.h" #include #include -#include -#include -#include +#include +#include +#include namespace stats { +struct PlayerSessionInit { + mce::UUID uuid; + std::string xuid; + std::string name; + Vec3 position; + int dimensionId; + uint64_t currentTick; +}; + class PlayerStats { struct MoveCache { uint64_t ride = 0; @@ -27,8 +37,7 @@ class PlayerStats { std::string mXuid; std::string mName; uint64_t mSneakingStartTick; - std::filesystem::path getPath(); - // void parseData(std::string const& data); + uint64_t mLastCheckpointTick; public: MoveCache mDistanceCache; @@ -36,15 +45,16 @@ class PlayerStats { int mLastDimensionId; // PlayerStats(); public: - PlayerStats(Player const& player); - mce::UUID getUuid(); - nlohmann::json getJson(); - // nlohmann::json getJsonStatsData(StatsType type); - bool saveData(); - void addStats(StatsType type, std::string key, uint64_t value = 1); - void addCustomStats(CustomType type, uint64_t value = 1); - void resetCustomStats(CustomType type, uint64_t value = 0); - void startSneaking(); - void stopSneaking(); + PlayerStats(PlayerSessionInit init, std::shared_ptr data); + mce::UUID getUuid() const; + PlayerInfo getInfo() const; + StatsData const& getData() const; + StatsDataMap const* getStatsMap(StatsType type) const; + void addStats(StatsType type, std::string const& key, uint64_t value = 1); + void addCustomStats(CustomType type, uint64_t value = 1); + void startSneaking(uint64_t currentTick); + void stopSneaking(uint64_t currentTick); + void checkpoint(uint64_t currentTick); + uint64_t getPendingPlayTime(uint64_t currentTick) const; }; -} // namespace stats \ No newline at end of file +} // namespace stats diff --git a/src/mod/Stats/RemoteCall.cpp b/src/mod/Stats/RemoteCall.cpp index 7b51cf1..50a98a8 100644 --- a/src/mod/Stats/RemoteCall.cpp +++ b/src/mod/Stats/RemoteCall.cpp @@ -1,64 +1,48 @@ #include "mc/platform/UUID.h" #include "mod/Stats/Stats.h" #include "mod/Stats/StatsData.h" +#include "mod/Stats/StatsJsonCodec.h" #include -#include #include #include namespace stats { -inline uint64_t getStatsDataMapValue(StatsDataMap const& map, std::string type) { - uint64_t value; - if (type.empty()) { - value = std::accumulate( - map.begin(), - map.end(), - 0, - [](int total, const std::pair& p) { return total + p.second; } - ); - } else { - auto mapValue = map.find(type); - if (mapValue != map.end()) { - value = mapValue->second; - } else { - value = 0; - } - } - return value; +namespace { + +std::size_t pageIndexFromRemote(int page) { return page <= 1 ? 0 : static_cast(page - 1); } + +std::size_t pageSizeFromRemote(int pageSize) { + return pageSize <= 0 ? query::DefaultPageSize : static_cast(pageSize); } -inline void -getRankData(std::vector>& data, StatsType statsType, std::string type) { - auto& cache = getStatsCache(); - for (auto it : cache) { - auto name = it.first.name; - auto map = it.second->getMap(statsType); - auto value = getStatsDataMapValue(*map, type); - data.push_back(std::make_pair(name, value)); - }; - std::sort( - data.begin(), - data.end(), - [](const std::pair& a, const std::pair& b) { - return a.second > b.second; - } - ); +query::StatsPage getPlayerStatsPage(mce::UUID const& uuid, StatsType type, int page, int pageSize) { + query::StatsEntries entries; + auto const* cached = findCachedStats(uuid); + auto const* values = cached && cached->second ? cached->second->getMap(type) : nullptr; + if (values) { + entries.reserve(values->size()); + for (auto const& entry : *values) entries.push_back(entry); + query::sortStatsEntries(entries, type == StatsType::custom); + } + return query::paginate(std::move(entries), pageIndexFromRemote(page), pageSizeFromRemote(pageSize)); } + +} // namespace + void exportRemoteCall() { std::string const RC_NAMESPACE = "LK-Stats"; RemoteCall::exportAs( RC_NAMESPACE, "getPlayerStats", [](std::string uuidStr, int type) -> std::unordered_map { - mce::UUID uuid(uuidStr); - auto& cache = getStatsCache(); - auto r = std::unordered_map(); - if (type < 1 || type > 9) return r; - for (auto& data : cache) { - if (mce::UUID(data.first.uuid) == uuid) { - return *data.second->getMap((StatsType)type); - } + mce::UUID uuid(uuidStr); + auto r = std::unordered_map(); + auto const statsType = static_cast(type); + if (!isValidStatsType(statsType)) return r; + auto const* cached = findCachedStats(uuid); + if (cached && cached->second) { + return *cached->second->getMap(statsType); } return r; } @@ -67,18 +51,49 @@ void exportRemoteCall() { RC_NAMESPACE, "getRankStats", [](int type, std::string key = "") -> std::unordered_map { - auto r = std::vector>(); - if (type < 1 || type > 9) { - } else if (type == 1 && key.empty()) { - } else { - getRankData(r, (StatsType)type, key); + auto const statsType = static_cast(type); + auto r = query::RankData{}; + if (isValidStatsType(statsType) && (statsType != StatsType::custom || !key.empty())) { + r = getStatsRank(statsType, key); } auto data = std::unordered_map(); - for (auto& a : r) { + data.reserve(r.size()); + for (auto const& a : r) { data.emplace(a.first, a.second); } return data; } ); + RemoteCall::exportAs( + RC_NAMESPACE, + "getPlayerStatsPage", + [](std::string uuidStr, int type, int page, int pageSize) -> std::string { + auto const statsType = static_cast(type); + if (!isValidStatsType(statsType)) { + return encodeStatsPageJson(query::paginate({}, 0, pageSizeFromRemote(pageSize)), "key"); + } + return encodeStatsPageJson( + getPlayerStatsPage(mce::UUID(uuidStr), statsType, page, pageSize), + "key" + ); + } + ); + RemoteCall::exportAs( + RC_NAMESPACE, + "getRankStatsPage", + [](int type, std::string key, int page, int pageSize) -> std::string { + auto const statsType = static_cast(type); + query::StatsPage rankPage = query::paginate({}, 0, pageSizeFromRemote(pageSize)); + if (isValidStatsType(statsType) && (statsType != StatsType::custom || !key.empty())) { + rankPage = getStatsRankPage( + statsType, + key, + pageIndexFromRemote(page), + pageSizeFromRemote(pageSize) + ); + } + return encodeStatsPageJson(rankPage, "player"); + } + ); } -} // namespace stats \ No newline at end of file +} // namespace stats diff --git a/src/mod/Stats/Stats.cpp b/src/mod/Stats/Stats.cpp index 31ac57c..aa6714c 100644 --- a/src/mod/Stats/Stats.cpp +++ b/src/mod/Stats/Stats.cpp @@ -1,124 +1,282 @@ #include "mod/Stats/Stats.h" -#include -#include +#include +#include +#include +#include #include #include #include +#include -#include +#include #include -#include +#include #include -#include +#include +#include +#include #include "mod/Events/Events.h" #include "mod/Hook/Hook.h" #include "mod/MyMod.h" #include "mod/Stats/Command/RegisterCommand.h" +#include "mod/Stats/StatsFileRepository.h" using namespace ll::i18n_literals; namespace stats { namespace { -PlayerStatsMap playerStatsMap; -StatsCache statsCache; -std::string levelName; +using PlayerStatsMap = phmap::flat_hash_map>; + +class StatsCacheStore { +public: + using UuidIndex = phmap::node_hash_map; + + StatsCacheData const* find(mce::UUID const& uuid) const { + auto const cached = mByUuid.find(uuid); + return cached == mByUuid.end() ? nullptr : &cached->second; + } + + StatsCacheData const* findByName(std::string const& name) const { + auto const cached = mUuidByName.find(name); + return cached == mUuidByName.end() ? nullptr : find(cached->second); + } + + void upsert(StatsCacheData data) { + auto const uuid = mce::UUID(data.first.uuid); + auto cached = mByUuid.find(uuid); + if (cached != mByUuid.end()) { + auto const oldName = cached->second.first.name; + if (auto const old = mUuidByName.find(oldName); old != mUuidByName.end() && old->second == uuid) { + mUuidByName.erase(old); + } + cached->second = std::move(data); + mUuidByName.insert_or_assign(cached->second.first.name, uuid); + return; + } + + auto const inserted = mByUuid.try_emplace(uuid, std::move(data)).first; + mUuidByName.insert_or_assign(inserted->second.first.name, uuid); + } + + void clear() { + mUuidByName.clear(); + mByUuid.clear(); + } + + void reserve(std::size_t size) { + mByUuid.reserve(size); + mUuidByName.reserve(size); + } + + UuidIndex const& entries() const { return mByUuid; } + +private: + UuidIndex mByUuid; + // Player names are secondary keys; the most recently upserted UUID owns a collision. + phmap::flat_hash_map mUuidByName; +}; + +PlayerStatsMap playerStatsMap; +StatsCacheStore statsCache; +std::shared_ptr autosaveTask; + +constexpr auto AutosaveInterval = std::chrono::minutes{5}; +constexpr auto RankCacheTtl = std::chrono::seconds{30}; +constexpr std::size_t MaxFullRankCacheEntries = 16; +constexpr std::size_t MaxRankPageCacheEntries = 64; + +using RankCacheClock = std::chrono::steady_clock; + +struct RankCacheKey { + StatsType type; + std::string key; + std::size_t pageIndex; + std::size_t pageSize; + + bool operator==(RankCacheKey const&) const = default; +}; + +struct RankCacheKeyHash { + std::size_t operator()(RankCacheKey const& value) const { + auto hash = std::hash{}(value.key); + hash ^= std::hash{}(static_cast(value.type)) + 0x9e3779b9 + (hash << 6) + (hash >> 2); + hash ^= std::hash{}(value.pageIndex) + 0x9e3779b9 + (hash << 6) + (hash >> 2); + hash ^= std::hash{}(value.pageSize) + 0x9e3779b9 + (hash << 6) + (hash >> 2); + return hash; + } +}; + +template +struct CachedRank { + RankCacheClock::time_point createdAt; + Value value; +}; + +using FullRankCache = + phmap::flat_hash_map, RankCacheKeyHash>; +using RankPageCache = + phmap::flat_hash_map, RankCacheKeyHash>; + +FullRankCache rankCache; +RankPageCache rankPageCache; + +template +void trimRankCache(Cache& cache, std::size_t capacity, RankCacheClock::time_point now) { + for (auto entry = cache.begin(); entry != cache.end();) { + if (now - entry->second.createdAt >= RankCacheTtl) { + cache.erase(entry++); + } else { + ++entry; + } + } + if (cache.size() < capacity) return; + + auto oldest = cache.begin(); + for (auto entry = std::next(cache.begin()); entry != cache.end(); ++entry) { + if (entry->second.createdAt < oldest->second.createdAt) oldest = entry; + } + cache.erase(oldest); +} + +void clearRankCaches() { + rankPageCache.clear(); + rankCache.clear(); +} + +void saveOnlinePlayers() { + auto const level = ll::service::getLevel(); + if (!level) return; + + auto const currentTick = level->getCurrentTick().tickID; + for (auto const& [uuid, playerStats] : playerStatsMap) { + (void)uuid; + playerStats->checkpoint(currentTick); + savePlayerStats(*playerStats); + } +} + +void scheduleAutosave() { + autosaveTask = ll::thread::ServerThreadExecutor::getDefault().executeAfter( + [] { + saveOnlinePlayers(); + scheduleAutosave(); + }, + AutosaveInterval + ); +} + +void stopAutosave() { + if (autosaveTask) autosaveTask->cancel(); + autosaveTask.reset(); +} } // namespace ll::io::Logger& getLogger() { return lk::MyMod::getInstance().getSelf().getLogger(); } -PlayerStatsMap& getPlayerStatsMap() { return playerStatsMap; } -StatsCache& getStatsCache() { return statsCache; } - -StatsCacheData parseStatsData(const std::string& data) { - auto j = nlohmann::json::parse(data); - auto tmpData = std::make_shared(); - auto tmpInfo = PlayerInfo(); - tmpInfo.name = j["playerInfo"]["name"]; - tmpInfo.uuid = j["playerInfo"]["uuid"]; - tmpInfo.xuid = j["playerInfo"]["xuid"]; - tmpData->custom = j["minecraft:custom"]; - tmpData->mined = j["minecraft:mined"]; - tmpData->broken = j["minecraft:broken"]; - tmpData->crafted = j["minecraft:crafted"]; - tmpData->used = j["minecraft:used"]; - tmpData->picked_up = j["minecraft:picked_up"]; - tmpData->dropped = j["minecraft:dropped"]; - tmpData->killed = j["minecraft:killed"]; - tmpData->killed_by = j["minecraft:killed_by"]; - StatsCacheData r = std::make_pair(tmpInfo, tmpData); - return r; +PlayerStats* findPlayerStats(mce::UUID const& uuid) { + auto const player = playerStatsMap.find(uuid); + return player == playerStatsMap.end() ? nullptr : player->second.get(); } -inline std::optional getLevelName() { - if (ll::service::getPropertiesSettings().has_value()) { - return ll::service::getPropertiesSettings()->mLevelName; - } else { - std::ifstream fin("server.properties"); - std::string buf; - while (getline(fin, buf)) { - if (buf.find("level-name=") != std::string::npos) { - if (buf.back() == '\n') buf.pop_back(); - if (buf.back() == '\r') buf.pop_back(); - return buf.substr(11); - } - } +void addPlayerStats(Player const& player) { + auto const uuid = player.getUuid(); + auto const* cached = findCachedStats(uuid); + bool const needsInitialSave = !cached || !cached->second; + auto data = needsInitialSave ? std::make_shared() : cached->second; + + PlayerInfo info{uuid.asString(), player.getXuid(), player.getRealName()}; + if (needsInitialSave) getLogger().debug("log.info.player_data_creating"_tr(info.name)); + upsertStatsCache(std::make_pair(info, data)); + PlayerSessionInit session{ + uuid, + info.xuid, + info.name, + player.getPosition(), + player.getDimensionId().mValue, + ll::service::getLevel()->getCurrentTick().tickID, + }; + playerStatsMap.try_emplace(uuid, std::make_unique(std::move(session), data)); + if (needsInitialSave) repository::save(info, *data); +} + +bool savePlayerStats(PlayerStats const& playerStats) { + auto const info = playerStats.getInfo(); + getLogger().debug("log.info.player_data_saving"_tr(info.name)); + return repository::save(info, playerStats.getData()); +} + +void removePlayerStats(mce::UUID const& uuid) { playerStatsMap.erase(uuid); } + +StatsCacheData const* findCachedStats(mce::UUID const& uuid) { return statsCache.find(uuid); } + +StatsCacheData const* findCachedStatsByName(std::string const& name) { return statsCache.findByName(name); } + +void upsertStatsCache(StatsCacheData data) { statsCache.upsert(std::move(data)); } + +void clearStatsCache() { + clearRankCaches(); + statsCache.clear(); +} + +query::RankData getStatsRank(StatsType type, std::string const& key) { + RankCacheKey const cacheKey{type, key, 0, 0}; + auto const now = RankCacheClock::now(); + if (auto cached = rankCache.find(cacheKey); cached != rankCache.end()) { + if (now - cached->second.createdAt < RankCacheTtl) return cached->second.value; + rankCache.erase(cached); + } + + query::RankBuilder builder(statsCache.entries().size(), key); + + for (auto const& entry : statsCache.entries()) { + auto const& data = entry.second; + auto const* stats = data.second ? data.second->getMap(type) : nullptr; + builder.add(data.first.name, stats); } - return std::nullopt; + auto result = std::move(builder).finish(); + trimRankCache(rankCache, MaxFullRankCacheEntries, now); + rankCache.insert_or_assign(cacheKey, CachedRank{RankCacheClock::now(), result}); + return result; } -std::filesystem::path getStatsPath() { - if (!levelName.empty()) { - return ll::file_utils::u8path("./worlds/" + levelName + "/stats"); - } else { - auto name = getLevelName(); - if (name.has_value()) { - levelName = name.value(); - } - return ll::file_utils::u8path("./worlds/" + levelName + "/stats"); + +query::StatsPage getStatsRankPage( + StatsType type, + std::string const& key, + std::size_t pageIndex, + std::size_t pageSize +) { + pageSize = std::clamp(pageSize, std::size_t{1}, query::MaxPageSize); + RankCacheKey const cacheKey{type, key, pageIndex, pageSize}; + auto const now = RankCacheClock::now(); + if (auto cached = rankPageCache.find(cacheKey); cached != rankPageCache.end()) { + if (now - cached->second.createdAt < RankCacheTtl) return cached->second.value; + rankPageCache.erase(cached); + } + + query::RankBuilder builder(statsCache.entries().size(), key); + for (auto const& entry : statsCache.entries()) { + auto const& data = entry.second; + auto const* stats = data.second ? data.second->getMap(type) : nullptr; + builder.add(data.first.name, stats); } + auto result = std::move(builder).finishPage(pageIndex, pageSize); + trimRankCache(rankPageCache, MaxRankPageCacheEntries, now); + rankPageCache.insert_or_assign(cacheKey, CachedRank{RankCacheClock::now(), result}); + return result; } bool loadStatsCache() { - auto oldPath = ll::file_utils::u8path("./stats"); - auto newPath = getStatsPath(); - std::string extension = ".json"; - auto existOldPath = std::filesystem::exists(oldPath); - auto existPath = std::filesystem::exists(newPath); - if (!existPath) { - if (existOldPath) { - getLogger().warn("log.info.ExistOldPath"_tr()); - try { - std::filesystem::rename(oldPath, newPath); - } catch (std::exception& excep) { - getLogger().error(excep.what()); - getLogger().warn("log.warn.moveStats.fail"_tr()); - return false; - } - } else { - getLogger().warn("log.info.CreateStatsPath"_tr()); - try { - std::filesystem::create_directory(newPath); - } catch (std::exception& excep) { - getLogger().error(excep.what()); - getLogger().warn("log.warn.CreatePath.fail"_tr()); - return false; - } - } - } - for (const auto& entry : std::filesystem::directory_iterator(newPath)) { - // 检查文件是否为所需后缀 - if (entry.path().extension() == extension) { - auto rawData = ll::file_utils::readFile(entry.path()); - try { - auto data = parseStatsData(*rawData); - statsCache.push_back(data); - } catch (std::exception& excep) { - getLogger().error(excep.what()); - getLogger().warn("data.parse.fail"_tr(entry.path().filename())); - } - } + clearStatsCache(); + if (!repository::initialize()) return false; + + std::vector records; + if (!repository::loadAll(records)) return false; + statsCache.reserve(records.size()); + for (auto& record : records) { + upsertStatsCache(std::make_pair(std::move(record.info), std::make_shared(std::move(record.data)))); } return true; } @@ -129,15 +287,20 @@ void load() { hook::hook(); command::registerCommand(); exportRemoteCall(); - getLogger().info("plugins.load.success"_tr()); + scheduleAutosave(); + getLogger().info("log.info.plugin_loaded"_tr()); } else { - getLogger().warn("plugins.load.fail"_tr()); + repository::shutdown(); + getLogger().warn("log.warning.plugin_load_failed"_tr()); } } void unload() { + stopAutosave(); + saveOnlinePlayers(); event::removeEvents(); hook::unhook(); + repository::shutdown(); } void printLogo() { @@ -153,4 +316,4 @@ void printLogo() { /// logger.info(R"( ---------------- LeafKnife Statistics Plugin --------------- )"); logger.info("Repository: https://github.com/LeafKnife/Stats"); } -} // namespace stats \ No newline at end of file +} // namespace stats diff --git a/src/mod/Stats/Stats.h b/src/mod/Stats/Stats.h index 7b5066d..45ad67d 100644 --- a/src/mod/Stats/Stats.h +++ b/src/mod/Stats/Stats.h @@ -1,33 +1,40 @@ #pragma once #include "mod/Stats/PlayerStats.h" +#include "mod/Stats/StatsQuery.h" +#include "mod/Stats/StatsRecord.h" #include #include +#include #include -#include #include +class Player; + namespace stats { -struct PlayerInfo { - std::string uuid; - std::string xuid; - std::string name; -}; -typedef std::map> PlayerStatsMap; typedef std::pair> StatsCacheData; -typedef std::vector StatsCache; - -ll::io::Logger& getLogger(); -PlayerStatsMap& getPlayerStatsMap(); -StatsCache& getStatsCache(); -StatsCacheData parseStatsData(std::string const& data); -std::filesystem::path getStatsPath(); +ll::io::Logger& getLogger(); +PlayerStats* findPlayerStats(mce::UUID const& uuid); +void addPlayerStats(Player const& player); +bool savePlayerStats(PlayerStats const& playerStats); +void removePlayerStats(mce::UUID const& uuid); +StatsCacheData const* findCachedStats(mce::UUID const& uuid); +StatsCacheData const* findCachedStatsByName(std::string const& name); +void upsertStatsCache(StatsCacheData data); +void clearStatsCache(); +query::RankData getStatsRank(StatsType type, std::string const& key); +query::StatsPage getStatsRankPage( + StatsType type, + std::string const& key, + std::size_t pageIndex, + std::size_t pageSize = query::DefaultPageSize +); bool loadStatsCache(); void load(); void unload(); void printLogo(); void exportRemoteCall(); -} // namespace stats \ No newline at end of file +} // namespace stats diff --git a/src/mod/Stats/StatsCustom.h b/src/mod/Stats/StatsCustom.h index ac2fda8..031b1f2 100644 --- a/src/mod/Stats/StatsCustom.h +++ b/src/mod/Stats/StatsCustom.h @@ -1,7 +1,10 @@ #pragma once -#include +#include +#include #include +#include +#include namespace stats { @@ -82,88 +85,95 @@ enum struct CustomType { sleep_in_bed, traded_with_villager, trigger_trapped_chest, - use_cauldron + use_cauldron, + count }; -static const std::map CustomTypeMap = { - {CustomType::animals_bred, "minecraft:animals_bred" }, - {CustomType::clean_armor, "minecraft:clean_armor" }, - {CustomType::clean_banner, "minecraft:clean_banner" }, - {CustomType::open_barrel, "minecraft:open_barrel" }, - {CustomType::bell_ring, "minecraft:bell_ring" }, - {CustomType::eat_cake_slice, "minecraft:eat_cake_slice" }, - {CustomType::fill_cauldron, "minecraft:fill_cauldron" }, - {CustomType::open_chest, "minecraft:open_chest" }, - {CustomType::damage_absorbed, "minecraft:damage_absorbed" }, - {CustomType::damage_blocked_by_shield, "minecraft:damage_blocked_by_shield" }, - {CustomType::damage_dealt, "minecraft:damage_dealt" }, - {CustomType::damage_dealt_absorbed, "minecraft:damage_dealt_absorbed" }, - {CustomType::damage_dealt_resisted, "minecraft:damage_dealt_resisted" }, - {CustomType::damage_resisted, "minecraft:damage_resisted" }, - {CustomType::damage_taken, "minecraft:damage_taken" }, - {CustomType::inspect_dispenser, "minecraft:inspect_dispenser" }, - {CustomType::nautilus_one_cm, "minecraft:nautilus_one_cm" }, - {CustomType::happy_ghast_one_cm, "minecraft:happy_ghast_one_cm" }, - {CustomType::boat_one_cm, "minecraft:boat_one_cm" }, - {CustomType::aviate_one_cm, "minecraft:aviate_one_cm" }, - {CustomType::horse_one_cm, "minecraft:horse_one_cm" }, - {CustomType::minecart_one_cm, "minecraft:minecart_one_cm" }, - {CustomType::pig_one_cm, "minecraft:pig_one_cm" }, - {CustomType::strider_one_cm, "minecraft:strider_one_cm" }, - {CustomType::climb_one_cm, "minecraft:climb_one_cm" }, - {CustomType::crouch_one_cm, "minecraft:crouch_one_cm" }, - {CustomType::fall_one_cm, "minecraft:fall_one_cm" }, - {CustomType::fly_one_cm, "minecraft:fly_one_cm" }, - {CustomType::sprint_one_cm, "minecraft:sprint_one_cm" }, - {CustomType::swim_one_cm, "minecraft:swim_one_cm" }, - {CustomType::walk_one_cm, "minecraft:walk_one_cm" }, - {CustomType::walk_on_water_one_cm, "minecraft:walk_on_water_one_cm" }, - {CustomType::walk_under_water_one_cm, "minecraft:walk_under_water_one_cm" }, - {CustomType::inspect_dropper, "minecraft:inspect_dropper" }, - {CustomType::open_enderchest, "minecraft:open_enderchest" }, - {CustomType::fish_caught, "minecraft:fish_caught" }, - {CustomType::leave_game, "minecraft:leave_game" }, - {CustomType::inspect_hopper, "minecraft:inspect_hopper" }, - {CustomType::interact_with_anvil, "minecraft:interact_with_anvil" }, - {CustomType::interact_with_beacon, "minecraft:interact_with_beacon" }, - {CustomType::interact_with_blast_furnace, "minecraft:interact_with_blast_furnace" }, - {CustomType::interact_with_brewingstand, "minecraft:interact_with_brewingstand" }, - {CustomType::interact_with_campfire, "minecraft:interact_with_campfire" }, - {CustomType::interact_with_cartography_table, "minecraft:interact_with_cartography_table"}, - {CustomType::interact_with_crafting_table, "minecraft:interact_with_crafting_table" }, - {CustomType::interact_with_furnace, "minecraft:interact_with_furnace" }, - {CustomType::interact_with_grindstone, "minecraft:interact_with_grindstone" }, - {CustomType::interact_with_lectern, "minecraft:interact_with_lectern" }, - {CustomType::interact_with_loom, "minecraft:interact_with_loom" }, - {CustomType::interact_with_smithing_table, "minecraft:interact_with_smithing_table" }, - {CustomType::interact_with_smoker, "minecraft:interact_with_smoker" }, - {CustomType::interact_with_stonecutter, "minecraft:interact_with_stonecutter" }, - {CustomType::drop, "minecraft:drop" }, - {CustomType::enchant_item, "minecraft:enchant_item" }, - {CustomType::jump, "minecraft:jump" }, - {CustomType::mob_kills, "minecraft:mob_kills" }, - {CustomType::play_record, "minecraft:play_record" }, - {CustomType::play_noteblock, "minecraft:play_noteblock" }, - {CustomType::tune_noteblock, "minecraft:tune_noteblock" }, - {CustomType::deaths, "minecraft:deaths" }, - {CustomType::pot_flower, "minecraft:pot_flower" }, - {CustomType::player_kills, "minecraft:player_kills" }, - {CustomType::raid_trigger, "minecraft:raid_trigger" }, - {CustomType::raid_win, "minecraft:raid_win" }, - {CustomType::clean_shulker_box, "minecraft:clean_shulker_box" }, - {CustomType::open_shulker_box, "minecraft:open_shulker_box" }, - {CustomType::time_since_death, "minecraft:time_since_death" }, - {CustomType::time_since_rest, "minecraft:time_since_rest" }, - {CustomType::sneak_time, "minecraft:sneak_time" }, - {CustomType::talked_to_villager, "minecraft:talked_to_villager" }, - {CustomType::target_hit, "minecraft:target_hit" }, - {CustomType::play_time, "minecraft:play_time" }, - {CustomType::total_world_time, "minecraft:total_world_time" }, - {CustomType::sleep_in_bed, "minecraft:sleep_in_bed" }, - {CustomType::traded_with_villager, "minecraft:traded_with_villager" }, - {CustomType::trigger_trapped_chest, "minecraft:trigger_trapped_chest" }, - {CustomType::use_cauldron, "minecraft:use_cauldron" } +inline const std::array, static_cast(CustomType::count)> CustomTypeMap = + { + {{CustomType::animals_bred, "minecraft:animals_bred"}, + {CustomType::clean_armor, "minecraft:clean_armor"}, + {CustomType::clean_banner, "minecraft:clean_banner"}, + {CustomType::open_barrel, "minecraft:open_barrel"}, + {CustomType::bell_ring, "minecraft:bell_ring"}, + {CustomType::eat_cake_slice, "minecraft:eat_cake_slice"}, + {CustomType::fill_cauldron, "minecraft:fill_cauldron"}, + {CustomType::open_chest, "minecraft:open_chest"}, + {CustomType::damage_absorbed, "minecraft:damage_absorbed"}, + {CustomType::damage_blocked_by_shield, "minecraft:damage_blocked_by_shield"}, + {CustomType::damage_dealt, "minecraft:damage_dealt"}, + {CustomType::damage_dealt_absorbed, "minecraft:damage_dealt_absorbed"}, + {CustomType::damage_dealt_resisted, "minecraft:damage_dealt_resisted"}, + {CustomType::damage_resisted, "minecraft:damage_resisted"}, + {CustomType::damage_taken, "minecraft:damage_taken"}, + {CustomType::inspect_dispenser, "minecraft:inspect_dispenser"}, + {CustomType::nautilus_one_cm, "minecraft:nautilus_one_cm"}, + {CustomType::happy_ghast_one_cm, "minecraft:happy_ghast_one_cm"}, + {CustomType::boat_one_cm, "minecraft:boat_one_cm"}, + {CustomType::aviate_one_cm, "minecraft:aviate_one_cm"}, + {CustomType::horse_one_cm, "minecraft:horse_one_cm"}, + {CustomType::minecart_one_cm, "minecraft:minecart_one_cm"}, + {CustomType::pig_one_cm, "minecraft:pig_one_cm"}, + {CustomType::strider_one_cm, "minecraft:strider_one_cm"}, + {CustomType::climb_one_cm, "minecraft:climb_one_cm"}, + {CustomType::crouch_one_cm, "minecraft:crouch_one_cm"}, + {CustomType::fall_one_cm, "minecraft:fall_one_cm"}, + {CustomType::fly_one_cm, "minecraft:fly_one_cm"}, + {CustomType::sprint_one_cm, "minecraft:sprint_one_cm"}, + {CustomType::swim_one_cm, "minecraft:swim_one_cm"}, + {CustomType::walk_one_cm, "minecraft:walk_one_cm"}, + {CustomType::walk_on_water_one_cm, "minecraft:walk_on_water_one_cm"}, + {CustomType::walk_under_water_one_cm, "minecraft:walk_under_water_one_cm"}, + {CustomType::inspect_dropper, "minecraft:inspect_dropper"}, + {CustomType::open_enderchest, "minecraft:open_enderchest"}, + {CustomType::fish_caught, "minecraft:fish_caught"}, + {CustomType::leave_game, "minecraft:leave_game"}, + {CustomType::inspect_hopper, "minecraft:inspect_hopper"}, + {CustomType::interact_with_anvil, "minecraft:interact_with_anvil"}, + {CustomType::interact_with_beacon, "minecraft:interact_with_beacon"}, + {CustomType::interact_with_blast_furnace, "minecraft:interact_with_blast_furnace"}, + {CustomType::interact_with_brewingstand, "minecraft:interact_with_brewingstand"}, + {CustomType::interact_with_campfire, "minecraft:interact_with_campfire"}, + {CustomType::interact_with_cartography_table, "minecraft:interact_with_cartography_table"}, + {CustomType::interact_with_crafting_table, "minecraft:interact_with_crafting_table"}, + {CustomType::interact_with_furnace, "minecraft:interact_with_furnace"}, + {CustomType::interact_with_grindstone, "minecraft:interact_with_grindstone"}, + {CustomType::interact_with_lectern, "minecraft:interact_with_lectern"}, + {CustomType::interact_with_loom, "minecraft:interact_with_loom"}, + {CustomType::interact_with_smithing_table, "minecraft:interact_with_smithing_table"}, + {CustomType::interact_with_smoker, "minecraft:interact_with_smoker"}, + {CustomType::interact_with_stonecutter, "minecraft:interact_with_stonecutter"}, + {CustomType::drop, "minecraft:drop"}, + {CustomType::enchant_item, "minecraft:enchant_item"}, + {CustomType::jump, "minecraft:jump"}, + {CustomType::mob_kills, "minecraft:mob_kills"}, + {CustomType::play_record, "minecraft:play_record"}, + {CustomType::play_noteblock, "minecraft:play_noteblock"}, + {CustomType::tune_noteblock, "minecraft:tune_noteblock"}, + {CustomType::deaths, "minecraft:deaths"}, + {CustomType::pot_flower, "minecraft:pot_flower"}, + {CustomType::player_kills, "minecraft:player_kills"}, + {CustomType::raid_trigger, "minecraft:raid_trigger"}, + {CustomType::raid_win, "minecraft:raid_win"}, + {CustomType::clean_shulker_box, "minecraft:clean_shulker_box"}, + {CustomType::open_shulker_box, "minecraft:open_shulker_box"}, + {CustomType::time_since_death, "minecraft:time_since_death"}, + {CustomType::time_since_rest, "minecraft:time_since_rest"}, + {CustomType::sneak_time, "minecraft:sneak_time"}, + {CustomType::talked_to_villager, "minecraft:talked_to_villager"}, + {CustomType::target_hit, "minecraft:target_hit"}, + {CustomType::play_time, "minecraft:play_time"}, + {CustomType::total_world_time, "minecraft:total_world_time"}, + {CustomType::sleep_in_bed, "minecraft:sleep_in_bed"}, + {CustomType::traded_with_villager, "minecraft:traded_with_villager"}, + {CustomType::trigger_trapped_chest, "minecraft:trigger_trapped_chest"}, + {CustomType::use_cauldron, "minecraft:use_cauldron"}} }; -static const std::map CustomInteractBlockMap = { + +inline std::string const& getCustomTypeName(CustomType type) { + return CustomTypeMap.at(static_cast(type)).second; +} + +inline const std::unordered_map CustomInteractBlockMap = { // 交互工作方块 营火 工作台无法通过监听方块交互检测到 {"minecraft:anvil", CustomType::interact_with_anvil }, {"minecraft:beacon", CustomType::interact_with_beacon }, @@ -214,4 +224,4 @@ static const std::map CustomInteractBlockMap = { // 音符盒 {"minecraft:noteblock", CustomType::tune_noteblock } }; -} // namespace stats \ No newline at end of file +} // namespace stats diff --git a/src/mod/Stats/StatsData.cpp b/src/mod/Stats/StatsData.cpp index 4825b84..2e8d6d2 100644 --- a/src/mod/Stats/StatsData.cpp +++ b/src/mod/Stats/StatsData.cpp @@ -1,10 +1,144 @@ #include "mod/Stats/StatsData.h" +#include +#include + namespace stats { -StatsDataMap* StatsData::getMap(StatsType type) { +namespace { + +uint64_t saturatingAdd(uint64_t current, uint64_t value) { + return value > std::numeric_limits::max() - current ? std::numeric_limits::max() + : current + value; +} + +void addToMap(StatsDataMap& values, std::string const& key, uint64_t value) { + auto& current = values[key]; + current = saturatingAdd(current, value); +} + +} // namespace + +void CustomStatsData::load(StatsDataMap values) { + mValues.fill(0); + mPresent.reset(); + mUnknown = std::move(values); + + for (std::size_t index = 0; index < CustomTypeMap.size(); ++index) { + auto const value = mUnknown.find(CustomTypeMap[index].second); + if (value == mUnknown.end()) continue; + mValues[index] = value->second; + mPresent.set(index); + mUnknown.erase(value); + } + mSnapshotDirty = true; +} + +void CustomStatsData::add(CustomType type, uint64_t value) { + auto const index = static_cast(type); + mValues.at(index) = saturatingAdd(mValues.at(index), value); + mPresent.set(index); + mSnapshotDirty = true; +} + +void CustomStatsData::add(std::string const& key, uint64_t value) { + for (std::size_t index = 0; index < CustomTypeMap.size(); ++index) { + if (CustomTypeMap[index].second != key) continue; + return add(CustomTypeMap[index].first, value); + } + addToMap(mUnknown, key, value); + mSnapshotDirty = true; +} + +void CustomStatsData::set(CustomType type, uint64_t value) { + auto const index = static_cast(type); + mValues.at(index) = value; + mPresent.set(index); + mSnapshotDirty = true; +} + +StatsDataMap const& CustomStatsData::asMap() const { + if (!mSnapshotDirty) return mSnapshot; + + mSnapshot = mUnknown; + mSnapshot.reserve(mSnapshot.size() + mPresent.count()); + for (std::size_t index = 0; index < CustomTypeMap.size(); ++index) { + if (!mPresent.test(index)) continue; + mSnapshot[CustomTypeMap[index].second] = mValues[index]; + } + mSnapshotDirty = false; + return mSnapshot; +} + +void StatsData::loadMap(StatsType type, StatsDataMap values) { + switch (type) { + case StatsType::custom: + return custom.load(std::move(values)); + case StatsType::mined: + mined = std::move(values); + return; + case StatsType::broken: + broken = std::move(values); + return; + case StatsType::crafted: + crafted = std::move(values); + return; + case StatsType::used: + used = std::move(values); + return; + case StatsType::picked_up: + picked_up = std::move(values); + return; + case StatsType::dropped: + dropped = std::move(values); + return; + case StatsType::killed: + killed = std::move(values); + return; + case StatsType::killed_by: + killed_by = std::move(values); + return; + case StatsType::count: + return; + } +} + +void StatsData::add(StatsType type, std::string const& key, uint64_t value) { + switch (type) { + case StatsType::custom: + return custom.add(key, value); + case StatsType::mined: + addToMap(mined, key, value); + return; + case StatsType::broken: + addToMap(broken, key, value); + return; + case StatsType::crafted: + addToMap(crafted, key, value); + return; + case StatsType::used: + addToMap(used, key, value); + return; + case StatsType::picked_up: + addToMap(picked_up, key, value); + return; + case StatsType::dropped: + addToMap(dropped, key, value); + return; + case StatsType::killed: + addToMap(killed, key, value); + return; + case StatsType::killed_by: + addToMap(killed_by, key, value); + return; + case StatsType::count: + return; + } +} + +StatsDataMap const* StatsData::getMap(StatsType type) const { switch (type) { case StatsType::custom: - return &custom; + return &custom.asMap(); case StatsType::mined: return &mined; case StatsType::broken: @@ -21,7 +155,9 @@ StatsDataMap* StatsData::getMap(StatsType type) { return &killed; case StatsType::killed_by: return &killed_by; + case StatsType::count: + return nullptr; } return nullptr; } -} // namespace stats \ No newline at end of file +} // namespace stats diff --git a/src/mod/Stats/StatsData.h b/src/mod/Stats/StatsData.h index 939b95b..27cb762 100644 --- a/src/mod/Stats/StatsData.h +++ b/src/mod/Stats/StatsData.h @@ -1,35 +1,140 @@ #pragma once +#include "mod/Stats/StatsCustom.h" + +#include +#include +#include #include -#include #include +#include #include -enum class StatsType : int { custom = 1, mined, broken, crafted, used, picked_up, dropped, killed, killed_by }; +enum class StatsType : int { custom = 1, mined, broken, crafted, used, picked_up, dropped, killed, killed_by, count }; namespace stats { using StatsDataMap = std::unordered_map; -static const std::map StatsTypeMap = { - {StatsType::custom, "minecraft:custom" }, - {StatsType::mined, "minecraft:mined" }, - {StatsType::broken, "minecraft:broken" }, - {StatsType::crafted, "minecraft:crafted" }, - {StatsType::used, "minecraft:used" }, - {StatsType::picked_up, "minecraft:picked_up"}, - {StatsType::dropped, "minecraft:dropped" }, - {StatsType::killed, "minecraft:killed" }, - {StatsType::killed_by, "minecraft:killed_by"}, +struct StatsDescriptor { + StatsType type; + std::string_view key; +}; + +inline constexpr std::array(StatsType::count) - 1> StatsSchema{ + { + {StatsType::custom, "minecraft:custom"}, + {StatsType::mined, "minecraft:mined"}, + {StatsType::broken, "minecraft:broken"}, + {StatsType::crafted, "minecraft:crafted"}, + {StatsType::used, "minecraft:used"}, + {StatsType::picked_up, "minecraft:picked_up"}, + {StatsType::dropped, "minecraft:dropped"}, + {StatsType::killed, "minecraft:killed"}, + {StatsType::killed_by, "minecraft:killed_by"}, + } +}; + +constexpr bool isValidStatsType(StatsType type) { + auto const value = static_cast(type); + return value >= static_cast(StatsType::custom) && value < static_cast(StatsType::count); +} + +constexpr std::size_t getStatsTypeIndex(StatsType type) { + return static_cast(type) - static_cast(StatsType::custom); +} + +constexpr std::string_view getStatsTypeKey(StatsType type) { + return isValidStatsType(type) ? StatsSchema[getStatsTypeIndex(type)].key : std::string_view{}; +} + +inline std::string getStatsCategoryTranslationKey(StatsType type) { + constexpr std::string_view namespacePrefix = "minecraft:"; + auto const storageKey = getStatsTypeKey(type); + if (!storageKey.starts_with(namespacePrefix)) return {}; + return "stats.category." + std::string(storageKey.substr(namespacePrefix.size())); +} + +constexpr std::string_view getStatsEntryTranslationPrefix(StatsType type) { + switch (type) { + case StatsType::mined: + return "block."; + case StatsType::broken: + return "item."; + case StatsType::killed: + case StatsType::killed_by: + return "entity."; + case StatsType::custom: + return "stats.custom."; + case StatsType::crafted: + case StatsType::used: + case StatsType::picked_up: + case StatsType::dropped: + case StatsType::count: + return {}; + } + return {}; +} + +constexpr bool isBlockOrItemStatsCategory(StatsType type) { + switch (type) { + case StatsType::crafted: + case StatsType::used: + case StatsType::picked_up: + case StatsType::dropped: + return true; + default: + return false; + } +} + +inline std::string getStatsEntryTranslationKey(StatsType type, std::string_view typeName) { + if (isBlockOrItemStatsCategory(type)) return {}; + return std::string(getStatsEntryTranslationPrefix(type)) + std::string(typeName); +} + +constexpr bool hasTranslation(std::string_view translation, std::string_view key) { + return !translation.empty() && translation != key; +} + +constexpr bool isStatsSchemaValid() { + for (std::size_t index = 0; index < StatsSchema.size(); ++index) { + auto const& descriptor = StatsSchema[index]; + if (getStatsTypeIndex(descriptor.type) != index || descriptor.key.empty()) return false; + for (std::size_t previous = 0; previous < index; ++previous) { + if (StatsSchema[previous].key == descriptor.key) return false; + } + } + return true; +} + +static_assert(isStatsSchemaValid()); + +class CustomStatsData { +public: + void load(StatsDataMap values); + void add(CustomType type, uint64_t value = 1); + void add(std::string const& key, uint64_t value = 1); + void set(CustomType type, uint64_t value = 0); + StatsDataMap const& asMap() const; + +private: + std::array(CustomType::count)> mValues{}; + std::bitset(CustomType::count)> mPresent; + StatsDataMap mUnknown; + mutable StatsDataMap mSnapshot; + mutable bool mSnapshotDirty = true; }; struct StatsData { - StatsDataMap custom; - StatsDataMap mined; - StatsDataMap broken; - StatsDataMap crafted; - StatsDataMap used; - StatsDataMap picked_up; - StatsDataMap dropped; - StatsDataMap killed; - StatsDataMap killed_by; - StatsDataMap* getMap(StatsType type); + CustomStatsData custom; + StatsDataMap mined; + StatsDataMap broken; + StatsDataMap crafted; + StatsDataMap used; + StatsDataMap picked_up; + StatsDataMap dropped; + StatsDataMap killed; + StatsDataMap killed_by; + void loadMap(StatsType type, StatsDataMap values); + void add(StatsType type, std::string const& key, uint64_t value = 1); + StatsDataMap const* getMap(StatsType type) const; }; -} // namespace stats \ No newline at end of file +} // namespace stats diff --git a/src/mod/Stats/StatsFileRepository.cpp b/src/mod/Stats/StatsFileRepository.cpp new file mode 100644 index 0000000..17cfcdb --- /dev/null +++ b/src/mod/Stats/StatsFileRepository.cpp @@ -0,0 +1,223 @@ +#include "mod/Stats/StatsFileRepository.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "mod/Stats/Stats.h" +#include "mod/Stats/StatsWriteQueue.h" + +using namespace ll::i18n_literals; + +namespace stats::repository { +namespace { + +std::filesystem::path statsPath; +std::unique_ptr writeQueue; + +std::optional getLevelName() { + if (auto const settings = ll::service::getPropertiesSettings()) { + return settings->mLevelName; + } + + std::ifstream file("server.properties"); + std::string line; + while (std::getline(file, line)) { + constexpr std::string_view prefix = "level-name="; + if (!line.starts_with(prefix)) continue; + auto name = line.substr(prefix.size()); + if (!name.empty() && name.back() == '\r') name.pop_back(); + return name; + } + return std::nullopt; +} + +std::filesystem::path resolveStatsPath() { + auto const levelName = getLevelName().value_or(""); + return ll::file_utils::u8path("./worlds/" + levelName + "/stats"); +} + +bool isValidSnapshot(std::filesystem::path const& path) { + auto const source = ll::file_utils::readFile(path); + if (!source) return false; + + try { + (void)decodeStatsJson(*source); + return true; + } catch (...) { + return false; + } +} + +void recoverPendingWrites() { + for (auto const& entry : std::filesystem::directory_iterator(statsPath)) { + auto const& tempPath = entry.path(); + if (tempPath.extension() != ".tmp" || tempPath.stem().extension() != ".json") continue; + + if (!isValidSnapshot(tempPath)) { + getLogger().warn("Ignoring invalid temporary stats snapshot: {}", tempPath.string()); + continue; + } + + auto targetPath = tempPath; + targetPath.replace_extension(); + + bool recover = !std::filesystem::exists(targetPath) || !isValidSnapshot(targetPath); + if (!recover) { + std::error_code timeError; + auto const tempTime = std::filesystem::last_write_time(tempPath, timeError); + if (!timeError) { + auto const targetTime = std::filesystem::last_write_time(targetPath, timeError); + if (!timeError) recover = tempTime > targetTime; + } + if (timeError) { + getLogger().warn("Could not compare pending stats snapshot times: {}", tempPath.string()); + continue; + } + } + + if (!recover) { + std::error_code removeError; + std::filesystem::remove(tempPath, removeError); + if (removeError) { + getLogger().warn("Failed to remove stale stats snapshot: {}", tempPath.string()); + } + continue; + } + + if (MoveFileExW( + tempPath.c_str(), + targetPath.c_str(), + MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH + )) { + getLogger().info("Recovered pending stats snapshot: {}", targetPath.string()); + } else { + getLogger().error( + "Failed to recover pending stats snapshot: {} ({})", + targetPath.string(), + GetLastError() + ); + } + } +} + +bool writeNow(DecodedStats const& record) { + try { + auto const path = statsPath / ll::file_utils::u8path(record.info.uuid + ".json"); + auto tempPath = path; + tempPath += ".tmp"; + if (!ll::file_utils::writeFile(tempPath, encodeStatsJson(record.info, record.data))) { + getLogger().error("Failed to write stats snapshot: {}", path.string()); + return false; + } + if (MoveFileExW( + tempPath.c_str(), + path.c_str(), + MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH + )) { + return true; + } + auto const replaceError = GetLastError(); + getLogger().error("Failed to replace stats snapshot: {} ({})", path.string(), replaceError); + return false; + } catch (std::exception const& exception) { + getLogger().error(exception.what()); + return false; + } +} + +void ensureWriteQueue() { + if (!writeQueue) writeQueue = std::make_unique(writeNow); +} + +} // namespace + +bool initialize() { + if (!statsPath.empty()) { + ensureWriteQueue(); + return true; + } + + auto const oldPath = ll::file_utils::u8path("./stats"); + auto const newPath = resolveStatsPath(); + if (!std::filesystem::exists(newPath)) { + if (std::filesystem::exists(oldPath)) { + getLogger().warn("log.warning.legacy_data_migration_started"_tr()); + try { + std::filesystem::rename(oldPath, newPath); + } catch (std::exception const& exception) { + getLogger().error(exception.what()); + getLogger().warn("log.warning.legacy_data_migration_failed"_tr()); + return false; + } + } else { + getLogger().warn("log.warning.data_directory_initializing"_tr()); + try { + std::filesystem::create_directory(newPath); + } catch (std::exception const& exception) { + getLogger().error(exception.what()); + getLogger().warn("log.warning.data_directory_creation_failed"_tr()); + return false; + } + } + } + + statsPath = newPath; + ensureWriteQueue(); + return true; +} + +bool loadAll(std::vector& records) { + records.clear(); + std::vector files; + try { + recoverPendingWrites(); + for (auto const& entry : std::filesystem::directory_iterator(statsPath)) { + if (entry.path().extension() == ".json") files.push_back(entry.path()); + } + } catch (std::exception const& exception) { + getLogger().error(exception.what()); + return false; + } + + records.reserve(files.size()); + for (auto const& path : files) { + auto const source = ll::file_utils::readFile(path); + if (!source) { + getLogger().warn("log.warning.data_parse_failed"_tr(path.filename())); + continue; + } + try { + records.push_back(decodeStatsJson(*source)); + } catch (std::exception const& exception) { + getLogger().error(exception.what()); + getLogger().warn("log.warning.data_parse_failed"_tr(path.filename())); + } + } + return true; +} + +bool save(PlayerInfo const& info, StatsData const& data) { + if (!writeQueue) return false; + return writeQueue->enqueue(DecodedStats{info, data}); +} + +void flush() { + if (writeQueue) writeQueue->flush(); +} + +void shutdown() { writeQueue.reset(); } + +} // namespace stats::repository diff --git a/src/mod/Stats/StatsFileRepository.h b/src/mod/Stats/StatsFileRepository.h new file mode 100644 index 0000000..b7df129 --- /dev/null +++ b/src/mod/Stats/StatsFileRepository.h @@ -0,0 +1,15 @@ +#pragma once + +#include "mod/Stats/StatsJsonCodec.h" + +#include + +namespace stats::repository { + +bool initialize(); +bool loadAll(std::vector& records); +bool save(PlayerInfo const& info, StatsData const& data); +void flush(); +void shutdown(); + +} // namespace stats::repository diff --git a/src/mod/Stats/StatsJsonCodec.cpp b/src/mod/Stats/StatsJsonCodec.cpp new file mode 100644 index 0000000..0042b4a --- /dev/null +++ b/src/mod/Stats/StatsJsonCodec.cpp @@ -0,0 +1,73 @@ +#include "mod/Stats/StatsJsonCodec.h" + +#include +#include +#include + +#include + +namespace stats { +namespace { + +StatsDataMap decodeStatsDataMap(nlohmann::json const& document, std::string const& category) { + auto const& values = document.at(category); + if (!values.is_object()) throw std::invalid_argument("Statistics category must be an object: " + category); + + StatsDataMap decoded; + decoded.reserve(values.size()); + for (auto const& [key, value] : values.items()) { + if (value.is_number_unsigned()) { + decoded.emplace(key, value.get()); + continue; + } + if (!value.is_number_integer()) throw std::invalid_argument("Statistic value must be an integer: " + key); + + auto const signedValue = value.get(); + decoded.emplace(key, signedValue < 0 ? 0 : static_cast(signedValue)); + } + return decoded; +} + +} // namespace + +std::string encodeStatsJson(PlayerInfo const& info, StatsData const& data) { + nlohmann::json json = { + {"playerInfo", {{"uuid", info.uuid}, {"xuid", info.xuid}, {"name", info.name}}} + }; + for (auto const& descriptor : StatsSchema) { + auto const* values = data.getMap(descriptor.type); + if (values) json[std::string(descriptor.key)] = *values; + } + return json.dump(); +} + +DecodedStats decodeStatsJson(std::string_view source) { + auto const json = nlohmann::json::parse(source); + auto const& playerInfo = json.at("playerInfo"); + + DecodedStats decoded; + playerInfo.at("uuid").get_to(decoded.info.uuid); + playerInfo.at("xuid").get_to(decoded.info.xuid); + playerInfo.at("name").get_to(decoded.info.name); + for (auto const& descriptor : StatsSchema) { + auto const key = std::string(descriptor.key); + decoded.data.loadMap(descriptor.type, decodeStatsDataMap(json, key)); + } + return decoded; +} + +std::string encodeStatsPageJson(query::StatsPage const& page, std::string_view labelField) { + nlohmann::json json = { + {"page", page.totalPages == 0 ? 0 : page.pageIndex + 1}, + {"pageSize", page.pageSize }, + {"total", page.totalEntries }, + {"totalPages", page.totalPages }, + {"items", nlohmann::json::array() }, + }; + for (auto const& [label, value] : page.entries) { + json["items"].push_back({{std::string{labelField}, label}, {"value", value}}); + } + return json.dump(); +} + +} // namespace stats diff --git a/src/mod/Stats/StatsJsonCodec.h b/src/mod/Stats/StatsJsonCodec.h new file mode 100644 index 0000000..144729e --- /dev/null +++ b/src/mod/Stats/StatsJsonCodec.h @@ -0,0 +1,21 @@ +#pragma once + +#include "mod/Stats/StatsData.h" +#include "mod/Stats/StatsQuery.h" +#include "mod/Stats/StatsRecord.h" + +#include +#include + +namespace stats { + +struct DecodedStats { + PlayerInfo info; + StatsData data; +}; + +std::string encodeStatsJson(PlayerInfo const& info, StatsData const& data); +DecodedStats decodeStatsJson(std::string_view json); +std::string encodeStatsPageJson(query::StatsPage const& page, std::string_view labelField); + +} // namespace stats diff --git a/src/mod/Stats/StatsQuery.cpp b/src/mod/Stats/StatsQuery.cpp new file mode 100644 index 0000000..4f4a451 --- /dev/null +++ b/src/mod/Stats/StatsQuery.cpp @@ -0,0 +1,128 @@ +#include "mod/Stats/StatsQuery.h" + +#include +#include +#include + +namespace stats::query { +namespace { + +bool valueOrder(StatsEntries::value_type const& left, StatsEntries::value_type const& right) { + return left.second != right.second ? left.second > right.second : left.first < right.first; +} + +bool nameOrder(StatsEntries::value_type const& left, StatsEntries::value_type const& right) { + return left.first < right.first; +} + +uint64_t saturatingAdd(uint64_t current, uint64_t value) { + return value > std::numeric_limits::max() - current ? std::numeric_limits::max() + : current + value; +} + +} // namespace + +RankBuilder::RankBuilder(std::size_t capacity, std::string key) : mKey(std::move(key)) { + mResult.reserve(capacity); +} + +void RankBuilder::add(std::string_view playerName, StatsDataMap const* stats) { + if (!stats) return; + mResult.emplace_back(playerName, getValue(*stats, mKey)); +} + +RankData RankBuilder::finish() && { + std::sort(mResult.begin(), mResult.end(), valueOrder); + return std::move(mResult); +} + +StatsPage RankBuilder::finishPage(std::size_t pageIndex, std::size_t pageSize) && { + return sortAndPaginate(std::move(mResult), pageIndex, pageSize); +} + +uint64_t getValue(StatsDataMap const& stats, std::string const& key) { + if (key.empty()) { + return std::accumulate(stats.begin(), stats.end(), uint64_t{0}, [](uint64_t total, auto const& pair) { + return saturatingAdd(total, pair.second); + }); + } + + auto const value = stats.find(key); + return value == stats.end() ? 0 : value->second; +} + +StatsEntries buildDisplayEntries(StatsDataMap const& stats, StatsType type, uint64_t playTimeDelta) { + constexpr std::string_view playTimeKey = "minecraft:play_time"; + + StatsEntries result; + result.reserve(stats.size() + (type == StatsType::custom ? 2 : 0)); + + bool hasPlayTime = false; + for (auto const& [key, value] : stats) { + if (type == StatsType::custom && key == playTimeKey) { + result.emplace_back(key, saturatingAdd(value, playTimeDelta)); + hasPlayTime = true; + } else { + result.emplace_back(key, value); + } + } + if (type == StatsType::custom && !hasPlayTime) { + result.emplace_back(playTimeKey, playTimeDelta); + } + return result; +} + +RankData buildRank(std::span entries, std::string const& key) { + RankBuilder builder(entries.size(), key); + for (auto const& entry : entries) { + builder.add(entry.playerName, entry.stats); + } + return std::move(builder).finish(); +} + +void sortStatsEntries(StatsEntries& entries, bool alphabetical) { + if (alphabetical) { + std::sort(entries.begin(), entries.end(), nameOrder); + return; + } + std::sort(entries.begin(), entries.end(), valueOrder); +} + +StatsPage paginate(StatsEntries entries, std::size_t pageIndex, std::size_t pageSize) { + pageSize = std::clamp(pageSize, std::size_t{1}, MaxPageSize); + auto const total = entries.size(); + auto const pageCount = total == 0 ? std::size_t{0} : (total + pageSize - 1) / pageSize; + if (pageCount == 0) { + return {{}, 0, pageSize, 0, 0}; + } + + pageIndex = std::min(pageIndex, pageCount - 1); + auto const begin = pageIndex * pageSize; + auto const end = std::min(begin + pageSize, total); + + StatsEntries pageEntries; + pageEntries.reserve(end - begin); + for (auto index = begin; index < end; ++index) { + pageEntries.push_back(std::move(entries[index])); + } + return {std::move(pageEntries), pageIndex, pageSize, total, pageCount}; +} + +StatsPage sortAndPaginate(StatsEntries entries, std::size_t pageIndex, std::size_t pageSize, bool alphabetical) { + pageSize = std::clamp(pageSize, std::size_t{1}, MaxPageSize); + auto const total = entries.size(); + auto const pageCount = total == 0 ? std::size_t{0} : (total + pageSize - 1) / pageSize; + if (pageCount == 0) return {{}, 0, pageSize, 0, 0}; + + pageIndex = std::min(pageIndex, pageCount - 1); + auto const end = std::min((pageIndex + 1) * pageSize, total); + std::partial_sort( + entries.begin(), + entries.begin() + static_cast(end), + entries.end(), + alphabetical ? nameOrder : valueOrder + ); + return paginate(std::move(entries), pageIndex, pageSize); +} + +} // namespace stats::query diff --git a/src/mod/Stats/StatsQuery.h b/src/mod/Stats/StatsQuery.h new file mode 100644 index 0000000..1265233 --- /dev/null +++ b/src/mod/Stats/StatsQuery.h @@ -0,0 +1,58 @@ +#pragma once + +#include "mod/Stats/StatsData.h" + +#include +#include +#include +#include +#include +#include + +namespace stats::query { + +using StatsEntries = std::vector>; +using RankData = StatsEntries; + +inline constexpr std::size_t DefaultPageSize = 20; +inline constexpr std::size_t MaxPageSize = 100; + +struct StatsPage { + StatsEntries entries; + std::size_t pageIndex; + std::size_t pageSize; + std::size_t totalEntries; + std::size_t totalPages; +}; + +struct RankEntryView { + std::string_view playerName; + StatsDataMap const* stats; +}; + +class RankBuilder { +public: + RankBuilder(std::size_t capacity, std::string key); + + void add(std::string_view playerName, StatsDataMap const* stats); + RankData finish() &&; + StatsPage finishPage(std::size_t pageIndex, std::size_t pageSize = DefaultPageSize) &&; + +private: + RankData mResult; + std::string mKey; +}; + +uint64_t getValue(StatsDataMap const& stats, std::string const& key); +StatsEntries buildDisplayEntries(StatsDataMap const& stats, StatsType type, uint64_t playTimeDelta); +RankData buildRank(std::span entries, std::string const& key); +void sortStatsEntries(StatsEntries& entries, bool alphabetical); +StatsPage paginate(StatsEntries entries, std::size_t pageIndex, std::size_t pageSize = DefaultPageSize); +StatsPage sortAndPaginate( + StatsEntries entries, + std::size_t pageIndex, + std::size_t pageSize = DefaultPageSize, + bool alphabetical = false +); + +} // namespace stats::query diff --git a/src/mod/Stats/StatsRecord.h b/src/mod/Stats/StatsRecord.h new file mode 100644 index 0000000..27d3bd8 --- /dev/null +++ b/src/mod/Stats/StatsRecord.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +namespace stats { + +struct PlayerInfo { + std::string uuid; + std::string xuid; + std::string name; +}; + +} // namespace stats diff --git a/src/mod/Stats/StatsWriteQueue.cpp b/src/mod/Stats/StatsWriteQueue.cpp new file mode 100644 index 0000000..5edb28d --- /dev/null +++ b/src/mod/Stats/StatsWriteQueue.cpp @@ -0,0 +1,87 @@ +#include "mod/Stats/StatsWriteQueue.h" + +#include +#include +#include +#include +#include +#include + +namespace stats { + +struct StatsWriteQueue::Impl { + explicit Impl(Writer callback) : writer(std::move(callback)), worker([this] { run(); }) {} + + void run() { + while (true) { + DecodedStats record; + { + std::unique_lock lock(mutex); + available.wait(lock, [this] { return stopping || !pending.empty(); }); + if (pending.empty()) break; + + auto entry = pending.extract(pending.begin()); + record = std::move(entry.mapped()); + writing = true; + } + + for (int attempt = 0; attempt < 3; ++attempt) { + try { + if (writer(record)) break; + } catch (...) { + // Keep the worker alive so shutdown and later snapshots can still complete. + } + if (attempt < 2) std::this_thread::sleep_for(std::chrono::milliseconds{25}); + } + + { + std::lock_guard lock(mutex); + writing = false; + if (pending.empty()) drained.notify_all(); + } + } + + std::lock_guard lock(mutex); + drained.notify_all(); + } + + Writer writer; + std::mutex mutex; + std::condition_variable available; + std::condition_variable drained; + std::unordered_map pending; + bool writing = false; + bool stopping = false; + std::thread worker; +}; + +StatsWriteQueue::StatsWriteQueue(Writer writer) : mImpl(std::make_unique(std::move(writer))) {} + +StatsWriteQueue::~StatsWriteQueue() { stop(); } + +bool StatsWriteQueue::enqueue(DecodedStats record) { + std::lock_guard lock(mImpl->mutex); + if (mImpl->stopping) return false; + + auto uuid = record.info.uuid; + mImpl->pending.insert_or_assign(std::move(uuid), std::move(record)); + mImpl->available.notify_one(); + return true; +} + +void StatsWriteQueue::flush() { + std::unique_lock lock(mImpl->mutex); + mImpl->drained.wait(lock, [this] { return mImpl->pending.empty() && !mImpl->writing; }); +} + +void StatsWriteQueue::stop() { + if (!mImpl || !mImpl->worker.joinable()) return; + { + std::lock_guard lock(mImpl->mutex); + mImpl->stopping = true; + } + mImpl->available.notify_one(); + mImpl->worker.join(); +} + +} // namespace stats diff --git a/src/mod/Stats/StatsWriteQueue.h b/src/mod/Stats/StatsWriteQueue.h new file mode 100644 index 0000000..96dbe09 --- /dev/null +++ b/src/mod/Stats/StatsWriteQueue.h @@ -0,0 +1,29 @@ +#pragma once + +#include "mod/Stats/StatsJsonCodec.h" + +#include +#include + +namespace stats { + +class StatsWriteQueue { +public: + using Writer = std::function; + + explicit StatsWriteQueue(Writer writer); + ~StatsWriteQueue(); + + StatsWriteQueue(StatsWriteQueue const&) = delete; + StatsWriteQueue& operator=(StatsWriteQueue const&) = delete; + + bool enqueue(DecodedStats record); + void flush(); + void stop(); + +private: + struct Impl; + std::unique_ptr mImpl; +}; + +} // namespace stats diff --git a/src/mod/Version.h.in b/src/mod/Version.h.in new file mode 100644 index 0000000..aed7fd4 --- /dev/null +++ b/src/mod/Version.h.in @@ -0,0 +1,6 @@ +#pragma once + +#define LK_STATS_VERSION_MAJOR ${LK_STATS_VERSION_MAJOR} +#define LK_STATS_VERSION_MINOR ${LK_STATS_VERSION_MINOR} +#define LK_STATS_VERSION_PATCH ${LK_STATS_VERSION_PATCH} +#define LK_STATS_VERSION_STRING "${LK_STATS_VERSION_STRING}" diff --git a/tests/StatsCustomTests.cpp b/tests/StatsCustomTests.cpp new file mode 100644 index 0000000..6dc820b --- /dev/null +++ b/tests/StatsCustomTests.cpp @@ -0,0 +1,36 @@ +#include "mod/Stats/StatsCustom.h" + +#include +#include +#include +#include + +int runStatsCustomTests() { + static_assert(stats::CustomTypeMap.size() == static_cast(stats::CustomType::count)); + + int failures = 0; + std::unordered_set names; + names.reserve(stats::CustomTypeMap.size()); + + for (std::size_t index = 0; index < stats::CustomTypeMap.size(); ++index) { + auto const& [type, name] = stats::CustomTypeMap[index]; + if (static_cast(type) != index) { + std::cerr << "FAILED: CustomType index mismatch at " << index << '\n'; + ++failures; + } + if (name.empty()) { + std::cerr << "FAILED: empty CustomType name at " << index << '\n'; + ++failures; + } + if (!names.emplace(name).second) { + std::cerr << "FAILED: duplicate CustomType name " << name << '\n'; + ++failures; + } + if (stats::getCustomTypeName(type) != name) { + std::cerr << "FAILED: CustomType lookup mismatch at " << index << '\n'; + ++failures; + } + } + + return failures; +} diff --git a/tests/StatsDataTests.cpp b/tests/StatsDataTests.cpp new file mode 100644 index 0000000..13c6299 --- /dev/null +++ b/tests/StatsDataTests.cpp @@ -0,0 +1,128 @@ +#include "mod/Stats/StatsData.h" + +#include +#include +#include +#include +#include +#include + +int runStatsDataTests() { + static_assert(stats::StatsSchema.size() == static_cast(StatsType::count) - 1); + static_assert(stats::isValidStatsType(StatsType::custom)); + static_assert(stats::isValidStatsType(StatsType::killed_by)); + static_assert(!stats::isValidStatsType(static_cast(0))); + static_assert(!stats::isValidStatsType(StatsType::count)); + static_assert(stats::isStatsSchemaValid()); + static_assert(!stats::isBlockOrItemStatsCategory(StatsType::mined)); + static_assert(!stats::isBlockOrItemStatsCategory(StatsType::broken)); + static_assert(stats::isBlockOrItemStatsCategory(StatsType::used)); + static_assert(!stats::isBlockOrItemStatsCategory(StatsType::killed)); + static_assert(stats::hasTranslation("石头", "block.minecraft:stone")); + static_assert(!stats::hasTranslation("", "block.minecraft:stone")); + static_assert(!stats::hasTranslation("item.minecraft:stone", "item.minecraft:stone")); + + int failures = 0; + + std::unordered_set schemaKeys; + schemaKeys.reserve(stats::StatsSchema.size()); + for (std::size_t index = 0; index < stats::StatsSchema.size(); ++index) { + auto const& descriptor = stats::StatsSchema[index]; + if (static_cast(descriptor.type) != index + 1) { + std::cerr << "FAILED: StatsType index mismatch at " << index << '\n'; + ++failures; + } + if (descriptor.key.empty()) { + std::cerr << "FAILED: empty StatsType key at " << index << '\n'; + ++failures; + } + if (!schemaKeys.emplace(descriptor.key).second) { + std::cerr << "FAILED: duplicate StatsType key " << descriptor.key << '\n'; + ++failures; + } + if (stats::getStatsTypeIndex(descriptor.type) != index + || stats::getStatsTypeKey(descriptor.type) != descriptor.key) { + std::cerr << "FAILED: StatsType lookup mismatch at " << index << '\n'; + ++failures; + } + } + + if (stats::getStatsEntryTranslationKey(StatsType::mined, "minecraft:stone") + != "block.minecraft:stone") { + std::cerr << "FAILED: mined stats use block translation keys\n"; + ++failures; + } + if (!stats::getStatsEntryTranslationKey(StatsType::used, "minecraft:arrow").empty()) { + std::cerr << "FAILED: mixed block/item stats must not select one source key\n"; + ++failures; + } + if (stats::getStatsEntryTranslationKey(StatsType::killed_by, "minecraft:arrow") + != "entity.minecraft:arrow") { + std::cerr << "FAILED: combat stats use entity translation keys\n"; + ++failures; + } + if (stats::getStatsCategoryTranslationKey(StatsType::custom) != "stats.category.custom") { + std::cerr << "FAILED: stats categories use category translation keys\n"; + ++failures; + } + if (stats::getStatsEntryTranslationKey(StatsType::custom, "minecraft:jump") + != "stats.custom.minecraft:jump") { + std::cerr << "FAILED: custom stats use their own translation keys\n"; + ++failures; + } + + stats::StatsDataMap source{ + {"minecraft:jump", 9 }, + {"minecraft:walk_one_cm", 120}, + {"third_party:custom_value", 17 }, + {"minecraft:deaths", 0 }, + }; + + stats::StatsData data; + data.custom.load(source); + if (data.custom.asMap() != source) { + std::cerr << "FAILED: custom stats preserve loaded JSON values\n"; + ++failures; + } + + data.custom.add(stats::CustomType::jump, 3); + if (data.custom.asMap().at("minecraft:jump") != 12) { + std::cerr << "FAILED: known custom stat uses array-backed update\n"; + ++failures; + } + + data.add(StatsType::custom, "third_party:custom_value", 5); + if (data.custom.asMap().at("third_party:custom_value") != 22) { + std::cerr << "FAILED: unknown custom stat survives generic update\n"; + ++failures; + } + + data.custom.set(stats::CustomType::deaths, 4); + auto const* customMap = data.getMap(StatsType::custom); + if (!customMap || customMap->at("minecraft:deaths") != 4) { + std::cerr << "FAILED: custom stat snapshot reflects reset values\n"; + ++failures; + } + + data.add(StatsType::mined, "minecraft:stone", 2); + auto const* minedMap = data.getMap(StatsType::mined); + if (!minedMap || minedMap->at("minecraft:stone") != 2) { + std::cerr << "FAILED: non-custom stats still use map storage\n"; + ++failures; + } + + data.custom.set(stats::CustomType::jump, std::numeric_limits::max() - 1); + data.custom.add(stats::CustomType::jump, 2); + if (data.custom.asMap().at("minecraft:jump") != std::numeric_limits::max()) { + std::cerr << "FAILED: custom stats saturate instead of wrapping\n"; + ++failures; + } + + data.add(StatsType::mined, "minecraft:stone", std::numeric_limits::max()); + if (data.getMap(StatsType::mined)->at("minecraft:stone") != std::numeric_limits::max()) { + std::cerr << "FAILED: category stats saturate instead of wrapping\n"; + ++failures; + } + + return failures; +} diff --git a/tests/StatsJsonCodecTests.cpp b/tests/StatsJsonCodecTests.cpp new file mode 100644 index 0000000..9e99403 --- /dev/null +++ b/tests/StatsJsonCodecTests.cpp @@ -0,0 +1,106 @@ +#include "mod/Stats/StatsJsonCodec.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace { + +int failures = 0; + +template +void expectEqual(Actual const& actual, Expected const& expected, char const* testName) { + if (actual == expected) return; + std::cerr << "FAILED: " << testName << '\n'; + ++failures; +} + +void testRoundTrip() { + stats::PlayerInfo info{"uuid-value", "xuid-value", "PlayerName"}; + stats::StatsData data; + for (std::size_t index = 0; index < stats::StatsSchema.size(); ++index) { + auto const& descriptor = stats::StatsSchema[index]; + auto values = stats::StatsDataMap{ + {std::string(descriptor.key) + ":test", index + 1} + }; + if (descriptor.type == StatsType::custom) { + values["minecraft:jump"] = 7; + values["third_party:custom_value"] = 9; + } else if (descriptor.type == StatsType::mined) { + values["minecraft:stone"] = std::numeric_limits::max(); + } + data.loadMap(descriptor.type, std::move(values)); + } + + auto const decoded = stats::decodeStatsJson(stats::encodeStatsJson(info, data)); + expectEqual(decoded.info.uuid, info.uuid, "preserves player UUID"); + expectEqual(decoded.info.xuid, info.xuid, "preserves player XUID"); + expectEqual(decoded.info.name, info.name, "preserves player name"); + for (auto const& descriptor : stats::StatsSchema) { + auto const* expected = data.getMap(descriptor.type); + auto const* actual = decoded.data.getMap(descriptor.type); + if (!expected || !actual) { + std::cerr << "FAILED: schema type has no data map " << descriptor.key << '\n'; + ++failures; + continue; + } + expectEqual(*actual, *expected, "preserves schema-driven stats"); + } +} + +void testRequiredFields() { + bool threw = false; + try { + static_cast(stats::decodeStatsJson("{}")); + } catch (std::exception const&) { + threw = true; + } + expectEqual(threw, true, "rejects missing required fields"); +} + +void testSanitizesNegativeCounterValues() { + stats::PlayerInfo info{"uuid-value", "xuid-value", "PlayerName"}; + stats::StatsData data; + auto json = nlohmann::json::parse(stats::encodeStatsJson(info, data)); + json["minecraft:custom"]["minecraft:jump"] = -1; + + auto const decoded = stats::decodeStatsJson(json.dump()); + expectEqual( + decoded.data.getMap(StatsType::custom)->at("minecraft:jump"), + uint64_t{0}, + "sanitizes negative counter values" + ); +} + +void testPageEncoding() { + stats::query::StatsEntries entries{ + {"Alice", 9}, + {"Bob", 7}, + {"Carol", 5}, + }; + auto const page = stats::query::paginate(std::move(entries), 1, 2); + auto const json = nlohmann::json::parse(stats::encodeStatsPageJson(page, "player")); + expectEqual(json.at("page").get(), 2, "encodes one-based page numbers"); + expectEqual(json.at("pageSize").get(), 2, "encodes page size"); + expectEqual(json.at("total").get(), 3, "encodes total entry count"); + expectEqual(json.at("totalPages").get(), 2, "encodes total page count"); + expectEqual(json.at("items").size(), std::size_t{1}, "encodes only current page items"); + expectEqual(json.at("items")[0].at("player").get(), std::string{"Carol"}, "encodes labels"); + expectEqual(json.at("items")[0].at("value").get(), uint64_t{5}, "encodes values"); +} + +} // namespace + +int runStatsJsonCodecTests() { + testRoundTrip(); + testRequiredFields(); + testSanitizesNegativeCounterValues(); + testPageEncoding(); + return failures; +} diff --git a/tests/StatsQueryTests.cpp b/tests/StatsQueryTests.cpp new file mode 100644 index 0000000..e96c50b --- /dev/null +++ b/tests/StatsQueryTests.cpp @@ -0,0 +1,174 @@ +#include "mod/Stats/StatsQuery.h" + +#include +#include +#include +#include +#include + +int runStatsCustomTests(); +int runStatsDataTests(); +int runStatsJsonCodecTests(); +int runStatsWriteQueueTests(); + +namespace { + +int failures = 0; + +template +void expectEqual(Actual const& actual, Expected const& expected, char const* testName) { + if (actual == expected) return; + std::cerr << "FAILED: " << testName << '\n'; + ++failures; +} + +void testValueLookup() { + stats::StatsDataMap values{ + {"stone", 7}, + {"dirt", 3} + }; + expectEqual(stats::query::getValue(values, "stone"), uint64_t{7}, "finds an existing key"); + expectEqual(stats::query::getValue(values, "missing"), uint64_t{0}, "returns zero for a missing key"); +} + +void testTotalsUse64Bits() { + stats::StatsDataMap values{ + {"large", static_cast(std::numeric_limits::max())}, + {"extra", 42 }, + }; + expectEqual( + stats::query::getValue(values, ""), + static_cast(std::numeric_limits::max()) + 42, + "sums values without 32-bit overflow" + ); +} + +void testTotalsSaturate() { + stats::StatsDataMap values{ + {"large", std::numeric_limits::max()}, + {"extra", 1 }, + }; + expectEqual( + stats::query::getValue(values, ""), + std::numeric_limits::max(), + "saturates total instead of wrapping" + ); +} + +uint64_t findEntryValue(stats::query::StatsEntries const& entries, std::string const& key) { + for (auto const& entry : entries) { + if (entry.first == key) return entry.second; + } + return 0; +} + +void testDisplayEntriesAddActivePlayTime() { + stats::StatsDataMap values{ + {"minecraft:play_time", 100}, + {"minecraft:jump", 7 }, + }; + + auto const entries = stats::query::buildDisplayEntries(values, StatsType::custom, 25); + expectEqual(entries.size(), std::size_t{2}, "keeps existing custom entries"); + expectEqual(findEntryValue(entries, "minecraft:play_time"), uint64_t{125}, "adds active play time"); + expectEqual(values.at("minecraft:play_time"), uint64_t{100}, "does not mutate stored play time"); +} + +void testDisplayEntriesInsertMissingPlayTime() { + stats::StatsDataMap values{ + {"minecraft:jump", 7} + }; + + auto const entries = stats::query::buildDisplayEntries(values, StatsType::custom, 25); + expectEqual(entries.size(), std::size_t{2}, "adds a missing play-time entry"); + expectEqual(findEntryValue(entries, "minecraft:play_time"), uint64_t{25}, "uses active play time for new entry"); +} + +void testRanking() { + stats::StatsDataMap alice{ + {"stone", 8}, + {"dirt", 2} + }; + stats::StatsDataMap bob{ + {"stone", 15} + }; + stats::StatsDataMap carol{ + {"dirt", 5} + }; + std::vector entries{ + {"Alice", &alice}, + {"Bob", &bob }, + {"Carol", &carol}, + }; + + auto const rank = stats::query::buildRank(entries, "stone"); + expectEqual(rank.size(), std::size_t{3}, "keeps every player in the ranking"); + expectEqual(rank[0], stats::query::RankData::value_type{"Bob", 15}, "sorts highest value first"); + expectEqual(rank[1], stats::query::RankData::value_type{"Alice", 8}, "sorts the second value"); + expectEqual(rank[2], stats::query::RankData::value_type{"Carol", 0}, "includes missing values as zero"); +} + +void testEmptyRanking() { + std::vector entries; + expectEqual(stats::query::buildRank(entries, "").size(), std::size_t{0}, "handles an empty ranking"); +} + +void testRankingUsesStableTieOrder() { + stats::StatsDataMap value{{"stone", 5}}; + std::vector entries{ + {"Carol", &value}, + {"Alice", &value}, + {"Bob", &value}, + }; + auto const rank = stats::query::buildRank(entries, "stone"); + expectEqual(rank[0].first, std::string{"Alice"}, "sorts rank ties by player name"); + expectEqual(rank[1].first, std::string{"Bob"}, "keeps rank pagination deterministic"); +} + +void testPagination() { + stats::query::StatsEntries entries; + for (uint64_t index = 0; index < 45; ++index) { + entries.emplace_back("entry-" + std::to_string(index), index); + } + + auto const middle = stats::query::paginate(entries, 1, 20); + expectEqual(middle.pageIndex, std::size_t{1}, "keeps a valid page index"); + expectEqual(middle.totalPages, std::size_t{3}, "calculates total pages"); + expectEqual(middle.entries.size(), std::size_t{20}, "returns one page of entries"); + expectEqual(middle.entries.front().first, std::string{"entry-20"}, "uses the correct page offset"); + + auto const last = stats::query::paginate(std::move(entries), 99, 20); + expectEqual(last.pageIndex, std::size_t{2}, "clamps an out-of-range page"); + expectEqual(last.entries.size(), std::size_t{5}, "returns the partial last page"); + + stats::query::StatsEntries unsorted; + for (uint64_t index = 0; index < 100; ++index) { + unsorted.emplace_back("rank-" + std::to_string(index), index); + } + auto const firstPage = stats::query::sortAndPaginate(std::move(unsorted), 0, 20); + expectEqual(firstPage.entries.size(), std::size_t{20}, "partially sorts only one rank page"); + expectEqual(firstPage.entries.front().second, uint64_t{99}, "keeps the highest rank first"); + expectEqual(firstPage.entries.back().second, uint64_t{80}, "returns the correct first-page cutoff"); +} + +} // namespace + +int main() { + failures += runStatsCustomTests(); + failures += runStatsDataTests(); + failures += runStatsJsonCodecTests(); + failures += runStatsWriteQueueTests(); + testValueLookup(); + testTotalsUse64Bits(); + testTotalsSaturate(); + testDisplayEntriesAddActivePlayTime(); + testDisplayEntriesInsertMissingPlayTime(); + testRanking(); + testEmptyRanking(); + testRankingUsesStableTieOrder(); + testPagination(); + + if (failures != 0) return 1; + std::cout << "Stats tests passed\n"; + return 0; +} diff --git a/tests/StatsWriteQueueTests.cpp b/tests/StatsWriteQueueTests.cpp new file mode 100644 index 0000000..2f15824 --- /dev/null +++ b/tests/StatsWriteQueueTests.cpp @@ -0,0 +1,83 @@ +#include "mod/Stats/StatsWriteQueue.h" + +#include +#include +#include +#include +#include +#include +#include + +int runStatsWriteQueueTests() { + std::mutex mutex; + std::condition_variable started; + std::condition_variable release; + bool blockerStarted = false; + bool releaseBlocker = false; + std::vector writes; + + stats::StatsWriteQueue queue([&](stats::DecodedStats const& record) { + if (record.info.uuid == "blocker") { + std::unique_lock lock(mutex); + blockerStarted = true; + started.notify_one(); + release.wait(lock, [&] { return releaseBlocker; }); + } + std::lock_guard lock(mutex); + writes.push_back(record); + return true; + }); + + queue.enqueue({{"blocker", "", ""}, {}}); + { + std::unique_lock lock(mutex); + started.wait(lock, [&] { return blockerStarted; }); + } + + stats::StatsData first; + first.add(StatsType::mined, "minecraft:stone", 1); + queue.enqueue({{"player", "xuid", "Old"}, first}); + + stats::StatsData latest; + latest.add(StatsType::mined, "minecraft:stone", 2); + queue.enqueue({{"player", "xuid", "Latest"}, latest}); + + { + std::lock_guard lock(mutex); + releaseBlocker = true; + } + release.notify_one(); + queue.flush(); + + if (writes.size() != 2) { + std::cerr << "FAILED: write queue coalesces pending player snapshots\n"; + return 1; + } + auto const& saved = writes.back(); + auto const* mined = saved.data.getMap(StatsType::mined); + if (saved.info.name != "Latest" || !mined || mined->at("minecraft:stone") != 2) { + std::cerr << "FAILED: write queue keeps the latest player snapshot\n"; + return 1; + } + + int attempts = 0; + stats::StatsWriteQueue failingQueue([&](stats::DecodedStats const&) -> bool { + ++attempts; + if (attempts == 1) throw std::runtime_error("write failed"); + return true; + }); + failingQueue.enqueue({{"first", "", ""}, {}}); + failingQueue.flush(); + failingQueue.enqueue({{"second", "", ""}, {}}); + failingQueue.flush(); + if (attempts != 3) { + std::cerr << "FAILED: write queue retries writer failures and remains usable\n"; + return 1; + } + failingQueue.stop(); + if (failingQueue.enqueue({{"late", "", ""}, {}})) { + std::cerr << "FAILED: stopped write queue rejects new snapshots\n"; + return 1; + } + return 0; +} diff --git a/tooth.json b/tooth.json index 7050841..37a34fc 100644 --- a/tooth.json +++ b/tooth.json @@ -2,7 +2,7 @@ "format_version": 3, "format_uuid": "289f771f-2c9a-4d73-9f3f-8492495a924d", "tooth": "github.com/LeafKnife/Stats", - "version": "0.9.0", + "version": "0.11.0", "info": { "name": "Stats", "description": "Minecraft 基岩版服务器(BDS) 统计信息插件", @@ -17,14 +17,14 @@ "label": "", "platform": "win-x64", "dependencies": { - "github.com/LiteLDev/LeviLamina": "26.20.*", - "github.com/LiteLDev/LegacyRemoteCall": "0.19.*" + "github.com/LiteLDev/LeviLamina": "26.40.*", + "github.com/LiteLDev/LegacyRemoteCall": "0.21.*" }, "assets": [ { "type": "zip", "urls": [ - "https://{{tooth}}/releases/download/v{{version}}/Stats-windows-x64.zip" + "https://{{tooth}}/releases/download/v{{version}}/Stats-server-windows-x64.zip" ], "placements": [ { @@ -49,4 +49,4 @@ } } ] -} \ No newline at end of file +} diff --git a/xmake.lua b/xmake.lua index 1d2cc40..3c38a74 100644 --- a/xmake.lua +++ b/xmake.lua @@ -1,32 +1,28 @@ add_rules("mode.debug", "mode.release") -add_repositories("levimc-repo " .. (get_config("levimc_repo") or "https://github.com/LiteLDev/xmake-repo.git")) +add_repositories("levimc-repo https://github.com/LiteLDev/xmake-repo.git") + +option("target_type") + set_default("server") + set_showmenu(true) + set_values("server", "client") +option_end() -- add_requires("levilamina x.x.x") for a specific version -- add_requires("levilamina develop") to use develop version -- please note that you should add bdslibrary yourself if using dev version -if is_config("target_type", "server") then - add_requires("levilamina 26.20.0", {configs = {target_type = "server"}}) -else - add_requires("levilamina 26.20.0", {configs = {target_type = "client"}}) -end - -set_toolchains("clang-cl") +add_requires("levilamina 26.40.*", {configs = {target_type = get_config("target_type")}}) add_requires("levibuildscript") -add_requires("legacyremotecall 0.19.0", {configs = {target_type = get_config(target_type)}}) +add_requires("legacyremotecall v0.21.0", {configs = {target_type = get_config("target_type")}}) +add_requires("nlohmann_json v3.11.3") +add_requires("parallel-hashmap v2.0.0") if not has_config("vs_runtime") then set_runtimes("MD") end -option("target_type") - set_default("server") - set_showmenu(true) - set_values("server", "client") -option_end() - option("publish") set_default(false) set_showmenu(true) @@ -54,44 +50,40 @@ target("LK-Stats") -- Change this to your mod name. ) set_toolchains("clang-cl") end - add_packages("levilamina","legacyremotecall") - set_exceptions("none") -- To avoid conflicts with /EHa. + add_packages("levilamina", "legacyremotecall", "nlohmann_json", "parallel-hashmap") + -- set_exceptions("none") -- To avoid conflicts with /EHa. set_kind("shared") set_languages("c++20") set_symbols("debug") add_headerfiles("src/**.h") add_files("src/**.cpp") - add_includedirs("src") + add_files("src/**.rc") + set_configdir("$(builddir)/config") + add_configfiles("src/(mod/Version.h.in)") + add_includedirs("src", "$(builddir)/config") if is_config("target_type", "server") then - add_defines("LL_PLAT_S") + -- add_defines("LL_PLAT_S") -- add_includedirs("src-server") -- add_files("src-server/**.cpp") else - add_defines("LL_PLAT_C") + -- add_defines("LL_PLAT_C") -- add_includedirs("src-client") -- add_files("src-client/**.cpp") end on_load(function (target) - local tag = os.iorun("git describe --tags --abbrev=0 --always") - local major, minor, patch, suffix = tag:match("v(%d+)%.(%d+)%.(%d+)(.*)") - if not major then - print("Failed to parse version tag, using 0.0.0") - major, minor, patch = 0, 0, 0 - end - local versionStr = major.."."..minor.."."..patch - if suffix then - prerelease = suffix:match("-(.*)") - if prerelease then - prerelease = prerelease:gsub("\n", "") - end - if prerelease then - versionStr = versionStr.."-"..prerelease - end - end - if not has_config("publish") then - local hash = os.iorun("git rev-parse --short HEAD") - versionStr = versionStr.."+"..hash:gsub("\n", "") - end + import("core.base.json") + local modVersionString = json.loadfile("tooth.json")["version"] + local modVersionMajor, modVersionMinor, modVersionPatch = + modVersionString:match("^(%d+)%.(%d+)%.(%d+)$") + if not modVersionMajor then raise("tooth.json version must use major.minor.patch format") end + + local hash = os.iorun("git rev-parse --short HEAD"):gsub("\n", "") + local versionStr = modVersionString .. "+" .. hash + target:set("version", modVersionString) + target:set("configvar", "LK_STATS_VERSION_MAJOR", modVersionMajor) + target:set("configvar", "LK_STATS_VERSION_MINOR", modVersionMinor) + target:set("configvar", "LK_STATS_VERSION_PATCH", modVersionPatch) + target:set("configvar", "LK_STATS_VERSION_STRING", versionStr) target:add("rules", "@levibuildscript/modpacker",{ modVersion = versionStr @@ -104,3 +96,31 @@ target("LK-Stats") -- Change this to your mod name. os.mkdir(outputPath) os.cp(langPath, outputPath) end) + +target("LK-Stats-Tests") + set_default(false) + set_kind("binary") + set_languages("c++20") + if is_plat("windows") then + add_cxflags("/utf-8") + end + add_includedirs("src") + add_packages("nlohmann_json") + add_files("tests/*.cpp") + add_files("src/mod/Stats/StatsData.cpp") + add_files("src/mod/Stats/StatsJsonCodec.cpp") + add_files("src/mod/Stats/StatsQuery.cpp") + add_files("src/mod/Stats/StatsWriteQueue.cpp") + +target("LK-Stats-Benchmarks") + set_default(false) + set_kind("binary") + set_languages("c++20") + if is_plat("windows") then + add_cxflags("/utf-8") + end + add_includedirs("src") + add_syslinks("psapi") + add_files("benchmarks/StatsQueryBenchmark.cpp") + add_files("src/mod/Stats/StatsData.cpp") + add_files("src/mod/Stats/StatsQuery.cpp")