package com.tiedup.remake.v2.bondage; import com.tiedup.remake.core.SettingsAccessor; import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.state.IBondageState; import com.tiedup.remake.state.PlayerBindState; import com.tiedup.remake.tasks.TyingTask; import com.tiedup.remake.tasks.V2TyingPlayerTask; import com.tiedup.remake.util.KidnappedHelper; import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper; import com.tiedup.remake.v2.bondage.V2EquipResult; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; /** * Handles the tying interaction flow for V2 data-driven ARMS items. * *
Extracted from {@code ItemBind.interactLivingEntity()} to support * the same tying task flow for data-driven items.
*/ public final class TyingInteractionHelper { private TyingInteractionHelper() {} /** * Handle right-click tying of a target entity with a V2 ARMS item. * Creates/continues a V2TyingPlayerTask. */ public static InteractionResult handleTying( ServerPlayer player, LivingEntity target, ItemStack stack, InteractionHand hand ) { IBondageState targetState = KidnappedHelper.getKidnappedState(target); if (targetState == null) return InteractionResult.PASS; // Kidnapper can't be tied themselves IBondageState kidnapperState = KidnappedHelper.getKidnappedState(player); if (kidnapperState != null && kidnapperState.isTiedUp()) { return InteractionResult.PASS; } // Already tied — try to swap (check both V1 state and V2 region to prevent dual-bind) if (targetState.isTiedUp() || V2EquipmentHelper.isRegionOccupied(target, BodyRegionV2.ARMS)) { if (stack.isEmpty()) return InteractionResult.PASS; ItemStack oldBind = V2EquipmentHelper.unequipFromRegion(target, BodyRegionV2.ARMS); if (!oldBind.isEmpty()) { V2EquipResult result = V2EquipmentHelper.equipItem(target, stack.copy()); if (result.isSuccess()) { stack.shrink(1); target.spawnAtLocation(oldBind); TiedUpMod.LOGGER.debug("[TyingInteraction] Swapped bind on {}", target.getName().getString()); return InteractionResult.SUCCESS; } else { // Equip failed — rollback: re-equip old bind V2EquipResult rollback = V2EquipmentHelper.equipItem(target, oldBind); if (!rollback.isSuccess()) { // Rollback also failed — drop old bind as safety net target.spawnAtLocation(oldBind); TiedUpMod.LOGGER.warn("[TyingInteraction] Swap AND rollback failed, dropped old bind for {}", target.getName().getString()); } else { TiedUpMod.LOGGER.debug("[TyingInteraction] Swap failed, rolled back old bind on {}", target.getName().getString()); } return InteractionResult.PASS; } } return InteractionResult.PASS; } // Distance + line-of-sight (skip for self-tying) boolean isSelfTying = player.equals(target); if (!isSelfTying) { if (player.distanceTo(target) > 4.0 || !player.hasLineOfSight(target)) { return InteractionResult.PASS; } } // Create/continue tying task PlayerBindState playerState = PlayerBindState.getInstance(player); if (playerState == null) return InteractionResult.PASS; int tyingSeconds = SettingsAccessor.getTyingPlayerTime(player.level().getGameRules()); V2TyingPlayerTask newTask = new V2TyingPlayerTask( stack.copy(), stack, targetState, target, tyingSeconds, player.level(), player ); TyingTask currentTask = playerState.getCurrentTyingTask(); if (currentTask == null || !(currentTask instanceof V2TyingPlayerTask) || !currentTask.isSameTarget(target) || currentTask.isOutdated() || !ItemStack.matches(currentTask.getBind(), stack)) { // Start new task (also handles case where existing task is V1 TyingPlayerTask) playerState.setCurrentTyingTask(newTask); newTask.start(); } else { newTask = (V2TyingPlayerTask) currentTask; } newTask.update(); if (newTask.isStopped()) { // Item already consumed by V2TyingPlayerTask.onComplete() — don't shrink again playerState.setCurrentTyingTask(null); TiedUpMod.LOGGER.info("[TyingInteraction] {} tied {}", player.getName().getString(), target.getName().getString()); } return InteractionResult.SUCCESS; } }