储罐升级器共用机制(ItemTankChanger)
本页说明 13 个"储罐升级"物品共享的全部行为。每个物品是 ItemTankChanger 的一个独立实例,差异只来自 TankChangerType 枚举的 4 个字段(源等级、目标等级、内部名、配方)。
三个类
| 类 | 文件 | 角色 |
|---|---|---|
ItemGeneric |
item/ItemGeneric.java |
抽象基类;只做未本地化名 item.irontank:<name>、创造标签、图标注册,并声明抽象方法 addRecipe() |
ItemTankChanger |
item/ItemTankChanger.java |
13 个升级器物品的同一个类,覆写 addRecipe() 与 onItemUseFirst() |
TankChangerType |
reference/TankChangerType.java |
枚举:源等级 / 目标等级 / 内部名 / 配方 |
ModItems(init/ModItems.java:11-26)创建 13 个 new ItemTankChanger(TankChangerType.X),注意声明类型是 ItemGeneric;init()(:29-41)用 TankChangerType.X.name 注册,即物品注册名 = 枚举的 name 字段(不是 name())。
构造器(ItemTankChanger:23-30)只设两件事:未本地化名 type.name、最大堆叠 1。没有耐久、没有附魔相关覆写、没有创造标签覆写(ItemGeneric:18 已设为 MainTab)。
升级是怎么发生的(ItemTankChanger:112-144)
触发点是 onItemUseFirst(右键方块、在方块放置之前触发):
TileEntity worldTile = world.getTileEntity(X, Y, Z);
TileTank curTankTile;
if (worldTile instanceof TileIronTank) { // 本 mod 的储罐
curTankTile = (TileTank) worldTile;
if (!type.canUpgrade(((TileIronTank) curTankTile).type)) return false; // 等级不匹配 → 无事发生
} else if (worldTile instanceof TileTank) { // BuildCraft 原版储罐
curTankTile = (TileTank) worldTile;
if (!type.canUpgrade(TankType.GLASS)) return false; // 只有 2 个玻璃升级器能处理
} else {
return false; // 不是储罐 → 无事发生
}
TileIronTank newIronTankTile = new TileIronTank(type.target);
newIronTankTile.tank.setFluid(curTankTile.tank.getFluid()); // 搬走流体
world.setBlock(X, Y, Z, Block.getBlockFromName(Reference.MODID + ":" + type.target.name));
world.setTileEntity(X, Y, Z, newIronTankTile);
stack.stackSize = 0; // 消耗 1 个
return true;
判定规则(canUpgrade,TankChangerType:65-67)是严格相等,不是"等级更低":
public boolean canUpgrade(TankType from) { return from == this.source; }
所以每个升级器只认一个源等级,对其他等级右键完全无效、也不消耗。
行为要点
| 问题 | 答案 | 依据 |
|---|---|---|
| 需要先倒空储罐吗? | 不需要。代码没有检查储罐是否为空,直接 setFluid(curTankTile.tank.getFluid()) 搬运 |
ItemTankChanger:136 |
| 会丢流体吗? | 不会。13 条升级路径的源容量全部 ≤ 目标容量(最大的一档 DIAMONDOBSIDIAN 是 64→64 相等),没有降容路径 |
TankChangerType:25-37 + TankType 容量字段 |
| 客户端会发生什么? | world.isRemote 时直接 return false,客户端完全不预测;只有服务端真正替换方块与 TileEntity,再由同步包把结果发给客户端 |
ItemTankChanger:115-117 |
| 物品会消失吗? | 会,stack.stackSize = 0 消耗 1 个,且不返还 |
ItemTankChanger:141 |
| 失败时消耗吗? | 不会,等级不匹配或目标不是储罐时都 return false,物品与储罐都不变 |
ItemTankChanger:123-132 |
| 目标的旧数据会保留吗? | 不会。world.setBlock + setTileEntity 是整体替换,旧 TileEntity 的其他 NBT 一律丢弃(TileIronTank 除 type 外无自有字段,所以实际无损失) |
ItemTankChanger:138-139 |
| 能降级吗? | 不能。没有任何一条 TankChangerType 的目标容量小于源容量 |
TankChangerType:25-37 |
| 能对着空气用吗? | 无意义。onItemUseFirst 只在点到方块时触发,点空气/方块时都是 return false |
ItemTankChanger:113 |
| 能升级别的 mod 的储罐吗? | 不能。非 TileIronTank 且非 BuildCraft TileTank 的 TileEntity 一律 return false |
ItemTankChanger:131-132 |
Block.getBlockFromName 的隐含约束
目标方块用字符串查找:Block.getBlockFromName("irontank:" + type.target.name)(:138)。13 个 target 全部是 ModBlocks 已注册的 10 个方块之一(GLASS 从不作为 target),所以查找必然成功;一旦某个 target 拼写错误或方块未注册,getBlockFromName 返回 null 并在 setBlock 时崩溃(该后果属 Forge API 行为推断)。
合成配方机制(ItemTankChanger:33-110)
配方是 TankChangerType 构造器 varargs 里的 3 个字符串(不是 9 字符单串),直接作为 ShapedOreRecipe 的行数组。
addRecipe() 会双重循环 type.source.materials × type.target.materials,把 'x' 映射到源材料、'y' 映射到目标材料(:42-45)。但:
- 13 条配方串里一次都没有出现
x或y(只用数字、等级字母和z), - 每个
TankType.materials列表也只有 1 个元素,
所以这个双重循环实际只跑 1 次,materials 字段对配方不起作用。等级差异完全靠配方串里硬编码的字母表达。
13 条配方的结构完全统一:
[ A A A ] A = 目标等级的双层板, 两侧是目标等级的 screw
[ z M z ] M = 源等级的材料板, z = paneGlass
[ r x v ] r = craftingToolHardHammer, v = craftingToolScrewdriver
- 中间行 = 源等级的材料板,顶行 = 目标等级,底行 = 硬锤 + 螺丝刀(
ItemTankChanger:105-106中r/v没有被重复定义,不存在 共用机制 里方块配方'r'的歧义)。 - 2 个玻璃升级器的中间行是
'j'=plateGlass,用来标记源是 BuildCraft 原版储罐。 - ⚠️ 命名不一致:
TankType.GLASS.materials写的是"blockGlass"(玻璃方块),而GLASSIRON/GLASSCOPPER配方实际用的是plateGlass(玻璃板)。两处不一致(但由于materials是死字段,不影响合成结果)。
ItemTankChanger 的字符表与 BlockExtendedTank 的不同,容易看混:
| 字母 | ItemTankChanger(升级器) |
BlockExtendedTank(储罐) |
|---|---|---|
q |
plateCopper |
BuildCraftFactory.tankBlock |
p |
plateSteel |
(未定义) |
f |
plateAluminium |
(未定义) |
l |
plateStainlessSteel |
(未定义) |
k |
plateTitanium |
(未定义) |
d |
plateDiamond |
plateDiamond |
r |
craftingToolHardHammer |
定义两次(见 共用机制) |
x / y |
源 / 目标材料(配方串里未使用) | paneGlass |
z |
paneGlass |
(未定义) |
注册流程
ModRecipies.init()(init/ModRecipies.java:5-32)逐个手写调用 13 个 ModItems.*.addRecipe() 与 10 个 ModBlocks.*.addRecipe(),没有循环也没有自动扫描。新增枚举值必须手动在此加一行,否则方块存在但无合成配方。
相关条目
- 13 个升级器物品:见分类页物品列表
- 储罐共用机制 - 10 个储罐方块的
BlockExtendedTank/TileIronTank/ItemBlockExtendedTank行为