Skip to content

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: true

Facade ​

emaki.jiuwu.craft.accessory.api.EmakiAccessoryApi is the static facade:

MethodDescription
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

MethodThreadDescription
List<AccessoryPartView> parts()Any threadList every part definition
Optional<AccessoryPartView> part(String partId)Any threadLook up a part by id
List<String> slotInstanceIds()Any threadList every slot instance id
List<String> pageIds()Any threadList every accessory page id, in page order. Since 1.0.3
String enabledPage(UUID playerId)That player's owner threadThe 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 threadWhat 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 threadWhat the player stores on one specific page. Since 1.0.3
int equippedSetPieces(UUID playerId, String setId)That player's owner threadHow 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. Use equippedOnPage() to inspect any other page, and enabledPage() to learn which page is active
  • equipped() has been narrowed (see the next section): it includes orphaned slots so callers can see items pending retrieval; filter them with EquippedAccessoryView#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 and equipped() returns an empty map, while the stored items stay untouched and remain retrievable
  • A null playerId always yields an empty result (equippedSetPieces returns 0)
  • equippedSetPieces() reads the piece counts of the most recent report; it returns 0 for 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)
MemberDescription
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)
MemberDescription
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:

ChangeCompatibilityMigration
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 pageSignature unchanged (source and binary compatible), but the semantics are incompatibleUse 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 componentsSource 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 runtimeUpgrade 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