Skip to content

Public API ​

Compile-time dependency ​

xml
<repositories><repository><id>jiuwu-releases</id><url>https://repo.crypticlib.com/repository/maven-public/</url></repository></repositories>
<dependency><groupId>emaki.jiuwu.craft</groupId><artifactId>emaki-skills-api</artifactId><version>2.8.7</version><scope>provided</scope></dependency>
kotlin
repositories { maven("https://repo.crypticlib.com/repository/maven-public/") }
dependencies { compileOnly("emaki.jiuwu.craft:emaki-skills-api:2.8.7") }

Compile only — never install, shade, or relocate this jar

The runtime already embeds this un-relocated API. A duplicate copy prevents Bukkit listeners and bridge types from matching, and the PDC codec being independently callable does not change that

Facade and layers ​

Availability is determined by EmakiSkillsApi.status().usable(). catalog(), operations(), and extensions() are never null: while the bridge is absent they return a stable unavailable implementation. install, uninstall, and Bridge are internal runtime contracts. Accessors are cheap and the bridge is replaced across a reload, so resolve the layer at the point of use; do not cache it in a field

catalog ​

catalog() returns skill definitions, player skill views, levels, learned state, cast mode, and max-level checks:

MethodThreadDescription
skills()anySnapshot of every loaded definition (including disabled ones), sorted by canonical id
skill(String skillId)anyOne definition; a blank or unknown id yields an empty Optional
playerSkills(Player)ownerSlot bindings and cast-mode snapshot; NOT_FOUND while the profile is not loaded
level(Player, String)ownerStored level, clamped into [1, maxLevel]
upgradePreview(Player, String)ownerQuotes the next upgrade: target level, success rate, currency and material costs. A quote only — nothing is charged or locked
hasLearned(Player, String)ownerTests only the manual-unlock source; it does not mean the player can use the skill
castModeEnabled(Player)ownerReads the cast-mode flag
atMaxLevel(Player, String)ownerWhether the skill is at its maximum. A skill without upgrades enabled has a maximum of 1

Thread contract: calling a per-player query off the owner thread returns WRONG_THREAD

Operations ​

The asynchronous cast methods are cast(Player,String) and castByTrigger(Player,String), both returning CompletableFuture<EmakiResult<SkillCastOutcome>>. They may be submitted from any thread; the runtime hops to the player's owner thread for the Bukkit, pre/post event, resource, and cooldown phases. The completion thread is not guaranteed, so any continuation that touches the player must hop back itself

learn, forget, forgetAll, equip, unequip, bindTrigger, upgrade, setLevel, addLevel, and setCastMode are synchronous methods and must be called on the player's owner thread. A failed upgrade roll is a successful process, represented by SkillUpgradeOutcome.successfulRoll()==false rather than an outer Failure. setLevel below 1 returns INVALID_INPUT, and values above the maximum are clamped rather than rejected

Extensions ​

extensions() registers two extension points: external skill sources and external skill definitions:

  • registerSkillSource(Plugin owner, SkillSourceProvider provider) returns a SkillSourceRegistration. Registering again under the same owner and provider id supersedes the previous entry, closing a superseded handle does not remove its replacement, and every registration owned by a plugin is dropped when that plugin is disabled. A null or already-disabled owner, or a blank id(), yields an inactive no-op handle
  • SkillSourceProvider.id() is unique within the owning plugin, priority() orders evaluation (lower first, default 100), and collect(Player) is called synchronously on the player's owner thread, so it must be fast and must not touch state owned by another entity or region

External skill definitions ​

registerSkillDefinition(Plugin owner, ExternalSkillDefinition definition) returns a SkillDefinitionRegistration; unregisterSkillDefinitions(Plugin owner) removes every external definition a plugin registered

ExternalSkillDefinition is an interface where only id() is abstract; every other accessor carries a default implementation:

MethodDefaultDescription
id()requiredCanonical skill id, normalized like a YAML skill id (trimmed, lower-cased with Locale.ROOT, spaces replaced by underscores). A blank id makes the registration inactive
displayName()falls back to id()Display name for UIs
description()empty listDescription lines shown in skill UIs
iconMaterial()""Material name used as the skill icon
activationType()"ACTIVE"ACTIVE or PASSIVE, case-insensitive; an unrecognized value behaves as ACTIVE
passiveTriggers()empty listPassive trigger ids this skill reacts to; ignored when active
cooldownTicks()0Cast cooldown in ticks; values below zero are clamped to zero
globalCooldownTicks()0Shared cooldown this skill imposes in ticks; values below zero are clamped to zero
scriptLines()empty mapCoreLib action lines per script phase, keyed by phase name; the recognized phases are cast, hit, miss, and fail (case-insensitive) and other keys are ignored; the syntax matches a YAML skill's script.actions. An empty map disables the native pipeline
scriptConditions()empty mapGate conditions per phase, using the same keys and syntax as scriptLines(); a phase runs only when its conditions pass
tags()empty listTags used for filtering and equip limits
loreAliases()empty listLore aliases that unlock this skill from item lore
pdcSkillId()""Persistent-data skill id used for equipment matching; empty to use id()
uiCategory()"default"UI category this skill belongs to; a blank value falls back to default
sortOrder()0Display order inside a category; lower appears first
showInSlots()trueWhether this skill appears in skill-slot UIs
enabled()trueWhether this skill is currently enabled; a disabled definition stays registered but is never reported as unlocked and cannot be cast
mythicSkill()""MythicMobs skill name to cast; used only when scriptLines() is empty

Behaviour boundaries (check before and after registering):

  • A registered definition participates exactly like a YAML skill from skills/: casting, slot binding, level and upgrade queries, skill-source collection, and what SkillCatalog reports; a registered definition overrides a YAML skill with the same id
  • Registering again under the same owner and id replaces the previous definition; every registration owned by a plugin is dropped when that plugin is disabled, and closing the handle unregisters earlier
  • Registering or removing a definition discards the compiled skill pipelines, so a replacement takes effect on the next cast without a /eskills reload; the definition survives a config reload for as long as the owning plugin stays enabled
  • The runtime reads the accessors each time it needs the definition and keeps the mapped result until the registration changes, so they must expose stable, side-effect-free values — build the definition once and return the same values on every call
  • The interface intentionally does not expose upgrade tables, cron schedules, parameter declarations, resource costs, or the condition DSL, so a code-defined skill has no upgrade path and no declarative cost or condition model; implement those with your own stages if needed. Its scripts are also not covered by the config precheck
  • When scriptLines() is empty but mythicSkill() names a MythicMobs skill, the skill is cast through MythicMobs instead
  • A null or already-disabled owner, a null definition, a blank or throwing id(), or a definition that cannot be mapped yields an inactive no-op handle; EmakiSkills being unavailable also yields a no-op handle

The script action registry is gone

Skill scripts are CoreLib pipelines, so third parties can no longer register script action stages with EmakiSkills. To add a stage you must use CoreLib's stage registry (EmakiCoreLibApi.registerActionStage); the stage then becomes available to every module at once. The twelve stages EmakiSkills registers itself are in CoreLib Actions

EquipmentSkillPdcCodec offers low-level payload read/write/copy/clear; live equipment access remains subject to the owner-thread constraint

Result semantics ​

No-payload success uses EmakiResult<Unit>; optional payloads are read through optionalValue(). The result factories are success, partial, and failure. FailureKind has exactly these values: UNAVAILABLE, NOT_FOUND, INVALID_INPUT, REJECTED, CANCELLED, TARGET_OFFLINE, WRONG_THREAD, INTERNAL_ERROR. Classify availability through status() or FailureKind; catching NullPointerException is not an acceptable substitute