Skip to content

Fix bugs of smart block placer.修复了智能方块放置器的bug - #4782

Merged
QiuShui1012 merged 2 commits into
Anvil-Dev:dev/1.21/1.6from
PigeonNian:placerfix/1.21/1.6
Sep 10, 2026
Merged

Fix bugs of smart block placer.修复了智能方块放置器的bug#4782
QiuShui1012 merged 2 commits into
Anvil-Dev:dev/1.21/1.6from
PigeonNian:placerfix/1.21/1.6

Conversation

@PigeonNian

@PigeonNian PigeonNian commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

- 规范顺序索引和存储索引的约定,避免错误计算
- 调整搬运方块的更新标志位,使用兼顾邻居和形状校验的标志
- 添加副部件存活校验跳过逻辑,避免误判
- 优化多方块搬运时的可破坏判断逻辑
- 实现穿梭效应进度检测,触发邻居放置器搬回时的触发器
- 放置器完成放置后复位游标,保证放置顺序稳定
- 修正有序点位构建顺序为层→行→列,保持与普通模式一致
- 增强 Jade 集成的脉冲发生器显示逻辑,空闲状态下进度条可见
- 提升放置器相关声音效果的音量,改善用户体验
@Gugle2308

Copy link
Copy Markdown
Collaborator

⚠️ Dangerous command requires approval:

for i in 4738 4739 4533; do echo "=== issue $i ==="; curl -s "https://api.github.com/repos/Anvil-Dev/AnvilCraft/issues/$i" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d['title']); pri...

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/$i — or: vet https://api.github.com/repos/Anvil-Dev/AnvilCraft/issues/$i (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

分析完成。以下是审查意见(将自动发布到 PR):


代码审查摘要 — PR #4782

操作: opened | 范围: 5 个文件(5 Java,0 新增/删除)/ 316 行 diff / +109 −31
分支: placerfix/1.21/1.6dev/1.21/1.6
标题建议: 英文标题过于笼统(且多了一个句点),建议改为 Fix smart block placer placement order, block updates and survival checks 修复智能方块放置器的放置顺序、方块更新与存活校验。已尝试 gh pr edit,但本机 gh auth status 显示 token 失效(Failed to log in to github.com account Gugle2308),故未能代为修改。

编译面核对(先行确认,均通过)

  • TriggerUtil.placerShuttle(Level, BlockPos) 在目标分支 dev/1.21/1.6 已存在(TriggerUtil.java:164),新 import 合法 ✅
  • BlockPlacementUtil.isSecondaryMultiblockPart 为 public static,已存在 ✅
  • 旧常量 MULTIBLOCK_UPDATE_FLAGS 无残留引用 ✅
  • getState() / getPhaseDuration()PulseGeneratorBlockEntity 类级 @Getter 生成 ✅
  • 同实例私有成员访问(neighbor.operation / neighbor.expectedShuttleTarget / getOrderedPositionTargets())合法 ✅

📋 声称验证表

声称 状态 依据
fixed #4738 放置顺序出错 buildOrderedPositions 循环改为 row 外层/column 内层(行=远→近、列=左→右);BlueprintLayout.getStorageIndexForOrder 由「存储序→顺序」重排改为恒等,三处调用(SBP:452/493/977)与顺序模式约定一致;与 26.1 分支 buildOrderedPositions(row, col) 排序结果一致
fixed #4739 打断后不从头放置 新增 resetPositionIndex(),放置成功后游标归零(SBP:353
fixed #4533 搬运不发更新/不校验合法性 ⚠️ 更新标志位与 canSurvive 预检已加,但搬运多方块的取源动作引入回归(见 🔴1)

🔴 关键

1. BlockPointer 清空源方块时改用 MOVE_UPDATE_FLAGS,会把多方块结构的另一半连带摧毁,随后 setBlock 因「状态未变化」返回 false → restoreParts 回滚:门/床/高植物/自定义多方块机器现在每次搬运都失败并静默复原。

机制链(BlockPointer.java:52 / 168-177):

  1. UPDATE_ALL = UPDATE_NEIGHBORS|UPDATE_CLIENTS = 3,不含 UPDATE_KNOWN_SHAPE(16);旧值 82 含 16。Level.setBlock(flags & 16) == 0 && recursionLeft > 0 才执行邻居形状更新,因此本次改动让「清源」这一步也触发了形状更新。
  2. 清掉门的下半 → DoorBlock.updateShape(DOWN) 发现同部件消失 → 返回 AIR → Block.updateOrDestroydestroyBlock(pos, true),上半被摧毁(BedBlock.updateShapeAbstractMultiPartBlock.updateShape(multipart 包,第 130-146 行)同理;这也是本仓库 AbstractMultiPartBlock.removePartsAndUpdate() 特意用 setBlock(bp, ..., 3, 0)recursionLeft=0 来规避的级联)。
  3. 循环随后对「已被摧毁的那一半」执行 setBlock(pos, source.state().getFluidState().createLegacyBlock(), ...),即 Blocks.AIR.defaultBlockState(),与现状态为同一实例 → LevelChunkSection.setBlockState 返回 null → Level.setBlock 返回 false → 命中 if (!level.setBlock(...)) { restoreParts(...); return false; } → 整批回滚。

后果:多方块(parts.size() > 1)的 MOVE 操作永远无法完成(每周期重试并复原一次);且被连带摧毁的那一半若其掉落表未带「仅主部件掉落」条件(AnvilCraft 自己的 multipart 用 preventCreativeDropFromMainPart 而非掉落表条件实现,掉落在主部件上),每次失败尝试都会额外掉出一份物品 —— 即可重复的物品复制。门/床/高植物的原版掉落表带 half=lower / part=foot 条件,因此这几类目前只是「搬不动」,自有多方块的掉落风险需实测确认。

建议:

  • 「清源」步骤不要用 UPDATE_ALL:恢复为 Block.UPDATE_CLIENTS | Block.UPDATE_KNOWN_SHAPE | Block.UPDATE_MOVE_BY_PISTON(原版活塞清源即用 18),目标放置继续用 MOVE_UPDATE_FLAGS。目标格保留完整更新即可满足 [Bug] 智能放置器移动模式移动方块不发出更新或不检查放置是否合法 #4533 的「发出应有的方块更新 / 存活校验」(canSurvive 预检 + 第 198-202 行的 updateNeighborsAt/updateNeighbourForOutputSignal 不受影响),因此这个拆分不会丢掉本 PR 想修的东西;或
  • 参照 AbstractMultiPartBlock.removePartsAndUpdaterecursionLeft = 0,一次性清空同批部件,避免中途互相摧毁+掉落;并
  • 给清源加「已是目标状态则跳过」的容错(if (!level.getBlockState(p).equals(clearedState) && !level.setBlock(...))),避免任何外部摧毁路径再次把 no-op 当成失败。

验收方法:MOVE 模式下搬运一扇有支撑的门 / 一张床,观察是否永远「闪一下就复原」;在回滚分支打日志可直接看到 setBlock 返回 false。

⚠️ 警告

2. BlockPointer.point() 删除 multiblock 例外后,含方块实体的多方块机器无法再被 MOVE 模式选中(行为回归,PR 描述未提及)。 isPushable(state, level, pos, facing.getOpposite(), false, facing.getOpposite()) 的本调用点两个方向参数相同,对 hasBlockEntity() 的方块恒为 false;旧条件 && (!multiblock || destroySpeed < 0) 正是为了放行「多方块且可破坏」的部件(即带 BE 的机器部件)。受影响的确定对象:OverseerBlockTransmissionPoleBlockRemoteTransmissionPoleBlockGiantMonolithCoreBlock(均 implements EntityBlock),以及任何第三方带 BE 的 multipart。MOVE 模式经 ModTargetPointers.BLOCKBlockPointer.Type.point(),故直接被拒。若这是有意的防刷限制,请写进 PR 描述;否则建议恢复该例外,或改用独立的「可搬运部件」判定而不是 piston 语义。

3. PulseGeneratorProvider 空闲分支是死代码,且与新注释不符。 isProcessing() 定义为 state != State.DEFAULT,故 processing == false 时 state 必为 DEFAULT,outputting = "OUTPUTTING".equals(state) 恒为 false → progress = outputting ? 1.0 : 0.0 永远取 0.0,注释里「空闲时…显示完整进度条」不会发生(进度条只会是 0 长度的蓝色条)。另外 total 空闲时固定为 max(waitingTime,1),玩家看到的仍是一个空条而非延迟进度。请确认期望表现:要么用 isOutputting()(含 outputInvert),要么把注释改成「空闲显示空进度条」。

4. MOVE_UPDATE_FLAGS 注释中「使箱子、溜槽等方块在搬运时不被视为破坏而掉落内容物」与实际路径不符。 箱子/溜槽含方块实体,point() 在改动前后都不会接受它们(isPushable 对此恒 false),根本进不到本条搬运路径;配合问题 2,注释会让人误以为「带 BE 方块是可搬运的」。建议删除或改写该句。

5. expectedShuttleTarget(瞬态字段)清理点偏少。 1.21 侧仅在 togglePositionSBP:716)与 setPickupModeSBP:956)清空;26.1 分支的同一功能在 4 处清空(断电停止工作时、setPickupModetogglePositionapplyDiskData 装载磁盘数据时)。断电重上电或换磁盘后残留的旧位置标记,可能让一次无关的放置误触发穿梭进度。仅影响进度成就,无物品影响,建议补齐。

💡 建议

  • checkShuttlePlacementSBP:365-391)在每次放置成功后遍历 6 个方向调用 level.getBlockEntity(),并对邻居调用 getOrderedPositionTargets()(每次重建最多 125 个 BlockPos 的列表)。可先做廉价短路(this.operation != MOVE || this.target != POSITION 已在方法内但排在 expectedShuttleTarget 判定之后,顺序合理),并考虑只在邻居的 currentPlacementIndex 可能命中时才重建列表。
  • resetPositionIndex() 与可替换方块存在一个交互:目标判定用 state.canBeReplaced(),草/雪/海草等可替换方块即使已被放上方块仍算「可用」,游标归零后会被反复选中同一格(0 号点位)而不再推进。建议在目标判定里对「本机放置过的位置」补一层排除(例如记录上次成功放置的位置并跳过)。
  • 注释小错:BlockPointer 新注释把「床尾」列为副部件,而 isSecondaryMultiblockPart 判定的副部件是床 HEAD(床头)。请统一措辞。
  • 渲染器三处音量统一为 0.6fSmartBlockPlacerRenderer:282/291/307)方向正确、彼此一致;retract 由 0.8 降到 0.6 属主观调整,建议在 PR 描述里一句话说明(不影响功能)。

🟢 看起来不错

  • getStorageIndexForOrder 恒等化把蓝图模式的放置顺序与顺序模式、存储布局对齐,三处调用点全部覆盖,且 getPosition(存储→世界坐标)未改动,几何映射不受影响 —— 修改范围干净。
  • canSurvive 预检放在构建 movingParts 的循环内、任何世界改动之前,return false 无副作用需要回滚,时机正确;跳过副部件(门/高植物上半、床 HEAD、multipart 非主部件)的理由与 isSecondaryMultiblockPart 的实现一致,正是 [Bug] 智能放置器移动模式移动方块不发出更新或不检查放置是否合法 #4533「浮空门/浮空拉杆」的根因修法。
  • 结论中的顺序修法与 26.1 分支 buildOrderedPositions(row, col) 排序语义一致,说明是与活跃分支对齐的做法,值得肯定。
  • restoreParts 与目标放置共用标志位在回滚路径上是安全的(复原顺序为先目标后源,副部件依赖关系自洽)。

🧪 测试建议

被测目标 推荐场景 优先级
BlockPointer.moveToPos 清源步骤 MOVE 搬运门/床/高植物/自有多方块机器:应成功且无回滚、无额外掉落 🔴
BlockPointer.point Overseer/TransmissionPole/RemoteTransmissionPole/GiantMonolithCore 取指针:确认是否为期望的「不可搬运」 🔴
blockUpdateFlags 搬运后目标格是否触发邻居/形状更新,源格周围附属方块(拉杆/火把)行为是否符合预期 🟡
buildOrderedPositions + resetPositionIndex 5×5×5 选区、含可替换方块的目标格、打断后重启:顺序应从远到近、从左往右且每轮从头开始 🟡

结论: REQUEST_CHANGES#4738 / #4739 的修复干净且与 26.1 分支对齐;#4533 的修法方向对(补更新 + 存活预检),但「清源」步骤套用 UPDATE_ALL 会把门/床/多方块机器的另一半连带摧毁并触发 no-op-false 回滚,使这类搬运彻底失效(且对自有多方块存在物品复制风险),建议拆分标志位并给清源加容错后再合并;同时请确认问题 2 的带 BE 多方块机器不可搬运是否为有意为之。


由 Hermes Agent 审查

@Gugle2308

Copy link
Copy Markdown
Collaborator

💾 Self-improvement review: Patched SKILL.md in skill 'minecraft-mod-pr-review' (1 replacement). · Patched references/block-update-flags-multipart-destruction.md in skill 'minecraft-mod-pr-review' (1 replacement).

@Gugle2308

Copy link
Copy Markdown
Collaborator

代码审查摘要 — PR #4782

操作: edited → 按完整流程复查(标题/描述变更,逐条重新核对 claim)
范围: 5 个文件(5 Java)/ +109 −31 / 1 个 commit(240d168c5d);无新增/删除文件,无 ghost 文件,无 EOF 缺失
基线: dev/1.21/1.6 @ c9026b2018(已用 GitHub API 确认 merge base)

📋 声称验证表

声称 判定 证据
fixed #4738(顺序应为:最外行自左向右 → 再往更近一行) buildOrderedPositions() 循环改为 行(远→近) 外层 / 列(左→右) 内层SmartBlockPlacerBlockEntity.java:683-685);BlueprintLayout.getStorageIndexForOrder() 由列优先置换改为恒等映射BlockPlacementUtil.java:408)。与 getPosition()row = 深度column = 左右)以及蓝图装载端 position = row * GRID + columnSmartBlockPlacerBlockEntity.java:831)的存储约定完全一致 → 正是 #4738 期望的行为
fixed #4739(打断后应从头发起) 新增 resetPositionIndex()(L533),成功路径改为 checkShuttlePlacement() + resetPositionIndex()(L352-353);失败/不可用路径保留 advancePositionIndex,语义自愈
fixed #4533(放置合法性校验 + 方块更新) 新增 canSurvive 校验(BlockPointer.java:143-146);更新标志由 UPDATE_CLIENTS|UPDATE_KNOWN_SHAPE|UPDATE_MOVE_BY_PISTON 改为 MOVE_UPDATE_FLAGS = UPDATE_ALL | UPDATE_MOVE_BY_PISTON(L52),补上邻居更新;浮空拉杆类方块现在会被拒绝
「不会再移动多方块」 ⚠️ 部分成立 移除 (!multiblock || destroySpeed < 0) 宽容后,isPushable(..., allowDestroy=false) 会拒绝门/床/高草等 PushReaction.DESTROY 多方块(浮空门消失的原因)。但 AnvilCraft 自有 AbstractMultiPartBlock 多方块仍可搬运:large_fluid_tank/acceleration_ring 等用 initialProperties(() -> Blocks.IRON_BLOCK)large_cauldronBlocks.CAULDRON,这类属性不是 BLOCK/DESTROY 反应 → isPushable 仍返回 true。若意图是彻底禁止,需在 point() 内对 isMultiblockBlock(state) 直接 return null;若只针对 #4533,请把描述改为「不再搬运门/床/高草等原版多方块」
(描述未提)穿梭/乒乓进度接线 ➕ 新增功能 base 上 ModCriterionTriggers#placer_shuttleAnvilCraftAdvancement 与生成数据 advancement/anvilcraft/placer_shuttle.json 都已存在但没有任何调用点(死代码);本 PR 通过 checkShuttlePlacement(L365-390)接通。建议在描述中单独列一条
(描述未提)Jade 进度条常显 + 音效音量 0.4/0.8 → 0.6 ➕ 附带改动 与三个 issue 均无关,建议描述补列(见下方建议)

🔴 关键问题

未发现阻塞性缺陷。以下几处是本次审查中唯一需要维护者确认的点(均按 ⚠️/💡 列出)。

⚠️ 警告

  1. resetPositionIndex() 的扫描代价是 O(N²) tickSmartBlockPlacerBlockEntity.java:533
    索引归零后,每个 tick 只能跳过 1 个已占用点位isTargetAvailable 失败 → advancePositionIndexreturn,L334-347)。因此在「顺序填满」这一最常见场景下,放置第 k 格需要先空转 k 个 tick,填满 N 格总耗时从 O(N) 变成 O(N²)(5×5×5 = 125 格选区约 1+2+…+125 ≈ 7.8k tick)。建议保留「从头开始」语义的同时,在同 tick 内用循环跳到第一个可用点位,避免速度回退。
  2. 蓝图模式放置顺序被一并改变BlockPlacementUtil.java:408 + SmartBlockPlacerBlockEntity.java:683
    getStorageIndexForOrder 的恒等化不仅影响 POSITION 模式,也改变了 BLUEPRINT 模式的放置序列(原先按列优先,现在按行优先,配合 applyToBlueprintPosition 的「按 order 等正确方块」逻辑,节奏会变)。[Bug] 智能方块放置器的放置顺序出错 #4738/[Bug] 智能方块放置器有时放置顺序会乱 #4739 描述的都是普通点位模式,请确认这是有意为之并在描述中说明。
  3. expectedShuttleTarget 生命周期不完整(L95 / L716 / L956)
    目前只在 togglePosition()setPickupMode() 清理。若玩家把它切到蓝图模式、放置器本身被搬运、或邻居始终没把方块搬回,标记会残留,之后一次无关放置恰好落在该坐标即会误触发进度。仅影响成就判定(非世界状态),建议在 target 模式切换/位置变更处一并清理。

💡 建议

  1. PulseGeneratorProvider 空闲分支是死代码PulseGeneratorProvider.java:50-58):isProcessing() ⇔ state != DEFAULT,而 outputting ⇔ state == OUTPUTTING,所以 processing == falseoutputting 必为 false,progress = outputting ? 1.0 : 0.0 永远得到 0.0——注释里「空闲时按配置延迟时长显示完整进度条」并未实现。建议简化为 progress = 0.0f(或显式实现满格意图)。
  2. 音效音量与 26.1 分支不一致SmartBlockPlacerRenderer 统一为 0.6,而 qiushui/dev/26.1/1.6 同位置仍是 0.4/0.4/0.4/0.8。跨分支手感会不同,建议同步。
  3. 可能重复的更新调用MOVE_UPDATE_FLAGS 已含 UPDATE_NEIGHBORS,而 moveToPos 尾部仍有显式 level.updateNeighborsAt()hasAnalogOutputSignal 分支(L197-205),原版 setBlock 在 NEIGHBORS 时已做过同类通知,可考虑精简(非阻塞)。另外请确认 UPDATE_ALL 是否已包含 UPDATE_MOVE_BY_PISTON——若已包含则 OR 冗余(无害,注释仍有说明价值)。
  4. isSecondaryMultiblockPart 覆盖面:它只识别 床(HEAD)/门、高草(UPPER)/AbstractMultiPartBlock 非主部件。若还有「靠同批搬运的主部件提供支撑」的其它副部件语义方块,需在此补充,否则新加的 canSurvive 校验会误拒其移动。
  5. PR 标题可更具描述性,例如 Fix Smart Block Placer bugs: placement order, floating multiblocks, missing block updates 修复智能方块放置器放置顺序/浮空多方块/方块更新(本次 gh 凭据失效,未能代为修改)。

🟢 看起来不错

  • 顺序修正与存储约定交叉验证通过getPosition()row = position / gridSize(沿朝向的深度,gridRadius - row)、column = position % gridSizeright 方向)与蓝图装载端 position = row * GRID_SIZE + column(L831)一致,因此「方块 ↔ 坐标」绑定不变,只是序列变化,不存在错位/贴错方块的风险
  • canSurvive 放在 isTargetAvailable 之后、清除源方块之前,失败时世界尚未被改写;且校验用的正是最终落地的 partTargetState(已 clearWaterlogged / 已套用蓝图规则),语义正确。
  • Jade 服务端补 key 向后兼容:无 key 时 getBoolean → falsetotal → 0total > 0 守卫同时避免了 getPhaseDuration() 为 0 时的除零/NaN(PulseGeneratorBlockEntityphaseStartGameTime < 0 || phaseDuration <= 0 时返回 0)。
  • 穿梭进度只在回程真正发生时发奖(先在邻居上标记、邻居搬回时才触发),不会双向各触发一次;判定条件同时校验邻居的 operation/target/source,比 26.1 分支的宽松判断更严格。
  • MOVE_UPDATE_FLAGS 的语义注释清楚(含 UPDATE_MOVE_BY_PISTON 防止箱子/溜槽在搬运时掉落内容物),与本文件既有的 removeBlockEntity 先摘 BE 的做法一致。

🧪 测试建议

被测目标 推荐场景 优先级
BlockPointer.moveToPos() 拉杆/高草/门/告示牌等 canSurvive 依赖型方块移动到无支撑格 → 应拒绝且世界无残留;副部件(高草上半)不误拒 🔴
buildOrderedPositions() / getStorageIndexForOrder() 5×5×5 全选点位 × 4 朝向 × upsideDown,核对实际放置序列与存储索引(行优先)逐格一致 🔴
resetPositionIndex() 放置中途打断/移除已填格 → 下一轮必须从最外行最左开始;并测量填满 125 格所需 tick 数 🟡
MOVE_UPDATE_FLAGS 搬运红石/比较器可读方块 → 邻居与模拟输出必须更新;搬运带内容物的箱子/溜槽 → 内容物不丢失 🟡
checkShuttlePlacement() 两台放置器对搬 → 仅触发一次 placer_shuttle;单向搬运(不回程)→ 不触发 🟢
PulseGeneratorProvider 空闲/等待/输出三态切换后端与客户端 Jade 数值一致性 🟢

结论: COMMENT —— 三处 bug 修复与 #4738/#4739/#4533 的期望行为逐条对得上,未发现阻塞性缺陷。合并前建议处理:①「不会再移动多方块」的表述与实现范围不符(自有多方块仍可搬运),②索引归零导致的 O(N²) 空转,③补齐描述中未提及的穿梭进度接线、Jade 与音效改动。


由 Hermes Agent 审查

@Gugle2308

Copy link
Copy Markdown
Collaborator

💾 Self-improvement review: Patched SKILL.md in skill 'coordinate-transform-pr-review' (1 replacement). · Patched SKILL.md in skill 'pr-fix-claim-verification' (1 replacement). · Patched SKILL.md in skill 'pr-diff-scope-reconciliation' (1 replacement).

- 详细描述了MOVE_UPDATE_FLAGS中UPDATE_MOVE_BY_PISTON的作用
- 解释了活塞式移动对onPlace和onRemove方法的影响
- 说明了部分方块根据该标志保留内容物的行为机制
@Gugle2308

Copy link
Copy Markdown
Collaborator

⚠️ Dangerous command requires approval:

for n in 4738 4739 4533; do echo "=== ISSUE $n ==="; curl -s "https://api.github.com/repos/Anvil-Dev/AnvilCraft/issues/$n" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('title'));...

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/$n — or: vet https://api.github.com/repos/Anvil-Dev/AnvilCraft/issues/$n (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.

@QiuShui1012
QiuShui1012 merged commit 8fd315d into Anvil-Dev:dev/1.21/1.6 Sep 10, 2026
2 checks passed
@Gugle2308

Copy link
Copy Markdown
Collaborator

已停止:本 PR #4782 的事件动作为 closed(已于 2026-09-10T06:32:03Z 合并)。按 webhook 审查约束第 1 条,closed/labeled 动作不进行审查、不发布评论。

如需审查,请在 reopened / synchronize 事件或新开 PR 上触发。

@Gugle2308

Copy link
Copy Markdown
Collaborator

⚠️ Dangerous command requires approval:

which unzip jar python3; ls -la /tmp/mc-1.21.1-sources.jar; python3 -c "import zipfile; z=zipfile.ZipFile('/tmp/mc-1.21.1-sources.jar'); n=z.namelist(); print(len(n)); print([x for x in n if 'Level.ja...

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

代码审查摘要 — PR #4782

操作: synchronize(placerfix/1.21/1.6dev/1.21/1.6
范围: 5 个文件(5 Java,0 新增,0 删除)/ 318 行 diff(+116 / -36),无 ghost 文件、无 EOF 缺失、无 TODO/密钥
备注: dev/1.21/1.6 上已存在本 PR 的 squash 合并 8fd315d060("Fix bugs of smart block placer…(#4782)"),因此以下问题建议以跟进 PR 处理。标题英文+中文格式已符合规范,无需改写(只是标题未体现本次同时新增的穿梭进度触发与 Jade/音效改动)。


🔴 关键

1. BlockPointer 的「清空源方块」阶段改用 UPDATE_ALL,会触发邻居形状更新,把同一批搬运的结构其余部件在中途销毁
MULTIBLOCK_UPDATE_FLAGSUPDATE_CLIENTS|UPDATE_KNOWN_SHAPE|UPDATE_MOVE_BY_PISTON)被替换为 MOVE_UPDATE_FLAGSUPDATE_ALL|UPDATE_MOVE_BY_PISTON)后,清空源格这一步(moveToPos 第一个循环、以及 restoreParts)也丢掉了 UPDATE_KNOWN_SHAPE。由于源格是在放置目标之前逐个清空的,setBlock(源格, 空气, 无 16 位标志) 会对邻居跑一遍邻居形状更新:

  • AbstractMultiPartBlock.updateShapeblock/multipart/AbstractMultiPartBlock.java:121-142)在期望相邻部件缺失/不符时返回 state.getFluidState().createLegacyBlock()(即空气)→ 巨型铁砧、大型蛋糕这类非 BE 多方块在搬运中途会被拆掉;原版门/高花的上半同理(DoorBlock/DoublePlantBlockupdateShape 亦返回空气)。
  • 现状后果:移动 2+ 部件结构时会「逐个碎裂再重建」(Level.destroyBlock 触发 2001 破坏粒子/音效 + gameEvent BLOCK_DESTROY),并会触发 onRemove 等其它监听。掉落物目前靠掉落表条件挡着(giant_anvil.jsonhalf=mid_center 掉落、门/高花有 half=lower 条件),即不出物品全靠掉落表兜底,风险偏高。
  • 代码库内既有约定正相反:block/sliding/ISlidingRail.java:165SlidingRailStopBlock.java:141 清空源格都用 0b1010010(= UPDATE_MOVE_BY_PISTON|UPDATE_KNOWN_SHAPE|UPDATE_CLIENTS,即旧常量),随后手动updateIndirectNeighbourShapes/updateNeighbourShapes(…, 0b0000010)——这正是原版活塞 moveBlocks 的写法(原版清空源格 flags=18);FishTankBlockEntity#removeFluidSilently 也用 UPDATE_CLIENTS|UPDATE_KNOWN_SHAPE 表达"静默移除"。这也正是本 PR 注释里"副部件存活取决于同批搬运的主部件"在运行时的同一条逻辑。

建议: 目标放置保留 UPDATE_ALL|UPDATE_MOVE_BY_PISTON(这才是修 #4533 的部分);清空源格与 restoreParts 继续使用含 UPDATE_KNOWN_SHAPE 的旧标志,红石/比较器通知交给已有的 updateNeighborsAt(source/target) + updateNeighbourForOutputSignal(或按 ISlidingRail 的顺序:先全部清空,再统一跑形状更新)。
建议验证: MOVE 模式搬运 ①橡木门 ②高花 ③巨型铁砧 ④大型蛋糕,观察中途破坏粒子/音效与结构完整性;再测一次搬迁失败时 restoreParts 的回滚路径。


⚠️ 警告

2. Type.point 收紧后,所有含方块实体的多方块(即 mod 自己的多方块机器)都不能再搬了
1.21 的 PistonBaseBlock.isPushablestate.hasBlockEntity() 返回 false,而被删掉的 (!multiblock || destroySpeed < 0) 正是为这类结构开的口子。受影响:大型坩埚、大型流体储罐、巨型石碑核心、大型板条箱、超维存储站等(LargeCauldronBlock implements MultiPartBlockEntity)。这与 PR 描述「不会再移动多方块」以及文档 101_smart_block_placer.md「该方块必须可被活塞移动,所以基岩和特定容器不会被移动」方向一致,但这是功能删除,建议确认是 v1.6 既定行为并写入 changelog。

同时新加的两处注释已与实际不可达的用例绑定,建议修正:

  • MOVE_UPDATE_FLAGS javadoc 举例「鱼缸、加工台据此在搬运时保留内容物」:FishTankBlock 有 BE(不可搬),且其 onPlacemovedByPiston 形参本身就是 boolean ignored(目标分支 FishTankBlock:312)。
  • canSurvive 跳过注释中的「床尾」同理(床有 BE,不可搬)。
  • 附带说明:moveToPospart.entity() 的保存/恢复与 IMoveableEntityBlock.notifyMoved 分支在自家 pointer 下已成死路径(仅附属 mod 通过 SmartBlockPlacerFindPointerEvent 提供自定义 pointer 时才可能走到),保留可以,但需知它现在不生效。

💡 建议

3. #4533 的「检查方块状态」还差一步:目标格放置前没有 Block.updateFromNeighbourShapes(state, level, pos)SlidingBlockSection.setBlock:159 就是这么做的),被搬运方块自身的形状不会按新邻居重算(栅栏/墙/红石线连接状态可能不刷新,直到后续有别的更新)。建议在 setBlock 前套一层,与目标端 UPDATE_ALL 配合最佳。

4. 穿梭触发的清理面不全:expectedShuttleTarget 已做到瞬态(不写 NBT)✅、togglePosition/setPickupMode 已清空 ✅;但 setSkipMissingMode、蓝图/点位模式切换、朝向变更(铁砧锤旋转)、方块被破坏等路径未清理。残留本身无害,但选区/模式已改的情况下仍可能延迟触发一次进度。建议在这些 setter 或 setRemoved 中一并清空,或在触发时再校验一次邻居条件。

5. 范围外改动(标题只写了 fix bugs):

  • Jade:空闲时 progress = 0.0(空条),注释却写"空闲时按配置的延迟时长显示完整进度条"——注释与实现不符(total>0 守卫已正确避免除零 ✅);另外 outputting 用的是原始状态字符串,未考虑 outputInvertPulseGeneratorBlockEntity.isOutputting() 才是含反向的语义),反向模式下进度条颜色/方向会与真实输出相反(既有问题,可顺手修)。
  • 音效:extend/开箱 0.4→0.6(提高),retract 0.8→0.6(降低),与 commit message「提升…音量」不符(实际是统一为 0.6),请确认 retract 变轻是预期。

🟢 看起来不错

  • 顺序修复自洽buildOrderedPositions 改为 层→行→列(下→上、远→近、左→右)后,BlueprintLayout.getPositionrelative(right, column-radius).relative(facing, radius-row))、蓝图载入(position = row*5+column 写入 getPositionIndex(layer, position))、以及 getComparatorOutput()(一直把 index 当存储索引直接用)现在全部同一约定,getStorageIndexForOrder 的恒等映射才成立——顺带修掉了"按列竖着走"和"比较器/放置顺序索引基准不一致"的隐患;两模式 targetFacing 都是 getFacing().getOpposite()distance=4,顺序真正对齐。
  • canSurvive 前置校验(对副部件跳过)精准覆盖 [Bug] 智能放置器移动模式移动方块不发出更新或不检查放置是否合法 #4533 的浮空门/浮空拉杆。
  • resetPositionIndex() 配合 selectNextPositionTarget 的向前扫描,不会死循环;比较器输出基于世界扫描,不受游标复位影响。
  • 无死代码(advancePositionIndex 仍在用);checkShuttlePlacement 的触发时机(等邻居搬回才触发)与注释描述一致。

📋 声称验证表

声称 状态 依据
fixed #4738 放置顺序出错 行列循环互换 + 恒等映射;符合「从左侧最外行向右、再往更近一行」
fixed #4739 打断后从头开始 成功放置后 resetPositionIndex() 复位游标
fixed #4533 不检查合法性/不发更新 ⚠️ 部分 合法性 ✅(canSurvive);更新 ✅ 但清空源格引入中间态破坏(🔴1);方块自身状态未重算(💡3)
不会再移动多方块 ✅(行为删除) 删除多方块豁免 → 含 BE 的多方块机器不可搬

结论: COMMENT — 核心逻辑(顺序索引约定、canSurvive 校验、游标复位)经交叉验证正确且与三个 issue 的预期一致,方向合理;但 🔴1 的清空标志位是搬运流程中的真实缺陷(依赖掉落表条件才没出事),建议按上文调整标志位并补搬运测试;⚠️2 属行为删除,需维护者确认并补文档/changelog。

🧪 测试建议

被测目标 推荐场景 优先级
BlockPointer.moveToPos 门/高花/巨型铁砧/大型蛋糕搬运:源清空阶段不得破坏同批部件;失败时 restoreParts 回滚完整 🔴
BlockPointer.Type.point 3×3×3 带 BE 机器(大型坩埚)应返回 null;巨型铁砧/大型蛋糕应返回非 null 🔴
buildOrderedPositions / getStorageIndexForOrder 5×5×5 全选顺序 = 层→行→列,且与 BlueprintLayout.getPosition 坐标一致 🟡
resetPositionIndex / selectNextPositionTarget 成功放置后 currentPlacementIndex==0;中断后从 0 重新扫描且不死循环 🟡
checkShuttlePlacement 两个 MOVE+POSITION 放置器对置时触发一次进度;非穿梭布置不触发;改选区/拾取模式后标记清空 🟡
PulseGeneratorProvider 空闲/WAITING/OUTPUTTING 三态进度与颜色;waitingTime=0 不除零 🟢

由 Hermes Agent 审查

@Gugle2308

Copy link
Copy Markdown
Collaborator

💾 Self-improvement review: Patched references/block-update-flags-multipart-destruction.md in skill 'minecraft-mod-pr-review' (1 replacement). · Patched SKILL.md in skill 'github-webhook-pr-review' (1 replacement).

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

Labels

None yet

Projects

None yet

3 participants