Authoring pieces
How a piece, a skin or a cloth is made: in Blockbench, or by hand. Pulled from the mod repository at build time.
A piece is two files and a PNG, plus a line in your language file for the name. None of it is code. Namespace them however you like; every namespace is scanned. Skins — the armor’s own texture rather than something worn on it — are authored the same way and are two PNGs and a file.
There are two ways to make them:
- In Blockbench with the mod’s plugin, which opens a piece on the vanilla player wearing real armor, paints the textures in place, previews any trim material, and writes every file on Save.
- By hand, writing the three files yourself. This section is also the reference for what the plugin writes.
Either way, giving a piece behaviour is one more field in the same file.
In Blockbench
tools/blockbench_plugin/armorpieces.js turns Blockbench into an editor for parts. A piece opens
on the vanilla player wearing real armor, its master, static layer and fitting masks are painted
in place, any trim material can be previewed live with the fittings filled or empty, and Save
puts everything back where it came from. The plugin has no geometry or colour maths of its own: it
drives the repo’s Python tools, so the editor and the command line cannot disagree.
Your own pack. The repository is the toolkit, not the workspace: the plugin needs a clone of
it, because the rigs, the preview and the game-asset extraction are its Python, but your content
never has to live inside it. A player’s part is two folders — a resource pack under
.minecraft/resourcepacks/<name> for the model, textures and names, and a datapack under
.minecraft/saves/<world>/datapacks/<name> for the part file, its recipe and any fittings — and
the plugin knows both. Tools › Armor Pieces › New Pack… makes either kind, with a pack.mcmeta
at the format the game this mod is built for wants — and, beside the game’s own pack section, an
"armorpieces": {"requires": "0.4.0"} section the game ignores and everything else reads: it is how
a pack says which mod version it needs, so the library can label it, the editor can grey it out on
an older toolkit and pack_manifest.py can report it. Every pack is additive (it may only define
ids nothing else defines; removing content is a server setting, never a pack), and a pack that only
adds still has to say what it adds to. Write the line yourself in a pack made by hand;
export_pack.py notes a pack that has none — and adds it to your list; Packs… is that
list, any folder holding data/ or assets/, added with a folder picker. Pieces are looked for
in your list, then the repository’s own places — src/main/resources, then every pack under
packs/, then its run/ resource packs and worlds’ datapacks — then the game’s resourcepacks/
and every world’s datapacks/, and the two halves of a namespace:name are paired wherever they sit, so the piece
list shows mypack:visor (.minecraft/saves/Home/datapacks/mine + .minecraft/resourcepacks/mine)
and every write goes to the right half. Export Pack… zips a folder for handing round, contents
at the root the way the game wants them. Three settings: Armor Pieces repository (the clone),
Armor Pieces Python (the interpreter, python by default) and Armor Pieces packs (your list,
edited through Packs…). Only the mod’s own namespace has masters in tools/decoration_masters;
a pack’s circlet is its own file.
This repository’s own packs — packs/<name>/{datapack,resourcepack}, where the pack line, the
set packs and the legacy restore pack keep their SOURCE — are found without being added to your
list, the same way src/main/resources is: they ship with the clone, so a clone has them. They are
searched before run/, because run/ holds what export_pack.py copied there for the game to
load and packs/ holds the source those copies were made from; the first root holding both halves
of a piece is the one every write goes to, and that must be the source.
Install. Blockbench 5.1 or later, and Python 3 with Pillow on PATH (the same requirement
as every tool in the repository). In Blockbench: File › Plugins › Load Plugin from File, and pick
the file. It finds the repository from its own location; if the file was copied elsewhere, set
Armor Pieces repository in Settings to the repo root. The first piece opened extracts the
vanilla textures the rig needs from the game jar.
Open a piece. The plugin adds an Armor Pieces section to Blockbench’s start screen —
New piece…, Open piece…, Packs…, Open skin… — and the same verbs under Tools › Armor
Pieces. Open Armor Piece… lists every part in your packs, in src/main/resources and under
run/, and in the game’s own folders, as cards down the body with a search box, and opens one as
its own tab. New Armor Piece… asks for a name, where it goes — a pack of yours on the site, a
pack here, or + New pack… — and its socket; the namespace follows the pack until typed. It
writes the datapack entry, a starter model, a blank texture and the language line into that
pack (one folder holds both halves), then opens it. A piece tab shows only what a part needs: Edit
and Paint modes, the outliner, transform, the UV editor, colour and palette, and one
Armor Piece panel with every control:
| Control | What it does |
|---|---|
| Piece, Anchor | Which part is open and which socket it is rigged on. Changing the anchor rebuilds the rig, which reloads the model from disk, so save modelling first; what Part… changed carries across. |
| New…, Part…, Save, Rebuild | Create a piece; edit its name, anchors and fittings; write everything back to the pack; reload the open one from disk. A line under the buttons says what the part is: name, anchors, fittings. |
| Editing: Master / Static / Mask | Which sheet the brush paints. The master is greyscale by definition — the palette turns grey and any colour painted on it folds to its value under the brush. The static layer keeps real colour and is created the first time it is selected. |
| Fitting | With Mask selected: which of the part’s masked fittings the brush paints. A mask is greyscale like the master — its values are the fitting’s shading — and is created blank the first time it is selected. |
| Material preview | Shows the part through a trim material’s palette. Strokes still land on the sheet being edited, the UV editor keeps showing the greyscale, and the preview follows the brush. |
| one row per fitting | With the preview on: what each fitting holds — a gem, a metal, a dye, a banner, or empty. The preview bakes the masks exactly as the game does, in the part’s order. A banner fitting is shown as a flat fill of the banner’s base colour over its bone, standing in for the pattern layers the game draws there. |
| Pose, Phase | The walk or sprint cycle, frozen at any point, without leaving Edit or Paint mode. |
| Show player, Show armor, Outliner: part only | Hide the reference figure, the armor layers, or everything but the part in the outliner. |
| Recipe centre, Recipe ring | The template recipe: one item in the middle of a ring of four, paper unless there is a reason. Item ids autocomplete from the game’s own list. Written on Save as data/<ns>/recipe/template_<part>.json. |
| Craftable | Whether that recipe works. Off, Save writes the same file with its type swapped to armorpieces:disabled — it loads, matches nothing and is absent from the recipe book — and the two items kept, so switching it back on is the reverse swap. The summary line says not craftable while it is off. For a part that is found rather than made. |
The part itself. Part… opens the datapack half as a dialog: the name a player reads,
the sockets the part may occupy, ticked by armor piece, and its fittings as an ordered list
picked from every fitting definition in the pack and the mod — each shown with its kind, a
mask to paint or a bone to draw on. The panel follows at once: the Anchor list, the Fitting
list, the preview rows and the summary line. New… beside that list defines a fitting the
pack does not have yet: a material fitting over a trim-material tag or over ticked materials,
which are written as a tag in the pack so another pack can add to it; a dye fitting; or a
banner fitting on one of the part’s own bones, with its pattern sheet and front face. The
definition, its language line and any tag are written the moment the dialog confirms, and
the fitting joins the part’s list. The same dialog takes the “Ingredients:” words for the
fitting’s template and the two items of its recipe, and writes
recipe/fitting_template_<name>.json beside the definition when a centre item is given. Effects are rows on the same dialog: pick a built-in
type and its fields appear, with the ranges, defaults and descriptions read out of the Java
that defines it, and attribute, mob-effect and damage-tag ids autocompleting from the game.
Any row can be switched from whenever the piece is worn to only while a fitting holds a
chosen material or dye, or only while the wearer matches an entity predicate typed as JSON —
armorpieces:if_fitting and armorpieces:if_wearer written for you. A number that may vary by
material takes a per material… button, which adds a row of material and value under it. An
effect the dialog cannot show — a type from another mod — is kept as it is and shown read-only. Loot is rows
of table, weight and chance, the table id autocompleting from the game’s own list with the
chests first; the summary line says where the part is found. An empty list is no field.
Modelling. Model inside the part group; everything else is the locked reference. The
mod’s format is box UV only, so the plugin keeps it that way: a cube added, converted or
resized is laid out in free space on the texture, the paint on its faces moves with them, and
the texture grows when it is full. All of that lands in the same undo step as the edit. Cubes
do not rotate — the mod’s cube has no rotation — so a tilt is a rotated bone. A cube’s size
may be fractional; its unwrap is always whole texels, rounded up, in the plugin, the checks and
the game alike, so a 2.1 × 11.4 × 0.9 cube paints as 3 × 12 × 1 and no two faces share a texel.
Saving. Save exports the geometry through bb_geo.py, writes the master, static layer and
masks back to their files, writes the template recipe, and — for a part whose master lives in
tools/decoration_masters/ — runs sync_decoration_masters.py, which installs the sheets and
checks them against the geometry. When Part… changed something it also writes the data file
and the language line, and nothing else: a field the dialog has no control for, an effect say,
is written back exactly as it was read, a file nothing changed is not touched, and the recipe
keeps a group or any other field the two item choices do not decide — including, when it is
switched off, the pattern and items the disabled file still holds.
tools/check_authoring.py runs the round trip over every shipped part, recipes included; given
a pack folder it checks that pack, and given two — check_authoring.py <datapack> <resourcepack> —
a piece split over both.
A skin, not a part. Open Armor Skin… opens the other kind of thing this plugin edits — see
Skins — as a workspace of its own: the same figure, but the armor is unlocked and painted
by the skin’s two greyscale sheets, and nothing is modelled, because a skin is the armor. It gets
an Armor Skin panel with the same shape as the piece one, and the palette becomes the sixteen
levels a skin is written in — 0–f, seventeen apart — so a colour picked off it is a value the
sheets really use.
| Control | What it does |
|---|---|
| Skin | Which skin is open. The list is every master pair under tools/skin_masters/, labelled with the pack half it belongs to, or masters only when it has none yet. |
| New…, Skin…, Save, Rebuild | Start a skin — its master pair, blank or seeded on a vanilla material’s own silhouette, its armor_skin file, its sheets and its name, into a datapack and a resource pack you pick; edit the name a player reads and where its template is found; write everything back; reload the sheets from disk. |
| Showing: Master / On a material / Vanilla’s light | The greyscale you are painting, the bake as the game performs it, or vanilla’s own lighting for that material on its own — mid grey where it changes nothing, and brighter or darker by exactly what it adds. Strokes always land on the greyscale master, whichever is shown. |
| Material | Which armor material the bake is shown on. Look at a skin on iron, gold and netherite: the light, the saturated and the dark end of the range. |
| Vanilla light | How much of that material’s own texture is mixed over the master before the ramp is read. 0.35 is what the game does; 0 is the pattern with none of it, and the two pictures either side say which shapes are yours. |
| Pose, Phase | The walk or sprint cycle, frozen, as a piece’s. |
| Show player, Helmet, Chestplate, Leggings, Boots | The figure and the four shells, one at a time. A boot drawn over the leggings and a helmet that swallows the face are only visible with the shell above taken off. |
| Recipe centre, Recipe ring, Craftable | The skin template’s recipe, exactly as a part’s, written on Save as data/<ns>/recipe/skin_template_<skin>.json. |
Save writes both sheets back to tools/skin_masters/<skin>/, installs the pair where the client
loads it, and — when the skin has a datapack half — writes the data file, the language line and the
recipe. The masters stay in the repository even when the content does not, because the rig and the
checks are built from them: authoring a skin needs the clone, shipping one does not.
Starting from a rig. Outside the plugin, authoring a part starts from a rig: each one holds
the vanilla body and all four armor layers at their real inflate, animated with the game’s own walk
and sprint cycles, with an empty group sitting exactly where the layer will draw.
python tools/bb_rig.py --all regenerates them; the skin, armor and palette textures they
reference are extracted from the game jar by tools/vanilla_assets.py on first use (or from
your own copy of the game: Use my game… in the menu, or --jar, --minecraft, --from-dir
on the command line) and are never
committed. bb_geo.py converts .bbmodel to the mod’s geometry and back.
From an agent. The same editor drives from an MCP client through tools/mcp, a small server
in front of Blockbench’s own bridge plugin (mcp-toolkit’s mcptoolkit_bridge.js, HTTP on
127.0.0.1:25801): it serves the tools a part author uses (an authoring profile of the plugin’s
twenty-six), adds armorpieces_open, _new, _check, _save, _part,
_set_part, _pieces and _close — with the skin workspace’s own set beside them,
armorpieces_skins, _open_skin, _skin_sheet, _skin_paint, _skin_material, _skin_check,
_save_skin and _close_skin — and after every editing call appends the check every shipped
part passes - clearance and shared planes from trace_geometry.py, unpainted faces and stray or
coloured paint from sync_decoration_masters.py, together in tools/check_part.py, and
tools/check_skin.py for a skin - so the reply that placed a cube on the helmet shell says so.
Save refuses while problems stand unless told otherwise. tools/mcp/README.md has the setup and what the model is told; check_part.py runs
the same report by hand over a shipped part (check_part.py antlers, --all), a pack piece, or
the piece open in Blockbench (--status).
By hand
1. The part — data/<ns>/armorpieces/armor_decoration/dragon_crest.json
{
"asset_id": "<ns>:dragon_crest",
"description": { "translate": "decoration.<ns>.dragon_crest" },
"anchors": ["crest"]
}
anchors lists every socket the part may occupy; a smithing attempt anywhere else will not craft.
The sockets are a closed list — they are places on the humanoid model, not data:
| Piece | Anchors |
|---|---|
| Helmet | crest, brow, horns |
| Chestplate | pauldrons, back, collar, vambraces |
| Leggings | belt, tassets, knees |
| Boots | spurs, greaves |
2. The shape — assets/<ns>/armorpieces/decoration/dragon_crest.json
Bones and cubes in Blockbench’s vocabulary, baked through vanilla’s own MeshDefinition path, so
UVs, per-bone pivots and rotations and cube inflation all behave as they do for any entity model.
Coordinates are entity-model units (1/16 block) and +Y points down, which is why something
standing on top of a head has a negative Y origin.
{
"texture_width": 64,
"texture_height": 32,
"bones": [
{
"name": "spine",
"pivot": [0, 0, 0],
"cubes": [ { "origin": [-1, -6, -4], "size": [2, 6, 8], "uv": [0, 0] } ],
"children": []
}
]
}
3. The texture — assets/<ns>/textures/entity/decoration/dragon_crest.png
One grayscale master: value is shading, alpha is the silhouette. The game maps it onto each trim material’s own palette at load time, so every material — including ones a pack adds tomorrow — is free.
Not everything is metal. An optional RGBA companion named dragon_crest_static.png keeps its own
colour instead of taking the material’s, so a horn stays ivory and a sash stays cloth while the
hardware still turns gold. The master remains the single source of truth for the silhouette. If a
part needs bespoke art in one material, dragon_crest_<suffix>.png beside the master wins.
3b. Fittings, if the part has any. A fitting is a region of the part that takes a second material. Declare it on the part, in the order the smithing table should offer an item to them:
"fittings": ["armorpieces:gemstone", "armorpieces:guard"]
and paint the region as a grayscale mask beside the master, named after the fitting:
dragon_crest_gemstone.png. While the fitting is empty the mask is ignored; while it holds a gem,
every opaque mask pixel takes the mask’s own value through the gem’s palette. Four fittings ship —
gemstone and guard take trim materials (gems and metals, by tag), inlay takes a dye, and
banner takes a banner made at a loom onto a bone of the geometry named banner. A fitting is a
datapack file too, data/<ns>/armorpieces/fitting/<name>.json, so a pack can add a pommel that
takes any metal:
{ "type": "armorpieces:material",
"description": { "translate": "fitting.<ns>.pommel" },
"materials": "#armorpieces:guard_metals",
"ingredients": { "translate": "fitting.<ns>.pommel.ingredients" } }
ingredients is optional: the “Ingredients:” line of the pommel’s own template (see 4 below),
and without it the type answers — Trim Materials for a material fitting, Any Dye for a dye
fitting, A Banner for a banner fitting.
A new fitting type — one that reads an item the three built-in types cannot, or draws its own
geometry instead of colouring a mask — is Java, the way a new effect type is: implement
Fitting (or Fitting.Masked) and register its codec with Fittings.register, plus a
FittingRenderer through FittingRenderers.register if it draws. Whatever item fills it must
also be in #armorpieces:fitting_additions; that tag is what the one fitting recipe accepts, so
an item outside it never lights the table up, however willing the fitting.
4. Handing it out. There is no recipe to write. The mod ships one smithing recipe per socket
and the part travels on the template stack, so a pack only has to give out a template carrying
the armorpieces:decoration component:
{
"type": "minecraft:crafting_shaped",
"pattern": [" # ", "#F#", " # "],
"key": { "#": "minecraft:paper", "F": "minecraft:dragon_breath" },
"result": {
"id": "armorpieces:crest_template",
"components": { "armorpieces:decoration": "<ns>:dragon_crest" }
}
}
A loot table with minecraft:set_components does the same, and so does nothing at all: the
creative tab is built by walking the registry, so a new part appears there the moment the pack
loads. The twelve socket templates are <socket>_template — crest_template, brow_template,
and so on — and fitting_template is the thirteenth, one item for every fitting the same way:
the fitting it is for rides on the stack as armorpieces:fitting, so a pack’s pommel gets a
pommel template from a recipe whose result carries "armorpieces:fitting": "<ns>:pommel", and
the item model picks its look by the same component — a texture each for the four shipped
fittings, the plain card for any other. A named template offers the third-slot item to that
fitting alone on each part; the bare template, with no component, still offers it to every
fitting in turn, and with an empty third slot empties every fitting where a named one empties
only its own. The four shipped recipes are recipe/fitting_template_<fitting>.json, an amethyst
block, a copper ingot, any dye and a loom in a ring of paper.
4b. Found rather than made. A pack can write a loot table of its own, but it cannot add to a
vanilla one — only replace it whole, and two packs replacing chests/ancient_city cannot both
win. So the mod does the adding, as each table loads, and there are two ways to tell it what.
The one that scales is a loot group: a category of loot tables, and the parts found in it.
// data/<ns>/armorpieces/loot_group/tomb.json
{ "chance": 0.12,
"tables": [ "minecraft:chests/desert_pyramid",
{ "table": "minecraft:chests/ancient_city", "chance": 0.2 } ],
"parts": "#<ns>:tomb",
"skins": [ "<ns>:sarcophagus" ],
"fittings": "#armorpieces:common" }
Membership is a tag, data/<ns>/tags/armorpieces/armor_decoration/tomb.json, so a part joins
by being tagged and nothing about the part’s own file changes. That is what makes it worth having:
one file says where a whole look is found, and adding the fortieth part to it is one line in a tag
rather than a fortieth edit. It also runs both ways — your part joins one of the mod’s groups by
being tagged into #armorpieces:knightly, without overriding a file of ours. weight sets what
every member of the group is worth against the other groups sharing a table.
All four template families ride in the same pool. skins and cloths are the same field for those
registries; fittings is the same field again, and is the only route into the world for a
fitting template, since a Fitting is a dispatched codec — one record per type — and a loot
field on it would have to be added to every type, including a pack’s own. That asymmetry is also
the right shape: a fitting template is not a look but the second step of one the player already
has, so the mod’s six groups all name #armorpieces:common and the four fitting templates are
found everywhere. The skins divide by theme instead, because a skin is a look.
The exact one is the loot list on the part’s own file, for a part that belongs in one named
place and nowhere else:
"loot": [
{ "table": "minecraft:chests/ancient_city", "weight": 2, "chance": 0.15 },
{ "table": "minecraft:chests/desert_pyramid", "weight": 1, "chance": 0.1 }
]
The chance belongs to the table, not to the part. However many groups and rows feed one table,
the mod adds it ONE pool, rolled once, with a single random_chance on the pool — so 0.12 means
“a chest of this kind holds one of ours about one time in eight”, and it goes on meaning that as
parts are added. What another part changes is which one is found. (The obvious alternative, a
chance per part, does not survive contact with ninety of them: the table’s real odds are
1-∏(1-c), which climbs until every chest holds something.) Where several sources name one table
the highest chance wins and the members pool together, and a part offered twice is one entry, not
two. weight is optional, 1 by default, and only decides which member is placed once the pool has
fired. Any table will do, a mob’s or a fishing pool’s as much as a chest’s.
A part with no recipe and no route into the world cannot be had in survival at all, so
check_authoring.py fails on it; a part that means to be creative-only says so with an empty
"loot": []. The mod ships six groups over the six themes its parts are drawn in — knightly,
court, beast, wayfarer, tidal, carapace — holding every part, the skins divided between
them, and every fitting template in every one. Craftable recipes are kept only where the centre item
genuinely is the part or what it is made of; everything else is found. (How many of each there are
is on the wiki, counted from the datapack at build time
rather than written down here.)
For decorated armor rather than a template — a helmet already wearing a circlet with an emerald in it — a table uses the mod’s loot function on an armor entry:
{ "function": "armorpieces:set_decoration",
"socket": "brow", "part": "<ns>:dragon_crest", "material": "minecraft:gold",
"fittings": { "armorpieces:gemstone": "minecraft:emerald" } }
The stack has to be armor for the socket’s slot and the part has to fit the socket, the same two
rules the smithing table applies; a mismatch is reported when the table loads, and the armor
passes through plain. fittings is optional and written as it is in /give. part also takes a
list or a tag — "part": "#armorpieces:knightly" — and one member that fits the socket is drawn
at random, so one function can name a whole theme and let the socket sort it out.
4c. A table of your own, reaching back. Everything above is the mod reaching into other people’s
tables. The other direction — your loot table offering one of ours — is the armorpieces:template
pool entry:
{ "type": "armorpieces:template", "parts": "#armorpieces:knightly", "weight": 3 }
It takes the same four fields a group does (parts, skins, cloths, fittings), draws one
member of the union at random, and hands out exactly the template stack the creative tab holds.
Write a tag rather than a list of ids, and it survives the pack that fills the tag not being
installed: an empty tag makes the entry drop out of the pool entirely, taking its weight with it,
where a minecraft:item entry naming an absent part fails the whole table to parse. It says nothing
about how often — it is an ordinary entry in your pool and your conditions and rolls decide that.
What is out of reach. All of this is loot tables. A mod that fills a chest from its own code
never asks the loot system anything, so nothing here — and nothing any datapack could write —
reaches it. /armorpieces loot list is how you find out which it is: if the container’s table is
not in that list, it does not have one.
Overriding what this mod ships. Same ids, your pack. A resource pack can restyle any part’s geometry or texture and a datapack can change where it may be worn.
Pieces do not merge: the last pack to load wins the file, whole. So a pack that carries
data/armorpieces/armorpieces/armor_decoration/circlet.json replaces the mod’s circlet outright for
anyone who has it below the mod in the list, and armor already wearing armorpieces:circlet picks up
the new one with nothing to migrate — the id is what the save holds, and the id still resolves. The
short way to do it is to take the piece into a collection on the site (or a pack of your own) and
edit it there; by hand, copy the files and keep every id.
Leave uid out of an override. The library recognises the id and gives your version the mod’s
lineage, which is what says the two are the same piece redrawn rather than strangers.
Turning a recipe off. A datapack cannot delete a file the mod ships, so the mod ships a recipe type that loads and does nothing. Override the recipe’s file with it:
{ "type": "armorpieces:disabled" }
It has no fields, matches nothing, and has no display, so it is absent from the recipe book and
from any recipe viewer that reads displays. That is how a part becomes loot-only
(recipe/template_circlet.json), how a server does without fittings (recipe/apply_fitting.json
and recipe/clear_fitting.json), or how a socket is closed to smithing altogether
(recipe/apply_horns.json) — any recipe the mod has, and any other mod’s just the same. Every
other field in the file is ignored, which is why the Blockbench plugin’s Craftable switch can
leave the pattern and items in place under the swapped type.
Moving or renaming a piece
A player’s armor names its parts, its skin and its garment by id. Change one and every save that used it points at nothing.
That no longer costs anybody an item. A piece an installation cannot name is kept — the armor holds it, does not draw it, says “not installed” in its tooltip, and gets it back the moment the pack that defines it is installed. Nothing is ever deleted automatically, and there is no deadline.
But “kept” is a floor, not a plan. Two optional fields turn a move into a non-event, and they belong
on armor_decoration, armor_skin and cloth files alike:
{
"asset_id": "armorpieces_hunt:tusks",
"description": { "translate": "decoration.armorpieces_hunt.tusks" },
"anchors": ["horns"],
"former_ids": ["armorpieces:tusks"],
"uid": "ap1n36fr45nqudsaphu7ksa"
}
former_ids is what you write when you move or rename something. The piece that ARRIVES declares
what it replaces, so a save written years before the move finds it with no migration and no action
from the player. It is the only one of the two that works on an item saved before either field
existed, which is why the mod’s own 0.3.0 → 0.4.0 split is carried by thirty of these rather than by
anything cleverer. An id may be claimed by one piece only.
uid is a lineage identifier, and you never make one up. It is issued, once, by whichever
registry your pack belongs to, and it is consulted only after the id and every former_ids have
missed — which is what makes a move you FORGOT to declare survivable too. Two registries issue them:
- the content library, and this is the one to prefer for a pack published through armorpieces.com. Upload the pack, or save it from the editor, and the site mints a uid for every piece that has none and writes it into the piece’s own file in everything you download back — the zip, the export, the pack the editor opens. Nothing to type, and the site is then the thing that knows the piece is yours;
python tools/mint_uids.py, for a pack kept in a repository with this mod’s toolchain. It mints one for anything that has none and records it inuids.lock.
Either way the uid travels in the file from then on, so uploading the pack again is recognised: the site matches the uids against what it already holds and tells you which of the pieces it already knows, instead of taking them for new ones.
Never copy somebody else’s uid into a piece of yours, and never change one that is already there.
A uid is minted once and never changes. A changed uid is worse than no uid, because a stale one sitting in somebody’s save rebinds to the wrong piece.
uids.lockis append-only — a line stays even for a piece that was deleted, because that piece’s uid may still be in a world somewhere — andpython tools/mint_uids.py --check(which the gate runs) fails if one has moved.
Note that it is the id that wins. If the id in a save still resolves, that is the piece used, even when a uid says otherwise: a pack that deliberately redefines an id — which is exactly what a restore pack does — has to be allowed to.
When something IS missing, three commands help and none of them is needed:
/armorpieces missing |
every id this world has read and could not resolve, with counts — the shopping list |
/armorpieces prune <targets> |
what would be dropped from what they wear and carry; ... confirm actually drops it, and it cannot be undone |
/armorpieces upgrade |
writes loaded chunks back in the new form now, rather than whenever they are next saved |
prune is the only thing in the mod that destroys a saved piece. Worn and carried armor needs nothing
from upgrade: player data is written on logout whatever happens.
When a pack is wrong
A mistake in a pack file costs that mistake and nothing else. The world opens, the rest of the pack loads, and the report says what happened. Every mistake belongs to one of three buckets:
| bucket | what it means | examples |
|---|---|---|
| worked around | the file was readable and one part of it was not; it loaded without that part | a socket that does not exist in anchors; a fittings id nothing defines (it gets an inert stand-in, so the part loads and that fitting can never be filled); an effects or loot entry that could not be read; no description, so the id is used |
| skipped | the file could not be read at all; that one element is left out and everything else loads | broken JSON; no asset_id; anchors that is not a list; a rule the record refuses (if_wearer over a glider) |
| reported | the file is legal and cannot do what you meant; nothing to drop, so it is a line in the report | anchors ends up empty; loot names a table no pack defines; a recipe for a part that is not installed, or for a socket it does not fit; two shaped recipes with the same grid; two packs defining one id, or claiming one former_ids entry or uid; an asset_id with no geometry in any resource pack |
A skipped element is missing content, and a piece wearing it is kept the same way a piece from an uninstalled pack is — see Moving or renaming a piece. The promise is not that a broken pack works; it is that the cost of a mistake is proportional to it.
Where to read it. Every line names the pack and the file.
| the server log | once per load, grouped by bucket, and again after every /reload |
/armorpieces packs |
the same lines, at gamemaster level, with what was done about each |
| on join | one line to operators, only when the report is not empty |
The client keeps a report of its own, because half of what can be wrong is art and art is only ever
wrong on the client: a part whose asset_id has no geometry in any loaded resource pack is swept once
per world join and named, rather than drawing nothing in silence. A mistyped asset_id — the datapack
half shipped without its resource pack — has always looked exactly like the mod being broken, and now
says what it is.
What it does not cover. A texture or mask sheet that is missing gets vanilla’s missing texture and
the bake’s own log line, nothing more. A fitting naming a trim material that does not exist dangles in
vanilla’s registry and is still fatal with vanilla’s message: registering a fake trim material to
paper over a typo is not this mod’s business. And the runtime only sees the pack that is installed —
tools/check_authoring.py, check_additive.py and check_lang.py take a pack directory and see what
no runtime can, so run them against your own work before shipping it.
Giving a part behaviour
Parts are cosmetic by default. A part that should do something while it is worn carries effects
— one more field on the file you were already writing, or a row on the plugin’s Part… dialog:
"effects": [
{ "type": "armorpieces:blink", "chance": 0.2, "damage_types": "minecraft:is_projectile" }
]
Four built-in behaviours reach four of the five hooks from JSON alone, and two more types gate them:
type |
Hooks | Fields |
|---|---|---|
armorpieces:attribute |
Attributes | id, attribute, amount*, operation |
armorpieces:mob_effect |
Ticking | effect, amplifier*, ambient, show_particles, show_icon |
armorpieces:blink |
Damage | chance*, radius, damage_types, attempts |
armorpieces:glide |
Gliding + Ticking | sink*, wear_interval |
armorpieces:if_fitting |
all, forwarded | if (a fitting, and optionally material or dye), then (any effect) |
armorpieces:if_wearer |
all, forwarded | if (an entity predicate), then (any effect) |
* may scale with the material — see below.
armorpieces:attribute is most of what a part would want, and the palette is vanilla’s own
attribute list: armor, armor_toughness, attack_damage, attack_speed, knockback_resistance,
movement_speed, luck, max_health, jump_strength, safe_fall_distance,
fall_damage_multiplier, gravity, air_drag_modifier, bounciness, oxygen_bonus,
block_interaction_range, submerged_mining_speed and some thirty others. Feather boots that jump
further are one entry with no Java at all:
{ "type": "armorpieces:attribute", "id": "mypack:heel_wings_jump",
"attribute": "minecraft:jump_strength", "amount": 0.15, "operation": "add_multiplied_base" }
id only has to be unique within the part: the same part in two sockets contributes two modifiers
that stack, because the socket is prefixed onto the id for you.
operation is the field to get right, because most attributes have a small base. add_value
adds your number to the base, add_multiplied_base adds that fraction of the base, and
add_multiplied_total scales everything already added. Movement speed’s base is 0.1, so the two
readings of the same figure are a world apart — {"amount": 0.05, "operation": "add_value"} is half
again as fast, and the same 0.05 as add_multiplied_base is a twentieth faster, which is what the
mod’s own puttees say:
{ "type": "armorpieces:attribute", "id": "armorpieces:puttees_speed",
"attribute": "minecraft:movement_speed", "amount": 0.05, "operation": "add_multiplied_base" }
Extra hearts are the other shape: max_health is counted in half-hearts and wants a flat
add_value, so 2.0 is one heart. That is also the default operation, which is why the mod’s
gorget can leave the field out — it gives one heart, and two in netherite:
{ "type": "armorpieces:attribute", "id": "armorpieces:gorget_health",
"attribute": "minecraft:max_health",
"amount": { "default": 2.0, "by_material": [ { "material": "minecraft:netherite", "value": 4.0 } ] } }
What a part grants is the maximum; where the wearer’s health actually sits is vanilla’s business,
and vanilla caps it on the tick after the piece comes off — LivingEntity.tick refreshes the
attributes the change marked dirty, and a lowered max_health clamps the current health to it. So
hearts cannot leave anybody standing above their own maximum, and nothing on your side has to
arrange that. max_absorption behaves the same way, if a yellow shield suits the part better than
red hearts.
Two more in the palette are worth knowing before reaching for them: scale moves the wearer’s
hitbox, not just how they look — vanilla answers a changed scale with refreshDimensions() —
and gravity, friction_modifier and air_drag_modifier are strong enough at small numbers to
read as a bug rather than a piece of armor. They work; they are simply not the quiet tier the rest
of the list is.
The two gates
if_fitting is what makes a gem more than paint: { "type": "armorpieces:if_fitting", "if": { "fitting": "armorpieces:gemstone", "material": "minecraft:emerald" }, "then": { ... } }
runs its effect only while an emerald is set. material and dye are alternatives — one narrows
a material fitting, the other a dye fitting, and a condition naming both is refused at load.
Naming neither is the shortest form: while there is anything in the fitting at all.
if_wearer asks about the person wearing the piece instead, in vanilla’s own entity predicate —
the same object an advancement or a loot table takes, so what it can ask grows with the game rather
than with this mod:
{ "type": "armorpieces:if_wearer",
"if": { "equipment": { "mainhand": { "items": "minecraft:air" } } },
"then": { "type": "armorpieces:attribute", "id": "mypack:claws_bite",
"attribute": "minecraft:attack_damage", "amount": 1.0 } }
Claws that only bite bare-handed, and nothing else needed. Which conditions are legal depends on the hook underneath, and the rule has one reason: attribute modifiers are applied when equipment changes and then left in place, so a modifier that depended on something else would be computed once and quietly stay wrong.
- Over an attribute effect,
if_wearermay testequipmentonly — what the wearer holds and wears. That is honoured properly: a main- or off-hand change re-checks the armor slots, so the claw appears the moment the hand is empty and is gone the moment it is not. Anything else in the predicate —location,flags,effects,nbt— fails the pack at load, naming the test that was the problem. - Over
mob_effect,blinkor anything else re-asked at the moment it runs, every test is legal:{ "flags": { "is_in_water": true } },{ "location": { "dimension": "minecraft:the_nether" } }, and so on. - Over a glider it is refused outright: vanilla asks
canGlideon the client too, where an entity predicate cannot be evaluated. Gate a glider withif_fitting, which asks about the part.
The two nest in either order, because each wraps any effect including the other.
Numbers that scale with the material
The fields marked * above take either a plain number or one per material, so a netherite claw and
an iron claw are one part rather than two:
"amount": { "default": 1.0,
"by_material": [ { "material": "minecraft:netherite", "value": 2.0 },
{ "material": "#armorpieces:gemstones", "value": 1.5 } ] }
The first case that matches wins, so a single material goes above a tag that contains it. A
material no case names gets default, and a tag nobody installed simply never matches — a reference
to another pack’s materials cannot break yours.
The five hooks are Ticking (every server tick worn), Damage (a veto — allowDamage false
cancels the hit outright), Attributes (equip/unequip, and now also a hand change), Gliding
(vanilla’s own canGlide check) and Lifecycle (put on and taken off, for state that lives
outside the item — the one hook no built-in behaviour reaches, so putting anything there means
Java). A part has no durability of its own, so an effect that costs something spends the
decorated piece’s durability — pinions wears the chestplate it is bolted to.
Damage dealt by the wearer is not a hook. Fabric offers no damage-modification event —
ALLOW_DAMAGE can only refuse a hit, never enlarge one — and reaching it would mean a mixin into
the damage path, which every hook here deliberately avoids so that one part’s effect cannot break an
unrelated mod by winning a race for an injection point. Hitting harder is attack_damage through
armorpieces:attribute, with vanilla’s own formula doing the arithmetic.
Seeing what a part is doing
A part that carries effects lists them on the piece’s tooltip, one line each, with the numbers that
part in that material actually gives. What a tooltip cannot show is a condition, since it has no
wearer to ask — so /armorpieces effects [<wearer>] prints what somebody is wearing, socket by
socket, and marks each effect with whether it is contributing at that moment. It takes any living
entity, an armor stand included.
Six parts in this mod carry an effect, one per mechanism: heel_wings (a scaled attribute),
puttees (a fraction of a small base), gorget (a flat one — hearts), circlet (a fitting gate),
cloak (a dodge) and pinions (a glider). Two more live in the packs alongside it: claws (an
equipment condition and a per-material number) and head_fins (a world condition). Every one of
them is a file any pack could have written; the other sixty parts carry nothing at all.
A new effect type is the one thing that needs Java, because behaviour is code. Implement a hook, register the codec, and add nothing else:
public record BlinkAway(float chance) implements DecorationEffect.Damage {
public static final MapCodec<BlinkAway> CODEC = RecordCodecBuilder.mapCodec(i -> i.group(
Codec.FLOAT.fieldOf("chance").forGetter(BlinkAway::chance)
).apply(i, BlinkAway::new));
public MapCodec<? extends DecorationEffect> codec() { return CODEC; }
public boolean allowDamage(DecorationEffectContext ctx, DamageSource src, float amount) { ... }
}
// in onInitialize
DecorationEffects.register(Identifier.fromNamespaceAndPath("examplemod", "blink_away"), BlinkAway.CODEC);
From then on "type": "examplemod:blink_away" works on any part in anybody’s datapack. The
context carries the whole entry, so an effect can scale with the material it was applied in.
Effects are synced to the client, because canGlide runs on both sides.
Skins
A skin is the third kind of template, and it says a different thing about a piece of armor than a part does. A part is geometry hung on the body; a trim is vanilla’s accent painted over the armor’s texture; a skin is the armor’s own texture — what the plate is, rather than what is bolted to it or painted over it. Nothing about a skin changes a silhouette, so every part, every trim and every socket carries on exactly as it did.
A skin is two greyscale sheets, one data file and a recipe:
assets/<ns>/textures/entity/skin/<skin>/humanoid.png 64x32, on vanilla's armor grid
assets/<ns>/textures/entity/skin/<skin>/humanoid_leggings.png 64x32, the leggings twin
data/<ns>/armorpieces/armor_skin/<skin>.json asset_id, description, loot
data/<ns>/recipe/skin_template_<skin>.json the ring of paper, as a part's is
{
"asset_id": "examplemod:carapace",
"description": { "translate": "skin.examplemod.carapace" },
"loot": [ { "table": "minecraft:chests/jungle_temple", "weight": 1, "chance": 0.05 } ]
}
Two files, not two per material. The colour comes from the armor, not from the skin: the sheets
are drawn in the sixteen greys, and the client recolours them through eight shades taken from that
armor material’s own vanilla texture, deepened so the master’s form has somewhere to live, with
vanilla’s own lighting mixed back over the top. So a skinned iron helmet still reads as iron beside
an unskinned one, and an armor material added by another mod is skinned the moment it is installed —
the only thing wanted from it is the equipment texture it already ships. SkinBake is the
arithmetic and python tools/bake_skin.py --report prints what each material gives you.
Three consequences worth knowing before you draw:
- Shade in bands four to five levels apart. A level is a position on an eight-stop ramp and most
materials repeat stops, so a step of one or two levels can bake to the same colour on iron.
python tools/bake_skin.py --levelsprints what every level buys on every material, and--pair 6 achecks the pair you fancy. - You are not drawing on bare armor. Vanilla’s own texture for the material — its panel edges,
the rim along the top of a plate, the shadow under an overhang — is added to your texel’s value
before the ramp is read. That is what keeps a skinned plate reading as metal, and it runs ±45:
two and a half of your sixteen levels, with up to five levels between two texels side by side. So
the band above is a floor against the ramp, not against the light, and a shape placed along a
seam vanilla already shades will not be seen.
python tools/bake_skin.py --lightinghas the numbers;check_skin.pycounts the steps it actually overrules on the worst material (the skins that ship lose 2–11%); and in Blockbench the Armor Skin panel’s Showing switch draws that offset on its own, mid grey where it changes nothing. - A skin never paints a visor, and never paints the raised helmet shell. The face window is what
the
browsockets need, and thehatnet at UV 32,0 sits exactly where those parts do — vanilla paints neither, and a skin that did would bury seven parts.
A pack may ship <sheet>_<material>.png beside the pair — humanoid_netherite.png — and that art
is used as it is for that one material, which is the same escape hatch a part has.
The template’s own icon draws itself, and yours does too. One item carries every skin and picks
its look at render time, off the armorpieces:skin component. The art is not drawn by hand and is
not a file anywhere: it is the top ten rows of your skin’s own chest front, lifted off
humanoid.png, greyed and levelled into the template card’s range, drawn by the game as the item
atlas is built. So the icon is a swatch of the armor rather than a symbol for it, it cannot drift
from what the player will wear, and a skin a pack ships gets an icon on exactly the same terms as
one the mod ships — draw the sheet, and the icon is there.
That last part is why this is in the mod rather than in a script. minecraft:select, the vanilla way
to give one item many looks, keeps its cases in one file, and resource packs resolve a file by
winning it outright rather than by merging it — so a select listing the mod’s own skins is a select
your skin could never join. The mod registers a sprite source (armorpieces:skin_template_icons,
declared in assets/minecraft/atlases/items.json) and an item model type
(armorpieces:skin_template) instead; between them they find every skin with a sheet, in any pack,
and give it its own icon. A skin whose art is missing falls back to the generic card.
Two escape hatches. Ship textures/item/skin_template_<skin>.png in your own namespace and that file
wins — a sprite source loses to a real texture of the same name — so a skin whose chest is a poor
summary of it can be given a hand-drawn icon. And the generic card itself is still authored:
python tools/gen_template_icons.py draws it with the twelve socket icons and the cloths, and
--sheet writes a magnified contact sheet of the lot.
Authoring is the same loop parts have, with its own tools: python tools/skin_sheets.py <skin>
prints a sheet as ASCII to work from (--vanilla netherite prints vanilla’s own),
python tools/bb_rig.py --skin <skin> builds the Blockbench rig, the plugin’s skin workspace paints
it on the real figure with a live material preview, python tools/check_skin.py <skin> is the check
every shipped skin passes, and python tools/sync_skin_masters.py <skin> [--as <name>] installs the
pair into the resources — which the plugin’s Save already does, the way saving a part installs
its master, so this one is for a skin installed under another name or for reinstalling the lot.
Wearing one. Skin template + the armor + the armor’s own reforging material — an iron ingot
for iron, a diamond for diamond, a turtle scute for the turtle helmet. Re-skinning is re-forging, so
it costs the metal the piece is made of; the mod does not keep a table of that, it asks the piece
what repairs it, which is right for armor it has never heard of. Leave the third slot empty and the
skin comes off. Armor listed in #armorpieces:unskinnable_armor refuses skins outright — vanilla
chainmail is in it, because the weave is its whole identity and it has no metal of its own.
Cloths
A cloth is the fourth kind of template, and the last thing that can be said about one piece of armor. A part is geometry hung on the body, a trim is vanilla’s accent painted over the armor’s texture, a skin is the armor’s own texture — and a cloth is a garment worn over that texture, dyed and patterned at a loom.
It sits between the skin and the parts, which is where it sits on the body: over the plate, under the trim’s edge line, and under every part. That layering costs nothing, because a cloth is not a new pass — it is composited into the armor’s own texture, so it is the armor model. Which is also why it moves correctly, clips nothing, and needs no rig.
What it cannot do is hang. A cloth is tight to the boxes the armor already draws: no flare, no side slits that swing, and a hem that reaches the top of the thigh and stops. A garment that hangs is geometry, which is a part — and a part cannot be under anything or take the armor’s shading. They are different features.
A cloth is a greyscale mask per sheet, one data file and a recipe:
assets/<ns>/textures/entity/cloth/<cloth>/humanoid.png 64x32, on vanilla's armor grid
assets/<ns>/textures/entity/cloth/<cloth>/humanoid_leggings.png the same, for the hem
data/<ns>/armorpieces/cloth/<cloth>.json asset_id, sheet, description, loot
data/<ns>/recipe/cloth_template_<cloth>.json the ring of paper, as a part's is
{
"asset_id": "examplemod:surcoat",
"sheet": "shield",
"description": { "translate": "cloth.examplemod.surcoat" },
"loot": [ { "table": "minecraft:chests/pillager_outpost", "weight": 2, "chance": 0.1 } ]
}
Where the garment reaches is the model’s word, not the design’s. A texture can only paint texels that some box of its own item samples, so a garment reaches exactly the boxes the armor draws — no further, and there are three of them:
chest the chestplate's torso box humanoid.png the garment
waist the leggings' belt humanoid_leggings.png the join
leg the leggings' two leg boxes humanoid_leggings.png the hem
The chestplate’s layer draws the torso and the two arms and nothing over the thigh, so its own sheet ends at the waist. What continues past it is on the leggings’ boxes.
The garment is still the chestplate’s, though, and one component. The renderer reads
armorpieces:cloth off the CHEST slot when it paints the leggings sheet, so a hem costs no second
template, no second smithing operation, and there is no such thing as half-wearing a garment. Leg
armor is refused by the recipe for exactly that reason: nothing on it is read. The rule this buys is
the one the feature already has a layer up — a garment is worn ON armor, so a tabard over bare legs
has no geometry to hang a hem on and simply has no hem.
Cut the waist box even though it lives inside the chestplate. Every vanilla material scallops the bottom row or two of its torso away, and what shows through that scallop is the leggings’ belt. Cloth on the chest box stops there, correctly — the armor is what ends it — and without cloth on the belt behind it a garment reads as two bands with the armor’s own metal between them. The rest of the box is transparent on every material, so the armor cuts it away for free: paint the whole face and only the belt survives.
A hem is short on purpose. Three to five rows, and the two shipped cloths deliberately differ — the tunic closes round each leg and goes to five, the tabard is two flat panels and stops at three. Two things bound it. A leg box inflates 0.4 where the chest box inflates 1.0, so the garment above overhangs the hem by half a texel: make the hem’s top row its darkest and the step reads as cloth tucked under cloth. And the legs are two boxes that swing apart, so a long hem splits down the middle at every step. Anything that genuinely hangs is geometry, which is a part.
check_authoring.py refuses a cloth with no mask at all, which would be a garment the game puts on
a piece of armor and then draws nothing for. One sheet and not the other is fine: it is a garment
that does not reach that far.
What the mask says
Each sheet, three jobs at once:
- Alpha is how far the garment reaches. Where it is transparent, the armor is untouched. Reach generously: how far it can reach is the armor’s to say — see below. The armor cuts the garment. The mask says how far a cloth reaches; the armor says how far it can. Where the piece being worn paints nothing, neither does the cloth — so the neck’s notch, the hem’s taper and the bare shoulders come out of the piece itself rather than out of rows counted by hand into a mask, and they are right on a skin’s cut as readily as on vanilla’s. Cut the mask generously and let the armor trim it. It is the same image the lighting is measured from: the thing the cloth is worn on.
The exception is a face the armor uses nowhere, which is not a hole to respect but room to use — and on the torso box that means the top and the underside, since a breastplate has no lid. The top is worth using: only its outer column each side is ever seen, the head covering the rest, and that column is the shoulder line a tunic hangs from and a tabard’’’s straps cross. The underside is not: it is a horizontal plate at the waist, the full width of a box inflated past the body, and it cuts straight through the legs at every step. Leave it empty, as vanilla does.
- Value is the cloth’s own form — the folds, the shadow where it turns a corner, the dark band at the hem. 127 is the dye exactly; below goes toward black and above toward white, on the same three-stop ramp a dye fitting and a horn’s ivory already use.
- The two torso panels are where the design lands.
chest.front(20,20 8x12) andchest.back(32,20 8x12) — and the same two rectangles on the leggings sheet, which is thewaistbox, because vanilla lays the two nets out identically. That is what makes a garment’s two halves line up, and it is why the belt band carries the design’s own bottom rows rather than a stripe of flat dye. Everywhere else the mask covers takes the base colour alone — the sides, the shoulders, and every face of the leg boxes, which are four texels wide at most and could not carry a charge anyway. So a hem never takes the pattern, front face included: it is a garment seen below the design, not the design continued onto the thigh.
Both panels get the design the right way round. A banner’s back is mirrored because a banner is one sheet of cloth read from behind; a tabard is two panels, each read from outside.
sheet chooses which of vanilla’s two pattern sprite sets the panels sample — shield (12x22) or
banner (20x40) — the same choice armorpieces:banner offers. shield is the closer proportion to
an 8x12 panel and is the default.
The colour comes from a banner, not from the cloth. Nothing in your art is coloured. What the armor supplies is neither the silhouette nor the colour but the light: the material’s own deviation from the middle of its range is added to your mask’s value before the ramp is read, so iron’s studs and diamond’s facets show through the garment, and a skinned piece’s own form shows instead when there is a skin. The bake runs at 256x128 so a charge has room, upsampling the armor’s own texels nearest so the plate still reads at vanilla resolution.
python tools/preview_cloth.py is the reference implementation of all of that and writes a contact
sheet of your garment in three designs on five materials, both sheets stacked in each cell —
--light 0 to see the pattern alone, --light 0.45 to see it overdone, --sheet humanoid for one
half. python tools/paint_cloth_masks.py is how the four shipped masks are cut, and reads its
rectangles out of skin_sheets.py so a cut and a bake cannot disagree.
The template’s own icon draws itself, as a skin’s does, but from a different half of the art: a
skin is a surface, so its icon is a swatch of that surface; a cloth is a shape, so its icon is the
cut read off the same mask that ships, with the armor grey showing where the garment is not. A
tunic’s collar gap and a tabard’s open flanks are the whole difference between them and both are
visible at eight texels wide. python tools/gen_template_icons.py writes the icon, the item model
and the select for every cloth with art in the resources.
Wearing one. Cloth template + the armor + a banner. The addition slot names a material on
every other recipe in this mod; a cloth’s colour is not a material, so here it means the design —
base colour and up to six layers, made at a loom, which is already the best pattern editor the game
has. The banner is consumed, as it is for a shield. Leave the third slot empty and the garment comes
off. What may wear one is #armorpieces:clothable_armor, which ships chest armor and nothing else —
and the chest SLOT besides, since that is the only slot the renderer reads a garment from. It is
also place 1 of the piece’s own row at the advanced smithing table, beside the trim and the skin.
Judging the result in game
/armorpieces stage (permission level 2) puts parts on armor stands in front of you, read from the
registries, so a pack’s parts appear alongside the shipped ones.
Three of its modes are galleries — a picture of the whole thing rather than a comparison — and each
takes a seed as its last argument, reported back so a roll worth keeping can be asked for again.
pieces [<seed>] puts every part in the game down exactly once, a row per socket, each on its own
randomly dressed and skinned suit with something in every fitting it declares: useless for comparing
two parts and exactly right for a picture of all of them. random [<count>] [<seed>] dresses whole
sets with nothing about them chosen — armor, skin, cloth, part, material and every fitting rolled —
which is the fastest way to find two parts that each read well and cannot be worn at once.
set [<name>] stages the six sets written out by hand, one per theme the parts were authored in, or
one of them alone; there is no randomness in it, so the same command gives the same picture on any
world, and a set naming a part, skin or cloth the loaded data does not have is staged without it
rather than refusing.
The other modes are grids, each holding everything still but one axis. bases [<armor item>] puts
every (part, socket) pair down the rows and every trim material across the columns, one block per
base armor set — the third axis is the point, since a material’s texture suffix is resolved against
the armor’s equipment asset, so gold parts on gold armor draw from gold_darker and the same part
on iron does not. fittings [<part>] shows every fitting filled with everything it takes, one block
per fitting with the part’s materials down the rows. skins [<skin>] puts every skin down the rows
and every armor material across the columns — the one view whose columns are the armor rather than
the trim, because that is the axis a skin’s colour comes from. clear removes them all.
/armorpieces loot is numbers rather than stands, and it is where a question about loot is
answered. list [<filter>] prints the loot table ids this world actually loaded, + against the
ones the mod adds to — the way to learn what a modded chest’s table is called, since nothing else
in the game will tell you. explain <table> says what was added to one table and why: every group
and every loot row that offered a member, at what chance and weight, and the number that ended up
on the pool. groups lists the loaded groups with the server config applied, so one that has been
turned off or re-tuned says so. roll <table> [rolls] rolls the table, a thousand times unless told
otherwise, and counts what the mod put in it — all four template families, and decorated armor by
what it wears — against everything else the table dropped, so the theory can be checked against a
measurement.
Tuning it as a server
Loot content is pack data, but how much of it is not, and a server owner should not have to author
a datapack to say so. config/armorpieces-server.json is written on first run and re-read at the
start of every datapack reload, so /reload picks up an edit and there is no second command:
{ "enabled": true,
"chance_multiplier": 1.0,
"groups": {
"armorpieces:knightly": {
"chance": 0.05,
"weight": 2,
"add": [ "somemod:chests/vault", { "table": "somemod:chests/deep_vault", "chance": 0.3 } ],
"remove": [ "minecraft:chests/desert_pyramid" ]
},
"armorpieces:court": { "enabled": false }
},
"parts": {
"mod_parts": true,
"disabled": [ "#armorpieces:knightly", "armorpieces_dragon:dragon_wings" ]
} }
Every key is optional and absent means “as the pack wrote it”. chance_multiplier scales every
chance the mod ends up putting on a table — groups and loot rows alike — and is the one knob for
“half as much of this mod” without naming a group; enabled: false at the top is the off switch.
Per group: enabled drops one theme, chance and weight replace the group’s own, add joins
tables the pack never named (a modded container needs no datapack at all) and remove takes tables
away. What a group contains is deliberately not overridable — different members are a different
group, which is content, which is a datapack.
The invariants survive all of it: still one pool per table, still one roll, and the chance is still a property of the table rather than of a part.
parts is the other subject of the file: not how much, but which. Every pack is additive — a pack
may only define ids nothing else defines — so a pack can never take content away, and removing
content is a setting, never a pack. parts.disabled is a list of ids and #tags in any of the
four content registries (parts, skins, cloths, fittings; one list serves all four, and a tag is
looked up in the member’s own registry), and parts.mod_parts: false switches off the whole
armorpieces namespace in one line. Switched off means not offered: the member is in no loot pool
and no armorpieces:template entry, set_decoration will not draw it, its template recipe is
dropped as the recipes load (so it is not in the recipe book either), and both smithing tables
refuse a template carrying it. It does not mean uninstalled: the data file still loads, a
piece already worn is still read and still drawn, and a template already in a chest is still an
item. The moment disabling stopped a worn piece rendering, the split’s break would have been
rebuilt as a setting. Loot and recipes follow a /reload; the creative tab, which is built on the
client from what the server tells it on join, is rebuilt on the next join.
/armorpieces table opens the advanced smithing table wherever you stand, behind the same
permission level and for the same reason: a piece can be dressed and undressed — Remove is the one
way a part, a fitting, a trim, a skin or a cloth ever comes off — without the block being placed.
The loop between the editor and the game has one thing every author trips on once. Textures and
geometry are resources: F3+T reloads them, and the change is on the stand a moment after Save.
A change to the part’s data file — its anchors, its fittings, its loot, a new part — is a change
to a dynamic registry, which the game reads once as a world opens, so it needs the world left and
re-entered; /reload is not enough. Recipes and loot tables do reload with /reload.
Before a release: the gate
python tools/gate.py runs everything in this repository that can fail and exits nonzero if any of
it did. It exists because nothing in the Gradle build runs tools/, and a check that is not run is
not a check: three painters were stale for a whole release before anyone looked.
Checks are grouped by what they need, so a machine without a game still runs most of them:
tier 0 the tree the authoring round trip, the skin masters, the language lines, the
effect schema, the painters and their traces, the plugin's syntax, and
the unit tests under tools/tests
tier 1 the JVM gradlew build - the mod compiles and the JUnit tests pass
tier 2 the server the game's own behaviour on a dedicated server, through the mcp-toolkit
bridge (designed in docs/plans/testing.md, not built yet)
tier 3 the client rendering, the smithing screen and tooltips, likewise not built yet
--tier 0 stops after the tree, --only <name> runs one check, --list says what would run and
--json <path> writes the same result as a file. A failure prints the tail of the check’s own output
and the command that reproduces it alone, because the checks are all meant to be run on their own
while fixing what they found.
Two of the checks are new and worth naming, since nothing else covers them. check_lang.py derives
every language key the mod needs — the literals in the Java, every part, skin, cloth and fitting in
the datapack, every socket in the anchor enum, every registered item and block — and fails on a
missing line, which is the one defect that ships silently and is found by a player reading
item.armorpieces.brow_template in a tooltip. check_effect_schema.py asserts that the schema
effect_schema.py parses out of the Java still names every registered effect type and classified
every field, because when that parser stops recognising a record it does not crash: the dialog
simply stops offering the effect.
Edit this page · pulled from the mod repository at build time; edit it there.