package com.tiedup.remake.items; import com.tiedup.remake.core.ModConfig; import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.entities.EntityDamsel; import com.tiedup.remake.entities.EntityKidnapper; import com.tiedup.remake.state.IBondageState; import com.tiedup.remake.state.PlayerBindState; import com.tiedup.remake.util.KidnappedHelper; import com.tiedup.remake.util.TiedUpSounds; import net.minecraft.core.particles.ParticleTypes; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; /** * Whip - Tool for discipline * Right-click a tied entity to deal damage and decrease their resistance. * * Phase 15: Full whip mechanics implementation * * Effects: * - Deals damage (configurable) * - Decreases bind resistance (configurable) * - Plays whip crack sound * - Shows damage particles * - Consumes durability * * Opposite of paddle (which increases resistance). */ public class ItemWhip extends Item { public ItemWhip() { super(new Item.Properties().stacksTo(1).durability(256)); } /** * Called when player right-clicks another entity with the whip. * Deals damage and decreases resistance if target is restrained. * * @param stack The item stack * @param player The player using the whip * @param target The entity being interacted with * @param hand The hand holding the whip * @return SUCCESS if whipping happened, PASS otherwise */ @Override public InteractionResult interactLivingEntity( ItemStack stack, Player player, LivingEntity target, InteractionHand hand ) { // Only run on server side if (player.level().isClientSide) { return InteractionResult.SUCCESS; } // NPC whip - visual/sound feedback only (no personality effect) if (target instanceof EntityDamsel damsel) { // Set whip time for anti-flee system (stops fleeing for ~10 seconds) damsel.setLastWhipTime(player.level().getGameTime()); // Visual feedback TiedUpSounds.playWhipSound(target); if (player.level() instanceof ServerLevel serverLevel) { serverLevel.sendParticles( ParticleTypes.CRIT, target.getX(), target.getY() + target.getBbHeight() / 2.0, target.getZ(), 10, 0.5, 0.5, 0.5, 0.1 ); } // Consume durability stack.hurtAndBreak(1, player, p -> p.broadcastBreakEvent(hand)); return InteractionResult.SUCCESS; } // Check if target can be restrained (Player, EntityDamsel, EntityKidnapper) IBondageState targetState = KidnappedHelper.getKidnappedState(target); if (targetState == null || !targetState.isTiedUp()) { return InteractionResult.PASS; } float damage = ModConfig.SERVER.whipDamage.get().floatValue(); int resistanceDecrease = ModConfig.SERVER.whipResistanceDecrease.get(); // 1. Play whip sound TiedUpSounds.playWhipSound(target); // 2. Deal damage DamageSource damageSource = player.damageSources().playerAttack(player); target.hurt(damageSource, damage); // 3. Show damage particles (critical hit particles) if (player.level() instanceof ServerLevel serverLevel) { serverLevel.sendParticles( ParticleTypes.CRIT, target.getX(), target.getY() + target.getBbHeight() / 2.0, target.getZ(), 10, // count 0.5, 0.5, 0.5, // spread 0.1 // speed ); } // 4. Decrease resistance decreaseResistance(targetState, target, resistanceDecrease); // 5. Damage the whip (consume durability) stack.hurtAndBreak(1, player, p -> { p.broadcastBreakEvent(hand); }); TiedUpMod.LOGGER.debug( "[ItemWhip] {} whipped {} (damage: {}, resistance -{})", player.getName().getString(), target.getName().getString(), damage, resistanceDecrease ); return InteractionResult.SUCCESS; } /** * Decrease the target's bind resistance. * Works for both players (via PlayerBindState) and NPCs. * * @param targetState The target's IBondageState state * @param target The target entity * @param amount The amount to decrease */ private void decreaseResistance( IBondageState targetState, LivingEntity target, int amount ) { if (target instanceof Player player) { // For players, use PlayerBindState PlayerBindState bindState = PlayerBindState.getInstance(player); int currentResistance = bindState.getCurrentBindResistance(); int newResistance = Math.max(0, currentResistance - amount); bindState.setCurrentBindResistance(newResistance); // MEDIUM FIX: Sync resistance change to client // Resistance is stored in bind item NBT, so we must sync inventory // Without this, client still shows old resistance value in UI // Sync V2 equipment (resistance NBT changed on the stored ItemStack) if (player instanceof net.minecraft.server.level.ServerPlayer serverPlayer) { com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper.sync(serverPlayer); } TiedUpMod.LOGGER.debug( "[ItemWhip] Player resistance: {} -> {}", currentResistance, newResistance ); } else { // For NPCs, resistance is not tracked the same way // Just log the whip action (NPC doesn't struggle, so resistance is less relevant) TiedUpMod.LOGGER.debug( "[ItemWhip] Whipped NPC (resistance not tracked for NPCs)" ); } } }