package com.tiedup.remake.util; import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.state.IPlayerLeashAccess; import java.util.UUID; import net.minecraft.core.BlockPos; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.effect.MobEffectInstance; import net.minecraft.world.effect.MobEffects; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.ai.attributes.AttributeInstance; import net.minecraft.world.entity.ai.attributes.AttributeModifier; import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.entity.decoration.LeashFenceKnotEntity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.FenceBlock; import net.minecraft.world.level.block.WallBlock; import net.minecraft.world.level.block.state.BlockState; import org.jetbrains.annotations.Nullable; /** * * Manages attribute modifiers for movement speed reduction and other effects. */ public class RestraintEffectUtils { // UUID for the movement speed modifier (must be consistent) private static final UUID BIND_SPEED_MODIFIER_UUID = UUID.fromString( "7f3c7c8e-9d4e-4c7a-8e5f-1a2b3c4d5e6f" ); private static final String BIND_SPEED_MODIFIER_NAME = "tiedup.bind_speed"; // Speed reduction: -0.09 (90% reduction) when tied up // Player base speed is 0.10, so this reduces them to 0.01 (10% speed) private static final double BIND_SPEED_REDUCTION = -0.09; // Full immobilization: -0.10 (100% reduction) for WRAP/LATEX_SACK // Player can only move by jumping private static final double FULL_IMMOBILIZATION_REDUCTION = -0.10; private static final boolean DEBUG = false; /** * Apply movement speed reduction to a tied entity. * * @param entity The living entity to apply the effect to * @deprecated For players, use {@link com.tiedup.remake.v2.bondage.movement.MovementStyleManager} * which handles speed via tick-based resolution. This method remains for NPC entities * (Damsel, MCA villagers) that are not managed by MovementStyleManager. * Scheduled for removal once NPC movement is migrated to V2. */ @Deprecated(forRemoval = true) public static void applyBindSpeedReduction(LivingEntity entity) { applyBindSpeedReduction(entity, false); } /** * Apply movement speed reduction to a tied entity. * * @param entity The living entity to apply the effect to * @param fullImmobilization If true, applies 100% speed reduction (for WRAP/LATEX_SACK) * @deprecated For players, use {@link com.tiedup.remake.v2.bondage.movement.MovementStyleManager} * which handles speed via tick-based resolution. This method remains for NPC entities * (Damsel, MCA villagers) that are not managed by MovementStyleManager. * Scheduled for removal once NPC movement is migrated to V2. */ @Deprecated(forRemoval = true) public static void applyBindSpeedReduction( LivingEntity entity, boolean fullImmobilization ) { if (entity == null) { TiedUpMod.LOGGER.warn( "[RESTRAINT-UTIL] Cannot apply speed reduction - entity is null" ); return; } AttributeInstance movementSpeed = entity.getAttribute( Attributes.MOVEMENT_SPEED ); if (movementSpeed == null) { TiedUpMod.LOGGER.error( "[RESTRAINT-UTIL] Entity {} has no MOVEMENT_SPEED attribute!", entity.getName().getString() ); return; } // Remove existing modifier if present (to avoid duplicates) removeBindSpeedReduction(entity); // Choose reduction amount based on immobilization type double reduction = fullImmobilization ? FULL_IMMOBILIZATION_REDUCTION : BIND_SPEED_REDUCTION; // Create and apply new modifier AttributeModifier modifier = new AttributeModifier( BIND_SPEED_MODIFIER_UUID, BIND_SPEED_MODIFIER_NAME, reduction, AttributeModifier.Operation.ADDITION ); movementSpeed.addPermanentModifier(modifier); if (DEBUG) { TiedUpMod.LOGGER.info( "[RESTRAINT-UTIL] Applied speed reduction to {} (base: {}, modified: {}, full: {})", entity.getName().getString(), movementSpeed.getBaseValue(), movementSpeed.getValue(), fullImmobilization ); } } /** * Remove movement speed reduction from an entity. * * @param entity The living entity to remove the effect from * @deprecated For players, use {@link com.tiedup.remake.v2.bondage.movement.MovementStyleManager} * which handles speed cleanup via tick-based resolution. This method remains for * NPC entities (Damsel, MCA villagers) that are not managed by MovementStyleManager. * Scheduled for removal once NPC movement is migrated to V2. */ @Deprecated(forRemoval = true) public static void removeBindSpeedReduction(LivingEntity entity) { if (entity == null) { TiedUpMod.LOGGER.warn( "[RESTRAINT-UTIL] Cannot remove speed reduction - entity is null" ); return; } AttributeInstance movementSpeed = entity.getAttribute( Attributes.MOVEMENT_SPEED ); if (movementSpeed == null) { TiedUpMod.LOGGER.error( "[RESTRAINT-UTIL] Entity {} has no MOVEMENT_SPEED attribute!", entity.getName().getString() ); return; } // Remove modifier if present if (movementSpeed.getModifier(BIND_SPEED_MODIFIER_UUID) != null) { movementSpeed.removeModifier(BIND_SPEED_MODIFIER_UUID); if (DEBUG) { TiedUpMod.LOGGER.info( "[RESTRAINT-UTIL] Removed speed reduction from {} (restored speed: {})", entity.getName().getString(), movementSpeed.getValue() ); } } else { if (DEBUG) { TiedUpMod.LOGGER.debug( "[RESTRAINT-UTIL] No speed modifier found on {} (already removed or never applied)", entity.getName().getString() ); } } } /** * Check if an entity currently has the bind speed reduction applied. * * @param entity The living entity to check * @return true if the modifier is active * @deprecated For players, movement style is tracked by {@link com.tiedup.remake.v2.bondage.movement.MovementStyleManager}. * Scheduled for removal once NPC movement is migrated to V2. */ @Deprecated(forRemoval = true) public static boolean hasBindSpeedReduction(LivingEntity entity) { if (entity == null) return false; AttributeInstance movementSpeed = entity.getAttribute( Attributes.MOVEMENT_SPEED ); if (movementSpeed == null) return false; return movementSpeed.getModifier(BIND_SPEED_MODIFIER_UUID) != null; } /** * Re-apply speed reduction if needed (called on login/respawn). * * @param entity The living entity * @param shouldBeSlowed Whether the entity should have reduced speed * @deprecated For players, use {@link com.tiedup.remake.v2.bondage.movement.MovementStyleManager} * which re-resolves on every tick. Scheduled for removal once NPC movement is migrated to V2. */ @Deprecated(forRemoval = true) public static void updateBindSpeedReduction( LivingEntity entity, boolean shouldBeSlowed ) { updateBindSpeedReduction(entity, shouldBeSlowed, false); } /** * Re-apply speed reduction if needed (called on login/respawn). * * @param entity The living entity * @param shouldBeSlowed Whether the entity should have reduced speed * @param fullImmobilization If true, applies 100% speed reduction (for WRAP/LATEX_SACK) * @deprecated For players, use {@link com.tiedup.remake.v2.bondage.movement.MovementStyleManager} * which re-resolves on every tick. Scheduled for removal once NPC movement is migrated to V2. */ @Deprecated(forRemoval = true) public static void updateBindSpeedReduction( LivingEntity entity, boolean shouldBeSlowed, boolean fullImmobilization ) { boolean currentlySlowed = hasBindSpeedReduction(entity); if (shouldBeSlowed && !currentlySlowed) { // Need to apply applyBindSpeedReduction(entity, fullImmobilization); if (DEBUG) { TiedUpMod.LOGGER.info( "[RESTRAINT-UTIL] Re-applied speed reduction to {} on login/respawn (full={})", entity.getName().getString(), fullImmobilization ); } } else if (shouldBeSlowed && currentlySlowed) { // Already slowed - but might need to change immobilization level // Re-apply with correct level applyBindSpeedReduction(entity, fullImmobilization); } else if (!shouldBeSlowed && currentlySlowed) { // Need to remove removeBindSpeedReduction(entity); if (DEBUG) { TiedUpMod.LOGGER.info( "[RESTRAINT-UTIL] Removed speed reduction from {} on login/respawn", entity.getName().getString() ); } } } // POLE BINDING UTILITIES /** * Find the closest fence or wall block within a radius. * * @param level The level to search in * @param center The center position to search from * @param radius The search radius in blocks * @return The position of the closest fence/wall, or null if none found */ @Nullable public static BlockPos findClosestFence( Level level, BlockPos center, int radius ) { if (level == null || center == null) return null; BlockPos closestFence = null; double closestDistance = Double.MAX_VALUE; for (int x = -radius; x <= radius; x++) { for (int y = -radius; y <= radius; y++) { for (int z = -radius; z <= radius; z++) { BlockPos checkPos = center.offset(x, y, z); BlockState state = level.getBlockState(checkPos); if ( state.getBlock() instanceof FenceBlock || state.getBlock() instanceof WallBlock ) { double dist = center.distSqr(checkPos); if (dist < closestDistance) { closestDistance = dist; closestFence = checkPos; } } } } } return closestFence; } /** * Tie an entity to the closest fence or wall block. * Works for both Players (via LeashProxy) and NPCs (via vanilla leash). * * @param entity The entity to tie * @param searchRadius The search radius for fence blocks * @return true if successfully tied, false otherwise */ public static boolean tieToClosestPole( LivingEntity entity, int searchRadius ) { if (entity == null || entity.level().isClientSide) return false; if (!(entity.level() instanceof ServerLevel serverLevel)) return false; BlockPos entityPos = entity.blockPosition(); BlockPos closestFence = findClosestFence( serverLevel, entityPos, searchRadius ); if (closestFence == null) { if (DEBUG) { TiedUpMod.LOGGER.debug( "[RESTRAINT-UTIL] No fence found within {} blocks of {}", searchRadius, entity.getName().getString() ); } return false; } // Get or create a LeashFenceKnotEntity at the fence position LeashFenceKnotEntity fenceKnot = LeashFenceKnotEntity.getOrCreateKnot( serverLevel, closestFence ); if (fenceKnot == null) { TiedUpMod.LOGGER.warn( "[RESTRAINT-UTIL] Failed to create fence knot at {}", closestFence ); return false; } // Handle differently based on entity type if (entity instanceof Player player) { // Player: use LeashProxy system if (player instanceof IPlayerLeashAccess access) { access.tiedup$attachLeash(fenceKnot); TiedUpMod.LOGGER.debug( "[RESTRAINT-UTIL] Tied player {} to pole at {}", player.getName().getString(), closestFence ); return true; } else { TiedUpMod.LOGGER.error( "[RESTRAINT-UTIL] Player {} does not implement IPlayerLeashAccess!", player.getName().getString() ); return false; } } else if (entity instanceof Mob mob) { // NPC (Mob): use vanilla leash mechanics mob.setLeashedTo(fenceKnot, true); TiedUpMod.LOGGER.debug( "[RESTRAINT-UTIL] Tied mob {} to pole at {}", mob.getName().getString(), closestFence ); return true; } return false; } // CHLOROFORM UTILITIES /** * Apply chloroform effects to an entity. * Effects: Slowness, Mining Fatigue, Blindness, Jump Boost (all at max amplifier). * * @param entity The entity to affect * @param durationSeconds Duration in seconds */ public static void applyChloroformEffects( LivingEntity entity, int durationSeconds ) { if (entity == null || entity.level().isClientSide) return; int tickDuration = durationSeconds * GameConstants.TICKS_PER_SECOND; entity.addEffect( new MobEffectInstance( MobEffects.MOVEMENT_SLOWDOWN, tickDuration, GameConstants.CHLOROFORM_SLOWDOWN_AMPLIFIER, false, false ) ); entity.addEffect( new MobEffectInstance( MobEffects.DIG_SLOWDOWN, tickDuration, GameConstants.CHLOROFORM_DIG_SLOWDOWN_AMPLIFIER, false, false ) ); entity.addEffect( new MobEffectInstance( MobEffects.BLINDNESS, tickDuration, GameConstants.CHLOROFORM_BLINDNESS_AMPLIFIER, false, false ) ); entity.addEffect( new MobEffectInstance( MobEffects.JUMP, tickDuration, GameConstants.CHLOROFORM_JUMP_AMPLIFIER, false, false ) ); // Stop navigation for mobs if (entity instanceof Mob mob) { mob.getNavigation().stop(); } if (DEBUG) { TiedUpMod.LOGGER.debug( "[RESTRAINT-UTIL] Applied chloroform to {} for {} seconds", entity.getName().getString(), durationSeconds ); } } }