玩家事件
基本信息
| 属性 | 值 |
|---|---|
| 事件实现类 | 20(api/event/Player*Event.java) |
| 已注册为脚本类型 | 20(全部带 @ZenClass) |
| 实际会被触发 | 5 |
| 声明但从不触发 | 15 |
| 管理器实现 | api/event/MTEventManager.java(IEventManager 的实现) |
| 事件基础设施 | IEventManager、MTEventManager、IEventHandle、minetweaker/util/EventList |
20 个事件类型
全部命名空间为 minetweaker.event.*,Java 类名见下表左列:
| 脚本名 | Java 类 | 实际触发 | 触发点 |
|---|---|---|---|
PlayerInteractEvent |
PlayerInteractEvent |
是 | ForgeEventHandler.java:47 |
PlayerLoggedInEvent |
PlayerLoggedInEvent |
是 | FMLEventHandler.java:50 |
PlayerCraftingEvent |
PlayerCraftedEvent |
是 | FMLEventHandler.java:61 |
PlayerSmeltedEvent |
PlayerSmeltedEvent |
是 | FMLEventHandler.java:72 |
PlayerLoggedOutEvent |
PlayerLoggedOutEvent |
是 | FMLEventHandler.java:82 |
PlayerAttackEntityEvent |
同名 | 否 | 无 |
PlayerBonemealEvent |
同名 | 否 | 无 |
PlayerChangedDimensionEvent |
同名 | 否 | 无 |
PlayerDeathDropsEvent |
同名 | 否 | 无 |
PlayerFillBucketEvent |
同名 | 否 | 无 |
PlayerInteractEntityEvent |
同名 | 否 | 无 |
PlayerOpenContainerEvent |
同名 | 否 | 无 |
PlayerPickupEvent |
同名 | 否 | 无 |
PlayerPickupItemEvent |
同名 | 否 | 无 |
PlayerPickupXpEvent |
同名 | 否 | 无 |
PlayerRespawnEvent |
同名 | 否 | 无 |
PlayerSleepInBedEvent |
同名 | 否 | 无 |
PlayerUseHoeEvent |
同名 | 否 | 无 |
PlayerUseItemStartEvent |
同名 | 否 | 无 |
PlayerUseItemTickEvent |
同名 | 否 | 无 |
「5 个触发」如何核实
grep -rn 'events\.publish' src/main/java 全仓只有 5 处命中,全部在两个 handler 里:
| 触发方法 | 宿主 | FML/Forge 事件 | 发布语句 |
|---|---|---|---|
onPlayerInteract |
ForgeEventHandler |
net.minecraftforge.event.entity.player.PlayerInteractEvent |
ForgeEventHandler.java:38-48 |
onPlayerLoggedIn |
FMLEventHandler |
PlayerEvent.PlayerLoggedInEvent |
FMLEventHandler.java:41-51 |
onPlayerItemCrafted |
FMLEventHandler |
PlayerEvent.ItemCraftedEvent |
FMLEventHandler.java:53-67 |
onPlayerItemSmelted |
FMLEventHandler |
PlayerEvent.ItemSmeltedEvent |
FMLEventHandler.java:69-77 |
onPlayerLoggedOut |
FMLEventHandler |
PlayerEvent.PlayerLoggedOutEvent |
FMLEventHandler.java:79-83 |
两个 handler 在预初始化期注册(MineTweakerMod.java:136-137):
MinecraftForge.EVENT_BUS.register(new ForgeEventHandler());
FMLCommonHandler.instance().bus().register(new FMLEventHandler());
对另外 15 个事件,逐个统计「除自身类文件与 MTEventManager.java 外的引用数」,结果全部为 1,且那唯一一处都是 IEventManager 的接口方法声明。例如:
$ grep -rn '\bPlayerAttackEntityEvent\b' src/main/java | grep -v MTEventManager | grep -v 'api/event/PlayerAttackEntityEvent.java'
src/main/java/minetweaker/api/event/IEventManager.java:24: IEventHandle onPlayerAttackEntity(IEventHandler<PlayerAttackEntityEvent> ev);
即:类型已注册、on* 订阅方法已实现、EventList 已分配,但没有任何 publish* 调用,事件永远不会到达脚本。这是 API 迁移留下的空壳,不是可用功能。
MTEventManager 的实现范围
MTEventManager 为全部 20 个事件各实现三件套(MTEventManager.java:78-390):
onXxx(IEventHandler<XxxEvent>)→IEventHandlehasXxx()→booleanpublishXxx(XxxEvent)→ void
clear()(:51-72)清空全部 20 个 EventList,由 reload() 步骤 2 调用(MineTweakerImplementationAPI.java:638),所以脚本订阅的事件监听器每次重载都会被清掉,必须写在脚本里重新注册。
缺陷:publishPlayerPickup 存在两个重载
MTEventManager 里 publishPlayerPickup 出现两次,靠参数类型区分:
| 签名 | 发布到 | 源码行 |
|---|---|---|
publishPlayerPickup(PlayerPickupEvent) |
elPlayerPickup |
MTEventManager.java:230-232 |
publishPlayerPickup(PlayerPickupXpEvent) |
elPlayerPickupXp |
MTEventManager.java:324-326 |
第二个名字应为 publishPlayerPickupXp。因为两个都是 0 调用方(见上节),目前无实际影响,但对阅读代码的人极具误导性。
事件句柄
IEventHandle(api/event/IEventHandle.java:12-13)标注 @ZenClass("minetweaker.event.IEventHandler")(注意脚本名与 Java 类名不同),继承 java.io.Closeable,只有 1 个 @ZenMethod:
@ZenMethod
@Override
void close();
脚本里保存 events.onPlayerCrafted(...) 的返回值即可用 .close() 取消订阅。
处理器实现
| 类 | 文件 | 说明 |
|---|---|---|
EventList<T> |
minetweaker/util/EventList.java:14 |
泛型双向链表式订阅列表:clear() :19、add() :23、hasHandlers() :39、isEmpty() :43、publish() :47;内嵌私有 EventNode implements IEventHandle(:63-76),其 close() 即取消订阅 |
IEventHandler<T> |
minetweaker/util/IEventHandler.java |
函数式回调,单方法 void handle(T event) |
CloseablePriorityList |
minetweaker/util/CloseablePriorityList.java |
通用可关闭优先列表(EventList 之外的独立工具) |