触发器与施法
主动触发器
EmakiSkills 内置了 14 种主动触发器,覆盖了鼠标点击、Q 键丢弃和数字键切换三类操作。主动触发器需要玩家在施法模式下手动按键触发:
| 触发器 ID | 显示名称 | 事件来源 | 冲突规则 |
|---|---|---|---|
left_click | [左键] | InteractTriggerSource | 与 shift_left_click 冲突 |
right_click | [右键] | InteractTriggerSource | 与 shift_right_click 冲突 |
shift_left_click | [Shift + 左键] | InteractTriggerSource | 与 left_click 冲突 |
shift_right_click | [Shift + 右键] | InteractTriggerSource | 与 right_click 冲突 |
drop_q | [Q 键] | DropTriggerSource | 无冲突 |
hotbar_1 | [数字键 1] | HotbarTriggerSource | 无冲突 |
hotbar_2 | [数字键 2] | HotbarTriggerSource | 无冲突 |
hotbar_3 | [数字键 3] | HotbarTriggerSource | 无冲突 |
hotbar_4 | [数字键 4] | HotbarTriggerSource | 无冲突 |
hotbar_5 | [数字键 5] | HotbarTriggerSource | 无冲突 |
hotbar_6 | [数字键 6] | HotbarTriggerSource | 无冲突 |
hotbar_7 | [数字键 7] | HotbarTriggerSource | 无冲突 |
hotbar_8 | [数字键 8] | HotbarTriggerSource | 无冲突 |
hotbar_9 | [数字键 9] | HotbarTriggerSource | 无冲突 |
冲突规则
冲突规则通过 incompatible_with 配置。当两个触发器互相冲突时,同一玩家不能同时把它们绑定到不同槽位。
为什么 left_click 和 shift_left_click 要冲突?因为在 Bukkit 中,玩家按 Shift+左键时会同时触发 PlayerInteractEvent 的左键事件。如果两个都绑了技能,系统无法确定玩家到底想放哪个。所以设计上让它们互斥,避免歧义。
事件来源
| 来源类 | 监听事件 | 说明 |
|---|---|---|
InteractTriggerSource | PlayerInteractEvent | 左键/右键/Shift 组合 |
DropTriggerSource | PlayerDropItemEvent | Q 键丢弃物品(事件会被取消,物品不会真的丢出去) |
HotbarTriggerSource | PlayerItemHeldEvent | 数字键切换快捷栏(事件会被取消,快捷栏不会真的切换) |
被动触发器
被动触发器由游戏事件自动触发,不需要施法模式。被动技能在定义文件中通过 passive_triggers 列表声明使用哪些被动触发器。
EmakiSkills 内置了 22 种被动触发器:
| 触发器 ID | 显示名称 | 触发事件 | target 传递 |
|---|---|---|---|
attack | [攻击命中] | 玩家攻击实体 | 被攻击实体 |
damaged_by_entity | [被实体伤害] | 玩家被实体伤害 | 攻击者 |
damaged | [受到伤害] | 玩家受到任意伤害 | 无 |
death | [死亡] | 玩家死亡 | 无 |
kill_entity | [击杀实体] | 击杀非玩家实体 | 被杀实体 |
kill_player | [击杀玩家] | 击杀玩家 | 被杀玩家 |
shoot_bow | [射出弓箭] | 射出弓箭 | 无 |
arrow_hit | [箭矢命中实体] | 箭矢命中实体 | 命中实体 |
arrow_land | [箭矢落地] | 箭矢命中方块 | 无(传递落点) |
shoot_trident | [掷出三叉戟] | 掷出三叉戟 | 无 |
trident_hit | [三叉戟命中实体] | 三叉戟命中实体 | 命中实体 |
trident_land | [三叉戟落地] | 三叉戟命中方块 | 无(传递落点) |
break_block | [破坏方块] | 破坏方块 | 无(传递方块位置) |
place_block | [放置方块] | 放置方块 | 无(传递方块位置) |
drop_item | [丢弃物品] | 丢弃物品(非潜行) | 掉落物实体 |
shift_drop_item | [Shift+丢弃物品] | 丢弃物品(潜行中) | 掉落物实体 |
swap_items | [交换主副手] | 交换主副手(非潜行) | 无 |
shift_swap_items | [Shift+交换主副手] | 交换主副手(潜行中) | 无 |
login | [登录] | 玩家加入服务器 | 无 |
sneak | [潜行] | 开始潜行 | 无 |
teleport | [传送] | 玩家传送 | 无(传递目标位置) |
timer | [定时] | 定时检查 | 无 |
timer 触发器的间隔由 passive_trigger_settings.timer_interval_ticks 配置,默认 20 tick(1 秒)。
被动施法流程
被动技能的施法流程比主动技能简单——跳过了施法模式检查和槽位绑定查找:
游戏事件 → PassiveTriggerDispatcher.dispatch(invocation)
│
├─ 1. 检查触发器是否存在且启用
│
├─ 2. 遍历玩家所有已解锁技能
│ 过滤:trigger_type == passive && passive_triggers 包含当前触发器
│
├─ 3. 检查强制延迟 / 全局冷却 / 技能冷却
│
├─ 4. 检查资源消耗
│
├─ 5. MythicMobs 施法(传递事件目标信息)
│
└─ 成功后:消耗资源、记录冷却target 传递
被动技能触发时,事件中的目标实体和位置会传递给 MythicMobs 的 @target。比如 attack 触发器会把被攻击的实体作为 target,MythicMobs 技能中的 @target 就指向这个实体。没有目标实体的触发器以玩家自身为施法者,不传递额外目标。
施法模式入口
主动技能需要玩家先进入施法模式,触发器事件才会被分发到技能系统。非施法模式下,主动触发器事件会被忽略,玩家的正常操作不受影响。被动技能不受施法模式影响,始终处于监听状态。
施法模式固定使用 F 键(交换主副手键)切换。按一下 F 键进入施法模式,再按一下退出。进入施法模式后,F 键原本的交换主副手功能会被拦截(事件被取消)。
cast_mode:
entry_key: "f"
restore_last_state_on_join: true为什么不支持 G 键?
G 键是 Minecraft 客户端的本地按键,按键事件不会发送到服务端。Spigot、ProtocolLib、PacketEvents 都无法监听到 G 键。F 键(PlayerSwapHandItemsEvent)是服务端能可靠捕获的按键,所以固定使用 F 键。
主动技能施法流程
当主动触发器事件到达 CastAttemptService 时,会依次执行以下 10 步检查。任何一步失败都会中止施法并返回对应的失败原因:
触发器事件 → CastAttemptService.attemptCast(player, triggerId)
│
├─ 1. 验证输入
│ player 非空,triggerId 非空
│
├─ 2. 检查施法模式
│ CastModeService.isCastModeEnabled(player) == true
│ ✗ → 失败: NOT_IN_CAST_MODE
│
├─ 3. 查找绑定
│ 遍历 PlayerSkillProfile.bindings,找到 triggerId 匹配的 SkillSlotBinding
│ ✗ → 失败: NO_BINDING
│
├─ 4. 查找技能定义
│ SkillRegistryService.getDefinition(binding.skillId)
│ ✗ → 失败: SKILL_NOT_FOUND
│
├─ 5. 验证技能仍在解锁池中
│ PlayerSkillStateService.getUnlockedSkills(player) 包含 binding.skillId
│ ✗ → 失败: SOURCE_LOST(装备已卸下)
│
├─ 6. 检查强制延迟
│ PlayerCastTimingState.isForcedDelayActive() == false
│ ✗ → 失败: FORCED_DELAY_ACTIVE
│
├─ 7. 检查全局冷却
│ PlayerCastTimingState.isGlobalCooldownActive() == false
│ ✗ → 失败: GLOBAL_COOLDOWN_ACTIVE
│
├─ 8. 检查技能冷却
│ PlayerCastTimingState.isSkillOnCooldown(skillId) == false
│ ✗ → 失败: SKILL_COOLDOWN_ACTIVE
│
├─ 9. 检查资源消耗
│ 遍历 SkillDefinition.resourceCosts:
│ ├─ local-resource → 检查 PlayerLocalResourceState
│ ├─ ea-resource → 通过 EaBridge 检查 EA 资源
│ └─ ea-attribute-check → 通过 EaBridge 检查 EA 属性
│ ✗ → 失败: RESOURCE_INSUFFICIENT
│
├─ 10. MythicMobs 施法
│ MythicSkillCastService.cast(player, mythicSkillId)
│ ✗ → 失败: MYTHIC_CAST_FAILED
│
└─ 成功后处理:
├─ 消耗资源(operation = CONSUME 的资源)
├─ 记录技能冷却 (cooldown_ticks)
├─ 记录全局冷却 (global_cooldown_ticks)
└─ 记录强制延迟 (forced_global_cast_delay_ticks)注意资源消耗的时机:检查在第 9 步,但实际扣除在第 10 步成功之后。这样如果 MythicMobs 施法失败,资源不会被白白消耗。
触发器有效性验证
同步技能池时(resync),系统会额外检查每个槽位绑定的触发器是否仍然有效。如果某个触发器在配置中被禁用了(enabled: false),对应的槽位绑定会被自动清除。这避免了"绑了技能但按键没反应"的困惑——与其让玩家自己排查,不如直接清掉无效绑定。
失败原因枚举
| 失败原因 | 说明 |
|---|---|
NOT_IN_CAST_MODE | 未进入施法模式 |
NO_BINDING | 触发器未绑定技能 |
SKILL_NOT_FOUND | 技能定义不存在(可能被删除或配置错误) |
SOURCE_LOST | 技能来源丢失(装备已卸下,技能不再解锁) |
FORCED_DELAY_ACTIVE | 强制延迟中(刚释放过技能,还在全局最小间隔内) |
GLOBAL_COOLDOWN_ACTIVE | 全局冷却中 |
SKILL_COOLDOWN_ACTIVE | 技能冷却中 |
RESOURCE_INSUFFICIENT | 资源不足 |
MYTHIC_SKILL_NOT_FOUND | MythicMobs 技能不存在 |
MYTHIC_CAST_FAILED | MythicMobs 施法失败 |
ActionBar 服务
ActionBarService 在施法模式下定时刷新玩家的 ActionBar,显示当前技能槽位状态。玩家可以一眼看到自己绑定了哪些技能、哪个在冷却中。
模板变量
| 变量 | 说明 |
|---|---|
{slot_1} ~ {slot_N} | 各槽位的技能名称(未绑定显示为空) |
{slot_display} | 所有槽位的格式化展示 |
{forced_delay} | 当前强制延迟剩余时间 |
配置示例
actionbar:
enabled: true
refresh_interval_ticks: 10
template_cast_mode: "&aCast Mode &7| {slot_display}"
template_idle: "&7Idle"EaBridge 与 MythicBridge
这两个桥接服务是 EmakiSkills 和外部插件通信的通道。
EaBridge
EaBridge 通过 Bukkit ServicesManager 获取 EmakiAttributeBridge 服务,用于读取和消耗 EA 管理的资源与属性:
| 方法 | 说明 |
|---|---|
isAvailable() | EA 桥接是否可用(EA 插件是否已加载) |
readResourceCurrent() | 读取 EA 资源当前值 |
consumeResource() | 消耗 EA 资源 |
readAttributeValue() | 读取 EA 属性值 |
providerMode() | 当前提供者模式 |
MythicBridge
MythicBridge 封装 MythicMobs API,负责实际的技能释放:
| 方法 | 说明 |
|---|---|
isAvailable() | MythicMobs 是否可用 |
skillExists() | 检查 MythicMobs 技能是否存在 |
cast() | 执行 MythicMobs 技能释放(主动技能,无 target) |
cast(target, location) | 执行 MythicMobs 技能释放(被动技能,传递 target 实体和位置) |
如果 MythicMobs 未安装,isAvailable() 返回 false,所有施法尝试都会在第 10 步失败。
GUI 布局
skills_gui — 技能 GUI(6 行)
行 1: [边框] [边框] [边框] [边框] [边框] [边框] [边框] [边框] [边框]
行 2: [边框] [技能1] [技能2] [技能3] [技能4] [技能5] [技能6] [技能7] [边框]
行 3: [边框] [技能8] [技能9] [技能10] [...] [...] [...] [...] [边框]
行 4: [边框] [边框] [边框] [边框] [边框] [边框] [边框] [边框] [边框]
行 5: [边框] [槽位1] [槽位2] [槽位3] [边框] [施法模式] [边框] [边框] [边框]
行 6: [边框] [边框] [边框] [边框] [边框] [边框] [边框] [边框] [边框]- 技能区域(行 2-3):显示所有已解锁的技能,点击选择要绑定的技能
- 槽位区域(行 5):显示当前绑定的技能槽位,点击后选择触发器
- 施法模式按钮:切换施法模式开关
trigger_select_gui — 触发器选择 GUI(4 行)
行 1: [边框] [边框] [边框] [边框] [边框] [边框] [边框] [边框] [边框]
行 2: [边框] [触发器1] [触发器2] [触发器3] [触发器4] [触发器5] [边框] [边框] [边框]
行 3: [边框] [触发器6] [触发器7] [...] [...] [...] [边框] [边框] [边框]
行 4: [边框] [边框] [边框] [边框] [返回] [边框] [边框] [边框] [边框]- 触发器区域(行 2-3):显示所有可用触发器。已被其他槽位使用的触发器会标记,与已选触发器冲突的会显示为灰色不可选
- 返回按钮(行 4):返回技能 GUI