Clean repo for open source release
Remove build artifacts, dev tool configs, unused dependencies, and third-party source dumps. Add proper README, update .gitignore, clean up Makefile.
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
package com.tiedup.remake.mixin.client;
|
||||
|
||||
import com.tiedup.remake.items.base.ItemBind;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
import com.tiedup.remake.items.base.PoseType;
|
||||
import com.tiedup.remake.state.HumanChairHelper;
|
||||
import com.tiedup.remake.state.PlayerBindState;
|
||||
import net.minecraft.client.Camera;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
/**
|
||||
* Mixin for Camera to lower first-person view when in DOG pose.
|
||||
*
|
||||
* In DOG pose, the player model is horizontal (like a dog), so the camera
|
||||
* should be lowered to match the eye level being closer to the ground.
|
||||
* Normal eye height is ~1.62 blocks, we lower it by ~0.6 blocks.
|
||||
*/
|
||||
@Mixin(Camera.class)
|
||||
public abstract class MixinCamera {
|
||||
|
||||
@Shadow
|
||||
private Vec3 position;
|
||||
|
||||
@Shadow
|
||||
protected abstract void setPosition(Vec3 pos);
|
||||
|
||||
@Inject(method = "setup", at = @At("TAIL"))
|
||||
private void tiedup$lowerCameraForDogPose(
|
||||
BlockGetter level,
|
||||
Entity entity,
|
||||
boolean detached,
|
||||
boolean thirdPersonReverse,
|
||||
float partialTick,
|
||||
CallbackInfo ci
|
||||
) {
|
||||
// Only affect first-person view
|
||||
if (detached) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(entity instanceof Player player)) {
|
||||
return;
|
||||
}
|
||||
|
||||
PlayerBindState state = PlayerBindState.getInstance(player);
|
||||
if (state == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ItemStack bind = state.getEquipment(BodyRegionV2.ARMS);
|
||||
if (bind.isEmpty() || !(bind.getItem() instanceof ItemBind itemBind)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (itemBind.getPoseType() != PoseType.DOG) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Lower camera by 0.6 blocks to match the horizontal body position
|
||||
// Normal eye height is ~1.62, DOG pose should be around ~1.0
|
||||
setPosition(position.add(0, -0.6, 0));
|
||||
|
||||
// Human chair: move camera forward into the head
|
||||
if (HumanChairHelper.isActive(bind)) {
|
||||
float facing = HumanChairHelper.getFacing(bind);
|
||||
float facingRad = (float) Math.toRadians(facing);
|
||||
double fwdX = -Math.sin(facingRad) * 0.6;
|
||||
double fwdZ = Math.cos(facingRad) * 0.6;
|
||||
setPosition(position.add(fwdX, 0, fwdZ));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.tiedup.remake.mixin.client;
|
||||
|
||||
import com.tiedup.remake.client.state.PetBedClientState;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
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.CallbackInfoReturnable;
|
||||
|
||||
/**
|
||||
* Client-side mixin: prevent vanilla sleeping visuals (laying flat) for
|
||||
* players on a pet bed in SLEEP mode. Server-side isSleeping() is unaffected,
|
||||
* so night skip still works.
|
||||
*/
|
||||
@Mixin(LivingEntity.class)
|
||||
public abstract class MixinLivingEntitySleeping {
|
||||
|
||||
@Inject(method = "isSleeping", at = @At("HEAD"), cancellable = true)
|
||||
private void tiedup$hidePetBedSleeping(
|
||||
CallbackInfoReturnable<Boolean> cir
|
||||
) {
|
||||
LivingEntity self = (LivingEntity) (Object) this;
|
||||
if (self.level().isClientSide() && self instanceof Player player) {
|
||||
if (PetBedClientState.get(player.getUUID()) == 2) {
|
||||
cir.setReturnValue(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package com.tiedup.remake.mixin.client;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
import com.tiedup.remake.compat.wildfire.WildfireCompat;
|
||||
import net.minecraft.client.model.geom.ModelPart;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Pseudo;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
/**
|
||||
* Mixin for MCA's PlayerEntityExtendedModel to hide breasts when Wildfire is loaded.
|
||||
*
|
||||
* <p>MCA adds breasts to players through PlayerEntityExtendedModel. When Wildfire
|
||||
* is also installed, we disable MCA's breasts to avoid double-rendering (Wildfire
|
||||
* renders its own breasts with physics).
|
||||
*
|
||||
* <p>Uses @Pseudo annotation - mixin is optional and will be skipped if MCA is not installed.
|
||||
*
|
||||
* <p>Target class: forge.net.mca.client.model.PlayerEntityExtendedModel
|
||||
*/
|
||||
@Pseudo
|
||||
@Mixin(
|
||||
targets = "forge.net.mca.client.model.PlayerEntityExtendedModel",
|
||||
remap = false
|
||||
)
|
||||
public class MixinMCAPlayerExtendedModel<T extends LivingEntity> {
|
||||
|
||||
/**
|
||||
* Shadow the breasts ModelPart to control visibility.
|
||||
*/
|
||||
@Shadow(remap = false)
|
||||
public ModelPart breasts;
|
||||
|
||||
/**
|
||||
* Shadow the breastsWear ModelPart (overlay layer).
|
||||
*/
|
||||
@Shadow(remap = false)
|
||||
public ModelPart breastsWear;
|
||||
|
||||
/**
|
||||
* Inject at the end of setAngles (m_6973_) to hide MCA breasts when Wildfire is loaded.
|
||||
*
|
||||
* <p>This runs after applyVillagerDimensions() which sets breast visibility.
|
||||
*/
|
||||
@Inject(method = "m_6973_", at = @At("TAIL"), remap = false)
|
||||
private void tiedup$hideBreastsInSetAngles(
|
||||
T entity,
|
||||
float limbSwing,
|
||||
float limbSwingAmount,
|
||||
float ageInTicks,
|
||||
float netHeadYaw,
|
||||
float headPitch,
|
||||
CallbackInfo ci
|
||||
) {
|
||||
tiedup$hideBreastsIfWildfire();
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject at the end of copyVisibility to prevent it from re-enabling breasts.
|
||||
*
|
||||
* <p>MCA's copyVisibility sets breasts.visible = model.body.visible.
|
||||
*/
|
||||
@Inject(method = "copyVisibility", at = @At("TAIL"), remap = false)
|
||||
private void tiedup$hideBreastsAfterCopyVisibility(CallbackInfo ci) {
|
||||
tiedup$hideBreastsIfWildfire();
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject at the end of render to hide breasts after breastsWear visibility is set.
|
||||
*
|
||||
* <p>MCA's render() sets breastsWear.visible = jacket.visible before rendering.
|
||||
*/
|
||||
@Inject(method = "m_7695_", at = @At("HEAD"), remap = false)
|
||||
private void tiedup$hideBreastsBeforeRender(
|
||||
PoseStack matrices,
|
||||
VertexConsumer vertices,
|
||||
int light,
|
||||
int overlay,
|
||||
float red,
|
||||
float green,
|
||||
float blue,
|
||||
float alpha,
|
||||
CallbackInfo ci
|
||||
) {
|
||||
tiedup$hideBreastsIfWildfire();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to hide both breast parts when Wildfire is loaded.
|
||||
*/
|
||||
private void tiedup$hideBreastsIfWildfire() {
|
||||
if (WildfireCompat.isLoaded()) {
|
||||
if (breasts != null) {
|
||||
breasts.visible = false;
|
||||
}
|
||||
if (breastsWear != null) {
|
||||
breastsWear.visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.tiedup.remake.mixin.client;
|
||||
|
||||
import com.tiedup.remake.compat.mca.MCACompat;
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.state.IBondageState;
|
||||
import java.util.UUID;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Pseudo;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
/**
|
||||
* Mixin for MCA's SpeechManager to block TTS when villager is gagged.
|
||||
*
|
||||
* <p>MCA's speech system works via TTS (Text-to-Speech) on the client side:
|
||||
* <ol>
|
||||
* <li>Server sends VillagerMessage packet to client</li>
|
||||
* <li>ClientInteractionManagerImpl.handleVillagerMessage() receives it</li>
|
||||
* <li>SpeechManager.onChatMessage() is called</li>
|
||||
* <li>TTS plays the sound</li>
|
||||
* </ol>
|
||||
*
|
||||
* <p>This mixin intercepts onChatMessage and cancels it if the villager is gagged.
|
||||
*
|
||||
* <p>Uses @Pseudo for soft dependency - only applies if MCA is present.
|
||||
* CLIENT SIDE ONLY.
|
||||
*/
|
||||
@Pseudo
|
||||
@Mixin(targets = "forge.net.mca.client.tts.SpeechManager", remap = false)
|
||||
public class MixinMCASpeechManager {
|
||||
|
||||
/**
|
||||
* Inject at HEAD of onChatMessage to cancel TTS for gagged villagers.
|
||||
*
|
||||
* <p>MCA signature: void onChatMessage(Text text, UUID sender)
|
||||
* <p>Note: Text = net.minecraft.network.chat.Component (Yarn mapping)
|
||||
*/
|
||||
@Inject(
|
||||
method = "onChatMessage",
|
||||
at = @At("HEAD"),
|
||||
cancellable = true,
|
||||
require = 0
|
||||
)
|
||||
private void tiedup$cancelGaggedSpeech(
|
||||
Component text,
|
||||
UUID sender,
|
||||
CallbackInfo ci
|
||||
) {
|
||||
try {
|
||||
ClientLevel level = Minecraft.getInstance().level;
|
||||
if (level == null) return;
|
||||
|
||||
// Find entity by UUID in rendered entities
|
||||
for (Entity entity : level.entitiesForRendering()) {
|
||||
if (
|
||||
entity.getUUID().equals(sender) &&
|
||||
entity instanceof LivingEntity living
|
||||
) {
|
||||
IBondageState state = MCACompat.getKidnappedState(living);
|
||||
if (state != null && state.isGagged()) {
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[MCA] Blocked TTS for gagged villager: {}",
|
||||
living.getName().getString()
|
||||
);
|
||||
ci.cancel();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[MCA] TTS cancellation check failed: {}",
|
||||
e.getMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.tiedup.remake.mixin.client;
|
||||
|
||||
import com.tiedup.remake.client.animation.render.DogPoseRenderHandler;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
import com.tiedup.remake.client.animation.util.DogPoseHelper;
|
||||
import com.tiedup.remake.items.base.ItemBind;
|
||||
import com.tiedup.remake.items.base.PoseType;
|
||||
import com.tiedup.remake.state.PlayerBindState;
|
||||
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() || !(bind.getItem() instanceof ItemBind itemBind)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (itemBind.getPoseType() != 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.getId()
|
||||
);
|
||||
boolean moving = DogPoseRenderHandler.isDogPoseMoving(player.getId());
|
||||
|
||||
// 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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
package com.tiedup.remake.mixin.client;
|
||||
|
||||
import com.tiedup.remake.client.animation.BondageAnimationManager;
|
||||
import com.tiedup.remake.client.animation.StaticPoseApplier;
|
||||
import com.tiedup.remake.client.animation.util.AnimationIdBuilder;
|
||||
import com.tiedup.remake.compat.mca.MCACompat;
|
||||
import com.tiedup.remake.items.base.ItemBind;
|
||||
import com.tiedup.remake.items.base.PoseType;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
|
||||
import com.tiedup.remake.state.IBondageState;
|
||||
import dev.kosmx.playerAnim.impl.IAnimatedPlayer;
|
||||
import dev.kosmx.playerAnim.impl.animation.AnimationApplier;
|
||||
import java.util.UUID;
|
||||
import net.minecraft.client.model.HumanoidModel;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Pseudo;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
/**
|
||||
* Mixin for MCA's VillagerEntityBaseModelMCA to apply tied poses.
|
||||
*
|
||||
* <p>This mixin injects at the end of setupAnim (m_6973_) to override MCA's default
|
||||
* animations when a villager is tied up. Without this, the bondage render layer
|
||||
* shows the tied pose but the underlying MCA model still shows normal walking/idle
|
||||
* animations.
|
||||
*
|
||||
* <p>Uses @Pseudo annotation - mixin is optional and will be skipped if MCA is not installed.
|
||||
*
|
||||
* <p>Target class: net.mca.client.model.VillagerEntityBaseModelMCA
|
||||
* <p>Target method: setupAnim (MCP name, remapped from Yarn by Architectury)
|
||||
*/
|
||||
@Pseudo
|
||||
@Mixin(
|
||||
targets = "forge.net.mca.client.model.VillagerEntityBaseModelMCA",
|
||||
remap = false
|
||||
)
|
||||
public class MixinVillagerEntityBaseModelMCA<T extends LivingEntity> {
|
||||
|
||||
// Note: Tick tracking moved to MCAAnimationTickCache for cleanup on world unload
|
||||
|
||||
/**
|
||||
* Inject at the end of setupAnim to apply tied pose after MCA has set its animations.
|
||||
*
|
||||
* <p>This completely overrides arm/leg positions when the villager is tied up.
|
||||
*
|
||||
* <p>Method signature: void setupAnim(T entity, float limbSwing, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch)
|
||||
* <p>Note: MCA uses Architectury which remaps Yarn's method names to Forge/MCP names
|
||||
*/
|
||||
@Inject(method = "m_6973_", at = @At("TAIL"), remap = false)
|
||||
private void tiedup$applyTiedPose(
|
||||
T villager,
|
||||
float limbSwing,
|
||||
float limbSwingAmount,
|
||||
float ageInTicks,
|
||||
float netHeadYaw,
|
||||
float headPitch,
|
||||
CallbackInfo ci
|
||||
) {
|
||||
// Only process on client side
|
||||
if (villager.level() == null || !villager.level().isClientSide()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if MCA is loaded and this villager is tied
|
||||
if (!MCACompat.isMCALoaded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
IBondageState state = MCACompat.getKidnappedState(villager);
|
||||
if (state == null || !state.isTiedUp()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get pose info from bind item
|
||||
ItemStack bind = state.getEquipment(BodyRegionV2.ARMS);
|
||||
PoseType poseType = PoseType.STANDARD;
|
||||
|
||||
if (bind.getItem() instanceof ItemBind itemBind) {
|
||||
poseType = itemBind.getPoseType();
|
||||
}
|
||||
|
||||
// Derive bound state from V2 regions, fallback to V1 bind mode NBT
|
||||
boolean armsBound = V2EquipmentHelper.isRegionOccupied(villager, BodyRegionV2.ARMS);
|
||||
boolean legsBound = V2EquipmentHelper.isRegionOccupied(villager, BodyRegionV2.LEGS);
|
||||
|
||||
if (!armsBound && !legsBound && bind.getItem() instanceof ItemBind) {
|
||||
armsBound = ItemBind.hasArmsBound(bind);
|
||||
legsBound = ItemBind.hasLegsBound(bind);
|
||||
}
|
||||
|
||||
// MCA doesn't track struggling state - use false for now
|
||||
// TODO: Add struggling support to MCA integration
|
||||
boolean isStruggling = false;
|
||||
|
||||
// Cast this mixin to HumanoidModel to apply pose
|
||||
// MCA's VillagerEntityBaseModelMCA extends HumanoidModel
|
||||
@SuppressWarnings("unchecked")
|
||||
HumanoidModel<?> model = (HumanoidModel<?>) (Object) this;
|
||||
|
||||
// Check if villager supports PlayerAnimator (via our mixin)
|
||||
if (villager instanceof IAnimatedPlayer animated) {
|
||||
// Build animation ID and play animation
|
||||
String animId = AnimationIdBuilder.build(
|
||||
poseType,
|
||||
armsBound,
|
||||
legsBound,
|
||||
null,
|
||||
isStruggling,
|
||||
true
|
||||
);
|
||||
BondageAnimationManager.playAnimation(villager, animId);
|
||||
|
||||
// Tick the animation stack only once per game tick (not every render frame)
|
||||
// ageInTicks increments by 1 each game tick, with fractional values between ticks
|
||||
int currentTick = (int) ageInTicks;
|
||||
UUID entityId = villager.getUUID();
|
||||
int lastTick =
|
||||
com.tiedup.remake.client.animation.tick.MCAAnimationTickCache.getLastTick(
|
||||
entityId
|
||||
);
|
||||
|
||||
if (lastTick != currentTick) {
|
||||
// New game tick - tick the animation
|
||||
animated.getAnimationStack().tick();
|
||||
com.tiedup.remake.client.animation.tick.MCAAnimationTickCache.setLastTick(
|
||||
entityId,
|
||||
currentTick
|
||||
);
|
||||
}
|
||||
|
||||
// Apply animation transforms to model parts
|
||||
AnimationApplier emote = animated.playerAnimator_getAnimation();
|
||||
if (emote != null && emote.isActive()) {
|
||||
// Use correct PlayerAnimator part names (torso, not body)
|
||||
emote.updatePart("head", model.head);
|
||||
emote.updatePart("torso", model.body);
|
||||
emote.updatePart("leftArm", model.leftArm);
|
||||
emote.updatePart("rightArm", model.rightArm);
|
||||
emote.updatePart("leftLeg", model.leftLeg);
|
||||
emote.updatePart("rightLeg", model.rightLeg);
|
||||
|
||||
// Force rotations using setRotation to ensure they're applied
|
||||
model.rightArm.setRotation(
|
||||
model.rightArm.xRot,
|
||||
model.rightArm.yRot,
|
||||
model.rightArm.zRot
|
||||
);
|
||||
model.leftArm.setRotation(
|
||||
model.leftArm.xRot,
|
||||
model.leftArm.yRot,
|
||||
model.leftArm.zRot
|
||||
);
|
||||
model.rightLeg.setRotation(
|
||||
model.rightLeg.xRot,
|
||||
model.rightLeg.yRot,
|
||||
model.rightLeg.zRot
|
||||
);
|
||||
model.leftLeg.setRotation(
|
||||
model.leftLeg.xRot,
|
||||
model.leftLeg.yRot,
|
||||
model.leftLeg.zRot
|
||||
);
|
||||
model.body.setRotation(
|
||||
model.body.xRot,
|
||||
model.body.yRot,
|
||||
model.body.zRot
|
||||
);
|
||||
model.head.setRotation(
|
||||
model.head.xRot,
|
||||
model.head.yRot,
|
||||
model.head.zRot
|
||||
);
|
||||
} else {
|
||||
// Fallback to static poses if animation not active
|
||||
StaticPoseApplier.applyStaticPose(
|
||||
model,
|
||||
poseType,
|
||||
armsBound,
|
||||
legsBound
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Fallback: entity doesn't support PlayerAnimator, use static poses
|
||||
StaticPoseApplier.applyStaticPose(model, poseType, armsBound, legsBound);
|
||||
}
|
||||
|
||||
// Hide arms for WRAP/LATEX_SACK poses (like DamselModel does)
|
||||
if (poseType == PoseType.WRAP || poseType == PoseType.LATEX_SACK) {
|
||||
model.leftArm.visible = false;
|
||||
model.rightArm.visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package com.tiedup.remake.mixin.client;
|
||||
|
||||
import dev.kosmx.playerAnim.api.layered.AnimationStack;
|
||||
import dev.kosmx.playerAnim.api.layered.IAnimation;
|
||||
import dev.kosmx.playerAnim.impl.IAnimatedPlayer;
|
||||
import dev.kosmx.playerAnim.impl.animation.AnimationApplier;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Pseudo;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
/**
|
||||
* Mixin to inject IAnimatedPlayer support into MCA villagers.
|
||||
*
|
||||
* <p>This allows MCA villagers to use PlayerAnimator animations for bondage poses,
|
||||
* instead of just static pose rotations.
|
||||
*
|
||||
* <p>Uses @Pseudo annotation - mixin is optional and will be skipped if MCA is not installed.
|
||||
*
|
||||
* <p>Target class: net.mca.entity.VillagerEntityMCA
|
||||
*/
|
||||
@Pseudo
|
||||
@Mixin(targets = "forge.net.mca.entity.VillagerEntityMCA", remap = false)
|
||||
public abstract class MixinVillagerEntityMCAAnimated
|
||||
implements IAnimatedPlayer
|
||||
{
|
||||
|
||||
/**
|
||||
* Animation stack for layered animations.
|
||||
*/
|
||||
@Unique
|
||||
private AnimationStack tiedup$animationStack;
|
||||
|
||||
/**
|
||||
* Animation applier for applying animations to model parts.
|
||||
*/
|
||||
@Unique
|
||||
private AnimationApplier tiedup$animationApplier;
|
||||
|
||||
/**
|
||||
* Storage for named animations.
|
||||
*/
|
||||
@Unique
|
||||
private final Map<ResourceLocation, IAnimation> tiedup$storedAnimations =
|
||||
new HashMap<>();
|
||||
|
||||
/**
|
||||
* Track if animation system has been initialized.
|
||||
*/
|
||||
@Unique
|
||||
private boolean tiedup$animInitialized = false;
|
||||
|
||||
/**
|
||||
* Initialize animation system after entity construction.
|
||||
*/
|
||||
@Inject(method = "<init>*", at = @At("RETURN"), remap = false)
|
||||
private void tiedup$initAnimations(CallbackInfo ci) {
|
||||
tiedup$ensureAnimationInit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazy initialization of animation system.
|
||||
* Called on first access to ensure system is ready.
|
||||
* Only initializes on CLIENT side!
|
||||
*/
|
||||
@Unique
|
||||
private void tiedup$ensureAnimationInit() {
|
||||
if (!tiedup$animInitialized) {
|
||||
// Only create animation stack on client side
|
||||
net.minecraft.world.entity.LivingEntity self =
|
||||
(net.minecraft.world.entity.LivingEntity) (Object) this;
|
||||
if (self.level() == null || !self.level().isClientSide()) {
|
||||
return; // Don't initialize on server
|
||||
}
|
||||
|
||||
this.tiedup$animationStack = new AnimationStack();
|
||||
this.tiedup$animationApplier = new AnimationApplier(
|
||||
this.tiedup$animationStack
|
||||
);
|
||||
tiedup$animInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// IAnimatedPlayer Implementation
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public AnimationStack getAnimationStack() {
|
||||
tiedup$ensureAnimationInit();
|
||||
return this.tiedup$animationStack;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnimationApplier playerAnimator_getAnimation() {
|
||||
tiedup$ensureAnimationInit();
|
||||
return this.tiedup$animationApplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IAnimation playerAnimator_getAnimation(ResourceLocation id) {
|
||||
return this.tiedup$storedAnimations.get(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IAnimation playerAnimator_setAnimation(
|
||||
ResourceLocation id,
|
||||
IAnimation animation
|
||||
) {
|
||||
if (animation == null) {
|
||||
return this.tiedup$storedAnimations.remove(id);
|
||||
} else {
|
||||
return this.tiedup$storedAnimations.put(id, animation);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user