Phase 2.1 : D-01 fix — LivingEntityPatch animator field + eager init

Close le trap CRITIQUE tracé dans docs/plans/rig/PHASE0_DEGRADATIONS.md D-01
(getAnimator()=null → NPE garanti sur premier appel à MoveCoordFunctions:202,263).

Pattern EF-style conforme LivingEntityPatch EF:146-156 :
- protected Animator animator field
- Override onConstructed(T) : super + factory apply + initAnimator + postInit
- initAnimator(Animator) hook no-op pour subclasses (bind LivingMotion→Anim)
- getAnimator() retourne le field (non-null après onConstructed)
- getClientAnimator() : cast conditionnel instanceof ClientAnimator

Factory TiedUpRigConstants.ANIMATOR_PROVIDER (déjà en place, pattern lazy
method-ref client/server split) fournit la bonne instance selon Dist.

Compile + tests GREEN maintenus (11 tests bridge).
This commit is contained in:
notevil
2026-04-22 20:31:00 +02:00
parent 4d90a87b48
commit 4a587b7478

View File

@@ -13,6 +13,7 @@ import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.phys.Vec3;
import com.tiedup.remake.rig.TiedUpRigConstants;
import com.tiedup.remake.rig.anim.Animator;
import com.tiedup.remake.rig.anim.LivingMotion;
import com.tiedup.remake.rig.anim.LivingMotions;
@@ -22,26 +23,64 @@ import com.tiedup.remake.rig.anim.types.DynamicAnimation;
import com.tiedup.remake.rig.armature.Armature;
/**
* Stub RIG Phase 0 — patch de capability attaché à un {@link LivingEntity}.
* Expose l'animator, l'armature, la motion courante + quelques helpers.
* La version finale sera étoffée en Phase 2 (NPCs + player) avec les données
* d'entraînement/bondage spécifiques.
* RIG Phase 2 — patch de capability attaché à un {@link LivingEntity}. Porte
* l'{@link Animator} (initialisé eager via {@link TiedUpRigConstants#ANIMATOR_PROVIDER}
* dans {@link #onConstructed(LivingEntity)}) et expose {@link #getArmature()}
* (abstract — subclass fournit son biped armature via {@code TiedUpRigRegistry}).
*
* <p><b>Pattern init EF-style (voir EF LivingEntityPatch:146-156)</b> :
* <ol>
* <li>{@code onConstructed} fait super.onConstructed puis construit l'animator
* via le factory client/server split</li>
* <li>{@link #initAnimator(Animator)} permet aux subclasses de registrer leurs
* {@code LivingMotion → StaticAnimation} avant que l'animator commence à
* tourner</li>
* <li>{@link Animator#postInit()} poste l'event {@code InitAnimatorEvent}
* pour les extensions tierces</li>
* </ol>
* </p>
*/
public abstract class LivingEntityPatch<T extends LivingEntity> extends EntityPatch<T> {
public LivingMotion currentLivingMotion = LivingMotions.IDLE;
public LivingMotion currentCompositeMotion = LivingMotions.IDLE;
/** Animator initialisé dans {@link #onConstructed(LivingEntity)}, non-null après. */
protected Animator animator;
public abstract void updateMotion(boolean considerInaction);
public abstract Armature getArmature();
@Override
public void onConstructed(T entity) {
super.onConstructed(entity);
this.animator = TiedUpRigConstants.ANIMATOR_PROVIDER.apply(this);
this.initAnimator(this.animator);
this.animator.postInit();
}
/**
* Hook pour subclasses : bind des {@code LivingMotion → StaticAnimation}
* sur l'animator avant qu'il ne commence à tourner. Default no-op.
*
* <p>Exemple subclass :</p>
* <pre>
* protected void initAnimator(Animator a) {
* a.addLivingAnimation(LivingMotions.IDLE, TiedUpAnimationRegistry.CONTEXT_STAND_IDLE);
* }
* </pre>
*/
protected void initAnimator(Animator animator) {
// no-op par défaut
}
public Animator getAnimator() {
return null;
return this.animator;
}
@Nullable
public ClientAnimator getClientAnimator() {
return null;
return this.animator instanceof ClientAnimator ca ? ca : null;
}
public LivingMotion getCurrentLivingMotion() {