05-events · git:20260827.8ccbbc4 · 2026-08-27 · sha256 361a222f0cdd1122
05-events git:20260827.8ccbbc4A
Immutable. This exact content is served forever at /api/v1/blob/361a222f0cdd1122.
# 05 — 事件系统
> 适用版本:Fabric 1.14.4
---
## 约束
### 核心原则
- Fabric 使用**事件回调**(Event Callback)而非 Forge 的 `@SubscribeEvent`
- 每个事件类型有自己的回调接口(如 `ServerTickCallback.EVENT`、`UseItemCallback`)
- 在 `onInitialize()` / `onInitializeClient()` 里对静态 `Event` 调用 `.register(lambda)`
- Fabric 事件是**静态 `Event` 字段**(单例),**没有** `@EventHandler` 注解
### 与 Forge 事件系统的区别
| Forge | Fabric |
|-------|--------|
| `@SubscribeEvent` 注解 | `SomeCallback.EVENT.register(lambda)`(或 `PlayerBlockBreakEvents.BEFORE.register`) |
| `MinecraftForge.EVENT_BUS` | 各事件类型上的静态 `Event` 字段 |
| `EventPriority` | Fabric **Phase**(`Event.DEFAULT_PHASE`),不是 Forge 的优先级枚举 |
| `event.setCanceled(true)` | 返回 `ActionResult` / `boolean`;不是 `CanceledEvent` |
不要编造 `ItemEvents` / `BlockEvents` / `EntityEvents` / `PlayerTickEvents` / `EntityTickEvents` / `AttackEvents`。
---
## Decision Flow
### Decision: 选择事件类型
```
IF 处理玩家每 tick 逻辑
→ 客户端 ClientTickCallback.EVENT
→ 服务端 ServerTickCallback.EVENT(再遍历 playerManager)
→ 不要 PlayerTickEvents(不是 Fabric API)
IF 处理实体每 tick 逻辑
→ 重写实体 tick(),或 ServerTickCallback.EVENT 里遍历世界实体
→ 不要 EntityTickEvents
IF 处理方块破坏
→ PlayerBlockBreakEvents.BEFORE / AFTER / CANCELED
→ 空手打方块:AttackBlockCallback.EVENT(官方 events 页示例)
IF 处理方块右键(含尝试放置)
→ UseBlockCallback.EVENT
→ 真正「方块已放置」后逻辑通常要 Mixin Block.place,没有 BlockEvents.BLOCK_PLACE
IF 处理物品使用(不对准方块)
→ UseItemCallback.EVENT
IF 处理实体死亡/伤害
→ AttackEntityCallback;死亡用 Mixin / 实体 onDeath
IF 处理服务端启动/关闭
→ ServerLifecycleEvents.SERVER_STARTED / SERVER_STOPPING / SERVER_STOPPED
IF 处理数据包加载/重载
→ loader-api 索引有 `ServerLifecycleEvents$StartDataPackReload` 等嵌套类型,但**字段表为空**,未用 1.14 FAPI javadoc 钉死静态字段名
→ 优先 `SERVER_STARTED`;不要编造 CallbackEvaluator、DataPackRegistry
```
---
## 常用 Fabric 事件
### 每 tick(客户端 / 服务端)
没有 `PlayerTickEvents`。客户端用 `ClientTickCallback.EVENT`,服务端用 `ServerTickCallback.EVENT`,需要「每个玩家」时自己遍历。
```java
@Environment(EnvType.CLIENT)
public class ExampleModClient implements ClientModInitializer {
@Override
public void onInitializeClient() {
ClientTickCallback.EVENT.register(client -> {
if (client.player == null) return;
// 每客户端 tick 结束(可访问 client.player)
});
}
}
public class ExampleMod implements ModInitializer {
@Override
public void onInitialize() {
ServerTickCallback.EVENT.register(server -> {
for (ServerPlayerEntity player : server.getPlayerManager().getPlayerList()) {
// 每 server tick、每个在线玩家
}
});
}
}
```
### 实体攻击(1.14.4 没有 `ServerLivingEntityEvents`)
不要编造 `EntityEvents.ENTITY_HURT` / `LivingEntityEvents` / `EntityEvent.TICK`。
死亡/受伤改数值用实体方法或 Mixin;交互用 `AttackEntityCallback`。
```java
AttackEntityCallback.EVENT.register((player, world, hand, entity, hitResult) -> {
if (entity instanceof PlayerEntity && !world.isClient) {
// 玩家攻击其他实体
}
return ActionResult.PASS;
});
```
### 方块破坏 / 右键方块
`PlayerBlockBreakEvents.BEFORE` 返回 **boolean**(`false` 取消破坏),不是 `ActionResult`。
```java
PlayerBlockBreakEvents.BEFORE.register((world, player, pos, state, blockEntity) -> {
if (state.getBlock() == Blocks.DIAMOND_ORE) {
// 防止挖掘钻石矿
return false;
}
return true;
});
PlayerBlockBreakEvents.AFTER.register((world, player, pos, state, blockEntity) -> {
// 方块已被破坏
});
// 官方文档示例同款:左键方块
AttackBlockCallback.EVENT.register((player, world, hand, pos, direction) -> {
return ActionResult.PASS;
});
// 右键方块(放置/交互)
UseBlockCallback.EVENT.register((player, world, hand, hitResult) -> {
return ActionResult.PASS;
});
```
### 物品使用(`UseItemCallback`)
对准方块的右键走 `UseBlockCallback`,不对准方块走这里。
本档返回 `ActionResult`。
不要返回不存在的 `ActionResult.PISTON`。
```java
UseItemCallback.EVENT.register((player, world, hand) -> {
ItemStack stack = player.getStackInHand(hand);
if (stack.getItem() == Items.DIAMOND && !world.isClient) {
// 使用钻石时的服务端逻辑
return ActionResult.SUCCESS;
}
return ActionResult.PASS;
});
```
### ServerLifecycleEvents
```java
ServerLifecycleEvents.SERVER_STARTED.register(server -> {
LOGGER.info("Server started: " + server.getName());
});
ServerLifecycleEvents.SERVER_STOPPING.register(server -> {
LOGGER.info("Server stopping...");
});
ServerLifecycleEvents.SERVER_STOPPED.register(server -> {
LOGGER.info("Server stopped");
});
ServerLifecycleEvents.END_DATA_PACK_RELOAD.register((server, resourceManager, success) -> {
if (success) {
// 数据包重载完成
}
});
```
## Fabric API 事件模块
交互/生命周期事件已包含在 **`fabric-api`** 里(`fabric-events-interaction-v0`、`fabric-lifecycle-events-v1` 等)。
不要写 `net.fabric.sdk`,也不要编造 `fabric-events-attack-v0` 或随手填模块版本号。
```groovy
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_api_version}"
```
需要给原版没有钩子的位置加事件时:自己 `EventFactory.createArrayBacked`,再在 Mixin 里 `EVENT.invoker()`(见官方 `develop/events`「Custom Events」)。不要编造 `AttackEvents.AFTER_DAMAGE`。
## 常见错误
- ❌ 使用 `@EventHandler` / `@SubscribeEvent` — Fabric 没有这些注解,回调不会被注册
- ❌ 在 `onInitialize()` 外、或条件分支里「有时才 register」— 容易漏注册;应在初始化时注册,逻辑放进 lambda
- ❌ 把 1.16+ 的 `ClientTickEvents` / `ServerTickEvents` 抄到 1.14.4 — 本档是 `ClientTickCallback` / `ServerTickCallback`
- ❌ 在客户端 lambda 里改服务端世界数据 — 用 `world.isClient` 区分,写世界只在服务端
- ❌ 忘记处理返回值 — `ActionResult` / `TypedActionResult` / `boolean` 决定是否取消或消费
- ❌ 把 `PlayerBlockBreakEvents.BEFORE` 当成 `ActionResult` — 它是 `boolean`
- ❌ 用 `ItemEvents` / `BlockEvents` / `PlayerTickEvents` — 不是本档 Fabric API
## 扩展点
| 配合 Skill | 协作说明 |
|-----------|---------|
| `mc-registry` | 事件处理中引用已注册的方块/物品/实体 |
| `mc-item` | `UseItemCallback` / `UseBlockCallback` 做物品交互 |
| `mc-entity` | 实体 tick / 属性;死亡用本页的 Living/Combat 事件或 Mixin |
| `mc-networking` | 事件里给玩家发自定义包(API 见 `06-networking.mdc`) |
| `mc-gui` | 客户端 tick 里打开 Screen;容器同步走 ScreenHandler/Container |