Skip to content

๐Ÿ“ก Event: QISkillUseEvent โ€‹

Previous: APIใ€€ยทใ€€Next: Placeholders

QS's main skill-cast chain is a Bukkit event โ€” QISkillUseEvent. It is provided by QinhCoreLib (not QS, not QI), so all three parties share the same EventBus contract. This chapter covers its fields, how QS uses it internally, and how other plugins can listen / cancel / read results. This document targets QS 1.0.22.


1. Event identity โ€‹

Package: com.qinhuai.corelib.action.skill
Class:   QISkillUseEvent   (extends PlayerEvent, implements Cancellable)

Why in CoreLib? Because QI handles the "press" and QS handles "whether it can be cast" โ€” both sides need to reference the same event type. Putting it in CoreLib, which both sides hard-depend on, is the only way to share a single event-bus contract.

Every skill cast (item keypress, command, QinhSkillsAPI call, passive trigger) fires one of these events via SkillEventGateway.dispatch.


2. Field overview โ€‹

FieldTypeR/WMeaning
playerPlayerRThe caster (PlayerEvent.getPlayer())
payloadStringRRaw payload (skill id / JSON)
triggerStringRCompatibility string trigger tag (e.g. "api", a QI action key)
triggerTypeTriggerTypeRNormalized trigger enum (fromLegacy(trigger))
itemItemStack?RThe triggering item (null for command/API triggers)
itemIdString?RThe triggering item's id
rawContextRawSkillContext?RRaw context (nullable); carries trigger-scene info like itemId / item / sneaking / source, provided by QinhCoreLib
skillHandledBooleanR/WWhether QS has handled it (set true on a successful cast)
castResultString?R/WCastResult.name (e.g. "SUCCESS", "ON_COOLDOWN")
castAttemptedBooleanR/WWhether a gate/execution attempt was entered
fallbackInvokedBooleanR/WWhether the fallback handler was taken
mythicInvokedBooleanR/WWhether MythicMobs execution was reached
primaryPipelineBooleanR/WWhether this is the primary chain (default true)

Cancellation: isCancelled() / setCancelled(Boolean) (from Cancellable).

โš ๏ธ castResult is a string, not an enum. It is CastResult's .name. To get the enum, use CastResult.valueOf(it) (and guard against future new result codes).


3. How QS uses it internally โ€‹

QS plays two related roles internally:

a) The gateway fires the event โ€” SkillEventGateway โ€‹

SkillEventGateway.dispatch builds the event and callEvents it, then derives the CastResult from the outcome:

kotlin
val event = QISkillUseEvent(
    player = player,
    payload = payload,
    trigger = trigger,
    triggerType = TriggerType.fromLegacy(trigger),
)
Bukkit.getPluginManager().callEvent(event)
if (event.isCancelled) return CastResult.SCRIPT_BLOCKED
if (!event.skillHandled) return event.castResult?.let { CastResult.valueOf(it) } ?: CastResult.MYTHIC_FAILED
return event.castResult?.let { CastResult.valueOf(it) } ?: CastResult.SUCCESS

b) Listening and executing โ€” QS's core listener โ€‹

QS listens for QISkillUseEvent, runs the full runtime (normalize โ†’ state machine โ†’ graph resolution โ†’ gating โ†’ execution โ†’ post-processing), then writes back the skillHandled / castResult / mythicInvoked fields.

c) QI action-system integration โ€” QiListener โ€‹

QS also implements CoreLib's QinhActionHandler contract (QiListener), registered into QI's action system with handlerId = "qinhskills:cast". This is the fallback path (the primary chain is QI firing the event directly via dispatchViaEvent); it does not synthesize a second event and only kicks in to backstop when the primary chain didn't handle the cast.

kotlin
class QiListener : QinhActionHandler {
    override val handlerId: String = QISkillBridge.HANDLER_ID   // "qinhskills:cast"
    override fun isAvailable(): Boolean = true
    override fun dispatch(context: QinhActionContext): ActionDispatchResult { /* backstop execution */ }
}

4. How other plugins listen โ€‹

Any plugin can register a standard Bukkit listener. Common uses: auditing, banning certain skills, applying buffs / logging on cast.

Reading the result (listen late, low priority) โ€‹

kotlin
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
fun onSkill(event: QISkillUseEvent) {
    val skillId = QinhSkillsAPI.resolvePayloadSkillId(event.payload) ?: return
    if (event.skillHandled && event.castResult == "SUCCESS") {
        plugin.logger.info("${event.player.name} successfully cast $skillId (mythic=${event.mythicInvoked})")
    }
}

Cancelling (intercept a skill) โ€‹

kotlin
@EventHandler(priority = EventPriority.HIGH)
fun blockInArena(event: QISkillUseEvent) {
    val skillId = QinhSkillsAPI.resolvePayloadSkillId(event.payload) ?: return
    if (skillId == "dash" && inSafeZone(event.player)) {
        event.isCancelled = true   // QS receives the cancellation โ†’ returns SCRIPT_BLOCKED, the skill isn't cast
    }
}

Cancelling the event makes SkillEventGateway return CastResult.SCRIPT_BLOCKED: the skill is not cast, no resource is spent, and no cooldown is entered.


What you want to doSuggested priorityNotes
Intercept / block certain skillsHIGH / HIGHESTCancel before QS processes
Read-only result for loggingMONITOR + ignoreCancelled = trueQS has already written back castResult
Mutate payload front-matterLOWESTRarely needed; be careful not to break normalization

QS's core listener sits at mid priority and writes back fields. Put your "read-only" logic at MONITOR so it can read QS's final verdict.


6. How to parse the payload โ€‹

The event's payload may be a plain skill id, or JSON passed through by an item plugin. Don't hand-parse it โ€” hand it to the API:

kotlin
val skillId: String? = QinhSkillsAPI.resolvePayloadSkillId(event.payload)

The return value is always lowercase, and null if it can't be parsed.


Further reading โ€‹

  • API โ€” QinhSkillsAPI and programmatic casting
  • Script API โ€” intercept / side-effect inside pre_js / post_js (another gate beyond events)
  • Diagnostics & Protocol โ€” view the event [EVENT] stage with debug trace