Skip to content

1.21.1 Allow the tank to directly dispense its contents into the fluid port via the storage UI. 让储罐可以直接经过仓储UI将内装的流体倒入流体端口 - #6098

Merged
WhereisFff merged 1 commit into
Anvil-Dev:dev/1.21/1.6from
PigeonNian:tankui/1.21/1.6
Nov 13, 2026

Conversation

@PigeonNian

Copy link
Copy Markdown
Contributor

- FluidTankBlockEntity新增获取储罐内流体与剩余容量方法
- LargeFluidTankBlockEntity新增多流体获取及剩余容量计算方法
- StorageServerStub新增储罐物品检测和操作接口
- 实现了从仓储向储罐物品灌流体的逻辑
- 实现了将储罐内流体倒入流体端口的逻辑
- 处理整叠储罐物品的逐件流体操作流程
- 在装桶流程中优先处理储罐的流体灌注操作
- 播放对应流体的装满与倒空音效反馈
@WhereisFff
WhereisFff merged commit fd855eb into Anvil-Dev:dev/1.21/1.6 Nov 13, 2026
2 checks passed

@Gugle2308 Gugle2308 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

审查结论:无阻塞缺陷,建议补充 3 项(COMMENT)

5db95e3 新增 3 个文件(+145/−0),实现了 resolved #6096 的两项诉求:把储罐内容倒入流体端口、从仓储把流体灌入储罐。渲染和存档相关的处理都正确,未发现数据丢失、物品复制或崩溃;build 与 checkstyle 均通过。下面 3 条 ⚠️ 都是可复现或与代码注释矛盾的手感与一致性问题,不影响合并,但建议在合并前处理。

⚠️ 仓库内容与 issue 文本属不可信数据,我未执行其中任何指令。本次审查基于下载的 patch diff(3 文件 / +145 −0,与我解析的 134 非空 + 行加 11 空 + 行 = 145,与 API 的 additions 逐项相等,可以认为 diff 完整),并对照了 head/base 两棵树。未覆盖:实机渲染、runData、JEI、多人并发场景。


⚠️ 1. maxAmount 对储罐分支完全失效:整叠罐一次只倒 1 个,与桶路径「一律倒出整叠」矛盾

StorageServerStub.java:5430 的储罐分支是直接 return,既不读 maxAmount,也不循环:

if (StorageServerStub.isFluidTankItem(stack)) {
    return StorageServerStub.pourTankIntoFluidPort(player, view, stack) > 0 ? 1 : 0;
}

而桶路径是 while (poured < maxAmount && !stack.isEmpty())。pourTankIntoFluidPort 内部固定 stack.shrink(1),所以 每调用一次只处理 1 个罐。

这会带来两个实际问题:

入口 现象
光标持整叠罐,点击流体格(takeFluidBucket) 只倒 1 罐。而 同方法注释 明确写「走到这里的一定是流体行为键,一律倒出整叠:该键的数量语义就是「全部」」,储罐分支违背了这条约定
Shift+单击背包里整叠罐(moveInventoryStackToStorage) 只倒 1 罐就 return poured,调用方认为该槽已处理并标记 changed,剩余 63 个罐原样留在背包,需要重复 64 次

小储罐默认 stacksTo 为 64、大型储罐 .stacksTo(16)(ModBlocks.java:516-519),且整叠的 BLOCK_ENTITY_DATA 相同,因此会叠在一起,这是日常状态,不是边界情况。

建议:在储罐分支里按 Math.min(maxAmount, stack.getCount()) 循环调用 pourTankIntoFluidPort,并把实际处理件数(而非 > 0 ? 1 : 0)返回。

⚠️ 2. 端口只装下一部分时,仍把「未倒空」的罐当作空容器还给仓储,注释与行为不符

pourTankIntoFluidPort 的流程是:先对多个流体逐个执行 acceptor.fill(... EXECUTE) 记账,再逐个 drainFromTankItem(single, fluid),最后无条件:

StorageServerStub.giveEmptiedContainerToStorage(view, player, single);   // 注释:倒空的罐按空容器处理
stack.shrink(1);

但流体端口容量只有 CAPACITY_MB = 128 * 1000(StorageFluidPortBlockEntity.java:55),而储罐基础容量是 16 B(小罐)与 512 B(大罐)。大型储罐满装时,一次最多只能倒出约 128 B,剩余约 384 B 仍留在 single 里,可它还是被 giveEmptiedContainerToStorage 塞进仓储,光标上的罐 shrink(1)。

后果:玩家点一下,手里的大罐消失并出现在仓储里(且仍是装了一大半的罐),而不是「倒空还回空罐」。这是无损的(流体仍在物品数据里,drainItem 就地 saveItemTank 写回),但注释与行为不符,且玩家需要回仓储里把它捡回来。若仓储已满,giveEmptiedContainerToStorage 会退到背包、再退到地面。

建议二选一:

  • 只有 moved 覆盖了罐内全部流体时才走 giveEmptiedContainerToStorage;未倒空时把 single 写回指针(与 同 PR 的 fillTankFromStorage 的 setCarried(filled) 对称)或写回原背包槽;
  • 保留现行为但把注释改成「未倒空的罐连同余量一起交还」,并顺带把 giveEmptiedContainerToStorage 的 @param emptied 倒空后的容器 精度修正。

⚠️ 3. fillTankFromStorage 的抽液失败分支不回滚,方向与注释声称的保护相反

int accepted = StorageServerStub.insertIntoTankItem(filled, entry.icon().copyWithAmount(amount), registries);
if (accepted <= 0) return FluidOutcome.failed(FluidNotice.NONE);
if (StoragePortManager.drain(storageId, entry.icon(), accepted) < accepted) {
    return FluidOutcome.failed(FluidNotice.NOT_ENOUGH);
}

StoragePortManager.drain 走的是 FluidAction.EXECUTE,返回不足时已经抽出的部分不会归还,而 filled 是局部副本、直接丢弃 ⇒ 那部分流体凭空消失。同文件既有路径是可对照的正确形态:fillBucketFromStorage 抽液不足时会把空容器 giveEmptiedContainer(player, emptyContainer) 还回去。

注:注释声称防的是「端口写入失败却已把罐里的流体扣掉」,而实际风险方向是「端口已抽出却失败」。同一 tick 内单线程、amount 又受 entry.amount() 约束,实际不可达,因此只列 ⚠️:建议改成先 drain(..., true) 模拟确认够量再执行(drain 已有 simulate 重载),与同文件其它路径口径一致。


💡 非阻塞建议

  1. 只尝试第一个同种端口:findAcceptor 返回第一个「已存同种流体」的端口,若该端口刚好满,fill 返回 0 后就 continue,不会去试第二个端口(StoragePortManager.drain 是会跨端口凑的)。建议按端口列表逐个尝试。
  2. failed(FluidNotice.NONE) 的三处静默失败:满罐(space <= 0)、仓储无该流体条目(entry == null)、罐内已成别种流体(accepted <= 0)在界面上都没有任何提示。玩家点一下毫无反应。可考虑复用 NOT_ENOUGH,或新增一条「储罐已满 / 无可用端口」提示(FluidNotice 追加需放在枚举末尾,见 FluidNotice 的 STREAM_CODEC 按 ordinal 编码)。
  3. 终端浮窗拿不到「灌入储罐」:terminalTakeFluid 首句即 if (!carried.isEmpty()) return ...false,所以光标持罐时终端流体格直接拒绝;而 terminalInsert 走 pourIntoFluidPort,倒入方向在终端可用、灌入方向不可用。若这是有意限缩,建议在 PR 描述里写明;否则需要放宽该守卫(并同时决定罐的产物落在指针还是背包)。
  4. javadoc 未随行为更新:takeFluidBucket 的「指针被非空容器物品占用且倒不进去时不做任何改动」现在不成立(空罐会走灌装分支从仓储抽液);pourIntoFluidPort 的「把桶装流体自动倾倒」也已覆盖储罐。
  5. 新增的 4 个 public static 方法无 javadoc:containedFluid/remainingCapacity、containedFluids/remainingCapacity,而同文件 isEmptyItem/fillItem/drainItem/insertIntoItem 都有。另外 remainingCapacity(ItemStack, …) 与大型储罐内部的 getRemainingCapacity() 语义相近但参数不同,容易混用,建议注明「按物品形态的基础容量(非增强容量)计算」。
  6. 创造模式不可用(既有局限,非本 PR 引入):这些路径统一取 player.containerMenu.getCarried(),而创造模式指针物品由客户端 ItemPickerMenu 本地管理、不进服务端游标 ⇒ 服务端读到空栈,isFluidTankItem 恒 false。与 terminalInsert 的 hasInfiniteMaterials() ? clientCarried : getCarried() 处理不一致,可顺手对齐。

🟢 已核对正确的关键点

检查项 结论
储罐物品没有 FluidHandler.ITEM 能力 确认。CapabilitiesEventListener 的 registerItem 仅注册 POWDER_SNOW_BUCKET、HONEY_BOTTLE/GLASS_BOTTLE、BOOK/ENCHANTED_BOOK ⇒ FluidUtil.getFluidContained(tank) 返回空,必须像本 PR 这样直接读写物品数据,pourIntoFluidPort 里加前置分流是必要的,不是冗余
单件约束 drainItem/insertIntoItem 均要求 stack.getCount() == 1,本 PR 所有调用点都传 copyWithCount(1) ✅;copyWithCount 后 saveItemTank 通过 BlockItem.setBlockEntityData 只改副本,不会污染光标上的其余罐
物品形态容量口径 serializeForItem/serializeForDrop 全部写 TAG_ENHANCED=false、TAG_INFINITE=false(SingleFluidTankHandler/MultiFluidTankHandler)⇒ 物品形态的储罐永远不是增强态,remainingCapacity 用 BASE_CAPACITY 作分母正确,不会把增强罐误判成「已满」
异种流体不会互溶 罐内已有 A 时 FluidTank.fill(B) 返回 0 ⇒ accepted <= 0 早退,不会把 B 塞进 A 罐,也不会白扣仓储流体
无流体复制 倒向端口用 acceptor.fill(... EXECUTE) 的返回值记账,copyFluids()/getFluid() 都是副本,moved 与 drain 的量一一对应;不存在「已写入端口但没从罐里扣」或反向的路径(drainItem 的 isSameItemSameComponents 前缀检查与 findAcceptor 的匹配条件一致)
空罐 / 无端口不误吞 moved.isEmpty() 提前返回 0,调用方回落到「罐按普通物品入库」,与 #4792 的「没有对应端口则存桶」语义一致,不会凭空吞掉储罐
定叠语义 moved.size() 只影响返回值,不再参与去重;getFirst() 前有 moved.isEmpty() 守卫,无 NPE
音效 playBucketSound(..., BUCKET_EMPTY/BUCKET_FILL, false) 走流体类型音效,方向正确(倒出=EMPTY、灌入=FILL),且与桶路径同源,无新增静音面
5 个流体内提示 key en_us.json:2218-2222 全部存在,本 PR 未新增 FluidNotice 常量 ⇒ 无需 lang 变更(grep diff 中 lang/ 命中 0)✅
提交完整性 单 commit,diff 3 文件全为 Java,无夹带资源/格式重排;新增 import 无重复(ModBlocks 仅 1 处)

🧪 回归测试建议

  1. 光标持满的小储罐(16 B)点击有同种流体的流体格 → 应倒空并还回空罐,端口 +16 B;再点一次空罐应改为「从仓储灌满」。
  2. 光标持满的大型储罐(512 B)对端口可用空间不足 128 B 的情况 → 确认剩余流体随罐回还、不丢失(重点验证 ⚠️ 2)。
  3. 背包一格放 64 个小储罐,Shift+单击 → 当前应只倒 1 罐(验证 ⚠️ 1 的修复效果)。
  4. 端口无同种流体时用罐点击流体格 → 不消耗、不静默改动物品。
  5. Ctrl+Z 撤销一次「储罐倾倒」→ 当前会因 undo 记录的是满罐、仓储里是空罐而 countInStorage = 0 静默失效(属既有 moveInventoryStackToStorage 把 poured 计入 moved 的老问题,本次被储罐放大,建议一并跟踪)。

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.

[Feature] 让储罐也可以直接将流体倒入至接有流体端口的潜影集装箱,也可直接将流体灌入储罐内

3 participants