package com.tiedup.remake.util; import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.network.sync.SyncManager; import com.tiedup.remake.state.IBondageState; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Predicate; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.InteractionResult; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import org.jetbrains.annotations.Nullable; /** * Helper class for common bondage equipment interaction logic. * * This class extracts the common interactLivingEntity pattern * for bondage equipment items (gags, blindfolds, mittens, earplugs, collars). */ public class EquipmentInteractionHelper { /** * Standard equipment interaction for bondage items. * * Handles the common flow: check state → equip or replace equipment. * * @param stack The item stack being used * @param player The player using the item * @param target The entity being equipped * @param isEquipped Predicate to check if target already has this equipment * @param putOn Consumer to equip the item on target (state, itemCopy) * @param replace Function to replace existing equipment (state, itemCopy) → oldItem * @param sendMessage Consumer to send message to target (player, target) * @param logPrefix Log prefix for this equipment type * @return InteractionResult */ public static InteractionResult equipOnTarget( ItemStack stack, Player player, LivingEntity target, Predicate isEquipped, BiConsumer putOn, BiFunction replace, BiConsumer sendMessage, String logPrefix ) { return equipOnTarget( stack, player, target, isEquipped, putOn, replace, sendMessage, logPrefix, null, null, null ); } /** * Equipment interaction with additional hooks for special items (like Collar). * * @param stack The item stack being used * @param player The player using the item * @param target The entity being equipped * @param isEquipped Predicate to check if target already has this equipment * @param putOn Consumer to equip the item on target (state, itemCopy) * @param replace Function to replace existing equipment (state, itemCopy) → oldItem * @param sendMessage Consumer to send message to target (player, target) * @param logPrefix Log prefix for this equipment type * @param preEquipHook Optional hook called before equipping (for owner registration, etc.) * @param postEquipHook Optional hook called after equipping (for sound, particles, etc.) * @param canReplace Optional predicate to check if replacement is allowed (for lock check) * @return InteractionResult */ public static InteractionResult equipOnTarget( ItemStack stack, Player player, LivingEntity target, Predicate isEquipped, BiConsumer putOn, BiFunction replace, BiConsumer sendMessage, String logPrefix, @Nullable EquipContext preEquipHook, @Nullable EquipContext postEquipHook, @Nullable ReplacePredicate canReplace ) { // Server-side only if (player.level().isClientSide) { return InteractionResult.SUCCESS; } // Get target state IBondageState targetState = KidnappedHelper.getKidnappedState(target); if (targetState == null) { return InteractionResult.PASS; } // Must be tied up if (!targetState.isTiedUp()) { return InteractionResult.PASS; } // Pre-equip hook (e.g., register collar owner) if (preEquipHook != null) { preEquipHook.execute(stack, player, target, targetState); } // Case 1: Not equipped - put on new if (!isEquipped.test(targetState)) { putOn.accept(targetState, stack.copy()); stack.shrink(1); sendMessage.accept(player, target); syncIfPlayer(target); // Post-equip hook (e.g., play sound) if (postEquipHook != null) { postEquipHook.execute(stack, player, target, targetState); } TiedUpMod.LOGGER.info( "[{}] {} put {} on {}", logPrefix, player.getName().getString(), logPrefix.toLowerCase(), target.getName().getString() ); return InteractionResult.SUCCESS; } // Case 2: Already equipped - replace else { // Check if replacement is allowed (e.g., not locked) if ( canReplace != null && !canReplace.canReplace(targetState, player) ) { return InteractionResult.PASS; } ItemStack oldItem = replace.apply(targetState, stack.copy()); if (oldItem != null && !oldItem.isEmpty()) { stack.shrink(1); targetState.kidnappedDropItem(oldItem); sendMessage.accept(player, target); syncIfPlayer(target); // Post-equip hook if (postEquipHook != null) { postEquipHook.execute(stack, player, target, targetState); } TiedUpMod.LOGGER.info( "[{}] {} replaced {} on {}", logPrefix, player.getName().getString(), logPrefix.toLowerCase(), target.getName().getString() ); return InteractionResult.SUCCESS; } } return InteractionResult.PASS; } /** * Sync inventory if target is a ServerPlayer. */ private static void syncIfPlayer(LivingEntity target) { if (target instanceof ServerPlayer serverPlayer) { SyncManager.syncInventory(serverPlayer); } } /** * Functional interface for pre/post equip hooks. */ @FunctionalInterface public interface EquipContext { void execute( ItemStack stack, Player player, LivingEntity target, IBondageState state ); } /** * Functional interface to check if replacement is allowed. */ @FunctionalInterface public interface ReplacePredicate { boolean canReplace(IBondageState state, Player player); } }