Better Teleporter

基本信息

属性 值
类名 net.blay09.mods.netherportalfix.BetterTeleporter
父类 net.minecraft.world.Teleporter
注册范围 Overworld (dimensionId == 0) 和 Nether (dimensionId == -1)
安装方式 替换 WorldServer.worldTeleporter 字段
触发事件 WorldEvent.Load(NetherPortalFix.onWorldLoad)
modid netherportalfix

功能

原版 Minecraft 的 Teleporter 在多人模式中存在一个传送目的地错配问题:玩家 A 进入下界时原版会缓存 目的地坐标,但如果另一位玩家 B 之后又从同一区块使用过同一对传送门,原版算法会把缓存的"目的地坐标"覆盖为 B 当时进入的方向,导致 A 返回主世界时被错误地传送到 B 的出口位置(而非自己的入口)。

BetterTeleporter 通过在玩家个体 NBT 上保存返回门户历史(每条记录含 From/To X/Y/Z 与 From/To Dim),并在 placeInPortal(玩家出传送门)与 placeInExistingPortal(玩家进入已存在传送门)两个时机维护这份历史,确保玩家返回主世界时仍能落在自己出发的那个传送门附近。

非玩家实体(僵尸、动物等)走原版 Teleporter 路径,不享受修复。

触发条件

  • 仅 WorldServer 且维度为 0(主世界)或 -1(下界)时被替换。
  • 世界加载事件 WorldEvent.Load 触发时一次性安装(@SubscribeEvent)。
  • 仅在玩家进入/退出传送门时被调用,对正常移动无影响。

关键常量

常量 值 含义
PORTAL_RANGE_SQR 9 返回门户匹配时的平方距离阈值(实际欧氏距离 ≤ 3 格)
NBT_RETURN_PORTALS "ReturnPortals" 玩家 NBT 中存储返回门户历史列表的 tag 名
NBT_FROM_X/Y/Z/DIM "FromX"/"FromY"/"FromZ"/"FromDim" 来源侧(出发时)坐标与维度
NBT_TO_X/Y/Z/DIM "ToX"/"ToY"/"ToZ"/"ToDim" 目的地侧(到达后)坐标与维度

返回门户历史以 NBTTagList 形式存放在玩家 EntityPlayer.PERSISTED_NBT_TAG 标签下,跨维度持久化。

placeInPortal(玩家出传送门)

调用时机:玩家到达目标维度并即将在传送门中生成。

算法:

  1. 在调用 super.placeInPortal 之前先构造 from = new PortalPositionAndDimension(oldX, oldY, oldZ) 记录出发地。
  2. 调 super.placeInPortal(entity, oldX, oldY, oldZ, rotationYaw),由原版逻辑决定玩家实际落地坐标 to = new PortalPositionAndDimension(player.getPlayerCoordinates())。
  3. 反向遍历 tagList(从末尾到头),把所有 to 与当前 to 在同一维度且平方距离 ≤ PORTAL_RANGE_SQR(≤3 格)的旧记录全部清除,避免同一出口被重复登记。
  4. 把当前的 (from, to) 打包成新 NBTTagCompound 并 appendTag 到列表末尾。

效果:每次玩家"出"传送门就在 NBT 中追加一条 (from, to) 记录。

placeInExistingPortal(玩家进入已存在的传送门)

调用时机:玩家进入传送门(通常是返回主世界)。

算法:

  1. 反向遍历 tagList(从末尾到头),逐条检查候选 to:
    • 若 to.dimensionId == entity.worldObj.provider.dimensionId(同维度)
    • 且 to.getDistanceSquaredToChunkCoordinates(lastPortalPosition) ≤ PORTAL_RANGE_SQR(≤3 格)
    • 则视为匹配当前出口位置。
  2. 取出该条记录的 from(原始入口坐标与维度),用 ChunkCoordIntPair.chunkXZ2Int(x, y) 作为 key 把它临时注入到 destinationCoordinateCache:
    • 若该 key 不存在则同步追加到 destinationCoordinateKeys。
    • 如果 destinationCoordinateCache 之前已有旧值,先把它备份到 oldValue。
  3. 调用 super.placeInExistingPortal,原版逻辑此时会按缓存中的 from 把玩家放到原入口附近。
  4. placeInExistingPortal 返回后,把 oldValue(如有)还原回缓存——这样下一次其他玩家进入同一区块时不会被本玩家的"私人目的地"污染。
  5. 移除本次匹配的 tagList 条目并写回玩家 NBT。

效果:把玩家私人的入口处作为目的地,避开原版"任何人都能覆盖全局缓存"的并发问题。

PortalPositionAndDimension

BetterTeleporter 内的内部类:

字段 类型 含义
posX/posY/posZ int(继承自 PortalPosition) 坐标
lastUpdateTime long(继承自 PortalPosition) 时间戳,由 super(...) 写入 world.getWorldTime()
dimensionId int 玩家所在维度 ID(在主世界为 0,在下界为 -1)

三个构造器分别接受 ChunkCoordinates、(x,y,z)(使用当前 world.provider.dimensionId)、(x,y,z,dim)。

用于把"维度 + 坐标"绑定到一处比较距离时只需比较坐标(前提是已校验维度相等)。

数值

数值 默认值 说明
匹配半径(平方) 9 平方欧氏距离 ≤ 9 → 实际 ≤ 3 格
单玩家 NBT 列表上限 无显式限制 每次出传送门新增一条,列表随玩家出入持续增长
玩家实体支持 EntityPlayer 非玩家实体走原版路径,不享受修复
维度支持 0 / -1 仅主世界与下界;末地(1)走原版 Teleporter

限制

  • 末地(维度 1)未安装:从末地返回主世界不会走 BetterTeleporter,原版的错配问题仍存在。
  • 非玩家实体未保护:僵尸、动物、掉落物走原版路径。
  • NBT 列表无界:长时间在线玩家若频繁进出传送门,ReturnPortals 列表可能膨胀。
  • 异步竞态:destinationCoordinateCache 是 WorldServer 单例,依赖"放入-调用-还原"三步序列在同一个 tick 内完成;其他 mod 若在 placeInExistingPortal 内插入事件,可能破坏还原时序。

第三方 mod 集成

本类只重写 Teleporter 的两个方法,不调用任何跨 mod API。无需第三方 mod 依赖即可工作。