Phase 2.4 : étoffer PlayerPatch stubs + biped armature procédurale

PlayerPatch : getArmature→TiedUpArmatures.BIPED, overrideRender→true,
getModelMatrix avec PLAYER_SCALE=15/16, initAnimator bind IDLE→EMPTY.
ServerPlayerPatch : ré-override overrideRender→false (le serveur ne rend pas).
ClientPlayerPatch : stub isFirstPersonHidden() pour Phase 3 (V3-REW-01).
LocalPlayerPatch : override render→false si on est soi-même en first-person
(laisse rendre les bras vanilla), true sinon.

TiedUpArmatures.BIPED : HumanoidArmature procédurale 20 joints identity
(Root → Thigh/Leg/Knee_R/L, Torso → Chest → Head + Shoulder/Arm/Elbow/
Hand/Tool_R/L). Phase 2.7 remplacera par JSON Blender-authored.

Fixe P2-RISK-01 (InitAnimatorEvent listeners NPE si getArmature()=null).

Tests : 11 bridge tests GREEN, full suite GREEN, compile clean.
This commit is contained in:
notevil
2026-04-22 22:12:44 +02:00
parent ccec6bd87e
commit 79fc470aa0
5 changed files with 450 additions and 79 deletions

View File

@@ -0,0 +1,193 @@
/*
* 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;
import java.util.LinkedHashMap;
import java.util.Map;
import net.minecraft.resources.ResourceLocation;
import com.tiedup.remake.rig.armature.HumanoidArmature;
import com.tiedup.remake.rig.armature.Joint;
import com.tiedup.remake.rig.asset.AssetAccessor;
import com.tiedup.remake.rig.math.OpenMatrix4f;
/**
* Registry des armatures TiedUp exposé via {@link AssetAccessor} constants.
*
* <h2>Phase 2.4 — version procédurale</h2>
*
* <p>Cette classe construit le biped TiedUp <b>from scratch en Java</b>
* (hiérarchie + offsets identity). Suffisant pour débloquer le rendering RIG
* Phase 2.4 : les joints existent dans le map, {@code searchJointByName}
* fonctionne, le GLB → SkinnedMesh bridge a un mapping valide, etc.</p>
*
* <p><b>Phase 2.7 remplacera par un JSON Blender-authored hot-reloadable</b>.
* Pour l'instant, les joints sont tous à l'identité (offset/rotation nuls).
* Visuellement ça donnera un biped "effondré" sur le point d'origine si on
* rend sans animation — c'est acceptable car :</p>
* <ul>
* <li>Phase 2.4 n'a pas encore de renderer player patched complet (Phase 2.5)</li>
* <li>Phase 2.7 rechargera des offsets depuis {@code assets/tiedup/armatures/biped.json}
* co-authored via addon Blender (cf. MIGRATION.md §2.2.1)</li>
* <li>Les tests existants `GltfToSkinnedMeshTest` utilisent déjà le même pattern
* (Armature identity, {@code bakeOriginMatrices}) et sont verts</li>
* </ul>
*
* <h2>Hiérarchie biped EF (20 joints)</h2>
*
* <pre>
* Root
* ├─ Thigh_R ── Leg_R ── Knee_R
* ├─ Thigh_L ── Leg_L ── Knee_L
* └─ Torso
* └─ Chest
* ├─ Head
* ├─ Shoulder_R ── Arm_R ── Elbow_R ── Hand_R ── Tool_R
* └─ Shoulder_L ── Arm_L ── Elbow_L ── Hand_L ── Tool_L
* </pre>
*
* <p><b>Noms conservés verbatim EF</b> (pas renommés en TiedUp style) car :</p>
* <ul>
* <li>Le {@code VanillaModelTransformer} forké EF (Phase 2.2) référence ces
* noms dans ses AABB / {@code WEIGHT_ALONG_Y} / {@code yClipCoord}</li>
* <li>Le bridge GLB ({@code LegacyJointNameMapper}) mappe déjà les joints
* PlayerAnimator legacy sur ces noms-là</li>
* <li>Re-authored serait un risque régression sans gain fonctionnel</li>
* </ul>
*/
public final class TiedUpArmatures {
private TiedUpArmatures() {}
/** ResourceLocation registry pour l'accessor (même path que EF pour cohérence doc). */
private static final ResourceLocation BIPED_REGISTRY_NAME =
ResourceLocation.fromNamespaceAndPath(TiedUpRigConstants.MODID, "armature/biped");
/**
* Singleton biped — chargé à la première ref (init order :
* {@link TiedUpRigConstants} → chaînes d'ids → ici via
* {@link com.tiedup.remake.rig.patch.PlayerPatch#getArmature()}).
*
* <p>Si construit plus tôt (ex. static init combat-lourd avant FMLCommonSetup)
* risquerait de toucher des classes client — garder cet accès lazy si les
* call sites évoluent.</p>
*/
private static HumanoidArmature BIPED_INSTANCE;
/**
* AssetAccessor biped biped TiedUp. Construit à la première lecture de
* {@link AssetAccessor#get()}.
*
* <p>Utilisé par {@link com.tiedup.remake.rig.patch.PlayerPatch#getArmature()}
* et par les futurs {@code StaticAnimation(… , BIPED)} Phase 2.7+.</p>
*/
public static final AssetAccessor<HumanoidArmature> BIPED = new AssetAccessor<>() {
@Override
public HumanoidArmature get() {
if (BIPED_INSTANCE == null) {
BIPED_INSTANCE = buildBiped();
}
return BIPED_INSTANCE;
}
@Override
public ResourceLocation registryName() {
return BIPED_REGISTRY_NAME;
}
@Override
public boolean inRegistry() {
// Pas dans un JsonAssetLoader registry tant que Phase 2.7 n'a pas
// posé le biped.json. Une fois fait, ce flag repassera à true via
// un nouveau registry layer.
return false;
}
};
/**
* Build procédural de la hiérarchie biped EF. 20 joints, IDs 0..19 dans
* l'ordre d'insertion (important pour {@link Joint#getId()} stabilité
* inter-runs).
*
* <p>Toutes les transforms sont identity — Phase 2.7 remplacera par les
* offsets Blender mesurés (cf. doc header).</p>
*/
private static HumanoidArmature buildBiped() {
// Tous les joints (ID == ordre d'insertion dans la map).
// On utilise LinkedHashMap pour garantir l'ordre d'itération stable
// (OpenMatrix4f.allocateMatrixArray dimensionne sur jointCount).
Map<String, Joint> joints = new LinkedHashMap<>(20);
// Pattern EF : tous les joints démarrent avec une localTransform identity.
// bakeOriginMatrices() calcule ensuite les toOrigin relatives à la
// hiérarchie parent→enfant.
Joint root = joint(joints, "Root", 0);
// Jambes
Joint thighR = joint(joints, "Thigh_R", 1);
Joint legR = joint(joints, "Leg_R", 2);
Joint kneeR = joint(joints, "Knee_R", 3);
Joint thighL = joint(joints, "Thigh_L", 4);
Joint legL = joint(joints, "Leg_L", 5);
Joint kneeL = joint(joints, "Knee_L", 6);
// Tronc
Joint torso = joint(joints, "Torso", 7);
Joint chest = joint(joints, "Chest", 8);
Joint head = joint(joints, "Head", 9);
// Bras droit
Joint shoulderR = joint(joints, "Shoulder_R", 10);
Joint armR = joint(joints, "Arm_R", 11);
Joint elbowR = joint(joints, "Elbow_R", 12);
Joint handR = joint(joints, "Hand_R", 13);
Joint toolR = joint(joints, "Tool_R", 14);
// Bras gauche
Joint shoulderL = joint(joints, "Shoulder_L", 15);
Joint armL = joint(joints, "Arm_L", 16);
Joint elbowL = joint(joints, "Elbow_L", 17);
Joint handL = joint(joints, "Hand_L", 18);
Joint toolL = joint(joints, "Tool_L", 19);
// Hiérarchie. addSubJoints est idempotent (skip si déjà présent) — safe
// de le réappeler, utile si on étend plus tard.
root.addSubJoints(thighR, thighL, torso);
thighR.addSubJoints(legR);
legR.addSubJoints(kneeR);
thighL.addSubJoints(legL);
legL.addSubJoints(kneeL);
torso.addSubJoints(chest);
chest.addSubJoints(head, shoulderR, shoulderL);
shoulderR.addSubJoints(armR);
armR.addSubJoints(elbowR);
elbowR.addSubJoints(handR);
handR.addSubJoints(toolR);
shoulderL.addSubJoints(armL);
armL.addSubJoints(elbowL);
elbowL.addSubJoints(handL);
handL.addSubJoints(toolL);
HumanoidArmature arm = new HumanoidArmature("biped", joints.size(), root, joints);
// Calcule les toOrigin relatifs — obligatoire après la construction
// sinon Pose.orElseEmpty retournerait des matrices non initialisées.
arm.bakeOriginMatrices();
return arm;
}
private static Joint joint(Map<String, Joint> target, String name, int id) {
Joint j = new Joint(name, id, new OpenMatrix4f());
target.put(name, j);
return j;
}
}

View File

@@ -10,45 +10,50 @@ import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import com.tiedup.remake.rig.armature.Armature;
import com.tiedup.remake.rig.math.OpenMatrix4f;
/**
* Stub Phase 2.3 — patch côté client pour les joueurs remote (autres joueurs
* dans une session multi). Etoffé Phase 2.4 avec la logique cape / scale /
* slim vs default model selection.
* Patch côté client pour les joueurs remote (autres joueurs en session multi).
*
* <p>Version minimale : {@code overrideRender=true} (on veut que le renderer
* patched intercepte et dispatch au RIG), {@code getArmature=null} stub (TODO
* Phase 2.4), {@code updateMotion} no-op (Phase 2.7).</p>
* <p>Hérite tout de {@link PlayerPatch} :
* {@link #overrideRender()} = true, armature biped, updateMotion stub.
* Pour un remote player on veut systématiquement le rendering RIG — pas de
* cas où on voudrait le modèle vanilla MC (qui perdrait les items bondage
* RIG-attached Phase 3).</p>
*
* <p><b>Différence avec {@link LocalPlayerPatch}</b> : ce dernier override
* {@link #overrideRender()} pour désactiver le render RIG en first-person
* (sinon on verrait l'intérieur du mesh). Les remote players ne sont jamais
* en first-person du point de vue de l'observer → pas besoin.</p>
*
* <p>Forke conceptuellement {@code yesman.epicfight.client.world.capabilites.entitypatch.player.AbstractClientPlayerPatch}
* (EF 479 LOC) mais réécrit from scratch (D-04) car ~80% du contenu original
* est combat/skill non réutilisable pour TiedUp.</p>
* est combat/skill non réutilisable pour TiedUp (slim/default model switch,
* cape handling, skin tick — tout ça pourrait être utile Phase 5 si on
* restaure le rendering custom skin layer).</p>
*
* @param <T> concrete type (remote player, et fallback pour LocalPlayer via
* {@link LocalPlayerPatch})
*/
@OnlyIn(Dist.CLIENT)
public class ClientPlayerPatch<T extends AbstractClientPlayer> extends PlayerPatch<T> {
@Override
public void updateMotion(boolean considerInaction) {
// no-op stub — Phase 2.7
}
@Override
public Armature getArmature() {
// TODO Phase 2.4 — retourner TiedUpRigRegistry.BIPED.get()
return null;
}
@Override
public boolean overrideRender() {
// On veut que le render pipeline patched prenne la main pour les
// remote players visibles.
return true;
}
@Override
public OpenMatrix4f getModelMatrix(float partialTick) {
return getMatrix(partialTick);
}
/**
* Placeholder pour la logique "cacher le player en first-person pour les
* items de type wrap / latex_sack / cape-over-head" (V3-REW-01, item
* rework Phase 3).
*
* <p>Actuellement retourne toujours {@code false} — aucun item ne demande
* ce hide. Sera re-implémenté Phase 3 quand le
* {@code PlayerArmHideEventHandler} V2 sera porté en jointMask-based
* logic côté StaticAnimation (cf.
* {@code docs/plans/rig/V3_REWORK_BACKLOG.md} V3-REW-01).</p>
*
* <p>Appelé depuis le renderer patched Phase 2.5+ : si true, skip le
* draw du mesh player (mais draw les items bondage attachés).</p>
*
* @return true si le player doit être invisible en first-person
*/
public boolean isFirstPersonHidden() {
// TODO Phase 3 (V3-REW-01) : check equipped items pour wrap / latex_sack.
return false;
}
}

View File

@@ -6,25 +6,61 @@
package com.tiedup.remake.rig.patch;
import net.minecraft.client.Minecraft;
import net.minecraft.client.player.LocalPlayer;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
/**
* Stub Phase 2.3 — patch spécifique au joueur local (self). Spécialisation de
* {@link ClientPlayerPatch} pour les hooks first-person / caméra / input
* capture bondage-specific (struggle keys, adjustment menu).
* Patch spécifique au joueur local (self). Override
* {@link #overrideRender()} pour gérer le cas first-person :
*
* <p>Version minimale : hérite de {@link ClientPlayerPatch} sans override.
* Phase 2.4 ajoutera les hooks first-person hide (arms/hands invisibles sous
* wrap/latex_sack), camera sync leash, etc.</p>
* <ul>
* <li><b>First-person (F5 = first)</b> : retourne {@code false} — on laisse
* le rendu vanilla Minecraft prendre la main (bras en FP, item held,
* overlay mains). Rendre le biped TiedUp en FP donnerait une vue
* depuis l'intérieur du mesh, illisible.</li>
* <li><b>Third-person (F5 = third, either view)</b> ou tout autre cas :
* retourne {@code true} — le renderer RIG patched prend la main et
* dessine le biped TiedUp comme pour les autres players.</li>
* </ul>
*
* <p>Forke conceptuellement {@code yesman.epicfight.client.world.capabilites.entitypatch.player.LocalPlayerPatch}
* (EF 572 LOC) mais réécrit from scratch (D-04) car skill UI state / input /
* camera cinematics non réutilisables.</p>
* <p><b>Pourquoi la check {@code entity == Minecraft.getInstance().player}</b> :
* {@link LocalPlayerPatch} est attaché uniquement au LocalPlayer (cf.
* {@link EntityPatchProvider#registerEntityPatchesClient()}), donc en pratique
* {@code this.original == Minecraft.getInstance().player} toujours. Le check
* est une ceinture-bretelles au cas où le dispatcher change un jour pour
* attacher ce patch à autre chose (très improbable).</p>
*
* <p>Pattern EF équivalent : {@code LocalPlayerPatch.java:143-152} —
* même logique mais EF ajoute un toggle {@code ClientConfig.enableAnimatedFirstPersonModel}
* pour ceux qui veulent le biped rendu même en FP. TiedUp peut l'ajouter
* plus tard si demande gameplay, mais par défaut FP = vanilla.</p>
*
* <p>Hooks first-person hide specific bondage items (wrap / latex_sack /
* blindfold = vision obstruée) → Phase 3 via
* {@link ClientPlayerPatch#isFirstPersonHidden()} (V3-REW-01).</p>
*/
@OnlyIn(Dist.CLIENT)
public class LocalPlayerPatch extends ClientPlayerPatch<LocalPlayer> {
// Hooks first-person / caméra / input → Phase 2.4
/**
* @return {@code false} si on est en first-person sur soi-même (laisse
* rendre le vanilla), {@code true} dans tous les autres cas.
*/
@Override
public boolean overrideRender() {
Minecraft mc = Minecraft.getInstance();
// mc.player peut être null durant un rejoin (~1 tick entre
// disconnect et respawn). Dans ce cas pas de FP à gérer, on tombe
// sur le comportement parent (true).
if (mc.player != null && this.original == mc.player) {
if (mc.options.getCameraType().isFirstPerson()) {
return false;
}
}
return super.overrideRender();
}
}

View File

@@ -8,10 +8,151 @@ package com.tiedup.remake.rig.patch;
import net.minecraft.world.entity.player.Player;
import com.tiedup.remake.rig.TiedUpArmatures;
import com.tiedup.remake.rig.TiedUpRigRegistry;
import com.tiedup.remake.rig.anim.Animator;
import com.tiedup.remake.rig.anim.LivingMotions;
import com.tiedup.remake.rig.armature.HumanoidArmature;
import com.tiedup.remake.rig.math.MathUtils;
import com.tiedup.remake.rig.math.OpenMatrix4f;
/**
* Stub RIG Phase 0 — patch de capability attaché à un {@link Player}.
* Re-implém complète Phase 2 (séparation ClientPlayerPatch / ServerPlayerPatch,
* input handling, first-person, cam sync).
* RIG Phase 2.4 — patch de capability attaché à un {@link Player}.
*
* <p>Étoffé à partir du stub Phase 0 pour :</p>
* <ul>
* <li>Fournir l'{@link #getArmature() armature biped} TiedUp
* ({@link TiedUpArmatures#BIPED})</li>
* <li>Activer le rendering RIG patched ({@link #overrideRender()} = true)
* — {@link ServerPlayerPatch} ré-override à false et
* {@link LocalPlayerPatch} gère le cas first-person</li>
* <li>Fournir un {@link #getModelMatrix(float)} avec scale player vanilla
* ({@value #PLAYER_SCALE}) — cf. EF {@code PlayerPatch:82,176}</li>
* <li>Stub {@link #initAnimator(Animator)} qui bind
* {@code LivingMotions.IDLE → EMPTY_ANIMATION} (Phase 2.7 remplacera par
* {@code CONTEXT_STAND_IDLE} co-authored)</li>
* </ul>
*
* <p><b>Ce qui manque vs EF PlayerPatch (766 LOC)</b>, voulu :</p>
* <ul>
* <li>Stamina system ({@code STAMINA} EntityDataAccessor + regen logic)</li>
* <li>PlayerMode VANILLA/EPICFIGHT toggle</li>
* <li>modelYRot sync / turningLocked handling</li>
* <li>xo/yo/zo previous pos caching</li>
* <li>PlayerEventListener — remplacé par SystemMessageManager/events TiedUp</li>
* <li>Skill system entièrement</li>
* <li>Toute la logique attaque / damage / offhand / armor negation</li>
* </ul>
*
* <p>TiedUp n'a pas de combat — ces features seraient dead code. Si on en
* veut une (ex. stamina pour struggle), on l'ajoutera au cas par cas (D-04).</p>
*/
public abstract class PlayerPatch<T extends Player> extends LivingEntityPatch<T> {
/**
* Scale vanilla player EF-identique (15/16 ≈ 0.9375). Le modèle vanilla
* Minecraft rend le joueur à 15/16 de sa taille logique — si on ne
* compense pas, le skinned mesh apparaît "trop grand" comparé à la hitbox.
*
* <p>Source : EF {@code PlayerPatch:82}.</p>
*/
protected static final float PLAYER_SCALE = 15.0F / 16.0F;
/**
* Override — stub sans transition / sans vélocité.
*
* <p>Phase 2.7+ implémentera la détection de motion (walk/run/sneak/sit/
* swim/kneel pour TiedUp) via vélocité + state checks. Ici on ne fait
* rien pour que les {@link #currentLivingMotion} restent à IDLE par
* défaut (cf. {@link LivingEntityPatch}).</p>
*
* @param considerInaction paramètre EF legacy — si true, l'impl complète
* doit bloquer la transition de motion si l'entité
* est dans une {@code inaction()}. Pas utilisé ici.
*/
@Override
public void updateMotion(boolean considerInaction) {
// Stub Phase 2.4 — pas de motion detection.
// Phase 2.7 : cf. EF LivingEntityPatch.updateMotion (velocity + state
// machine) + mapping sur LivingMotions TiedUp (idle/walk/kneel/sit).
}
/**
* Retourne l'armature biped TiedUp — identique pour tous les players.
*
* <p>Phase 2.4 : version procédurale ({@link TiedUpArmatures#BIPED}
* construit from scratch). Phase 2.7 remplacera par un chargement JSON
* avec offsets Blender-authored.</p>
*
* <p>Pattern EF :
* {@code AbstractClientPlayerPatch.java:~70} expose {@code this.armature}
* initialisé au constructor. On renvoie directement le singleton — léger
* et thread-safe (construction lazy dans {@link TiedUpArmatures#BIPED}).</p>
*/
@Override
public HumanoidArmature getArmature() {
return TiedUpArmatures.BIPED.get();
}
/**
* Override — tous les players passent par le renderer RIG patched.
*
* <p>{@link ServerPlayerPatch} ré-override à false (le serveur ne rend
* rien). {@link LocalPlayerPatch} ré-override pour gérer le cas
* first-person (cf. doc là-bas).</p>
*/
@Override
public boolean overrideRender() {
return true;
}
/**
* Model matrix pour le render : position identity (le renderer applique
* déjà la translation) + rotation yaw (le corps suit la caméra) + scale
* {@value #PLAYER_SCALE}.
*
* <p>Simplifié vs EF : pas de handling {@code modelYRot} (self-managed
* rotation lerp pour turning lock) et pas de {@code ridingEntity} override
* — Phase 2.7+ si on ajoute un système de mount TiedUp (maid pet sitting
* etc.).</p>
*
* <p>Baby support (half scale) — EF applique 0.5 si {@code isBaby}. On
* garde pour cohérence (les NPCs Damsel n'ont pas d'état baby mais
* VillagerMCA si un jour on les patch le veut).</p>
*/
@Override
public OpenMatrix4f getModelMatrix(float partialTick) {
float scale = (this.original.isBaby() ? 0.5F : 1.0F) * PLAYER_SCALE;
// Signature : xPosO, xPos, yPosO, yPos, zPosO, zPos, xRotO, xRot,
// yRotO, yRot, partialTick, scaleX, scaleY, scaleZ
// On ignore pos/xRot (delta pose) — l'entity renderer applique déjà
// la position monde. Seul le yRot (yaw corps, pas tête) est utile
// pour que le mesh s'oriente avec l'entity.
return MathUtils.getModelMatrixIntegral(
0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F,
0.0F, 0.0F,
this.original.yBodyRotO, this.original.yBodyRot,
partialTick, scale, scale, scale
);
}
/**
* Hook {@link LivingEntityPatch#initAnimator(Animator)} : bind la motion
* IDLE sur l'animation "ne fait rien" par défaut. Phase 2.7 remplacera
* par {@code TiedUpAnimationRegistry.CONTEXT_STAND_IDLE} avec quelques
* keyframes de balancement.
*
* <p>L'ordre EF ajoute ~25 motions différentes (WALK, RUN, SNEAK, SIT,
* SLEEP, etc.). On se limite à IDLE Phase 2.4 — ajouter les autres
* sans anim source = pollution registre pour rien. Au fur et à mesure
* que les JSON co-authored arrivent (Phase 2.7 / 4), on ajoute les
* binds correspondants.</p>
*/
@Override
protected void initAnimator(Animator animator) {
super.initAnimator(animator);
animator.addLivingAnimation(LivingMotions.IDLE, TiedUpRigRegistry.EMPTY_ANIMATION);
}
}

View File

@@ -8,45 +8,41 @@ package com.tiedup.remake.rig.patch;
import net.minecraft.server.level.ServerPlayer;
import com.tiedup.remake.rig.armature.Armature;
import com.tiedup.remake.rig.math.OpenMatrix4f;
/**
* Stub Phase 2.3 — patch côté serveur pour les joueurs. Version minimale pour
* débloquer le dispatch via {@link EntityPatchProvider}. La version complète
* (kidnap state sync, struggle progression, ownership) est Phase 2.4+.
* Patch côté serveur pour les joueurs. Hérite tout de {@link PlayerPatch} —
* on ne garde qu'un seul override : {@link #overrideRender()} forcé à
* {@code false}.
*
* <p>Garanties actuelles :</p>
* <p><b>Pourquoi override render → false</b> : le serveur ne rend rien
* visuellement, mais l'animator continue de tourner pour calculer la pose
* "aveugle" (pos joints à un tick donné). Cette pose sert au tracker serveur
* (combat hitboxes EF, hook AI TiedUp). {@link PlayerPatch#overrideRender()}
* retourne {@code true} par défaut → si on oubliait l'override, un dispatcher
* de rendering théorique côté serveur (il n'y en a pas actuellement, mais
* l'API publique le permet) se déclencherait à tort.</p>
*
* <p><b>Ce qui est dans PlayerPatch et hérité ici</b> :</p>
* <ul>
* <li>{@code overrideRender() = false} — serveur ne rend rien, pas de besoin
* d'intercepter le render pipeline</li>
* <li>{@code getArmature() = null} — le serveur n'a pas besoin de mesh data,
* il joue des animations "blind" (calcule la pose pour syncer aux clients).
* Sera fixé en Phase 2.4 avec un fallback vers {@code TiedUpRigRegistry.BIPED}.</li>
* <li>{@code updateMotion} no-op — Phase 2.7 hook tick réel</li>
* <li>{@code getArmature()} → {@link com.tiedup.remake.rig.TiedUpArmatures#BIPED}</li>
* <li>{@code getModelMatrix(partialTick)} — inutile côté serveur mais pas de
* coût à le garder (pas appelé)</li>
* <li>{@code updateMotion(boolean)} no-op Phase 2.4</li>
* <li>{@code initAnimator} bind IDLE → EMPTY_ANIMATION</li>
* </ul>
*
* <p>Phase 2.7+ : {@code serverTick} override pour poser motion + sync aux
* clients via packet (cf. EF {@code PlayerPatch.serverTick}). Phase 3+ :
* hooks struggle / kidnap state ici.</p>
*/
public class ServerPlayerPatch extends PlayerPatch<ServerPlayer> {
@Override
public void updateMotion(boolean considerInaction) {
// no-op stub — Phase 2.7
}
@Override
public Armature getArmature() {
// TODO Phase 2.4 — retourner TiedUpRigRegistry.BIPED.get()
return null;
}
@Override
public boolean overrideRender() {
// Serveur ne rend rien.
return false;
}
@Override
public OpenMatrix4f getModelMatrix(float partialTick) {
return getMatrix(partialTick);
}
/**
* Override à false — le serveur n'intercepte jamais le render (il ne rend
* pas). Voir doc classe pour pourquoi c'est important malgré l'absence
* de renderer serveur actuel.
*/
@Override
public boolean overrideRender() {
return false;
}
}