diff --git a/src/main/java/com/tiedup/remake/rig/patch/PlayerPatch.java b/src/main/java/com/tiedup/remake/rig/patch/PlayerPatch.java index e78462d..38fbded 100644 --- a/src/main/java/com/tiedup/remake/rig/patch/PlayerPatch.java +++ b/src/main/java/com/tiedup/remake/rig/patch/PlayerPatch.java @@ -6,17 +6,22 @@ package com.tiedup.remake.rig.patch; +import net.minecraft.util.Mth; import net.minecraft.world.entity.player.Player; import com.tiedup.remake.rig.TiedUpAnimationRegistry; 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.LivingMotion; import com.tiedup.remake.rig.anim.LivingMotions; +import com.tiedup.remake.rig.anim.TiedUpLivingMotions; import com.tiedup.remake.rig.anim.types.StaticAnimation; import com.tiedup.remake.rig.armature.HumanoidArmature; import com.tiedup.remake.rig.math.MathUtils; import com.tiedup.remake.rig.math.OpenMatrix4f; +import com.tiedup.remake.v2.client.BondageStateHelpers; +import com.tiedup.remake.v2.furniture.EntityFurniture; /** * RIG Phase 2.4 — patch de capability attaché à un {@link Player}. @@ -33,6 +38,10 @@ import com.tiedup.remake.rig.math.OpenMatrix4f; *
Ce qui manque vs EF PlayerPatch (766 LOC), voulu :
@@ -61,22 +70,110 @@ public abstract class PlayerPatchPhase 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}).
+ *Arbre de décision (priorité décroissante) :
+ *Pattern EF : {@code EntityState.inaction()} gate la transition + * — quand une action-animation (knockdown, struggle forcé, etc.) verrouille + * l'entité, on ne change PAS {@code currentLivingMotion} pour éviter de + * couper l'action en cours. L'{@link Animator#getEntityState()} reflète + * l'état courant de la couche base.
+ * + *Logique extraite : la résolution pure est dans + * {@link #resolveMotion} pour permettre des tests unitaires sans instancier + * un {@link Player} réel.
+ * + * @param considerInaction si {@code true}, respecte {@code state.inaction()} + * et n'update pas la motion si l'entité est + * verrouillée par une action-animation */ @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). + if (considerInaction + && this.animator != null + && this.animator.getEntityState().inaction()) { + return; + } + + Player p = this.original; + if (p == null) return; + + boolean onFurnitureVehicle = p.getVehicle() instanceof EntityFurniture; + boolean sleepingBound = BondageStateHelpers.isSleepingBound(p); + boolean dogPose = BondageStateHelpers.isDogPoseActive(p); + boolean struggling = BondageStateHelpers.isStrugglingClient(p); + boolean falling = !p.onGround() && p.getDeltaMovement().y < 0.0D; + boolean inWater = p.isInWater(); + boolean moving = Mth.abs(p.walkDist - p.walkDistO) > 0.01F; + boolean sneaking = p.isShiftKeyDown(); + boolean bound = BondageStateHelpers.isBound(p); + + this.currentLivingMotion = resolveMotion( + onFurnitureVehicle, + sleepingBound, + dogPose, + struggling, + falling, + inWater, + moving, + sneaking, + bound); + } + + /** + * Résout la {@link LivingMotion} courante en fonction de flags booleans + * décrivant l'état du joueur. Extrait en méthode statique pure pour + * permettre des tests unitaires sans instancier un {@link Player} MC. + * + *Voir {@link #updateMotion(boolean)} pour l'arbre de priorité.
+ * + *Package-private : consommé uniquement par + * {@code PlayerPatchUpdateMotionTest} et {@link #updateMotion(boolean)} + * lui-même — pas d'API publique.
+ */ + static LivingMotion resolveMotion( + boolean onFurnitureVehicle, + boolean sleepingBound, + boolean dogPose, + boolean struggling, + boolean falling, + boolean inWater, + boolean moving, + boolean sneaking, + boolean bound) { + if (onFurnitureVehicle) return TiedUpLivingMotions.POSE_FURNITURE_SEAT; + if (sleepingBound) return TiedUpLivingMotions.POSE_SLEEP_BOUND; + if (dogPose) return TiedUpLivingMotions.POSE_DOG; + if (struggling) return TiedUpLivingMotions.STRUGGLE_BOUND; + if (falling) return bound ? TiedUpLivingMotions.FALL_BOUND : LivingMotions.FALL; + if (inWater) return LivingMotions.SWIM; + if (moving) { + if (sneaking) return bound ? TiedUpLivingMotions.SNEAK_BOUND : LivingMotions.SNEAK; + return bound ? TiedUpLivingMotions.WALK_BOUND : LivingMotions.WALK; + } + return LivingMotions.IDLE; } /** diff --git a/src/test/java/com/tiedup/remake/rig/patch/PlayerPatchUpdateMotionTest.java b/src/test/java/com/tiedup/remake/rig/patch/PlayerPatchUpdateMotionTest.java new file mode 100644 index 0000000..1918fc6 --- /dev/null +++ b/src/test/java/com/tiedup/remake/rig/patch/PlayerPatchUpdateMotionTest.java @@ -0,0 +1,185 @@ +/* + * © 2026 TiedUp! Remake Contributors, distributed under GPLv3. + */ + +package com.tiedup.remake.rig.patch; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +import com.tiedup.remake.rig.anim.LivingMotion; +import com.tiedup.remake.rig.anim.LivingMotions; +import com.tiedup.remake.rig.anim.TiedUpLivingMotions; + +/** + * Tests unitaires pour {@link PlayerPatch#resolveMotion} — l'arbre de décision + * qui route {@code currentLivingMotion} selon l'état du joueur (P3-08). + * + *Stratégie : {@code updateMotion(boolean)} lit un {@link + * net.minecraft.world.entity.player.Player} et dérive 9 flags booleans qu'il + * passe à {@link PlayerPatch#resolveMotion} (pure function). Les tests + * exercent uniquement cette fonction pure — pas besoin de Player/Level/ + * capabilities MC pour vérifier la logique de priorité.
+ * + *Le binding {@code Player → flags} dans {@code updateMotion} est un thin + * wrapper trivial (un if de gate + 9 appels de getters) — couvert par les + * tests in-game manuels de P3-05/06, pas par JUnit.
+ */ +class PlayerPatchUpdateMotionTest { + + @Test + void resolveMotion_furniture_returnsPoseFurnitureSeat() { + LivingMotion got = PlayerPatch.resolveMotion( + /* furniture */ true, + /* sleeping */ false, + /* dog */ false, + /* struggle */ false, + /* falling */ false, + /* inWater */ false, + /* moving */ false, + /* sneaking */ false, + /* bound */ false); + assertEquals(TiedUpLivingMotions.POSE_FURNITURE_SEAT, got); + } + + @Test + void resolveMotion_sleepingBound_returnsPoseSleepBound() { + LivingMotion got = PlayerPatch.resolveMotion( + false, true, false, false, false, false, false, false, true); + assertEquals(TiedUpLivingMotions.POSE_SLEEP_BOUND, got); + } + + @Test + void resolveMotion_dogPose_returnsPoseDog() { + LivingMotion got = PlayerPatch.resolveMotion( + false, false, true, false, false, false, false, false, true); + assertEquals(TiedUpLivingMotions.POSE_DOG, got); + } + + @Test + void resolveMotion_struggling_returnsStruggleBound() { + LivingMotion got = PlayerPatch.resolveMotion( + false, false, false, true, false, false, false, false, true); + assertEquals(TiedUpLivingMotions.STRUGGLE_BOUND, got); + } + + @Test + void resolveMotion_fallingBound_returnsFallBound() { + LivingMotion got = PlayerPatch.resolveMotion( + false, false, false, false, + /* falling */ true, + false, false, false, + /* bound */ true); + assertEquals(TiedUpLivingMotions.FALL_BOUND, got); + } + + @Test + void resolveMotion_fallingNotBound_returnsVanillaFall() { + LivingMotion got = PlayerPatch.resolveMotion( + false, false, false, false, + /* falling */ true, + false, false, false, + /* bound */ false); + assertEquals(LivingMotions.FALL, got); + } + + @Test + void resolveMotion_inWater_returnsSwim() { + LivingMotion got = PlayerPatch.resolveMotion( + false, false, false, false, false, + /* inWater */ true, + false, false, false); + assertEquals(LivingMotions.SWIM, got); + } + + @Test + void resolveMotion_walkingBound_returnsWalkBound() { + LivingMotion got = PlayerPatch.resolveMotion( + false, false, false, false, false, false, + /* moving */ true, + /* sneaking */ false, + /* bound */ true); + assertEquals(TiedUpLivingMotions.WALK_BOUND, got); + } + + @Test + void resolveMotion_walkingNotBound_returnsVanillaWalk() { + LivingMotion got = PlayerPatch.resolveMotion( + false, false, false, false, false, false, + /* moving */ true, + /* sneaking */ false, + /* bound */ false); + assertEquals(LivingMotions.WALK, got); + } + + @Test + void resolveMotion_sneakingBound_returnsSneakBound() { + LivingMotion got = PlayerPatch.resolveMotion( + false, false, false, false, false, false, + /* moving */ true, + /* sneaking */ true, + /* bound */ true); + assertEquals(TiedUpLivingMotions.SNEAK_BOUND, got); + } + + @Test + void resolveMotion_sneakingNotBound_returnsVanillaSneak() { + LivingMotion got = PlayerPatch.resolveMotion( + false, false, false, false, false, false, + /* moving */ true, + /* sneaking */ true, + /* bound */ false); + assertEquals(LivingMotions.SNEAK, got); + } + + @Test + void resolveMotion_idle_returnsIdle() { + LivingMotion got = PlayerPatch.resolveMotion( + false, false, false, false, false, false, false, false, false); + assertEquals(LivingMotions.IDLE, got); + } + + /** + * Edge case — tous les flags d'override à {@code true} : la priorité + * doit faire gagner {@code furniture} (branche la plus haute). + * + *Important si jamais un joueur monte une furniture alors qu'il est + * bound + en struggle (cas théorique : furniture chair tie-down avec + * prisoner struggling). La pose furniture override tout.
+ */ + @Test + void resolveMotion_priorityOrder_furnitureBeatsAll() { + LivingMotion got = PlayerPatch.resolveMotion( + /* furniture */ true, + /* sleeping */ true, + /* dog */ true, + /* struggle */ true, + /* falling */ true, + /* inWater */ true, + /* moving */ true, + /* sneaking */ true, + /* bound */ true); + assertEquals(TiedUpLivingMotions.POSE_FURNITURE_SEAT, got); + } + + /** + * Edge case — sans furniture, sleep > dog > struggle > falling. + * Vérifie qu'un sleep bound override un dog-pose (ex: joueur qui met + * un lit alors qu'il porte des moufles DOG). + */ + @Test + void resolveMotion_priorityOrder_sleepBeatsDog() { + LivingMotion got = PlayerPatch.resolveMotion( + /* furniture */ false, + /* sleeping */ true, + /* dog */ true, + /* struggle */ true, + /* falling */ true, + /* inWater */ false, + /* moving */ false, + /* sneaking */ false, + /* bound */ true); + assertEquals(TiedUpLivingMotions.POSE_SLEEP_BOUND, got); + } +}