Skip to content

Fix chute placement preview over containers 修复溜槽对准容器时的放置预览 - #5121

Merged
PigeonNian merged 1 commit into
Anvil-Dev:dev/1.21/1.6from
WhereisFff:codex/fix-5120-chute-preview
Oct 2, 2026
Merged

PigeonNian merged 1 commit into
Anvil-Dev:dev/1.21/1.6from
WhereisFff:codex/fix-5120-chute-preview

Conversation

@WhereisFff

@WhereisFff WhereisFff commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

fix(client): 修复溜槽对准容器时的放置预览 (#5120)

  • 按 ChuteBlockItem.onItemUseFirst 的优先级,检查原始点击位置的 IItemHandlerHolder 或点击面的物品处理能力,恢复主手溜槽对准容器时的预览。
  • 保留创造板条箱及仓储端口的中心/边缘判定;副手仍遵循主手容器交互优先级,潜行及其他方块行为保持不变。
  • fixed [Bug] 溜槽放置预览 #5120

- 按溜槽首轮交互规则检查原始点击位置和方向的物品容器,恢复主手放置预览
- 保留创造板条箱及仓储端口的中心交互,以及副手、潜行和其他方块的既有交互优先级
@Gugle2308

Copy link
Copy Markdown
Collaborator

审查完成。以下内容将作为 PR 评论输出(未执行任何 gh pr review/comment;标题已含中英双语且准确,无需 gh pr edit)。


代码审查摘要 — PR #5121 Fix chute placement preview over containers 修复溜槽对准容器时的放置预览

操作: opened(PR 仍为开放式;data-status 无 merged/closed 标记)
范围: 1 个文件,+9 行(src/main/java/dev/dubhe/anvilcraft/util/PlacementInteractions.java),无新增/删除文件
diff 对账: 用 git 3-dot 对 base 分支当前 tip(dev/1.21/1.6 @ 4677f8aebf)复核 head(623a66776a):1 file changed, 9 insertions(+),与下载的 patch-diff 完全一致(无截断、无合并漂移)。文件末行有换行符。

✅ 一致性核对(本 PR 的核心不变量:预览判定 == 实际放置优先级)

新分支(PlacementInteractions.java:40-46)逐项对照 ChuteBlockItem.onItemUseFirst:

检查项 onItemUseFirst 新增预览分支 结论
判据表达式 be instanceof IItemHandlerHolder || level.getCapability(Capabilities.ItemHandler.BLOCK, clickedPos, clickedFace) != null 同上,逐字一致 ✅ 无偏差
取位方式 context.getClickedPos()(不用 多方块主方块 menuPos) 同样用 getClickedPos() ✅ 一致(这点很关键:若这里改用 menuPos 就会与实际放置产生偏差)
面向参数 context.getClickedFace() 同 ✅ 一致,因此「粉碎台/冲压台/筛选台/拆包台 底部 side == DOWN → null」的例外也被同侧保留
手序 不区分手 额外要求 MAIN_HAND ⚠️ 见下
判定位置 — 位于 isStorageInteraction 之后、isSecondaryUseActive 之后 ✅ 创造板条箱/仓储端口的「中心→GUI、边缘→放置」判定确实被保留

🟢 看起来不错

  • 仅影响客户端预览,不改服务端行为:allowsPlacement 全仓库只有一个调用点 —— client/event/LargeBlockPlacePreviewEventListener.java:154(客户端预览事件),服务端放置路径不受影响,风险面可控。
  • 不会产生「假预览」:allowsPlacement 返回 true 后仍会走 BuildingRodService.singlePlacement/singleAttempt(BlockPlaceContext.canPlace() + getStateForPlacement + item.canPlace),cells.isEmpty() 直接 return。所以新增分支在「能力存在但放不下」的场景不会画出幽灵方块——它是一个纯粹的交互优先级闸门,这正好是修复 [Bug] 溜槽放置预览 #5120 所需的语义。
  • 客户端能力可用性成立:CapabilitiesEventListener 是 @EventBusSubscriber(modid = ...),未限定 dist,RegisterCapabilitiesEvent 双端触发,所以客户端预览路径里对 AnvilCraft 容器的 getCapability 能命中(原版容器走 NeoForge 自身的双端 hook)。
  • 版本交叉验证:gradle/libs.versions.toml 中 neoForge = "21.1.238",与 PR 描述里「外置 NeoForge 21.1.238 运行时回归」的版本一致,验证陈述可信。
  • 新增两条 import 在各自分组内字母序正确(api.itemhandler < block.entity;net.minecraft < net.neoforged),与项目 Checkstyle 分组约定相符,预期不引入新的 import-order 告警。
  • fixed #5120 + 单 commit + 只改一个文件,范围收敛得很干净。

⚠️ 警告(非阻塞)

  • 判据重复,存在后续漂移风险 — PlacementInteractions.java:40-46 与 ChuteBlockItem.onItemUseFirst(约 :28-38)现在各存一份完全相同的容器判定表达式。这两个地方必须永远同步(一个决定「会不会放」,一个决定「放哪/预览给不给」),任何一方后续调整(例如新增某类容器、换能力查询参数)都会静默破坏本 PR 建立的不变量。建议抽成共享静态方法,例如在 ChuteBlockItem 暴露 public static boolean isContainerPlacementTarget(UseOnContext ctx),两侧都调用它;顺便在这一行补一句注释说明「与 onItemUseFirst 优先级保持一致」。
  • 副手仍存在同类不一致(建议明确取舍并注释) — 预览监听器在「主手选中物品不是 BlockItem」时会退回副手(hand = OFF_HAND),此时新分支因 MAIN_HAND 条件不成立而跳过。而 onItemUseFirst 的判据完全不区分手:若副手溜槽的交互确实可达(主手交互返回 PASS 后由 ServerPlayerGameMode 重试副手),那么在创造板条箱/存储箱/原版箱子这类 MenuProvider 容器上,副手会出现「能放置但不显示预览」,正是 [Bug] 溜槽放置预览 #5120 的同类问题。我这边没有 MC 运行时,无法像作者那样用 2880 组场景复现,因此不作为阻塞项——但如果是有意保留现状,建议加一行注释说明原因(例如「副手优先级由主手物品决定,实际不可达」),避免后人把它当成遗漏。作者的回归矩阵已覆盖双手,如果数据表明副手不可达,注释里引用该结论即可。

💡 建议

  • 本次修复顺带把预览开到了「会打开 GUI 的容器」上,与 onItemUseFirst 的溜槽优先级一致,属预期行为。为便于后续核对,列出新增返回 true 的目标面(依据 CapabilitiesEventListener):
    • BE 级注册 21 类:BATCH_CRAFTER、BATCH_CUTTER、CHARGER、DISCHARGER、CHUTE、SIMPLE_CHUTE、SIMPLE_MAGNETIC_CHUTE、MAGNETIC_CHUTE、OVERFLOW_CHUTE、ITEM_COLLECTOR、ITEM_SPLITTER、CONFINEMENT_CHAMBER、FISH_TANK、STRUCTURE_SCANNER、SMART_BLOCK_PLACER、TRADING_STATION、BURNING_HEATER、CREATIVE_CRATE、STORAGE_PORT、STORAGE_PORT_CONSOLIDATOR、HYPERDIMENSION_UPLOADER;
    • block 级注册:CRATE(存储箱)、SHULKER_CONTAINER/LARGE_CRATE/HYPERDIMENSION_STORAGE_STATION(多方块,任意部位均可命中主部件的只读/写入 handler)、HONEY_CAULDRON(恒定非空)、VOID_MATTER_BLOCK(恒定非空)、CELESTIAL_FORGING_ANVIL_LOGISTICS_INTERFACE、AUTO_ENCHANTING_TABLE、LARGE_CAULDRON;
    • 说明:这批目标里有相当一部分右击是开界面(BaseMachineBlockEntity 系、自动附魔台、鱼缸、收集器、物品分拣器等),预览现在会盖在它们上面。这是与放置实际一致的正确结果,但作者已明确「未做客户端画面人工验证」,建议补一轮短的人工目视:原版箱子/木桶/漏斗/熔炉(NeoForge 能力)+存储箱+潜影盒容器/大板条箱/超维存储站台+蜂蜜锅+虚空物质块,确认预览出现且与点击后结果吻合。
  • 极小性能提示(可选,无需处理):客户端预览是逐帧调用,能力查询被移进了这条件里;个别 provider 每次查找都会构造对象(new HoneyCauldronWrapper(level, pos)、ReadOnlyItemHandlerWrapper.wrap(...),VoidItemHandler.INSTANCE 是常量)。瞄准单个方块时只是每帧一次分配,量级可忽略,列出仅供后续 profiling 参考。

📋 声称验证表

PR 声称 状态 证据
按 ChuteBlockItem.onItemUseFirst 优先级检查原始点击位的 IItemHandlerHolder 或点击面能力 ✅ 表达式逐字一致,同用 getClickedPos()/getClickedFace()
保留创造板条箱及仓储端口的中心/边缘判定 ✅ 新分支位于 isStorageInteraction 早退之后;PlacementInteractions.java:37(isStorageInteraction → return false)未被改动
副手仍遵循主手容器交互优先级,潜行及其他方块行为不变 ✅(行为未变) 新分支要求 MAIN_HAND;isSecondaryUseActive 早退在其之前;MenuProvider/StorageBlockEntity/交互方块 分支原样保留。⚠️ 但见上文副手不一致项
仅修改 PlacementInteractions.java ✅ git 3-dot:1 file changed, 9 insertions(+)
未提交 GameTest 或相关测试资源 ✅ 无新增文件;仓库内亦无 PlacementInteractions 相关测试
clean build / Checkstyle 通过 ⚪ 未复现 无构建环境(本次仅静态核对 import 分组与行序,未见新增告警来源)
2880 组外置运行时回归、336 组恢复 / 2544 组不变 ⚪ 无法复现 依赖 MC 运行时;版本号(NeoForge 21.1.238)与 libs.versions.toml 一致,陈述可信

🧪 测试建议

被测目标 推荐测试场景 优先级
PlacementInteractions.allowsPlacement() 参数化用例钉死不变量:主手溜槽 + 能力容器(含 DOWN 面为 null 的粉碎台/冲压台)→ true;创造板条箱/仓储端口中心点击 → false;副手溜槽 + MenuProvider 容器 → 记录期望值以固化取舍 🟡
ChuteBlockItem.onItemUseFirst vs allowsPlacement 二者共用抽出的 isContainerPlacementTarget() 后加一个「同一 UseOnContext 下两处判据同真同假」的断言,防止未来漂移 🟡

结论: APPROVE — 修复方向正确、实现极小且与 onItemUseFirst 的优先级精确对齐;由于只作用于客户端预览闸门、且其后仍有 BuildingRodService 的放置可行性校验,「假预览」风险已闭环。两条 ⚠️ 均为非阻塞(判据去重、副手取舍注释),建议顺手处理;标题准确,未作修改。

由 Hermes Agent 审查

@Gugle2308

Copy link
Copy Markdown
Collaborator

💾 Self-improvement review: Patched SKILL.md in skill 'tool-block-interaction-pr-review' (1 replacement).

@WhereisFff
WhereisFff marked this pull request as ready for review October 2, 2026 02:04
Copilot AI balanced review requested due to automatic review settings October 2, 2026 02:04

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@Gugle2308

Copy link
Copy Markdown
Collaborator

⚠️ Dangerous command requires approval:

which javap java; ls /opt/data/workspace/ageratum_probe/a534/META-INF/jarjar/ | grep -i util; cd /tmp && rm -rf anvutil && mkdir anvutil && cd anvutil && unzip -o -q /opt/data/workspace/ageratum_probe...

Reason: recursive delete

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

@Gugle2308

Copy link
Copy Markdown
Collaborator

代码审查摘要 — PR #5121

操作: ready_for_review(PR 状态 open,未合并;base 4677f8ae = dev/1.21/1.6,head 623a6677)
范围: 1 个文件(util/PlacementInteractions.java)/ +9 −0 / 3 hunks / 无测试文件
对应 Issue: #5120「准星对准容器时无法预览溜槽的放置」(open,🐛 Bug)

✅ 核心逻辑核验:新分支与 ChuteBlockItem.onItemUseFirst 逐路径一致

我把新分支(PlacementInteractions.java:40-46)与 block/item/ChuteBlockItem.java 的优先级逐分支对了一遍,判据完全相同(getClickedPos() + getClickedFace() 的 IItemHandlerHolder / Capabilities.ItemHandler.BLOCK 查询,与 ChuteBlockItem:37-39 逐字对应),因此预览门控与实际交互不会漂移:

场景(主手溜槽) 实际 onItemUseFirst 预览 allowsPlacement 一致
机器 / 容器(BaseMachineBlockEntity implements MenuProvider, IItemHandlerHolder) useOn() 尝试放置 → 消耗交互,GUI 打不开 新分支 → true(修复前落在 entity instanceof MenuProvider → false)✅ 正是 #5120 的症状 ✅
创造板条箱 中心 isStorageInteraction → PASS → 方块 useItemOn 收纳物品(CreativeCrateBlock.useItemOn:51-56) 第 38 行 → false ✅ 保留
创造板条箱 边缘 isStorageInteraction=false → useOn() 放置 新分支 → true(旧代码第 58-61 行同样 true) ✅ 保留
仓储端口/合并器 中心(主手) PASS → 端口交互 第 38 行 → false ✅ 保留
仓储端口 边缘 / 副手 useOn() 放置 新分支或第 53 行 !isStorageInteraction → true ✅ 保留
潜行 useOn() 放置 第 39 行(先于新分支)→ true ✅ 不变
无能力且非 MenuProvider 的普通方块 super.onItemUseFirst → PASS → 方块交互 第 56/58-61 行原逻辑 ✅ 不变

MAIN_HAND 限定是必要且正确的:预览的 hand 选择是「选中槽非 BlockItem 才取副手」,而副手溜槽对准可交互容器时主手路径会先被方块交互消耗(与 issue 里「手上拿溜槽时无法交互容器」的描述一致)——因此副手不显示预览正是实际行为。

✅ 已独立复算的 PR 声称

声称 结果
仅改 PlacementInteractions.java,未提交测试资源 ✅ git diff --stat 1 file, +9/−0;无 GameTest
4 种溜槽物品 ✅ CHUTE/MAGNETIC_CHUTE/OVERFLOW_CHUTE/SIMPLE_MAGNETIC_CHUTE,均在 PLACEMENT_PREVIEW 标签内(修复对溜槽预览确实生效)
git diff --check 通过 ✅ 复算通过
Checkstyle 无告警 ✅ 新增行最长 121 字符(文件内既有最长 135),import 顺序 dev.dubhe.* → net.minecraft.* → net.neoforged.* 正确
2880 组外置运行时回归 ⚠️ 属作者侧外部证据,仓库内无法复现(见下)

💡 建议(非阻塞)

  1. 判据重复、有漂移风险 —— 新分支复刻了 ChuteBlockItem 的能力判定。该判定如今已有 3 处消费者(ChuteBlockItem.onItemUseFirst、ItemStackMixin:51、本预览门控)。建议抽成 ChuteBlockItem 的公共静态谓词(如 ChuteBlockItem.willAttemptPlacement(UseOnContext)),由 onItemUseFirst 与预览共用;否则将来改动 ChuteBlockItem 的优先级时,预览会静默失配(这正是 [Bug] 溜槽放置预览 #5120 的成因类型)。
  2. 描述遗漏了一处波及面(方向上正确) —— 新分支的 getCapability 一路会让「能力只来自注册表、且不是 IItemHandlerHolder」的方块从 false 翻成 true,典型是 StorageBlockEntity(CapabilitiesEventListener:303 为它注册了 storageItemHandler,而旧代码第 57 行显式 return false)。这与 onItemUseFirst 的通用分支一致(持溜槽时该交互确实会被 item 路径消耗,FAIL 亦然),属正确修复,但 PR 描述只提到「容器」,建议补一句并确认回归用例的 15 种目标覆盖到 storage block。
  3. 记录一个边界(无需改) —— 多方块容器(BE 只在主部件,预览侧用 getMultipartMainPartPos 的 menuPos 取实体,而新分支与 onItemUseFirst 一样只看 getClickedPos()):点击子部件时两处都取不到能力 → 预览不显示,实际交互也落到方块 GUI,自洽。
  4. 测试建议 —— 2880 组外置运行时回归无法进 CI,建议补一条 GameTest/单元用例把这条判据钉住:
被测目标 建议场景 优先级
PlacementInteractions.allowsPlacement() 主手溜槽 × BaseMachineBlockEntity/箱子 → true;创造板条箱 中心 false / 边缘 true;潜行 true;副手 false;旁观者/空玩家 false 🔴
与 ChuteBlockItem.onItemUseFirst 的一致性 对上述同一批 context 断言「预览 true ⇔ 交互被 item 路径消耗」 🟡

结论: APPROVE(comment 级)— 无阻塞问题

修复点选得准(根因是预览门控只看 MenuProvider,未复用 ChuteBlockItem 的溜槽优先级),改动最小、与 onItemUseFirst 判据严格同构,中心/边缘、潜行、副手语义均保留。仅建议消除判据重复并补充描述/回归覆盖。PR 标题已准确(双语),无需修改;另:本机 gh token 已失效,本次未做任何标题变更。


由 Hermes Agent 审查

@Gugle2308

Copy link
Copy Markdown
Collaborator

💾 Self-improvement review: Patched references/placement-preview-gate-mirroring.md in skill 'tool-block-interaction-pr-review' (1 replacement). · Patched SKILL.md in skill 'tool-block-interaction-pr-review' (1 replacement).

@Gugle2308

Copy link
Copy Markdown
Collaborator

代码审查摘要 — PR #5121

操作: review_requested(PR 状态 data-status="pullOpened",merged_at: null — 未合并,正常审查)
范围: 1 个文件(1 Java,0 新增/删除)/ 33 行 diff / +9 −0,3 hunk
diff 完整性对账: 本地 +9/-0、changed_files=1 与 GitHub API(additions: 9, changed_files: 1)一致;无 ghost 文件、无 EOF 缺换行(diff sha256 前 16 位 492d62df283ba31f)


✅ 正确性验证(先说结论:镜像关系成立)

新增守卫与 ChuteBlockItem.onItemUseFirst(block/item/ChuteBlockItem.java:37-39,本 PR 未改动)逐字同构:

blockEntity instanceof IItemHandlerHolder
    || level.getCapability(Capabilities.ItemHandler.BLOCK, clickedPos, clickedFace) != null
  → this.useOn(context)   // 真的会放置
  • 优先级位置正确:守卫插在 isStorageInteraction → false(第 4 步)与 isSecondaryUseActive → true(第 5 步)之后、MenuProvider / StorageBlockEntity → false(第 7 步)之前。而 isStorageInteraction 正是 onItemUseFirst 第一分支的判定(创造板条箱/仓储端口中心点击 → PASS → 开界面),先拦截、守卫后置,两条路径不会互相污染 ✅ 与 PR 描述"保留创造板条箱及仓储端口的中心/边缘判定"一致。
  • 修复覆盖面比描述更宽(值得写进描述):第 7 步 entity instanceof StorageBlockEntity → return false 屏蔽的不止 StoragePort——CapabilitiesEventListener.java:118-161 里 LARGE_CRATE / SHULKER_CONTAINER / HYPERDIMENSION_STORAGE_STATION 是 registerBlock(...) 注册能力且 BE 为 StorageBlockEntity(entity/storage/StorageBlockEntity.java:35,不实现 IItemHandlerHolder),原本一律被第 7 步拦死;新守卫的 getCapability 分支能命中(provider 内部用 getMainPartPos,多方块任意部件均可解析)。即这 3 类多方块仓储方块的预览也一并恢复——作者描述里只提到"仓储端口",建议补一句。
  • 短路顺序合理:getHand()==MAIN_HAND → instanceof ChuteBlockItem → 才做 getBlockEntity/getCapability,非溜槽物品零额外开销 ✅
  • API 形态无误:Level#getCapability(BlockCapability, BlockPos, C) 在 base 分支已大量使用(BlockDevourerBlock.java:233、ItemHandlerUtil.java:157),Capabilities.ItemHandler.BLOCK 的 context 类型是 @Nullable Direction,传非空 face 合法 ✅

⚠️ 需要确认(非阻塞)

  1. 预览路径从此是"客户端每帧能力探测",与类 javadoc「只读判断交互优先级」的契约有出入
    LargeBlockPlacePreviewEventListener.updatePreview()(@EventBusSubscriber(Dist.CLIENT),渲染阶段每帧调用)→ 本守卫 → 对仓储方块会落到 CapabilitiesEventListener.storageItemHandler(该类 303-316 行),它不是纯读:

    • if (be.getId() == null) { UUID.randomUUID(); be.setId(id); }(写 BE 状态)
    • Storages.get().getOrCreate(id, clazz)(可能新建存储条目)
    • crate.refreshDispose()

    缓解因素我已核对:TerminalSourceManager/StorageComparatorManager.registerIfApplicable 均先 level.isClientSide() 早退;Storages.get() 在非服务端返回 CLIENT_COPY(saved/storage/Storages.java:41-44),不会抛 IllegalStateException。所以大概率只是客户端侧的多余写入/客户端副本条目,但 PR 自述"未进行客户端画面的人工视觉验证" —— 建议实机手持溜槽悬停在大型板条箱 / 潜影集装箱 / 超维存储站上看一眼(日志无异常、幽灵位置与实际落点一致),并在守卫处加一句注释说明"仅主手持有溜槽时才会探测"。

  2. MAIN_HAND 限制与 ChuteBlockItem 的差异需要注释固化
    ChuteBlockItem.onItemUseFirst 第二分支与手无关(副手溜槽同样 useOn 放置),而守卫要求主手。语义上通常仍一致(带 GUI 的容器会先吃掉主手交互,副手到不了 onItemUseFirst),但本文件对副手是显式建模的(第 3 步、return context.getHand() != MAIN_HAND)。判据差异是刻意为之的话,建议落到注释/提交信息,否则后续改动容易踩。

  3. 多方块"非主体部件"的残余不一致(先于本 PR 存在,仅记录)
    守卫用 clickedPos,同函数第 7 步用 menuPos;StoragePort 这类多方块在非主体部件点击时 getBlockEntity(clickedPos) 可能为 null(AbstractMultiPartBlock 未覆写 getBlockEntity),实际交互会走 super.onItemUseFirst → PASS → 方块自身交互(开界面),而第 7 步 return !isStorageInteraction(context) 仍给 true。本 PR 未触碰该行,建议另开 issue。


💡 建议

  • 抽出共享谓词,消除"必然漂移":守卫是 ChuteBlockItem.java:37-39 的逐字复制。建议在 ChuteBlockItem 暴露 public static boolean willPlaceOnContainer(UseOnContext),onItemUseFirst 与 PlacementInteractions 共用,预览侧用 {@link ChuteBlockItem} 指明优先级来源——这也顺带解决 ⚠️1 的"契约"问题(副作用留在物品交互路径,预览只问谓词)。
  • 局部变量提升可读性:(context.getLevel().getBlockEntity(...) instanceof … || context.getLevel().getCapability(...) != null) 嵌在 if 里偏密;抽 Level/BlockPos 局部变量或私有静态方法,与该文件后半段 state/block/entity 的风格对齐。
  • 测试资源:本 PR 未提交任何测试,回归完全依赖仓库外的运行时编排,CI 无法复现。

🟢 看起来不错

  • 改动最小(9 行、0 删除、单文件),严格镜像真实放置判据,未误伤其他物品/方块分支。
  • 守卫放在 storageInteraction 与 sneak 之后,说明是真按 onItemUseFirst 优先级对齐,而非"在函数开头抢跑 return true"——这是本 PR 最容易写错的地方,值得肯定。
  • getCapability 置于 instanceof IItemHandlerHolder 之后,短路掉绝大多数探测。

📋 声称验证表

声称 状态 证据
恢复 #5120(溜槽对准容器的放置预览) ✅ 守卫镜像 ChuteBlockItem.onItemUseFirst:37-39;额外覆盖 StorageBlockEntity 系(LARGE_CRATE / SHULKER_CONTAINER / 超维存储站)
仅修改 PlacementInteractions.java ✅ diff 仅 1 文件,base 54 行 → head 63 行(+2 import、+7 守卫),与 fork head 文件逐行一致
保留创造板条箱/仓储端口中心-边缘判定 ✅ 新守卫位于 isStorageInteraction → false(第 4 步)之后,中心点击不会进入守卫
副手仍遵循主手容器交互优先级 ⚠️ 行为满足(守卫限 MAIN_HAND),但与 ChuteBlockItem 手无关的第二分支存在判据差异,建议注释固化
build / Checkstyle 通过、无新增告警 ⚠️ 无法独立验证 环境无 gradle/JDK;改动行导入归入 net.minecraft.* 之后的 net.neoforged.*,与 ChuteBlockItem 风格一致,未见明显风格偏差
2880 组点击场景回归、336 组预览恢复 ⚠️ 无法独立验证 仓库内无测试资源,外部编排不可复核(与 PR 自述相互印证)
未提交 GameTest/测试资源 ✅ diff 中无测试文件

🧪 测试建议

被测目标 推荐场景 优先级
PlacementInteractions.allowsPlacement(溜槽 + 容器) 主手溜槽 → true;副手同场景 → false;大板条箱/潜影集装箱/超维存储站各一例 🔴
同上(仓储端口 / 创造板条箱) 中心点击 → false(开界面);边缘点击 → true;潜行 → true 🔴
预览 ⟺ 真实行为一致性(防漂移) 同一 (目标, 手, 面, 中心/边缘) 组合断言 allowsPlacement ⟺ onItemUseFirst != PASS,可直接复刻 PR 声称的矩阵 🟡
客户端安全性 手持溜槽悬停仓储方块(单机 + 联机)观察日志与客户端存储副本 🟡
多方块非主体部件 溜槽点击多方块容器非主部件时预览/实际是否一致(现存残余,非本 PR 引入) 🟢

结论: ✅ LGTM — 建议合并(附带 3 条非阻塞确认项)
改动最小、与真实放置判据逐字对齐,未发现回归;唯一系统性风险是"同一判据在两处复制 + 预览从纯读变成每帧客户端能力探测"——建议后续把容器判定抽成共享谓词并补一致性测试。PR 标题 Fix chute placement preview over containers 修复溜槽对准容器时的放置预览 已准确且中英齐备,未修改标题(gh pr edit 无需执行)。


由 Hermes Agent 审查

@Gugle2308

Copy link
Copy Markdown
Collaborator

💾 Self-improvement review: Patched SKILL.md in skill 'tool-block-interaction-pr-review' (1 replacement). · Patched references/placement-preview-gate-mirroring.md in skill 'tool-block-interaction-pr-review' (1 replacement).

@Gugle2308

Copy link
Copy Markdown
Collaborator

⚠️ Dangerous command requires approval:

curl -s "https://api.github.com/repos/Anvil-Dev/AnvilCraft/issues/5120" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('title')); print('state:',d.get('state')); print((d.get('body...

Reason: Security scan — [HIGH] Pipe to interpreter: curl | python3: Command pipes output from 'curl' directly to interpreter 'python3'. Downloaded content will be executed without inspection.
Safer: tirith run https://api.github.com/repos/Anvil-Dev/AnvilCraft/issues/5120 — or: vet https://api.github.com/repos/Anvil-Dev/AnvilCraft/issues/5120 (https://getvet.sh); script execution via -e/-c flag

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

@Gugle2308

Copy link
Copy Markdown
Collaborator

⚠️ Dangerous command requires approval:

curl -s "https://api.github.com/repos/neoforged/NeoForge/tags?per_page=100" -o /tmp/nftags.json; python3 -c "import json;d=json.load(open('/tmp/nftags.json'));print([t['name'] for t in d[:20]])" 2>&1 ...

Reason: script execution via -e/-c flag

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

@Gugle2308

Copy link
Copy Markdown
Collaborator

审查完成。以下是本次审查结果(action=edited,PR 处于 open、mergeable=clean、1 文件 +9/-0):


代码审查摘要 — PR #5121

操作: edited(标题/描述/分支变更 → 全量重审)
范围: 1 个文件(1 Java,新增 1 处早退分支 + 2 个 import)/ +9 −0 行
目标分支: dev/1.21/1.6(NeoForge 21.1.238 / MC 1.21.1,Capabilities.ItemHandler.BLOCK + IItemHandlerHolder 传统 API 核对无误)

结论先行

改动逻辑正确、范围最小、与 ChuteBlockItem.onItemUseFirst 的判定等价,fixed #5120 成立(#5120 仍 open,症状「准星对准容器时无法预览溜槽放置」与本改动靶点完全对应)。有 1 条非阻塞的 ⚠️ 观察 + 2 条可选改进。

🔴 关键

  • 无。

⚠️ 警告(非阻塞,建议关注)

  • PlacementInteractions.java:40-45 — 新的早退分支使「只读」工具类产生了带副作用的能力查询,且它跑在客户端逐帧渲染路径上。
    • 类注释明确写着:/** 只读判断交互优先级,预览阶段不能调用 use 方法打开界面或改变世界。 */,而 Level#getCapability(Capabilities.ItemHandler.BLOCK, pos, face) 会实际调用注册的 provider。对 CRATE / SHULKER_CONTAINER / LARGE_CRATE / HYPERDIMENSION_STORAGE_STATION 这些方块,provider 是 CapabilitiesEventListener::storageItemHandler(CapabilitiesEventListener.java:303-315),它并非只读:
      1. getId()==null 时 be.setId(UUID.randomUUID()) → setChanged() + TerminalSourceManager/StorageComparatorManager.registerIfApplicable + level.sendBlockUpdated(...)(StorageBlockEntity.java:46-60);
      2. Storages.get().getOrCreate(id, clazz) → 客户端 Storages.get() 返回静态单例 Storages.CLIENT_COPY(Storages.java:41-43),会往这个静态 map 里插入一个空占位存储并 setDirty();
      3. crate.refreshDispose()。
    • 实际影响有限(已逐个核实:两个 Manager 与 refreshDispose 均有 level.isClientSide() 早退,ClientLevel#sendBlockUpdated 为空实现,CLIENT_COPY 不落盘),所以不会崩、不会写存档;代价是持溜槽对准存储类方块时逐帧多做一次带副作用的查询(updatePreview() 由渲染事件调用,见 LargeBlockPlacePreviewEventListener.java:242)、客户端可能给 BE 写入一个与服务端不一致的随机 storage_id(待下一次 update tag 覆盖),以及 CLIENT_COPY 里累积占位项。
    • 建议(可选):把该判定抽成 ChuteBlockItem 的静态方法并在两处共用,同时在方法上补一句注释,说明此处的容器能力查询在客户端并非纯读;或让客户端预览走一条不触发懒初始化的只读探测。

💡 建议

  • 判定条件与 ChuteBlockItem.onItemUseFirst 最后一元表达式是手工复制的重复逻辑(ChuteBlockItem.java:37-39 与新增块逐字相同)。本 PR 的目的正是「让预览与实际交互优先级一致」,一旦 onItemUseFirst 的优先级以后改了,这里会静默漂移。该类已经以 isStorageInteraction(context) 的形式与 item 共享逻辑,建议同样抽一个:
    public static boolean isItemHandlerTarget(UseOnContext context) {
        return context.getLevel().getBlockEntity(context.getClickedPos()) instanceof IItemHandlerHolder
            || context.getLevel().getCapability(
                Capabilities.ItemHandler.BLOCK, context.getClickedPos(), context.getClickedFace()) != null;
    }
    由 onItemUseFirst 与本处共同调用。
  • 副手(OFF_HAND)语义现在不一致,建议确认是否为本意:新增分支限定 MAIN_HAND(与 PR 描述一致),而紧邻的仓储端口分支 PlacementInteractions.java:52-54 对副手溜槽返回 true(因为 isStorageInteraction 要求主手,故 !isStorageInteraction == true)。即:副手溜槽对准板条箱/箱子仍无预览(落到 L56 的 MenuProvider 规则),对准仓储端口却有预览。若「副手一律不覆盖主手容器交互」是设计意图,则仓储端口那支是既有例外,建议加一行注释把「手性」规则写清楚(不阻塞本 PR)。

🟢 看起来不错

  • 取的是原始点击坐标系:context.getClickedPos()/getClickedFace() 与 onItemUseFirst 完全一致,且调用方传的是未经 BlockPlacementPicking.forPlacement 变换的 mc.hitResult(LargeBlockPlacePreviewEventListener.java:153-154)——刻意没有用 AbstractMultiPartBlock#getMainPartPos 的 menuPos,这是对的,因为 onItemUseFirst 查的就是原始 pos。
  • 插入位置正确:放在 isSecondaryUseActive()(潜行)之后、isStorageInteraction 早退之后,因此创造板条箱 / 仓储端口的中心-边缘判定、潜行优先级均未被破坏——与 PR 描述逐条吻合。
  • 与菜单规则的先后关系正好理顺:能力存在 ⇒ onItemUseFirst 会走 useOn ⇒ 方块确实会放下 ⇒ 显示预览是正确的;反之只有 MenuProvider、没有物品能力的工作台/铁砧仍不预览(回归面正确)。
  • IItemHandlerHolder 分支不可省:例如 ProcessingTableBlockEntity 实现了 IItemHandlerHolder 但没有注册 Capabilities.ItemHandler.BLOCK(已在 CapabilitiesEventListener 中核对),删除该分支会漏掉处理台这一情形;保留它是必要的。
  • 风格/校验无问题:最长新增行 121 字符 < style.xml LineLength max=140;import 顺序合规;无 ghost、无 EOF 缺换行。

📋 声称验证表

声称 状态 对应证据
按 onItemUseFirst 优先级检查原始点击位置的 IItemHandlerHolder 或点击面物品能力 ✅ 表达式与 ChuteBlockItem.java:37-39 逐字一致;用 getClickedPos/getClickedFace 而非 menuPos
保留创造板条箱及仓储端口的中心/边缘判定 ✅ L38(isStorageInteraction 早退)与 L52-54 均未改动且排在新增分支之前
副手仍遵循主手容器交互优先级 ✅(附⚠️) 新增分支 MAIN_HAND 门控成立;但仓储端口分支对副手返回 true,手性规则不统一(见建议)
潜行行为保持不变 ✅ isSecondaryUseActive() → true 仍在前
其他方块行为保持不变 ✅ 分支以 instanceof ChuteBlockItem 门控,且只在 PLACEMENT_PREVIEW 标签内的方块上触发(chute / magnetic_chute / overflow_chute / item_splitter 四个 item 都是 ChuteBlockItem)
fixed #5120 ✅ issue #5120 仍 open(State: open,label 🐛 Bug),症状与靶点一致;原版箱子/木桶只提供能力而非 IItemHandlerHolder,由能力分支修复

补充核对项(不阻塞):预览在客户端执行,修复依赖「客户端也存在方块物品能力」。AnvilCraft 自身经 @EventBusSubscriber(modid=...)(无 Dist 限制,CapabilitiesEventListener.java:37)注册 ⇒ 双侧可用;NeoForge 的原版容器 provider 亦按侧注册。建议合入前在游戏内对原版箱子/木桶实机确认一次预览恢复(并被 #5120 报告者路径覆盖)。

结论: APPROVE(可合并) — 逻辑正确、改动最小,可直接合并;上述 ⚠️ 与两条 💡 均为非阻塞改进,建议顺手把判定抽成共享方法以免后续漂移。

PR 标题 Fix chute placement preview over containers 修复溜槽对准容器时的放置预览 已准确且中英双语,无需修改(另:gh auth status 显示 token 已失效,本次亦未执行任何 gh 改动,符合 webhook 约束)。

🧪 测试建议

被测目标 推荐测试场景 优先级
PlacementInteractions.allowsPlacement() 主手溜槽对准原版箱子/木桶(能力路径)→ 有预览;对准创造板条箱/仓储端口中心 → 无预览、边缘 → 有预览;DEBUG-HIT 面朝向各 6 面各测一次 🔴
同上 副手溜槽对准容器 → 确认预期行为(当前无预览),锁定手性规则 🟡
PlacementInteractions.allowsPlacement()(回归) 工作台/铁砧/按钮/门/活板门 → 仍不显示预览 🟡
存储类 provider 副作用 持溜槽反复扫过板条箱/大型板条箱/潜影集装箱 → 客户端无异常、存储终端显示不错乱 🟡

由 Hermes Agent 审查

@Gugle2308

Copy link
Copy Markdown
Collaborator

代码审查摘要 — PR #5121

操作: edited
范围: 1 个文件 (+9/-0) — src/main/java/dev/dubhe/anvilcraft/util/PlacementInteractions.java
完整性核对: API files = 1 file / additions 9 / deletions 0,与下载到的 diff 一致(md5 d7ed7d3e…,10 个 + 行 = 9 代码 + 1 头,3 个 hunk,无 ghost、无 No newline、无尾随空白)

🟢 核心结论:修复方向正确,判据与真实交互逐字一致

  • 新增判据与 ChuteBlockItem.onItemUseFirst:37-39 的通用分支逐字相同(sed 对比两处表达式确认)→ 预览显示"会放置"时,服务端确实走 this.useOn(context)(放置),而不是 super.onItemUseFirst(把交互让给方块 use/GUI)。主手溜槽对准容器这条路径已经对齐。
  • 分支位置正确:位于 isStorageInteraction(...) → false(:38)与 isSecondaryUseActive() → true(:39)之后 → 创造板条箱/仓储端口的中心点击(服务端 PASS 交回方块 use)仍不显示预览;潜行仍显示预览(isStorageInteraction 潜行时本返回 false,服务端走放置)。与 PR 描述的三条声称全部吻合。
  • 多方块主部件解析未被破坏:新分支只在点击位置本身暴露 handler 时触发,与 onItemUseFirst 同样用 getClickedPos();仓储端口/大板条箱这类 BE 在主部件的多方块仍由 :52-57 的 getMainPartPos 分支处理(大板条箱走 block 级 provider,自带主部件解析)✅
  • 编译/风格:getCapability(Capabilities.ItemHandler.BLOCK, pos, face) 与同分支 ChuteBlockItem 的调用形式完全相同(无签名风险);import 顺序、行宽(最长 121 ≤ style.xml LineLength 140)、无尾随空白、无 EOF 缺行、未引入非 JSpecify 空注解(符合 AGENTS.md)。

⚠️ 警告(建议本 PR 内处理)

  1. 预览谓词不再是"只读"的,而且每帧执行。 文件头注释写着"只读判断交互优先级,预览阶段不能调用 use 方法打开界面或改变世界",但 Level.getCapability(...) 会执行任意已注册的能力 provider,本 mod 自己的 provider 就带副作用 —— CapabilitiesEventListener:303 storageItemHandler(注册给 CRATE,并经 block 级 provider 用于 LARGE_CRATE/SHULKER_CONTAINER/HYPERDIMENSION_STORAGE_STATION):
    UUID id = be.getId();
    if (id == null) { id = UUID.randomUUID(); be.setId(id); }   // 改 BE 状态
    IItemHandler handler = Storages.get().getOrCreate(id, be.getStorageType().clazz()).getItems();
    • StorageBlockEntity.setId:45-57 → setChanged()(客户端无害的另有 TerminalSourceManager/StorageComparatorManager 注册,两者都有 isClientSide 守卫;sendBlockUpdated 在 ClientLevel 是空实现),于是客户端 BE 会被写入一个随机 UUID(服务端之后另行分配;getUpdateTag 带真实 storage_id、loadAdditional 直接赋值可覆盖,但仍是一次状态污染)。
    • Storages.get() 在客户端返回静态 Storages.CLIENT_COPY(Storages:41-44),而 getOrCreate:111-118 在缺项时 newInstance(id) 建一个空存储并缓存 + setDirty()。即:持溜槽把准星停在板条箱上,客户端 CLIENT_COPY 里会出现一条服务端从未下发的条目;此后客户端侧读 Storages.get().get(id) 的路径(如 StorageBlockEntity.getTotalCount():176-190、isCraftingUnlocked():171)看到的是"存在但为空",而不是"不存在"。
    • 该调用在 LargeBlockPlacePreviewEventListener.renderGhost → updatePreview()(RenderLevelStageEvent.AFTER_BLOCK_ENTITIES)中每帧执行,除状态副作用外还每帧构造 HoneyCauldronWrapper(level,pos)(CapabilitiesEventListener:171)、ReadOnlyItemHandlerWrapper.wrap(...)(:126/:141)等包装对象,属于渲染热路径上的无谓分配。
    • 建议二选一:(a) 保持预览谓词纯净 —— 主手判据用 IItemHandlerHolder(已覆盖全部 AnvilCraft 机器/溜槽),需要放行的容器类改用类型白名单(CrateBlock/LargeCrateBlock/ShulkerContainerBlock/HyperdimensionStorageStationBlock/HoneyCauldronBlock/LargeCauldronBlock/虚空物质块…),不触发存储 provider;或 (b) 按交互目标缓存判定 —— 监听器已有 currentPos/currentItem 等缓存字段,缓存键需含 pos + face + getClickLocation()(isStorageInteraction 依赖边缘判定),只在目标/命中点变化时重算,同时消掉每帧分配。
  2. 客户端能力的可用性/纯净性无法由本 PR 保证。 客户端包此前没有任何 getCapability(...) 调用(按 src/main/java/dev/dubhe/anvilcraft/client/** 全量 grep 为 0),这条新依赖带来两个不确定:① 原版容器(箱子/桶/漏斗)的 item handler 是否在客户端可查,直接决定"对准普通箱子无预览"能否修好,建议实测确认(NeoForge 是否在客户端注册 vanilla provider);② 第三方 provider 若只在服务端返回非空、或自身有副作用,预览判据就会与服务端真实决策再次不一致 —— 恰是本 PR 想消除的那类问题。若不能保证,方案 1(a) 更稳。
  3. 存储方块(板条箱等)的预览被顺带打开,请确认是预期。 新分支位于 getMenuProvider(...) != null || entity instanceof MenuProvider || entity instanceof StorageBlockEntity(:56-57)之前,所以主手溜槽对准 CrateBlockEntity(extends StorageBlockEntity,CRATE 注册了 item handler)时预览由"不显示"变为"显示放置"。这与 onItemUseFirst 的通用分支(useOn → 放置)自洽,但超出"恢复对准容器时的预览"的字面范围:板条箱/大板条箱/潜影容器并不像创造板条箱/仓储端口那样参与 isStorageInteraction 的中心/边缘判定,如果设计上它们也应如此,本 PR 是用一个"放置预览"盖住了那层语义差异。

💡 建议

  • 抽出共享判据:新增表达式与 ChuteBlockItem.onItemUseFirst 的通用分支逐字相同,两处手写同步极易漂移(本文件已有先例:isStorageInteraction 被两边共用)。建议在 ChuteBlockItem 加静态方法(如 hasItemHandlerTarget(UseOnContext))供两边调用,并注明"本判据必须与 ChuteBlockItem.onItemUseFirst 的优先级保持一致"。
  • 副手分支:新判据限定 MAIN_HAND,而 onItemUseFirst 通用分支不看手。我按原版手序核对过:对准容器时主手的交互(方块 use/GUI)会先消费动作,副手一般不会被尝试,所以"副手不显示预览"是自洽的;建议把这点写进代码注释或 PR 描述("副手仍遵循主手容器交互优先级"容易被读成实现细节而非有意约束)。
  • 本变更纯客户端视觉判定,仓库无 src/test、无 gametest 目录,建议在 PR 附一段手动验证清单:主手/副手 × 创造板条箱中心/边缘 × 仓储端口 × 板条箱 × 大板条箱 × 原版箱子 × 潜行。

📋 声称验证表

声称 状态 依据
按 onItemUseFirst 优先级检查原始点击位置的 IItemHandlerHolder/点击面能力 ✅ 与 ChuteBlockItem.java:37-39 逐字一致
保留创造板条箱及仓储端口的中心/边缘判定 ✅ isStorageInteraction → false(:38)仍在新分支之前;该方法潜行返回 false
副手仍遵循主手容器交互优先级 ✅(口径见建议 2) 新分支限定 MAIN_HAND,副手路径未改动
潜行及其他方块行为保持不变 ✅ :39 未动,其后分支未动
仅修改 PlacementInteractions.java、未提交测试 ✅ API: 1 file/+9/-0;仓库无测试源集

结论: COMMENT(建议按 ⚠️1 调整后合并) — 修复本身正确、最小、与真实交互优先级对齐;唯一实质担忧是它把"只读预览判定"变成了一次每帧的能力查询,而本 mod 的存储 provider 在客户端有状态副作用(随机 UUID + CLIENT_COPY 幽灵条目),与文件自身契约冲突。

建议标题(gh auth status 显示 token 已失效,本次未自动修改):Fix chute placement preview over item-handler blocks 修复溜槽对准容器时的放置预览 —— 原标题可用,但 "over containers" 容易被读成只针对容器方块,实际判据是"任何暴露物品处理能力的方块(含机器/能力方块)"。

🧪 测试建议

被测目标 推荐场景 优先级
PlacementInteractions.allowsPlacement() 主手溜槽 × {创造板条箱中心/边缘、仓储端口、板条箱、大板条箱、原版箱子、AnvilCraft 机器、潜行} 共 14 组,逐组与服务端 ChuteBlockItem.onItemUseFirst 的实际结果对照 🔴
同上(副作用) 准星停在板条箱上数秒后,检查客户端 BE getId() 是否被改成随机值、Storages.CLIENT_COPY 是否多出条目 🔴
原版容器能力 客户端 Level.getCapability(Capabilities.ItemHandler.BLOCK, …) 对箱子/桶是否非空(决定 issue 场景是否真修好) 🟡

由 Hermes Agent 审查(diff 经 patch-diff.githubusercontent.com 匿名获取,交叉核对了 head 623a6677 与 base 956c11b4 的源码)

@Gugle2308

Copy link
Copy Markdown
Collaborator

💾 Self-improvement review: Patched SKILL.md in skill 'perf-optimization-pr-review' (1 replacement).

@Gugle2308

Copy link
Copy Markdown
Collaborator

💾 Self-improvement review: Patched references/placement-preview-gate-mirroring.md in skill 'tool-block-interaction-pr-review' (1 replacement). · Patched SKILL.md in skill 'tool-block-interaction-pr-review' (1 replacement).

@PigeonNian
PigeonNian merged commit 9cb2569 into Anvil-Dev:dev/1.21/1.6 Oct 2, 2026
2 checks passed
@Gugle2308

Copy link
Copy Markdown
Collaborator

PR #5121 状态为 closed,按 webhook 规则(closed/labeled → 停止)不进行审查、不发布评论。

已停止,未下载 diff、未执行任何 gh 评论命令。

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.

[Bug] 溜槽放置预览

4 participants