API
What another plugin can do with shield designs: read what somebody wears, draw one onto an item, and read the shared library.
ShieldsService reads a player's slots, draws designs onto shield items, and reaches the shared
library. One lookup gets you the whole surface.
ExyliaAPI.get(ShieldsService.class).ifPresent(shields ->
shields.activeDesign(player.getUniqueId())
.ifPresent(design -> player.sendMessage("Layers: " + design.layers().size())));An empty result from the lookup means shield designs are not part of this server, rather than that something failed.
The artifact, the repository and the plugin.yml line are the same for every Exylia plugin and live
on the public API page.
Patterns and colours are ids, not enums
A layer is a vanilla banner pattern drawn in a dye colour, and both halves are the ids the server's own
configuration uses — plain strings, not Bukkit enums. There is no closed set to switch over:
availablePatterns() and availableColors() are what this server offers, the owner curates them,
and permissions are built from the same ids (exyliashields.pattern.<pattern> and
exyliashields.color.<color>).
That is deliberate. A layer naming something the running server does not know is still a line of somebody's design rather than an error: unknown layers are dropped when the shield is drawn, not when it is read.
A player's slots
Slots are held in memory while a player is online and written back when they leave. Everything here
that takes a UUID reads that memory: a player who is offline, or whose row has not arrived yet,
answers as though they own nothing rather than blocking the caller on a database.
Slots are numbered from zero. The menus show them one higher, because players count from one and this contract does not.
| Method | What it does |
|---|---|
Optional<ShieldDesign> activeDesign(UUID player) | The design they are wearing. Empty when the active slot is empty or they are not loaded. |
int activeSlot(UUID player) | The slot they are wearing, counted from zero. Zero for a player who is not loaded too, since zero is also where everybody starts — ask activeDesign to tell the two apart. |
Optional<ShieldDesign> designInSlot(UUID player, int slot) | The design in one slot. Empty when the slot is empty, out of range, or the player is not loaded. |
int slotCount(UUID player) | How many slots they have designs in — the size of their stored list, gaps from deleted designs included. 0 when they are not loaded. |
int maxSlots(Player player) | How many slots their permissions allow, counted down from the highest exyliashields.max_slots.<n> they hold. A player holding none may use no slots at all. |
What the server offers
| Method | What it does |
|---|---|
List<String> availablePatterns() | The pattern ids this server lets players draw with, in menu order. A curated list rather than every vanilla pattern. |
List<String> availableColors() | The dye colour ids this server lets players draw with, in menu order. |
int maxLayers() | How many layers one design may hold. |
boolean mayUsePattern(Player player, String patternId) | Whether a player may draw with a pattern. |
boolean mayUseColor(Player player, String colorId) | Whether a player may draw with a colour. |
Permissions are checked again whenever a player joins, and layers they have lost the right to are
dropped from what they built. A design read out of a slot has therefore already been filtered — the two
mayUse checks are for callers building one.
Drawing on shields
| Method | What it does |
|---|---|
void selectSlot(Player player, int slot) | Puts a player on one of their slots and redraws the shield they hold. The same thing clicking the slot in the menu does, message included. |
void applyActiveDesign(Player player) | Draws whatever they are wearing onto the shield in their hands. Off hand first, then main hand; a player holding no shield is left alone. |
void applyDesign(ItemStack shield, ShieldDesign design) | Draws a design onto a shield item, in place. Anything that is not a shield is left alone. |
void stripDesign(ItemStack shield) | Takes every pattern off a shield item, leaving it plain. |
applyActiveDesign is what to call after handing a player a shield — a kit, a crate, an arena
loadout. An item that arrived from somewhere else carries no design until something draws one on it.
Layers naming a pattern or colour this server does not have are skipped rather than refused: one unknown line is not a reason to draw a blank shield.
Drawing on a shield a player holds writes to their inventory. Call it from the player's own thread — on Folia, their region's.
The shared library
The library is a table, not a cache, so the two methods that read it hand back a CompletableFuture.
It completes off the main thread — anything touching the world from there has to be scheduled back.
| Method | What it does |
|---|---|
void importDesign(Player player, long libraryId) | Copies a published design into the player's first free slot. A player with no free slot is told so and nothing is copied. |
CompletableFuture<Optional<PublishedDesign>> publishedDesign(long libraryId) | One design from the library. Empty when the library holds no such row, or holds one nothing can be read from any more. |
CompletableFuture<List<PublishedDesign>> mostUsedDesigns(int limit) | The most copied designs, highest use count first. What the in-game browser lists. |
An imported copy stays attached to the row it came from until the player edits it, which is what makes the use count mean anything. A copy is counted once per player, so taking the same design twice does not make it look twice as popular.
The types
ShieldDesign carries libraryId(), baseColor() and layers(): a base dye colour with layers drawn
over it, bottom first. libraryId() is 0 when the design is nobody's published design; otherwise it
is the row importDesign takes, and it survives edits because it identifies the row rather than this
particular arrangement of layers. Build one yourself and hand it to applyDesign; the constructor
copies the layer list, so a caller that keeps editing the list it passed is not editing a design
somebody is already drawing.
ShieldLayer is a pattern() and a color() — stripe_top and RED, in the server's own ids.
PublishedDesign carries id(), owner(), ownerName(), uses() and design(). ownerName() is
kept as text rather than looked up, because the player may have changed their name since, and a
browser showing the name under which the design became popular is the honest one.
A design is a snapshot, not a live view. Every edit inside the plugin produces a new one, so what you hold is the arrangement that was in the slot when you asked — read it again rather than keeping it across ticks.
What it does not expose
Editing is left out on purpose. Slots are written by the in-game editor, and publishing, renaming and deleting a library row belong to the player who owns it — a public contract cannot be broken later, so it holds what an integration genuinely needs and not the flows that only make sense inside the menus.
If something you need is missing, ask on Discord.
Something missing on this page? Tell us on Discord