๐ก 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 โ
| Field | Type | R/W | Meaning |
|---|---|---|---|
player | Player | R | The caster (PlayerEvent.getPlayer()) |
payload | String | R | Raw payload (skill id / JSON) |
trigger | String | R | Compatibility string trigger tag (e.g. "api", a QI action key) |
triggerType | TriggerType | R | Normalized trigger enum (fromLegacy(trigger)) |
item | ItemStack? | R | The triggering item (null for command/API triggers) |
itemId | String? | R | The triggering item's id |
rawContext | RawSkillContext? | R | Raw context (nullable); carries trigger-scene info like itemId / item / sneaking / source, provided by QinhCoreLib |
skillHandled | Boolean | R/W | Whether QS has handled it (set true on a successful cast) |
castResult | String? | R/W | CastResult.name (e.g. "SUCCESS", "ON_COOLDOWN") |
castAttempted | Boolean | R/W | Whether a gate/execution attempt was entered |
fallbackInvoked | Boolean | R/W | Whether the fallback handler was taken |
mythicInvoked | Boolean | R/W | Whether MythicMobs execution was reached |
primaryPipeline | Boolean | R/W | Whether this is the primary chain (default true) |
Cancellation: isCancelled() / setCancelled(Boolean) (from Cancellable).
โ ๏ธ
castResultis a string, not an enum. It isCastResult's.name. To get the enum, useCastResult.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:
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.SUCCESSb) 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.
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) โ
@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) โ
@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
SkillEventGatewayreturnCastResult.SCRIPT_BLOCKED: the skill is not cast, no resource is spent, and no cooldown is entered.
5. Recommended listener priorities โ
| What you want to do | Suggested priority | Notes |
|---|---|---|
| Intercept / block certain skills | HIGH / HIGHEST | Cancel before QS processes |
| Read-only result for logging | MONITOR + ignoreCancelled = true | QS has already written back castResult |
| Mutate payload front-matter | LOWEST | Rarely 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
MONITORso 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:
val skillId: String? = QinhSkillsAPI.resolvePayloadSkillId(event.payload)The return value is always lowercase, and null if it can't be parsed.
Further reading โ
- API โ
QinhSkillsAPIand 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