Warp 数据包

[!INFO] 处理器:shukaro.warptheory.net.PacketHandler(SimpleChannelInboundHandler<IWarpPacket>) 分发器:shukaro.warptheory.net.PacketDispatcher 数据包:7 个具体类 + 1 个接口 IWarpPacket

结构

net/packets/ 下共 8 个文件:

类型 文件
接口 IWarpPacket
具体包(7) BloodPacket、ClearPacket、ClientEventPacket、DecrementPacket、EnderParticlesPacket、FakeRainPacket、VelocityPacket

⚠️ PacketHandler 用 SimpleChannelInboundHandler<IWarpPacket> 泛型通配接收,故一个 handler 承载全部包类型; channelRead0 内部靠 instanceof 逐个分派(不是每个包一个 handler)。

按方向分派

channelRead0 先取网络处理器判断方向:

INetHandler handler = ctx.channel().attr(NetworkRegistry.NET_HANDLER).get();

if (handler instanceof NetHandlerPlayServer) { … }
else if (handler instanceof NetHandlerPlayClient) { … }
包 方向 处理
DecrementPacket C→S 递减玩家的 ears 曲曲标记
EnderParticlesPacket S→C 客户端生成 128 个 portal 粒子
VelocityPacket S→C 玩家 addVelocity
BloodPacket S→C 写入 WarpBlood.bloody / blackBloody
ClearPacket S→C 清理客户端血块缓存
ClientEventPacket S→C 通用事件通知
FakeRainPacket S→C 触发假雨

⚠️ 7 个包中只有 DecrementPacket 是客户端→服务端, 其余 6 个都是服务端→客户端。

DecrementPacket 的服务端处理

唯一的上行包,处理逻辑硬编码了 id == 0:

if (dec.id == 0) {
    EntityPlayer player = MiscHelper.getPlayerByEntityID(dec.player);
    int ears = MiscHelper.getWarpTag(player).getInteger("ears");
    MiscHelper.getWarpTag(player).setInteger("ears", --ears);
    if (ears <= 0) MiscHelper.getWarpTag(player).removeTag("ears");
}

即 id 字段目前只有一个有效值 0, 其语义是对玩家 NBT 标签 ears 减 1,降到 ≤0 时移除该标签。 ears 对应 默认曲曲效果 中 默认关闭的 EARS(name = "Deaf")—— 即「致聋」的剩余时间计数。

⚠️ dec.player 来自客户端,仅经 MiscHelper.getPlayerByEntityID 转换, 无任何额外校验。id 非 0 时静默忽略(无日志)。

EnderParticlesPacket 的 128 粒子循环

客户端处理固定循环 128 次生成 portal 粒子:

for (int l = 0; l < 128; ++l) world.spawnParticle("portal",
        blink.x + world.rand.nextDouble() - world.rand.nextDouble(), …,
        (double) (world.rand.nextFloat() - 0.5F) * 0.2F, …);

坐标抖动为 ±1(两次 nextDouble() 相减), 速度分量为 (rand-0.5) * 0.2,即 ±0.1。 粒子类型固定 "portal",对应 BLINK 瞬移效果(门槛 130)的视觉表现。

player == null 时直接 return(静默)。

PacketDispatcher 的 7 个发送方法

方法 目标 范围
sendToAllAround(EnderParticlesPacket, dim, x, y, z, 128) 周围玩家 半径 128
sendToPlayer(VelocityPacket, player) 单玩家 —
sendToPlayer(BloodPacket, player) 单玩家 —
sendToPlayer(ClearPacket(0), player) 单玩家 —
sendToServer(DecrementPacket(0, player.getEntityId())) 服务端 —
sendToPlayer(ClientEventPacket(0, amount), player) 单玩家 —
sendToPlayer(FakeRainPacket(eventLevel), player) 单玩家 —

⚠️ sendToAllAround 的最后一个参数硬编码 128(方块半径), 是全模组唯一的广播发送;其余 6 个都是点对点。

⚠️ 多个包的 id 字段在发送侧硬编码为 0 或直接传 eventLevel, ClearPacket(0) 与 DecrementPacket(0, …) 的 id 参数虽为 0, 但服务端只对 DecrementPacket 检查 id == 0; ClearPacket 的 id 与 ClientEventPacket 的 id 在 channelRead0 中未被检查。

BloodPacket 的两级事件

BloodPacket 用 eventLevel 区分两种血块缓存:

eventLevel 写入的缓存
0 WarpBlood.bloody
1 WarpBlood.blackBloody

两者都是 Map<维度, BlockCoord>(util/BlockCoord), 由 BloodPacket 携带的 dim 字段索引。

副作用:Warp 事件可被其它模组 hook

handlers/ 下有 4 个事件接口, 供外部代码在曲曲触发前后插入逻辑:

接口 用途
IWarpEvent 曲曲效果本体(抽象类,非接口)
IMultiWarpEvent 一次触发多次的效果(如 WarpBats)
ITimerWarpEvent 定时类效果
IWorldTickWarpEvent 世界刻类效果

IMultiWarpEvent 的构造签名含「次数」与「次数计算函数」, 如 WarpBats:super("bats", minWarp, 3, world -> 15 + world.rand.nextInt(30)) = 触发 3 次,每次在半径 15 + rand(30) 内。

⚠️ IWarpEvent 虽以 I 开头,实际是 public abstract class IWarpEvent(抽象类), 只有 IHealable / IHurtable / IWarpPacket 等才是真接口。

相关条目