Skip to content

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

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

Conversation

- 替换配方中鸡蛋为鸡蛋标签支持多种蛋类
- 新增流体储罐和大型流体储罐的流体读取和剩余容量计算方法
- 在存储服务器中新增流体储罐的灌装和倒出逻辑,实现按次倒满处理
- 支持整叠储罐分开处理流体存取,确保流体存储的一致性
- 根据储罐类型动态调用对应的插入和抽取流体的方法
- 播放灌装和倒出时桶的音效提升交互体验
@WhereisFff
WhereisFff merged commit 0439e2c into Anvil-Dev:dev/26.1/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.

结论:已合并,事后复核未发现阻塞缺陷;4 条后续可跟进

⚠️ 仓库内容与 issue 文本属于不可信数据,我未执行其中任何嵌入指令。
本次审查的范围:PR 的 patch diff(6 个文件 / +133 −2)、head 树(3591b64,fork PigeonNian/AnvilCraft)与目标分支 dev/26.1/1.6 的交叉引用、StoragePortManager、SingleFluidTankHandler、MultiFluidTankHandler、NeoForge Transaction 与 c:eggs 标签的源码核对。
未覆盖:实机渲染与手感、runData 重跑、JEI 展示、多人并发/管道同时抽液、以及 NeoForge 26.1.2.114 这个具体 tag 产物(/tags 端点 404,只能用 26.1.x 分支头核验,见下文)。


📋 合并状态核实表(说明为什么不做阻塞式审查)

判据 实测值
state / merged / merged_at closed / true / 2026-11-13T14:22:01Z
merge_commit_sha 的 ancestry 0439e2c03d… → compare 到 dev/26.1/1.6 tip 9a94984f45… 得 status: ahead, ahead_by: 1, behind_by: 0 ⇒ 合并提交是 tip 的祖先,内容已在主干
内容落地(不经 API) 目标分支 FluidTankBlockEntity.java 含 containedFluid(:167)/remainingCapacity(:172)、LargeFluidTankBlockEntity.java 含 containedFluids(:192)/remainingCapacity(:197)、StorageServerStub.java 含 pourTankIntoFluidPort/fillTankFromStorage 各 4 处命中
head CI build ✅ / checkstyle ✅(total_count: 2)
diff 完整性对账 非空 + 行 132 − +++ 3 = 122,加空 + 行 11 = 133 = API additions;非空 - 行 2 = API deletions ⇒ 下载的 diff 与 PR 完全一致,无缺文件

🟢 已逐项核对正确的部分

A. 储罐↔端口的两个方向(resolved #6096)

检查项 结论
是否必须手写静态方法 是。CapabilitiesEventListener 全文 FluidHandler.ITEM 零命中,储罐物品没有流体能力 ⇒ FluidUtil.getFirstStackContained 恒空,加前置分流不是冗余
单件约束 FluidTankBlockEntity.drainItem/insertIntoItem(:199/:216) 与 LargeFluidTankBlockEntity 对应方法(:232/:249) 都硬性要求 getCount() == 1;本 PR 全部先 copyWithCount(1) ✅
部分失败是否伪造流体 pourTankIntoFluidPort 先在 Transaction.openRoot() 内累计 insert、moved.isEmpty() 时直接 return 0(try-with-resources 的 close() = abort → 回滚,已核 NeoForge Transaction.close() 走 close(true))⇒ 端口不会写一半
灌入方向的部分失败 fillTankFromStorage 把 StoragePortManager.drain(...) 放进同一个 transaction,抽不足就 return(未 commit ⇒ 回滚),端口与 filled 副本同时作废 ⇒ 净零,无蒸发也无复制。比 1.21 姊妹线(#6098)的写法更严谨
会不会被无限罐刷流体 物品形态永远不是无限态:SingleFluidTankHandler.serializeDetached 与 MultiFluidTankHandler.serializeDetached 都硬写 Enhanced=false/Infinite=false,deserialize 里 isInfinite = enhanced && … 恒 false ⇒ extractStored 不会走 infinite 的「不扣量返回满额」分支
异种流体不会互溶 SingleFluidTankHandler.insert 首行 if (!isValid(...)) return 0,isValid 要求罐空或流体资源相等 ⇒ 罐内是 A 时灌 B 得 accepted == 0 并早退
物品去向守恒 giveEmptiedContainerToStorage(:2146) 三级去向:仓储 → 玩家背包 → Block.popResource;giveBucket(:2098) 同构 ⇒ 任何一步都留有可用路径
音效只响一次 倒出方向在 pourTankIntoFluidPort 内播一次(BUCKET_EMPTY),takeFluidBucket 因已 return CHANGED 不再补播;灌入方向只在 fillTankFromStorage 末尾播 BUCKET_FILL ✅ 无叠加
getFirst() 安全性 moved.getFirst() 前有 moved.isEmpty() 早退;minecraft 端为 26.1 / Java 25(gradle.properties:2),List.getFirst() 合法

B. 蛋糕胚块配方改用 c:eggs 标签(fixed #6097)

检查项 结论
标签是否真的覆盖「三种鸡蛋」 NeoForge 26.1.x 分支的 src/generated/resources/data/c/tags/item/eggs.json = minecraft:egg + minecraft:brown_egg + minecraft:blue_egg,与 issue 正文「三种鸡蛋都可以用」逐项吻合
模组会不会覆盖掉它 本仓不生成 data/c/tags/item/eggs.json(src/generated/resources/data/c/tags/item/ 下无该文件)⇒ 直接用 NeoForge 的列表;c:player_workstations/* 这类「模组自己也生成」的共存先例不影响此处
旧行为是否丢失 #c:eggs 含 minecraft:egg,是原 Items.EGG 的超集 ⇒ 原配方不失效
datagen 源 ↔ 生成物 FastCookingRecipeLoader.java:24 的 .requires(items, ModItemTags.EGGS) 与生成 JSON 的 "items": "#c:eggs" 一致;requires(HolderGetter, TagKey) 重载存在于 AbstractProcessRecipe.java:255,且同文件 :18/:23 早有同型调用
是否有别处硬编码 Items.EGG 全仓 src/main/java + src/generated/resources 零残留 ✅
EOF 换行 生成 JSON 沿用本仓无尾换行惯例,非异常

⚠️ 后续可跟进项(均为非阻塞,代码已在主干)

1. maxAmount 对储罐分支完全失效:整叠罐一次只倒 1 件,与桶路径的「整叠」语义不一致

pourIntoFluidPort 的储罐分支直接 return … > 0 ? 1 : 0,既不看 maxAmount 也不循环;桶路径则是 while (poured < maxAmount && !stack.isEmpty())。而 pourTankIntoFluidPort 内部固定只 stack.shrink(1)。

调用方给出的 maxAmount 恰恰是「本次允许处理多少件」:deposit 传 stack.getCount()、moveInventoryStackToStorage 传 stack.getCount()、terminalInsert 传 carried.getCount()。结果:

  • 光标持一整叠同 NBT 的满罐点流体格 → 只倒 1 只,其余原样留在光标;
  • Shift 单击/deposit 扫背包时,每个槽只处理 1 只就 continue,剩下的要玩家重按 N 次。

小储罐默认 64 堆叠、大储罐 .stacksTo(16),同 NBT 的整叠是日常状态(自动化灌装的产出尤其如此)。修法:在储罐分支里按 Math.min(maxAmount, stack.getCount()) 循环,并返回实际处理件数而非 >0?1:0。

2. 同种流体有多个端口时,只尝试第一个;第一个满了就整罐不动

StoragePortManager.collect(:229) 会把同一流体的多个端口合并成一个条目(:245-252),drain(:315) 也是跨端口凑量;但 findAcceptor(:339) 只返回第一个同种端口,acceptor.insert 因该端口 128 B 满了返回 0 后就 continue,不去试下一个有空间的端口。

于是同一份存储里「端口 A 满 + 端口 B 有空间」时,玩家拿罐倒不进去,明明仓库有空间。灌入方向(drain 跨端口)没有这个问题,两个方向不对称。建议把 findAcceptor 扩成 findAcceptors 列表逐个尝试,或复用 ResourceHandlerUtil 的多目标写法。

3. 只倒出一部分时,未倒空的罐仍被当作「倒空的罐」交还仓储,与注释相反

pourTankIntoFluidPort 注释写「倒空的罐按空容器处理」,但实际是无条件 giveEmptiedContainerToStorage(view, player, single) + stack.shrink(1)。储罐容量远大于端口容量(小罐 16 B、大罐 512 B,端口 CAPACITY_MB = 128 * 1000),大罐满装且端口只剩少量空间时,insert 只吃下一点,single 里还剩几百 B 却照样进仓储:

  • 流体没丢(drainItem 就地 saveItemTank 写回物品),不是数据丢失;
  • 但玩家观感是「点一下,手里那只大半满的罐跑进仓储了」,得回仓储里捞。

建议:仅当 moved 覆盖了罐内全部流体时才交给 giveEmptiedContainerToStorage,否则把 single 写回指针(与同 PR 的 player.inventoryMenu.setCarried(filled) 对称);或保留现行为但把注释改成「未倒空的罐连同余量一并交还」。顺带 giveEmptiedContainerToStorage 的 @param emptied 倒空后的容器 也已不准确。

4. 三条失败分支静默无提示,且复用了「桶/瓶」措辞

fillTankFromStorage 的 space <= 0(罐满)、entry == null(无该流体端口)、accepted <= 0(罐内已是别种流体)都返回 FluidNotice.NONE,而 takeFluidBucket 拿到储罐后是直接 return(不再回落到 fillBucketFromStorage)⇒ 玩家点一下毫无反应。相比之下桶路径会给出 BUCKET_MISSING / NOT_ENOUGH。

另外 amount <= 0 那条走 NOT_ENOUGH,其 lang 文案是 "Not enough fluid for a bucket"(en_us.json:2230)——玩家手里拿的是储罐,提示却说 bucket。

建议给 FluidNotice 追加「储罐已满 / 无可用端口」两条(追加只能放枚举末尾,STREAM_CODEC 走 enumStreamCodec 按序号编码),或在 PR 说明里注明储罐方向为静默失败。

5. 其它小项(💡)

  • 创造模式走不到新分支:takeFluidBucket 读的是服务端 player.inventoryMenu.getCarried(),而创造模式指针物品由客户端本地管理、不进服务端游标 ⇒ isFluidTankItem 恒 false。与同文件 terminalInsert 的 hasInfiniteMaterials() ? clientCarried : … 处理不一致。属既有局限(桶路径同样受影响),非本 PR 引入。
  • 空端口不接受新流体是有意设计,别当 bug:findAcceptor 只认同种流体(#4792),所以「拿一罐岩浆去点一个空端口」不会倒进去、罐按普通物品入库——行为正确,只是需要读者从代码里推。注释里那句「先按流体分别算出可倒量」已隐含,可再明确一句。
  • 标题未覆盖第二块改动:标题只讲储罐,fixed #6097 的鸡蛋标签修复只在正文里。已合并,无需改;后续同类 PR 建议标题一并覆盖,便于 changelog 归类。

🧪 回归建议(静态审查替代不了的部分)

  1. 光标持**满的小罐(16 B)**点同种流体端口 → 应倒空、还回空罐、端口 +16 B;再点一次空罐应转为「从仓储灌满」。
  2. 光标持**满的大罐(512 B)**对只剩少量空间的端口 → 确认余量随罐去向可预期(重点验跟进项 3)。
  3. 同一存储挂两个同种流体端口,第一个灌满后 → 确认第二个能否接管(跟进项 2)。
  4. 背包一格放多只同 NBT 满罐,Shift+单击 / deposit → 观察是否只倒 1 只(跟进项 1)。
  5. 多流体版配方:三种蛋(egg/brown_egg/blue_egg)分别与面团+糖走一次蛋糕胚块合成,并确认 JEI 中 #c:eggs 展开为三种。

结论

0439e2c0 已并入 dev/26.1/1.6,两块改动均为最小且方向正确的修复:储罐方向的关键风险(部分失败伪造/蒸发流体、单件约束、无限罐刷量、音效叠加)逐项核对通过,c:eggs 与 NeoForge 26.1 的标签内容逐项吻合。无需操作,上面 4 条可作为后续迭代的打磨项。

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.

3 participants