English
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:
| Method | Thread | Description |
|---|---|---|
skills() | any | Snapshot of every loaded definition (including disabled ones), sorted by canonical id |
skill(String skillId) | any | One definition; a blank or unknown id yields an empty Optional |
playerSkills(Player) | owner | Slot bindings and cast-mode snapshot; NOT_FOUND while the profile is not loaded |
level(Player, String) | owner | Stored level, clamped into [1, maxLevel] |
upgradePreview(Player, String) | owner | Quotes the next upgrade: target level, success rate, currency and material costs. A quote only — nothing is charged or locked |
hasLearned(Player, String) | owner | Tests only the manual-unlock source; it does not mean the player can use the skill |
castModeEnabled(Player) | owner | Reads the cast-mode flag |
atMaxLevel(Player, String) | owner | Whether 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 aSkillSourceRegistration. 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-disabledowner, or a blankid(), yields an inactive no-op handleSkillSourceProvider.id()is unique within the owning plugin,priority()orders evaluation (lower first, default100), andcollect(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:
| Method | Default | Description |
|---|---|---|
id() | required | Canonical 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 list | Description 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 list | Passive trigger ids this skill reacts to; ignored when active |
cooldownTicks() | 0 | Cast cooldown in ticks; values below zero are clamped to zero |
globalCooldownTicks() | 0 | Shared cooldown this skill imposes in ticks; values below zero are clamped to zero |
scriptLines() | empty map | CoreLib 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 map | Gate conditions per phase, using the same keys and syntax as scriptLines(); a phase runs only when its conditions pass |
tags() | empty list | Tags used for filtering and equip limits |
loreAliases() | empty list | Lore 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() | 0 | Display order inside a category; lower appears first |
showInSlots() | true | Whether this skill appears in skill-slot UIs |
enabled() | true | Whether 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 whatSkillCatalogreports; 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 butmythicSkill()names a MythicMobs skill, the skill is cast through MythicMobs instead - A null or already-disabled
owner, anulldefinition, a blank or throwingid(), 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