๐งฉ QinhSkillsAPI โ
Previous: graph & CombosใยทใNext: Events
This chapter is for plugin developers: how to depend on QS, where the entry point lives, which methods QinhSkillsAPI exposes, and how to cast skills programmatically. This document targets QS 1.0.22.
QS has only one runtime pipeline. Every cast (item keypress / command / API / passive) flows into the same
SkillCastPipelineโSkillEventGateway. There is no "legacy / new" split in this chapter.
1. Depending on QS โ
QS works just like QI: the QS runtime is already installed on the server, so you only need a compile-time dependency (provided) โ don't bundle it into your own jar.
# plugin.yml
softdepend: [QinhSkills]โ ๏ธ Some
QinhSkillsAPIsignatures reference types fromQinhCoreLib(e.g. theQISkillUseEventevent,TriggerType). If compilation reports missingcom.qinhuai.corelib.*, also install-file QinhCoreLib and reference it asprovided.
Availability check (soft fail) โ
Don't call the API when QS is absent. Rely on softdepend for load order, and check liveness before use:
val qsReady = Bukkit.getPluginManager().getPlugin("QinhSkills")?.isEnabled == true
if (qsReady) {
QinhSkillsAPI.cast(player, "fire_wave")
}QS itself hard-depends on QinhCoreLib: if CoreLib isn't enabled, QS doesn't enable, so "QS enabled" implies CoreLib is present too.
2. Entry point: QinhSkillsAPI โ
Package: com.qinhuai.skills.api
Class: QinhSkillsAPI (Kotlin object singleton)- Kotlin direct:
QinhSkillsAPI.cast(player, "fire_wave") - Java with
.INSTANCE:QinhSkillsAPI.INSTANCE.cast(player, "fire_wave")
Every method accepts either a skill id or a QI payload (JSON / plain string). Internally it first normalizes the input to a lowercase skill id via resolvePayloadSkillId, so you don't have to care which form you pass in.
3. Method overview โ
| Method | Returns | Effect |
|---|---|---|
resolvePayloadSkillId(payload) | String? | Resolves the skill id from a payload (JSON / plain string); returns null if it can't be parsed |
hasSkillDefinition(skillIdOrPayload) | Boolean | Whether the skill is defined on the server |
isUnlocked(player, skillIdOrPayload) | Boolean | Whether the player has unlocked the skill |
unlock(player, skillId) | โ | Unlocks and saves the profile immediately |
lock(player, skillId) | โ | Locks and saves the profile immediately |
cast(player, payload) | Boolean | Casts the skill; == (castDetailed==SUCCESS) |
castDetailed(player, payload) | CastResult | Casts and returns a detailed result code |
castSkill(player, skillId) | CastResult | Same as castDetailed, semantically takes a skill id as input |
setLevel(player, skillId, level) | โ | Sets the skill level (floor 1), saves automatically |
setSlot(player, slot, skillId?) | โ | Sets/clears a skill slot (skillId=null clears it), saves automatically |
silence(player, durationMs) | โ | Silence/lockout: no skills can be cast for N milliseconds |
isSilenced(player) | Boolean | Whether currently silenced/locked out (state machine LOCKED) |
unsilence(player) | โ | Lifts silence immediately |
๐
unlock/lock/setLevel/setSlotall write to disk (PlayerProfileStore.save). Watch the frequency when batch-calling across many players; see Data Storage.
4. Casting skills โ
Simplest: success or not โ
val ok: Boolean = QinhSkillsAPI.cast(player, "fire_wave")
if (!ok) player.sendMessage("ยงcCan't cast that")Getting the reason code โ
when (val r = QinhSkillsAPI.castDetailed(player, "fire_wave")) {
CastResult.SUCCESS -> {}
CastResult.ON_COOLDOWN -> player.sendMessage("ยง7On cooldown")
CastResult.NOT_UNLOCKED -> player.sendMessage("ยง7Not unlocked yet")
CastResult.INSUFFICIENT_RESOURCE -> player.sendMessage("ยง9Not enough mana")
CastResult.SILENCED -> player.sendMessage("ยงcSkills are locked")
else -> player.sendMessage("ยงcCast failed: $r")
}All CastResult values โ
| Result code | Meaning |
|---|---|
SUCCESS | Success (handed off to MM for execution) |
SKILL_NOT_FOUND | Skill not defined |
INVALID_PAYLOAD | Could not parse a skill id from the payload |
NOT_UNLOCKED | Player has not unlocked it |
ON_COOLDOWN | On cooldown (including cooldown groups, charges exhausted) |
INSUFFICIENT_RESOURCE | Insufficient resource (e.g. mana) |
CONFLICT | Hit a mutex group |
CAST_MODE_BLOCKED | Cast mode not allowed (e.g. toggle state conflict) |
CONDITION_FAILED | Declarative condition / pre_js didn't pass |
MYTHIC_FAILED | MM execution stage failed (or the event went unhandled) |
SCRIPT_BLOCKED | A listener cancelled the event |
CHANNELING | A channel cast bar is in progress; cannot re-enter |
NO_TARGET | Targeting was required:true but no target was locked |
SILENCED | Silenced/locked out |
5. The programmatic cast path โ
QinhSkillsAPI.castDetailed internally goes through the unified entry point:
QinhSkillsAPI.castDetailed
โโ SkillCastPipeline.executeViaGateway(player, payload, trigger="api")
โโ SkillEventGateway.dispatch(plugin, player, payload, trigger)
โโ Builds QISkillUseEvent and callEvent (other plugins can listen/cancel)
โโ Cancelled โ SCRIPT_BLOCKED
โโ skillHandled โ castResult (default SUCCESS)
โโ Unhandled โ castResult (default MYTHIC_FAILED)Key points:
- Every cast fires a
QISkillUseEvent. This means other plugins can listen and cancel your programmatic cast, or readcastResult. See Events. - The
triggerstring is normalized into an enum byTriggerType.fromLegacy(trigger). The API path always passes"api". - Don't bypass
QinhSkillsAPIto synthesize a second event yourself โ the gateway has already fired one for you, and a duplicate callEvent would double-charge / double-gate.
If you want a custom
triggertag (e.g. to let passive logic distinguish the source), you can call the lower-levelSkillCastPipeline.executeViaGateway(player, payload, "your tag")directly, but for the vast majority of casesQinhSkillsAPIis enough.
6. Resolving a skill id from a payload โ
Item plugins often pass an entire JSON payload through to you. To extract the QS skill id from it:
val skillId: String? = QinhSkillsAPI.resolvePayloadSkillId(rawPayload)
// Returns null if it can't be parsed (not a QS payload)
if (skillId != null && QinhSkillsAPI.hasSkillDefinition(skillId)) {
// Confirmed to be a defined QS skill
}resolvePayloadSkillId accepts both a plain string skill id and a JSON form, and the return value is always lowercase.
Further reading โ
- Events โ
QISkillUseEventfields and listener examples - Placeholders โ runtime data exposed via PlaceholderAPI
- Script API โ
pre_js/post_jsinjected context - Diagnostics & Protocol โ
/qs protocol,/qs bridge, the protocol layer - Data Storage โ the
PlayerSkillProfileon-disk structure