Skip to content

触发器系统设计

1. 设计理念

触发器系统是 Symphony 的事件驱动核心,参考 MythicMobs 的触发器机制但更加通用化。它是连接游戏事件与词条/技能效果的桥梁。

核心流程:游戏事件 → 触发器匹配 → 条件检查 → 动作执行

2. 触发器类型注册

触发器类型采用注册表模式,支持内置类型和自定义扩展:

kotlin
// 触发器类型(可扩展)
class TriggerType private constructor(val id: String) {
    companion object {
        private val registry = ConcurrentHashMap<String, TriggerType>()
        
        fun of(id: String): TriggerType = registry[id] 
            ?: error("Unknown trigger type: $id")
        
        fun register(id: String): TriggerType {
            val type = TriggerType(id)
            registry[id] = type
            return type
        }
        
        fun custom(id: String) = register(id)
        
        // 内置类型
        val ON_ATTACK = register("ON_ATTACK")
        val ON_DEFEND = register("ON_DEFEND")
        // ... 其他内置类型
    }
}

3. 内置触发器

攻击/伤害类

触发器 ID说明上下文变量
ON_ATTACK攻击实体时attacker, victim, damage, damageType, isCritical, weapon
ON_ATTACK_CRITICAL暴击时attacker, victim, damage, critMultiplier
ON_KILL击杀实体时killer, victim, damage
ON_DAMAGE造成最终伤害时attacker, victim, finalDamage, rawDamage
ON_MELEE_ATTACK近战攻击时attacker, victim, damage, weapon
ON_RANGED_ATTACK远程攻击时attacker, victim, damage, projectile

防御/受伤类

触发器 ID说明上下文变量
ON_DEFEND被攻击时attacker, victim, damage, damageType
ON_DAMAGED受到伤害后attacker, victim, finalDamage, currentHealth
ON_BLOCK格挡成功时attacker, victim, blockedDamage
ON_DODGE闪避成功时attacker, victim, dodgedDamage
ON_DEATH死亡时victim, killer, damage
ON_LOW_HEALTH生命值低于阈值时entity, currentHealth, maxHealth, threshold

移动/交互类

触发器 ID说明上下文变量
ON_MOVE移动时entity, from, to, speed
ON_JUMP跳跃时entity, location
ON_SNEAK潜行切换时entity, isSneaking
ON_SPRINT疾跑切换时entity, isSprinting
ON_INTERACT交互时player, action, block, item
ON_RIGHT_CLICK右键点击时player, item, block
ON_LEFT_CLICK左键点击时player, item, block

装备/物品类

触发器 ID说明上下文变量
ON_EQUIP装备穿戴时player, item, slot
ON_UNEQUIP装备卸下时player, item, slot
ON_HOLD手持物品切换时player, item, previousItem
ON_CONSUME消耗物品时player, item
ON_BREAK_ITEM物品耐久耗尽时player, item

周期/状态类

触发器 ID说明上下文变量
ON_TIMER定时触发entity, interval, tickCount
ON_ENTER_COMBAT进入战斗时entity
ON_LEAVE_COMBAT脱离战斗时entity, combatDuration
ON_LEVEL_UP升级时player, oldLevel, newLevel
ON_RESPAWN重生时player
ON_JOIN加入服务器时player
ON_QUIT退出服务器时player

技能/法力类

触发器 ID说明上下文变量
ON_SKILL_CAST释放技能时caster, skill, level, targets
ON_SKILL_HIT技能命中时caster, victim, skill, damage
ON_MANA_USE消耗法力时entity, amount, remaining
ON_MANA_FULL法力充满时entity

4. 触发器上下文

kotlin
interface ITriggerContext {
    val triggerType: TriggerType
    val entity: LivingEntity
    val target: LivingEntity?
    val timestamp: Long
    val location: Location
    
    fun <T> get(key: String): T?
    fun set(key: String, value: Any)
    fun has(key: String): Boolean
    
    // 便捷属性
    val damage: Double? get() = get("damage")
    val item: ItemStack? get() = get("item")
    val isCritical: Boolean get() = get("isCritical") ?: false
}

5. 触发条件系统

5.1 内置条件

条件类型说明参数
CHANCE概率判定value (0~100)
COOLDOWN冷却时间value (毫秒)
HEALTH_ABOVE生命值高于value (百分比 0~100)
HEALTH_BELOW生命值低于value (百分比 0~100)
MANA_ABOVE法力值高于value (百分比 0~100)
MANA_BELOW法力值低于value (百分比 0~100)
HAS_PERMISSION拥有权限value (权限节点)
IN_WORLD在指定世界value (世界名)
IN_BIOME在指定生物群系value (生物群系 ID)
IS_SNEAKING正在潜行
IS_SPRINTING正在疾跑
IS_FLYING正在飞行
HOLDING_TYPE手持物品类型value (Material 名)
WEARING_SET穿戴套装value (套装 ID)
HAS_AFFIX拥有指定词条value (词条 ID)
ATTRIBUTE_ABOVE属性值高于attribute, value
ATTRIBUTE_BELOW属性值低于attribute, value
LEVEL_RANGE等级范围min, max
DAMAGE_TYPE伤害类型匹配value (physical/magic/fire...)
TARGET_TYPE目标类型value (PLAYER/MOB/BOSS...)
SCRIPTAria 脚本条件code (返回 boolean)

5.2 条件组合

支持 ANDORNOT 逻辑组合,可嵌套:

yaml
conditions:
  - type: AND
    children:
      - type: CHANCE
        value: 30
      - type: COOLDOWN
        value: 5000
      - type: OR
        children:
          - type: HEALTH_BELOW
            value: 50
          - type: HAS_AFFIX
            value: "berserker"

条件求值采用短路策略:AND 遇到 false 立即返回,OR 遇到 true 立即返回。

5.3 条件接口

kotlin
interface ITriggerCondition {
    val type: String
    fun evaluate(context: ITriggerContext, params: Map<String, Any>): Boolean
}

// 组合条件
class AndCondition(val children: List<ITriggerCondition>) : ITriggerCondition {
    override val type = "AND"
    override fun evaluate(context: ITriggerContext, params: Map<String, Any>): Boolean {
        return children.all { it.evaluate(context, params) }
    }
}

6. 触发器分发器

TriggerDispatcher 负责监听 Bukkit 事件并分发到词条系统:

kotlin
object TriggerDispatcher {
    
    fun dispatch(
        triggerType: TriggerType,
        entity: LivingEntity,
        contextBuilder: TriggerContext.Builder.() -> Unit
    ) {
        val context = TriggerContext.Builder(triggerType, entity)
            .apply(contextBuilder)
            .build()
        
        // 获取实体所有装备上的词条
        val affixes = collectEntityAffixes(entity)
        
        for (affix in affixes) {
            val definition = AffixManager.getDefinition(affix.affixId) ?: continue
            
            for (trigger in definition.triggers) {
                if (trigger.type != triggerType) continue
                
                // 评估条件
                if (!evaluateConditions(trigger.conditions, context, affix.parameters)) continue
                
                // 发布事件(允许取消)
                val event = AffixTriggerEvent(entity, affix, triggerType, context)
                Bukkit.getPluginManager().callEvent(event)
                if (event.isCancelled) continue
                
                // 执行动作
                executeActions(trigger.actions, context, affix)
            }
        }
    }
}

7. 自定义触发器

其他插件可通过 API 注册和触发自定义触发器:

kotlin
// 注册自定义触发器类型
TriggerManager.register(
    TriggerType.custom("myplugin:on_combo"),
    displayName = "连击触发",
    contextKeys = listOf("comboCount", "totalDamage")
)

// 在适当时机触发
TriggerDispatcher.dispatch(
    TriggerType.of("myplugin:on_combo"),
    entity = player
) {
    set("comboCount", 5)
    set("totalDamage", 150.0)
}

8. ON_TIMER 触发器实现

定时触发器通过 BukkitRunnable 实现,每 tick 检查:

kotlin
class PeriodicTriggerTask : BukkitRunnable() {
    private var tickCount = 0L
    
    override fun run() {
        tickCount++
        
        for (player in Bukkit.getOnlinePlayers()) {
            val affixes = collectEntityAffixes(player)
            
            for (affix in affixes) {
                val definition = AffixManager.getDefinition(affix.affixId) ?: continue
                
                for (trigger in definition.triggers) {
                    if (trigger.type != TriggerType.ON_TIMER) continue
                    
                    val interval = trigger.getParam("interval", 20) // 默认 20 tick = 1 秒
                    if (tickCount % interval != 0L) continue
                    
                    TriggerDispatcher.dispatch(TriggerType.ON_TIMER, player) {
                        set("interval", interval)
                        set("tickCount", tickCount)
                    }
                }
            }
        }
    }
}