Floodgate事件总线揭秘:FloodgateEventBus与SkinApplyEvent开发实战指南
【免费下载链接】FloodgateHybrid mode plugin to allow for connections from Geyser to join online mode servers.项目地址: https://gitcode.com/gh_mirrors/fl/Floodgate
Floodgate 是连接 Geyser 与在线模式 Java 服务器的混合模式插件,它的事件总线(FloodgateEventBus)是二次开发的入口。本文带你完整理解 Floodgate 事件总线的三层架构,并通过皮肤事件 SkinApplyEvent 动手实战:如何获取总线、注册监听器、控制事件顺序、取消皮肤应用。🎯
事件总线是什么?为什么需要它?
Floodgate 在玩家登录、皮肤下发、插件卸载等关键节点都会触发内部事件。事件总线就是这些事件的"广播中心":
- 解耦:皮肤应用、账号绑定、统计上报等功能各自监听事件,互不干扰
- 可拦截:事件可被取消(
Cancellable),插件可以阻止默认行为 - 有序执行:通过
PostOrder控制监听器在 EARLY / NORMAL / LAST 阶段执行
对开发者来说,想让自己的插件响应 Floodgate 的行为(比如"玩家皮肤即将被替换时插入自定义逻辑"),事件总线就是官方支持的标准做法。
三层架构:接口、实现、订阅者
Floodgate 的事件系统分为清晰的三层:
| 层级 | 类 | 作用 |
|---|---|---|
| API 接口 | FloodgateEventBus | 暴露给第三方插件的总线接口 |
| 核心实现 | EventBus | 基于 GeyserMC 事件框架的EventBusImpl |
| 订阅实现 | EventSubscriber | 把监听方法包装成总线上的订阅 |
FloodgateEventBus本身只是一个薄封装,继承自 GeyserMC 通用事件框架:
public interface FloodgateEventBus extends EventBus<Object, FloodgateSubscriber<?>> { }📄 接口定义:api/src/main/java/org/geysermc/floodgate/api/event/FloodgateEventBus.java#L30
真正的实现类EventBus负责创建订阅者,并把@Subscribe注解上的postOrder(执行顺序)、ignoreCancelled(是否忽略已取消事件)参数传递下去:
📄 实现类:core/src/main/java/org/geysermc/floodgate/event/EventBus.java#L39-L62
如何拿到事件总线实例
最推荐的方式是通过FloodgateApi获取,无需关心注入细节:
FloodgateEventBus eventBus = FloodgateApi.getInstance().getEventBus();内部由InstanceHolder静态持有总线实例,在插件初始化时通过set()一次性注入,并带有一个UUID key防止跨实例误用:
📄 获取方法:api/src/main/java/org/geysermc/floodgate/api/FloodgateApi.java#L154-L156
📄 实例持有者:api/src/main/java/org/geysermc/floodgate/api/InstanceHolder.java#L36-L44
SkinApplyEvent 皮肤事件详解
SkinApplyEvent是 Floodgate 目前对外暴露的核心事件:当 Floodgate 收到一个玩家皮肤、准备应用到服务器玩家身上时触发。
它携带三类信息(接口定义在 api/src/main/java/org/geysermc/floodgate/api/event/skin/SkinApplyEvent.java#L39-L73):
player():即将接收皮肤的FloodgatePlayercurrentSkin():玩家当前已应用的皮肤(可能为null)newSkin():待应用的新皮肤,可通过newSkin(SkinData)替换
皮肤数据结构SkinData包含两部分:value()(Base64 纹理数据)和signature()(签名),这是 Bedrock 平台向服务器证明皮肤合法性的关键。
关键行为:默认情况下事件会被取消
接口注释中有一句非常重要的说明 👇
当
hasSkin为 true 时,事件默认会被取消——因为 Floodgate 默认只在玩家还没有皮肤时才应用皮肤。
也就是说:如果你只想"观察"皮肤流,监听方法里做日志即可;如果你想强制覆盖玩家已有皮肤,需要在监听器中调用event.setCancelled(false)把它重新放行。
事件的具体实现是SkinApplyEventImpl,继承自AbstractCancellable,只允许修改newSkin、不允许修改玩家对象,保证了事件的不可变语义:
📄 事件实现:core/src/main/java/org/geysermc/floodgate/event/skin/SkinApplyEventImpl.java#L35-L67
实战:监听 SkinApplyEvent 的三个步骤
第一步:定义订阅类
订阅者需要实现FloodgateSubscriber<T>接口(对 GeyserMCSubscriber的标记性继承):
📄 订阅者接口:api/src/main/java/org/geysermc/floodgate/api/event/FloodgateSubscriber.java#L30
import org.geysermc.floodgate.api.event.FloodgateSubscriber; import org.geysermc.floodgate.api.event.skin.SkinApplyEvent; public class MySkinListener implements FloodgateSubscriber<SkinApplyEvent> { @Subscribe public void onSkinApply(SkinApplyEvent event) { // 观察或修改皮肤数据 event.newSkin(newSkinData); } }第二步:控制执行顺序
Floodgate 内部监听器大量使用@Subscribe(order = PostOrder.EARLY)/PostOrder.LAST来卡位。例如 Velocity 端的监听器在 velocity/src/main/java/org/geysermc/floodgate/listener/VelocityListener.java#L122 就用PostOrder.EARLY保证先于普通监听执行。
经验法则:
- 想先于所有业务逻辑:
PostOrder.EARLY - 想最后兜底、看最终结果:
PostOrder.LAST - 只想旁观、事件被取消时不参与:加上
ignoreCancelled = true
第三步:注册到总线
FloodgateEventBus eventBus = FloodgateApi.getInstance().getEventBus(); eventBus.subscribe(new MySkinListener());也可以直接订阅函数式处理器(EventBus.makeSubscription同时支持Subscriber和Consumer两种形式):
eventBus.subscribe(SkinApplyEvent.class, event -> logger.info("Skin for " + event.player().getName()), PostOrder.EARLY);这种函数式写法在核心代码里也很常见,例如关闭时清理资源:
📄 函数式订阅示例:core/src/main/java/org/geysermc/floodgate/module/CommonModule.java#L102
事件从哪里发出?看一条完整链路
以皮肤上传为例,可以清楚看到"事件源 → 总线 → 监听器"的完整链路:
- 玩家通过 Bedrock 上传皮肤,WebSocket 客户端接收数据:core/src/main/java/org/geysermc/floodgate/skin/SkinUploadSocket.java#L47
SkinUploadManager收到消息后,构造SkinApplyEventImpl并通过总线的@Subscribe处理器分发:core/src/main/java/org/geysermc/floodgate/skin/SkinUploadManager.java#L69- 各平台(Spigot / Bungee / Velocity)的
SkinApplier根据事件是否被取消,决定最终是否向 Java 端下发皮肤,例如 spigot/src/main/java/org/geysermc/floodgate/pluginmessage/SpigotSkinApplier.java
这条链路体现了 Floodgate 事件设计的精髓:平台差异被隔离在监听器里,事件本身保持平台无关。
常见问题清单 📋
| 问题 | 说明 |
|---|---|
| 监听器没被触发 | 确认订阅的是SkinApplyEvent接口而不是SkinApplyEventImpl实现类 |
| 事件总是"已取消" | 默认行为,见上文;需要时手动setCancelled(false) |
| 拿不到总线 | 插件加载太早,InstanceHolder尚未初始化;建议监听PostEnableEvent后再订阅 |
| 想区分执行先后 | 使用PostOrder.EARLY / NORMAL / LAST |
📄 生命周期事件参考:core/src/main/java/org/geysermc/floodgate/event/lifecycle/PostEnableEvent.java
总结
Floodgate 事件总线虽小(接口只有几行),但麻雀虽小五脏俱全:FloodgateEventBus负责暴露能力,EventBus负责调度,EventSubscriber负责把监听方法挂到总线上,而SkinApplyEvent则是最典型的"可取消 + 可修改 + 有序执行"综合案例。掌握这三层结构和PostOrder的用法,你就能安全地把自己的逻辑织入 Floodgate 的玩家生命周期,而不必修改任何平台插件代码。🚀
【免费下载链接】FloodgateHybrid mode plugin to allow for connections from Geyser to join online mode servers.项目地址: https://gitcode.com/gh_mirrors/fl/Floodgate
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考