跳到正文
烈焰甘蔗Wiki

Custom Mapping

Custom Mapping 系统允许 BetterBuildersWands 为特定方块指定自定义放置行为,用于处理 mod 方块的特殊放置逻辑。

用途

某些方块在 Block.getStackedBlock() 返回的 ItemStack 与实际放置的方块不同。例如:

  • 青金石矿石的 getStackedBlock(0) 返回青金石方块而非青金石矿石
  • 红石矿石(点燃/未点燃)之间的映射

CustomMapping 的定位是:指定"看到某方块时,应该用某物品建造、放成某方块"。

数据结构

ICustomMapping 接口的 5 个查询方法:

方法 返回 用途
getLookBlock() Block 玩家看到的源方块
getMeta() int 源方块的 metadata
getItems(world, player, point) ItemStack 需要消耗的物品(可覆写以按 TileEntity 动态生成)
getPlaceBlock() Block 实际放下的方块
getPlaceMeta() int 实际放下方块的 metadata
shouldCopyTileNBT() boolean 是否复制 TileEntity 的 NBT

CustomMappingManager.getMapping(block, meta) 是线性扫描: 只要 getLookBlock() == block && getMeta() == meta 就返回该条,先注册者优先。

配置格式

minecraft:lapis_ore/0=>1*minecraft:lapis_ore/4=>minecraft:lapis_ore/0

解析为:源方块/元数据=>数量*源方块/元数据=>目标方块/元数据 即"看青金石矿石 → 需要 1 个青金石矿石(meta 4) → 放成青金石矿石(meta 0)"。

解析由 CustomMappingManager.parseMappingString 完成,要求整串完全匹配 下面这条正则(多一条少一条都会整条失效并记 error 日志):

final String REGEX = "^(\\w[^:]+:[^/]+)/(\\d+)=>(\\d)+\\*(\\w[^:]+:[^/]+)/(\\d+)=>(\\w[^:]+:[^/]+)/(\\d+)$";
组 含义 解析方式
1 源方块名 Block.getBlockFromName
2 源 meta Integer.parseInt
3 数量 Integer.parseInt
4 所需物品名 GameRegistry.findItem(mod, name) —— 可以是任意物品,不限于方块
5 物品 meta Integer.parseInt
6 目标方块名 Block.getBlockFromName
7 目标 meta Integer.parseInt

多条映射用英文逗号分隔。任一 Block / Item 解析为 null 则整条丢弃。

内置映射(来自配置文件 forced_blocks)

Lapis Ore 映射

  • 源:minecraft:lapis_ore/0(元数据 0)
  • 需求:1 个 minecraft:lapis_ore/4
  • 目标:minecraft:lapis_ore/0
  • shouldCopyTileNBT:false

Redstone Ore 映射

  • 源:minecraft:lit_redstone_ore/0
  • 需求:1 个 minecraft:redstone_ore/0
  • 目标:minecraft:lit_redstone_ore/0
  • shouldCopyTileNBT:false

⚠️ 这两条来自 ConfigValues.OVERRIDES_RECIPES_DEFAULT, 在 preInit 阶段由 mappingManager.loadConfig(configValues.OVERRIDES_RECIPES) 加载。 把 forced_blocks 设为空字符串会禁用它们(loadConfig 直接 return)。

Mod 集成

ArchitectureCraft

ArchitectureCraftCustomMapping.java 继承 CustomMapping,注册 2 条:

源方块 meta shouldCopyTileNBT
ArchitectureCraft:shape 0 true
ArchitectureCraft:shapeSE 15 true

getItems() 被覆写:从 TileShape 动态生成物品, shapeTile.newItemStack(1) 并把 metadata 强制设为 getMeta():

public ItemStack getItems(IWorldShim world, IPlayerShim player, Point3d point) {
    TileEntity tileEntity = world.getTile(point);
    if (tileEntity instanceof TileShape) {
        ItemStack itemStack = ((TileShape) tileEntity).newItemStack(1);
        itemStack.setItemDamage(getMeta());
        return itemStack;
    }
    return super.getItems(world, player, point);
}

只在 Loader.isModLoaded("ArchitectureCraft") 时调用 register()。

Forestry

ForestryMCCustomMapping.java 继承 CustomMapping, shouldCopyTileNBT = true,在 PluginManager.Module.ARBORICULTURE.isEnabled() 时 批量注册原木 / 木板 / 台阶 / 楼梯 / 栅栏及其耐火变种:

Forestry 方块组 注册的 meta
logs 0, 4, 8
logsFireproof 0, 4, 8
planks 0
planksFireproof 0
slabs 0, 8
slabsFireproof 0, 8
slabsDouble 0
slabsDoubleFireproof 0
stairs 0 ~ 7
stairsFireproof 0 ~ 7
fences 0
fencesFireproof 0

getItems() 被覆写,从 TileWood 取出正确的树种/树种状态:

if (tileEntity instanceof TileWood) {
    return TileWood.getPickBlock(getLookBlock(), world.getWorld(), point.x, point.y, point.z);
}

这正是 NBT 复制机制的意义:Forestry 原木的树种信息存在 TileWood 里, shouldCopyTileNBT = true 让 BFS 只复制完全相同 TileEntity 状态的方块, 放置后再把源 TileEntity 的 NBT 写过去。 详见 BFS 方块扩展算法 的过滤 9。

Ztones

Ztones.java 不是 CustomMapping 映射,而是一套独立的特例处理: Ztones 的 ofanix 物品能让魔杖把圆石视为无限供应。

检查点 位置 效果
Ztones.isLoaded() BetterBuildersWandsMod.init 中 Loader.isModLoaded("Ztones") 时调用 init() 设置 isLoaded = (OFANIX != null)
上限绕过 ItemBasicWand.onItemUse 目标是圆石且背包有 ofanix → numBlocks = wand.getMaxBlocks(...),跳过 countItems
扣取绕过 BasicPlayerShim.useItem 同样条件下直接返回全额,不扣任何材料

客户端预览(BlockEvents)也实现了同样的上限绕过, 所以看到 4096 格的线框是真实可放置的。

shouldCopyTileNBT 汇总

映射来源 shouldCopyTileNBT 影响
配置文件 forced_blocks false 不复制 TileEntity;countItems 只按 item+meta 匹配,不比 NBT
ArchitectureCraftCustomMapping true 比对 + 复制 TileShape NBT
ForestryMCCustomMapping true 比对 + 复制 TileWood NBT
Backhand 副手临时构造 false 1:1 映射,不复制 NBT

CustomMapping 有 5 参构造(shouldCopyTileNBT 默认 false)和 6 参构造。 配置文件解析走 5 参版本,两个 mod 集成都显式传 true。

缓存机制

StackedBlockManager 缓存 Block.getStackedBlock(int) 的调用结果, 减少重复的反射开销:

属性 值
缓存键 `方块注册名 + "
容器 HashMap<String, ItemStack>
反射方法名 优先 func_149644_j(1.7.10 的 MCP 混淆名),回退 createStackedBlock(1.8+)
可见性 setAccessible(true),并沿 block.getClass() 向上遍历父类查找覆写版本
生命周期 随 mod 实例存活,不清空

它只被 WandWorker.getEquivalentItemStack 在 canSilkHarvest 为真时调用, 属于兜底推断路径。装了 Backhand 时副手优先,这条路径通常走不到。

死代码

shims/BlockShim.getPickBlock(...) 是一个基于 Block.getPickBlock 的封装, 但源码中没有任何调用点(ItemBasicWand.onItemUse 与 BlockEvents 中 对应的 getPickBlock 调用都被注释掉了)。模组改用 getStackedBlock / getItemDropped 两条推断路径,不走 getPickBlock。

相关条目

浏览全部模组