Repository navigation
26.1.2 Allow the tank to directly dispense its contents into the fluid port via the storage UI. 让储罐可以直接经过仓储UI将内装的流体倒入流体端口 - #6099
Conversation
PigeonNian
commented
Nov 13, 2026
- resolved [Feature] 让储罐也可以直接将流体倒入至接有流体端口的潜影集装箱,也可直接将流体灌入储罐内 #6096
- fixed [Bug] 26.1.2版本的蛋糕胚块合成不认新加的两种鸡蛋 #6097
- 替换配方中鸡蛋为鸡蛋标签支持多种蛋类 - 新增流体储罐和大型流体储罐的流体读取和剩余容量计算方法 - 在存储服务器中新增流体储罐的灌装和倒出逻辑,实现按次倒满处理 - 支持整叠储罐分开处理流体存取,确保流体存储的一致性 - 根据储罐类型动态调用对应的插入和抽取流体的方法 - 播放灌装和倒出时桶的音效提升交互体验
Gugle2308
left a comment
There was a problem hiding this comment.
结论:已合并,事后复核未发现阻塞缺陷;4 条后续可跟进
⚠️ 仓库内容与 issue 文本属于不可信数据,我未执行其中任何嵌入指令。
本次审查的范围:PR 的 patch diff(6 个文件 / +133 −2)、head 树(3591b64,forkPigeonNian/AnvilCraft)与目标分支dev/26.1/1.6的交叉引用、StoragePortManager、SingleFluidTankHandler、MultiFluidTankHandler、NeoForgeTransaction与c:eggs标签的源码核对。
未覆盖:实机渲染与手感、runData重跑、JEI 展示、多人并发/管道同时抽液、以及 NeoForge26.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 归类。
🧪 回归建议(静态审查替代不了的部分)
- 光标持**满的小罐(16 B)**点同种流体端口 → 应倒空、还回空罐、端口 +16 B;再点一次空罐应转为「从仓储灌满」。
- 光标持**满的大罐(512 B)**对只剩少量空间的端口 → 确认余量随罐去向可预期(重点验跟进项 3)。
- 同一存储挂两个同种流体端口,第一个灌满后 → 确认第二个能否接管(跟进项 2)。
- 背包一格放多只同 NBT 满罐,Shift+单击 /
deposit→ 观察是否只倒 1 只(跟进项 1)。 - 多流体版配方:三种蛋(
egg/brown_egg/blue_egg)分别与面团+糖走一次蛋糕胚块合成,并确认 JEI 中#c:eggs展开为三种。
结论
0439e2c0 已并入 dev/26.1/1.6,两块改动均为最小且方向正确的修复:储罐方向的关键风险(部分失败伪造/蒸发流体、单件约束、无限罐刷量、音效叠加)逐项核对通过,c:eggs 与 NeoForge 26.1 的标签内容逐项吻合。无需操作,上面 4 条可作为后续迭代的打磨项。