diff --git a/src/main/java/com/tiedup/remake/client/gltf/GltfAnimationApplier.java b/src/main/java/com/tiedup/remake/client/gltf/GltfAnimationApplier.java index b095ba6..79850a9 100644 --- a/src/main/java/com/tiedup/remake/client/gltf/GltfAnimationApplier.java +++ b/src/main/java/com/tiedup/remake/client/gltf/GltfAnimationApplier.java @@ -7,6 +7,7 @@ import com.tiedup.remake.client.animation.context.GlbAnimationResolver; import com.tiedup.remake.client.animation.context.RegionBoneMapper; import dev.kosmx.playerAnim.core.data.KeyframeAnimation; import java.util.HashSet; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; @@ -251,7 +252,40 @@ public final class GltfAnimationApplier { } // === Layer 2: Composite item animation === - String compositeCacheKey = "multi#" + stateKey; + // Pre-resolve animation data and variant names for all items BEFORE cache lookup. + // This ensures random variant selection happens fresh on each context change, + // and each variant combination gets its own cache entry. + record ResolvedItem( + GltfData animData, + String glbAnimName, + RegionBoneMapper.V2ItemAnimInfo info + ) {} + + List resolvedItems = new ArrayList<>(); + StringBuilder variantKeyBuilder = new StringBuilder("multi#").append(stateKey); + + for (RegionBoneMapper.V2ItemAnimInfo item : items) { + ResourceLocation animSource = + item.animSource() != null ? item.animSource() : item.modelLoc(); + + GltfData animData = GlbAnimationResolver.resolveAnimationData( + item.modelLoc(), item.animSource() + ); + if (animData == null) { + LOGGER.warn( + "[GltfPipeline] Failed to load GLB for multi-item: {}", + animSource + ); + continue; + } + + String glbAnimName = GlbAnimationResolver.resolve(animData, context); + resolvedItems.add(new ResolvedItem(animData, glbAnimName, item)); + variantKeyBuilder.append('#') + .append(glbAnimName != null ? glbAnimName : "default"); + } + + String compositeCacheKey = variantKeyBuilder.toString(); if (failedLoadKeys.contains(compositeCacheKey)) { activeStateKeys.put(entity.getUUID(), stateKey); @@ -273,29 +307,13 @@ public final class GltfAnimationApplier { boolean anyLoaded = false; - for (int i = 0; i < items.size(); i++) { - RegionBoneMapper.V2ItemAnimInfo item = items.get(i); + for (ResolvedItem resolved : resolvedItems) { + RegionBoneMapper.V2ItemAnimInfo item = resolved.info(); + GltfData animData = resolved.animData(); + String glbAnimName = resolved.glbAnimName(); ResourceLocation animSource = - item.animSource() != null - ? item.animSource() - : item.modelLoc(); + item.animSource() != null ? item.animSource() : item.modelLoc(); - GltfData animData = GlbAnimationResolver.resolveAnimationData( - item.modelLoc(), - item.animSource() - ); - if (animData == null) { - LOGGER.warn( - "[GltfPipeline] Failed to load GLB for multi-item: {}", - animSource - ); - continue; - } - - String glbAnimName = GlbAnimationResolver.resolve( - animData, - context - ); GltfData.AnimationClip rawClip; if (glbAnimName != null) { rawClip = animData.getRawAnimation(glbAnimName); @@ -310,9 +328,7 @@ public final class GltfAnimationApplier { // if the item declares per-animation bone filtering. Set effectiveParts = item.ownedParts(); if (glbAnimName != null && !item.animationBones().isEmpty()) { - Set override = item - .animationBones() - .get(glbAnimName); + Set override = item.animationBones().get(glbAnimName); if (override != null) { Set filtered = new HashSet<>(override); filtered.retainAll(item.ownedParts()); @@ -323,19 +339,13 @@ public final class GltfAnimationApplier { } GltfPoseConverter.addBonesToBuilder( - builder, - animData, - rawClip, - effectiveParts + builder, animData, rawClip, effectiveParts ); anyLoaded = true; LOGGER.debug( "[GltfPipeline] Multi-item: {} -> owned={}, effective={}, anim={}", - animSource, - item.ownedParts(), - effectiveParts, - glbAnimName + animSource, item.ownedParts(), effectiveParts, glbAnimName ); }