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 的最大迭代轮数

形成机制

  1. NEI 调用 MultiblockHandler.loadCraftingRecipes(stack) 或 loadUsageRecipes(stack)
  2. 调用抽象方法 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 后填充)
  3. loadCraftingRecipes 填充 currentMultiblocks 后,drawBackground(recipe) 中调用 guiHandler.loadMultiblock(multi, stackForm, ConstructableData.getTierData(multi).setTierFromStack(lastStack))
  4. 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