Files
TiedUp-/src/main/java/com/tiedup/remake/state/components/PlayerEquipment.java
NotEvil 3d61c9e9e6 feat(D-01/C): consumer migration — 85 files migrated to V2 helpers
Phase 1 (state): PlayerBindState, PlayerCaptorManager, PlayerEquipment,
  PlayerDataRetrieval, PlayerLifecycle, PlayerShockCollar, StruggleAccessory
Phase 2 (client): AnimationTickHandler, NpcAnimationTickHandler, 5 render
  handlers, DamselModel, 3 client mixins, SelfBondageInputHandler,
  SlaveManagementScreen, ActionPanel, SlaveEntryWidget, ModKeybindings
Phase 3 (entities): 28 entity/AI files migrated to CollarHelper,
  BindModeHelper, PoseTypeHelper, createStack()
Phase 4 (network): PacketSlaveAction, PacketMasterEquip,
  PacketAssignCellToCollar, PacketNpcCommand, PacketFurnitureForcemount
Phase 5 (events): RestraintTaskTickHandler, PetPlayRestrictionHandler,
  PlayerEnslavementHandler, ChatEventHandler, LaborAttackPunishmentHandler
Phase 6 (commands): BondageSubCommand, CollarCommand, NPCCommand,
  KidnapSetCommand
Phase 7 (compat): MCAKidnappedAdapter, MCA mixins
Phase 8 (misc): GagTalkManager, PetRequestManager, HangingCagePiece,
  BondageItemBlockEntity, TrappedChestBlockEntity, DispenserBehaviors,
  BondageItemLoaderUtility, RestraintApplicator, StruggleSessionManager,
  MovementStyleResolver, CampLifecycleManager

Some files retain dual V1/V2 checks (instanceof V1 || V2Helper) for
coexistence — V1-only branches removed in Branch D.
2026-04-15 00:16:50 +02:00

516 lines
16 KiB
Java

package com.tiedup.remake.state.components;
import com.tiedup.remake.items.base.ILockable;
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 ILockable lockable) {
if (!force && lockable.isLocked(collar)) return ItemStack.EMPTY;
lockable.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<ItemStack> 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
}
}