Documentation • Public addon integration
NobleSteeds Developer API
NobleSteeds 2.0.0 • Public API contract 1.0
The API ships in the same completely free server plugin. Compile against the plugin JAR; no separate API plugin or PlaceholderAPI expansion is required.
Getting started
Dependency & Service Discovery
NobleSteeds 2.0.0 ships public API contract 1.0 inside its single server JAR. Use provided/compileOnly scope; never shade NobleSteeds or copy API classes into your addon. Bukkit must load the same API classes as the registered service.
Get the NobleSteeds server JARmvn install:install-file -Dfile=NobleSteeds-2.0.0.jar -DgroupId=com.rismr1 -DartifactId=NobleSteeds -Dversion=2.0.0 -Dpackaging=jar<dependency>
<groupId>com.rismr1</groupId>
<artifactId>NobleSteeds</artifactId>
<version>2.0.0</version>
<scope>provided</scope>
</dependency>dependencies {
compileOnly(files("libs/NobleSteeds-2.0.0.jar"))
}name: ExampleMountAddon
version: 1.0.0
main: example.ExampleMountAddon
api-version: "1.18"
depend: [NobleSteeds]
# If genuinely optional: softdepend: [NobleSteeds]
# Guard missing-plugin/API class access in optional integrations.package example;
import com.rismr1.noblesteeds.api.NobleSteedsApi;
import com.rismr1.noblesteeds.api.NobleSteedsApiProvider;
import org.bukkit.plugin.java.JavaPlugin;
public final class ExampleMountAddon extends JavaPlugin {
@Override
public void onEnable() {
NobleSteedsApi api = NobleSteedsApiProvider.get().orElse(null);
if (api == null || !NobleSteedsApi.API_VERSION.equals(api.version())
|| !api.capabilities().contains("mount-queries")) {
getLogger().warning("Compatible NobleSteeds API is unavailable.");
getServer().getPluginManager().disablePlugin(this);
return;
}
getLogger().info("NobleSteeds API " + api.version() + " available.");
}
}NobleSteedsApiProvider.get() wraps Bukkit.getServicesManager().load(NobleSteedsApi.class). Discover after enable; check version/capabilities, and stop using service references after disable. Required dependencies provide load order; optional integrations need guarded class loading and lifecycle handling.
API reference
Version & Capability Contract
version() returns API contract 1.0, not plugin release 2.0.0. capabilities() returns the immutable supported names below.
| Capability | Surface |
|---|---|
| mount-queries | Mount records |
| mount-actions | Rename, call, stable, store, restore |
| gui-opening | Permission-checked menus |
| persistent-rewards | Idempotent rewards and claims |
| cached-placeholders | Memory-only placeholder values |
| mount-events | Public events |
| market-queries | Read-only listings |
| shop-queries | Read-only templates |
| payout-queries | Read-only ledger |
| stable-queries | Named presets |
| lineage-queries | Recorded ancestry |
API reference
Snapshots, Queries & Identity
| Signature | Result |
|---|---|
| mount(UUID mountId) | Optional<MountSnapshot> using persistent logical ID |
| mounts(UUID owner) | Immutable owned mount snapshots |
| pendingRewards(UUID owner) | Uncompleted pending/review rewards |
| listings() | Read-only market listings |
| shopEntries() | Read-only shop templates |
| payouts(UUID owner) | Seller payment ledger views |
| stables(UUID owner) | Named presets, not every individually assigned location |
| lineage(UUID mountId) | Optional lineage; parent fields may be null/unknown |
| placeholder(UUID player, String parameter) | Cached parameter lookup without surrounding %noblesteeds_...% |
Reads are asynchronous-safe immutable cached snapshots: no database queries, chunk loads or spawning. Cache refresh is every 20 ticks and after API operations. Separate calls are not an atomic transaction. Pass valid UUIDs and enforce your addon’s authorization before displaying global server data.
| Field / concept | Rule |
|---|---|
| mountId() | Persistent logical identity for actions and UUID placeholders |
| entityId() | Physical entity UUID; changes on restore/revive. /nsa inspect expects this saved physical UUID |
| slot() | Owner position; can change on compaction/transfers |
| type / variant | Seven type names; horse/llama colors use Bukkit enums |
| speed / jump / health | 0–100 percentages, not raw attributes or blocks per second |
| breedingQuality | Nullable when ineligible/disabled |
| Stored rewards | May have no physical entity and empty world; placeholder entity ID is not an existing entity |
| Lineage | Unknown parents can be null; no invented ancestry |
| Payout state / mailId / queueId | Ledger progress/delivery references, not attachment-claim proof |
NobleSteedsApi api = NobleSteedsApiProvider.get().orElseThrow();
var owned = api.mounts(playerUuid);
var queued = api.pendingRewards(playerUuid);
var presets = api.stables(playerUuid);
String activeCount = api.placeholder(playerUuid, "claimed");
owned.forEach(mount -> {
UUID logicalId = mount.mountId();
UUID physicalId = mount.entityId();
// Do not use Bukkit entity/chunk APIs off-thread.
});API reference
Actions, Permissions & Menus
| Method | Checks |
|---|---|
| call(Player, UUID mountId) | Owner online, noblesteeds.call, living Active unlisted, cooldown/lock |
| sendToStable(Player, UUID mountId) | Owner online, noblesteeds.stable.send, eligible state and assigned world/location |
| rename(Player, UUID mountId, String name) | noblesteeds.rename, Active claimed record (including unloaded), bounded nonblank name |
| store(Player, UUID mountId) | noblesteeds.storage.deposit, living unlisted Active mount, Stored capacity |
| restore(Player, UUID mountId) | noblesteeds.storage.withdraw, living unlisted Stored mount, Active capacity |
| openMenu(Player, NobleSteedsApi.Menu) | Online and menu permission; MARKET/SHOP usable |
| grantMount(Plugin source, RewardRequest) | Enabled source, validated persistent request; never bypass capacity |
| claimReward(Player, UUID rewardId, MountDestination) | Online recipient, noblesteeds.rewards.claim, ownership, state/capacity |
Actions return CompletableFuture<OperationResult> and do not grant administrator authority. Ownership/state/permissions are rechecked around async work and events. Command-entry configuration is not necessarily every GUI/API-entry gate; disabling a command alone is not blanket integration disablement.
| Menu | Permission |
|---|---|
| ACTIVE | noblesteeds.manage |
| STORAGE | noblesteeds.storage |
| MARKET | noblesteeds.market |
| SHOP | noblesteeds.shop |
| REWARDS | noblesteeds.rewards.claim |
api.mounts(player.getUniqueId()).stream()
.filter(mount -> mount.status().equals("ACTIVE"))
.findFirst()
.ifPresent(mount -> api.call(player, mount.mountId()));
api.openMenu(player, NobleSteedsApi.Menu.STORAGE)
.thenAccept(result -> {
// Inspect status; opening a menu is not granting a mount.
});Integration workflows
Persistent Quest Rewards & Retries
Persist one rewardKey per earned reward. RewardRequest includes the key, recipient UUID, MountTemplate, ACTIVE/STORED destination and optional sourceLabel. Keys are namespaced by source plugin name; keep that name and your earned completion identity stable across retries.
NobleSteedsApi api = NobleSteedsApiProvider.get().orElseThrow();
RewardRequest request = new RewardRequest(
"dragon-hunt:" + questCompletionId,
playerUuid,
new MountTemplate("HORSE", "BLACK", "Dragon Runner", 80, 70, 75, 60.0),
MountDestination.STORED,
"Dragon Hunt");
api.grantMount(this, request).thenAccept(result -> {
switch (result.status()) {
case SUCCESS, ALREADY_GRANTED -> markRewardAccepted(questCompletionId);
case PENDING_CAPACITY -> markRewardAccepted(questCompletionId);
case QUEUE_FULL -> retainUndeliveredQuestReward(questCompletionId);
default -> logRewardOutcome(result);
}
});The callback methods are yours, not NobleSteeds methods; this is your enabled Bukkit plugin instance. Persist earned/accepted state. Retry the exact request after timeout: changed recipient/template/destination returns REQUEST_CONFLICT. Never generate a fresh key to retry.
| Value | Constraint |
|---|---|
| rewardKey | 1–160: letters/digits/underscore/dot/colon/slash/hyphen |
| sourceLabel | Max 128; null becomes empty; blank uses source name |
| type | HORSE, DONKEY, MULE, LLAMA, TRADER_LLAMA, SKELETON_HORSE, ZOMBIE_HORSE |
| variant | Horse/llama color enum; null normalizes BROWN; other types ignore color |
| name | Nonblank visual text; max 512 raw characters / 64 visible code points |
| speed / jump / health | Finite 0–100 |
| breedingQuality | null or finite 0–100 for eligible types; null for Mules/undead |
| source | Non-null enabled plugin |
- SUCCESS / ALREADY_GRANTED record delivery. PENDING_CAPACITY is durable acceptance without a mount delivered yet.
- QUEUE_FULL, invalid input and cancelled acceptance are not acceptance. A non-null reward ID alone is not proof.
- Active grants use an online recipient’s position; offline Active grants queue. Stored delivery can work offline using default capacity.
- Pending rewards never expire, appear in both collection menus and can be claimed to Active or Stored.
- The queue cap defaults to 100. Rejected new requests remain the source addon’s responsibility.
- Completed identities remain persistent; exact grant/claim retries do not create another mount.
Integration workflows
Results, Threading & Recovery
OperationResult carries operationId, status and nullable mountId/rewardId. delivered() checks SUCCESS or ALREADY_GRANTED; meaning still depends on the action. Successful menu opening is not reward delivery, and payout queries cannot change payment state.
| Status | Handling |
|---|---|
| SUCCESS | Operation completed |
| ALREADY_GRANTED | Exact delivered reward retry; no extra grant |
| PENDING_CAPACITY | Durably queued, not yet delivered |
| LIMIT_REACHED | Action lacks destination capacity |
| QUEUE_FULL | New request rejected; retain source responsibility |
| INVALID_STATS | Invalid mount template |
| INVALID_REQUEST | Invalid source, player, request/name/state |
| REQUEST_CONFLICT | Same identity, different reward parameters |
| NOT_FOUND | Mount/location/reward absent |
| NOT_OWNER | Ownership mismatch |
| NO_PERMISSION | Permission missing |
| BUSY | Listing/operation/cooldown guard |
| DISABLED | Unavailable/shutdown |
| CANCELLED | Pre-action rejected |
| FAILED | Inspect outcome before retry |
| REVIEW_REQUIRED | Investigate uncertain delivery; do not grant again |
Off-thread actions marshal onto the server scheduler. Normal completions are on the server thread; immediate shutdown rejection can complete on the calling thread. Never block the server thread with join() or get(). If you change callback executors, schedule Bukkit work explicitly on the server thread.
Shutdown completes pending futures with DISABLED even though a request may already be durable. After restart inspect/retry its original key. Interrupted DELIVERING records reconcile against saved ownership; uncertain outcomes become REVIEW_REQUIRED rather than spawn twice.
Integration workflows
Public Events & Guarantees
| Event | Guarantees / limits |
|---|---|
| MountActionEvent | Cancellable API pre-action only, not every command/GUI action. GRANT, CLAIM, RENAME, RELOCATE, STORE, RESTORE. getMountId(), getActor(), getAction(); reward actions can identify prospective reward identity. |
| MountChangedEvent | Non-cancellable persisted MountSnapshot change; getMount(), getAction(), currently RECORD_SAVED. Not transactional sale/payment/attachment claiming. |
| MountOperationCompletedEvent | Non-cancellable server-thread successful API GRANT, CLAIM, RENAME, CALL, STABLE, STORE, RESTORE, OPEN_MENU. getAction()/getResult(). No queued, rejected or ALREADY_GRANTED notification. |
@EventHandler(ignoreCancelled = true)
public void beforeApiMove(MountActionEvent event) {
if (event.getAction() == MountActionEvent.Action.RELOCATE
&& shouldBlockMove(event.getActor(), event.getMountId())) {
event.setCancelled(true);
}
}
@EventHandler
public void afterApiOperation(MountOperationCompletedEvent event) {
OperationResult result = event.getResult();
// Observe success; retain durable source reward-key tracking.
}Register the Bukkit listener and supply your own shouldBlockMove implementation. Pre-events must not perform irreversible work assuming success. Reentrant acceptance and ownership/capacity rechecks still apply. Completion events do not replace future results or durable reward bookkeeping.
Reference & support
Placeholder Integration
Bundled PlaceholderAPI delegates to api.placeholder(ownerUuid, parameter). Direct API parameters are claimed, stored, mount_1_name etc., without %noblesteeds_ prefix/suffix. See the wiki for every external placeholder and player-context rule.
Complete PlaceholderAPI setup and listCounts without known context are 0; unknown limits/missing mounts/ineligible quality are empty; unknown keys return null. Reads use immutable cached data. Offline owners retain cached permission limits where available, otherwise defaults. Trusted configured-text parsing is opt-in and never executes player names/descriptions as external placeholders.
Reference & support
Complete Public Contract Reference
These 12 top-level public types and their nested records/enums are copied from 2.0.0 source. Internal service/storage/payout managers are excluded. Compile against the obtained plugin JAR; do not copy these classes into your addon.
ApiViews • Public API
package com.rismr1.noblesteeds.api;
import java.util.UUID;
public final class ApiViews {
private ApiViews() { }
public record Listing(UUID listingId, UUID mountId, UUID seller, String name, double money, String itemPrice, String description) { }
public record ShopEntry(UUID entryId, MountTemplate mount, double money, String itemPrice, boolean enabled) { }
public record Payout(UUID transactionId, UUID seller, String state, double money, String itemPrice, long createdAt, String mailId, String queueId) { }
public record Stable(String name, String world, double x, double y, double z) { }
public record Parent(UUID mountId, String name, Double breedingQuality) { }
public record Lineage(Parent mother, Parent father) { }
}
MountDestination • Public API
package com.rismr1.noblesteeds.api;
public enum MountDestination { ACTIVE, STORED }
MountSnapshot • Public API
package com.rismr1.noblesteeds.api;
import java.util.List;
import java.util.UUID;
public record MountSnapshot(UUID mountId, UUID entityId, UUID ownerId, int slot, String name,
String type, String variant, String status, String rarity,
double speed, double jump, double health, Double breedingQuality,
String world, double x, double y, double z, List<String> history) {
public MountSnapshot { history = List.copyOf(history); }
}
MountTemplate • Public API
package com.rismr1.noblesteeds.api;
import java.util.Objects;
public record MountTemplate(String type, String variant, String name, double speed,
double jump, double health, Double breedingQuality) {
public MountTemplate {
Objects.requireNonNull(type); Objects.requireNonNull(name);
type = type.toUpperCase(java.util.Locale.ROOT);
variant = variant == null ? "BROWN" : variant.toUpperCase(java.util.Locale.ROOT);
}
}
NobleSteedsApi • Public API
package com.rismr1.noblesteeds.api;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
public interface NobleSteedsApi {
String API_VERSION = "1.0";
enum Menu { ACTIVE, STORAGE, MARKET, SHOP, REWARDS }
String version();
Set<String> capabilities();
Optional<MountSnapshot> mount(UUID mountId);
List<MountSnapshot> mounts(UUID owner);
List<PendingMountReward> pendingRewards(UUID owner);
List<ApiViews.Listing> listings();
List<ApiViews.ShopEntry> shopEntries();
List<ApiViews.Payout> payouts(UUID owner);
List<ApiViews.Stable> stables(UUID owner);
Optional<ApiViews.Lineage> lineage(UUID mountId);
CompletableFuture<OperationResult> grantMount(Plugin source, RewardRequest request);
CompletableFuture<OperationResult> claimReward(Player player, UUID rewardId, MountDestination destination);
CompletableFuture<OperationResult> rename(Player player, UUID mountId, String name);
CompletableFuture<OperationResult> call(Player player, UUID mountId);
CompletableFuture<OperationResult> sendToStable(Player player, UUID mountId);
CompletableFuture<OperationResult> store(Player player, UUID mountId);
CompletableFuture<OperationResult> restore(Player player, UUID mountId);
CompletableFuture<OperationResult> openMenu(Player player, Menu menu);
String placeholder(UUID player, String parameter);
}
NobleSteedsApiProvider • Public API
package com.rismr1.noblesteeds.api;
import org.bukkit.Bukkit;
import java.util.Optional;
public final class NobleSteedsApiProvider {
private NobleSteedsApiProvider() { }
public static Optional<NobleSteedsApi> get() {
return Optional.ofNullable(Bukkit.getServicesManager().load(NobleSteedsApi.class));
}
}
OperationResult • Public API
package com.rismr1.noblesteeds.api;
import java.util.UUID;
public record OperationResult(UUID operationId, Status status, UUID mountId, UUID rewardId) {
public enum Status {
SUCCESS, ALREADY_GRANTED, PENDING_CAPACITY, LIMIT_REACHED, QUEUE_FULL,
INVALID_STATS, INVALID_REQUEST, REQUEST_CONFLICT, NOT_FOUND, NOT_OWNER,
NO_PERMISSION, BUSY, DISABLED, CANCELLED, FAILED, REVIEW_REQUIRED
}
public boolean delivered() { return status == Status.SUCCESS || status == Status.ALREADY_GRANTED; }
}
PendingMountReward • Public API
package com.rismr1.noblesteeds.api;
import java.util.UUID;
public record PendingMountReward(UUID rewardId, UUID recipient, String source, MountTemplate mount,
MountDestination requestedDestination, String state, long createdAt) { }
RewardRequest • Public API
package com.rismr1.noblesteeds.api;
import java.util.Objects;
import java.util.UUID;
public record RewardRequest(String rewardKey, UUID recipient, MountTemplate mount,
MountDestination destination, String sourceLabel) {
public RewardRequest {
Objects.requireNonNull(rewardKey); Objects.requireNonNull(recipient);
Objects.requireNonNull(mount); Objects.requireNonNull(destination);
sourceLabel = sourceLabel == null ? "" : sourceLabel;
}
}
MountActionEvent • Events
package com.rismr1.noblesteeds.api.event;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import java.util.UUID;
public final class MountActionEvent extends Event implements Cancellable {
public enum Action { GRANT, CLAIM, RENAME, RELOCATE, STORE, RESTORE }
private static final HandlerList HANDLERS = new HandlerList();
private final UUID mountId;
private final UUID actor;
private final Action action;
private boolean cancelled;
public MountActionEvent(UUID mountId, UUID actor, Action action) {
this.mountId = mountId; this.actor = actor; this.action = action;
}
public UUID getMountId() { return mountId; }
public UUID getActor() { return actor; }
public Action getAction() { return action; }
public boolean isCancelled() { return cancelled; }
public void setCancelled(boolean cancelled) { this.cancelled = cancelled; }
public HandlerList getHandlers() { return HANDLERS; }
public static HandlerList getHandlerList() { return HANDLERS; }
}
MountChangedEvent • Events
package com.rismr1.noblesteeds.api.event;
import com.rismr1.noblesteeds.api.MountSnapshot;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
public final class MountChangedEvent extends Event {
private static final HandlerList HANDLERS = new HandlerList();
private final MountSnapshot mount;
private final String action;
public MountChangedEvent(MountSnapshot mount, String action) { this.mount = mount; this.action = action; }
public MountSnapshot getMount() { return mount; }
public String getAction() { return action; }
public HandlerList getHandlers() { return HANDLERS; }
public static HandlerList getHandlerList() { return HANDLERS; }
}
MountOperationCompletedEvent • Events
package com.rismr1.noblesteeds.api.event;
import com.rismr1.noblesteeds.api.OperationResult;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
public final class MountOperationCompletedEvent extends Event {
public enum Action { GRANT, CLAIM, RENAME, CALL, STABLE, STORE, RESTORE, OPEN_MENU }
private static final HandlerList HANDLERS = new HandlerList();
private final Action action;
private final OperationResult result;
public MountOperationCompletedEvent(Action action, OperationResult result) { this.action = action; this.result = result; }
public Action getAction() { return action; }
public OperationResult getResult() { return result; }
public HandlerList getHandlers() { return HANDLERS; }
public static HandlerList getHandlerList() { return HANDLERS; }
}
Reference & support
Integration Checklist & Support
- Compile against 2.0.0 and discover API 1.0 with required capabilities.
- Test missing-plugin, enable/disable and stale-reference handling.
- Exercise off-thread work without blocking; test permission/ownership, busy/listed/dead/stored state and capacity.
- Persist earned identity and retry exactly; cover ALREADY_GRANTED, PENDING_CAPACITY, QUEUE_FULL and REQUEST_CONFLICT.
- Test cancellation, interrupted delivery and review without new identities or duplicate mounts.
- Verify logical identity survives restore/revive while entity UUIDs/slots change.
- Treat global market/shop/payout snapshots as read-only information.
Use the official support guide with a minimal reproduction and sanitized logs. Never publish credentials or private data.
ImagineCraft Plugin SupportBack to NobleSteeds 2.0.0