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:
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user