package com.tiedup.remake.entities; import com.tiedup.remake.core.SystemMessageManager; import com.tiedup.remake.v2.bondage.CollarHelper; import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem; import net.minecraft.resources.ResourceLocation; import com.tiedup.remake.state.PlayerBindState; import com.tiedup.remake.util.MessageDispatcher; import com.tiedup.remake.util.teleport.Position; import com.tiedup.remake.util.time.Timer; import com.tiedup.remake.v2.BodyRegionV2; import java.util.HashMap; import java.util.Map; import java.util.UUID; import net.minecraft.ChatFormatting; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; /** * Handles the bondage service system for TiedUp NPCs. * When enabled via collar settings, players who attack the NPC * will be captured and teleported to the configured prison. * *

Flow: *

* *

This system is transient - serviceTargets are not persisted. */ public class BondageServiceHandler { private static final String DEFAULT_MESSAGE = "Hello! Would you like to be tied up in my owner's prison?"; private static final int WARNING_TIMER_SECONDS = 10; private final AbstractTiedUpNpc npc; private final Map serviceTargets = new HashMap<>(); public BondageServiceHandler(AbstractTiedUpNpc npc) { this.npc = npc; } /** * Check if bondage service is enabled for this damsel. * Requires: collar with bondage service flag ON and cell assigned. * * @return true if bondage service is active */ public boolean isEnabled() { if (!npc.hasCollar()) return false; ItemStack collar = npc.getEquipment(BodyRegionV2.NECK); return ( CollarHelper.hasCellAssigned(collar) && CollarHelper.isBondageServiceEnabled(collar) ); } /** * Get the custom bondage service message from the collar. * * @return Custom message or default message */ public String getMessage() { if (npc.hasCollar()) { ItemStack collar = npc.getEquipment(BodyRegionV2.NECK); String message = CollarHelper.getServiceSentence(collar); if (message != null && !message.isEmpty()) { return message; } } return DEFAULT_MESSAGE; } /** * Handle a player attack on the damsel. * If bondage service is enabled, intercepts the attack. * * @param player The attacking player * @return true if the attack was intercepted (damage should be cancelled) */ public boolean handlePlayerAttack(Player player) { if (player == null || npc.isTiedUp() || !isEnabled()) { return false; } UUID playerID = player.getUUID(); Timer timer = serviceTargets.get(playerID); if (timer != null && !timer.isExpired()) { // SECOND HIT - Capture! capturePlayer(player); serviceTargets.remove(playerID); } else { // FIRST HIT - Warning String message = getMessage() + " Hit me again!"; MessageDispatcher.talkTo(npc, player, message); serviceTargets.put( playerID, new Timer(WARNING_TIMER_SECONDS, npc.level()) ); } return true; // Attack intercepted } /** * Capture a player and teleport them to the configured cell. * * @param player The player to capture */ private void capturePlayer(Player player) { ItemStack collar = npc.getEquipment(BodyRegionV2.NECK); if (!CollarHelper.isCollar(collar)) return; java.util.UUID cellId = CollarHelper.getCellId(collar); if (cellId == null) return; // Get cell position from registry if ( !(npc.level() instanceof net.minecraft.server.level.ServerLevel serverLevel) ) return; com.tiedup.remake.cells.CellDataV2 cell = com.tiedup.remake.cells.CellRegistryV2.get(serverLevel).getCell( cellId ); if (cell == null) return; Position cellPosition = new Position( cell.getSpawnPoint().above(), serverLevel.dimension() ); // Warn masters if configured warnOwners(player, collar); // Get player's kidnapped state PlayerBindState state = PlayerBindState.getInstance(player); if (state != null) { // Apply bondage state.equip( BodyRegionV2.ARMS, DataDrivenBondageItem.createStack(ResourceLocation.fromNamespaceAndPath("tiedup", "ropes")) ); state.equip( BodyRegionV2.MOUTH, DataDrivenBondageItem.createStack(ResourceLocation.fromNamespaceAndPath("tiedup", "ball_gag")) ); // Teleport to cell state.teleportToPosition(cellPosition); // Tie to pole if configured on collar if (CollarHelper.shouldTieToPole(collar)) { state.tieToClosestPole(3); } } // Announce capture MessageDispatcher.talkTo(npc, player, "Welcome to my owner's cell!"); } /** * Warn collar owners about a capture via bondage service. * * @param capturedPlayer The player who was captured * @param itemCollar The collar item * @param collarStack The collar ItemStack */ private void warnOwners( Player capturedPlayer, ItemStack collarStack ) { if (!CollarHelper.shouldWarnMasters(collarStack)) { return; } String message = npc.getNpcName() + " captured " + capturedPlayer.getName().getString() + " via bondage service!"; for (UUID ownerUUID : CollarHelper.getOwners(collarStack)) { Player owner = npc.level().getPlayerByUUID(ownerUUID); if (owner != null) { SystemMessageManager.sendChatToPlayer( owner, message, ChatFormatting.GOLD ); } } } }