package com.tiedup.remake.state.components; import com.tiedup.remake.items.base.ILockable; import com.tiedup.remake.items.base.ItemBind; import com.tiedup.remake.items.base.ItemCollar; import com.tiedup.remake.state.hosts.IPlayerBindStateHost; import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.bondage.IV2BondageEquipment; import com.tiedup.remake.v2.bondage.IV2BondageItem; import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper; import java.util.function.Supplier; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; /** * Component responsible for bondage equipment management. * Handles putting on, taking off, and replacing all bondage items. * * Single Responsibility: Equipment lifecycle management * Complexity: MEDIUM (V2 equipment coupling) * Risk: MEDIUM (core equipment system) * * Epic 5F: Uses V2EquipmentHelper. * Uses low-level setInRegion for equips because V1 items (IBondageItem) do not * implement IV2BondageItem, so V2EquipmentHelper.equipItem() would reject them. */ public class PlayerEquipment { private final IPlayerBindStateHost host; public PlayerEquipment(IPlayerBindStateHost host) { this.host = host; } // ========== Put On Methods ========== /** Equips a bind item and applies speed reduction. */ public void putBindOn(ItemStack bind) { equipInRegion(BodyRegionV2.ARMS, bind); } /** Equips a gag (enables gag talk if implemented). Issue #14 fix: now calls onEquipped. */ public void putGagOn(ItemStack gag) { equipInRegion(BodyRegionV2.MOUTH, gag); } /** Equips a blindfold (restricts vision). Issue #14 fix: now calls onEquipped. */ public void putBlindfoldOn(ItemStack blindfold) { equipInRegion(BodyRegionV2.EYES, blindfold); } /** Equips a collar (starts GPS/Shock monitoring). Issue #14 fix: now calls onEquipped. */ public void putCollarOn(ItemStack collar) { equipInRegion(BodyRegionV2.NECK, collar); } /** Issue #14 fix: now calls onEquipped. Syncs clothes config to all clients. */ public void putClothesOn(ItemStack clothes) { equipInRegion(BodyRegionV2.TORSO, clothes); // Sync clothes config (dynamic textures, etc.) to all tracking clients host.syncClothesConfig(); } /** Equips mittens (blocks hand interactions). Mittens system. Issue #14 fix: now calls onEquipped. */ public void putMittensOn(ItemStack mittens) { equipInRegion(BodyRegionV2.HANDS, mittens); checkMittensAfterApply(); } /** Equips earplugs (muffles sounds). Issue #14 fix: now calls onEquipped. */ public void putEarplugsOn(ItemStack earplugs) { equipInRegion(BodyRegionV2.EARS, earplugs); checkEarplugsAfterApply(); } // ========== Take Off Methods ========== /** Removes binds and restores speed. */ public ItemStack takeBindOff() { return V2EquipmentHelper.unequipFromRegion( host.getPlayer(), BodyRegionV2.ARMS ); } public ItemStack takeGagOff() { return V2EquipmentHelper.unequipFromRegion( host.getPlayer(), BodyRegionV2.MOUTH ); } public ItemStack takeBlindfoldOff() { return V2EquipmentHelper.unequipFromRegion( host.getPlayer(), BodyRegionV2.EYES ); } public ItemStack takeCollarOff() { return V2EquipmentHelper.unequipFromRegion( host.getPlayer(), BodyRegionV2.NECK ); } /** Removes mittens. Mittens system */ public ItemStack takeMittensOff() { ItemStack mittens = V2EquipmentHelper.getInRegion( host.getPlayer(), BodyRegionV2.HANDS ); if (isLocked(mittens, false)) { return ItemStack.EMPTY; } return V2EquipmentHelper.unequipFromRegion( host.getPlayer(), BodyRegionV2.HANDS ); } public ItemStack takeEarplugsOff() { ItemStack earplugs = V2EquipmentHelper.getInRegion( host.getPlayer(), BodyRegionV2.EARS ); if (isLocked(earplugs, false)) { return ItemStack.EMPTY; } return V2EquipmentHelper.unequipFromRegion( host.getPlayer(), BodyRegionV2.EARS ); } public ItemStack takeClothesOff() { ItemStack clothes = V2EquipmentHelper.unequipFromRegion( host.getPlayer(), BodyRegionV2.TORSO ); // Sync clothes removal to all tracking clients host.syncClothesConfig(); return clothes; } /** * Tries to remove the collar. Fails if locked unless forced. */ public ItemStack takeCollarOff(boolean force) { Player player = host.getPlayer(); ItemStack collar = V2EquipmentHelper.getInRegion( player, BodyRegionV2.NECK ); if (collar.isEmpty()) return ItemStack.EMPTY; if (collar.getItem() instanceof ItemCollar collarItem) { if (!force && collarItem.isLocked(collar)) return ItemStack.EMPTY; collarItem.setLocked(collar, false); } return V2EquipmentHelper.unequipFromRegion(player, BodyRegionV2.NECK); } // ========== Replacement Methods ========== /** Replaces the blindfold and returns the old one. Issue #14 fix: now calls lifecycle hooks. */ public ItemStack replaceBlindfold(ItemStack newBlindfold) { ItemStack current = V2EquipmentHelper.getInRegion( host.getPlayer(), BodyRegionV2.EYES ); if (current.isEmpty()) return ItemStack.EMPTY; return replaceInRegion(BodyRegionV2.EYES, newBlindfold); } /** Replaces the gag and returns the old one. Issue #14 fix: now calls lifecycle hooks. */ public ItemStack replaceGag(ItemStack newGag) { ItemStack current = V2EquipmentHelper.getInRegion( host.getPlayer(), BodyRegionV2.MOUTH ); if (current.isEmpty()) return ItemStack.EMPTY; return replaceInRegion(BodyRegionV2.MOUTH, newGag); } /** Replaces the collar and returns the old one. Issue #14 fix: now calls lifecycle hooks. */ public ItemStack replaceCollar(ItemStack newCollar) { ItemStack current = V2EquipmentHelper.getInRegion( host.getPlayer(), BodyRegionV2.NECK ); if (current.isEmpty()) return ItemStack.EMPTY; return replaceInRegion(BodyRegionV2.NECK, newCollar); } /** * Thread Safety: Synchronized to prevent inventory tearing (gap between remove and add). */ public synchronized ItemStack replaceBind(ItemStack newBind) { ItemStack old = takeBindOff(); if (!old.isEmpty()) { putBindOn(newBind); } return old; } /** * Thread Safety: Synchronized to prevent inventory tearing (gap between remove and add). */ public synchronized ItemStack replaceBind( ItemStack newBind, boolean force ) { // Safety: Don't remove current bind if newBind is empty if (newBind.isEmpty()) { return ItemStack.EMPTY; } ItemStack current = V2EquipmentHelper.getInRegion( host.getPlayer(), BodyRegionV2.ARMS ); if (isLocked(current, force)) { return ItemStack.EMPTY; } ItemStack old = takeBindOff(); if (!old.isEmpty()) { putBindOn(newBind); } return old; } public ItemStack replaceGag(ItemStack newGag, boolean force) { ItemStack current = V2EquipmentHelper.getInRegion( host.getPlayer(), BodyRegionV2.MOUTH ); if (isLocked(current, force)) { return ItemStack.EMPTY; } ItemStack old = V2EquipmentHelper.unequipFromRegion( host.getPlayer(), BodyRegionV2.MOUTH ); putGagOn(newGag); return old; } public ItemStack replaceBlindfold(ItemStack newBlindfold, boolean force) { ItemStack current = V2EquipmentHelper.getInRegion( host.getPlayer(), BodyRegionV2.EYES ); if (isLocked(current, force)) { return ItemStack.EMPTY; } ItemStack old = V2EquipmentHelper.unequipFromRegion( host.getPlayer(), BodyRegionV2.EYES ); putBlindfoldOn(newBlindfold); return old; } public ItemStack replaceCollar(ItemStack newCollar, boolean force) { ItemStack current = V2EquipmentHelper.getInRegion( host.getPlayer(), BodyRegionV2.NECK ); if (isLocked(current, force)) { return ItemStack.EMPTY; } ItemStack old = takeCollarOff(force); putCollarOn(newCollar); return old; } public ItemStack replaceEarplugs(ItemStack newEarplugs) { return replaceEarplugs(newEarplugs, false); } public ItemStack replaceEarplugs(ItemStack newEarplugs, boolean force) { ItemStack current = V2EquipmentHelper.getInRegion( host.getPlayer(), BodyRegionV2.EARS ); if (isLocked(current, force)) { return ItemStack.EMPTY; } ItemStack old = takeEarplugsOff(); putEarplugsOn(newEarplugs); return old; } public ItemStack replaceMittens(ItemStack newMittens) { return replaceMittens(newMittens, false); } public ItemStack replaceMittens(ItemStack newMittens, boolean force) { ItemStack current = V2EquipmentHelper.getInRegion( host.getPlayer(), BodyRegionV2.HANDS ); if (isLocked(current, force)) { return ItemStack.EMPTY; } ItemStack old = takeMittensOff(); if (!old.isEmpty() || !hasMittens()) { putMittensOn(newMittens); } return old; } // ========== Resistance Methods ========== /** */ public synchronized int getCurrentBindResistance() { Player player = host.getPlayer(); ItemStack stack = V2EquipmentHelper.getInRegion( player, BodyRegionV2.ARMS ); if (stack.isEmpty()) return 0; // V1 and V2 both implement IHasResistance if (stack.getItem() instanceof com.tiedup.remake.items.base.IHasResistance resistance) { return resistance.getCurrentResistance(stack, player); } return 0; } /** */ public synchronized void setCurrentBindResistance(int resistance) { Player player = host.getPlayer(); ItemStack stack = V2EquipmentHelper.getInRegion( player, BodyRegionV2.ARMS ); if (stack.isEmpty()) return; // V1 and V2 both implement IHasResistance if (stack.getItem() instanceof com.tiedup.remake.items.base.IHasResistance resistanceItem) { resistanceItem.setCurrentResistance(stack, resistance); } } /** */ public synchronized int getCurrentCollarResistance() { Player player = host.getPlayer(); ItemStack stack = V2EquipmentHelper.getInRegion( player, BodyRegionV2.NECK ); if (stack.isEmpty()) return 0; // V1 and V2 both implement IHasResistance if (stack.getItem() instanceof com.tiedup.remake.items.base.IHasResistance resistance) { return resistance.getCurrentResistance(stack, player); } return 0; } /** */ public synchronized void setCurrentCollarResistance(int resistance) { Player player = host.getPlayer(); ItemStack stack = V2EquipmentHelper.getInRegion( player, BodyRegionV2.NECK ); if (stack.isEmpty()) return; // V1 and V2 both implement IHasResistance if (stack.getItem() instanceof com.tiedup.remake.items.base.IHasResistance resistanceItem) { resistanceItem.setCurrentResistance(stack, resistance); } } // ========== Helper Methods ========== /** Helper to drop an item at the kidnapped player's feet. */ public void kidnappedDropItem(ItemStack stack) { if (stack.isEmpty() || host.getPlayer() == null) return; host.getPlayer().drop(stack, false); } /** * Helper to take a bondage item if it's unlocked, and drop it. */ public void takeBondageItemIfUnlocked( ItemStack item, Supplier takeOffMethod ) { if (isLocked(item, false)) { return; // Item is locked, cannot remove } ItemStack removed = takeOffMethod.get(); if (!removed.isEmpty()) { kidnappedDropItem(removed); } } /** * Check if an item is locked. * @param stack The item to check * @param force If true, returns false (bypasses lock) * @return true if locked and not forced */ public boolean isLocked(ItemStack stack, boolean force) { if (force) return false; if (stack.isEmpty()) return false; if (stack.getItem() instanceof ILockable lockable) { return lockable.isLocked(stack); } return false; } private boolean hasMittens() { return V2EquipmentHelper.isRegionOccupied( host.getPlayer(), BodyRegionV2.HANDS ); } // ========== Low-level V2 equipment operations ========== /** * Low-level equip: writes to V2 capability, calls IBondageItem lifecycle hooks, syncs. * Uses setInRegion directly because V1 items do not implement IV2BondageItem. */ private void equipInRegion(BodyRegionV2 region, ItemStack stack) { Player player = host.getPlayer(); if (player == null || player.level().isClientSide) return; if (stack.isEmpty()) return; IV2BondageEquipment equip = V2EquipmentHelper.getEquipment(player); if (equip == null) return; // Check if already occupied if (equip.isRegionOccupied(region)) return; // Check canEquip via V1 IBondageItem interface if (stack.getItem() instanceof IV2BondageItem bondageItem) { if (!bondageItem.canEquip(stack, player)) return; } equip.setInRegion(region, stack.copy()); // Fire lifecycle hook if (stack.getItem() instanceof IV2BondageItem bondageItem) { bondageItem.onEquipped(stack, player); } V2EquipmentHelper.sync(player); } /** * Low-level replace: unequips old item with lifecycle hooks, equips new item. * Unequips old item (with onUnequipped hook), sets new item, fires onEquipped. */ private ItemStack replaceInRegion(BodyRegionV2 region, ItemStack newStack) { Player player = host.getPlayer(); if ( player == null || player.level().isClientSide ) return ItemStack.EMPTY; IV2BondageEquipment equip = V2EquipmentHelper.getEquipment(player); if (equip == null) return ItemStack.EMPTY; ItemStack oldStack = equip.getInRegion(region); // Call onUnequipped for the old item if ( !oldStack.isEmpty() && oldStack.getItem() instanceof IV2BondageItem oldItem ) { oldItem.onUnequipped(oldStack, player); } // Set the new item equip.setInRegion(region, newStack.copy()); // Call onEquipped for the new item if ( !newStack.isEmpty() && newStack.getItem() instanceof IV2BondageItem newItem ) { newItem.onEquipped(newStack, player); } V2EquipmentHelper.sync(player); return oldStack; } // ========== Callbacks ========== public void checkGagAfterApply() { // Gag talk handled via ChatEventHandler + GagTalkManager (event-based) // No initialization needed here - effects apply automatically on chat } public void checkBlindfoldAfterApply() { // Effects applied client-side via rendering } public void checkEarplugsAfterApply() { // Effects applied client-side via sound system } public void checkCollarAfterApply() { // Collar timers/GPS handled elsewhere } public void checkMittensAfterApply() { // Mittens effects handled elsewhere } }