Astral Realms Documentation Help

Skins

A skin is a cosmetic overlay for a custom item: it swaps the item's minecraft:item_model (and, optionally, its minecraft:equippable armour asset) for another one, without changing the item's blueprint, stats, or enchantments. The only item data it touches is its own skin entry. Skins are physical items themselves — players hold a skin item and apply it to a compatible custom item.

Skins are defined in plugins/AstralItems/skins/**.yml and loaded by SkinService.

Defining a skin

One file per skin; the folder is walked recursively, so sub-folders are scanned. SkinService.load() clears the index and re-reads the whole folder. A second file declaring an id that is already indexed is dropped with a Duplicate skin blueprint ID found: <id>. Skipping. warning — which of the two files survives is not deterministic, so keep ids unique.

id: "example" model-key: "skins:example" item: material: "DIAMOND_SWORD" name: "<gray>Example Skin" lore: - "<gray>This is an example skin." target: - "astralien:hoe"

Field

Type

Description

id

String

Required. Lookup id, and the value written into the skin item's PDC.

model-key

Key (namespace:value)

Required. Set as the target item's minecraft:item_model data component when the skin is applied.

armor-asset-key

Key

Optional. When set, applying the skin rebuilds the item's minecraft:equippable component with this assetId.

item

ItemStackWrapper

Required. The physical skin item — a standard AstralCore item-stack block (material, name, lore, …).

target

Set<Key>

Required. Blueprints the skin may be applied to, as the blueprint's composite key <rarity-id>:<blueprint-id>.

The skin item

SkinService.build(blueprint) renders the item: block and stamps the PDC string key astralitems:skin with the skin id. SkinService.fromItemStack(stack) reads that key back, which is how every code path recognises a stack as a skin item.

Skin items are also reachable through the items.skins item-stack supplier, whose key is the bare skin id. Reference one as %stacksuppliers_items.skins_<skin-id>% wherever an ItemStack placeholder is accepted, or as items.skins-<skin-id> in a node deserialized as an ItemStack (see Developer API):

actions: - "[give-item] %stacksuppliers_items.skins_example%"

Applying and removing in an inventory

SkinListener handles both directions on InventoryClickEvent at HIGH priority (ignoreCancelled).

Apply — swap a skin item onto the item

Hold the skin item on the cursor and click the custom item in a slot (SWAP_WITH_CURSOR). The listener cancels the click and then:

  1. Plays block.anvil.land (0.5 / 1.0) and stops if the item's blueprint key is not in the skin's target.

  2. If the item already carries a skin: the same skin id plays block.anvil.land and stops; a different skin id returns the old skin item to the player through the AstralMailbox giveOrAdd path. If that old id no longer resolves to a loaded skin, the listener plays block.anvil.land and stops without applying.

  3. Applies the skin and persists it with ItemService.updateItemStack.

  4. Decrements the cursor stack by one (clearing it when it was the last one) and plays block.anvil.use.

Remove — shift-right-click with an empty cursor

Shift-right-click the skinned item so it would move to the other inventory (MOVE_TO_OTHER_INVENTORY + SHIFT_RIGHT) while the cursor is empty. The listener cancels the click, unapplies the skin, persists the item, and puts the recovered skin item on the cursor. Same feedback sounds as above.

Not in the anvil

Skins are not applied in the custom anvil. That branch existed once and was removed: it skipped the target check and never called updateItemStack, so the result stack wore the visual components while the astralitems:skin data was discarded. A skin item in the anvil's right slot is now rejected like any other non-book sacrifice.

Commands

Unlike the inventory flow, neither command hands a skin item back to the player.

What is stored on the item

Applying a skin writes a SkinItemData entry under astralitems:skin holding the skin id and its model key. Removing the skin resets the item model — and, when the blueprint's display itself declares an equippable, the equippable asset — to the blueprint's display values, and deletes that entry.

Surviving a rebuild

ItemFactory.make rebuilds the stack from the blueprint's display on every version upgrade, evolution and /items update, so anything not restored from the instance is lost. It therefore hands the stored SkinItemData to SkinService.reapply, which repaints both the ITEM_MODEL and the EQUIPPABLE asset id — the item model alone would leave worn armour showing the blueprint's own texture.

reapply re-resolves the skin by id, so a skin whose config changed repaints with its current model-key/armor-asset-key. The model key persisted on the item is only the fallback, used when the skin id no longer resolves to a loaded skin (in which case no armour asset is applied). Items with no skin data, or with an empty one, are left alone.

Both reapply and unapply write the visuals through one path: a null model key unsets ITEM_MODEL rather than leaving the previous one, and the armour asset is only touched on stacks that actually carry an EQUIPPABLE component. unapply clears the asset id even when the blueprint declares no equippable of its own, so removing an armour skin cannot leave the skin's texture behind.

Orphan skins

An orphan skin is an item that visually wears a skin but carries no SkinItemData — what the removed anvil branch produced. Such an item could not be un-skinned (removal bails out on missing data) and would have lost its skin at the next rebuild.

ItemService.updateItemStack repairs them: before rebuilding, an instance with no astralitems:skin entry has its current ITEM_MODEL looked up against every loaded skin's model-key. A match that is not the blueprint's own default model is adopted — the SkinItemData is written back — which both preserves the skin through the rebuild and makes it removable again. SkinService.findByModelKey/findOrphanSkin expose the same lookup.

Placeholders

%items_skins% iterates every loaded skin. Each skin uses the skin namespace — see Placeholders › Skin.

See also

Last modified: 03 September 2026