English
API and Integration
Third-party plugins reach EmakiAccessory through emaki-accessory-api; the dependency coordinates, facade, and query-layer contract follow
Maven Coordinates
xml
<dependency>
<groupId>emaki.jiuwu.craft</groupId>
<artifactId>emaki-accessory-api</artifactId>
<version>1.1.7</version>
<scope>provided</scope>
</dependency>Never put emaki-accessory-api-*.jar into the server plugins/ folder; it is a compile-time artifact only
Declare EmakiAccessory as a dependency in your paper-plugin.yml:
yaml
dependencies:
server:
EmakiAccessory:
load: BEFORE
required: false
join-classpath: trueFacade
emaki.jiuwu.craft.accessory.api.EmakiAccessoryApi is the static facade:
| Method | Description |
|---|---|
status() | Availability and identity metadata, returning CoreLib's ApiStatus |
catalog() | Query layer, see AccessoryCatalog below |
install(Bridge) / uninstall(Bridge) | For EmakiAccessory's own lifecycle only |
Accessors never return null. While EmakiAccessory is absent, catalog() returns empty answers, so callers must not treat a NullPointerException as an availability signal
Bridge is annotated @ApiStatus.NonExtendable; third-party plugins must not implement it
No Operation or Extension Layer
This is deliberate. Accessories grant attributes and skills by pushing contributions into EmakiAttribute and EmakiSkills through their existing owner-scoped extension points, so there is no accessory-specific combat or skill pipeline for a third party to hook. The module therefore exposes only a query layer
To influence the attributes or skills accessories provide, use the corresponding extension point in EmakiAttribute or EmakiSkills; do not ask EmakiAccessory for an interface
Do Not Cache the Layer
Resolve catalog() at the point of use and never store it in a field: the backing bridge is replaced across a reload
java
// Correct
EmakiAccessoryApi.catalog().parts();
// Wrong: this reference points at the old bridge after a reload
private final AccessoryCatalog cached = EmakiAccessoryApi.catalog();AccessoryCatalog — Query Layer
Annotated @ApiStatus.NonExtendable
| Method | Thread | Description |
|---|---|---|
List<AccessoryPartView> parts() | Any thread | List every part definition |
Optional<AccessoryPartView> part(String partId) | Any thread | Look up a part by id |
List<String> slotInstanceIds() | Any thread | List every slot instance id |
List<String> pageIds() | Any thread | List every accessory page id, in page order. Since 1.0.3 |
String enabledPage(UUID playerId) | That player's owner thread | The enabled page id; an empty string when unknown or currently not usable. Since 1.0.3 |
Map<String, EquippedAccessoryView> equipped(UUID playerId) | That player's owner thread | What the player has equipped on the enabled page, keyed by slot instance id |
Map<String, EquippedAccessoryView> equippedOnPage(UUID playerId, String pageId) | That player's owner thread | What the player stores on one specific page. Since 1.0.3 |
int equippedSetPieces(UUID playerId, String setId) | That player's owner thread | How many pieces of one set the player has equipped, counting the enabled page only; 0 when unknown |
Part, slot, and page id queries read configuration facts and may be called from any thread
Player queries read the in-memory session cache and must be called on that player's owner thread. They return an empty result rather than blocking when the player's data has not finished loading, so an empty result must not be read as "this player has no accessories equipped"
Five things to keep in mind about multi-page semantics:
equipped()reports the enabled page only. UseequippedOnPage()to inspect any other page, andenabledPage()to learn which page is activeequipped()has been narrowed (see the next section): it includes orphaned slots so callers can see items pending retrieval; filter them withEquippedAccessoryView#orphaned()when not wanted. Orphaned slots never contribute to set piece counts- When the player lacks the enabled page's permission,
enabledPage()returns an empty string andequipped()returns an empty map, while the stored items stay untouched and remain retrievable - A
nullplayerIdalways yields an empty result (equippedSetPiecesreturns0) equippedSetPieces()reads the piece counts of the most recent report; it returns0for a player who has never produced one, or right after a reload (which resets the cache)
These methods are pure queries with no readiness gate. They read stale data during a reload; callers that need to invalidate their own cache after a reload use CoreLib's resident readiness listener:
java
EmakiCoreLibApi.addModuleListener(myPlugin, "EmakiAccessory", phase -> {
if (phase == ModuleReadinessPhase.READY) {
rebuildMyCache();
}
});Model Types
The emaki.jiuwu.craft.accessory.api.model package:
AccessoryPartView
java
public record AccessoryPartView(String partId, int count, String displayName, List<String> slotInstanceIds)| Member | Description |
|---|---|
partId() | Part id |
count() | Number of expanded slots, at least 1 |
displayName() | MiniMessage display name; may be an empty string |
slotInstanceIds() | Expanded slot instance ids in index order, each <partId>_<index> with the index starting at 1. The list is immutable |
EquippedAccessoryView
java
public record EquippedAccessoryView(String pageId, String slotInstanceId, String partId, ItemStack item, boolean orphaned)| Member | Description |
|---|---|
pageId() | Id of the page holding this accessory. Since 1.0.3, and the first component of the record |
slotInstanceId() | Slot instance id |
partId() | Owning part id, or the raw prefix when the part no longer exists (may be an empty string) |
item() | A defensive copy of the stored item. The query layer never produces entries for empty slots, so views obtained from it carry a non-null item; the record itself permits null (and then returns null) |
orphaned() | Whether the slot is no longer declared by its owning page |
item() returns a fresh clone on every call, so callers cannot mutate stored state through the view
Version Compatibility and Breaking Changes
The multi-page model (@since 1.0.3) introduced two changes a developer has to act on:
| Change | Compatibility | Migration |
|---|---|---|
AccessoryCatalog#equipped(UUID) narrowed: returned entries changed from "every slot the player holds" to "the enabled page only", returning an empty map with no permission or no enabled page | Signature unchanged (source and binary compatible), but the semantics are incompatible | Use equippedOnPage(playerId, pageId) to inspect other pages, and enabledPage(playerId) to learn which page is active |
EquippedAccessoryView gained the leading component pageId, moving the record from 4 to 5 components | Source and binary incompatible: the canonical constructor arity changed from 4 to 5, so direct new call sites fail to compile and already-compiled old call sites throw NoSuchMethodError at runtime | Upgrade the dependency and recompile, then supply the 5-argument constructor with pageId first |
pageIds(), enabledPage() and equippedOnPage() are the query methods added in the same batch; the interface is annotated @ApiStatus.NonExtendable, so third parties should not implement it and the added methods do not affect callers