Remove build artifacts, dev tool configs, unused dependencies, and third-party source dumps. Add proper README, update .gitignore, clean up Makefile.
583 lines
21 KiB
Java
583 lines
21 KiB
Java
package com.tiedup.remake.client.model;
|
|
|
|
import com.mojang.blaze3d.vertex.PoseStack;
|
|
import com.mojang.blaze3d.vertex.VertexConsumer;
|
|
import com.mojang.logging.LogUtils;
|
|
import com.tiedup.remake.client.animation.StaticPoseApplier;
|
|
import com.tiedup.remake.client.animation.util.DogPoseHelper;
|
|
import com.tiedup.remake.entities.AbstractTiedUpNpc;
|
|
import com.tiedup.remake.entities.EntityKidnapperArcher;
|
|
import com.tiedup.remake.entities.EntityMaster;
|
|
import com.tiedup.remake.entities.ai.master.MasterState;
|
|
import com.tiedup.remake.v2.BodyRegionV2;
|
|
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
|
|
import com.tiedup.remake.items.base.ItemBind;
|
|
import com.tiedup.remake.items.base.PoseType;
|
|
import com.tiedup.remake.items.clothes.GenericClothes;
|
|
import dev.kosmx.playerAnim.core.impl.AnimationProcessor;
|
|
import dev.kosmx.playerAnim.core.util.SetableSupplier;
|
|
import dev.kosmx.playerAnim.impl.Helper;
|
|
import dev.kosmx.playerAnim.impl.IMutableModel;
|
|
import dev.kosmx.playerAnim.impl.IUpperPartHelper;
|
|
import dev.kosmx.playerAnim.impl.animation.AnimationApplier;
|
|
import dev.kosmx.playerAnim.impl.animation.IBendHelper;
|
|
import net.minecraft.client.model.HumanoidModel;
|
|
import net.minecraft.client.model.PlayerModel;
|
|
import net.minecraft.client.model.geom.ModelPart;
|
|
import net.minecraft.core.Direction;
|
|
import net.minecraft.world.item.ItemStack;
|
|
import net.minecraftforge.api.distmarker.Dist;
|
|
import net.minecraftforge.api.distmarker.OnlyIn;
|
|
import org.slf4j.Logger;
|
|
|
|
/**
|
|
* Model for AbstractTiedUpNpc - Humanoid female NPC.
|
|
*
|
|
* Phase 14.2.3: Rendering system
|
|
* Phase 19: Extends PlayerModel for full layer support (hat, jacket, sleeves, pants)
|
|
*
|
|
* Features:
|
|
* - Extends PlayerModel for player-like rendering with outer layers
|
|
* - Supports both normal (4px) and slim (3px) arm widths
|
|
* - Modifies animations based on bondage state
|
|
* - Has jacket, sleeves, pants layers like player skins
|
|
*
|
|
* Uses vanilla ModelLayers.PLAYER / PLAYER_SLIM for geometry.
|
|
*/
|
|
@OnlyIn(Dist.CLIENT)
|
|
public class DamselModel
|
|
extends PlayerModel<AbstractTiedUpNpc>
|
|
implements IMutableModel
|
|
{
|
|
|
|
private static final Logger LOGGER = LogUtils.getLogger();
|
|
private static boolean loggedBendyStatus = false;
|
|
|
|
/** Track if bendy-lib has been initialized for this model */
|
|
private boolean bendyLibInitialized = false;
|
|
|
|
/** Emote supplier for bending support - required by IMutableModel */
|
|
private final SetableSupplier<AnimationProcessor> emoteSupplier =
|
|
new SetableSupplier<>();
|
|
|
|
/**
|
|
* Create model from baked model part.
|
|
*
|
|
* @param root The root model part (baked from vanilla PLAYER layer)
|
|
* @param slim Whether this is a slim (Alex) arms model
|
|
*/
|
|
public DamselModel(ModelPart root, boolean slim) {
|
|
super(root, slim);
|
|
initBendyLib(root);
|
|
}
|
|
|
|
/**
|
|
* Initialize bendy-lib bend points on model parts.
|
|
*
|
|
* <p>This enables visual bending of knees and elbows when animations
|
|
* specify bend values. Without this initialization, bend values in
|
|
* animation JSON files have no visual effect.
|
|
*
|
|
* <p>Also marks upper parts (head, arms, hat) for proper bend rendering.
|
|
*
|
|
* @param root The root model part
|
|
*/
|
|
private void initBendyLib(ModelPart root) {
|
|
if (bendyLibInitialized) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Check if bendy-lib is available
|
|
if (IBendHelper.INSTANCE == null) {
|
|
if (!loggedBendyStatus) {
|
|
LOGGER.warn(
|
|
"[DamselModel] IBendHelper.INSTANCE is null - bendy-lib not available"
|
|
);
|
|
loggedBendyStatus = true;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Log bendy-lib status
|
|
if (!loggedBendyStatus) {
|
|
LOGGER.info(
|
|
"[DamselModel] IBendHelper.INSTANCE class: {}",
|
|
IBendHelper.INSTANCE.getClass().getName()
|
|
);
|
|
LOGGER.info(
|
|
"[DamselModel] Helper.isBendEnabled(): {}",
|
|
Helper.isBendEnabled()
|
|
);
|
|
loggedBendyStatus = true;
|
|
}
|
|
|
|
// Initialize bend points for each body part
|
|
// Direction indicates which end of the limb bends
|
|
IBendHelper.INSTANCE.initBend(
|
|
root.getChild("body"),
|
|
Direction.DOWN
|
|
);
|
|
IBendHelper.INSTANCE.initBend(
|
|
root.getChild("right_arm"),
|
|
Direction.UP
|
|
);
|
|
IBendHelper.INSTANCE.initBend(
|
|
root.getChild("left_arm"),
|
|
Direction.UP
|
|
);
|
|
IBendHelper.INSTANCE.initBend(
|
|
root.getChild("right_leg"),
|
|
Direction.UP
|
|
);
|
|
IBendHelper.INSTANCE.initBend(
|
|
root.getChild("left_leg"),
|
|
Direction.UP
|
|
);
|
|
|
|
// Mark upper parts for proper bend rendering
|
|
// These parts will be rendered after applying body bend rotation
|
|
((IUpperPartHelper) (Object) this.rightArm).setUpperPart(true);
|
|
((IUpperPartHelper) (Object) this.leftArm).setUpperPart(true);
|
|
((IUpperPartHelper) (Object) this.head).setUpperPart(true);
|
|
((IUpperPartHelper) (Object) this.hat).setUpperPart(true);
|
|
|
|
LOGGER.info("[DamselModel] bendy-lib initialized successfully");
|
|
bendyLibInitialized = true;
|
|
} catch (Exception e) {
|
|
LOGGER.error("[DamselModel] bendy-lib initialization failed", e);
|
|
// bendy-lib not available or initialization failed
|
|
// Animations will still work, just without visual bending
|
|
}
|
|
}
|
|
|
|
// ========================================
|
|
// IMutableModel Implementation
|
|
// ========================================
|
|
|
|
@Override
|
|
public void setEmoteSupplier(SetableSupplier<AnimationProcessor> supplier) {
|
|
if (supplier != null && supplier.get() != null) {
|
|
this.emoteSupplier.set(supplier.get());
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public SetableSupplier<AnimationProcessor> getEmoteSupplier() {
|
|
return this.emoteSupplier;
|
|
}
|
|
|
|
/**
|
|
* Setup animations for the damsel.
|
|
*
|
|
* Modifies arm and leg positions based on bondage state:
|
|
* - Tied up: Arms behind back, legs frozen (or variant pose based on bind type)
|
|
* - Free: Normal humanoid animations
|
|
*
|
|
* Phase 15: Different poses for different bind types (straitjacket, wrap, latex_sack)
|
|
* Phase 15.1: Hide arms for wrap/latex_sack (matching original mod)
|
|
*
|
|
* @param entity AbstractTiedUpNpc instance
|
|
* @param limbSwing Limb swing animation value
|
|
* @param limbSwingAmount Limb swing amount
|
|
* @param ageInTicks Age in ticks for idle animations
|
|
* @param netHeadYaw Head yaw rotation
|
|
* @param headPitch Head pitch rotation
|
|
*/
|
|
@Override
|
|
public void setupAnim(
|
|
AbstractTiedUpNpc entity,
|
|
float limbSwing,
|
|
float limbSwingAmount,
|
|
float ageInTicks,
|
|
float netHeadYaw,
|
|
float headPitch
|
|
) {
|
|
// Phase 18: Handle archer arm poses BEFORE super call
|
|
// Only show bow animation when in ranged mode (has active shooting target)
|
|
if (entity instanceof EntityKidnapperArcher archer) {
|
|
if (archer.isInRangedMode()) {
|
|
// In ranged mode: show bow animation
|
|
// isAiming() indicates full draw, otherwise ready position
|
|
this.rightArmPose = HumanoidModel.ArmPose.BOW_AND_ARROW;
|
|
this.leftArmPose = HumanoidModel.ArmPose.BOW_AND_ARROW;
|
|
} else {
|
|
// Not in ranged mode: reset to normal poses (no bow animation)
|
|
this.rightArmPose = HumanoidModel.ArmPose.EMPTY;
|
|
this.leftArmPose = HumanoidModel.ArmPose.EMPTY;
|
|
}
|
|
}
|
|
|
|
// Call parent to setup base humanoid animations
|
|
super.setupAnim(
|
|
entity,
|
|
limbSwing,
|
|
limbSwingAmount,
|
|
ageInTicks,
|
|
netHeadYaw,
|
|
headPitch
|
|
);
|
|
|
|
// Reset all visibility (may have been hidden in previous frame)
|
|
// Arms
|
|
this.leftArm.visible = true;
|
|
this.rightArm.visible = true;
|
|
// Outer layers (Phase 19)
|
|
this.hat.visible = true;
|
|
this.jacket.visible = true;
|
|
this.leftSleeve.visible = true;
|
|
this.rightSleeve.visible = true;
|
|
this.leftPants.visible = true;
|
|
this.rightPants.visible = true;
|
|
|
|
// Animation triggering is handled by NpcAnimationTickHandler (tick-based).
|
|
// This method only applies transforms, static pose fallback, and layer syncing.
|
|
boolean inPose =
|
|
entity.isTiedUp() || entity.isSitting() || entity.isKneeling();
|
|
|
|
if (inPose) {
|
|
ItemStack bind = entity.getEquipment(BodyRegionV2.ARMS);
|
|
PoseType poseType = PoseType.STANDARD;
|
|
|
|
if (bind.getItem() instanceof ItemBind itemBind) {
|
|
poseType = itemBind.getPoseType();
|
|
}
|
|
|
|
// Hide arms for wrap/latex_sack poses
|
|
if (poseType == PoseType.WRAP || poseType == PoseType.LATEX_SACK) {
|
|
this.leftArm.visible = false;
|
|
this.rightArm.visible = false;
|
|
}
|
|
}
|
|
|
|
// Apply animation transforms via PlayerAnimator's emote.updatePart()
|
|
// AbstractTiedUpNpc implements IAnimatedPlayer, so we can call directly
|
|
AnimationApplier emote = entity.playerAnimator_getAnimation();
|
|
boolean emoteActive = emote != null && emote.isActive();
|
|
|
|
// Track current pose type for DOG pose compensation
|
|
PoseType currentPoseType = PoseType.STANDARD;
|
|
if (inPose) {
|
|
ItemStack bindForPoseType = entity.getEquipment(BodyRegionV2.ARMS);
|
|
if (bindForPoseType.getItem() instanceof ItemBind itemBindForType) {
|
|
currentPoseType = itemBindForType.getPoseType();
|
|
}
|
|
}
|
|
|
|
// Check if this is a Master in human chair mode (head should look around freely)
|
|
boolean isMasterChairAnim =
|
|
entity instanceof EntityMaster masterEnt &&
|
|
masterEnt.getMasterState() == MasterState.HUMAN_CHAIR &&
|
|
masterEnt.isSitting();
|
|
|
|
if (emoteActive) {
|
|
// Animation is active - apply transforms via AnimationApplier
|
|
this.emoteSupplier.set(emote);
|
|
|
|
// Apply transforms to each body part
|
|
// Skip head for master chair animation — let vanilla look-at control the head
|
|
if (!isMasterChairAnim) {
|
|
emote.updatePart("head", this.head);
|
|
}
|
|
emote.updatePart("torso", this.body);
|
|
emote.updatePart("leftArm", this.leftArm);
|
|
emote.updatePart("rightArm", this.rightArm);
|
|
emote.updatePart("leftLeg", this.leftLeg);
|
|
emote.updatePart("rightLeg", this.rightLeg);
|
|
|
|
// DOG pose: PoseStack handles body rotation in setupRotations()
|
|
// Reset body.xRot to prevent double rotation from animation
|
|
// Apply head compensation so head looks forward instead of at ground
|
|
if (currentPoseType == PoseType.DOG) {
|
|
// Reset body rotation (PoseStack already rotates everything)
|
|
this.body.xRot = 0;
|
|
|
|
// Head compensation: body is horizontal via PoseStack
|
|
// Head needs to look forward instead of at the ground
|
|
DogPoseHelper.applyHeadCompensation(
|
|
this.head,
|
|
null, // hat is synced via copyFrom below
|
|
headPitch,
|
|
netHeadYaw
|
|
);
|
|
}
|
|
|
|
// Sync outer layers to their parents (Phase 19)
|
|
this.hat.copyFrom(this.head);
|
|
this.jacket.copyFrom(this.body);
|
|
this.leftSleeve.copyFrom(this.leftArm);
|
|
this.rightSleeve.copyFrom(this.rightArm);
|
|
this.leftPants.copyFrom(this.leftLeg);
|
|
this.rightPants.copyFrom(this.rightLeg);
|
|
} else if (inPose) {
|
|
// Animation not yet active (1-frame delay) - apply static pose as fallback
|
|
// This ensures immediate visual feedback when bind is applied
|
|
ItemStack bind = entity.getEquipment(BodyRegionV2.ARMS);
|
|
PoseType fallbackPoseType = PoseType.STANDARD;
|
|
|
|
if (bind.getItem() instanceof ItemBind itemBind) {
|
|
fallbackPoseType = itemBind.getPoseType();
|
|
}
|
|
|
|
// Derive bound state from V2 regions (AbstractTiedUpNpc implements IV2EquipmentHolder)
|
|
boolean armsBound = V2EquipmentHelper.isRegionOccupied(entity, BodyRegionV2.ARMS);
|
|
boolean legsBound = V2EquipmentHelper.isRegionOccupied(entity, BodyRegionV2.LEGS);
|
|
|
|
if (!armsBound && !legsBound && bind.getItem() instanceof ItemBind) {
|
|
armsBound = ItemBind.hasArmsBound(bind);
|
|
legsBound = ItemBind.hasLegsBound(bind);
|
|
}
|
|
|
|
// Apply static pose directly to model parts
|
|
StaticPoseApplier.applyStaticPose(
|
|
this,
|
|
fallbackPoseType,
|
|
armsBound,
|
|
legsBound
|
|
);
|
|
|
|
// DOG pose: Apply head compensation for horizontal body (same as emote case)
|
|
if (fallbackPoseType == PoseType.DOG) {
|
|
// Reset body rotation (PoseStack handles it)
|
|
this.body.xRot = 0;
|
|
|
|
// Head compensation for horizontal body
|
|
DogPoseHelper.applyHeadCompensation(
|
|
this.head,
|
|
null, // hat is synced via copyFrom below
|
|
headPitch,
|
|
netHeadYaw
|
|
);
|
|
}
|
|
|
|
// Sync outer layers after static pose
|
|
this.hat.copyFrom(this.head);
|
|
this.jacket.copyFrom(this.body);
|
|
this.leftSleeve.copyFrom(this.leftArm);
|
|
this.rightSleeve.copyFrom(this.rightArm);
|
|
this.leftPants.copyFrom(this.leftLeg);
|
|
this.rightPants.copyFrom(this.rightLeg);
|
|
|
|
// Clear emote supplier since we're using static pose
|
|
this.emoteSupplier.set(null);
|
|
} else {
|
|
// Not in pose and no animation - clear emote supplier and reset bends
|
|
this.emoteSupplier.set(null);
|
|
resetBends();
|
|
|
|
// Sync outer layers
|
|
this.hat.copyFrom(this.head);
|
|
this.jacket.copyFrom(this.body);
|
|
this.leftSleeve.copyFrom(this.leftArm);
|
|
this.rightSleeve.copyFrom(this.rightArm);
|
|
this.leftPants.copyFrom(this.leftLeg);
|
|
this.rightPants.copyFrom(this.rightLeg);
|
|
}
|
|
|
|
// Phase 19: Hide wearer's outer layers based on clothes settings
|
|
// This MUST happen after super.setupAnim() which can reset visibility
|
|
hideWearerLayersForClothes(entity);
|
|
}
|
|
|
|
/**
|
|
* Hide wearer's outer layers when clothes are equipped.
|
|
* Called at the end of setupAnim() to ensure it happens after any visibility resets.
|
|
*
|
|
* <p>Logic: When clothes are equipped, hide ALL wearer's outer layers.
|
|
* The clothes will render their own layers on top.
|
|
* Exception: If keepHead is enabled, the head/hat layers remain visible.
|
|
*
|
|
* @param entity The entity wearing clothes
|
|
*/
|
|
private void hideWearerLayersForClothes(AbstractTiedUpNpc entity) {
|
|
ItemStack clothes = entity.getEquipment(BodyRegionV2.TORSO);
|
|
if (
|
|
clothes.isEmpty() ||
|
|
!(clothes.getItem() instanceof GenericClothes gc)
|
|
) {
|
|
return;
|
|
}
|
|
|
|
// Check if keepHead is enabled
|
|
boolean keepHead = gc.isKeepHeadEnabled(clothes);
|
|
|
|
// When wearing clothes, hide wearer's outer layers
|
|
// Exception: if keepHead is true, don't hide head/hat
|
|
if (!keepHead) {
|
|
this.hat.visible = false;
|
|
}
|
|
this.jacket.visible = false;
|
|
this.leftSleeve.visible = false;
|
|
this.rightSleeve.visible = false;
|
|
this.leftPants.visible = false;
|
|
this.rightPants.visible = false;
|
|
}
|
|
|
|
/**
|
|
* Reset bend values on all body parts.
|
|
* Called when animation stops to prevent lingering bend effects.
|
|
*/
|
|
private void resetBends() {
|
|
if (IBendHelper.INSTANCE == null) {
|
|
return;
|
|
}
|
|
try {
|
|
IBendHelper.INSTANCE.bend(this.body, null);
|
|
IBendHelper.INSTANCE.bend(this.leftArm, null);
|
|
IBendHelper.INSTANCE.bend(this.rightArm, null);
|
|
IBendHelper.INSTANCE.bend(this.leftLeg, null);
|
|
IBendHelper.INSTANCE.bend(this.rightLeg, null);
|
|
} catch (Exception e) {
|
|
LOGGER.debug(
|
|
"[DamselModel] bendy-lib not available for bend reset",
|
|
e
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Override renderToBuffer to apply body bend rotation.
|
|
*
|
|
* <p>When an animation has a body bend value, we need to:
|
|
* 1. Render non-upper parts (legs) normally
|
|
* 2. Apply the bend rotation to the matrix stack
|
|
* 3. Render upper parts (head, arms, body) with the rotation applied
|
|
*
|
|
* <p>This creates the visual effect of the body bending (like kneeling).
|
|
*/
|
|
@Override
|
|
public void renderToBuffer(
|
|
PoseStack matrices,
|
|
VertexConsumer vertices,
|
|
int light,
|
|
int overlay,
|
|
float red,
|
|
float green,
|
|
float blue,
|
|
float alpha
|
|
) {
|
|
// Check if we should use bend rendering
|
|
if (
|
|
Helper.isBendEnabled() &&
|
|
emoteSupplier.get() != null &&
|
|
emoteSupplier.get().isActive()
|
|
) {
|
|
// Render with bend support
|
|
renderWithBend(
|
|
matrices,
|
|
vertices,
|
|
light,
|
|
overlay,
|
|
red,
|
|
green,
|
|
blue,
|
|
alpha
|
|
);
|
|
} else {
|
|
// Normal rendering
|
|
super.renderToBuffer(
|
|
matrices,
|
|
vertices,
|
|
light,
|
|
overlay,
|
|
red,
|
|
green,
|
|
blue,
|
|
alpha
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Render model parts with body bend applied.
|
|
*
|
|
* <p>Based on PlayerAnimator's bendRenderToBuffer logic:
|
|
* - First render non-upper parts (legs) normally
|
|
* - Then apply body bend rotation
|
|
* - Then render upper parts (head, body, arms) with rotation
|
|
*/
|
|
private void renderWithBend(
|
|
PoseStack matrices,
|
|
VertexConsumer vertices,
|
|
int light,
|
|
int overlay,
|
|
float red,
|
|
float green,
|
|
float blue,
|
|
float alpha
|
|
) {
|
|
// Get all body parts
|
|
Iterable<ModelPart> headParts = headParts();
|
|
Iterable<ModelPart> bodyParts = bodyParts();
|
|
|
|
// First pass: render non-upper parts (legs)
|
|
for (ModelPart part : headParts) {
|
|
if (!((IUpperPartHelper) (Object) part).isUpperPart()) {
|
|
part.render(
|
|
matrices,
|
|
vertices,
|
|
light,
|
|
overlay,
|
|
red,
|
|
green,
|
|
blue,
|
|
alpha
|
|
);
|
|
}
|
|
}
|
|
for (ModelPart part : bodyParts) {
|
|
if (!((IUpperPartHelper) (Object) part).isUpperPart()) {
|
|
part.render(
|
|
matrices,
|
|
vertices,
|
|
light,
|
|
overlay,
|
|
red,
|
|
green,
|
|
blue,
|
|
alpha
|
|
);
|
|
}
|
|
}
|
|
|
|
// Apply body bend rotation
|
|
matrices.pushPose();
|
|
IBendHelper.rotateMatrixStack(
|
|
matrices,
|
|
emoteSupplier.get().getBend("body")
|
|
);
|
|
|
|
// Second pass: render upper parts (head, body, arms) with bend applied
|
|
for (ModelPart part : headParts) {
|
|
if (((IUpperPartHelper) (Object) part).isUpperPart()) {
|
|
part.render(
|
|
matrices,
|
|
vertices,
|
|
light,
|
|
overlay,
|
|
red,
|
|
green,
|
|
blue,
|
|
alpha
|
|
);
|
|
}
|
|
}
|
|
for (ModelPart part : bodyParts) {
|
|
if (((IUpperPartHelper) (Object) part).isUpperPart()) {
|
|
part.render(
|
|
matrices,
|
|
vertices,
|
|
light,
|
|
overlay,
|
|
red,
|
|
green,
|
|
blue,
|
|
alpha
|
|
);
|
|
}
|
|
}
|
|
|
|
matrices.popPose();
|
|
}
|
|
}
|