Armour for Strut models

One model for each slot, worn by any Strut-drawn entity or player body, worked out from the equipment each client already sees.

Included: the core, trims, overlay, glint, animal armour, bone maps and fits (wearer profiles, sets, looks, atlas columns, dye tint, covers, the synced hidden flags). Strut does not draw a player's cape or elytra.

The idea

Armour is not part of the wearer's model. Each slot (head, chest, legs, feet) is a model of its own, a worn model bound to the wearer's bones by name, so it follows every animation and physics bone of the wearer. One set therefore fits two variant models such as an enchanted or a frosted variant model of a character, and every character built on the same skeleton. Make a slot model in Blockbench on top of the wearer's skeleton (same bone names and pivots) and keep only the part that is worn: in the plugin set Armour slot on the groups that hold each piece and Hidden by slot on the wearer's groups an armour slot hides (the pouches under a chestplate), then use File > Export > Export Armour Slot Model: <slot> (marked groups) for each slot, which writes only their geometry and keeps the groups above them empty, and Export Armour Wearer Profile (JSON), which writes the profile (the armour set and what each slot hides) from the marks. (Export Armour Slot Model (Binary) exports the bones you have selected in the outliner, with no marks: select the bones that hold the piece first, then pick its slot from the list and give the armour set; the file is named from the two.) /strutarmour in game says what each slot of the player (or the entity you look at) resolved to: the look, the model, the texture and what it hides.

The files

All under assets/<namespace>/strut/armour/, in a resource pack, so a pack can add or replace any of them. The id of each is <namespace>:<path>.

A set: sets/creature.json

{
  "texture": "mymod:textures/armor/armor.png",
  "texture_size": [4096, 4096],
  "column": [72, 0]
}

The texture is an atlas of materials: column is the size of one cell in pixels, and a look picks the cell, which becomes the UV offset the worn model is drawn with. This set has no models, so it is a template for others to extend.

A character's set: sets/ava.json

{ "extends": "mymod:creature" }

The slot models are the files of a folder, named for the slot: strut/models/armour/ava/head.strutmodel, chest, legs, feet (and body for animal armour). The folder is named after the set file; "models": "mymod:armour/ava_v2" names another one. A set that extends another says only what differs, so the texture and atlas come from creature; a "slots" object still names a model one by one and wins over the file of the folder.

Checked when the game starts

The armour files are checked every time resources load, and a mistake stops the game with one message that lists every problem and says which file to add: a registered wearer whose profile does not exist or names no armour_set, a wearer whose set has no models, a slot a wearer covers that its set has no model for, a model file named for a slot that does not exist (head, chest, legs, feet, body or one a wearer lists), a models folder with nothing in it, and sets that extend a missing set or each other.

A wearer profile: wearers/creature.json

{
  "extends": "mymod:creature_base",
  "armour_set": "mymod:creature",
  "slots": { "chest": { "covers": ["pouchL", "pouchR"] }, "legs": { "covers": ["belly"] } }
}

Which set the wearer wears (without armour_set it wears the set of the same name as the profile, so wearers/ava.json with just "extends": "mymod:creature" wears sets/ava.json), and which of its own bones a slot hides while it is worn. A profile may extend another and list only what differs (a slot it lists replaces the parent's for that slot).

Looks: looks/materials.json

{ "set": "mymod:creature", "match": { "materials": ["minecraft:diamond"] }, "column": 1 }
{ "set": "mymod:creature", "match": { "materials": ["minecraft:leather"] }, "column": 6, "tint": "dye", "undyed": "#A06540" }
{ "match": { "items": ["mymod:crown"] }, "model": "mymod:armour/crown", "texture": "mymod:textures/armour/crown.png" }
{ "column": 0 }

How an item looks when worn. The more exactly a look names an item the more it wins: its id, then a tag, then its armour material, then a look that matches everything; a look for the wearer's set beats a general one of the same kind. Items are never matched by their names. A look can bring a model and texture of its own (a modded helmet with a crest), limit itself to slots, and tint by "dye", "none" or a colour.

Models made on another skeleton

{ "armour_set": "mymod:creature",
  "bones": { "armorHead": "head", "armorBody": "upperBody" },
  "fit": { "armorHead": { "offset": [0, 2, 0], "rotation": [0, 0, 0], "scale": 1.1 } } }

A slot model whose bones are not named as the wearer's (made for another rig, or by a modder who never saw it) gets a bones map in the wearer profile: the worn bone on the left follows the wearer's bone on the right. A fit moves, turns (degrees) or scales a worn bone from where the wearer's bone puts it, for a different proportion: offsets are in the units animations move bones in (sixteenths of a block), a single number scales every axis. Both are added to by a profile that extends this one. The worn model still has to share the wearer's rest pose where it matches.

Trims (optional)

"trims": { "texture": "mymod:textures/armour/trims/{pattern}.png", "colours": { "mymod:ruby": "#C01030" } }

Put this in a set to draw armour trims; a set without it has none. Each trim pattern is a texture with the UV layout of the slot models, transparent where there is no trim ({pattern} and {namespace} stand for the pattern's id). The trim is the slot's model drawn again, blended over the armour, in that texture and tinted by the trim material: the vanilla materials' colours are built in, colours changes or adds some. A wearer profile with "trims": false shows none even where its set has them, and so does anything that extends it.

Overlay and glint

{ "match": { "materials": ["minecraft:leather"] }, "column": 6, "tint": "dye", "overlay_column": 8 }
"glint": { "texture": "mymod:textures/armour/glint.png", "strength": 0.75, "repeat": 0.16 }

A look's overlay_column (and overlay_row) is a second cell of the atlas drawn over the first, never tinted: leather's buckles and stitching that dye does not colour. An enchanted piece shines with the game's armour glint by default: the glint texture scrolls over the slot model and is added to the armour (strength is how strongly, repeat how many times the texture repeats over 64 texels of the atlas, the size of the game's own armour sheet: the game's is 0.16, a coarse sheen; a higher number is a finer pattern). A set can change it with the object above or turn it off with "glint": false, and a wearer profile can turn it off for itself and what extends it with "glint": false. Under a shader pack the glint takes the pack's own translucent blending instead of being added.

The glint is added only where the piece's own texture is opaque: the transparent texels of the atlas show no shine. (It is kept to them by an exact depth test against what the piece itself drew, so under a shader pack, where the glint is drawn by the pack's own program, it covers the whole model as before. -Dstrut.glintMask=false turns the test off.)

Animal armour

{ "slots": { "body": "mymod:armour/horse_body" }, "texture": "mymod:textures/armour/horse.png", "texture_size": [64, 64], "column": [16, 0] }

Horse, wolf and llama armour is a single slot, body (the game's body slot, which is also a llama's carpet). A set for an animal lists only that slot, a profile may give it covers, and the looks match horse armour by material or item as for any armour and a carpet by tag (minecraft:wool_carpets). The slot model is made on the animal's skeleton like any other.

Who wears it

StrutArmour.wearer(MyEntities.CREATURE, Identifier.of("mymod", "creature"));   // an entity type
PlayerBody.builder(id).armour(Identifier.of("mymod", "creature"))          // a player body

An entity that needs its own say implements ArmourWearer: armourProfile() (a custom creature with her own profile), armourStack(slot) for armour kept somewhere other than the game's equipment, and extraSlots() for slots of its own (a backpack). The game's own slots are read from the equipment every client already has, so nothing is sent.

Hiding armour

StrutArmour.setHidden(entity, "chest", true);   // server: everyone who sees the entity, saved with it
StrutArmour.isHidden(entity, "chest");          // either side

A slot that is hidden draws no piece and hides none of the bones it covers. A character's "show armour" button and a scene that hides it are the same call. The covers are low-priority overrides (priority -500), so a mod's own override on the same bone still wins.

Limits