Phase 2.8 : V2 player rendering cleanup + QA checklist doc
Cut wholesale du pipeline V2 player-anim suite au bascule RIG (pas d'opt-in, pas de rollback — accepté par le mainteneur). - rm MixinPlayerModel : le renderer RIG patched ne passe plus par PlayerModel.setupAnim, donc l'injection @TAIL devenait dead code. Les features dog pose head compensation seront ré-exprimées en StaticAnimation pose_dog.json (V3-REW-07). - Strip tiedup.mixins.json : retiré client/MixinPlayerModel, restent MixinCamera + MixinLivingEntitySleeping. - BondageAnimationManager.init() : retiré les 3 PlayerAnimationFactory registrations (context / item / furniture), le path joueur n'en dépend plus. Factory IDs conservés car les getPlayerLayer*() sont tolérantes au null retour via try/catch existants — et restent utilisées par le cache fallback remote. Les NPCs continuent d'utiliser cette classe via l'accès direct animation stack (IAnimatedPlayer.getAnimationStack().addAnimLayer), inchangé. - TiedUpMod.onClientSetup : suppression de l'appel BondageAnimationManager.init() (la méthode est maintenant un log no-op, conservée pour la signature publique + doc du changement). - AnimationTickHandler.onClientTick : retrait de la boucle mc.level.players() + updatePlayerAnimation + tickFurnitureSafety + cold-cache furniture retry. Les joueurs sont ticked par RigAnimationTickHandler (Phase 2.7). Conservé : le cleanup périodique ClothesClientCache (hygiène mémoire indépendante), le hook onPlayerLogout (cleanup per-UUID des caches NPC restants), et le hook onWorldUnload (caches V2 encore utilisés par NpcAnimationTickHandler). Imports unused strippés. - DogPoseHelper : mis à jour la javadoc pour refléter le retrait du path player (NPCs only désormais). Compile GREEN. 20/20 tests rig GREEN. QA runtime : cf. docs/plans/rig/PHASE2_QA.md (non commit — fichier working doc sous docs/plans/ gitignored par convention repo). Net LOC : -276.
This commit is contained in:
@@ -90,41 +90,30 @@ public class BondageAnimationManager {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the animation system.
|
* Initialize the animation system.
|
||||||
* Must be called during client setup to register the player animation factory.
|
*
|
||||||
|
* <p>Phase 2.8 RIG cleanup — les 3 {@link PlayerAnimationFactory}
|
||||||
|
* (context / item / furniture) ont été supprimées : le renderer RIG
|
||||||
|
* patched ne passe plus par le pipeline PlayerAnimator pour le joueur,
|
||||||
|
* donc les factories devenaient dead code (aucun call site n'atteint
|
||||||
|
* jamais la map associée côté joueur).</p>
|
||||||
|
*
|
||||||
|
* <p>Les NPCs continuent d'être animés via cette classe : le chemin
|
||||||
|
* {@link #getOrCreateLayer} pour les entités {@code IAnimatedPlayer}
|
||||||
|
* non-joueur utilise {@code animated.getAnimationStack().addAnimLayer(...)}
|
||||||
|
* en direct — ça ne dépend d'aucune factory. Voir {@code NpcAnimationTickHandler}
|
||||||
|
* pour le consumer. Le path player dans {@link #getOrCreateLayer} est
|
||||||
|
* laissé en place volontairement : il retombe proprement sur null
|
||||||
|
* (PlayerAnimationAccess throw → catch → null) et laisse le tick RIG
|
||||||
|
* s'occuper du joueur.</p>
|
||||||
|
*
|
||||||
|
* <p>Conservé comme méthode publique pour ne pas casser les call sites
|
||||||
|
* externes et pour documenter la bascule. Rework V3 (player anim
|
||||||
|
* natives RIG) : voir V3-REW-01 dans {@code docs/plans/rig/V3_REWORK_BACKLOG.md}.</p>
|
||||||
*/
|
*/
|
||||||
public static void init() {
|
public static void init() {
|
||||||
LOGGER.info("BondageAnimationManager initializing...");
|
|
||||||
|
|
||||||
// Context layer: lower priority = evaluated first, overridable by item layer.
|
|
||||||
// In AnimationStack, layers are sorted ascending by priority and evaluated in order.
|
|
||||||
// Higher priority layers override lower ones.
|
|
||||||
PlayerAnimationFactory.ANIMATION_DATA_FACTORY.registerFactory(
|
|
||||||
CONTEXT_FACTORY_ID,
|
|
||||||
CONTEXT_LAYER_PRIORITY,
|
|
||||||
player -> new ModifierLayer<>()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Item layer: higher priority = evaluated last, overrides context layer
|
|
||||||
PlayerAnimationFactory.ANIMATION_DATA_FACTORY.registerFactory(
|
|
||||||
FACTORY_ID,
|
|
||||||
ITEM_LAYER_PRIORITY,
|
|
||||||
player -> new ModifierLayer<>()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Furniture layer: highest priority = overrides item layer on blocked bones.
|
|
||||||
// Non-blocked bones are disabled via FurnitureAnimationContext so items
|
|
||||||
// can still animate free regions (gag, blindfold, etc.).
|
|
||||||
PlayerAnimationFactory.ANIMATION_DATA_FACTORY.registerFactory(
|
|
||||||
FURNITURE_FACTORY_ID,
|
|
||||||
FURNITURE_LAYER_PRIORITY,
|
|
||||||
player -> new ModifierLayer<>()
|
|
||||||
);
|
|
||||||
|
|
||||||
LOGGER.info(
|
LOGGER.info(
|
||||||
"BondageAnimationManager: Factories registered — context (pri {}), item (pri {}), furniture (pri {})",
|
"BondageAnimationManager: player-side factories no-op (Phase 2.8 RIG cleanup). " +
|
||||||
CONTEXT_LAYER_PRIORITY,
|
"NPC-side animation stack access untouched."
|
||||||
ITEM_LAYER_PRIORITY,
|
|
||||||
FURNITURE_LAYER_PRIORITY
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,29 +3,17 @@ package com.tiedup.remake.client.animation.tick;
|
|||||||
import com.mojang.logging.LogUtils;
|
import com.mojang.logging.LogUtils;
|
||||||
import com.tiedup.remake.client.animation.AnimationStateRegistry;
|
import com.tiedup.remake.client.animation.AnimationStateRegistry;
|
||||||
import com.tiedup.remake.client.animation.BondageAnimationManager;
|
import com.tiedup.remake.client.animation.BondageAnimationManager;
|
||||||
import com.tiedup.remake.client.animation.PendingAnimationManager;
|
|
||||||
import com.tiedup.remake.client.animation.context.AnimationContext;
|
|
||||||
import com.tiedup.remake.client.animation.context.AnimationContextResolver;
|
|
||||||
import com.tiedup.remake.client.animation.context.RegionBoneMapper;
|
|
||||||
import com.tiedup.remake.client.events.CellHighlightHandler;
|
import com.tiedup.remake.client.events.CellHighlightHandler;
|
||||||
import com.tiedup.remake.client.events.LeashProxyClientHandler;
|
import com.tiedup.remake.client.events.LeashProxyClientHandler;
|
||||||
import com.tiedup.remake.client.gltf.GltfAnimationApplier;
|
import com.tiedup.remake.client.gltf.GltfAnimationApplier;
|
||||||
import com.tiedup.remake.client.state.ClothesClientCache;
|
import com.tiedup.remake.client.state.ClothesClientCache;
|
||||||
import com.tiedup.remake.client.state.MovementStyleClientState;
|
import com.tiedup.remake.client.state.MovementStyleClientState;
|
||||||
import com.tiedup.remake.client.state.PetBedClientState;
|
import com.tiedup.remake.client.state.PetBedClientState;
|
||||||
import com.tiedup.remake.util.HumanChairHelper;
|
|
||||||
import com.tiedup.remake.state.PlayerBindState;
|
import com.tiedup.remake.state.PlayerBindState;
|
||||||
import com.tiedup.remake.v2.BodyRegionV2;
|
|
||||||
import com.tiedup.remake.v2.bondage.IV2BondageEquipment;
|
|
||||||
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
|
|
||||||
import com.tiedup.remake.v2.bondage.movement.MovementStyle;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.player.AbstractClientPlayer;
|
|
||||||
import net.minecraft.world.entity.player.Player;
|
|
||||||
import net.minecraft.world.item.ItemStack;
|
|
||||||
import net.minecraftforge.api.distmarker.Dist;
|
import net.minecraftforge.api.distmarker.Dist;
|
||||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||||
import net.minecraftforge.event.TickEvent;
|
import net.minecraftforge.event.TickEvent;
|
||||||
@@ -35,16 +23,29 @@ import net.minecraftforge.fml.common.Mod;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event handler for player animation tick updates.
|
* Event handler for animation tick updates.
|
||||||
*
|
*
|
||||||
* <p>Simplified handler that:
|
* <p><b>Phase 2.8 RIG cleanup</b> : le ticking <i>player</i> V2 (boucle
|
||||||
|
* {@code mc.level.players()} + {@code updatePlayerAnimation}) est entièrement
|
||||||
|
* désactivé. Les joueurs sont désormais pilotés par
|
||||||
|
* {@link com.tiedup.remake.rig.tick.RigAnimationTickHandler} via le pipeline
|
||||||
|
* RIG (capability {@code LivingEntityPatch} + {@code Animator} natif EF).
|
||||||
|
* Les features V2 qui dépendaient du tick player sont trackées dans
|
||||||
|
* {@code docs/plans/rig/V3_REWORK_BACKLOG.md} (V3-REW-01/02/03/07).</p>
|
||||||
|
*
|
||||||
|
* <p>Restent actifs ici :
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>Tracks tied/struggling/sneaking state for players</li>
|
* <li>Nettoyage périodique de {@code ClothesClientCache} (cache remote
|
||||||
* <li>Plays animations via BondageAnimationManager when state changes</li>
|
* players, hygiène mémoire indépendante du pipeline de rendu)</li>
|
||||||
* <li>Handles cleanup on logout/world unload</li>
|
* <li>Cleanup logout / world unload (caches V2 encore utilisés par les
|
||||||
|
* NPCs ticked par {@link NpcAnimationTickHandler})</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* <p>Registered on the FORGE event bus (not MOD bus).
|
* <p>Le ticking NPC est assuré par {@link NpcAnimationTickHandler}. Ce
|
||||||
|
* handler ne tick plus les NPCs directement — il ne gère que les hooks
|
||||||
|
* lifecycle globaux (logout + world unload).</p>
|
||||||
|
*
|
||||||
|
* <p>Registered on the FORGE event bus (not MOD bus).</p>
|
||||||
*/
|
*/
|
||||||
@Mod.EventBusSubscriber(
|
@Mod.EventBusSubscriber(
|
||||||
modid = "tiedup",
|
modid = "tiedup",
|
||||||
@@ -83,8 +84,20 @@ public class AnimationTickHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Client tick event - called every tick on the client.
|
* Client tick event — called every tick on the client.
|
||||||
* Updates animations for all players when their bondage state changes.
|
*
|
||||||
|
* <p>Phase 2.8 : la boucle {@code mc.level.players()} qui appelait
|
||||||
|
* {@code updatePlayerAnimation}, {@code tickFurnitureSafety} et le
|
||||||
|
* cold-cache retry furniture a été entièrement supprimée. Les joueurs
|
||||||
|
* sont désormais ticked par {@link com.tiedup.remake.rig.tick.RigAnimationTickHandler}
|
||||||
|
* via le pipeline RIG (capability {@code LivingEntityPatch} +
|
||||||
|
* {@code Animator}). Les régressions visuelles (V2 bondage layer cassé,
|
||||||
|
* furniture seat pose sur joueur cassée, pet bed pose cassée) sont
|
||||||
|
* listées dans {@code docs/plans/rig/V3_REWORK_BACKLOG.md}.</p>
|
||||||
|
*
|
||||||
|
* <p>Seul le nettoyage périodique de {@link ClothesClientCache} reste
|
||||||
|
* — c'est de l'hygiène mémoire sur un cache indexé UUID joueur,
|
||||||
|
* indépendant du pipeline de rendu.</p>
|
||||||
*/
|
*/
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
public static void onClientTick(TickEvent.ClientTickEvent event) {
|
public static void onClientTick(TickEvent.ClientTickEvent event) {
|
||||||
@@ -97,193 +110,17 @@ public class AnimationTickHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process pending animations first (retry failed animations for remote players)
|
// Periodic cleanup of stale clothes cache entries (every 60 seconds = 1200 ticks).
|
||||||
PendingAnimationManager.processPending(mc.level);
|
// Indépendant du rendu V2/RIG — c'est juste un cache UUID→ClothesData qui
|
||||||
|
// doit libérer la mémoire des joueurs déconnectés depuis >5min.
|
||||||
// Periodic cleanup of stale cache entries (every 60 seconds = 1200 ticks)
|
|
||||||
if (++cleanupTickCounter >= 1200) {
|
if (++cleanupTickCounter >= 1200) {
|
||||||
cleanupTickCounter = 0;
|
cleanupTickCounter = 0;
|
||||||
ClothesClientCache.cleanupStale();
|
ClothesClientCache.cleanupStale();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then update all player animations
|
// Le tick per-player V2 (updatePlayerAnimation, tickFurnitureSafety,
|
||||||
for (Player player : mc.level.players()) {
|
// cold-cache furniture retry) est délégué à RigAnimationTickHandler
|
||||||
if (player instanceof AbstractClientPlayer clientPlayer) {
|
// Phase 2.7+. Rien à faire ici.
|
||||||
updatePlayerAnimation(clientPlayer);
|
|
||||||
}
|
|
||||||
// Safety: remove stale furniture animations for players no longer on seats
|
|
||||||
BondageAnimationManager.tickFurnitureSafety(player);
|
|
||||||
// Cold-cache retry: if the player is seated on furniture but has no
|
|
||||||
// active pose (GLB was not yet loaded at mount time, or the GLB cache
|
|
||||||
// entry was a transient failure), retry until the cache warms.
|
|
||||||
// FurnitureGltfCache memoizes failures via Optional.empty(), so
|
|
||||||
// retries after a genuine parse failure return instantly with no
|
|
||||||
// reparse. Bounded at MAX_FURNITURE_RETRIES so a legacy V1-only
|
|
||||||
// GLB (no Player_* armature → seatSkeleton==null → no animation
|
|
||||||
// ever possible) doesn't spam retries at 20 Hz forever.
|
|
||||||
// Single read of getVehicle() — avoids a re-read where the
|
|
||||||
// vehicle could change between instanceof and cast.
|
|
||||||
com.tiedup.remake.v2.furniture.EntityFurniture furniture =
|
|
||||||
player.getVehicle() instanceof
|
|
||||||
com.tiedup.remake.v2.furniture.EntityFurniture f ? f : null;
|
|
||||||
boolean hasAnim = BondageAnimationManager.hasFurnitureAnimation(
|
|
||||||
player
|
|
||||||
);
|
|
||||||
UUID playerUuid = player.getUUID();
|
|
||||||
if (furniture != null && !hasAnim) {
|
|
||||||
int retries = furnitureRetryCounters.getOrDefault(
|
|
||||||
playerUuid,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
if (retries < MAX_FURNITURE_RETRIES) {
|
|
||||||
furnitureRetryCounters.put(playerUuid, retries + 1);
|
|
||||||
com.tiedup.remake.v2.furniture.client.FurnitureClientAnimator
|
|
||||||
.start(furniture, player);
|
|
||||||
if (retries + 1 == MAX_FURNITURE_RETRIES) {
|
|
||||||
LOGGER.debug(
|
|
||||||
"[FurnitureAnim] Giving up on furniture animation retry for {} after {} attempts — GLB likely has no Player_* armature.",
|
|
||||||
player.getName().getString(),
|
|
||||||
MAX_FURNITURE_RETRIES
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Dismounted or successfully applied — drop the counter so a
|
|
||||||
// later re-mount starts fresh.
|
|
||||||
furnitureRetryCounters.remove(playerUuid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update animation for a single player.
|
|
||||||
*/
|
|
||||||
private static void updatePlayerAnimation(AbstractClientPlayer player) {
|
|
||||||
// Safety check: skip for removed/dead players
|
|
||||||
if (player.isRemoved() || !player.isAlive()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
PlayerBindState state = PlayerBindState.getInstance(player);
|
|
||||||
UUID uuid = player.getUUID();
|
|
||||||
|
|
||||||
// Check if player has ANY V2 bondage item equipped (not just ARMS).
|
|
||||||
// isTiedUp() only checks ARMS, but items on LEGS, HEAD, etc. also need animation.
|
|
||||||
boolean isTied =
|
|
||||||
state != null &&
|
|
||||||
(state.isTiedUp() || V2EquipmentHelper.hasAnyEquipment(player));
|
|
||||||
boolean wasTied =
|
|
||||||
AnimationStateRegistry.getLastTiedState().getOrDefault(uuid, false);
|
|
||||||
|
|
||||||
// Pet bed animations take priority over bondage animations
|
|
||||||
if (PetBedClientState.get(uuid) != 0) {
|
|
||||||
// Lock body rotation to bed facing (prevents camera from rotating the model)
|
|
||||||
float lockedRot = PetBedClientState.getFacing(uuid);
|
|
||||||
player.yBodyRot = lockedRot;
|
|
||||||
player.yBodyRotO = lockedRot;
|
|
||||||
|
|
||||||
// Clamp head rotation to ±50° from body (like vehicle)
|
|
||||||
float headRot = player.getYHeadRot();
|
|
||||||
float clamped =
|
|
||||||
lockedRot +
|
|
||||||
net.minecraft.util.Mth.clamp(
|
|
||||||
net.minecraft.util.Mth.wrapDegrees(headRot - lockedRot),
|
|
||||||
-50f,
|
|
||||||
50f
|
|
||||||
);
|
|
||||||
player.setYHeadRot(clamped);
|
|
||||||
player.yHeadRotO = clamped;
|
|
||||||
|
|
||||||
AnimationStateRegistry.getLastTiedState().put(uuid, isTied);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Human chair: clamp 1st-person camera only (body lock handled by MixinLivingEntityBodyRot)
|
|
||||||
// NO return — animation HUMAN_CHAIR must continue playing below
|
|
||||||
if (isTied && state != null) {
|
|
||||||
ItemStack chairBind = state.getEquipment(BodyRegionV2.ARMS);
|
|
||||||
if (HumanChairHelper.isActive(chairBind)) {
|
|
||||||
// 1st person only: clamp yRot so player can't look behind
|
|
||||||
// 3rd person: yRot untouched → camera orbits freely 360°
|
|
||||||
if (
|
|
||||||
player == Minecraft.getInstance().player &&
|
|
||||||
Minecraft.getInstance().options.getCameraType() ==
|
|
||||||
net.minecraft.client.CameraType.FIRST_PERSON
|
|
||||||
) {
|
|
||||||
float lockedRot = HumanChairHelper.getFacing(chairBind);
|
|
||||||
float camClamped =
|
|
||||||
lockedRot +
|
|
||||||
net.minecraft.util.Mth.clamp(
|
|
||||||
net.minecraft.util.Mth.wrapDegrees(
|
|
||||||
player.getYRot() - lockedRot
|
|
||||||
),
|
|
||||||
-90f,
|
|
||||||
90f
|
|
||||||
);
|
|
||||||
player.setYRot(camClamped);
|
|
||||||
player.yRotO =
|
|
||||||
lockedRot +
|
|
||||||
net.minecraft.util.Mth.clamp(
|
|
||||||
net.minecraft.util.Mth.wrapDegrees(
|
|
||||||
player.yRotO - lockedRot
|
|
||||||
),
|
|
||||||
-90f,
|
|
||||||
90f
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isTied) {
|
|
||||||
// Resolve V2 equipped items
|
|
||||||
IV2BondageEquipment equipment = V2EquipmentHelper.getEquipment(
|
|
||||||
player
|
|
||||||
);
|
|
||||||
Map<BodyRegionV2, ItemStack> equipped =
|
|
||||||
equipment != null ? equipment.getAllEquipped() : Map.of();
|
|
||||||
|
|
||||||
// Resolve ALL V2 items with GLB models and per-item bone ownership
|
|
||||||
java.util.List<RegionBoneMapper.V2ItemAnimInfo> v2Items =
|
|
||||||
RegionBoneMapper.resolveAllV2Items(equipped);
|
|
||||||
|
|
||||||
if (!v2Items.isEmpty()) {
|
|
||||||
// V2 path: multi-item composite animation
|
|
||||||
java.util.Set<String> allOwnedParts =
|
|
||||||
RegionBoneMapper.computeAllOwnedParts(v2Items);
|
|
||||||
MovementStyle activeStyle = MovementStyleClientState.get(
|
|
||||||
player.getUUID()
|
|
||||||
);
|
|
||||||
AnimationContext context = AnimationContextResolver.resolve(
|
|
||||||
player,
|
|
||||||
state,
|
|
||||||
activeStyle
|
|
||||||
);
|
|
||||||
GltfAnimationApplier.applyMultiItemV2Animation(
|
|
||||||
player,
|
|
||||||
v2Items,
|
|
||||||
context,
|
|
||||||
allOwnedParts
|
|
||||||
);
|
|
||||||
} else if (GltfAnimationApplier.hasActiveState(player)) {
|
|
||||||
// Clear any residual V2 composite animation when the player
|
|
||||||
// is still isTiedUp() but has no GLB-bearing items — e.g.
|
|
||||||
// a non-GLB item keeps the tied state, or a GLB item was
|
|
||||||
// removed while another V2 item remains on a non-animated
|
|
||||||
// region. Leaving the composite in place locks the arms in
|
|
||||||
// the pose of an item the player no longer wears.
|
|
||||||
GltfAnimationApplier.clearV2Animation(player);
|
|
||||||
}
|
|
||||||
} else if (wasTied) {
|
|
||||||
// Was tied, now free - stop all animations
|
|
||||||
if (GltfAnimationApplier.hasActiveState(player)) {
|
|
||||||
GltfAnimationApplier.clearV2Animation(player);
|
|
||||||
} else {
|
|
||||||
BondageAnimationManager.stopAnimation(player);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
AnimationStateRegistry.getLastTiedState().put(uuid, isTied);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -15,22 +15,14 @@ import net.minecraftforge.api.distmarker.OnlyIn;
|
|||||||
* <li>Head yaw: convert to zRot (roll) since yRot axis is sideways</li>
|
* <li>Head yaw: convert to zRot (roll) since yRot axis is sideways</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* <h2>Architecture: Players vs NPCs</h2>
|
* <h2>Architecture — NPCs only (Phase 2.8 RIG cleanup)</h2>
|
||||||
* <pre>
|
* <p>Le path PLAYER (ex-{@code MixinPlayerModel.setupAnim @TAIL}) a été retiré
|
||||||
* ┌─────────────────────────────────────────────────────────────────┐
|
* Phase 2.8 : le renderer RIG patched ne passe plus par {@code PlayerModel.setupAnim},
|
||||||
* │ PLAYERS │
|
* donc le mixin devenait dead code. La compensation head dog pose sera ré-exprimée
|
||||||
* ├─────────────────────────────────────────────────────────────────┤
|
* nativement en StaticAnimation {@code pose_dog.json} (cf. V3-REW-07 dans
|
||||||
* │ 1. PlayerArmHideEventHandler.onRenderPlayerPre() │
|
* {@code docs/plans/rig/V3_REWORK_BACKLOG.md}).</p>
|
||||||
* │ - Offset vertical (-6 model units) │
|
|
||||||
* │ - Rotation Y lissée (dogPoseState tracking) │
|
|
||||||
* │ │
|
|
||||||
* │ 2. Animation (PlayerAnimator) │
|
|
||||||
* │ - body.pitch = -90° → appliqué au PoseStack automatiquement │
|
|
||||||
* │ │
|
|
||||||
* │ 3. MixinPlayerModel.setupAnim() @TAIL │
|
|
||||||
* │ - Uses DogPoseHelper.applyHeadCompensationClamped() │
|
|
||||||
* └─────────────────────────────────────────────────────────────────┘
|
|
||||||
*
|
*
|
||||||
|
* <pre>
|
||||||
* ┌─────────────────────────────────────────────────────────────────┐
|
* ┌─────────────────────────────────────────────────────────────────┐
|
||||||
* │ NPCs │
|
* │ NPCs │
|
||||||
* ├─────────────────────────────────────────────────────────────────┤
|
* ├─────────────────────────────────────────────────────────────────┤
|
||||||
@@ -48,25 +40,13 @@ import net.minecraftforge.api.distmarker.OnlyIn;
|
|||||||
* └─────────────────────────────────────────────────────────────────┘
|
* └─────────────────────────────────────────────────────────────────┘
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* <h2>Key Differences</h2>
|
|
||||||
* <table>
|
|
||||||
* <tr><th>Aspect</th><th>Players</th><th>NPCs</th></tr>
|
|
||||||
* <tr><td>Rotation X application</td><td>Auto by PlayerAnimator</td><td>Manual in setupRotations()</td></tr>
|
|
||||||
* <tr><td>Rotation Y smoothing</td><td>PlayerArmHideEventHandler</td><td>EntityDamsel.tick() via RotationSmoother</td></tr>
|
|
||||||
* <tr><td>Head compensation</td><td>MixinPlayerModel</td><td>DamselModel.setupAnim()</td></tr>
|
|
||||||
* <tr><td>Reset body.xRot</td><td>Not needed</td><td>Yes (prevents double rotation)</td></tr>
|
|
||||||
* <tr><td>Vertical offset</td><td>-6 model units</td><td>-7 model units</td></tr>
|
|
||||||
* </table>
|
|
||||||
*
|
|
||||||
* <h2>Usage</h2>
|
* <h2>Usage</h2>
|
||||||
* <p>Used by:
|
* <p>Used by:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>MixinPlayerModel - for player head compensation</li>
|
|
||||||
* <li>DamselModel - for NPC head compensation</li>
|
* <li>DamselModel - for NPC head compensation</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* @see RotationSmoother for Y rotation smoothing
|
* @see RotationSmoother for Y rotation smoothing
|
||||||
* @see com.tiedup.remake.mixin.client.MixinPlayerModel
|
|
||||||
* @see com.tiedup.remake.client.model.DamselModel
|
* @see com.tiedup.remake.client.model.DamselModel
|
||||||
*/
|
*/
|
||||||
@OnlyIn(Dist.CLIENT)
|
@OnlyIn(Dist.CLIENT)
|
||||||
|
|||||||
@@ -196,9 +196,11 @@ public class TiedUpMod {
|
|||||||
// RIG Phase 2 — override client dispatch PLAYER → Local/Client/ServerPlayerPatch
|
// RIG Phase 2 — override client dispatch PLAYER → Local/Client/ServerPlayerPatch
|
||||||
com.tiedup.remake.rig.patch.EntityPatchProvider.registerEntityPatchesClient();
|
com.tiedup.remake.rig.patch.EntityPatchProvider.registerEntityPatchesClient();
|
||||||
|
|
||||||
// Initialize unified BondageAnimationManager
|
// Phase 2.8 RIG cleanup : BondageAnimationManager.init() (factory
|
||||||
com.tiedup.remake.client.animation.BondageAnimationManager.init();
|
// registrations PlayerAnimator côté joueur) a été supprimé — le RIG
|
||||||
LOGGER.info("BondageAnimationManager initialized");
|
// prend le relai pour les joueurs via RigAnimationTickHandler.
|
||||||
|
// Les NPCs continuent d'être animés via BondageAnimationManager en
|
||||||
|
// accès direct animation stack (cf. NpcAnimationTickHandler).
|
||||||
|
|
||||||
// Initialize OBJ model registry for 3D bondage items
|
// Initialize OBJ model registry for 3D bondage items
|
||||||
com.tiedup.remake.client.renderer.obj.ObjModelRegistry.init();
|
com.tiedup.remake.client.renderer.obj.ObjModelRegistry.init();
|
||||||
|
|||||||
@@ -1,83 +0,0 @@
|
|||||||
package com.tiedup.remake.mixin.client;
|
|
||||||
|
|
||||||
import com.tiedup.remake.client.animation.render.DogPoseRenderHandler;
|
|
||||||
import com.tiedup.remake.client.animation.util.DogPoseHelper;
|
|
||||||
import com.tiedup.remake.items.base.PoseType;
|
|
||||||
import com.tiedup.remake.v2.bondage.PoseTypeHelper;
|
|
||||||
import com.tiedup.remake.state.PlayerBindState;
|
|
||||||
import com.tiedup.remake.v2.BodyRegionV2;
|
|
||||||
import net.minecraft.client.model.PlayerModel;
|
|
||||||
import net.minecraft.client.player.AbstractClientPlayer;
|
|
||||||
import net.minecraft.world.entity.LivingEntity;
|
|
||||||
import net.minecraft.world.item.ItemStack;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mixin for PlayerModel to handle DOG pose head adjustments.
|
|
||||||
*
|
|
||||||
* When in DOG pose (body horizontal):
|
|
||||||
* - Head pitch offset so player looks forward
|
|
||||||
* - Head yaw converted to zRot (roll) since yRot axis is sideways when body is horizontal
|
|
||||||
*/
|
|
||||||
@Mixin(PlayerModel.class)
|
|
||||||
public class MixinPlayerModel {
|
|
||||||
|
|
||||||
@Inject(method = "setupAnim", at = @At("TAIL"))
|
|
||||||
private void tiedup$adjustDogPose(
|
|
||||||
LivingEntity entity,
|
|
||||||
float limbSwing,
|
|
||||||
float limbSwingAmount,
|
|
||||||
float ageInTicks,
|
|
||||||
float netHeadYaw,
|
|
||||||
float headPitch,
|
|
||||||
CallbackInfo ci
|
|
||||||
) {
|
|
||||||
if (!(entity instanceof AbstractClientPlayer player)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
PlayerBindState state = PlayerBindState.getInstance(player);
|
|
||||||
if (state == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ItemStack bind = state.getEquipment(BodyRegionV2.ARMS);
|
|
||||||
if (bind.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (PoseTypeHelper.getPoseType(bind) != PoseType.DOG) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
PlayerModel<?> model = (PlayerModel<?>) (Object) this;
|
|
||||||
|
|
||||||
// === HEAD ROTATION FOR HORIZONTAL BODY ===
|
|
||||||
// Body is at -90° pitch (horizontal, face down)
|
|
||||||
// We apply a rotation delta to the poseStack in PlayerArmHideEventHandler
|
|
||||||
// The head needs to compensate for this transformation
|
|
||||||
|
|
||||||
float rotationDelta = DogPoseRenderHandler.getAppliedRotationDelta(
|
|
||||||
player.getUUID()
|
|
||||||
);
|
|
||||||
boolean moving = DogPoseRenderHandler.isDogPoseMoving(player.getUUID());
|
|
||||||
|
|
||||||
// netHeadYaw is head relative to vanilla body (yHeadRot - yBodyRot)
|
|
||||||
// We rotated the model by rotationDelta, so compensate:
|
|
||||||
// effectiveHeadYaw = netHeadYaw + rotationDelta
|
|
||||||
float headYaw = netHeadYaw + rotationDelta;
|
|
||||||
|
|
||||||
// Clamp based on movement state and apply head compensation
|
|
||||||
float maxYaw = moving ? 60f : 90f;
|
|
||||||
DogPoseHelper.applyHeadCompensationClamped(
|
|
||||||
model.head,
|
|
||||||
model.hat,
|
|
||||||
headPitch,
|
|
||||||
headYaw,
|
|
||||||
maxYaw
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,7 +9,6 @@
|
|||||||
"MixinLivingEntityBodyRot"
|
"MixinLivingEntityBodyRot"
|
||||||
],
|
],
|
||||||
"client": [
|
"client": [
|
||||||
"client/MixinPlayerModel",
|
|
||||||
"client/MixinCamera",
|
"client/MixinCamera",
|
||||||
"client/MixinLivingEntitySleeping"
|
"client/MixinLivingEntitySleeping"
|
||||||
],
|
],
|
||||||
|
|||||||
Reference in New Issue
Block a user