NEI 多方块预览 (NEI Multiblock Preview)
基本信息
| 属性 | 值 |
|---|---|
| 核心类 | blockrenderer6343.integration.nei.MultiblockHandler (抽象) |
| GUI 类 | blockrenderer6343.integration.nei.GuiMultiblockHandler (抽象) |
| 渲染器 | ImmediateWorldSceneRenderer → WorldSceneRenderer |
| 假世界 | TrackedDummyWorld / ObserverWorld |
| 假玩家 | BRUtil.FAKE_PLAYER (ClientFakePlayer) |
| 注册名 (NEI) | blockrenderer6343.multiblock.structure (Multiblock Structure) |
系统要求
- 必须加载:NotEnoughItems (
NotEnoughItems)、structurelib、gtnhlib - 可选加载:
gregtech_nh(启用GTNEIMultiblockHandler注册项)、neenergistics(启用 NEI Overlay 上传)、bartworks(仅作isBartworksLoaded标志)
结构要求
- 无摆放要求 —— 全部通过 NEI 内嵌 GUI 渲染
- 场景位置恒为
(0, 64, 0)(常量MB_PLACE_POS) MAX_PLACE_ROUNDS = 2000—— 假世界中survivalConstruct的最大迭代轮数
形成机制
- NEI 调用
MultiblockHandler.loadCraftingRecipes(stack)或loadUsageRecipes(stack) - 调用抽象方法
tryLoadingMultiblocks(stack):- GregTech 路径 (
GTNEIMultiblockHandler):若candidate.getItem() instanceof ItemMachines,按IMetaTileEntity.getStackForm(1)匹配;否则查询multiBlockComponents(由GTConstructableScan后台线程扫描METATILEENTITIES中所有IConstructable后填充的Long2ObjectMap<ObjectSet<IConstructable>>) - StructureLib 路径 (
StructureCompatNEIHandler):查询multiBlockComponents(由MultiblockInfoContainerScan后台线程扫描IMultiblockInfoContainer.MULTIBLOCK_MAP后填充)
- GregTech 路径 (
loadCraftingRecipes填充currentMultiblocks后,drawBackground(recipe)中调用guiHandler.loadMultiblock(multi, stackForm, ConstructableData.getTierData(multi).setTierFromStack(lastStack))initializeSceneRenderer(true):构造新ImmediateWorldSceneRenderer(new TrackedDummyWorld())→ 调用抽象方法placeMultiblock()摆放多方块 → 自动调用TrackedDummyWorld.updateEntitiesForNEI()
功能
NEI 集成预览界面呈现任意 IConstructable(GT 多方块机器、第三方 mod 多导包):
- 鼠标左键拖动 =
rotationYaw/rotationPitch旋转(pitch 限制 ±89.9°) - 鼠标右键上下拖动 =
zoom调整(范围 3 ~ 999) - 鼠标中键拖动 = 平移
center - 滚轮 = 缩放
- 右键点击方块 = 选中结构元素 → 调用
BRUtil.scanCandidates(...)收集该IStructureElement的所有合法替换物块(通过DummyWorld+AUTO_PLACE_ENVIRONMENT+CreativeItemSource.instance) Layer滑块 = 显示单层 (viewAllBlocks=true, setVisibleYLevel=layer) 或全部 (-1)Tier滑块 = 调整trigger.stackSize,对分层多方块(线圈等级/玻璃等级)即时重建Channel滑块 = 通过ChannelDataAccessor.setChannelData(trigger, channel, tier)写入 hologram trigger 子通道(如 GT 仓室通道)- 顶部按钮
P=projectMultiblock:在玩家面前放置多方块(BRUtil.projectMultiblock) - 顶部按钮
?=neiOverlay:通过 NEE 把渲染物品发送到合成模板(见 自动填入 NEI 伪样板) - 顶部按钮
C=copyToHologram:将当前 trigger 的通道数据uploadChannels到手持ItemConstructableTrigger(见BRUtil.copyToHologram) - GT 专属按钮
H=highlightHatch:高亮所有 hatch 位置(按 dot index 着色,最多 12 色,循环EnumColor.VALUES)
数值
| 数值 | 值 |
|---|---|
| 默认 FOV | 60.0f(gluPerspective) |
| 默认 unproject FOV | 30°(ProjectionUtils.unProject) |
初始 rotationYaw |
20.0f |
初始 rotationPitch |
50.0f |
DEFAULT_RANGE_MULTIPLIER |
3.5f |
| 初始 zoom 公式 | 3.5 * sqrt(max(size)) * (1 + log10(max)) / 1.5 |
UNDER_PREVIEW_Y |
136 |
RECIPE_WIDTH / RECIPE_HEIGHT |
163 / 153 |
SCENE_HEIGHT |
RECIPE_WIDTH - 10 = 153 |
MAX_TIERS_TO_CHECK |
100(StructureHacks 中等级探测上限) |
MAX_PLACE_ROUNDS |
2000(survivalConstruct 循环上限) |
SLIDER_WIDTH |
BUTTON_RIGHT - BETWEEN_BUTTON_X - 2 = 145 - 22 - 2 = 121 |
| zoom 范围 | 3 ~ 999 |
| zoom 滚轮步长 | 5 |
| zoom 右键拖动步长 | ±0.15 |
| 中键平移步长 | 0.15f |
限制
- 场景位置固定为
(0, 64, 0):MB_PLACE_POS为new BlockPos(0, 64, 0),所有多方块都摆在该坐标 - 仅客户端使用:服务端
CommonProxy不注册任何事件 updateEntities()被DummyWorld重写为空,NEI 中通过updateEntitiesForNEI()显式调用super.updateEntities()完成某些多方块的最后一步构建BUILDER.world.updateEntitiesForNEI()仅迭代tileMap.values()中canUpdate()的 TileEntity
实现差异(GT vs StructureLib)
GregTech 子类(GTGuiMultiblockHandler)
- 在
placeMultiblock()中:- 复制
stackForm,调用onItemUse(stack, FAKE_PLAYER, renderer.world, 0, 64, 0, 0, 0, 64, 0)在假世界放置控制器 (ITurnable) tTileEntity.setFrontFacing(ForgeDirection.SOUTH)强制朝南ChannelDataAccessor.setChannelData(trigger, GTStructureChannels.HATCH.get(), 1)先把 hatch 通道设为 1- 调用
INEIPreviewModifier.onPreviewConstruct(trigger)(GT 系列输入) - 若
ISurvivalConstructable→ 跑survivalConstruct直到!world.hasChanged()或达到MAX_PLACE_ROUNDS - 否则若
IConstructableProvider/IConstructable→ 直接拿 constructable →construct(trigger, false) - 最后
NEIPreviewModifier.onPreviewStructureComplete(trigger)(GT 系列输入) ChannelDataAccessor.unsetChannelData(trigger, GTStructureChannels.HATCH.get())
- 复制
- 额外维护
dotForPos(结构位置 → dot 编号),hatchGroupPositions(dot → 位置集合),hintForDot(dot → 描述文本) - 识别 hatchElement 多实现(
HATCH_ELEMENT/ 无 placement /buildAndChain)通过反射HATCH_BUILDER_GETTER取HatchElementBuilder拿getHint()+getHatchItemTypeName() getHoveredTooltip在原 tooltip 后追加「Hint N dot: X」「Valid hatches: Y」两行(gold 颜色 + dot 色)- 高亮 overlay 关闭深度测试,按
EnumColor.VALUES[dot % 12].color着色(alpha=150)通过BRUtil.renderOverlay(positions, pos, color, 150)
StructureLib 子类(StructureCompatGuiHandler)
- 在
placeMultiblock()中:- 通过
IMultiblockInfoContainer.get(tileClass)拿容器 →toConstructable(tile, ExtendedFacing.DEFAULT)转 ISurvivalConstructable - 调用
survivalConstruct(buildStack, Integer.MAX_VALUE, ISurvivalBuildEnvironment.create(CreativeItemSource.instance, FAKE_PLAYER)) - 若返回 -2(应进入构造阶段),
tryConstruct = true并 break 循环,再multi.construct(getBuildTriggerStack(), false)
- 通过
getContextObject()返回renderer.world.getTileEntity(MB_PLACE_POS.x, .y, .z)(用于BRUtil.scanCandidates上下文)
扫描线程
两个 MultiblockHandler 子类在静态初始化中各开一条线程:
GTConstructableScan (GregTech)
- 输入
METATILEENTITIES中所有IConstructable子类 - 每个
IConstructable通过反射取IStructureDefinition,对coil等 tiered 元素StructureHacks.addTieredElement - 在新线程中调用每个 constructable 的
survivalConstruct/construct,用ObserverWorld.estimateTierFromConstructable(stackConsumer, multi)收集:每当world.setBlock且哈希未记录则触发stackConsumer.accept(stack),hash 用BRUtil.hashStack(stack) = item.hashCode() * 31 + meta * 31 - 最终写入
multiBlockComponents = Long2ObjectMap<ObjectSet<IConstructable>>
MultiblockInfoContainerScan (StructureLib)
- 输入
IMultiblockInfoContainer.MULTIBLOCK_MAP(所有@MultiBlockEntry注册的 TileEntity 多导包) - 用
ObserverWorld.getConstructableFromContainer(className, container)反射拿 TileEntity → constructable - 注册
MinecraftForge.EVENT_BUS监听StructureEvent.StructureElementVisitedEvent - 每个被访问的
IStructureElement调用StructureHacks.getStacksForElement(tile, element, currentData)提取物块 - 同时用
estimateTierFromInfoContainer探测最大 tier(嵌套 construct + 累加空 hash 直到hasChanged=false或tier < MAX_TRIES=64) - 收集
stacks = constructable -> ItemStack(用于 NEI 显示名)
参与方块
- 无注册方块(纯客户端库)
- 依赖第三方 mod 的多方块机器和 ItemConstructableTrigger