Skip to content

Implement features of the blocks for the storage system 添加存储系统方块的功能#3795

Draft
QiuShui1012 wants to merge 8 commits into
Anvil-Dev:dev/26.1/1.6from
QiuShui1012:storage/26.1/1.6
Draft

Implement features of the blocks for the storage system 添加存储系统方块的功能#3795
QiuShui1012 wants to merge 8 commits into
Anvil-Dev:dev/26.1/1.6from
QiuShui1012:storage/26.1/1.6

Conversation

@QiuShui1012

@QiuShui1012 QiuShui1012 commented Jun 20, 2026

Copy link
Copy Markdown
Collaborator
  • resolved [TODO] 仓储系统通用部分 #3582
    • 主界面
      • 1: 标题
      • 2: 内容物显示
      • 2.1: 数量字体
      • 3: 内容物滚动条
      • 4: 玩家物品栏
      • 5: “放入”按钮
      • 6: “取出”按钮
      • 7: 搜索栏
      • 8: 搜索栏状态按钮
      • 9: 排序方式按钮
      • 10: 排序正序倒序按钮
      • 11: 是否堆叠显示不同nbt物品按钮
      • 12: 类别按钮
      • 13: 类别设置按钮
      • 14: 类别滚动条
    • 类别设置
      • 玩家存储
      • 1: 类别按钮
      • 2: 添加自定义类别
      • 3: 备选类别
      • 3.1: 预制类别
      • 4: 确认按钮
      • 5: 取消按钮
      • 快捷键退出行为配置
  • resolved [TODO] 板条箱和大型板条箱 #3583
    • 小板条箱
      • 合成配方
      • 物品管道兼容
      • 3x3x3搜索
    • 大板条箱
      • 合成配方
      • 物品管道兼容
      • 快捷替换小板条箱
  • resolved [TODO] 潜影集装箱 #3584
    • 潜影集装箱
      • 制作
      • 升级
      • 切换模型
  • 附赠 超维存储站 的 界面 与 存储

@Anvil-Dev Anvil-Dev deleted a comment from Gugle2308 Jun 20, 2026
@Anvil-Dev Anvil-Dev deleted a comment from Gugle2308 Jun 20, 2026
@Anvil-Dev Anvil-Dev deleted a comment from Gugle2308 Jun 20, 2026
@Anvil-Dev Anvil-Dev deleted a comment from Gugle2308 Jun 20, 2026
@anvil-craft

Copy link
Copy Markdown

现在我对所有变更有了全面了解。以下是完整的审查报告。


代码审查摘要

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

🔴 关键

  • UnstackableCategory.java:38 — 复制粘贴错误:test() 方法检查 stack.getItem() instanceof BlockItem,与 BlockCategory.test() 完全相同。应检查物品是否不可堆叠(stack.getMaxStackSize() <= 1)。这会直接导致"不可堆叠物品"分类无法正确筛选。
    // 当前(错误):
    public boolean test(UnlimitedItemStack stack) {
        return stack.getItem() instanceof BlockItem;
    }
    // 应为:
    public boolean test(UnlimitedItemStack stack) {
        return stack.getMaxStackSize() <= 1;
    }

⚠️ 警告

  • ShulkerContainerBlockEntity.java:11 — 仍保留 // TODO: 实现潜影集装箱功能 注释,表明潜影集装箱功能尚未实现。PR 描述中 [TODO] 潜影集装箱 #3584 的对应项也标为未完成。

  • StorageBlockEntity.setId() 逻辑缺陷 — 方法先调用 sendBlockUpdated,再检查 ID 是否已存在。已设 ID 后重新调用会无意义地发送方块更新。建议将更新调用放到赋值之后:

    public void setId(UUID id) {
        if (this.id != null) return;
        this.id = id;
        if (this.level != null) {
            this.level.sendBlockUpdated(this.getBlockPos(), getBlockState(), getBlockState(), Block.UPDATE_ALL);
        }
    }
  • hammer_removable.json 末尾无换行符 — 标签文件末尾 \ No newline at end of file,虽不破坏功能,但表明生成器/编辑器配置存在问题。共 3 处无换行符文件。

💡 建议

  • StorageMenuState 全局静态 Map 无主动清理机制STATES 表仅在服务器/客户端停止时通过 ServerLifecycleEventListenerClientEventListener 清理。若单个会话中玩家打开/关闭大量不同 UUID 的存储容器,STATES 会持续膨胀直到服务器重启。建议添加关闭 GUI 时清理对应状态的机制。

  • 物品管道兼容性需验证 — PR 描述中"大板条箱的物品管道兼容"标为未完成。未在 CrateBlockLargeCrateBlock 中看到 IHammerRemovableisPipe/isInventory 接口实现。需确认已有方块在注册的 ModCapabilities 中注册了 Item 能力,但方块本身未实现管道/流体相关接口。建议检查 PipeNodeBlock.evaluateNeighbor()PipeBlockItem.tryConnectAdjacent() 是否已更新为新存储方块做兼容。

  • foods_and_drinksfood_and_drink 重命名 — 翻译键变更会破坏所有使用旧键的数据包(如数据包、函数、已放置的方块 NBT 引用)。请确认没有任何现有内容引用旧键。

  • playerWillDestroy 在各方块中重复CrateBlockLargeCrateBlockShulkerContainerBlockHyperdimensionStorageStationBlock 四个方块都写了几乎相同的 playerWillDestroy 方法(检查 BE 类型、掉落物品)。可考虑提取到 StorageBlockEntity 的静态工具方法。

✅ 看起来不错

  • 存储系统架构设计清晰BaseStorageCrateStorage/LargeCrateStorage/ShulkerContainerStorage/HyperdimensionStorage 的继承体系合理,StorageType 枚举 + codec dispatch 的序列化方案优秀。
  • 网络协议设计良好StoragePackets 中分 SyncSlots(C→S)、Sync2CFull(S→C)等不同数据包,职责明确。使用 REGISTRY_FRIENDLY + Unpooled.buffer 分段传输大列表设计合理。
  • IEnergyHandlerHolder 接口抽象 — 将 FE 收集器、能量转换器中散落的 getEnergyStorage 统一为 getEnergyHandler,减少重复代码。
  • 语言文件完整更新en_us.jsonen_ud.json 均已同步添加所有新键,en_ud 的颠倒文本生成正确。
  • 数据组件注册规范StorageRef 同时实现了 TooltipProvider,在工具提示中显示 Storage ID,方便调试。persistent + networkSynchronized 配置正确。
  • 配置文件可扩展storageRecoverMaxSize 配置项已添加,与现有 multiphaseRecoverMaxSize 风格一致。
  • applyImplicitComponents 正确绑定 — 方块实体从 Item 的 StorageRef component 恢复 UUID,放置时自动建立关联。

@anvil-craft

Copy link
Copy Markdown

现在我已经掌握足够信息,可以提交完整的审查了。


代码审查摘要

结论:请求更改(2 个严重 bug,1 个警告,多条建议)

PR 范围: 存储系统方块功能实现 + 包结构重构。90 个文件变更(84 Java,5 生成资源,1 资源文件)。


🔴 严重

1. StoragePackets.FullSyncer.sync() — 数组拷贝越界崩溃(L381)

Sync2CFull[] array = this.packets.toArray(new Sync2CFull[0]);
int length = array.length - 1;
Sync2CFull[] dest = new Sync2CFull[length];
System.arraycopy(array, 0, dest, 1, length);  // ← BUG
PacketDistributor.sendToPlayer(this.player, this.packets.getFirst(), dest);

System.arraycopy 的起始偏移是 destPos=1 但从 srcPos=0 拷贝 length 个元素。当 array.length ≥ 2 时,dest[array.length-1] 一定越界(dest 长度为 array.length-1,索引只到 array.length-2)。

修复: System.arraycopy(array, 1, dest, 0, length) — 跳过第一个元素(已作为主 payload 发送),将剩余元素放入 dest[0..n-2]

2. StorageMenuState.sync(int head, List<UnlimitedItemStack> stacks) — ArrayList 索引越界(L57–63)

public void sync(int head, List<UnlimitedItemStack> stacks) {
    this.mapping.ensureCapacity(head);
    int size = stacks.size();
    for (int i = head; i < head + size; i++) {
        if (this.mapping.get(i) == null) {       // i ≥ mapping.size() → IOOBE
            this.mapping.add(i, stacks.get(i));  // i > mapping.size() → IOOBE
        } else {
            this.mapping.set(i, stacks.get(i));  // i ≥ mapping.size() → IOOBE
        }
    }
}

ArrayList.ensureCapacity() 不改变 size()(仅预分配内部数组)。当客户端首次接收分块同步时,mapping 为空列表(size=0),任何 ≥0 的索引都会抛 IndexOutOfBoundsException

同样问题也存在于 sync(Map) 方法(L68–72):this.mapping.set(index, ...)index ≥ mapping.size() 时抛 IOOBE。

修复(两个方法均适用): 写入前显式填充 ArrayList 到所需大小:

while (this.mapping.size() <= index) {
    this.mapping.add(UnlimitedItemStack.EMPTY);
}

⚠️ 警告

3. StorageMenu.quickMoveStack 为空实现

@Override
public ItemStack quickMoveStack(Player player, int slotIndex) {
    return this.getSlot(slotIndex).getItem();  // ← 直接返回,未移动物品
}

Shift+点击物品不会发生任何传输。这是功能性的缺失,对应 Task #3582 清单中未标 [x] 的项目。需要实现物品槽逻辑或至少在 Sprint 完成前添加 TODO 注释。

4. StorageScreen.reorder 为空方法

protected void reorder() {
}

点击类别按钮时调用,但分类排序逻辑尚未实现。


💡 建议

5. 包结构重构中的生成资源删除确认(foods_and_drinks.json)

src/generated/resources/data/anvilcraft/anvilcraft/category/foods_and_drinks.json 被删除。请确认此数据不会影响已部署的实例读取旧存档。建议在 StoragesPlayerSettings 初始化时添加向前兼容性处理,避免旧存档中引用了已删除的类别类型。

6. StoragePackets.Sync2CFull.encode() — 非 FullSyncer 调用的防护可能不够清晰

if (data.buf == null) {
    throw new UnsupportedOperationException("Use the FullSyncer");
}

编码路径仅在 FullSyncer 内部使用,但构造函数是 private 的,理论上不会从外部调用。建议添加 @ApiStatus.Internal 注解提高可读性。

7. CrateBlock 实现了 EntityBlock 但没有 @OverridegetTicker

小板条箱是单方块(非多方块),如果未来需要 tick(例如 3x3x3 搜索功能),需要添加 ticker 注册。目前 OK,但建议标记此注意事项。

8. StorageBlockEntity.setId(UUID)id 不可变保护

public void setId(UUID id) {
    if (this.id != null) {
        return;  // 首次设置后不可变更
    }
    this.id = id;
}

此设计确保 UUID 只在方块放置时从 StorageRef 组件解析,之后不可变更。但在 loadAdditionalonDataPacket 中直接设置 this.id = id 绕过了此保护。建议统一通过 setId 方法。

9. 翻译资源更新确认(CategoryLang / ScreenLang / ToolPropertyLang)

新的语言键已添加到数据生成器:

  • category.anvilcraft.block, category.anvilcraft.unstackable, category.anvilcraft.food_and_drink(注意:旧的 foods_and_drinks key 被替换为 food_and_drink
  • tooltip.anvilcraft.property.storage.idstorage.id.null
  • 存储界面翻译键(screen.anvilcraft.storage.*

en_us.jsonen_ud.json 均已更新,未发现缺失翻译。

10. 新文件的 @NullMarked 标注一致性好

所有新包均使用了 package-info.java 中的 @NullMarked 注解(storage 包、category/store 包、setting 包、setting/mode 包),代码质量高。


✅ 看起来不错

  • 存储系统架构设计清晰BaseStorage → 子类(带有 Registry dispatcher 的 CODEC)、Storages SavedData 管理器、UUID 标识体系
  • 网络同步设计稳健FullSyncer 实现分块同步(512KB 限制),支持超大数据传输
  • 数据组件化StorageRef 组件使物品箱可携带 UUID 引用,放置时自动恢复存储关联;collectImplicitComponents / applyImplicitComponents 实现正确
  • 客户端/服务端状态分离StorageMenuState 的双端状态管理含客户端调用安全防护
  • playerWillDestroy 保护:所有存储方块均正确保存组件到掉落物物品
  • 锤子可破坏标签更新hammer_removable.json 将掉落物箱子添加至可破坏列表
  • ExtraEnchantmentsPredicate 重构:旧的数据组件谓词 merciless_enchantments / disabled_enchantments(对象型)迁移为 merciless_enchantment / disabled_enchantment(列表型),类型系统更加干净
  • ShulkerContainerBlockItem\ No newline 已修复
  • ModCapabilities 重构:能量物品注册抽象为 energy(int) 辅助方法,减少重复代码
  • 类别系统抽象良好ICategory 接口、CategoryModeBlockCategory/UnstackableCategory 等预制类别

…1/1.6

# Conflicts:
#	src/generated/resources/assets/anvilcraft/lang/en_ud.json
#	src/generated/resources/assets/anvilcraft/lang/en_us.json
#	src/main/java/dev/dubhe/anvilcraft/data/lang/ScreenLang.java
#	src/main/java/dev/dubhe/anvilcraft/init/ModMenuTypes.java
@anvil-craft

Copy link
Copy Markdown

⚠️ Dangerous command requires approval:

# 验证 ShulkerContainerBlockItem 的变更
echo "=== ShulkerContainerBlockItem diff ==="
sed -n '/^diff --git.*ShulkerContainerBlockItem.java/,/^diff --git /p' /tmp/pr3795.diff

echo ""
echo "=== 验证 Increment...

Reason: Security scan — [MEDIUM] Variation selector characters detected: Content contains Unicode variation selectors (VS1-256). These are commonly used in emoji sequences but may indicate steganographic encoding or obfuscation

Reply /approve to execute, /approve session to approve this pattern for the session, /approve always to approve permanently, or /deny to cancel.

@QiuShui1012

Copy link
Copy Markdown
Collaborator Author

⚠️ Dangerous command requires approval:

# 验证 ShulkerContainerBlockItem 的变更
echo "=== ShulkerContainerBlockItem diff ==="
sed -n '/^diff --git.*ShulkerContainerBlockItem.java/,/^diff --git /p' /tmp/pr3795.diff

echo ""
echo "=== 验证 Increment...

Reason: Security scan — [MEDIUM] Variation selector characters detected: Content contains Unicode variation selectors (VS1-256). These are commonly used in emoji sequences but may indicate steganographic encoding or obfuscation

Reply /approve to execute, /approve session to approve this pattern for the session, /approve always to approve permanently, or /deny to cancel.

/approve

@anvil-craft

Copy link
Copy Markdown

我已经全面审阅了此 PR 的 94 个文件变更(5217 行 diff / 2208 行 Java 逻辑)。以下是审查意见。


代码审查摘要

结论:请求更改(1 个严重 Bug,4 个建议)

🔴 严重

1. IncrementalSyncer.sync() 数组拷贝越界 — StoragePackets.java:~380

System.arraycopy 的参数错误:

// 当前代码(有问题):
Sync2CIncremental[] dest = new Sync2CIncremental[length];
System.arraycopy(array, 0, dest, 1, length);  // ← dest 索引越界

// FullSyncer 的正确写法(可借鉴):
System.arraycopy(array, 1, dest, 0, length);

当增量同步产生 2 个或以上 数据包时,dest 大小为 array.length - 1,但 destPos=1, length=length 会写入 dest[1..length],即超出数组上界。会导致 ArrayIndexOutOfBoundsException,使大容量存储的增量同步完全崩溃。

建议: 将参数修正为 System.arraycopy(array, 1, dest, 0, length),与 FullSyncer 保持一致。


⚠️ 警告

无阻塞性问题。


💡 建议

1. StorageScreen.reorder() 为空方法

文件: StorageScreen.java:102

reorder() 方法体完全为空,但已被注册为 CategoryList 的回调(_ -> this.reorder())。点击分类按钮时不会有任何响应。建议要么实现排序逻辑,要么在回调中使用 noop 提高可读性。

2. StorageMenu 没有构建自定义槽位

文件: StorageMenu.java

quickMoveStack() 直接返回 this.getSlot(slotIndex).getItem(),未调用 doClick() 或父类的移动逻辑。当前没有添加任何 SlotaddSlot() 未调用),因此 Transfer 快捷键完全不可用。这对应 issue #3582 中的"玩家物品栏、取出/放入按钮"等未完成项,建议在菜单中添加槽位布局。

3. ShulkerContainerBlockItem 被替换为 FlexibleMultiPartBlockItem

文件: ModBlocks.java:1123-1126

潜影集装箱的 BlockItem 从专门的 ShulkerContainerBlockItem 改为通用的 FlexibleMultiPartBlockItem。虽然旧类只继承自 FlexibleMultiPartBlockItem 且没有额外逻辑,但若未来需要为潜影集装箱添加特殊行为(如模型切换、升级逻辑),建议保留专用 BlockItem 作为扩展点。

4. 生成资源文件缺少换行符(6 处)

en_ud.json 和分类数据文件中存在 \ No newline at end of file。对于生成资源通常不影响运行时,但建议检查数据生成器是否已正确配置末尾换行。


✅ 架构评价

整体架构设计合理,各层职责清晰:

层级 设计 评价
存储类型 StorageType 枚举 + BaseStorage 抽象 + 多态 Codec 分发 ✅ 清晰,易于扩展新的存储类型
持久化 Storages 作为 SavedData 全局单例,按 UUID 索引 ✅ 标准模式
网络同步 FullSyncer + IncrementalSyncer 双模式,分包大小控制(512KB) ✅ 设计周到(除 bug 外)
UI 状态 StorageMenuState 双端同步,changes 增量记录 ✅ 避免了全量重新同步
类别系统 ICategory 接口 + Registry 注册 + PlayerSetting 持久化 ✅ 灵活,支持自定义类别
Capability 存储方块统一委托到 Storages ✅ 避免重复的 item handler 实例

数据容量对比:

类型 槽位数
小板条箱 (Crate) 2,048
大板条箱 (Large Crate) 65,536
潜影集装箱 65,536 × 65,536 (双层)
超维存储站 65,536

已完成 checklist 项: 小板条箱/大板条箱的合成配方和管道兼容、类别系统基础框架、玩家设置持久化、超维存储站 UI 和存储逻辑。未完成项(UI 细节、潜影集装箱功能等)均在 issue #3582/#3583/#3584 中明确标记为 [ ],符合预期。


由 Hermes Agent 审查

@anvil-craft

Copy link
Copy Markdown

⚠️ Dangerous command requires approval:

echo "=== 零变更文件检测 ===" && for f in $(grep "^diff --git.*src/main/java/" /tmp/pr3795-java.diff | sed 's|.* b/||'); do f_esc=$(echo "$f" | sed 's|/|\\\\/|g'); hunks=$(sed -n "/^diff --git.*$f_esc/,/^dif...

Reason: Security scan — [MEDIUM] Variation selector characters detected: Content contains Unicode variation selectors (VS1-256). These are commonly used in emoji sequences but may indicate steganographic encoding or obfuscation

Reply /approve to execute, /approve session to approve this pattern for the session, /approve always to approve permanently, or /deny to cancel.

@QiuShui1012

Copy link
Copy Markdown
Collaborator Author

/approve always

@anvil-craft

Copy link
Copy Markdown

我已经完成了对 PR #3795 的详细审查。以下是完整分析:


代码审查摘要

结论:请求更改(发现 4 个 Bug,其中 3 个可能导致运行时崩溃或数据丢失)


🔴 关键

1. FullSyncer.sync() 循环越界 — 最后一项索引被跳过

  • 文件: StoragePackets.java (FullSyncer.sync())
  • 行: for (int i = 0; i < max; i++)
  • 问题: max 取的是 changes.keySet() 的最大值,但循环条件 i < max 排除掉了最后一项。假设 changes = {0: item1, 5: item2}max = 5,循环仅遍历 0~4,索引 5 的 item2 被完全遗漏。极端情况若只有单一项 {0: item}max = 0,循环一次都不执行。
  • 修复: for (int i = 0; i <= max; i++)

2. FullSyncer.sync()stack == nullstack.isEmpty() 触发 NPE

  • 文件: StoragePackets.java (FullSyncer.sync())
  • 问题: changes 是一个 HashMap,只包含有变更的 slot。当 i 遍历到 changes 中不存在的 key(间隔空缺)时,stacknull。外层 if 通过 stack == null 进入,但随后立即执行 if (stack.isEmpty())NullPointerException,导致整个同步流程崩溃。
  • 修复: if (stack != null && stack.isEmpty()) 或直接 if (stack != null)

3. StorageMenuState.sync(int head, List stacks) — 多包同步时 IndexOutOfBoundsException

  • 文件: StorageMenuState.java
  • 行: while (this.mapping.size() <= head) 仅保证 size > head,但后续循环需要 size > head + stacks.size() - 1。当 stacks.size() > 1 时,mapping.get(i) 对较高 i 抛出 IndexOutOfBoundsException
  • 复现: server 发送了多包数据,head = 5, stacks.size() = 3,mapping 展开到 size=6 后尝试 get(6)get(7) → 崩溃。
  • 修复: while (this.mapping.size() < head + stacks.size())

⚠️ 警告

4. StorageScreen.reorder() — 空方法残留

  • 文件: StorageScreen.java
  • 行: protected void reorder() {}
  • 问题: 方法体为空,但类别按钮的 onPress 回调引用了它。用户期望点击类别可排序,但实际无任何效果。

5. 潜影集装箱 (ShulkerContainer) 功能未实现

  • 文件: ShulkerContainerBlockEntity.java
  • 行: // TODO: 实现潜影集装箱功能
  • 问题: 代码框架已搭建(extends StorageBlockEntity、注册 CODEC、物品管道能力注册),但注释明确标记未完成。PR 描述也标注了"潜影集装箱 — 制作、升级、切换模型"均未勾选。

6. playerWillDestroy 重复逻辑 — 3 个类中完全相同

  • 文件: CrateBlock.java, HyperdimensionStorageStationBlock.java, LargeCrateBlock.java, ShulkerContainerBlock.java
  • 问题: playerWillDestroy 中掉落物品并附加 StorageRef component 的逻辑完全一致。建议提取到工具方法或基类中,避免维护 4 份相同代码。

7. 未使用字段/变量风险

  • 文件: CategorySetting.javawidgets 字段在构造函数中通过 addRenderableWidget 添加组件,但 widgets 自身在 mouseClicked 中用到了——需要确认该列表始终与屏幕的 children() 同步,否则可能出现鼠标事件未被 widget 处理的情况。

8. 旧包删除/包重构 — 兼容性风险

  • 以下类被删除:block.container.CrateBlock, block.entity.container.LargeCrateBlockEntity, block.entity.container.ShulkerContainerBlockEntity
  • 以下类同时被移动并重写:HyperdimensionStorageStationBlock, LargeCrateBlock, ShulkerContainerBlock
  • Categories 类被完全删除,功能迁移到 PlayerSetting + CategoryEntry
  • 需确认升级存档时旧数据能否被正确迁移。

💡 建议

9. initStacks()setCount() 覆盖而非累计

  • 文件: TypeLimitItemStacksResourceHandler.java
  • 行: exist.setCount(stack.count()); (反序列化循环内)
  • 说明: 反序列化时每个 stack 是原子对象,因此覆盖是正确的。但如果未来数据格式中出现重复 key,第二个会覆盖第一个的值导致数据丢失。建议添加注释说明不能出现重复 key 的前提假设,或加一份安全断言。

10. 语言文件缺失同步

  • 检查: PR 新增了大量翻译 keys(category.anvilcraft.*, tooltip.anvilcraft.property.storage.*),en_us.jsonen_ud.json 已更新,但 zh_cn/zh_tw/zh_hk 等其他语言文件不在 diff 中。建议确认这些 key 已同步翻译。

11. PipeBlockItem.tryConnectAdjacent()/evaluateNeighbor() 兼容性检查

  • 文件列表: 全 diff 中未找到对管道系统的兼容方法调用
  • 说明: PR 描述中"小板条箱:物品管道兼容 ✓",但代码中仅通过 ModCapabilities 注册了 Capabilities.Item.BLOCK。如果管道系统额外依赖 isPipe/isInventory 等独立判断方法(而非通用 Capability),则小板条箱的管道兼容可能不完整。建议检查 PipeBlockItem/PipeNodeBlock 中是否有独立的类型判断方法未更新。

12. HyperdimensionStorageStationBlockMultiPartBlockEntity 关系

  • 该 Block 继承 SimpleMultiPartBlock,其 createBlockEntity 返回 MultiPartBlockEntityplayerWillDestroy 中强制转型为 HyperdimensionStorageStationBlockEntity。实际运行逻辑通过 ModBlockEntities.HYPERDIMENSION_STORAGE_STATION 创建的是 StorageBlockEntity 的子类,需要确认 MultiPart 系统与新的 Storage BE 兼容。

13. No newline at end of file 修复

  • 文件: ShulkerContainerBlockItem.java 原有末尾无换行符,本次修复了 ✓。建议在其他生成资源中也检查此问题。

✅ 看起来不错

  • 架构设计优秀: StorageType 枚举 + BaseStorage 基类 + 各存储类型子类的模式清晰,Codec 复用性好
  • 类型安全: StorageType.find(Class)Storages.get(UUID, Class) 的双重类型校验设计严谨
  • 网络同步架构完善: FullSyncer + IncrementalSyncer 双模式设计,配合分包策略(512KB 限制)和 varint 编码,有效控制网络负载
  • 类别系统灵活: ICategory 接口 + registry-dispatch 的编解码设计,支持注册表、直接实例、HolderHolder 多种序列化形态
  • Capability 注册齐全: 四种存储方块均注册了 Capabilities.Item.BLOCK,管道/漏斗可正常交互
  • 代码规范良好: 使用 var 改进类型推断(Java 21+),注释清晰(中英双语),@Nullable/@ApiStatus.Internal 标注到位
  • PlayerSetting 设计合理: 以 record 形式将分类设置、自定义过滤、存储设置统一管理,与 PlayerSettings 单例层职责分离

核心 Bug 汇总:

# 严重度 文件 问题 影响
1 🔴 StoragePackets.java (FullSyncer) 循环越界 i < max 漏掉最后一项 存储界面打开时最后一项物品消失
2 🔴 StoragePackets.java (FullSyncer) stack == nullstack.isEmpty() NPE 服务端崩溃
3 🔴 StorageMenuState.java mapping 展开不足导致 IOOBE 客户端/服务端崩溃
4 ⚠️ StorageScreen.java reorder() 为空 类别排序无功能

@anvil-craft

Copy link
Copy Markdown

现在我有了全面的了解。让我们来组织审查。


代码审查摘要 — PR #3795: 存储系统方块功能

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

审查范围: 5261 行差异 | 95 个文件 | 87 Java 源文件 | 7 生成资源 | 1 配置文件


🔴 关键

1. StorageMenu.quickMoveStack() 为无操作空方法

文件: src/main/java/dev/dubhe/anvilcraft/inventory/StorageMenu.java:28-30

@Override
public ItemStack quickMoveStack(Player player, int slotIndex) {
    return this.getSlot(slotIndex).getItem();
}

quickMoveStack 只返回当前槽位中的物品,但不执行任何移动作业——没有将物品移到玩家物品栏,也没有处理 Slot.mayPickup / moveItemStackTo 检查。这导致玩家 Shift+点击时,客户端的物品显示会短暂闪烁但实际没有移动。在 1.21 的 NeoForge 菜单系统中,如果 quickMoveStack 不返回 ItemStack.EMPTY 表示"已全部移动完毕",且不执行 moveItemStackTo(),客户端与服务端之间的物品同步就会出现逻辑错误。

建议:quickMoveStack 中实现完整的 Shift+点击逻辑,或至少为存储槽位增加 moveItemStackTo() 调用。如果此 PR 暂不实现玩家物品栏,则需确认 StorageMenu 当前构造器中 addSlot() 的调用是否也为空。


⚠️ 警告

1. 6 个文件末尾缺少换行符

- src/generated/resources/assets/anvilcraft/lang/en_ud.json
- src/generated/resources/assets/anvilcraft/lang/en_us.json
- src/generated/resources/data/anvilcraft/anvilcraft/category/block.json
- src/generated/resources/data/anvilcraft/anvilcraft/category/minecraft.json
- src/generated/resources/data/anvilcraft/anvilcraft/category/unstackable.json
- src/main/java/dev/dubhe/anvilcraft/item/block/ShulkerContainerBlockItem.java

其中 ShulkerContainerBlockItem.java 是手写 Java 源码,缺少 EOF 换行符会导致 POSIX 兼容性问题(diff 末尾显式标记 \ No newline at end of file)。生成资源文件也建议修复数据生成器以统一追加新行。

2. ShulkerContainerBlockEntity 中的 TODO 未解决

文件: src/main/java/dev/dubhe/anvilcraft/block/entity/storage/ShulkerContainerBlockEntity.java:8

public class ShulkerContainerBlockEntity extends StorageBlockEntity { // TODO: 实现潜影集装箱功能

PR 描述中 #3584 的 "潜影集装箱" 条目除模型切换外全部未勾选。目前该 BlockEntity 中的 collectComponents() 等方法继承自 StorageBlockEntity,但潜影集装箱的特殊升级/制作行为未实现。

3. LargeCrateBlock / ShulkerContainerBlock / HyperdimensionStorageStationBlock 三处的 playerWillDestroy 逻辑存在微小差异

文件: ShulkerContainerBlock.java, LargeCrateBlock.java, HyperdimensionStorageStationBlock.java

三个方块都实现了相同的 drop-on-destroy 保护模式:

if (!level.isClientSide() && player.preventsBlockDrops() && be.getId() != null) {

小板条箱 (CrateBlock)playerWillDestroy 在哪里?对比 Patch-Diff 发现 CrateBlock.java(storage 包下)似乎是一个空文件(仅包重命名引用)。如果 CRATE 方块类型也需要掉落保护,则这块逻辑缺失。

4. 物品管道兼容(Item Pipe Compatibility)未实现

PR #3582#3583 中,"物品管道兼容" 对小板条箱和 板条箱都已勾选,但 diff 中未见任何管道适配器、IItemResourceHandlerHolder 实现或管道连接逻辑。目前存储方块的 capability 已通过 ModCapabilities.item() 注册,但未实现 IItemResourceHandlerHolder 接口的方法。


💡 建议

1. StorageType.find(Class<?>) 使用 getSimpleName() 脆弱

文件: StorageType.java:25-33

return switch (clazz.getSimpleName()) {
    case "CrateStorage" -> StorageType.CRATE;
    ...
    default -> throw new IllegalStateException(...);
};

getSimpleName() 在类被混淆、重命名或内联时会静默失效。改用一个从 Class<?>StorageType 的静态 Map(或直接调用 StorageType.find(storage) 的重载版本)会更健壮。

2. UnlimitedItemStack.isStackable() 未考虑组件限制

文件: UnlimitedItemStack.java:177

public boolean isStackable() {
    return this.getMaxStackSize() > 1 && (!this.stack.isDamageableItem() || !this.stack.isDamaged());
}

MC 1.21 中 ItemStackgetMaxStackSize() 已受 DataComponent(如 max_stack_size 组件)影响。这里调用的是 this.getItem().getMaxStackSize(this.toStack()),但由于 this.stackItemStack 实例(而非 vanilla ItemStack),组件可能未正确传递。

3. CategoryList.children() 缓存重建逻辑重复

文件: CategoryList.java:167-173

public List<Button> children() {
    if (this.cache.size() == this.categoryButtons.size() + 1) return this.cache;
    this.cache.clear();
    this.cache.addAll(this.categoryButtons);
    this.cache.add(this.settingButton);
    return this.cache;
}

rebuild() 被调用时,categoryButtons.clear() 会清空列表但缓存不会同步更新(只有 children() 被调用时才会刷新)。建议在 rebuild() 末尾显式 this.cache.clear() 以避免偶发的竞态偏移错误。

4. SharedTextures 删除了五个纹理常量

文件: SharedTextures.java

删除了 PLAYER_ALLOWPLAYER_NOT_ALLOWVILLAGER_ALLOWVILLAGER_NOT_ALLOW,并将 SMALL_SLIDER 重命名为 SMALL_MACHINE_SLIDER。请确认这些纹理在其他屏幕类中确实已被移除引用,否则会引起编译/运行时错误。

5. 模板变量名称为全大写(%s)不符合中文 UI 习惯

文件: ScreenLang.java 新增的 lang key:

"screen.anvilcraft.storage.category.name": "Name: %s"
"screen.anvilcraft.storage.category.mode": "Mode: %s"

在中文语境下建议使用 "${名称}:%s" 格式,或提供中文翻译键(zh_cn.json)。当前 PR 未包含中文语言文件的更新。

6. 新的 mixin.json 条目缺少专用包(package)配置

文件: anvilcraft.mixins.json

新加的 EnchantmentsPredicateMixin 是否在 mixin.json 的 package 域覆盖范围内?当前 mixin 列表位于第 28 行,EnchantmentsPredicateMixin 在第 27 行被添加。需确认 compatibilityLevelpackage 配置是否已覆盖 dev.dubhe.anvilcraft.mixin(通常不应需要特别处理,但与 invoker.BaseMappedRegistryInvoker 在同一包内)。


✅ 看起来不错

  • 存储架构设计清晰: BaseStorageCrateStorage/LargeCrateStorage/ShulkerContainerStorage/HyperdimensionStorage 的继承链,配合 StorageType enum 的 codec()/streamCodec() 工厂方法,扩展性强
  • Codec/StreamCodec 双向一致性: 所有存储子类的持久化码与网络码都严格按照同一字段顺序定义,避免序列化不一致
  • 能量系统重构提取:ItemFEStorage.create(...) 重复调用提取为 ModCapabilities.energy(int capacity) 工厂方法,减少样板代码
  • ModCapabilities 统一存储物品注册: 通过 Storages.get().getOrCreate(...).getItems() 统一指向 Storages 管理的数据,避免为每个方块额外维护内部 handler
  • 玩家设置持久化: PlayerSettings + PlayerSettingsSyncPacket 使得存储 UI 设置(搜索、排序、排序顺序、NBT 显示模式)在玩家间独立保存
  • 类别系统的多态设计: ICategory 接口配合 ModCategoryTypes 注册中心,支持动态扩展的 BlockCategoryUnstackableCategoryHasComponentCategory 等多种过滤方式,且支持通过 Holder<ICategory> 进行引用
  • 包结构重组合理: containercontainer.storage 的迁移明确了职责边界

总结: 此 PR 搭建了存储系统的完整后端基础设施(持久化、网络同步、类别过滤、玩家设置),但部分 UI 交互(Shift+点击、物品栏整合、搜索栏等)和潜影集装箱的特殊功能尚未实现。建议先修复 StorageMenu.quickMoveStack 的空实现和 EOF 换行符问题后合并,剩余 TODO 项可在后续 PR 中完成。

由 Hermes Agent 审查

@anvil-craft

Copy link
Copy Markdown

⚠️ Dangerous command requires approval:

echo "=== 无 hunk 的幽灵文件 ===" && for f in $(grep "^diff --git" /tmp/pr3795.diff | sed 's|.* b/||'); do
  f_esc=$(echo "$f" | sed 's|/|\\/|g')
  hunks=$(sed -n "/^diff --git.*$f_esc/,/^diff --git /p" /tm...

Reason: Security scan — [MEDIUM] Variation selector characters detected: Content contains Unicode variation selectors (VS1-256). These are commonly used in emoji sequences but may indicate steganographic encoding or obfuscation

Reply /approve to execute, /approve session to approve this pattern for the session, /approve always to approve permanently, or /deny to cancel.

@anvil-craft

Copy link
Copy Markdown

Good — 我已经下载并分析了全部 5245 行 diff (95 个文件, 87 个 Java 源文件)。以下是审查结果:


代码审查摘要

结论:请求更改 — 发现 1 个严重 Bug,2 个警告,若干建议。存储系统的架构整体设计良好,但网络层存在数据无法正确传输的关键问题。


🔴 关键

1. StoragePacketssetBytes 不推进 writerIndex,导致网络发包丢失所有物品数据

文件: src/main/java/dev/dubhe/anvilcraft/network/multiple/StoragePackets.java

Sync2CFull.encode()Sync2CIncremental.encode() 中:

buf.ensureWritable(data.buf.readableBytes());
buf.setBytes(buf.writerIndex(), data.buf, data.buf.readableBytes());

Netty 的 setBytes(int index, ByteBuf src, int length) 不会推进目标 ByteBufwriterIndex。数据确实写入了底层内存,但 writerIndex 没有增加。这意味着框架发送数据包时只会发送 [0, writerIndex) 范围的字节,而 setBytes 写入的原始堆栈数据不会被包含在发送内容中。

客户端在 decode 时读取 UUID 和 head 后就无法正确解码物品列表,导致 GUI 中不显示任何物品内容

修复:setBytes 改为 writeBytes,后者会正确推进 writerIndex

buf.writeBytes(data.buf, data.buf.readableBytes());

2. FullSyncer.sync()IncrementalSyncer.sync()System.arraycopy 包分割模式存在溢出风险

Sync2CFull[] array = this.packets.toArray(new Sync2CFull[0]);
int length = array.length - 1;
Sync2CFull[] dest = new Sync2CFull[length];
System.arraycopy(array, 1, dest, 0, length);

packets.size() == 1 时:

  • length = 0, dest = new Sync2CFull[0]
  • System.arraycopy(array, 1, dest, 0, 0) — 虽然 length=0 时不实际读取 array[1],但从语义上 srcPos=1length=0 时不越界(JLS 规范),但代码可读性差且易在后续修改中出错。

建议加一个显式的边界判断或统一使用 List.subList()/List.toArray() 模式。


⚠️ 警告

1. 生成的分类 JSON 文件缺少尾部换行符

三个新生成的文件末尾缺少换行符:

  • src/generated/resources/data/anvilcraft/anvilcraft/category/block.json
  • src/generated/resources/data/anvilcraft/anvilcraft/category/minecraft.json
  • src/generated/resources/data/anvilcraft/anvilcraft/category/unstackable.json

en_ud.json 的旧文件遗留问题(\ No newline at end of file)也被带入(虽然 en_us.json 已修复)。建议检查数据生成器的 EOF 处理逻辑。

2. PlayerSetting.list() 缺少重复检查

public void list(ICategory category) {
    this.listed.add(new CategoryEntry(category));
}

直接添加而不检查 category 是否已被列出,可能导致同一类别多次出现。建议:

public void list(ICategory category) {
    if (this.listed.stream().noneMatch(e -> e.getCategory().equals(category))) {
        this.listed.add(new CategoryEntry(category));
    }
}

💡 建议

1. StorageMenuState.STATES 使用非线程安全 HashMap

Map<UUID, StorageMenuState> 使用 HashMap 且无同步机制。虽然当前仅在游戏主线程访问,但建议改为 ConcurrentHashMap 以防止未来的多线程上下文问题。

2. 潜影集装箱 ShulkerContainerBlockEntity 含 TODO 但已部分功能化

public class ShulkerContainerBlockEntity extends StorageBlockEntity { // TODO: 实现潜影集装箱功能

该类实际上已通过继承 StorageBlockEntity 实现了基本的存储功能(UUID、数据组件、菜单打开、破坏掉落)。TODO 注释与实际功能程度不匹配——建议更新注释为具体说明哪些功能尚未实现(制作配方、升级、模型切换)。

3. 小板条箱「3x3x3搜索」未实现

PR 描述勾选了小板条箱的合成配方和物品管道兼容,但 3x3x3搜索 未勾选且代码中未见实现。建议在 BlockEntity 或 ServerLevel 层面预留接口。

4. 大板条箱「物品管道兼容」未实现

大板条箱的 useItemOn 打开了 GUI 菜单但没有实现与物品管道的直接交互(如通过 CapabilityProvider 暴露 IItemHandler)。小板条箱勾选了管道兼容但代码中未见对应的 CapabilityProviderResourceHandler 注册。

5. StorageSettingsearch/sort/order/nbtDisplay 的默认值未校验

StorageSetting 的构造函数使用 @AllArgsConstructor 但四个枚举字段没有 @Nullable 标记,若反序列化失败可能产生 NPE。建议通过 @Builder + @Builder.Default 或自定义反序列化验证来设置安全的默认值。

6. category.anvilcraft.foods_and_drinksfood_and_drink 的 key 变更

该 key 的变更是破坏性的——如果用户旧存档引用了 foods_and_drinks 翻译 key,将回退到未翻译状态。建议在 ScreenLangDataFixer 中添加兼容处理。


✅ 看起来不错

  1. 架构设计StorageBlockEntityBaseStorageStorages(SavedData) 层级清晰,职责分明。相比旧的空壳 BlockEntity 方案是巨大进步。

  2. 存储类型枚举化StorageType 使用 enum + switch 模式,CODEC/STREAM_CODEC 通过 dispatchMap 分发,扩展新存储类型非常方便。

  3. UUID 标识体系 — 通过 DataComponentStorageRef 将方块物品与存储 ID 绑定,破坏时通过 playerWillDestroy 保留引用,设计巧妙。

  4. 网络同步设计FullSyncer + IncrementalSyncer 双模式:首次全量同步 + 增量变更推送,每包 512KB 限制,支持分包。除上述 setBytes Bug 外,设计非常成熟。

  5. 类别系统重构Categories 类(旧)被删除,改用 PlayerSetting + CategoryEntry + CategoryMode 替代,每个存储站/箱子可以有独立的类别显示模式(unlimited/allowlist/blocklist),灵活性大幅提升。

  6. 语言文件同步en_us.jsonen_ud.json 均已同步更新新增的翻译 key,en_ud 中乱序文本正确生成。

  7. IEnergyHandlerHolder 接口统一getEnergyHandler() 方法名规范化替代了 getEnergyStorage(),且 FeCollectorBlockEntityPowerConverterBlockEntity 均已实现。

  8. ExtraEnchantmentsPredicate — 新的类型化附魔谓词替代了之前的泛型 DataComponentPredicate.AnyValueType 方案,类型安全性和可维护性更好。


任务清单对照

需求 状态 备注
#3582 主界面 4/14 标题、类别按钮、类别设置按钮 ✅
#3582 类别设置 7/7 已完成 ✅
#3583 小板条箱 2/3 配方 + 管道兼容 ✅,3x3x3搜索 ❌
#3583 大板条箱 1/3 配方 ✅,管道兼容 ❌,快捷替换 ❌
#3584 潜影集装箱 0/3 框架已建,功能均未实现 ❌
超维存储站界面与存储 已完成

结论: 存储系统的骨架(架构、持久化、网络同步、GUI 框架、类别系统)已搭建完成,但:

  • 必须修复 setBytes Bug,否则客户端无法接收任何物品数据
  • UI 界面上大量交互功能(搜索、排序、滚动条、物品栏、放入/取出按钮)尚未实现
  • 管道兼容方面需要进一步集成

建议先合并基础架构部分,在后续 PR 中分批完成 UI 交互和功能特性。

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.

2 participants