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). * *

Uses ISkinnedEntity interface for polymorphic texture lookup. * Each entity subclass overrides getSkinTexture() to return the appropriate texture. * *

Issue #19 fix: Replaced 6+ instanceof checks with single interface call. */ @OnlyIn(Dist.CLIENT) public class DamselRenderer extends HumanoidMobRenderer { /** * Normal arms model (4px wide - Steve model). */ private final DamselModel normalModel; /** * Slim arms model (3px wide - Alex model). */ private final DamselModel slimModel; /** * Create renderer. * * Phase 19: Uses vanilla ModelLayers.PLAYER for full layer support (jacket, sleeves, pants). */ 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. * * Phase 19: Wearer layer hiding is now handled in DamselModel.setupAnim() * 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. * *

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); } } }