Cosmetic

Affichez un modele 3D Blockbench sur n'importe quel slot d'armure d'un joueur. Le modele suit naturellement les bones vanilla (tilt de la tete, sneak, animations de marche, swing des bras) sans une seule ligne d'OpenGL.

fr.eri.eriapi.cosmetic Client + Serveur Forge 1.12.2 v1.8.0

Le module Cosmetic resout un probleme classique du modding 1.12.2 : afficher un modele 3D Blockbench sur un joueur sans ecrire un LayerRenderer custom par item, sans toucher au pipeline de rendu vanilla et sans casser l'armure deja equipee.

Le builder cree un ItemArmor classique avec une texture d'armure totalement transparente (la couche vanilla est invisible) puis attache un LayerRenderer partage qui dessine votre modele Blockbench au-dessus, accroche au bon bone du joueur.

Ce que vous pouvez faire

  • Un masque, casque, couronne, chapeau sur le slot HEAD
  • Une cape, sac a dos, plastron decoratif sur le slot CHEST
  • Des jambieres custom sur le slot LEGS
  • Des bottes 3D sur le slot FEET
  • Un set complet avec multi-bones (chaque partie suit son bone vanilla : bras, torse, jambes)

Installation rapide en 3 etapes

1. Concevez le modele dans Blockbench

Utilisez le preset Blockbench « Item / Modded Block » (ou « Modded Entity » si vous voulez du multi-bones). La taille standard recommandee pour un casque : un cube centre sur (8, 28, 8) en pixels Blockbench. Sauvegardez le modele JSON dans :

assets/votremod/models/item/barry_mask.json

Et les textures associees dans :

assets/votremod/textures/blocks/barry_mask/<nomTexture>.png

2. Creez l'item avec le builder

Java — CommonProxy ou registry d'init
import fr.eri.eriapi.cosmetic.EriCosmetics;
import net.minecraft.inventory.EntityEquipmentSlot;

Item barryMask = EriCosmetics.armor()
    .modId("eriniumfaction").registryName("barry_mask")
    .slot(EntityEquipmentSlot.HEAD)
    .displayName("Masque de Barry")
    .creativeTab(MyCreativeTab.INSTANCE)
    .model("eriniumfaction:item/barry_mask")
    .build();

3. Enregistrez l'item dans le registry Forge

Le builder ne s'enregistre pas automatiquement dans le registry Forge (meme convention que EriItem). C'est a vous de le faire dans votre handler de registry :

Java — @SubscribeEvent register
@SubscribeEvent
public static void registerItems(RegistryEvent.Register<Item> event) {
    event.getRegistry().register(barryMask);
}

Cote client, ne faites rien de plus — le layer renderer est attache automatiquement par EriAPI sur le RenderPlayer de chaque variante de skin (default + slim) pendant init.

Recommande (v1.8.5+) : EriCosmetics.preInit() dans votre FMLPreInit

Depuis EriAPI 1.8.5, vous pouvez appeler EriCosmetics.preInit() depuis le FMLPreInitializationEvent de votre ClientProxy. Cela enregistre explicitement le ModelBakeEvent listener qui installe le ArmorCosmeticBakedModel. Optionnel — EriAPI le fait deja automatiquement depuis son propre preInit, mais l'appel explicite documente la dependance et reste valide si une future version d'EriAPI retirait l'enregistrement implicite.

@SideOnly(Side.CLIENT)
public class ClientProxy extends CommonProxy {
    @Override
    public void preInit(FMLPreInitializationEvent e) {
        super.preInit(e);
        EriCosmetics.preInit();
    }
}
Rendu 3D dans tous les contextes (depuis v1.8.2)

Depuis EriAPI 1.8.2, le module Cosmetic dessine le modele Blockbench en 3D partout : inventaire, hotbar, premiere personne, troisieme personne, sol, fixed (item frame), head. Aucun modele item/generated a creer — le ArmorCosmeticBakedModel est genere automatiquement au ModelBakeEvent et delegue la geometrie au ArmorCosmeticTEISR. Le bloc display du JSON Blockbench (cles gui, firstperson_*, thirdperson_*, ground, fixed, head) est respecte automatiquement par contexte — les translations en pixels Blockbench sont divisees par 16 pour matcher la convention OpenGL des transforms vanilla.

Textures : aucune contrainte de dimension

Le rendu passe par TextureManager.bindTexture() (via SimpleTexture), hors de l'item atlas. Resultat : n'importe quelle dimension de PNG fonctionne (POT non requis, dimensions non carrees autorisees, ex. 121x152). Le multi-texture est supporte (chaque cle de la map textures du JSON peut pointer vers une PNG differente) et les UV custom de chaque face sont respectes verbatim — pratique pour les modeles Blockbench complexes avec texture_size non-standard.

API du builder

Entry point : EriCosmetics.armor() retourne un ArmorCosmeticBuilder.

Methodes fluent

MethodeDefautDescription
modId(String)Namespace du registry. Obligatoire.
registryName(String)Path du registry. Obligatoire. ID final : modId:registryName.
slot(EntityEquipmentSlot)HEAD, CHEST, LEGS ou FEET. Obligatoire.
model(String)ID Blockbench ("mod:item/nom"). Obligatoire.
displayName(String)nullNom affiche en tooltip. Si null, utilise la cle de traduction.
creativeTab(CreativeTabs)nullOnglet creatif. Si null, l'item n'apparait dans aucun onglet.
scale(float)1.0fScale uniforme autour de l'origine du bone.
offset(float x, float y, float z)(0, 0, 0)Translation supplementaire en blocs GL (= pixels Blockbench / 16), appliquee APRES le bone offset par defaut.
rotation(float x, float y, float z)(0, 0, 0)Rotation Euler en degres autour de l'origine du bone, dans l'ordre X puis Y puis Z.
followBones(boolean)trueSi true, les groupes Blockbench dont le nom matche un bone vanilla sont rendus a ce bone (voir section suivante). Sinon, tout va au bone principal du slot.
build()Cree et retourne l'Item. Vous devez ensuite l'enregistrer dans le registry Forge.

Validation a build()

build() leve IllegalStateException si :

  • modId est null ou vide
  • registryName est null ou vide
  • slot est null ou pas un des 4 slots autorises
  • model est null ou vide

Mapping des bones par slot

Chaque slot d'armure pointe vers un bone principal. Si votre modele Blockbench n'a qu'un seul groupe (ou plusieurs groupes sans nom particulier), tout est rendu a ce bone.

SlotBone principalBones additionnels reconnus (multi-bones)
HEADbipedHead(aucun)
CHESTbipedBodybipedRightArm, bipedLeftArm
LEGSbipedRightLegbipedLeftLeg
FEETbipedRightLegbipedLeftLeg
i
Pourquoi FEET utilise le bone des jambes ?

En 1.12.2, les bottes vanilla sont dessinees sur le bone bipedRightLeg / bipedLeftLeg, pas sur un bone « foot » dedie. Le module suit la meme convention — vos bottes 3D bougent avec la jambe.

Modeles multi-bones

Pour un cosmetic qui couvre plusieurs parties du corps (par exemple un plastron + des manches), nommez vos groupes Blockbench avec les conventions suivantes (insensible a la casse, les espaces et tirets bas sont accepted) :

Nom du groupe BlockbenchBone vanilla cible
headbipedHead
bodybipedBody
right_arm, rightarmbipedRightArm
left_arm, leftarmbipedLeftArm
right_leg, rightlegbipedRightLeg
left_leg, leftlegbipedLeftLeg

Chaque groupe racine dont le nom matche est rendu a son bone. Les groupes au nom non-matche tombent dans le bone principal du slot.

Exemple : un plastron avec manches qui suivent les bras

Structure du JSON Blockbench (resumee)
"groups": [
  { "name": "body",      "children": [...] },
  { "name": "right_arm", "children": [...] },
  { "name": "left_arm",  "children": [...] }
]

Resultat : le body est rendu sur le torse, les right_arm / left_arm suivent les bras du joueur (et donc l'animation d'arm-swing pendant la marche, le mouvement lors d'une attaque, etc.).

Desactiver le dispatch multi-bones

Si vous voulez que TOUT votre modele reste sur le bone principal (par exemple un casque conique avec un groupe interne nomme « body » pour le maillage de support), utilisez .followBones(false) :

Java
EriCosmetics.armor()
    .modId("mymod").registryName("conical_hat")
    .slot(EntityEquipmentSlot.HEAD)
    .model("mymod:item/conical_hat")
    .followBones(false)        // tout va sur bipedHead
    .build();

Offset par defaut & reperage Blockbench

Quand le module dessine votre modele, il appelle d'abord bone.postRender(0.0625f) sur le bone cible (place la matrice OpenGL au pivot du bone, avec sa rotation appliquee — meme pipeline que l'armure vanilla), puis applique un offset par defaut qui ramene l'origine Blockbench (0, 0, 0) a l'emplacement attendu dans le repere du bone.

BoneOffset par defaut (x, y, z) en blocs GL
bipedHead(-0.5, -1.5, -0.5)
bipedBody(-0.5, -1.5, -0.5)
bipedRightArm(-0.5, -1.5, -0.5)
bipedLeftArm(-0.5, -1.5, -0.5)
bipedRightLeg(-0.5, -0.75, -0.5)
bipedLeftLeg(-0.5, -0.75, -0.5)

Verifier visuellement

Concevez votre modele dans Blockbench en partant des reperes suivants :

  • Casque : centre la tete sur (8, 28, 8) en pixels — le centre du cube tomb pile sur la tete vanilla.
  • Torse : meme reperage que le casque (centre sur (8, 18, 8)) car le bone du body partage le meme pivot Y que la tete.
  • Jambes : modelez chaque jambe centree sur (8, 6, 8), hauteur 12px (de y=0 a y=12).

Le .offset() du builder s'ajoute AU dessus de l'offset par defaut — utilisez-le pour ajuster finement (par exemple si votre casque doit etre legerement en avant : .offset(0, 0, -0.05f)).

Exemple de calcul

Pour un masque dont le devant est dessine en Blockbench from [4,23,3.9] to [12,32,3.9] — centre (8, 27.5, 3.9) :

  • Conversion en blocs : (0.5, 1.71875, 0.24375)
  • Apres l'offset (-0.5, -1.5, -0.5) : (0, 0.21875, -0.25625) dans le repere du bone bipedHead
  • La tete vanilla s'etend de y=-0.5 (cou) a y=0 (sommet) en bone local. y=0.21875 est legerement au-dessus du sommet — corrigeable avec .offset(0, -0.22f, 0) si besoin.
  • La face avant de la tete vanilla est a z=-0.25. Le masque tombe a z=-0.256 — pile devant la tete.

BlockbenchRenderer (helper public)

Le module Cosmetic utilise en interne BlockbenchRenderer, un helper qui rend un AnimatedBlockModel deja parse avec ou sans AnimationPose. Ce helper a ete extrait de AnimatedBlockTESR et est public — vous pouvez l'utiliser pour vos propres systemes de rendu custom (TileEntity, items en main, GUI 3D, etc.).

API

// Rendu complet du modele en pose statique, textures auto-bind
BlockbenchRenderer.renderModel(AnimatedBlockModel model);

// Rendu avec une AnimationPose interpolee
BlockbenchRenderer.renderModel(AnimatedBlockModel model, AnimationPose pose);

// Controle total (bind manuel des textures par le caller)
BlockbenchRenderer.renderModel(AnimatedBlockModel model, AnimationPose pose, boolean bindTextures);

// Rendre un seul groupe a la position actuelle
BlockbenchRenderer.renderGroup(ModelGroup group);
BlockbenchRenderer.renderGroup(ModelGroup group, AnimationPose pose);
BlockbenchRenderer.renderGroup(ModelGroup group, AnimationPose pose,
                               Map<String, ResourceLocation> textures, boolean bindTextures);

// Variante avec texture overrides (utilisee par AnimatedBlockTESR)
BlockbenchRenderer.renderModelWithOverrides(AnimatedBlockModel model, AnimationPose pose,
                                            Map<String, String> textureOverrides);
!
Etat OpenGL

Le helper part du principe que l'etat GL est deja prepare : matrices en place, blending / culling / lighting setup, lightmap correct. Il ne push / pop pas la matrice racine et ne reset pas la couleur ou le blend mode. Encapsulez l'appel dans pushMatrix() / popMatrix() et configurez votre etat avant.

Troubleshooting

Le modele n'apparait pas du tout

  • Verifiez que vous avez bien enregistre l'item dans le registry Forge (le builder ne le fait pas).
  • Verifiez le chemin du modele : .model("mod:item/nom") doit pointer sur assets/mod/models/item/nom.json.
  • Regardez les logs au demarrage : EriAPI - ArmorCosmeticManager: attached cosmetic layer to N player renderers doit apparaitre. Si N=0, votre ClientProxy tourne avant que le render manager soit pret — ouvrez une issue.

Le casque vanilla est encore visible

La texture transparente est embarquee dans EriAPI sous assets/eriapi/textures/models/armor/transparent_layer_1.png. Si vous voyez un casque blanc / rose (texture manquante), verifiez que le jar EriAPI est bien dans votre dossier mods/ (en dev : ajoutez-le en compile files() dans le build.gradle).

Le modele est decalle / dans la mauvaise position

  • Verifiez que votre modele Blockbench est concu dans le bon repere — voir Offsets par defaut.
  • Ajustez avec .offset(x, y, z) (en blocs GL = pixels Blockbench / 16).
  • Pour un modele importe depuis un autre logiciel, essayez .rotation(0, 180, 0) — certains exporters inversent le sens du Z.

Les manches ne suivent pas les bras

Verifiez que vos groupes Blockbench s'appellent bien right_arm / left_arm (insensible a la casse, le tiret bas est tolere). Le mapping cherche le nom exact — un groupe nomme « ArmRight » ne sera pas reconnu.

Le modele clignote / disparait selon l'angle

Cause classique : votre modele a des faces transparentes mal triees pour le blend OpenGL. Le module active disableCull() et enableBlend() par defaut, mais l'ordre de dessin reste celui des elements du JSON Blockbench. Pour un effet propre, evitez de superposer plusieurs faces transparentes — preferez un seul cube avec une texture alpha.

Performance

Le layer est appele chaque frame pour chaque joueur visible. Le cout est minimal car :

  • Le lookup Item -> ArmorCosmeticDefinition est un HashMap.get() O(1).
  • Le modele Blockbench est cache en memoire (premier load uniquement).
  • Aucune allocation par frame — les boucles utilisent des for(int i) sur les listes existantes.
  • Si le joueur est invisible (isInvisible()), le layer est court-circuite.