ForgeRelocation

[!INFO] Git Commit: 21da7aa | Updated: 2026-08-04

ForgeRelocation(modid ForgeRelocation)不是一个内容 mod,而是一个 API + 引擎:让任意 mod 能在世界里把一整块方块结构(包括方块实体)平滑地推移一格,并负责客户端动画与状态同步。

同一个 jar 里还塞了第二个 mod MCFrames(modid MCFrames),它是 ForgeRelocation 引擎的一个完整参考实现——提供玩家可用的框架方块与红石马达。

mcmod.info 里唯一的描述是仓库 README 的第一句:“An API for handling the movement of blocks.”

⚠️ 这个 mod 不是什么

任务简报把它描述为"改写 Minecraft 物品/metadata 模型的 mod"。这是错的,源码核实结论:

  • 没有物品 / metadata 重写、没有注册表重映射、没有 ore dict 映射(addMapping 零命中)
  • 这里的 “relocation” 指的是在世界内搬迁方块(move blocks from one place to another),不是"重定位物品 ID"
  • 唯一的 metadata 相关代码是方块搬运器查表时读的 w.getBlockMetadata(x, y, z)——用来决定某个方块的哪个 meta 变体绑哪个搬运器,本身不改写任何东西
  • 本 mod 唯一的方块改写手段是 ASM 核心 mod,且只改写两个原版客户端渲染类(RenderBlocks.renderBlockByRenderType、TileEntityRendererDispatcher.renderTileEntity),与注册表无关

基本信息

属性 值
仓库目录 ForgeRelocation(作者 MrTJP,GTNewHorizons fork)
Minecraft 1.7.10
Forge 10.13.4.1614(gradle.properties)
源码规模 33 个源文件:20 个 .scala + 13 个 .java(混合工程)
根包 mrtjp.relocation(modGroup)
依赖 required-after:MrTJPCore;构建期另有 CodeChickenCore:1.4.17:dev、MrTJPCore:1.3.7:dev
构建 GTNH 约定构建(gtnh.settings.blowdryerTag = 0.2.0),enableModernJavaSyntax = false
核心 mod coreModClass = asm.RelocationPlugin(usesMixins = false)
内容维度 仅 block 与 setting
配置 config/ForgeRelocation.cfg + config/MCFrames.cfg,共 4 项,见 配置文件

语言构成

层 语言 文件数 位置
API 层 Java 13 mrtjp/relocation/api/(7)、mrtjp/mcframes/api/(6)
实现层 Scala 20 mrtjp/relocation/(12)、mrtjp/mcframes/(8)

13 个 Java 文件全部是接口 / 抽象类 / 简单 POJO(含 2 个 package-info.java),没有一行是 net.minecraft 的类。全部 @Mod 入口、ASM 转换器、运动引擎、渲染代码都是 Scala。两个 @Mod 注解里都写了 modLanguage = "scala"。

一个 jar、两个 mod

项 ForgeRelocation MCFrames
@Mod 类 mrtjp.relocation.handler.RelocationMod mrtjp.mcframes.handler.MCFramesMod
modid ForgeRelocation MCFrames
内容 纯引擎 + API,3 个方块中只有 1 个不可获得 2 个玩家可用方块 + 1 条配方
@API 标记 ForgeRelocation|API 被刻意注释掉(避免循环依赖,见下)

两者都硬依赖 MrTJPCore,但都不声明彼此为 FML 依赖——MCFrames 隐式依赖 ForgeRelocation。

⚠️ 由此产生一个工程问题:FML 排序 mod 容器时会算进 @API,而同一 jar 里的两个 API 互相"嵌入对方",形成循环加载顺序。源码的处理方式是把 mrtjp/mcframes/api/package-info.java 的 @API 注解整段注释掉并在文件里留了一张 ASCII 依赖图说明。副作用:MCFramesAPI 不受 API 版本隔离保护。

全部 11 个条目

方块(3 个)

方块 modid 方块名 能否获得 说明
移动占位方块 relocation.blockmovingrow ❌ setCreativeTab(null) 动画期间占据行首的内部方块,带动态碰撞箱与实体推动
框架方块 mcframes.frame ✅ 唯一配方产出 8 个 6 面全 true 的连接器,64 种朝向模型运行时代码生成
马达方块 mcframes.motor ❌ 无配方 6 面 × 4 姿态,红石驱动,16 tick 推一格

全 jar 只有 1 条配方(GameRegistry.addRecipe 全仓库仅 1 处):logWood + Items.stick 环形产出 8 个框架方块。马达与占位方块都没有配方。

机制与设定(8 个)

条目 内容
运动模型(结构与行的分解) 核心:tryStartMove 把乱序坐标切成平行的 BlockRow、ID 分配、每 tick 推进、完成时的 5 步收尾序列(含 scheduled tick 修复)
方块搬运器注册表 3 个内置搬运器(saveload/coordpush/static)、四级查找优先级、preferredMover/mandatoryMover 两层绑定
客户端渲染:移动中的方块如何画出来 MovingRenderer 的半透明重绘、MovingRenderBlocks 的 z-fighting 规避、MovingWorld 光照代理、ASMHacks
ASM 核心 mod IFMLLoadingPlugin + 单个 Transformer,用 Map 驱动改写 2 个原版渲染类;开发/生产双 SRG 名判定
网络协议 2 类 PacketCustom(type 1 描述符 / type 2 完成)、Short.MaxValue 与 255 双终止符、DC: 前缀踢人
MCFrames 卡扣注册表 resolveStick 三级 getFrame 回退、方向性 latchMap、StickResolver_Impl 工作表 BFS
API 接口 10 个 Java 接口/抽象类;Relocator 双栈池与 8 项校验;MCFrames 无 @API 标记的原因
配置文件 两个 .cfg 共 4 项;mover registry 的归一化回写;src/main/resources 完整清单

核心机制速览

一次移动的完整链路

[马达方块] 红石信号 → TileMotor.update()
  ↓  MCFramesAPI.getStickResolver.getStructure(...)   ← 解析整条框架结构(排除马达自身)
  ↓  Relocator.push / setWorld / setDirection / setSpeed(1/16) / addBlocks / execute / pop
[ForgeRelocation] MovementManager2.tryStartMove()
  ↓  检查 blocks.size <= moveLimit(2048)
  ↓  MathLib.normal/basis 分桶 → splitLine → rhrAxis  ← 切成平行的 BlockRow
  ↓  MovingTileRegistry.canRunOverBlock 逐行检查行首     ← 不通则整次失败
  ↓  放置 TileMovingRow 占位方块 + BlockStruct + sendStruct()
[每 tick]  push() 推进 progress → 客户端插值渲染 + TileMovingRow 推实体
[完成]      doMove → postMove → endMove → rescheduleTicks → 邻居更新 → rerender

三个内置搬运器

名称 机制 适用
saveload(默认) writeToNBT → 改 NBT 坐标 → 删原 TE → 写目标方块 → TileEntity.createAndLoadEntity 任何正确实现 NBT 的方块实体;可靠但 CPU 密集
coordpush invalidate() → 改 xCoord/yCoord/zCoord → validate() 同一 TE 实例,快;要求方块实体不缓存坐标
static 全部空实现,canMove 恒 false 钉死某方块

搬运器查找优先级

blockMetaMap[(b, meta)] → blockMetaMap[(b, -1)] → modMap[modid] → defaultMover

叠加优先级:配置文件 > preferredMover(软)> mandatoryMover(硬,覆盖一切)。

本 mod 没有的东西(grep 证据)

以下断言均已在 /Users/evlos/a/mirror/ForgeRelocation/ 的 src/ 全量检索核实:

维度 检索命令 结果
指令 grep -rn "ICommand|addChatCommand|CommandBase" src/ 0 命中
热键 grep -rn "KeyBinding" src/ 0 命中
实体 grep -rn "registerEntity|registerModEntity|EntityFX|spawnParticle" src/ 0 命中
附魔 / Buff grep -rnE "Enchantment|PotionEffect" src/ 0 命中
群系 / 世界生成 / 命令方块 / 维度 grep -rnE "BiomeGen|IBiomeProvider|CommandBlock|WorldGenerator|registerWorldGenerator|WorldProvider|registerDimension" src/ 2 命中,均无关:renders.scala:184-185 的 override def getBiomeGenForCoords —— MovingWorld 转发 IBlockAccess 接口方法的一行实现
结构 / 成就 grep -rn "Achievement|structure" src/(structure 仅在 javadoc/注释中作英文单词) 无注册点
多方块 grep -rn "Multiblock|IMultiblock|Controller" src/ 0 命中
能量 / 流体接口 grep -rnE "IEnergyHandler|IFluidHandler|FluidRegistry" src/ 0 命中(马达只读红石 getBlockPowerInput)
矿词典 API 调用 grep -rn "OreDictionary" src/ 0 命中
ore dict 映射 / 存档兼容 grep -rn "addMapping|missingMapping" src/ 0 命中
FML 原生配置 grep -rnE "@Config|ForgeConfiguration|Configuration\.get" src/ 0 命中(全部走 MrTJPCore ModConfig)
第二个 IClassTransformer 实现 grep -rn "IClassTransformer" src/ 2 命中,同一文件(asm/Transformer.scala:12 的 import、:19 的类声明);getASMTransformerClass 返回长度为 1 的数组
per-mod 映射数据文件 find src/main/resources -type f | grep -vE '\.(png|obj)$' 仅 mcmod.info 与 assets/mcframes/lang/en_US.lang
Access Transformer / Mixin getAccessTransformerClass = null;gradle.properties 中 accessTransformersFile =、usesMixins = false 无 src/main/resources/META-INF/ 目录,无 *.mixins.json
中文语言文件 find src/main/resources -name "*.lang" 仅 en_US.lang(5 行),无 zh_CN.lang

因此只建了 block/ 与 setting/ 两个目录;item、entity、multiblock、enchantment、dimension、keybinding、potioneffect、biome、structure、achievement、item-effect 共 11 个维度无内容,未建目录、未在正文里留占位。

源码中的若干不一致(均为原文,未修正)

位置 现象
mcframes/handler/proxies.scala:39 registerPreferredMover("mod:Relocation", "coordpush") —— 但 modid 是 ForgeRelocation,Loader.isModLoaded("Relocation") 恒假,该软绑定完全失效;且模式匹配落到 case _,把 "mod:Relocation" 当方块名塞进 blockMetaMap 的 (null, -1) 键
mcframes/handler/motor.scala 的 onBlockActivated setSide((side + 1) % 6) 对 6 取模,但 side 只有 0~3 四个有效值;setSide 用位或写入,传入 4/5 会让 orientation 越出预期范围
asm/Transformer.scala:25-26 lazy val blockClass 与 lazy val teClass 从未被引用(死代码)
api/IFrameInteraction.java、api/IFramePlacement.java javadoc 均称 “must be registered in the RelocationAPI”,实际应注册到 MCFramesAPI
handler/Relocator_Impl.scala:97 execute() 声明返回 Unit(Scala → void),而 Java 抽象方法声明 boolean;且 tryStartMove 的 Boolean 结果被直接丢弃,调用方拿不到 moveLimit 超限的反馈
handler/mcframes/api/StickResolver_Impl world/start/excl 是 object 上的全局可变字段,不可重入、非线程安全;iterate 抛异常时收尾的三行清理不执行
registry.scala 的 StaticTileMover canMove 恒 false,但生产代码路径从不查询 canMove,因此"钉死"实际不生效
assets/mcframes/lang/en_US.lang 4 条显示名全部英文原文;马达的显示名是 “Debug Frame Motor”(调试用)
语言文件位置 ForgeRelocation 自己的方块语言键写在 MCFrames 的 en_US.lang 里(ForgeRelocation 无 assets/relocation/ 目录)
network.scala:47-50 handlePacket 的 match 无 case _,未知包类型抛 MatchError(不带 DC: 前缀),不会被同方法的 catch 捕获
events.scala RelocationEventHandler 同时注册到 FMLCommonHandler.bus 与 MinecraftForge.EVENT_BUS,FML 事件在两条总线上各触发一次