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 等才是真接口。