Skip to content

Auto enchanting table 添加自动附魔台#4050

Draft
burin-ljx wants to merge 9 commits into
Anvil-Dev:dev/26.1/1.6from
burin-ljx:auto_enchanting_table
Draft

Auto enchanting table 添加自动附魔台#4050
burin-ljx wants to merge 9 commits into
Anvil-Dev:dev/26.1/1.6from
burin-ljx:auto_enchanting_table

Conversation

@burin-ljx

@burin-ljx burin-ljx commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

burin-ljx and others added 4 commits July 7, 2026 13:07
- 添加自动附魔台方块
- 添加自动附魔台方块实体
- 添加自动附魔台方块实体渲染器
- 实现渲染书本模型
- 实现流体储存
- 实现物品储存
- 实现流体渲染
- 实现自动附魔台的无引物、原版的附魔功能
- 修复创造板条箱渲染物品的z打架
…ting_table

# Conflicts:
#	src/main/java/dev/dubhe/anvilcraft/block/entity/CreativeCrateBlockEntity.java
@Gugle2308

Copy link
Copy Markdown
Collaborator

现在我已获得所有必要信息,将输出完整的审查意见。


代码审查摘要

PR: #4050 — Auto enchanting table
作者: burin-ljx
分支: auto_enchanting_tabledev/26.1/1.6
结论:请求更改(2 个关键问题,2 个警告)


🔴 关键

1. AutoEnchantingTableBlockEntity.setItem() 使用 copyWithCount(1) 导致批量输入丢物品

文件: AutoEnchantingTableBlockEntity.java:480

public void setItem(int slot, ItemStack itemStack) {
    this.items.set(slot, itemStack.copyWithCount(1));  // ← 强制截断为 1
    this.onChange();
}

BE 实现 WorldlyContainercanPlaceItemThroughFace(0, ...) 返回 true,因此外部系统(漏斗、管道)允许向 slot 0 放入物品。当管道(如本次新增的 Pipez 依赖)批量推入堆叠物品时,copyWithCount(1) 会导致 剩余物品被静默丢弃

影响场景:

  • 玩家通过漏斗输入 64 个钻石剑 → 只保留 1 个 → 63 个丢失
  • 使用新增的 Pipez 管道输入 → 堆叠物品同样被截断

建议修复:setItem() 中先检查 slot 0 是否已有物品,若非空则拒绝写入(return 不做任何操作),或使用 Dropper/Hopper 式的逐件处理模式:

@Override
public void setItem(int slot, ItemStack itemStack) {
    if (slot == 0 && !this.getItem(0).isEmpty()) {
        return; // slot 0 已有物品时拒绝覆盖
    }
    // 或使用 extract 模式:将多余物品弹出到世界
    super.setItem(slot, itemStack);
    this.onChange();
}

2. 方块模型 JSON 文件缺失

blockstates/auto_enchanting_table.jsonitems/auto_enchanting_table.json 均引用模型 anvilcraft:block/auto_enchanting_table,但 diff 中未包含 models/block/auto_enchanting_table.json 文件。

若该模型已存在于仓库中(非本次新增)则可接受,但需要确认。若确实缺失,客户端会显示紫黑块缺失纹理。


⚠️ 警告

3. 3 个 JSON 文件缺少末尾换行符

  • src/generated/resources/assets/anvilcraft/items/auto_enchanting_table.json
  • src/generated/resources/data/anvilcraft/loot_table/blocks/auto_enchanting_table.json
  • src/main/resources/assets/anvilcraft/blockstates/auto_enchanting_table.json

3 个文件都有 \ No newline at end of file 标记。这会导致 POSIX 兼容性问题和 diff 噪音。建议数据生成器或手动编辑工具确保末尾换行。

4. BaseFluidHandlerHolderRenderer.FLUID_RENDER_TYPE 可见性修改未说明原因

文件: BaseFluidHandlerHolderRenderer.java:29

- private static final RenderType FLUID_RENDER_TYPE = ...
+ public static final RenderType FLUID_RENDER_TYPE = ...

private 改为 public 是为了让 AutoEnchantingTableBlockEntityRenderer 引用该 RenderType。这是合理的解耦方式,但建议添加简短注释解释用途。


💡 建议

5. 流体渲染液面梯度分档过粗

文件: AutoEnchantingTableBlockEntityRenderer.java:1158-1164

if (amount > 0 && amount <= 2000)      { y += 0.0625f; }
else if (amount > 2000 && amount <= 4000) { y += 0.125f; }
else if (amount > 4000 && amount <= 6000) { y += 0.1875f; }
else if (amount > 6000 && amount <= 8000) { y += 0.25f; }

液面高度仅分 5 档(<2000 一档,>8000 全满),不是线性映射。当经验值在 0~2000 mB 之间变化时,液面视觉上不动。

建议改为线性比例计算:y = 0.375f + (amount / 8000f) * 0.25f,并 Mth.clamp[0.375, 0.625]

6. 无引物模式的经验消耗固定为书架数 × 400,上限 6000

文件: AutoEnchantingTableBlockEntity.java:317-324

int exp = 0;
for (BlockPos offset : AutoEnchantingTableBlock.BOOKSHELF_OFFSETS) {
    if (EnchantingTableBlock.isValidBookShelf(level, pos, offset)) {
        exp += 400;
        if (exp >= 6000) { exp = 6000; break; }
    }
}

15 个书架 = 6000 mB exp,对应约 30 级附魔。但 EnchantmentHelper.getEnchantmentCost() 会根据书架数量动态计算附魔等级消耗。如果书架较少(如 6 个),exp=2400 但附魔质量也低,性价比是一致的,只是定价策略问题。建议在实际测试中确认价格曲线是否合理。

7. getEnchantmentListrandom.setSeed() 重复使用

getCostAndEnchant() 中使用 random.setSeed(this.enchantmentSeed) 调用 getEnchantmentList()(仅用于界面显示 clue),随后 serverTick() 使用 random.setSeed(this.enchantmentSeed + slot) 再次调用 getEnchantmentList()(实际应用附魔)。两次种子不同,但同一个 random 实例的 setSeed 互相覆盖。虽然逻辑上不造成功能错误(第一次结果只用于 UI 显示),但建议将 UI 渲染用的随机种子与实际应用的分离,提升可维护性。

8. 新增 Pipez 依赖但 PR 描述未提及

dependencies.gradle 新增 pipez 依赖。如果计划在该 PR 中集成管道输入,建议说明或移出到独立 PR。当前仅作为依赖添加但代码未引用,属于死依赖。


✅ 看起来不错

  • 序列化模式正确:使用 26.1 的 ValueOutput/ValueInput + TagValueOutput.createWithContext()/buildResult(),与项目当前规范一致
  • Tank onContentsChanged 回调完整:同时调用了 setChanged() + sendBlockUpdated()
  • RenderState 字段覆盖完整extractRenderState() 中所有 10+ 个字段的 setter 均有赋值(对比声明列表,无遗漏)
  • Block 交互方法use() 中正确包含 GameType.SPECTATOR 守卫
  • Registrum builder 链完整register() 结尾的合法性已确认
  • 语言文件同步en_us.jsonen_ud.json 的 key 一一对应
  • 拾取镐标签mineable/pickaxe 已添加
  • Capability 注册完整:Item 和 Fluid 两种 capability 均注册
  • 渲染生命周期正确:书本动画(bookAnimationTick)仅在客户端运行,服务端逻辑分离清晰

由 Hermes Agent 审查

@burin-ljx burin-ljx changed the title Auto enchanting table Auto enchanting table 添加自动附魔台 Jul 8, 2026
@burin-ljx
burin-ljx marked this pull request as draft July 8, 2026 13:31
@Gugle2308

Copy link
Copy Markdown
Collaborator

现在我已完整理解这个 PR。下面是代码审查报告:


代码审查摘要 — PR #4050: Auto Enchanting Table

结论:请求更改(2 个关键问题,1 个警告,2 条建议)

🔴 关键

  1. CreativeCrateRenderer.java:1264 — 底部面(DOWN, case 4)Y 坐标错误

    // 当前代码(expected: 1.8f)
    case 4 -> poseStack.translate(1f, 0.2f, 1f);  // ❌ Y=0.2
    // case 5 (UP) 正确:
    case 5 -> poseStack.translate(1f, 1.8f, 1f);  // ✅ Y=1.8

    Scale 0.5f 后坐标系缩小 2 倍,旧坐标 (0.5, 0.9, 0.5) 应按 2x 映射为 (1.0, 1.8, 1.0),但 case 4 的 Y 写成了 0.2(疑似从 case 5 复制粘贴过来没改)。这将导致底部面的物品渲染在错误位置(顶面附近而非底面)。

  2. BaseFluidHandlerHolderRenderer.java:1229FLUID_RENDER_TYPE 可见性从 private 改为 public
    ✅ 这个改动本身是正确的——新 AutoEnchantingTableBlockEntityRenderer 确实需要引用该常量。这是必要的重构,确认无误。

⚠️ 警告

  1. enchantmentMap 字段 + getEnchantmentMap() 方法已定义但未被运行时调用
    字段声明在 AutoEnchantingTableBlockEntity.java:188,静态构造方法 getEnchantmentMap(Level):219,但目前 serverTick()getCostAndEnchant() 使用的是 vanilla EnchantmentHelper.selectEnchantment() + EnchantmentTags.IN_ENCHANTING_TABLE 逻辑。enchantmentMap 从未被 populate(初始为空 Map),也没有任何代码调用 getEnchantmentMap() 给其赋值。

    我知道这是为"自选附魔"功能预留的代码([ ] 自选附魔 未勾选),但提交未使用的运行时字段和方法是代码异味。建议:

    • 要么删除当前未使用的 enchantmentMapgetEnchantmentMap(),等实现自选附魔时再引入
    • 要么加 // TODO: 用于自选附魔 (unused in v1) 注释并标注 @Deprecated

💡 建议

  1. enchantmentSeed 未持久化
    saveAdditional() 没有保存 enchantmentSeedloadAdditional() 也没有读取。当区块卸载后重载,或服务器重启后,已确定的附魔种子会丢失,下次操作会生成全新的附魔组合。

    虽然 vanilla 附魔台每次打开 GUI 也重新生成种子,但自动化方块是连续运行的,持久化种子可以在跨 tick 操作中保持一致性。建议补充保存。

  2. 3 个 JSON 文件缺少末尾换行符(No newline at end of file)

    • items/auto_enchanting_table.json
    • loot_table/blocks/auto_enchanting_table.json
    • blockstates/auto_enchanting_table.json

    这会导致 GitHub diff 末尾出现 \ No newline at end of file 标记,且可能在某些 POSIX 工具中引起兼容性问题。建议补上换行符。

  3. 引物槽(Slot 2)可用但引物模式为 TODO
    引物槽(slot index 38)的 mayPlace 使用默认实现(返回 true),玩家可以通过快捷移动放入物品。但 serverTick()// TODO: 引物模式 分支为空,放入的物品会无声无息地积累在槽中,无任何消耗或效果。建议:

    • 暂时将引物槽设为不可用(mayPlace 返回 false),直到引物模式实现
    • 或在 GUI 中标注"暂未开放"提示

✅ 看起来不错

  • 26.1 模式正确TagValueOutput.createWithContext() + ValueOutput/ValueInput 序列化 ✓
  • BlockEntityRenderer 使用 <BE, RenderState> 泛型(26.1 新 API)✓
  • Block 端生命周期分离getTicker() 正确分派 bookAnimationTick(客户端)和 serverTick(服务端)✓
  • onChange() 同时调用 setChanged() + sendBlockUpdated()
  • 流体 handler 注册正确Capabilities.Fluid.BLOCK + Capabilities.Item.BLOCK 使用 WorldlyContainerWrapper
  • Registrum 注册链完整ModBlocksModBlockEntitiesModMenuTypes 均为每个条目以 .register() 结尾 ✓
  • 语言文件同步en_us.json + en_ud.json 均包含新的 block/item 翻译 ✓
  • 方块可挖掘标签 — 添加到 minecraft:pickaxe 标签 ✓
  • 渲染器流体显示机制 — 4 面分区域显示流体量,复用 BaseFluidHandlerHolderRenderer.FLUID_RENDER_TYPE
  • CreativeCrateRenderer 26.1 迁移 — 除 case 4 Y 坐标外,其余 5 面的 Scale 0.5 + 2x Translation 转换正确 ✓
  • 依赖添加 — Pipez 用于管道运输集成 ✓

@Gugle2308

Copy link
Copy Markdown
Collaborator

我已经获取了足够的变更信息。以下是完整审查结果:


代码审查摘要

结论:请求更改(2 个关键问题,1 个警告,4 条建议)

PR 实现了自动附魔台的核心功能:通过书架提供 EXP 流体、消耗 EXP 附魔物品、支持多种宝石/金属锭作为引物材料。整体架构符合 AnvilCraft 风格(Registrum 注册、26.1 ValueOutput/ValueInput 序列化、IPowerConsumer)。以下为具体发现:


🔴 关键

  1. AutoEnchantingTableMenu.quickMoveStack 快捷移动逻辑 Bug —— 热栏物品静默丢失
    文件:AutoEnchantingTableMenu.java:954-976

    Container slot 索引分配:0=输入, 1=输出, 2=引物, 3..29=玩家背包(27), 30..38=快捷栏(9)。

    quickMoveStack 中:

    • slotIndex == 36 || slotIndex == 37(快捷栏第 6、7 格)→ 返回 ItemStack.EMPTY,物品静默消失
    • slotIndex == 38(快捷栏第 8 格)→ 移入 slots 1..36(包括输出槽位 1,不应可放入)
    • slotIndex >= 0 && <= 35 → 仅移向第 38 格,而非合理地分发给所有容器槽

    建议修复: 按 Forge 容器的标准模式重写:容器槽 → 移至背包/快捷栏;背包/快捷栏 → 移至对应的容器槽(输入/引物)。参考项目内其他 Container 的实现模式。

  2. 3 个生成资源文件末尾缺少换行符

    • blockstates/auto_enchanting_table.json
    • items/auto_enchanting_table.json
    • loot_table/blocks/auto_enchanting_table.json

    均为生成资源,建议在数据生成器中修复 EOF 换行问题。可用 awk 确认:grep -c 'No newline' /tmp/pr.diff 结果为 3。


⚠️ 警告

  1. dependencies.gradle 新增了 pipez 依赖
    文件:dependencies.gradle:29-30

    compileOnly("maven.modrinth:pipez:neoforge-1.2.30+26.1.2")
    runtimeOnly("maven.modrinth:pipez:neoforge-1.2.30+26.1.2")
    

    此 PR 功能描述为"自动附魔台",与管道模组无关。此依赖添加应说明用途(是否用于管道连接自动附魔台?)或拆分到独立 PR。


💡 建议

  1. setItem 强制 count=1 可能导致物品静默丢失
    文件:AutoEnchantingTableBlockEntity.java:480

    this.items.set(slot, itemStack.copyWithCount(1));

    当漏斗/管道输入 64 个物品时,只取 1 个,剩余 63 个不被返还给输入源(应为 ItemStack 返回值)。建议:从输入栈中 extract 1 个,将剩余部分返回或保留在原位。如果仅支持单物品处理,需在 canPlaceItemThroughFacesetItem 中明确处理多物品堆叠逻辑。

  2. MutableValue.setValue 可能 NPE
    文件:MutableValue.java:14

    public void setValue(T value) {
        if (!this.value.equals(value)) {  // value 为 null 时 NPE

    若外部调用 setValue(null) 或在构造时 value 未被赋值前调用 equals,会抛出 NPE。建议添加 null 守卫:

    if (this.value == null || !this.value.equals(value))
  3. cooldown 初始值 0 导致首次 tick 立即执行
    文件:AutoEnchantingTableBlockEntity.java:100

    首次 serverTickcooldown == 0 立即尝试附魔。此时区块可能尚未完全加载,书架检测可能不可靠。建议初始化为一个非零值(如 40 = 2s 延迟),或首次 tick 跳过。

  4. 缺少 setRemoved/onChunkUnloaded 覆写确认
    文件:AutoEnchantingTableBlockEntity.java

    该类实现了 IPowerConsumer,但未覆写 setRemoved()PowerGrid 中注销自身。需确认 BaseContainerBlockEntityIPowerConsumer 的父类已处理此清理。若没有,服务端重启后 PowerGrid 中会残留已卸载的消费引用。

  5. Block model JSON 未在 diff 中出现
    blockstate 引用了 "model": "anvilcraft:block/auto_enchanting_table",但 assets/anvilcraft/models/block/auto_enchanting_table.json 未出现在变更文件中。如果该模型文件已存在于仓库中(非本次新增),则正常。若不存在,方块会渲染为缺失模型(紫黑方块)。建议确认模型文件存在。


✅ 看起来不错

  • 26.1 序列化:正确使用 ValueOutput/ValueInput,嵌套 fluidHandler.serialize(output) 结构 ✅
  • Capabilities 注册:Item 和 Fluid Capabilities 都正确注册了 WorldlyContainerWrappergetFluidHandler()
  • 附魔逻辑:复用 EnchantmentHelper.selectEnchantmentEnchantmentHelper.getEnchantmentCost,与原版附魔机制一致 ✅
  • RenderState 字段覆盖extractRenderState() 中所有 9 个字段均有赋值,无遗漏 ✅
  • Renderer 书本动画:正确地沿用原版《E》附魔台书籍渲染(BookModel),并复用 EnchantTableRenderer.BOOK_TEXTURE
  • CreativeCrate 迁移:同步清理了 getUpdateTag 中废弃的 NbtOps/RegistryOps 导入,改为 TagValueOutput
  • 语言文件en_us.jsonen_ud.json 同步更新 ✅
  • Loot table:标准掉落自身,含 survives_explosion 条件 ✅
  • pickaxe 标签:已加入可挖掘标签 ✅

总结: 自动附魔台的核心功能实现扎实,流程清晰。quickMoveStack 的 slot 索引 Bug 和数据生成器 EOF 换行问题是必须在合并前修复的关键问题。Pipez 依赖说明建议补充。

@Gugle2308

Copy link
Copy Markdown
Collaborator

好的,我已经仔细审查了所有 1682 行差异。以下是我的审查意见。


代码审查摘要 — PR #4050: Auto enchanting table 添加自动附魔台

结论:评论(无阻塞性问题,一些建议和改进项)

范围: 16 个 Java 文件(12 个新文件 + 4 个修改文件),5 个生成资源文件,1 个 blockstate JSON,1 个 PNG 纹理,1 个 Gradle 依赖变更。


严重

警告

建议

  1. FluidDisplayWidget.java:25 — 旧式 @Nullable 注解

    import org.jetbrains.annotations.Nullable;

    其它新文件均已使用 org.jspecify.annotations.Nullable(26.1 标准),该 widget 文件是唯一的遗漏。建议统一为 jspecify。

  2. AutoEnchantingTableMenu.java:93 — 调试日志残留

    AnvilCraft.LOGGER.debug("index: {}", slotIndex);

    发布前建议移除或改为 isClientSide 守卫。

  3. MutableValue.java:11setValue 可能 NPE

    public void setValue(T value) {
        if (!this.value.equals(value)) {  // value 为 null 时抛 NPE

    若初始值设为非 null 后后续传入 null,会抛 NullPointerException。建议添加 null 防卫:

    if (!Objects.equals(this.value, value)) {
  4. blockstates/auto_enchanting_table.json — 末尾缺少换行符
    \ No newline at end of file — 建议添加尾随换行符以符合 POSIX 标准。

  5. AutoEnchantingTableBlockEntity.java:188enchantmentMap 字段未被填充

    private final Map<Item, List<ResourceKey<Enchantment>>> enchantmentMap = new Object2ObjectOpenHashMap<>();

    该字段初始化为空 map,虽有 @Getter 但从未从静态方法 getEnchantmentMap(Level) 的结果赋值。推测是 TODO「自选附魔」功能的占位结构——建议添加 // TODO: populate for self-selection mode 注释或直接删除该字段以减少混淆。

  6. AutoEnchantingTableBlockEntity.java:205cooldown 初始化逻辑的可读性改进

    private int cooldown = 0;
    ...
    if (this.cooldown > 0) {
        this.cooldown--;
    } else if (this.cooldown == 0) {
        this.cooldown = 80;

    首次 tick 时 cooldown == 0 会立即运行逻辑。考虑初始化为 80 以避免首 tick 立即执行完整的附魔流程(虽然正确,但语义上首 tick 应等待)。

看起来不错

  • 26.1 API 使用正确ValueOutput/ValueInput 序列化、extractWidgetRenderState/extractBackground/extractLabels 渲染接口、BlockEntityRenderer<BE, RenderState> 双泛参签名、RenderPipelines.GUI_TEXTUREDGuiGraphicsExtractor 等全部正确。
  • 电网集成:实现 IPowerConsumergetInputPower() 根据是否有引物品分别返回 16/64,serverTickgrid != null && !grid.isWorking() 守卫正确。
  • 流体处理FluidStacksResourceHandleronContentsChanged 回调 + onChange()setChanged() + sendBlockUpdated())完整。Capability 注册正确(Capabilities.Fluid.BLOCKCapabilities.Item.BLOCK 均注册)。
  • 物流接口WorldlyContainer 实现正确——slot 0 只进、slot 1 只出、getMaxStackSize() 返回 1。
  • 书本动画:完整复刻原版附魔台的书本动画逻辑(bookAnimationTick+ BookModel + EnchantTableRenderer.BOOK_TEXTURE),正确使用 Mth.lerp 插值渲染。
  • 流体渲染:四方位的流体容器模型 + splitNumber 分级(每 8000mB 对应一个面),renderFluidBox 方法位置计算正确。
  • 物品渲染:输入/输出物品在书本上方旋转展示,使用 FeatureRendererSupport.initialize 兼容 26.1 物品渲染。
  • CreativeCrateBlockEntity 清理:将旧的 NbtOps/RegistryOps 手工序列化改为 TagValueOutput 统一模式 👍
  • GUI 界面FluidDisplayWidget 在经验流体条右侧显示,鼠标悬浮时以 tooltip 展示精确 mB 数。
  • 注册链完整ModBlocksModBlockEntitiesModMenuTypesModCapabilities 全部通过 Registrum builder 链注册,每条链都有 .register() 终结。

其他检查

  • ✅ PR 声明完整:「原版附魔的自动化」已完成,「自选附魔」为 TODO 未勾选,与代码中预留的 enchantmentMap 字段和 TODO: 引物模式 一致
  • en_us.jsonen_ud.json 同步更新
  • ✅ 配方、战利品表、Item 模型均已生成
  • pipez 依赖在 Gradle 中以 compileOnly + runtimeOnly 添加(用于管道交互)
  • ✅ 无硬编码密钥、无调试语句遗留(除建议中提到的 LOGGER.debug)
  • onChange() 模式正确:同时调用 setChanged()sendBlockUpdated()

@burin-ljx burin-ljx linked an issue Jul 8, 2026 that may be closed by this pull request
@Gugle2308

Copy link
Copy Markdown
Collaborator

Now I have a thorough understanding of the PR. Let me compile the review.


代码审查摘要 — PR #4050: 自动附魔台

审查者: Hermes Agent
PR: #4050 — Auto Enchanting Table 添加自动附魔台
作者: burin-ljx | 分支: auto_enchanting_table → dev/26.1/1.6
状态: edited(非草稿) → 完整审查

结论:评论(有建议,无阻塞性问题)


概述

该 PR 新增了一个「自动附魔台」方块,包含完整的 26.1 生态集成(注册、BE、Menu、Screen、Renderer、Capability 注册)。总 24 个变更文件,其中 12 个新增、0 删除。主要功能:

  • 自动附魔台方块(供电 + EXP 流体驱动)
  • 3 个槽位:物品输入(槽 0)、物品输出(槽 1)、引物(槽 2,暂为 TODO)
  • 原版书架检测(5×2×5 范围)用于提供 EXP 等级
  • 费时 80 tick(~4 秒)自动附魔一本书/可附魔物品
  • 附魔来源:护符/宝石/金属锭对应特定附魔池
  • 含书本动画、流体渲染(EXP 液体显示在 4 侧)、物品渲染
  • 修复了创造板条箱物品渲染 z 打架问题([TODO] 自动附魔台 #3629
  • 依赖 pipez 模组用于管道自动输入/输出

⚠️ 警告

1. 三处 JSON 文件缺少末尾换行符(POSIX 兼容性)

  • src/generated/resources/assets/anvilcraft/items/auto_enchanting_table.json
  • src/generated/resources/data/anvilcraft/loot_table/blocks/auto_enchanting_table.json
  • src/main/resources/assets/anvilcraft/blockstates/auto_enchanting_table.json

生成资源缺少 EOF 换行符数量不大(3 个),通常不影响运行,但手动编写的 blockstate JSON 应修正。建议在 blockstate 末尾添加换行符。

2. FluidDisplayWidget 使用了旧版 org.jetbrains.annotations.Nullable

文件 FluidDisplayWidget.java 使用 import org.jetbrains.annotations.Nullable;,而项目其他新文件统一使用 org.jspecify.annotations.Nullable(26.1 迁移标准)。更奇特的是该文件同一方法内又使用了内联的 @org.jspecify.annotations.Nullable Identifier。建议统一为 jspecify。

# 找到出处
awk '/^diff --git.*FluidDisplayWidget/{curr=$0} /^\+import org.jetbrains/{print curr": "$0}' /tmp/pr4050.diff

💡 建议

1. Block model 文件缺失风险

Blockstate JSON 和生成的 item JSON 都引用了 "model": "anvilcraft:block/auto_enchanting_table",但 diff 中没有包含任何 block model JSON 文件models/block/auto_enchanting_table.json)。如果该模型不由数据生成器自动生成,方块在游戏中可能显示为 missing model。请确认:

  • 数据生成器是否自动创建了该模型?或
  • 模型文件是否已存在于目标分支?或
  • 是否需要手动添加一个模型 JSON?

2. 输入槽(槽 0)玩家无法通过 GUI 取回物品

AutoEnchantingTableMenu 中槽 0 和槽 1 的 mayPickup 都返回 false。这意味着如果玩家不小心把物品放入输入槽,只能通过管道取出或破坏方块。建议考虑允许 mayPickup 返回 true,或在 GUI 中添加提取按钮。

3. MutableValue.setValue() 缺少 null 保护

MutableValue.setValue() 使用 this.value.equals(value) 做去重判断,当 this.value 为 null 时抛出 NPE。虽然当前使用 MutableValue<Integer> 不会出现 null,但泛型约束未禁止。建议加 null 检查:

public void setValue(T value) {
    if (!Objects.equals(this.value, value)) {
        this.value = value;
    }
}

4. enchantmentSeed 未序列化——重载后种子重置

saveAdditional/loadAdditional 未保存 enchantmentSeed,因此区块重载后 seed 归零,会在下一次 getCostAndEnchant 中被重新随机初始化。这导致每次重载后附魔池变化。如果这是期望行为(类似原版附魔台 UI 关闭后刷新),建议添加注释说明。如果不是,请补上序列化:

// saveAdditional 中添加
output.putInt("enchantmentSeed", this.enchantmentSeed);

// loadAdditional 中添加
input.getInt("enchantmentSeed").ifPresent(seed -> this.enchantmentSeed = seed);

5. 处理失败时 cooldown 仍重置为 80

serverTick 中因无供电、无物品、EXP 不足等原因返回时,cooldown 已在前面的 else if (this.cooldown == 0) 分支中被设为 80。这意味着失败后要等 4 秒才重试。考虑在失败路径中重置 cooldown 为更短的值(如 20 tick),使机器在条件满足后更快响应。

6. FluidDisplayWidget 的流体纹理路径推导较脆弱

getFluidTexture() 通过 fluid.getRegisteredName().split(":")[1] 构造纹理路径,假设纹理总是 textures/block/<name>.png。对于自定义流体(如 EXP 流体)可能正确,但这不是通用方案。建议增加 fallback 或通过 FluidType 的 getStillTexture() 获取。

7. quickMoveStack 中 primer 槽转移排除玩家槽 0

AutoEnchantingTableMenu.quickMoveStack 的 primer 槽(index 38)转移使用 moveItemStackTo(item, 1, 36, true),跳过了玩家背包槽 0(热栏第一格)。可能是笔误?建议改为 0, 36, false 以覆盖所有玩家槽位。

8. FluidDisplayWidgetNullable 注解混用

该文件同时使用了 import org.jetbrains.annotations.Nullable(行级)和内联 @org.jspecify.annotations.Nullable Identifier(方法内)。建议统一为 jspecify。


✅ 看起来不错

  • 整体架构清晰:Block → BE → Menu → Screen → Renderer 链完整,遵循 26.1 的标准化模式(TagValueOutputBlockEntityRenderer<BE, RenderState>GuiGraphicsExtractor 等)
  • Capabilities 注册完整:同时注册了 Item.BLOCK(通过 WorldlyContainerWrapper)和 Fluid.BLOCK,支持管道自动输入输出
  • 附魔池设计合理:使用各护符/金属锭对应特定附魔集(蓝宝石→水亲和/深海探索者/击退、红宝石→火焰保护、绿宝石→可交易附魔等),并用到 ModEnchantments 自定义附魔
  • CreativeCrateRenderer z-fighting 修复:scale 0.5 + 坐标重算方向正确,案例 [TODO] 自动附魔台 #3629 的修复合理
  • BaseFluidHandlerHolderRenderer.FLUID_RENDER_TYPE 改为 public:让新 renderer 可复用,避免重复定义
  • power grid null 检查正确this.grid != null && !this.grid.isWorking() 防止空指针

审查清单检查

项目 状态
硬编码密钥/凭据 ✅ 无
Objects.requireNonNull 对可能为 null 的返回值 ✅ 无新增
@SuppressWarnings("deprecation") 滥用 ✅ 无新增(仅已存在的 1 处)
Lombok 循环递归 ✅ 无手动覆写 getter/setter
Blockstate 模型文件存在性 ⚠️ 见建议 1
序列化与反序列化一致性 ⚠️ 见建议 4(enchantmentSeed
onChanged / setChanged() / sendBlockUpdated ✅ 三者同时调用正确
PowerGrid null + isWorking 双保险 grid != null && !grid.isWorking()
注册 builder 链完整性 ✅ ModBlocks、ModBlockEntities、ModMenuTypes 链均以 .register() 结尾
26.1 Nullable 注解迁移 ⚠️ FluidDisplayWidget 使用旧 jetbrains 注解
EOF 换行符 ⚠️ 3 个文件缺失
PR 范围纯净度 ✅ 全部与自动附魔台相关(含 pipez 依赖,是合理的)

…ting_table

# Conflicts:
#	src/main/java/dev/dubhe/anvilcraft/block/entity/CreativeCrateBlockEntity.java
#	src/main/java/dev/dubhe/anvilcraft/data/recipe/ShapedRecipeLoader.java
#	src/main/java/dev/dubhe/anvilcraft/init/ModMenuTypes.java
#	src/main/java/dev/dubhe/anvilcraft/init/block/ModBlockEntities.java
@Gugle2308

Copy link
Copy Markdown
Collaborator

现在我已完整阅读了整个 diff,以下是审查结果。


代码审查摘要

PR #4050 — 自动附魔台(synchronize)
作者: burin-ljx → dev/26.1/1.6
文件: 36 个(19 个新增 Java 源文件,8 个生成资源,2 个资源文件,7 个修改文件)


✅ 看起来不错

  • 架构清晰:Block、BE、Menu、Screen、Renderer 各层职责分明,符合 AnvilCraft 现有模式
  • 26.1 API 正确使用TagValueOutputValueOutput/ValueInput 序列化、GuiGraphicsExtractor 渲染管线、RenderPipelines 等新 API 使用正确
  • 流体渲染:复用 BaseFluidHandlerHolderRenderer.FLUID_RENDER_TYPE + FluidRenderHelper.renderFluidBox(),与现有流体容器渲染风格一致
  • 创造板条箱 Z-fighting 修复 ([TODO] 自动附魔台 #3629):将旧的 CompoundTag 构建改为 TagValueOutput 模式,修复了物品渲染的深度冲突问题
  • TriStateButton 重构:将硬编码的 texYDiff 三态计算改为灵活的 int[] stateOffset,且 SmartBlockPlacerScreen 的对应更新也都同步了——干净的重构 ✅
  • 超限方块附魔能力TranscendiumBlock.getEnchantPowerBonus() = 10f + 注册到 #enchantment_power_provider tag,设计合理
  • 附魔筛选系统:引物(护符/锭/紫水晶)→ 过滤对应的附魔列表,玩家可自选——设计精巧
  • 电源集成IPowerConsumer 接口实现正确,OVERLOAD 状态渲染,无引物 16u/有引物 64u 的功耗差异合理
  • 网络同步AutoEnchantingTableSyncPacket 作为 IServerboundPacket,在关闭 GUI 时将选中的附魔列表同步到服务端
  • 注册完整性:ModBlocks、ModBlockEntities、ModMenuTypes、ModCapabilities、FunctionalBlocks、ShapedRecipeLoader 全部正确注册

⚠️ 警告

1. 客户端 EXP 检查使用了容量而非实际存量(AutoEnchantingTableScreen.java 第 1428 行)

// Line 1428 - 检查的是 capacity,不是实际 amount
totalLevel * 400 <= this.menu.getBlockEntity().getFluidHandler().getCapacityAsInt(0, FluidResource.EMPTY)

客户端 UI 中将费用与 capacity(32,000) 比较而非 实际 fluid amount。这意味着玩家在 EXP 不足时仍能选中附魔,UI 不会阻止超过当前存量的选择。服务端 serverTick() 第 497 行有正确的 fluidHandler.getAmountAsInt(0) 检查,所以实际运行不会超出——但玩家体验上会产生误导(选中后屏幕上显示附魔预览,但附魔台不运作也不报错)。

建议: 将第 1428 行的 getCapacityAsInt 改为 getAmountAsInt,与第 497 行服务端逻辑保持一致。

2. shouldCloseOnEsc()onClose() 双重发送同步包

AutoEnchantingTableScreen.java 中,按 ESC 时:

  1. shouldCloseOnEsc()(第 1560 行)发送 AutoEnchantingTableSyncPacket
  2. 返回 trueonClose()(第 1578 行)再次发送相同的包

虽然不产生功能错误(第二次是幂等操作),但会产生一次多余的网络包发送。

建议:onClose() 中判断是否需要发送,或将逻辑全部移到 onClose()shouldCloseOnEsc() 只返回 true

3. 生成资源文件末尾缺失换行符

src/generated/resources/assets/anvilcraft/items/auto_enchanting_table.json
src/generated/resources/data/anvilcraft/advancement/recipes/misc/auto_enchanting_table.json
src/generated/resources/data/anvilcraft/loot_table/blocks/auto_enchanting_table.json
src/generated/resources/data/anvilcraft/recipe/auto_enchanting_table.json
src/main/resources/assets/anvilcraft/blockstates/auto_enchanting_table.json

共计 5 个 JSON 文件末尾缺少换行符(\ No newline at end of file)。这是数据生成器的常见问题,虽然不直接影响运行时行为,但建议修复以保持 diff 整洁。


💡 建议

4. grid 字段空值检查(AutoEnchantingTableBlockEntity.java 第 417 行)

PowerGrid grid 字段通过 @Getter @Setter 注入。serverTick() 中有 null 检查:

if (this.grid == null) {
    level.setBlockAndUpdate(..., OVERLOAD, true);
    return;
}

这是安全的。但建议在 onLoad() 中也加入 grid 的初始化检查或日志,因为放置/加载时序可能导致 grid 在首次 tick 时尚未被赋值——目前的静默 OVERLOAD=true 回退虽然不会崩溃,但玩家可能困惑为什么附魔台一直显示过载。

5. 默认附魔模式成本计算(serverTick 第 444-471 行)

无引物模式下:exp = clamp(bookshelf * 400, 0, 6000),然后消耗的也是该值。15 个书架 = 6000mb ≈ 7.5 级经验。这个单价偏高了——考虑到需要流体管道输入 EXP、每个操作间隔 80 tick(4 秒),大容量 EXP 存储是必须的。可考虑将系数从 400 适当下调(如 200),或添加配置项。

6. getFluidTexture() 的 AtomicReference 使用(FluidDisplayWidget.java 第 1116-1125 行)

AtomicReference<Identifier> texture = new AtomicReference<>();
// ...
holder.ifPresent((fluid) -> {
    texture.set(...);
});
return texture.get();

在单线程(客户端渲染线程)中使用 AtomicReference 没有实际意义。可简化为直接使用一个普通的 @Nullable Identifier 局部变量 + lambda 内赋值。但在 26.1 的 lambda effectively final 约束下,使用 AtomicReference 是目前较简洁的写法,非阻塞性问题。


🔴 未发现关键问题

  • 没有 SQL 注入/XSS 等安全问题
  • 没有硬编码密钥或凭据
  • 没有 NPE 隐患(grid 字段有 null 守卫)
  • 序列化路径(saveAdditional/loadAdditional/getUpdateTag)一致
  • 注册删除污染:无
  • 构造器链完整性:无构造器循环依赖
  • 行为变化检测:附魔台是全新的方块,不会破坏已有行为

结论

状态:评论(无阻塞性问题)

PR 实现了完整的自动附魔台功能,包括无引物(随机附魔)和有引物(自选附魔)双模式,代码质量良好,正确使用了 26.1 API 和 AnvilCraft 的电源/流体/管道系统。创造板条箱的 z-fighting 修复干净利落。

建议在后续迭代中处理上述警告(客户端 capacity vs amount 检查、双重发包、文件末尾换行符)。PR 整体可以合并。

@Gugle2308

Copy link
Copy Markdown
Collaborator

现在我已经完整审阅了整个 PR。以下是审查结果:


代码审查摘要 — PR #4050: 自动附魔台

结论:请求更改(1 个关键问题,2 个警告)

作者: burin-ljx | 分支: auto_enchanting_tabledev/26.1/1.6 | 文件: 36 个(19 个新增,17 个修改)


🔴 关键

1. 客户端与服务器容量校验不一致 — 客户端的预选限制过于宽松

AutoEnchantingTableScreen.java(第 1428 行)的按钮回调中,校验使用 getCapacityAsInt()(最大容量 32000)来限制可选附魔等级总数:

totalLevel * 400 <= this.menu.getBlockEntity().getFluidHandler().getCapacityAsInt(0, FluidResource.EMPTY)

AutoEnchantingTableBlockEntity.serverTick()(第 497 行)使用 getAmountAsInt()(当前实际存储量):

totalLevel * 400 > this.fluidHandler.getAmountAsInt(0)

影响: 当存储经验液体不足时,客户端仍允许玩家选择大量附魔(显示为可选),但由于服务器校验会失败,附魔处理(引物模式)将静默不执行——无任何反馈告诉玩家原因。玩家会看到按钮可选但机器不工作,体验令人困惑。

建议: 将客户端校验也改为检查 getAmountAsInt(0),或至少在经验不足时显示能消耗的视觉反馈(而非仅依赖 errorCooldown 闪烁)。


⚠️ 警告

1. AutoEnchantingTableContainer.java — 死代码,未被任何地方引用

新增文件 AutoEnchantingTableContainer.java(38行)定义了继承 SimpleContainer 实现 WorldlyContainer 的类,但根据 diff 搜索没有任何代码实例化或引用该类。BlockEntity 直接实现了 WorldlyContainer 接口,因此该容器类完全未使用。

建议: 删除此文件或确认未来用途后添加引用。

2. MutableValue.setValue() 潜在 NPE

MutableValue.java 第 14 行:

public void setValue(T value) {
    if (!this.value.equals(value)) {

this.valuenull 时,.equals() 调用会抛 NPE。当前 AutoEnchantingTableRenderStateamount 初始化为 new MutableValue<>(0) 因此安全,但该工具类标记为 @Getter 公开并位于 util 包中,未来在其他场景复用时有风险。

建议: 增加 null 守卫:if (this.value == null || !this.value.equals(value))

3. auto_enchanting_table.json blockstate 缺少末尾换行符

src/main/resources/assets/anvilcraft/blockstates/auto_enchanting_table.json 末尾缺少换行符(\ No newline at end of file)。其余 5 个缺少的文件均为生成资源(数据生成器输出)— 这是数据生成器的普遍问题,建议在数据生成器中修复。

建议: 为手动编写的 blockstate JSON 添加末尾换行。


💡 建议

1. 输入/输出槽完全不可通过 GUI 操作

Slot 0(输入)和 Slot 1(输出)的 mayPlacemayPickup 均返回 false,且 quickMoveStack 对这俩槽位返回 ItemStack.EMPTY。这意味着玩家无法通过 GUI 手动放入或取出物品——只能通过管道/漏斗系统交互。而引物槽(Slot 2)支持正常交互。

这是一个明确的设计选择,但建议:

  • 输出槽至少允许玩家手工取出
  • 或在 Slot 上方加注释说明

2. finishItem 预览在输入物品变化时不刷新

屏幕中 finishItem 预览(显示最终附魔结果)仅在打开屏幕和点击附魔按钮时更新。如果输入槽(Slot 0)通过管道系统在 GUI 打开期间被放入新物品,预览不会自动刷新。

建议:containerTick() 或通过 listener 机制监测输入物品变化时刷新 finishItem

3. 非引物模式的随机附魔可能无提示地"跳过"

在无引物模式下(AutoEnchantingTableBlockEntity.serverTick() 第 445-471 行),如果 getCostAndEnchant() 返回空数组或经验不足,都会直接 return——机器没有任何音效或粒子反馈告诉玩家"条件不满足"。

建议: 在无引物模式下缺少条件时,可以考虑在 OVERLOAD 或额外状态上体现出错。

4. getEnchantmentList() 的性能考虑

AutoEnchantingTableMenu.getEnchantmentList() 每次引物槽变化时都重建完整的附魔列表(包括所有护符类型的独立列表构建)。由于 slotsChanged 在每次容器变更时调用,如果玩家频繁在引物槽中取放物品,会有轻微但可感知的列表重建开销。

建议: 可考虑缓存按引物物品分类的 Map,引物类型不变时直接返回缓存。


✅ 看起来不错

  • 26.1 API 适配完整:所有新文件使用 jspecify @NullableTagValueOutput 序列化,双参数 BlockEntityRenderer<BE, RenderState>ValueOutput/ValueInput 模式
  • 注册链完整:Block → BlockEntity → Menu → Screen → Capabilities → Tags → Recipe → Creative Tab 全部正确注册
  • Multiblock 附魔台书架检测:使用了和原版附魔台相同的 BOOKSHELF_OFFSETS+isValidBookShelf 模式
  • 引物/护符系统有创意:不同类型的引物材料(紫水晶、皇家钢、护符等)对应不同附魔池,与模组现有物品体系深度结合
  • Fluid Display Widget 通用设计:可作为其他需要液体显示的 GUI 复用
  • TriStateButton 重构正确stateOffset 数组替换 texYDiffOnPress 接口提取,向后兼容(SmartBlockPlacer 等旧调用点均已更新)
  • CreativeCrateBlockEntity getUpdateTag 迁移到 TagValueOutput
  • TranscendiumBlock 的 getEnchantPowerBonus 提供 15.0f 附魔能力,增加到 ENCHANTMENT_POWER_PROVIDER 标签 ✅
  • 旁观者守卫存在AutoEnchantingTableBlock.use() 第 915 行检查 GameType.SPECTATOR) ✅
  • TagValueOutput 用法正确createWithContext(...) + saveAdditional(output) + buildResult() 模式在 getUpdateTag 中正确使用,不会丢失父类数据

@Gugle2308

Copy link
Copy Markdown
Collaborator

I now have a comprehensive understanding of the entire PR. Here is my review.


代码审查摘要

PR #4050Auto Enchanting Table 自动附魔台
作者: burin-ljx
分支: auto_enchanting_tabledev/26.1/1.6
文件: 35 个(18 新增,17 修改,0 删除) | 新增行: 2284 | 删除行: 72

🔴 关键

  • src/main/resources/assets/anvilcraft/blockstates/auto_enchanting_table.json缺少方块模型 JSON 文件。
    blockstate 引用了两个模型变体:

    • anvilcraft:block/auto_enchanting_table(overload=false)
    • anvilcraft:block/auto_enchanting_table_overload(overload=true)

    但 PR 中未包含这两个模型 JSON 文件(应位于 src/main/resources/assets/anvilcraft/models/block/)。items JSON(auto_enchanting_table.json)也引用了 auto_enchanting_table 模型。缺少模型文件会导致方块在游戏中渲染为缺失纹理/不可见。请补充两个模型文件,或确认是否通过其他机制(如数据生成器)自动生成。

⚠️ 警告

  • AutoEnchantingTableBlockEntity.java:520loadAdditionalisOpenMenu 字段未赋值。

    // 当前代码(第 520 行):
    input.getBooleanOr("openMenu", false);
    
    // 应改为:
    this.isOpenMenu = input.getBooleanOr("openMenu", false);

    saveAdditional 写了这个字段但 loadAdditional 丢弃了返回值。虽然 isOpenMenu 是运行时 UI 标志位(默认 false),世界重载后默认 false 是正确的安全行为,但保存/加载的不对称性需要修复或删除保存。

  • AutoEnchantingTableBlockEntity.java:93enchantmentSeed 字段未持久化。

    enchantmentSeed(第 93 行声明)决定了无引物模式下可用的附魔选项。原版 EnchantmentTableBlockEntity 会保存此种子以保证跨重载一致性。当前代码中该字段未出现在 saveAdditional/loadAdditional 中,世界重载后附魔选项会重新随机。建议将 enchantmentSeed 也序列化。

💡 建议

  • AutoEnchantingTableScreen.java:344,377shouldCloseOnEsc()onClose() 都发送了相同的 AutoEnchantingTableSyncPacket
    关闭 GUI 时两条路径都会触发,导致数据包重复发送。虽然服务器端 handleOnServer 幂等(clear + addAll),建议在 onClose() 中发送,shouldCloseOnEsc() 中仅返回 true 不做额外发送。

  • MutableValue.setValue() 的 null 安全性 — 当泛型类型 T 为 null 初始值或 null 参数时,this.value.equals(value) 会 NPE。当前 RenderState 中初始化为 new MutableValue<>(0) 是安全的,但作为通用工具类建议添加 null 守卫。

  • AutoEnchantingTableMenu.javaquickMoveStack 快速移动不支持从 prologue 槽(slot 38)向物品栏移动多个物品。当前逻辑正确(仅 1 格,最大栈 1),但移出时未尝试合并到已有堆叠,可优化。

✅ 看起来不错

  • 整体架构: Block → BlockEntity → Menu → Screen → Renderer → Network Packet 分层清晰,职责单一
  • 26.1 API 一致性: 使用 ValueOutput/ValueInput 序列化、GuiGraphicsExtractor/RenderPipelines 渲染、Registrum 注册模式,与代码库风格统一
  • 电力系统集成: 正确实现 IPowerConsumerOVERLOAD 状态随电网工作状态切换,getInputPower() 根据是否有引物返回 16/64
  • 容器访问控制: WorldlyContainer 实现正确——管道只能通过 face 访问 input(slot 0)和 output(slot 1),prologue 槽(slot 2)仅玩家可操作
  • 流体处理: FluidStacksResourceHandler 只接受 EXP_FLUID,使用 Transaction 事务提交,onPlayerUse 中通过 FluidUtil.interactWithFluidHandler 让玩家可直接填充
  • 交互顺序: useItemOn → BE 流体交互(优先处理手持桶/瓶),use → 打开菜单,互不干扰
  • 双模式设计: 无引物模式(随机附魔=原版行为)和有引物模式(选择指定附魔+宝石过滤),合理
  • 附魔清单映射: 蓝宝石/红宝石等护符→特定附魔类型的设计与模组其他护符系统一致,扩展性好
  • 翻译完整性: en_us.jsonen_ud.json 四个 key 全部同步,含翻转文本的 en_ud
  • GUI 功能完整: 搜索过滤、滚动列表、滚动条拖动、结果预览、流体显示、越界提示、右键清空搜索框
  • getUpdateTag 在 CreativeCrateBlockEntity 中的迁移: 从 RegistryOps + tag.store() 迁移到 TagValueOutput,与 26.1 最佳实践一致
  • TranscendiumBlock 作为书架: getEnchantPowerBonus 返回 10 + 注册到 ENCHANTMENT_POWER_PROVIDER tag,逻辑完整
  • 配方、战利品表、进度 全部由数据生成器生成,格式正确
  • TriStateButton 重构: 从固定 texYDiff/3 改为 stateOffset[] 数组,增加了灵活性和可复用性,SmartBlockPlacerScreen 同步更新

结论:请求更改(2 个严重/警告问题需修复:缺少模型文件 + loadAdditional 返回值未赋值)

@alpha-hhh

Copy link
Copy Markdown
Contributor

1、
image
附魔完成后物品仍渲染,直到再次点入界面
2、
d9652f24e14575968e29fd1f55496ce6
a1397973456844177c30ae8389107996

看不到tooltip
3、发射器只能取出经验流体而不能存入
4、
image
会附出冲突附魔
image
暂时有以上bug

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[TODO] 自动附魔台

4 participants