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 事件在两条总线上各触发一次 |