Phase 2.8 review fixes : strip player fallback + backlog V3-REW-12-14 + QA edge cases

- BondageAnimationManager : strip total du path joueur, tous les call sites
  'player' retournent null/false avec WARN once-per-UUID. getOrCreateLayer
  court-circuite sur Player direct. Retire dead code factory/furniture
  (FACTORY_ID, FURNITURE_*, npcFurnitureLayers, getPlayerLayer, etc.).
  Javadoc init() reflete la semantique NPC-only.
- DogPoseHelper.applyHeadCompensationClamped : @Deprecated(since=2.8)
  pointant vers V3-REW-07 (dead apres retrait MixinPlayerModel).
- DogPoseRenderHandler.getAppliedRotationDelta + isDogPoseMoving :
  @Deprecated(since=2.8) meme raison.
- Docs (gitignored) : V3_REWORK_BACKLOG.md ajoute V3-REW-12/13/14 (pet bed
  body-lock, human chair yaw clamp, context layer sit/kneel/sneak), tableau
  recap 14 -> 17 items. PHASE2_QA.md ajoute sec 2.5 edge cases + corrige
  le grep pattern 4 -> >=4 lignes. PHASE0_DEGRADATIONS.md ajoute la section
  Phase 2.8 findings.

Compile GREEN. 20/20 tests rig GREEN. Net LOC src : -239 (strip dead code
+ guards player).

Call sites player no-op (attendu par design) :
- PacketSyncPetBedState.playAnimation -> V3-REW-12
- PacketPlayTestAnimation.playAnimation (debug) -> V3-REW-14
- FurnitureClientAnimator.playFurniture -> furniture seat rework V3
This commit is contained in:
notevil
2026-04-23 01:10:02 +02:00
parent 5a39fb0c1c
commit f80dc68c0b
3 changed files with 134 additions and 352 deletions

View File

@@ -1,13 +1,11 @@
package com.tiedup.remake.client.animation;
import com.mojang.logging.LogUtils;
import com.tiedup.remake.v2.furniture.ISeatProvider;
import dev.kosmx.playerAnim.api.layered.IAnimation;
import dev.kosmx.playerAnim.api.layered.KeyframeAnimationPlayer;
import dev.kosmx.playerAnim.api.layered.ModifierLayer;
import dev.kosmx.playerAnim.core.data.KeyframeAnimation;
import dev.kosmx.playerAnim.impl.IAnimatedPlayer;
import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationAccess;
import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationFactory;
import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationRegistry;
import java.util.Map;
@@ -15,7 +13,6 @@ import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraftforge.api.distmarker.Dist;
@@ -39,81 +36,42 @@ public class BondageAnimationManager {
private static final Logger LOGGER = LogUtils.getLogger();
/** Cache of ModifierLayers for NPC entities (players use PlayerAnimationAccess) */
/** Cache of item-layer ModifierLayers for NPC entities. */
private static final Map<UUID, ModifierLayer<IAnimation>> npcLayers =
new ConcurrentHashMap<>();
/** Cache of context ModifierLayers for NPC entities */
/** Cache of context-layer ModifierLayers for NPC entities. */
private static final Map<UUID, ModifierLayer<IAnimation>> npcContextLayers =
new ConcurrentHashMap<>();
/** Cache of furniture ModifierLayers for NPC entities */
private static final Map<
UUID,
ModifierLayer<IAnimation>
> npcFurnitureLayers = new ConcurrentHashMap<>();
/** Factory ID for PlayerAnimator item layer (players only) */
private static final ResourceLocation FACTORY_ID =
ResourceLocation.fromNamespaceAndPath("tiedup", "bondage");
/** Factory ID for PlayerAnimator context layer (players only) */
private static final ResourceLocation CONTEXT_FACTORY_ID =
ResourceLocation.fromNamespaceAndPath("tiedup", "bondage_context");
/** Factory ID for PlayerAnimator furniture layer (players only) */
private static final ResourceLocation FURNITURE_FACTORY_ID =
ResourceLocation.fromNamespaceAndPath("tiedup", "bondage_furniture");
/** Priority for context animation layer (lower = overridable by item layer) */
private static final int CONTEXT_LAYER_PRIORITY = 40;
/** Priority for item animation layer (higher = overrides context layer) */
private static final int ITEM_LAYER_PRIORITY = 42;
/**
* Priority for furniture animation layer (highest = overrides item layer on blocked bones).
* Non-blocked bones are disabled so items can still animate them via the item layer.
*/
private static final int FURNITURE_LAYER_PRIORITY = 43;
/** Number of ticks to wait before removing a stale furniture animation. */
private static final int FURNITURE_GRACE_TICKS = 3;
/**
* Tracks ticks since a player with an active furniture animation stopped riding
* an ISeatProvider. After {@link #FURNITURE_GRACE_TICKS}, the animation is removed
* to prevent stuck poses from entity death or network issues.
*
* <p>Uses ConcurrentHashMap for safe access from both client tick and render thread.</p>
*/
private static final Map<UUID, Integer> furnitureGraceTicks =
new ConcurrentHashMap<>();
/**
* Initialize the animation system.
*
* <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><b>Pipeline NPC-only</b> — depuis Phase 2.7, les joueurs sont tickés par
* {@code RigAnimationTickHandler} via le renderer RIG patched. Aucune
* {@link PlayerAnimationFactory} n'est enregistrée pour le joueur et tous
* les chemins joueur dans cette classe sont de short-circuits logués.</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>Cette classe reste active <b>uniquement pour les NPCs</b>
* (entités implémentant {@link IAnimatedPlayer} qui ne sont pas un
* {@link Player}) : {@link #getOrCreateLayer} leur crée un {@link ModifierLayer}
* via accès direct au stack d'animation
* ({@code animated.getAnimationStack().addAnimLayer(...)}) — ce path ne dépend
* d'aucune factory. Consumer principal : {@code NpcAnimationTickHandler}.</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>
* externes. Rework V3 (player anim natives RIG) : voir V3-REW-01 dans
* {@code docs/plans/rig/V3_REWORK_BACKLOG.md}.</p>
*/
public static void init() {
LOGGER.info(
"BondageAnimationManager: player-side factories no-op (Phase 2.8 RIG cleanup). " +
"NPC-side animation stack access untouched."
"BondageAnimationManager: NPC-only pipeline (Phase 2.8 RIG cleanup). " +
"Players handled by RigAnimationTickHandler; all player call sites no-op."
);
}
@@ -140,6 +98,11 @@ public class BondageAnimationManager {
* <p>If the animation layer is not available (e.g., remote player not fully
* initialized), the animation will be queued for retry via PendingAnimationManager.
*
* <p><b>Phase 2.8</b> — les appels sur un {@link Player} sont no-op : le pipeline
* joueur est désormais RIG-native (voir {@link #init} Javadoc). Un WARN est logué
* une fois par UUID pour signaler les call sites stale qui devraient être purgés
* lors du rework V3.</p>
*
* @param entity The entity to animate
* @param animId Full ResourceLocation of the animation
* @return true if animation started successfully, false if layer not available
@@ -152,6 +115,12 @@ public class BondageAnimationManager {
return false;
}
// Phase 2.8 : player path is dead. Log once per UUID and no-op.
if (entity instanceof Player player) {
logPlayerCallOnce(player, "playAnimation(" + animId + ")");
return false;
}
KeyframeAnimation anim = PlayerAnimationRegistry.getAnimation(animId);
if (anim == null) {
// Try fallback: remove _sneak_ suffix if present
@@ -188,7 +157,7 @@ public class BondageAnimationManager {
}
layer.setAnimation(new KeyframeAnimationPlayer(anim));
// Remove from pending queue if it was waiting
// Remove from pending queue if it was waiting (legacy, may still hold NPC entries)
PendingAnimationManager.remove(entity.getUUID());
LOGGER.debug(
@@ -198,23 +167,11 @@ public class BondageAnimationManager {
);
return true;
} else {
// Layer not available - queue for retry if it's a player
if (entity instanceof AbstractClientPlayer) {
PendingAnimationManager.queueForRetry(
entity.getUUID(),
animId.getPath()
);
LOGGER.debug(
"Animation layer not ready for {}, queued for retry",
entity.getName().getString()
);
} else {
LOGGER.warn(
"Animation layer is NULL for NPC: {} (type: {})",
entity.getName().getString(),
entity.getClass().getSimpleName()
);
}
LOGGER.warn(
"Animation layer is NULL for NPC: {} (type: {})",
entity.getName().getString(),
entity.getClass().getSimpleName()
);
return false;
}
}
@@ -235,6 +192,12 @@ public class BondageAnimationManager {
return false;
}
// Phase 2.8 : player path is dead.
if (entity instanceof Player player) {
logPlayerCallOnce(player, "playDirect");
return false;
}
ModifierLayer<IAnimation> layer = getOrCreateLayer(entity);
if (layer != null) {
IAnimation current = layer.getAnimation();
@@ -262,6 +225,11 @@ public class BondageAnimationManager {
return;
}
// Phase 2.8 : player path is dead — no layer to clear.
if (entity instanceof Player) {
return;
}
ModifierLayer<IAnimation> layer = getLayer(entity);
if (layer != null) {
layer.setAnimation(null);
@@ -273,56 +241,36 @@ public class BondageAnimationManager {
/**
* Get the ModifierLayer for an entity (without creating).
*
* <p>Phase 2.8 : returns {@code null} directly for any {@link Player} — the
* player animation pipeline is RIG-native, this manager only tracks NPCs.</p>
*/
private static ModifierLayer<IAnimation> getLayer(LivingEntity entity) {
// Players: try PlayerAnimationAccess first, then cache
if (entity instanceof AbstractClientPlayer player) {
ModifierLayer<IAnimation> factoryLayer = getPlayerLayer(player);
if (factoryLayer != null) {
return factoryLayer;
}
// Check cache (for remote players using fallback)
return npcLayers.get(entity.getUUID());
if (entity instanceof Player) {
return null;
}
// NPCs: use cache
return npcLayers.get(entity.getUUID());
}
/**
* Get or create the ModifierLayer for an entity.
*
* <p>Phase 2.8 : returns {@code null} directly for any {@link Player} — the
* player fallback via {@code IAnimatedPlayer.getAnimationStack()} has been
* retired because it was partially alive (FP vanilla render consumed it,
* TP RIG override bypassed it), producing a confusing behavior split. All
* player anim needs are now handled by {@code RigAnimationTickHandler}.</p>
*/
@SuppressWarnings("unchecked")
private static ModifierLayer<IAnimation> getOrCreateLayer(
LivingEntity entity
) {
UUID uuid = entity.getUUID();
// Players: try factory-based access first, fallback to direct stack access
if (entity instanceof AbstractClientPlayer player) {
// Try the registered factory first (works for local player)
ModifierLayer<IAnimation> factoryLayer = getPlayerLayer(player);
if (factoryLayer != null) {
return factoryLayer;
}
// Fallback for remote players: use direct stack access like NPCs
// This handles cases where the factory data isn't available
if (player instanceof IAnimatedPlayer animated) {
return npcLayers.computeIfAbsent(uuid, k -> {
ModifierLayer<IAnimation> newLayer = new ModifierLayer<>();
animated
.getAnimationStack()
.addAnimLayer(ITEM_LAYER_PRIORITY, newLayer);
LOGGER.info(
"Created animation layer for remote player via stack: {}",
player.getName().getString()
);
return newLayer;
});
}
// Phase 2.8 : strip player path entirely (no partially-alive fallback).
if (entity instanceof Player) {
return null;
}
UUID uuid = entity.getUUID();
// NPCs implementing IAnimatedPlayer: create/cache layer
if (entity instanceof IAnimatedPlayer animated) {
return npcLayers.computeIfAbsent(uuid, k -> {
@@ -342,87 +290,49 @@ public class BondageAnimationManager {
return null;
}
/** Per-player dedup set so we log the factory-access failure at most once per UUID. */
private static final java.util.Set<UUID> layerFailureLogged =
/** Per-player-UUID dedup so stale call sites log at most once per session. */
private static final java.util.Set<UUID> playerCallLogged =
java.util.concurrent.ConcurrentHashMap.newKeySet();
/**
* Get the animation layer for a player from PlayerAnimationAccess.
*
* <p>Throws during the factory-race window for remote players (the factory
* hasn't yet initialized their associated data). This is the expected path
* for the {@link PendingAnimationManager} retry loop, so we log at DEBUG
* and at most once per UUID — a per-tick log would flood during busy
* multiplayer.</p>
* Log once per player UUID that a stale call site is invoking this manager.
* Used by the player no-op short-circuits ({@link #playAnimation},
* {@link #playDirect}) to surface call sites that should be migrated to the
* RIG pipeline (tracked in V3_REWORK_BACKLOG).
*/
@SuppressWarnings("unchecked")
private static ModifierLayer<IAnimation> getPlayerLayer(
AbstractClientPlayer player
) {
try {
return (ModifierLayer<
IAnimation
>) PlayerAnimationAccess.getPlayerAssociatedData(player).get(
FACTORY_ID
private static void logPlayerCallOnce(Player player, String op) {
if (playerCallLogged.add(player.getUUID())) {
LOGGER.warn(
"BondageAnimationManager.{} called on player {} — no-op " +
"(RIG owns player anims since Phase 2.7). " +
"Migrate call site to RigAnimationTickHandler (V3 rework).",
op,
player.getName().getString()
);
} catch (Exception e) {
if (layerFailureLogged.add(player.getUUID())) {
LOGGER.debug(
"Animation layer not yet available for player {} (will retry): {}",
player.getName().getString(),
e.toString()
);
}
return null;
}
}
/**
* Safely get the animation layer for a player.
* Returns null if the layer is not yet initialized.
*
* <p>Public method for PendingAnimationManager to access.
* Checks both the factory-based layer and the NPC cache fallback.
* <p>Phase 2.8 : always returns {@code null}. The player pipeline is
* RIG-native; the {@link PendingAnimationManager} retry loop is no
* longer fed (player calls to {@link #playAnimation} short-circuit
* before queueing), so this getter is maintained only to preserve the
* public signature for external call sites.</p>
*
* @param player The player
* @return The animation layer, or null if not available
* @param player The player (unused)
* @return always null in Phase 2.8+
*/
@javax.annotation.Nullable
public static ModifierLayer<IAnimation> getPlayerLayerSafe(
AbstractClientPlayer player
) {
// Try factory first
ModifierLayer<IAnimation> factoryLayer = getPlayerLayer(player);
if (factoryLayer != null) {
return factoryLayer;
}
// Check NPC cache (for remote players using fallback path)
return npcLayers.get(player.getUUID());
return null;
}
// CONTEXT LAYER (lower priority, for sit/kneel/sneak)
/**
* Get the context animation layer for a player from PlayerAnimationAccess.
* Returns null if the layer is not yet initialized.
*/
@SuppressWarnings("unchecked")
@javax.annotation.Nullable
private static ModifierLayer<IAnimation> getPlayerContextLayer(
AbstractClientPlayer player
) {
try {
return (ModifierLayer<
IAnimation
>) PlayerAnimationAccess.getPlayerAssociatedData(player).get(
CONTEXT_FACTORY_ID
);
} catch (Exception e) {
return null;
}
}
/**
* Get or create the context animation layer for an NPC entity.
* Uses CONTEXT_LAYER_PRIORITY, below the item layer at ITEM_LAYER_PRIORITY.
@@ -460,13 +370,14 @@ public class BondageAnimationManager {
return false;
}
ModifierLayer<IAnimation> layer;
if (entity instanceof AbstractClientPlayer player) {
layer = getPlayerContextLayer(player);
} else {
layer = getOrCreateNpcContextLayer(entity);
// Phase 2.8 : player context layer is dead (sit/kneel/sneak visuals
// will be re-expressed as RIG StaticAnimations — cf. V3-REW-14).
if (entity instanceof Player player) {
logPlayerCallOnce(player, "playContext");
return false;
}
ModifierLayer<IAnimation> layer = getOrCreateNpcContextLayer(entity);
if (layer != null) {
layer.setAnimation(new KeyframeAnimationPlayer(anim));
return true;
@@ -484,13 +395,12 @@ public class BondageAnimationManager {
return;
}
ModifierLayer<IAnimation> layer;
if (entity instanceof AbstractClientPlayer player) {
layer = getPlayerContextLayer(player);
} else {
layer = npcContextLayers.get(entity.getUUID());
// Phase 2.8 : player path is dead — no layer to clear.
if (entity instanceof Player) {
return;
}
ModifierLayer<IAnimation> layer = npcContextLayers.get(entity.getUUID());
if (layer != null) {
layer.setAnimation(null);
}
@@ -522,194 +432,46 @@ public class BondageAnimationManager {
return false;
}
ModifierLayer<IAnimation> layer = getOrCreateFurnitureLayer(player);
if (layer != null) {
layer.setAnimation(new KeyframeAnimationPlayer(animation));
// Reset grace ticks since we just started/refreshed the animation
furnitureGraceTicks.remove(player.getUUID());
LOGGER.debug(
"Playing furniture animation on player: {}",
player.getName().getString()
);
return true;
}
LOGGER.warn(
"Furniture layer not available for player: {}",
player.getName().getString()
);
// Phase 2.8 : player furniture seat pose is dead (will be ported to
// RIG StaticAnimations — cf. V3_REWORK_BACKLOG furniture seat entry).
logPlayerCallOnce(player, "playFurniture");
return false;
}
/**
* Stop the furniture layer animation for a player.
*
* <p>Phase 2.8 : no-op — the player furniture layer is dead. Kept for
* signature compatibility with {@code EntityFurniture} cleanup call site.</p>
*
* @param player the player whose furniture animation should stop
*/
public static void stopFurniture(Player player) {
if (player == null || !player.level().isClientSide()) {
return;
}
ModifierLayer<IAnimation> layer = getFurnitureLayer(player);
if (layer != null) {
layer.setAnimation(null);
}
furnitureGraceTicks.remove(player.getUUID());
LOGGER.debug(
"Stopped furniture animation on player: {}",
player.getName().getString()
);
// Phase 2.8 : dead path. Retained signature for backward-compat.
}
/**
* Check whether a player currently has an active furniture animation.
*
* <p>Phase 2.8 : always returns {@code false} — player furniture layer is dead.</p>
*
* @param player the player to check
* @return true if the furniture layer has an active animation
* @return always false in Phase 2.8+
*/
public static boolean hasFurnitureAnimation(Player player) {
if (player == null || !player.level().isClientSide()) {
return false;
}
ModifierLayer<IAnimation> layer = getFurnitureLayer(player);
return layer != null && layer.getAnimation() != null;
return false;
}
/**
* Get the furniture ModifierLayer for a player (READ-ONLY).
* Uses PlayerAnimationAccess for local/factory-registered players,
* falls back to NPC cache for remote players. Returns null if no layer
* has been created yet — callers that need to guarantee a layer should use
* {@link #getOrCreateFurnitureLayer}.
*/
@SuppressWarnings("unchecked")
@javax.annotation.Nullable
private static ModifierLayer<IAnimation> getFurnitureLayer(Player player) {
if (player instanceof AbstractClientPlayer clientPlayer) {
try {
ModifierLayer<IAnimation> layer = (ModifierLayer<
IAnimation
>) PlayerAnimationAccess.getPlayerAssociatedData(
clientPlayer
).get(FURNITURE_FACTORY_ID);
if (layer != null) {
return layer;
}
} catch (Exception e) {
// Fall through to NPC cache
}
// Fallback for remote players: check NPC furniture cache
return npcFurnitureLayers.get(player.getUUID());
}
// Non-player entities: use NPC cache
return npcFurnitureLayers.get(player.getUUID());
}
/**
* Get or create the furniture ModifierLayer for a player. Mirrors
* {@link #getOrCreateLayer} but for the FURNITURE layer priority.
* Safety tick for furniture animations.
*
* <p>For the local player (factory-registered), returns the factory layer.
* For remote players, creates a new layer on first call and caches it in
* {@link #npcFurnitureLayers} — remote players don't own a factory layer,
* so without a fallback they can't receive any furniture seat pose.</p>
*/
@SuppressWarnings("unchecked")
@javax.annotation.Nullable
private static ModifierLayer<IAnimation> getOrCreateFurnitureLayer(
Player player
) {
if (player instanceof AbstractClientPlayer clientPlayer) {
try {
ModifierLayer<IAnimation> layer = (ModifierLayer<
IAnimation
>) PlayerAnimationAccess.getPlayerAssociatedData(
clientPlayer
).get(FURNITURE_FACTORY_ID);
if (layer != null) {
return layer;
}
} catch (Exception e) {
// Fall through to fallback-create below.
}
// Remote players: fallback-create via the animation stack.
if (clientPlayer instanceof IAnimatedPlayer animated) {
return npcFurnitureLayers.computeIfAbsent(
clientPlayer.getUUID(),
k -> {
ModifierLayer<IAnimation> newLayer =
new ModifierLayer<>();
animated
.getAnimationStack()
.addAnimLayer(FURNITURE_LAYER_PRIORITY, newLayer);
LOGGER.debug(
"Created furniture animation layer for remote player via stack: {}",
clientPlayer.getName().getString()
);
return newLayer;
}
);
}
return npcFurnitureLayers.get(clientPlayer.getUUID());
}
// Non-player entities: use NPC cache (read-only; NPC furniture animation
// is not currently produced by this codebase).
return npcFurnitureLayers.get(player.getUUID());
}
/**
* Safety tick for furniture animations. Call once per client tick per player.
* <p>Phase 2.8 : no-op — the player furniture layer is dead, nothing to
* guard. Kept as an empty stub in case older call sites remain.</p>
*
* <p>If a player has an active furniture animation but is NOT riding an
* {@link ISeatProvider}, increment a grace counter. After
* {@link #FURNITURE_GRACE_TICKS} consecutive ticks without a seat, the
* animation is removed to prevent stuck poses from entity death, network
* desync, or teleportation.</p>
*
* <p>If the player IS riding an ISeatProvider, the counter is reset.</p>
*
* @param player the player to check
* @param player the player to check (unused)
*/
public static void tickFurnitureSafety(Player player) {
if (player == null || !player.level().isClientSide()) {
return;
}
if (!hasFurnitureAnimation(player)) {
// No furniture animation active, nothing to guard
furnitureGraceTicks.remove(player.getUUID());
return;
}
UUID uuid = player.getUUID();
// Check if the player is riding an ISeatProvider
Entity vehicle = player.getVehicle();
boolean ridingSeat = vehicle instanceof ISeatProvider;
if (ridingSeat) {
// Player is properly seated, reset grace counter
furnitureGraceTicks.remove(uuid);
} else {
// Player has furniture anim but no seat -- increment grace
int ticks = furnitureGraceTicks.merge(uuid, 1, Integer::sum);
if (ticks >= FURNITURE_GRACE_TICKS) {
LOGGER.info(
"Removing stale furniture animation for player {} " +
"(not riding ISeatProvider for {} ticks)",
player.getName().getString(),
ticks
);
stopFurniture(player);
}
}
// Phase 2.8 : dead path. Retained signature for backward-compat.
}
// FALLBACK ANIMATION HANDLING
@@ -778,8 +540,9 @@ public class BondageAnimationManager {
* @param entityId UUID of the removed entity
*/
/** All NPC layer caches, for bulk cleanup operations. */
@SuppressWarnings({ "unchecked", "rawtypes" })
private static final Map<UUID, ModifierLayer<IAnimation>>[] ALL_NPC_CACHES =
new Map[] { npcLayers, npcContextLayers, npcFurnitureLayers };
new Map[] { npcLayers, npcContextLayers };
public static void cleanup(UUID entityId) {
for (Map<UUID, ModifierLayer<IAnimation>> cache : ALL_NPC_CACHES) {
@@ -788,8 +551,7 @@ public class BondageAnimationManager {
layer.setAnimation(null);
}
}
furnitureGraceTicks.remove(entityId);
layerFailureLogged.remove(entityId);
playerCallLogged.remove(entityId);
LOGGER.debug("Cleaned up animation layers for entity: {}", entityId);
}
@@ -802,8 +564,7 @@ public class BondageAnimationManager {
cache.values().forEach(layer -> layer.setAnimation(null));
cache.clear();
}
furnitureGraceTicks.clear();
layerFailureLogged.clear();
playerCallLogged.clear();
LOGGER.info("Cleared all NPC animation layers");
}
}

View File

@@ -52,8 +52,15 @@ public class DogPoseRenderHandler {
/**
* Get the rotation delta applied to a player's render for DOG pose.
* Used by MixinPlayerModel to compensate head rotation.
*
* @deprecated since Phase 2.8 — this getter fed {@code MixinPlayerModel}
* (removed Phase 2.8 RIG cleanup) so head rotation could be compensated
* against the body's -90° pitch. No remaining reader. To be deleted
* when V3-REW-07 re-expresses dog pose head compensation as a RIG
* {@code StaticAnimation pose_dog.json}. See
* {@code docs/plans/rig/V3_REWORK_BACKLOG.md#V3-REW-07}.
*/
@Deprecated(since = "2.8")
public static float getAppliedRotationDelta(UUID playerUuid) {
float[] state = dogPoseState.get(playerUuid);
return state != null ? state[IDX_DELTA] : 0f;
@@ -61,7 +68,14 @@ public class DogPoseRenderHandler {
/**
* Check if a player is currently moving in DOG pose.
*
* @deprecated since Phase 2.8 — same cause as {@link #getAppliedRotationDelta}
* (fed {@code MixinPlayerModel}, now removed). To be deleted alongside
* V3-REW-07 when dog pose head compensation is re-expressed as a RIG
* {@code StaticAnimation pose_dog.json}. See
* {@code docs/plans/rig/V3_REWORK_BACKLOG.md#V3-REW-07}.
*/
@Deprecated(since = "2.8")
public static boolean isDogPoseMoving(UUID playerUuid) {
float[] state = dogPoseState.get(playerUuid);
return state != null && state[IDX_MOVING] > 0.5f;

View File

@@ -110,7 +110,14 @@ public final class DogPoseHelper {
* @param headPitch Player's up/down look angle in degrees
* @param headYaw Head yaw relative to body in degrees
* @param maxYaw Maximum allowed yaw angle in degrees
* @deprecated since Phase 2.8 — player dog pose head compensation was
* previously applied via {@code MixinPlayerModel.setupAnim @TAIL}
* (removed Phase 2.8 RIG cleanup). No remaining call site; retained
* only to preserve the API until V3-REW-07 re-expresses the behavior
* as a RIG {@code StaticAnimation pose_dog.json}. See
* {@code docs/plans/rig/V3_REWORK_BACKLOG.md#V3-REW-07}.
*/
@Deprecated(since = "2.8")
public static void applyHeadCompensationClamped(
ModelPart head,
ModelPart hat,