WIP Phase 2.5 : fork render pipeline (PatchedEntityRenderer family)

Fork EF client renderer patched/entity/ → rig/render/, imports rewrités.

Nouveaux fichiers :
 - render/PatchedEntityRenderer.java   (~96 LOC, était stub 16 LOC)  — base abstraite,
   mulPoseStack + setArmaturePose + nameplate via mixin invoker
 - render/PatchedLivingEntityRenderer.java  (~230 LOC, EF 294 → stripped)  — hot path
   render, strippé du JSON customLayers loading + EntityDecorations (pas impl. côté
   LivingEntityPatch TiedUp)
 - render/PHumanoidRenderer.java       (~95 LOC, EF 53 → adapté)  — intermediate
   biped, baby head scale, enregistre PatchedArmorLayer + PatchedItemInHandLayer
 - render/TiedUpPlayerRenderer.java    (~95 LOC, EF PPlayerRenderer 60 → adapté)  —
   dispatch slim/default (Meshes.ALEX vs BIPED), propage modelParts visible flags,
   skip cape/bee/arrow layers (V3-REW-08/09)
 - render/PatchedLayer.java            (~55 LOC)  — base no-op layer
 - render/PatchedArmorLayer.java       (~45 LOC)  — stub no-op (V3-REW-04)
 - render/PatchedItemInHandLayer.java  (~45 LOC)  — stub no-op (Phase 3)
 - mixin/client/MixinEntityRenderer.java       (~35 LOC)  — invoker shouldShowName +
   renderNameTag (EF verbatim)
 - mixin/client/MixinLivingEntityRenderer.java (~30 LOC)  — invoker isBodyVisible,
   getRenderType, getBob

Modifs :
 - resources/tiedup-rig.mixins.json : enregistre les 2 nouveaux mixins client

Dépendances stubbées :
 - PatchedArmorLayer / PatchedItemInHandLayer no-op — "mangent" le layer vanilla
   équivalent pour éviter double-rendu, mais ne dessinent rien. À implémenter
   Phase 3 (armor : V3-REW-04 ; item-in-hand : tool joints GLB).
 - EntityDecorations (color/light/overlay modifiers) strippées du render path —
   le patch TiedUp n'expose pas encore cette API. Hurt/death overlay natifs
   conservés. À rework si feature premium glow/tint NPC.

TODOs Phase 2.6 / Phase 3 :
 - animated_layers/*.json datapack-driven loading (strippé du constructor) si
   besoin futur
 - PlayerItemInHandLayer (version player-spécifique, distincte de ItemInHandLayer
   générique) pas encore mangée côté player → layer vanilla continue de draw
 - ElytraLayer / CustomHeadLayer pas mangés (cohérent "on mange juste les layers
   qui entrent en conflit avec le mesh skinné")

Surprise pendant le fork :
 - ItemInHandLayer vanilla MC 1.20.1 exige M extends EntityModel<E> & ArmedModel,
   pas HeadedModel comme PatchedItemInHandLayer EF 1.20.1 pouvait le suggérer
   via ses anciennes versions. Alignement forcé sur ArmedModel.
 - EF utilise 2 mixins @Invoker (MixinEntityRenderer / MixinLivingEntityRenderer)
   pour accéder aux méthodes protected de vanilla — forkés verbatim dans
   rig/mixin/client/.

Tests :
 - ./gradlew compileJava : BUILD SUCCESSFUL
 - ./gradlew test --tests "com.tiedup.remake.rig.*" : 15/15 GREEN
   (TiedUpArmaturesTest 4 + GlbJointAliasTableTest 4 + GltfToSkinnedMeshTest 6 =
   14 rig + 1 utilitaire)
This commit is contained in:
notevil
2026-04-22 22:39:41 +02:00
parent 39f6177595
commit 8dff4c0e03
10 changed files with 736 additions and 5 deletions

View File

@@ -0,0 +1,36 @@
/*
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
* by the Epic Fight Team, licensed under GPLv3.
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
*/
package com.tiedup.remake.rig.mixin.client;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.entity.EntityRenderer;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.Entity;
/**
* Expose les méthodes protected {@code shouldShowName(Entity)} et
* {@code renderNameTag(...)} de {@link EntityRenderer} pour que
* {@link com.tiedup.remake.rig.render.PatchedEntityRenderer#render}
* puisse rendre le nameplate sans les hard-coder.
*
* <p>Pure @Invoker mixin — pas de logique injectée, équivalent EF de
* {@code yesman.epicfight.mixin.client.MixinEntityRenderer}.</p>
*/
@Mixin(value = EntityRenderer.class)
public interface MixinEntityRenderer {
@Invoker("shouldShowName")
boolean invokeShouldShowName(Entity entity);
@Invoker("renderNameTag")
void invokeRenderNameTag(Entity entity, Component name, PoseStack poseStack, MultiBufferSource bufferSource, int packedLight);
}

View File

@@ -0,0 +1,36 @@
/*
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
* by the Epic Fight Team, licensed under GPLv3.
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
*/
package com.tiedup.remake.rig.mixin.client;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.entity.LivingEntityRenderer;
import net.minecraft.world.entity.LivingEntity;
/**
* Expose les méthodes protected {@code isBodyVisible}, {@code getRenderType}
* et {@code getBob} de {@link LivingEntityRenderer} pour que
* {@link com.tiedup.remake.rig.render.PatchedLivingEntityRenderer#render}
* puisse les appeler sans dépendre d'access transformers lourds.
*
* <p>Pure @Invoker mixin — équivalent EF de
* {@code yesman.epicfight.mixin.client.MixinLivingEntityRenderer}.</p>
*/
@Mixin(value = LivingEntityRenderer.class)
public interface MixinLivingEntityRenderer {
@Invoker("isBodyVisible")
boolean invokeIsBodyVisible(LivingEntity entity);
@Invoker("getRenderType")
RenderType invokeGetRenderType(LivingEntity entity, boolean isVisible, boolean isVisibleToPlayer, boolean isGlowing);
@Invoker("getBob")
float invokeGetBob(LivingEntity entity, float partialTicks);
}

View File

@@ -0,0 +1,92 @@
/*
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
* by the Epic Fight Team, licensed under GPLv3.
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
*/
package com.tiedup.remake.rig.render;
import net.minecraft.client.model.HumanoidModel;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.client.renderer.entity.LivingEntityRenderer;
import net.minecraft.client.renderer.entity.layers.HumanoidArmorLayer;
import net.minecraft.client.renderer.entity.layers.ItemInHandLayer;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import com.tiedup.remake.rig.anim.Pose;
import com.tiedup.remake.rig.armature.Armature;
import com.tiedup.remake.rig.armature.JointTransform;
import com.tiedup.remake.rig.asset.AssetAccessor;
import com.tiedup.remake.rig.math.OpenMatrix4f;
import com.tiedup.remake.rig.math.Vec3f;
import com.tiedup.remake.rig.mesh.HumanoidMesh;
import com.tiedup.remake.rig.patch.LivingEntityPatch;
/**
* Niveau intermédiaire humanoïde — s'utilise pour tout ce qui rentre dans le
* moule biped (player, kidnapper, damsel, maid, master). Ajoute :
*
* <ul>
* <li>Un mesh par défaut injecté au constructor (évite la duplication de
* {@link #getDefaultMesh()} dans chaque sous-classe)</li>
* <li>Le scale baby Head 1.25x via {@link #setJointTransforms} — pattern EF
* strict, le modèle vanilla scale le head au rendu, donc on doit matcher
* côté pose pour que les items (gags etc.) restent attachés</li>
* <li>Les stubs {@link PatchedArmorLayer} et {@link PatchedItemInHandLayer}
* pour "manger" les layers vanilla équivalents (sinon rendu double)</li>
* <li>{@link #getDefaultLayerHeightCorrection()} = 0.75F (hauteur standard
* humanoïde pour les particles overlays)</li>
* </ul>
*
* @param <E> entité humanoïde
* @param <T> patch RIG associé
* @param <M> HumanoidModel vanilla
* @param <R> LivingEntityRenderer vanilla qui gère {@code M}
* @param <AM> mesh humanoïde (typiquement {@link HumanoidMesh} direct ou
* dérivé)
*/
@OnlyIn(Dist.CLIENT)
public class PHumanoidRenderer<E extends LivingEntity, T extends LivingEntityPatch<E>, M extends HumanoidModel<E>, R extends LivingEntityRenderer<E, M>, AM extends HumanoidMesh> extends PatchedLivingEntityRenderer<E, T, M, R, AM> {
private final AssetAccessor<AM> mesh;
public PHumanoidRenderer(AssetAccessor<AM> mesh, EntityRendererProvider.Context context, EntityType<?> entityType) {
super(context, entityType);
this.mesh = mesh;
// RIG Phase 2.5 : les 2 layers vanilla pertinents (armor + main items)
// sont mangés par des stubs no-op. Voir PatchedArmorLayer / PatchedItemInHandLayer
// et V3-REW-04 (armor) / Phase 3 (item-in-hand réel).
//
// ElytraLayer, CustomHeadLayer volontairement non-mappés Phase 2.5 — le
// layer vanilla les dessinera directement (couche vanilla séparée du
// mesh RIG). Cohérent avec la stratégie "on mange juste les layers dont
// la géométrie entre en conflit avec le mesh skinné".
this.addPatchedLayer(HumanoidArmorLayer.class, new PatchedArmorLayer<E, T, M>());
this.addPatchedLayer(ItemInHandLayer.class, new PatchedItemInHandLayer<E, T, M>());
}
@Override
public void setJointTransforms(T entitypatch, Armature armature, Pose pose, float partialTicks) {
if (entitypatch.getOriginal().isBaby()) {
// Compensation du scale 1.25x que le HumanoidModel vanilla applique sur
// la tête quand young==true — sans ça les items tête (gags, masks) se
// détachent visuellement du mesh.
pose.orElseEmpty("Head").frontResult(JointTransform.scale(new Vec3f(1.25F, 1.25F, 1.25F)), OpenMatrix4f::mul);
}
}
@Override
protected float getDefaultLayerHeightCorrection() {
return 0.75F;
}
@Override
public AssetAccessor<AM> getDefaultMesh() {
return this.mesh;
}
}

View File

@@ -0,0 +1,38 @@
/*
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
* by the Epic Fight Team, licensed under GPLv3.
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
*/
package com.tiedup.remake.rig.render;
import net.minecraft.client.model.HumanoidModel;
import net.minecraft.client.renderer.entity.layers.HumanoidArmorLayer;
import net.minecraft.world.entity.LivingEntity;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import com.tiedup.remake.rig.patch.LivingEntityPatch;
/**
* Stub RIG Phase 2.5 — remplace le {@link HumanoidArmorLayer} vanilla pour
* les entités riggées.
*
* <p>Actuellement <b>no-op</b> : le render d'armure n'est pas implémenté
* tant que V3-REW-04 n'est pas livré (Phase 3). Enregistré dans
* {@link PHumanoidRenderer} pour que la table {@code patchedLayers}
* "mange" le layer vanilla sans aucun visuel intermédiaire — évite le
* double-rendu armor vanilla par-dessus le mesh skinné.</p>
*
* <p>Voir {@code docs/plans/rig/V3_REWORK_BACKLOG.md} V3-REW-04 et
* {@code PHASE0_DEGRADATIONS.md} S-03.</p>
*
* @param <E> entité vivante rigg-compatible
* @param <T> patch RIG associé
* @param <M> HumanoidModel vanilla
*/
@OnlyIn(Dist.CLIENT)
public class PatchedArmorLayer<E extends LivingEntity, T extends LivingEntityPatch<E>, M extends HumanoidModel<E>> extends PatchedLayer<E, T, M, HumanoidArmorLayer<E, M, M>> {
// renderLayer hérité = no-op (voir super). Phase 3 : wire armor bake via
// HumanoidMesh.getHumanoidArmorModel + WearableItemLayer-like drawing.
}

View File

@@ -6,10 +6,90 @@
package com.tiedup.remake.rig.render;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.entity.EntityRenderer;
import net.minecraft.client.renderer.entity.LivingEntityRenderer;
import net.minecraft.world.entity.LivingEntity;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.client.event.RenderNameTagEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.Event.Result;
import com.tiedup.remake.rig.anim.Pose;
import com.tiedup.remake.rig.armature.Armature;
import com.tiedup.remake.rig.asset.AssetAccessor;
import com.tiedup.remake.rig.math.MathUtils;
import com.tiedup.remake.rig.math.OpenMatrix4f;
import com.tiedup.remake.rig.math.QuaternionUtils;
import com.tiedup.remake.rig.mesh.SkinnedMesh;
import com.tiedup.remake.rig.mixin.client.MixinEntityRenderer;
import com.tiedup.remake.rig.patch.LivingEntityPatch;
/**
* Stub RIG Phase 0 — renderer EF substituant {@code EntityRenderer} vanilla
* pour les entités riggées. Implémentation complète Phase 2 (pipeline :
* pose → armature → skinning → VertexConsumer).
* Base abstraite du pipeline de rendu RIG patché — remplace l'{@link EntityRenderer}
* vanilla pour les entités dont le patch retourne {@code overrideRender() == true}.
*
* <p>Fournit trois responsabilités :</p>
* <ol>
* <li>Render nameplate (via {@link RenderNameTagEvent} Forge + mixin invoker)</li>
* <li>Application de la model matrix entité (rotation 180° + yaw corps) au {@link PoseStack}</li>
* <li>Baking de la pose courante sur l'{@link Armature} (pour que
* {@code armature.getPoseMatrices()} soit à jour avant le draw du mesh)</li>
* </ol>
*
* <p>Fork verbatim de {@code yesman.epicfight.client.renderer.patched.entity.PatchedEntityRenderer}
* (EF 62 LOC). Imports rewrités vers les packages RIG TiedUp.</p>
*
* @param <E> entité vivante ({@code LivingEntity}+)
* @param <T> patch RIG associé
* @param <R> renderer vanilla ({@code EntityRenderer<E>})
* @param <AM> mesh skinné dérivé
*/
public abstract class PatchedEntityRenderer<E, T, M, R> {
@OnlyIn(Dist.CLIENT)
public abstract class PatchedEntityRenderer<E extends LivingEntity, T extends LivingEntityPatch<E>, R extends EntityRenderer<E>, AM extends SkinnedMesh> {
public void render(E entity, T entitypatch, R renderer, MultiBufferSource buffer, PoseStack poseStack, int packedLight, float partialTicks) {
RenderNameTagEvent renderNameplateEvent = new RenderNameTagEvent(entity, entity.getDisplayName(), renderer, poseStack, buffer, packedLight, partialTicks);
MinecraftForge.EVENT_BUS.post(renderNameplateEvent);
MixinEntityRenderer entityRendererAccessor = (MixinEntityRenderer) renderer;
if ((entityRendererAccessor.invokeShouldShowName(entity) || renderNameplateEvent.getResult() == Result.ALLOW) && renderNameplateEvent.getResult() != Result.DENY) {
entityRendererAccessor.invokeRenderNameTag(entity, renderNameplateEvent.getContent(), poseStack, buffer, packedLight);
}
}
public void mulPoseStack(PoseStack poseStack, Armature armature, E entity, T entitypatch, float partialTicks) {
OpenMatrix4f modelMatrix = entitypatch.getModelMatrix(partialTicks);
poseStack.mulPose(QuaternionUtils.YP.rotationDegrees(180.0F));
MathUtils.mulStack(poseStack, modelMatrix);
if (LivingEntityRenderer.isEntityUpsideDown(entity)) {
poseStack.translate(0.0D, entity.getBbHeight() + 0.1F, 0.0D);
poseStack.mulPose(QuaternionUtils.ZP.rotationDegrees(180.0F));
}
}
public void setArmaturePose(T entitypatch, Armature armature, float partialTicks) {
Pose pose = entitypatch.getAnimator().getPose(partialTicks);
this.setJointTransforms(entitypatch, armature, pose, partialTicks);
armature.setPose(pose);
}
public AssetAccessor<AM> getMeshProvider(T entitypatch) {
return this.getDefaultMesh();
}
public abstract AssetAccessor<AM> getDefaultMesh();
/**
* Override pour modifier la pose juste avant le bake (ex. scale de la tête
* pour les babies). <b>Developers shouldn't implement any interpolations in
* this method</b> — utilise {@link LivingEntityPatch#poseTick} à la place.
*/
public void setJointTransforms(T entitypatch, Armature armature, Pose pose, float partialTicks) {
}
}

View File

@@ -0,0 +1,37 @@
/*
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
* by the Epic Fight Team, licensed under GPLv3.
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
*/
package com.tiedup.remake.rig.render;
import net.minecraft.client.model.ArmedModel;
import net.minecraft.client.model.EntityModel;
import net.minecraft.client.renderer.entity.layers.ItemInHandLayer;
import net.minecraft.world.entity.LivingEntity;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import com.tiedup.remake.rig.patch.LivingEntityPatch;
/**
* Stub RIG Phase 2.5 — remplace le {@link ItemInHandLayer} vanilla pour les
* entités riggées (items tenus en main, main gauche/droite).
*
* <p>Actuellement <b>no-op</b> : l'item-in-hand RIG-attached arrive en
* Phase 3 (items bondage GLB + rendu via tool joints {@code Tool_L} /
* {@code Tool_R} du biped). Enregistré dans {@link PHumanoidRenderer}
* pour empêcher le layer vanilla de dessiner l'item ailleurs pendant
* que le mesh est en pose RIG.</p>
*
* @param <E> entité tenant un item
* @param <T> patch RIG associé
* @param <M> {@link EntityModel} qui implémente aussi {@link ArmedModel}
* (contrainte vanilla {@link ItemInHandLayer})
*/
@OnlyIn(Dist.CLIENT)
public class PatchedItemInHandLayer<E extends LivingEntity, T extends LivingEntityPatch<E>, M extends EntityModel<E> & ArmedModel> extends PatchedLayer<E, T, M, ItemInHandLayer<E, M>> {
// renderLayer hérité = no-op. Phase 3 : draw hand item via pose matrix du
// Tool_L/Tool_R joint sur la base du RenderItemBase existant.
}

View File

@@ -0,0 +1,58 @@
/*
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
* by the Epic Fight Team, licensed under GPLv3.
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
*/
package com.tiedup.remake.rig.render;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.model.EntityModel;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.entity.layers.RenderLayer;
import net.minecraft.world.entity.LivingEntity;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import com.tiedup.remake.rig.math.OpenMatrix4f;
import com.tiedup.remake.rig.patch.LivingEntityPatch;
/**
* Base abstraite pour les layers additionnels (armor, item-in-hand, cape...)
* rendus par dessus le mesh principal. Équivalent EF
* {@code client.renderer.patched.layer.PatchedLayer}.
*
* <p><b>RIG Phase 2.5</b> : infrastructure minimale — la méthode
* {@link #renderLayer} est no-op par défaut, et les 2 sous-classes concrètes
* (armor / item-in-hand) sont stubs. Implémentations réelles en Phase 3
* (V3-REW-04 armor, V3-REW-08/09 cape/arrows).</p>
*
* @param <E> entité vanilla
* @param <T> patch RIG de {@code E}
* @param <M> EntityModel vanilla
* @param <L> RenderLayer vanilla concret (ex. {@code HumanoidArmorLayer})
*/
@OnlyIn(Dist.CLIENT)
public abstract class PatchedLayer<E extends LivingEntity, T extends LivingEntityPatch<E>, M extends EntityModel<E>, L extends RenderLayer<E, M>> {
/**
* Méthode appelée par {@link PatchedLivingEntityRenderer#renderLayer} pour
* chaque layer patché enregistré. Stub no-op par défaut.
*
* @param entity entité rendue
* @param entitypatch patch RIG associé
* @param originalRender layer vanilla original (peut être {@code null} pour les customLayers)
* @param poseStack pose stack courant (déjà en espace entité)
* @param buffer buffer source
* @param packedLight lumière packée
* @param poses matrices de pose armature (index = jointId)
* @param bob phase de bobbing vanilla
* @param yRotHeadDelta delta entre yRot body et yRot head
* @param pitch pitch (xRot) view
* @param partialTicks partial ticks
*/
public void renderLayer(E entity, T entitypatch, L originalRender, PoseStack poseStack, MultiBufferSource buffer, int packedLight, OpenMatrix4f[] poses, float bob, float yRotHeadDelta, float pitch, float partialTicks) {
// no-op — subclasses override
}
}

View File

@@ -0,0 +1,255 @@
/*
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
* by the Epic Fight Team, licensed under GPLv3.
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
*/
package com.tiedup.remake.rig.render;
import java.util.List;
import java.util.Map;
import org.joml.Vector4f;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.Minecraft;
import net.minecraft.client.model.EntityModel;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.client.renderer.entity.LivingEntityRenderer;
import net.minecraft.client.renderer.entity.layers.RenderLayer;
import net.minecraft.client.renderer.texture.OverlayTexture;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.common.MinecraftForge;
import com.tiedup.remake.rig.armature.Armature;
import com.tiedup.remake.rig.asset.AssetAccessor;
import com.tiedup.remake.rig.event.PrepareModelEvent;
import com.tiedup.remake.rig.math.MathUtils;
import com.tiedup.remake.rig.math.OpenMatrix4f;
import com.tiedup.remake.rig.mesh.SkinnedMesh;
import com.tiedup.remake.rig.mixin.client.MixinLivingEntityRenderer;
import com.tiedup.remake.rig.patch.LivingEntityPatch;
/**
* Orchestrateur principal du rendu RIG pour les entités vivantes — remplace
* {@link LivingEntityRenderer} vanilla pour les entités patched.
*
* <p>Flow de {@link #render} :</p>
* <ol>
* <li>super.render : nameplate</li>
* <li>mulPoseStack : applique modelMatrix (rotation body + scale + upsideDown)</li>
* <li>prepareVanillaModel : setup les champs vanilla du {@link EntityModel} (riding/young,
* walkAnimation, head yRot) — utile pour que les layers vanilla non patchés voient
* le même état que s'ils étaient rendus par vanilla</li>
* <li>setArmaturePose : bake la pose courante dans {@code armature.getPoseMatrices()}</li>
* <li>prepareModel : mesh.initialize() (détecte hidden parts)</li>
* <li>mesh.draw : dispatch vers shader compute ou CPU skinning via {@code drawPosed}</li>
* <li>renderLayer : itère les layers vanilla mappés vers des {@link PatchedLayer} (ex.
* armor, item-in-hand, cape) et les customLayers enregistrés manuellement</li>
* <li>Debug hitboxes si F3+B actif</li>
* </ol>
*
* <p><b>Stripped vs EF source</b> (volontaire, Phase 2.5) :</p>
* <ul>
* <li>Chargement JSON datapack-driven des {@code animated_layers/{entity_type}.json} —
* TiedUp n'a pas de customLayers data-driven pour l'instant</li>
* <li>{@code LayerRenderer} interface (registre public des addPatchedLayer/addCustomLayer) —
* conservé en API mais pas exposé via interface dédiée</li>
* <li>{@code entitypatch.getEntityDecorations()} — decoration overlays (glow custom, elite
* tint) pas implémentés côté {@link LivingEntityPatch} TiedUp. Couleur et overlay
* utilisent les defaults vanilla.</li>
* </ul>
*
* @param <E> entité vivante
* @param <T> patch RIG associé
* @param <M> EntityModel vanilla
* @param <R> renderer vanilla ({@code LivingEntityRenderer})
* @param <AM> mesh skinné dérivé
*/
@OnlyIn(Dist.CLIENT)
public abstract class PatchedLivingEntityRenderer<E extends LivingEntity, T extends LivingEntityPatch<E>, M extends EntityModel<E>, R extends LivingEntityRenderer<E, M>, AM extends SkinnedMesh> extends PatchedEntityRenderer<E, T, R, AM> {
protected final Map<Class<?>, PatchedLayer<E, T, M, ? extends RenderLayer<E, M>>> patchedLayers = Maps.newHashMap();
protected final List<PatchedLayer<E, T, M, ? extends RenderLayer<E, M>>> customLayers = Lists.newArrayList();
protected PatchedLivingEntityRenderer(EntityRendererProvider.Context context, EntityType<?> entityType) {
// RIG Phase 2.5 : chargement JSON datapack des animated_layers strippé
// (EF L55-81). Si besoin futur d'customLayers data-driven, réimplémenter
// via FileToIdConverter.json("animated_layers/" + type.getPath()).
}
@Override
public void render(E entity, T entitypatch, R renderer, MultiBufferSource buffer, PoseStack poseStack, int packedLight, float partialTicks) {
super.render(entity, entitypatch, renderer, buffer, poseStack, packedLight, partialTicks);
Minecraft mc = Minecraft.getInstance();
MixinLivingEntityRenderer livingEntityRendererAccessor = (MixinLivingEntityRenderer) renderer;
boolean isVisible = livingEntityRendererAccessor.invokeIsBodyVisible(entity);
boolean isVisibleToPlayer = !isVisible && !entity.isInvisibleTo(mc.player);
boolean isGlowing = mc.shouldEntityAppearGlowing(entity);
RenderType renderType = livingEntityRendererAccessor.invokeGetRenderType(entity, isVisible, isVisibleToPlayer, isGlowing);
Armature armature = entitypatch.getArmature();
poseStack.pushPose();
this.mulPoseStack(poseStack, armature, entity, entitypatch, partialTicks);
this.prepareVanillaModel(entity, renderer.getModel(), renderer, partialTicks);
this.setArmaturePose(entitypatch, armature, partialTicks);
if (renderType != null) {
AM mesh = this.getMeshProvider(entitypatch).get();
this.prepareModel(mesh, entity, entitypatch, renderer);
PrepareModelEvent prepareModelEvent = new PrepareModelEvent(this, mesh, entitypatch, buffer, poseStack, packedLight, partialTicks);
if (!MinecraftForge.EVENT_BUS.post(prepareModelEvent)) {
// RIG Phase 2.5 : EntityDecorations (color/light/overlay modifiers) pas
// implémentées côté LivingEntityPatch TiedUp. On utilise les defaults
// vanilla : alpha 0.15 si invisible-mais-visible-pour-player, sinon 1.0.
Vector4f color = new Vector4f(1.0F, 1.0F, 1.0F, isVisibleToPlayer ? 0.15F : 1.0F);
mesh.draw(poseStack, buffer, renderType, packedLight, color.x(), color.y(), color.z(), color.w(), this.getOverlayCoord(entity, entitypatch, partialTicks), armature, armature.getPoseMatrices());
}
}
if (!entity.isSpectator()) {
this.renderLayer(renderer, entitypatch, entity, armature.getPoseMatrices(), buffer, poseStack, packedLight, partialTicks);
}
if (renderType != null) {
if (mc.getEntityRenderDispatcher().shouldRenderHitBoxes() && entitypatch.getClientAnimator() != null) {
entitypatch.getClientAnimator().renderDebuggingInfoForAllLayers(poseStack, buffer, partialTicks);
}
}
poseStack.popPose();
}
protected void prepareVanillaModel(E entity, M model, LivingEntityRenderer<E, M> renderer, float partialTicks) {
boolean shouldSit = entity.isPassenger() && (entity.getVehicle() != null && entity.getVehicle().shouldRiderSit());
model.riding = shouldSit;
model.young = entity.isBaby();
float f = Mth.rotLerp(partialTicks, entity.yBodyRotO, entity.yBodyRot);
float f1 = Mth.rotLerp(partialTicks, entity.yHeadRotO, entity.yHeadRot);
float f2 = f1 - f;
if (shouldSit && entity.getVehicle() instanceof LivingEntity livingentity) {
f = Mth.rotLerp(partialTicks, livingentity.yBodyRotO, livingentity.yBodyRot);
f2 = f1 - f;
float f3 = Mth.wrapDegrees(f2);
if (f3 < -85.0F) {
f3 = -85.0F;
}
if (f3 >= 85.0F) {
f3 = 85.0F;
}
f = f1 - f3;
if (f3 * f3 > 2500.0F) {
f += f3 * 0.2F;
}
f2 = f1 - f;
}
float f6 = Mth.lerp(partialTicks, entity.xRotO, entity.getXRot());
if (LivingEntityRenderer.isEntityUpsideDown(entity)) {
f6 *= -1.0F;
f2 *= -1.0F;
}
float f7 = ((MixinLivingEntityRenderer) renderer).invokeGetBob(entity, partialTicks);
float f8 = 0.0F;
float f5 = 0.0F;
if (!shouldSit && entity.isAlive()) {
f8 = entity.walkAnimation.speed(partialTicks);
f5 = entity.walkAnimation.position() - entity.walkAnimation.speed() * (1.0F - partialTicks);
if (entity.isBaby()) {
f5 *= 3.0F;
}
if (f8 > 1.0F) {
f8 = 1.0F;
}
}
model.prepareMobModel(entity, f5, f8, partialTicks);
model.setupAnim(entity, f5, f8, f7, f2, f6);
}
protected void prepareModel(AM mesh, E entity, T entitypatch, R renderer) {
mesh.initialize();
}
@SuppressWarnings("unchecked")
protected void renderLayer(LivingEntityRenderer<E, M> renderer, T entitypatch, E entity, OpenMatrix4f[] poses, MultiBufferSource buffer, PoseStack poseStack, int packedLight, float partialTicks) {
float f = MathUtils.lerpBetween(entity.yBodyRotO, entity.yBodyRot, partialTicks);
float f1 = MathUtils.lerpBetween(entity.yHeadRotO, entity.yHeadRot, partialTicks);
float f2 = f1 - f;
float f7 = entity.getViewXRot(partialTicks);
float bob = ((MixinLivingEntityRenderer) renderer).invokeGetBob(entity, partialTicks);
for (RenderLayer<E, M> layer : renderer.layers) {
Class<?> layerClass = layer.getClass();
if (layerClass.isAnonymousClass()) {
layerClass = layerClass.getSuperclass();
}
if (this.patchedLayers.containsKey(layerClass)) {
@SuppressWarnings("rawtypes")
PatchedLayer patchedLayer = this.patchedLayers.get(layerClass);
patchedLayer.renderLayer(entity, entitypatch, layer, poseStack, buffer, packedLight, poses, bob, f2, f7, partialTicks);
}
}
for (PatchedLayer<E, T, M, ? extends RenderLayer<E, M>> patchedLayer : this.customLayers) {
@SuppressWarnings("rawtypes")
PatchedLayer raw = patchedLayer;
raw.renderLayer(entity, entitypatch, null, poseStack, buffer, packedLight, poses, bob, f2, f7, partialTicks);
}
}
protected int getOverlayCoord(E entity, T entitypatch, float partialTicks) {
// RIG Phase 2.5 : EntityDecorations.modifyOverlay strippé — utilise
// les defaults vanilla (hurt flash + death fade).
int initU = 0;
int initV = OverlayTexture.v(entity.hurtTime > 0 || entity.deathTime > 0);
return OverlayTexture.pack(initU, initV);
}
@Override
public void mulPoseStack(PoseStack poseStack, Armature armature, E entity, T entitypatch, float partialTicks) {
super.mulPoseStack(poseStack, armature, entity, entitypatch, partialTicks);
if (entity.isCrouching()) {
poseStack.translate(0.0D, 0.15D, 0.0D);
}
}
public void addPatchedLayer(Class<?> originalLayerClass, PatchedLayer<E, T, M, ? extends RenderLayer<E, M>> patchedLayer) {
this.patchedLayers.putIfAbsent(originalLayerClass, patchedLayer);
}
public void addCustomLayer(PatchedLayer<E, T, M, ? extends RenderLayer<E, M>> patchedLayer) {
this.customLayers.add(patchedLayer);
}
protected float getDefaultLayerHeightCorrection() {
return 1.15F;
}
}

View File

@@ -0,0 +1,97 @@
/*
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
* by the Epic Fight Team, licensed under GPLv3.
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
*/
package com.tiedup.remake.rig.render;
import net.minecraft.client.model.PlayerModel;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.client.renderer.entity.player.PlayerRenderer;
import net.minecraft.world.entity.EntityType;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import com.tiedup.remake.rig.asset.AssetAccessor;
import com.tiedup.remake.rig.mesh.HumanoidMesh;
import com.tiedup.remake.rig.mesh.Meshes;
import com.tiedup.remake.rig.mesh.SkinnedMesh;
import com.tiedup.remake.rig.patch.ClientPlayerPatch;
/**
* Renderer RIG patché pour les joueurs (local + remote).
*
* <p>Dispatch slim vs default via {@link #getMeshProvider} — suit la règle
* vanilla {@code entity.getModelName().equals("slim")} → Alex mesh, sinon
* Steve/biped mesh.</p>
*
* <p>Override de {@link #prepareModel} pour propager les flags de visibilité
* des {@link PlayerModel#head head / hat / jacket / sleeves / pants} vers les
* {@link HumanoidMesh} parts correspondants — nécessaire pour que les
* toggles client MC ("Cape", "Left Sleeve" etc.) fonctionnent avec le rendu
* RIG.</p>
*
* <p><b>Layers skipés vs EF</b> (Phase 2.5, à rework Phase 3) :</p>
* <ul>
* <li>{@code ArrowLayer} — flèches plantées dans le player (V3-REW-09)</li>
* <li>{@code BeeStingerLayer} — dards d'abeille plantés (V3-REW-09)</li>
* <li>{@code CapeLayer} — cape vanilla (V3-REW-08, compat skin layers)</li>
* <li>{@code PlayerItemInHandLayer} — items en main (version player-spécifique,
* distincte de {@link net.minecraft.client.renderer.entity.layers.ItemInHandLayer}) —
* le layer vanilla continue de dessiner jusqu'à la Phase 3</li>
* </ul>
*
* <p>Fork conceptuel de {@code yesman.epicfight.client.renderer.patched.entity.PPlayerRenderer}
* (EF 60 LOC) — signatures identiques mais sans patched layers combat EF.</p>
*/
@OnlyIn(Dist.CLIENT)
public class TiedUpPlayerRenderer extends PHumanoidRenderer<AbstractClientPlayer, ClientPlayerPatch<AbstractClientPlayer>, PlayerModel<AbstractClientPlayer>, PlayerRenderer, HumanoidMesh> {
public TiedUpPlayerRenderer(EntityRendererProvider.Context context, EntityType<?> entityType) {
super(Meshes.BIPED, context, entityType);
}
@Override
protected void prepareModel(HumanoidMesh mesh, AbstractClientPlayer entity, ClientPlayerPatch<AbstractClientPlayer> entitypatch, PlayerRenderer renderer) {
super.prepareModel(mesh, entity, entitypatch, renderer);
// Renderer vanilla : lit les player options (modelParts toggle UI)
// pour mettre à jour model.{head,hat,jacket,leftArm,...}.visible.
renderer.setModelProperties(entity);
PlayerModel<AbstractClientPlayer> model = renderer.getModel();
// Propage vers le mesh RIG : chaque part peut être hidden indépendamment.
// HumanoidMesh peut avoir des parts null (legacy biped sans sleeves) —
// on protège, sinon NPE au runtime sur les meshes partiels.
setHiddenSafe(mesh.head, !model.head.visible);
setHiddenSafe(mesh.hat, !model.hat.visible);
setHiddenSafe(mesh.jacket, !model.jacket.visible);
setHiddenSafe(mesh.torso, !model.body.visible);
setHiddenSafe(mesh.leftArm, !model.leftArm.visible);
setHiddenSafe(mesh.leftLeg, !model.leftLeg.visible);
setHiddenSafe(mesh.leftPants, !model.leftPants.visible);
setHiddenSafe(mesh.leftSleeve, !model.leftSleeve.visible);
setHiddenSafe(mesh.rightArm, !model.rightArm.visible);
setHiddenSafe(mesh.rightLeg, !model.rightLeg.visible);
setHiddenSafe(mesh.rightPants, !model.rightPants.visible);
setHiddenSafe(mesh.rightSleeve, !model.rightSleeve.visible);
}
private static void setHiddenSafe(SkinnedMesh.SkinnedMeshPart part, boolean hidden) {
if (part != null) {
part.setHidden(hidden);
}
}
@Override
public AssetAccessor<HumanoidMesh> getMeshProvider(ClientPlayerPatch<AbstractClientPlayer> entitypatch) {
return entitypatch.getOriginal().getModelName().equals("slim") ? Meshes.ALEX : Meshes.BIPED;
}
@Override
public AssetAccessor<HumanoidMesh> getDefaultMesh() {
return Meshes.BIPED;
}
}

View File

@@ -6,7 +6,9 @@
"refmap": "tiedup-rig.refmap.json",
"mixins": [],
"client": [
"client.MixinAgeableListModel"
"client.MixinAgeableListModel",
"client.MixinEntityRenderer",
"client.MixinLivingEntityRenderer"
],
"injectors": {
"defaultRequire": 1