Skip to content

๐Ÿงฉ 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.

yaml
# plugin.yml
softdepend: [QinhSkills]

โš ๏ธ Some QinhSkillsAPI signatures reference types from QinhCoreLib (e.g. the QISkillUseEvent event, TriggerType). If compilation reports missing com.qinhuai.corelib.*, also install-file QinhCoreLib and reference it as provided.

Availability check (soft fail) โ€‹

Don't call the API when QS is absent. Rely on softdepend for load order, and check liveness before use:

kotlin
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 โ€‹

MethodReturnsEffect
resolvePayloadSkillId(payload)String?Resolves the skill id from a payload (JSON / plain string); returns null if it can't be parsed
hasSkillDefinition(skillIdOrPayload)BooleanWhether the skill is defined on the server
isUnlocked(player, skillIdOrPayload)BooleanWhether 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)BooleanCasts the skill; == (castDetailed==SUCCESS)
castDetailed(player, payload)CastResultCasts and returns a detailed result code
castSkill(player, skillId)CastResultSame 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)BooleanWhether currently silenced/locked out (state machine LOCKED)
unsilence(player)โ€”Lifts silence immediately

๐Ÿ“Œ unlock / lock / setLevel / setSlot all write to disk (PlayerProfileStore.save). Watch the frequency when batch-calling across many players; see Data Storage.


4. Casting skills โ€‹

Simplest: success or not โ€‹

kotlin
val ok: Boolean = QinhSkillsAPI.cast(player, "fire_wave")
if (!ok) player.sendMessage("ยงcCan't cast that")

Getting the reason code โ€‹

kotlin
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 codeMeaning
SUCCESSSuccess (handed off to MM for execution)
SKILL_NOT_FOUNDSkill not defined
INVALID_PAYLOADCould not parse a skill id from the payload
NOT_UNLOCKEDPlayer has not unlocked it
ON_COOLDOWNOn cooldown (including cooldown groups, charges exhausted)
INSUFFICIENT_RESOURCEInsufficient resource (e.g. mana)
CONFLICTHit a mutex group
CAST_MODE_BLOCKEDCast mode not allowed (e.g. toggle state conflict)
CONDITION_FAILEDDeclarative condition / pre_js didn't pass
MYTHIC_FAILEDMM execution stage failed (or the event went unhandled)
SCRIPT_BLOCKEDA listener cancelled the event
CHANNELINGA channel cast bar is in progress; cannot re-enter
NO_TARGETTargeting was required:true but no target was locked
SILENCEDSilenced/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 read castResult. See Events.
  • The trigger string is normalized into an enum by TriggerType.fromLegacy(trigger). The API path always passes "api".
  • Don't bypass QinhSkillsAPI to 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 trigger tag (e.g. to let passive logic distinguish the source), you can call the lower-level SkillCastPipeline.executeViaGateway(player, payload, "your tag") directly, but for the vast majority of cases QinhSkillsAPI is 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:

kotlin
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 โ€‹