Hide the cloak during emotes that curve the torso

A flat cloak can't follow an arched back, so on the twerk it cut through the player. When the
emote being played bends the torso more than 10 degrees at any point, the cloak is hidden for the
whole emote; hiding it only while the back is curved would make it flicker. Each emote is checked
once and cached. Emotes that bend the torso less still hang the cloak from the leaning chest.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-09-29 18:34:34 +02:00
co-authored by claude
parent 8b1c94151a
commit ba5a32fb34
@@ -1,6 +1,9 @@
package org.saturnclient.impl.cosmetics;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.WeakHashMap;
import org.joml.Matrix3f;
import org.joml.Matrix4f;
@@ -11,6 +14,8 @@ import org.saturnclient.client.player.SaturnPlayer;
import org.saturnclient.common.ref.asset.IdentifierRef;
import org.saturnclient.config.Config;
import org.saturnclient.cosmetics.Cloaks;
import org.saturnclient.emotes.core.Animation;
import org.saturnclient.emotes.core.Bone;
import org.saturnclient.emotes.core.bend.LimbBend;
import org.saturnclient.emotes.core.bend.Vec3;
import org.saturnclient.emotes.fabric.access.BendablePart;
@@ -387,9 +392,34 @@ public class CloakFeatureRenderer extends FeatureRenderer<PlayerEntityRenderStat
}
}
/** A torso bend past this curves the back more than a flat cloak can follow. */
private static final float MAX_CLOAK_BEND = (float) Math.toRadians(10);
/** Whether each animation bends the torso past that at any point. */
private static final Map<Animation, Boolean> BENDS_TORSO = Collections.synchronizedMap(new WeakHashMap<>());
/**
* An emote that bends the torso leans the chest from the hips, so the cloak hangs from where the
* top of the back ends up, turned as much as the torso is there.
* Whether the player is playing an emote that curves the torso (a twerk, the griddy). The cloak
* would cut through the arched back, so it's hidden for the whole emote rather than only while
* the back is curved, which would flicker.
*/
private static boolean playingTorsoBend(SaturnPlayer player) {
return org.saturnclient.emotes.fabric.Emotes.playing(player.uuid)
.map(playing -> BENDS_TORSO.computeIfAbsent(playing.animation(), CloakFeatureRenderer::bendsTorso))
.orElse(false);
}
private static boolean bendsTorso(Animation animation) {
for (float tick = 0; tick <= animation.length(); tick += 0.5f) {
if (animation.sample(tick).get(Bone.BODY).filter(body -> Math.abs(body.bend()) > MAX_CLOAK_BEND).isPresent()) {
return true;
}
}
return false;
}
/**
* An emote that bends the torso a little leans the chest from the hips, so the cloak hangs from
* where the top of the back ends up, turned as much as the torso is there.
*/
private static void followBend(MatrixStack matrices, ModelPart body) {
LimbBend bend = ((BendablePart) (Object) body).saturnEmotes$getBend();
@@ -439,7 +469,7 @@ public class CloakFeatureRenderer extends FeatureRenderer<PlayerEntityRenderStat
//?} else
//SaturnPlayer player = SaturnPlayer.get(state.name);
if (player == null) {
if (player == null || playingTorsoBend(player)) {
return;
}