传送目标流程

一条路牌从「被刻写」到「被传送」的完整数据链路,以及链路上每个环节的实际约束。所有坐标都源自客户端,服务端不做距离/权限校验。

阶段一:刻写(创建传送目标)

玩家手持魔法路牌右键未刻写的路牌(客户端 onBlockActivated)
  → openSignPostLabelGui(x, y, z, player.posX, floor(posY-1), player.posZ)
  → 玩家输入铭文(上限 15 字符)并点「完成」
  → CreateJumpTargetMessage(signX/Y/Z, jumpX/Y/Z, label)      [判别号 0, C→S]
  → CreateJumpTargetMessageHandler
  → SignPostsMod.addJumpTarget(signX, signY, signZ, jumpX, jumpY, jumpZ, label, player)

SignPostsMod.addJumpTarget 做了什么(服务端):

步骤 代码 说明
1 player.worldObj.getTileEntity(signX, signY, signZ) 取路牌 TileEntity
2 if (tileEntity instanceof SignPostTileEntity) 不是路牌则整段跳过(不报错、不扣耐久)
3 int markerId = addGlobalMarker(signX, signZ, label, player.worldObj) 在 Antique Atlas 建全局标记(只取 X/Z)并 MarkersPacket 广播
4 signPost.setAsJumpLocation(jumpX, jumpY, jumpZ, markerId) 写入 4 个 NBT 字段(经 getBottom() 落到下半格)并 markBlockForUpdate
5 MarkerToTileMap.get(worldObj).setTileForMarker(signX, signY, signZ, markerId) 记录 markerId → {x,y,z} 映射(存档数据)
6 currentItemStack.damageItem(1, player) 扣 魔法路牌标记 1 点耐久,耗尽则销毁

重复刻写不会先删除旧标记:onBlockActivated 只在 markerId == 0 时才打开界面,所以正常玩家路径下不会产生孤儿标记。

阶段二:传送

玩家手持 Atlas 右键已刻写的路牌(客户端 onBlockActivated)
  → openAtlasGui(atlasStack, "signposts")
  → SignPostsAtlasGui:悬停 signPost 类型标记 → 染灰、toJump = marker
  → 玩家点击该标记
  → JumpMessage(markerId, "signposts")                        [判别号 1, C→S]
  → JumpMessageHandler
  → SignPostsMod.playerJump(markerId, player, "signposts")

SignPostsMod.playerJump 的执行顺序(服务端):

步骤 代码 说明
1 paymentHandlers.get(paymentHandlerKey) 按客户端给的键查处理器(可能为 null,源码无检查)
2 MarkerToTileMap.get(worldObj).getTileForMarker(markerId) 反查 {x,y,z},为 null 则整个方法静默返回
3 先用 coords[0]+0.5 / coords[1]+1 / coords[2]+0.5 作为默认跳转点 方块中心上方 1 格
4 if (tileEntity instanceof SignPostTileEntity) 则改用 tile.getJumpX/Y/Z() 覆盖默认值,读取刻写时存下的玩家原坐标
5 if (jumpX != 0 && jumpY != 0 && jumpZ != 0) 三个轴都必须非 0
6 player.capabilities.isCreativeMode || paymentHandler.pay(player, (int) jumpX, (int) jumpY, (int) jumpZ) 创造模式免付费
7 player.setPositionAndUpdate(jumpX, jumpY, jumpZ) 传送(浮点坐标直接 setPositionAndUpdate)

关键约束与已知失效条件

约束 来源 后果
目标必须是 SignPostTileEntity addJumpTarget / playerJump 坐标不对时静默无操作
markerId 必须在 MarkerToTileMap 中 playerJump 第 2 步 查不到则静默无操作
跳转目标三个轴都不能为 0 playerJump 第 5 步 刻写在坐标含 0 的轴上的路牌(主世界原点 X=0 或 Z=0)永久无法传送
无权限检查 全仓库 grep 无 permission/opLevel/ICommand 任何玩家都能传送,费用一致
无距离限制 playerJump 无距离判断 可跨整个世界瞬移
无维度校验 playerJump 不比对 player.dimension 目标坐标若与当前维度不符,由服务端坐标系统自行处理(通常落入地形或虚空)
客户端坐标直接采信 CreateJumpTargetMessageHandler 无校验 理论上可写入任意坐标

拆除时

路牌 的 breakBlock 在拆下半格时调用 SignPostsMod.removeJumpTarget(x, y, z, world):

int markerId = signPost.getMarkerId();
if (markerId != 0) {
    removeGlobalMarker(markerId, world);              // AtlasAPI.markers.deleteGlobalMarker
    MarkerToTileMap.get(world).removeMarker(markerId); // 清理映射
}

拆上半格只连带拆下半格、不调用 removeJumpTarget(由下半格的 breakBlock 负责清理)。markerId == 0(从未刻写)时不做任何清理。

延迟加载侧

传送目的地不做区块加载请求——player.setPositionAndUpdate 直接把玩家放进目标坐标,区块由服务端按需加载。因此跳转到未加载的远距离区域时可能短暂看到未渲染地形。

相关条目