Files
TiedUp-/src/main/java/com/tiedup/remake/client/renderer/DamselRenderer.java
NotEvil a71093ba9c Remove internal phase comments and format code
Strip all Phase references, TODO/FUTURE roadmap notes, and internal
planning comments from the codebase. Run Prettier for consistent
formatting across all Java files.
2026-04-12 01:25:55 +02:00

225 lines
7.4 KiB
Java

package com.tiedup.remake.client.renderer;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.math.Axis;
import com.tiedup.remake.client.animation.render.RenderConstants;
import com.tiedup.remake.client.model.DamselModel;
import com.tiedup.remake.compat.wildfire.WildfireCompat;
import com.tiedup.remake.compat.wildfire.render.WildfireDamselLayer;
import com.tiedup.remake.entities.AbstractTiedUpNpc;
import net.minecraft.client.model.HumanoidArmorModel;
import net.minecraft.client.model.geom.ModelLayers;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.client.renderer.entity.HumanoidMobRenderer;
import net.minecraft.client.renderer.entity.layers.HumanoidArmorLayer;
import net.minecraft.client.renderer.entity.layers.ItemInHandLayer;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
/**
* Renderer for AbstractTiedUpNpc and all subtypes (Kidnapper, Elite, Archer, Merchant, Shiny).
*
* <p>Uses ISkinnedEntity interface for polymorphic texture lookup.
* Each entity subclass overrides getSkinTexture() to return the appropriate texture.
*
* <p><b>Issue #19 fix:</b> Replaced 6+ instanceof checks with single interface call.
*/
@OnlyIn(Dist.CLIENT)
public class DamselRenderer
extends HumanoidMobRenderer<AbstractTiedUpNpc, DamselModel>
{
/**
* Normal arms model (4px wide - Steve model).
*/
private final DamselModel normalModel;
/**
* Slim arms model (3px wide - Alex model).
*/
private final DamselModel slimModel;
/**
* Create renderer.
*
*/
public DamselRenderer(EntityRendererProvider.Context context) {
super(
context,
new DamselModel(context.bakeLayer(ModelLayers.PLAYER), false),
0.5f // Shadow radius
);
// Store both models for runtime swapping
this.normalModel = this.getModel();
this.slimModel = new DamselModel(
context.bakeLayer(ModelLayers.PLAYER_SLIM),
true
);
// Add armor render layer (renders equipped armor)
this.addLayer(
new HumanoidArmorLayer<>(
this,
new HumanoidArmorModel<>(
context.bakeLayer(ModelLayers.PLAYER_INNER_ARMOR)
),
new HumanoidArmorModel<>(
context.bakeLayer(ModelLayers.PLAYER_OUTER_ARMOR)
),
context.getModelManager()
)
);
// Add item in hand layer (renders held items)
this.addLayer(
new ItemInHandLayer<>(this, context.getItemInHandRenderer())
);
// Add Wildfire breast layer BEFORE bondage (so bondage renders on top of breasts)
if (WildfireCompat.isLoaded()) {
this.addLayer(
new WildfireDamselLayer<>(this, context.getModelSet())
);
}
// Add V2 bondage render layer (GLB-based V2 equipment rendering)
this.addLayer(
new com.tiedup.remake.v2.bondage.client.V2BondageRenderLayer<>(this)
);
}
/**
* Render the entity.
* Uses entity's hasSlimArms() for model selection.
*
* to ensure it happens after visibility resets.
*
* DOG pose: X rotation is applied in setupRotations() AFTER Y rotation,
* so the "belly down" direction follows entity facing.
* Head compensation is applied in DamselModel.setupAnim().
* Body rotation smoothing is handled in AbstractTiedUpNpc.tick().
*/
@Override
public void render(
AbstractTiedUpNpc entity,
float entityYaw,
float partialTicks,
PoseStack poseStack,
MultiBufferSource buffer,
int packedLight
) {
// Use entity's hasSlimArms() - each entity type overrides this appropriately
boolean useSlim = entity.hasSlimArms();
// Swap to appropriate model
this.model = useSlim ? this.slimModel : this.normalModel;
// Apply vertical offset for sitting/kneeling/dog poses
// This ensures the model AND all layers (gag, blindfold, etc.) move together
float verticalOffset = getVerticalOffset(entity);
boolean pushedPose = false;
if (verticalOffset != 0) {
poseStack.pushPose();
pushedPose = true;
// Convert from model units (16 = 1 block) to render units
poseStack.translate(0, verticalOffset / 16.0, 0);
}
// Call parent render
// Note: Wearer layer hiding happens in DamselModel.setupAnim()
super.render(
entity,
entityYaw,
partialTicks,
poseStack,
buffer,
packedLight
);
if (pushedPose) {
poseStack.popPose();
}
}
/**
* Get vertical offset for sitting/kneeling/dog poses.
* Returns offset in model units (16 units = 1 block).
*
* @param entity The entity to check
* @return Vertical offset (negative = down)
*/
private float getVerticalOffset(AbstractTiedUpNpc entity) {
if (entity.isSitting()) {
return RenderConstants.DAMSEL_SIT_OFFSET;
} else if (entity.isKneeling()) {
return RenderConstants.DAMSEL_KNEEL_OFFSET;
} else if (entity.isDogPose()) {
return RenderConstants.DAMSEL_DOG_OFFSET;
}
return 0;
}
/**
* Get the texture location based on entity type.
*
* <p>Issue #19 fix: Uses ISkinnedEntity interface instead of instanceof cascade.
* Each entity subclass implements getSkinTexture() to return appropriate texture.
*/
@Override
public ResourceLocation getTextureLocation(AbstractTiedUpNpc entity) {
// ISkinnedEntity provides polymorphic skin texture lookup
// Each entity type (Damsel, Kidnapper, Elite, Archer, Merchant, Shiny)
// overrides getSkinTexture() to return the correct texture
return entity.getSkinTexture();
}
/**
* Apply scale transformation.
*/
@Override
protected void scale(
AbstractTiedUpNpc entity,
PoseStack poseStack,
float partialTick
) {
poseStack.scale(0.9375f, 0.9375f, 0.9375f);
}
/**
* Setup rotations for the entity.
*
* DOG pose: After Y rotation is applied by parent, add X rotation
* to make the body horizontal. This is applied in entity-local space,
* so the "belly down" direction follows the entity's facing.
*/
@Override
protected void setupRotations(
AbstractTiedUpNpc entity,
PoseStack poseStack,
float ageInTicks,
float rotationYaw,
float partialTicks
) {
// Call parent to apply Y rotation (body facing)
super.setupRotations(
entity,
poseStack,
ageInTicks,
rotationYaw,
partialTicks
);
// DOG pose: Apply X rotation to make body horizontal
// This happens AFTER Y rotation, so it's in entity-local space
if (entity.isDogPose()) {
// Rotate -90° on X axis around the model's pivot point
// Pivot at waist height (12 model units = 0.75 blocks up from feet)
poseStack.translate(0, 0.75, 0);
poseStack.mulPose(Axis.XP.rotationDegrees(-90));
poseStack.translate(0, -0.75, 0);
}
}
}