From 8dff4c0e030a93a273c4a483fb8c0fcdbd7269d9 Mon Sep 17 00:00:00 2001 From: notevil Date: Wed, 22 Apr 2026 22:39:41 +0200 Subject: [PATCH] WIP Phase 2.5 : fork render pipeline (PatchedEntityRenderer family) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 & 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) --- .../rig/mixin/client/MixinEntityRenderer.java | 36 +++ .../client/MixinLivingEntityRenderer.java | 36 +++ .../remake/rig/render/PHumanoidRenderer.java | 92 +++++++ .../remake/rig/render/PatchedArmorLayer.java | 38 +++ .../rig/render/PatchedEntityRenderer.java | 88 +++++- .../rig/render/PatchedItemInHandLayer.java | 37 +++ .../remake/rig/render/PatchedLayer.java | 58 ++++ .../render/PatchedLivingEntityRenderer.java | 255 ++++++++++++++++++ .../rig/render/TiedUpPlayerRenderer.java | 97 +++++++ src/main/resources/tiedup-rig.mixins.json | 4 +- 10 files changed, 736 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/tiedup/remake/rig/mixin/client/MixinEntityRenderer.java create mode 100644 src/main/java/com/tiedup/remake/rig/mixin/client/MixinLivingEntityRenderer.java create mode 100644 src/main/java/com/tiedup/remake/rig/render/PHumanoidRenderer.java create mode 100644 src/main/java/com/tiedup/remake/rig/render/PatchedArmorLayer.java create mode 100644 src/main/java/com/tiedup/remake/rig/render/PatchedItemInHandLayer.java create mode 100644 src/main/java/com/tiedup/remake/rig/render/PatchedLayer.java create mode 100644 src/main/java/com/tiedup/remake/rig/render/PatchedLivingEntityRenderer.java create mode 100644 src/main/java/com/tiedup/remake/rig/render/TiedUpPlayerRenderer.java diff --git a/src/main/java/com/tiedup/remake/rig/mixin/client/MixinEntityRenderer.java b/src/main/java/com/tiedup/remake/rig/mixin/client/MixinEntityRenderer.java new file mode 100644 index 0000000..1f393c2 --- /dev/null +++ b/src/main/java/com/tiedup/remake/rig/mixin/client/MixinEntityRenderer.java @@ -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. + * + *

Pure @Invoker mixin — pas de logique injectée, équivalent EF de + * {@code yesman.epicfight.mixin.client.MixinEntityRenderer}.

+ */ +@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); +} diff --git a/src/main/java/com/tiedup/remake/rig/mixin/client/MixinLivingEntityRenderer.java b/src/main/java/com/tiedup/remake/rig/mixin/client/MixinLivingEntityRenderer.java new file mode 100644 index 0000000..e948191 --- /dev/null +++ b/src/main/java/com/tiedup/remake/rig/mixin/client/MixinLivingEntityRenderer.java @@ -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. + * + *

Pure @Invoker mixin — équivalent EF de + * {@code yesman.epicfight.mixin.client.MixinLivingEntityRenderer}.

+ */ +@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); +} diff --git a/src/main/java/com/tiedup/remake/rig/render/PHumanoidRenderer.java b/src/main/java/com/tiedup/remake/rig/render/PHumanoidRenderer.java new file mode 100644 index 0000000..d656efc --- /dev/null +++ b/src/main/java/com/tiedup/remake/rig/render/PHumanoidRenderer.java @@ -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 : + * + * + * + * @param entité humanoïde + * @param patch RIG associé + * @param HumanoidModel vanilla + * @param LivingEntityRenderer vanilla qui gère {@code M} + * @param mesh humanoïde (typiquement {@link HumanoidMesh} direct ou + * dérivé) + */ +@OnlyIn(Dist.CLIENT) +public class PHumanoidRenderer, M extends HumanoidModel, R extends LivingEntityRenderer, AM extends HumanoidMesh> extends PatchedLivingEntityRenderer { + + private final AssetAccessor mesh; + + public PHumanoidRenderer(AssetAccessor 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()); + this.addPatchedLayer(ItemInHandLayer.class, new PatchedItemInHandLayer()); + } + + @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 getDefaultMesh() { + return this.mesh; + } +} diff --git a/src/main/java/com/tiedup/remake/rig/render/PatchedArmorLayer.java b/src/main/java/com/tiedup/remake/rig/render/PatchedArmorLayer.java new file mode 100644 index 0000000..6b7688b --- /dev/null +++ b/src/main/java/com/tiedup/remake/rig/render/PatchedArmorLayer.java @@ -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. + * + *

Actuellement no-op : 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é.

+ * + *

Voir {@code docs/plans/rig/V3_REWORK_BACKLOG.md} V3-REW-04 et + * {@code PHASE0_DEGRADATIONS.md} S-03.

+ * + * @param entité vivante rigg-compatible + * @param patch RIG associé + * @param HumanoidModel vanilla + */ +@OnlyIn(Dist.CLIENT) +public class PatchedArmorLayer, M extends HumanoidModel> extends PatchedLayer> { + // renderLayer hérité = no-op (voir super). Phase 3 : wire armor bake via + // HumanoidMesh.getHumanoidArmorModel + WearableItemLayer-like drawing. +} diff --git a/src/main/java/com/tiedup/remake/rig/render/PatchedEntityRenderer.java b/src/main/java/com/tiedup/remake/rig/render/PatchedEntityRenderer.java index 0116073..681edb6 100644 --- a/src/main/java/com/tiedup/remake/rig/render/PatchedEntityRenderer.java +++ b/src/main/java/com/tiedup/remake/rig/render/PatchedEntityRenderer.java @@ -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}. + * + *

Fournit trois responsabilités :

+ *
    + *
  1. Render nameplate (via {@link RenderNameTagEvent} Forge + mixin invoker)
  2. + *
  3. Application de la model matrix entité (rotation 180° + yaw corps) au {@link PoseStack}
  4. + *
  5. Baking de la pose courante sur l'{@link Armature} (pour que + * {@code armature.getPoseMatrices()} soit à jour avant le draw du mesh)
  6. + *
+ * + *

Fork verbatim de {@code yesman.epicfight.client.renderer.patched.entity.PatchedEntityRenderer} + * (EF 62 LOC). Imports rewrités vers les packages RIG TiedUp.

+ * + * @param entité vivante ({@code LivingEntity}+) + * @param patch RIG associé + * @param renderer vanilla ({@code EntityRenderer}) + * @param mesh skinné dérivé */ -public abstract class PatchedEntityRenderer { +@OnlyIn(Dist.CLIENT) +public abstract class PatchedEntityRenderer, R extends EntityRenderer, 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 getMeshProvider(T entitypatch) { + return this.getDefaultMesh(); + } + + public abstract AssetAccessor getDefaultMesh(); + + /** + * Override pour modifier la pose juste avant le bake (ex. scale de la tête + * pour les babies). Developers shouldn't implement any interpolations in + * this method — utilise {@link LivingEntityPatch#poseTick} à la place. + */ + public void setJointTransforms(T entitypatch, Armature armature, Pose pose, float partialTicks) { + } } diff --git a/src/main/java/com/tiedup/remake/rig/render/PatchedItemInHandLayer.java b/src/main/java/com/tiedup/remake/rig/render/PatchedItemInHandLayer.java new file mode 100644 index 0000000..9a247a4 --- /dev/null +++ b/src/main/java/com/tiedup/remake/rig/render/PatchedItemInHandLayer.java @@ -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). + * + *

Actuellement no-op : 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.

+ * + * @param entité tenant un item + * @param patch RIG associé + * @param {@link EntityModel} qui implémente aussi {@link ArmedModel} + * (contrainte vanilla {@link ItemInHandLayer}) + */ +@OnlyIn(Dist.CLIENT) +public class PatchedItemInHandLayer, M extends EntityModel & ArmedModel> extends PatchedLayer> { + // 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. +} diff --git a/src/main/java/com/tiedup/remake/rig/render/PatchedLayer.java b/src/main/java/com/tiedup/remake/rig/render/PatchedLayer.java new file mode 100644 index 0000000..c08cd53 --- /dev/null +++ b/src/main/java/com/tiedup/remake/rig/render/PatchedLayer.java @@ -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}. + * + *

RIG Phase 2.5 : 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).

+ * + * @param entité vanilla + * @param patch RIG de {@code E} + * @param EntityModel vanilla + * @param RenderLayer vanilla concret (ex. {@code HumanoidArmorLayer}) + */ +@OnlyIn(Dist.CLIENT) +public abstract class PatchedLayer, M extends EntityModel, L extends RenderLayer> { + + /** + * 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 + } +} diff --git a/src/main/java/com/tiedup/remake/rig/render/PatchedLivingEntityRenderer.java b/src/main/java/com/tiedup/remake/rig/render/PatchedLivingEntityRenderer.java new file mode 100644 index 0000000..cb9fd4f --- /dev/null +++ b/src/main/java/com/tiedup/remake/rig/render/PatchedLivingEntityRenderer.java @@ -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. + * + *

Flow de {@link #render} :

+ *
    + *
  1. super.render : nameplate
  2. + *
  3. mulPoseStack : applique modelMatrix (rotation body + scale + upsideDown)
  4. + *
  5. 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
  6. + *
  7. setArmaturePose : bake la pose courante dans {@code armature.getPoseMatrices()}
  8. + *
  9. prepareModel : mesh.initialize() (détecte hidden parts)
  10. + *
  11. mesh.draw : dispatch vers shader compute ou CPU skinning via {@code drawPosed}
  12. + *
  13. renderLayer : itère les layers vanilla mappés vers des {@link PatchedLayer} (ex. + * armor, item-in-hand, cape) et les customLayers enregistrés manuellement
  14. + *
  15. Debug hitboxes si F3+B actif
  16. + *
+ * + *

Stripped vs EF source (volontaire, Phase 2.5) :

+ *
    + *
  • Chargement JSON datapack-driven des {@code animated_layers/{entity_type}.json} — + * TiedUp n'a pas de customLayers data-driven pour l'instant
  • + *
  • {@code LayerRenderer} interface (registre public des addPatchedLayer/addCustomLayer) — + * conservé en API mais pas exposé via interface dédiée
  • + *
  • {@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.
  • + *
+ * + * @param entité vivante + * @param patch RIG associé + * @param EntityModel vanilla + * @param renderer vanilla ({@code LivingEntityRenderer}) + * @param mesh skinné dérivé + */ +@OnlyIn(Dist.CLIENT) +public abstract class PatchedLivingEntityRenderer, M extends EntityModel, R extends LivingEntityRenderer, AM extends SkinnedMesh> extends PatchedEntityRenderer { + + protected final Map, PatchedLayer>> patchedLayers = Maps.newHashMap(); + protected final List>> 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 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 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 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> 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> patchedLayer) { + this.patchedLayers.putIfAbsent(originalLayerClass, patchedLayer); + } + + public void addCustomLayer(PatchedLayer> patchedLayer) { + this.customLayers.add(patchedLayer); + } + + protected float getDefaultLayerHeightCorrection() { + return 1.15F; + } +} diff --git a/src/main/java/com/tiedup/remake/rig/render/TiedUpPlayerRenderer.java b/src/main/java/com/tiedup/remake/rig/render/TiedUpPlayerRenderer.java new file mode 100644 index 0000000..310724a --- /dev/null +++ b/src/main/java/com/tiedup/remake/rig/render/TiedUpPlayerRenderer.java @@ -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). + * + *

Dispatch slim vs default via {@link #getMeshProvider} — suit la règle + * vanilla {@code entity.getModelName().equals("slim")} → Alex mesh, sinon + * Steve/biped mesh.

+ * + *

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.

+ * + *

Layers skipés vs EF (Phase 2.5, à rework Phase 3) :

+ *
    + *
  • {@code ArrowLayer} — flèches plantées dans le player (V3-REW-09)
  • + *
  • {@code BeeStingerLayer} — dards d'abeille plantés (V3-REW-09)
  • + *
  • {@code CapeLayer} — cape vanilla (V3-REW-08, compat skin layers)
  • + *
  • {@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
  • + *
+ * + *

Fork conceptuel de {@code yesman.epicfight.client.renderer.patched.entity.PPlayerRenderer} + * (EF 60 LOC) — signatures identiques mais sans patched layers combat EF.

+ */ +@OnlyIn(Dist.CLIENT) +public class TiedUpPlayerRenderer extends PHumanoidRenderer, PlayerModel, PlayerRenderer, HumanoidMesh> { + + public TiedUpPlayerRenderer(EntityRendererProvider.Context context, EntityType entityType) { + super(Meshes.BIPED, context, entityType); + } + + @Override + protected void prepareModel(HumanoidMesh mesh, AbstractClientPlayer entity, ClientPlayerPatch 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 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 getMeshProvider(ClientPlayerPatch entitypatch) { + return entitypatch.getOriginal().getModelName().equals("slim") ? Meshes.ALEX : Meshes.BIPED; + } + + @Override + public AssetAccessor getDefaultMesh() { + return Meshes.BIPED; + } +} diff --git a/src/main/resources/tiedup-rig.mixins.json b/src/main/resources/tiedup-rig.mixins.json index 3ca0e1c..c80ba1a 100644 --- a/src/main/resources/tiedup-rig.mixins.json +++ b/src/main/resources/tiedup-rig.mixins.json @@ -6,7 +6,9 @@ "refmap": "tiedup-rig.refmap.json", "mixins": [], "client": [ - "client.MixinAgeableListModel" + "client.MixinAgeableListModel", + "client.MixinEntityRenderer", + "client.MixinLivingEntityRenderer" ], "injectors": { "defaultRequire": 1