package com.tiedup.remake.compat.mca; import com.tiedup.remake.compat.mca.ai.MCABondageAIController; import com.tiedup.remake.compat.mca.ai.MCABondageAILevel; import com.tiedup.remake.compat.mca.dialogue.MCADialogueManager; import com.tiedup.remake.compat.mca.personality.MCAMoodManager; import com.tiedup.remake.compat.mca.personality.MCAPersonality; import com.tiedup.remake.compat.mca.personality.MCAPersonalityManager; import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.state.IRestrainable; import java.util.Map; import java.util.WeakHashMap; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.item.ItemStack; import org.jetbrains.annotations.Nullable; /** * Central coordinator for all MCA-TiedUp integration. * * Responsibilities: * - Manages AI state for tied MCA villagers * - Coordinates sync operations * - Provides unified API for bondage operations * - Handles capture/release lifecycle * * This is the main entry point for all MCA bondage operations. * Other code should go through this manager rather than directly * manipulating capabilities or AI. */ public class MCABondageManager { // Singleton instance private static final MCABondageManager INSTANCE = new MCABondageManager(); // Track AI controllers for villagers (weak ref to avoid memory leaks) private final Map aiControllers = new WeakHashMap<>(); private MCABondageManager() { // Private constructor for singleton } /** * Get the singleton instance. */ public static MCABondageManager getInstance() { return INSTANCE; } // LIFECYCLE EVENTS /** Dialogue broadcast radius in blocks */ private static final double DIALOGUE_RADIUS = 16.0; /** * Called when MCA villager is tied up. * Initializes or updates AI control and syncs state. * * @param villager The MCA villager entity * @param bind The bind item being applied */ public void onVillagerTied(LivingEntity villager, ItemStack bind) { if (!MCACompat.isMCAVillager(villager)) return; MCAPersonality personality = MCAPersonalityManager.getInstance().getPersonality(villager); TiedUpMod.LOGGER.debug( "[MCA Manager] Villager tied: {} with {} (personality: {})", villager.getName().getString(), bind.getItem().getClass().getSimpleName(), personality.getMcaId() ); // Update AI level with personality MCABondageAIController controller = getOrCreateAIController(villager); controller.setPersonality(personality); controller.updateAILevel(); // Mood change MCAMoodManager.getInstance().onTied(villager); // Dialogue String dialogue = MCADialogueManager.getBeingTiedDialogue(villager); MCADialogueManager.broadcastDialogue( villager, dialogue, DIALOGUE_RADIUS ); // Sync to clients syncBondageState(villager); } /** * Called when MCA villager is untied. * Updates AI control and syncs state. * * @param villager The MCA villager entity */ public void onVillagerUntied(LivingEntity villager) { if (!MCACompat.isMCAVillager(villager)) return; TiedUpMod.LOGGER.debug( "[MCA Manager] Villager untied: {}", villager.getName().getString() ); // Update AI level (may restore normal behavior) MCABondageAIController controller = aiControllers.get(villager); if (controller != null) { controller.updateAILevel(); // If fully free, cleanup controller if (controller.getCurrentLevel() == MCABondageAILevel.NONE) { controller.cleanup(); aiControllers.remove(villager); } } // Sync to clients syncBondageState(villager); } /** * Called when MCA villager is captured (leashed to a captor). * * @param villager The MCA villager entity * @param captor The entity capturing the villager */ public void onVillagerCaptured(LivingEntity villager, Entity captor) { if (!MCACompat.isMCAVillager(villager)) return; TiedUpMod.LOGGER.debug( "[MCA Manager] Villager captured: {} by {}", villager.getName().getString(), captor.getName().getString() ); // Update AI to include follow behavior MCABondageAIController controller = getOrCreateAIController(villager); controller.updateAILevel(); // Sync to clients syncBondageState(villager); } /** * Called when MCA villager is freed from capture. * * @param villager The MCA villager entity */ public void onVillagerFreed(LivingEntity villager) { if (!MCACompat.isMCAVillager(villager)) return; TiedUpMod.LOGGER.debug( "[MCA Manager] Villager freed: {}", villager.getName().getString() ); // Update AI level MCABondageAIController controller = aiControllers.get(villager); if (controller != null) { controller.updateAILevel(); } // Mood change (happy to be freed, usually) MCAMoodManager.getInstance().onFreed(villager); // Dialogue String dialogue = MCADialogueManager.getFreedDialogue(villager); MCADialogueManager.broadcastDialogue( villager, dialogue, DIALOGUE_RADIUS ); // Sync to clients syncBondageState(villager); } /** * Called when collar is added/removed. * * @param villager The MCA villager entity * @param hasCollar Whether the villager now has a collar */ public void onCollarChanged(LivingEntity villager, boolean hasCollar) { if (!MCACompat.isMCAVillager(villager)) return; TiedUpMod.LOGGER.debug( "[MCA Manager] Collar changed for {}: {}", villager.getName().getString(), hasCollar ? "added" : "removed" ); // Update AI level MCABondageAIController controller = getOrCreateAIController(villager); controller.updateAILevel(); // Mood and dialogue if (hasCollar) { MCAMoodManager.getInstance().onCollared(villager); String dialogue = MCADialogueManager.getCollarPutOnDialogue( villager ); MCADialogueManager.broadcastDialogue( villager, dialogue, DIALOGUE_RADIUS ); } else { MCAMoodManager.getInstance().onCollarRemoved(villager); } // Sync to clients syncBondageState(villager); } /** * Called when gag state changes. * * @param villager The MCA villager entity * @param isGagged Whether the villager is now gagged */ public void onGagChanged(LivingEntity villager, boolean isGagged) { if (!MCACompat.isMCAVillager(villager)) return; // Update AI level (gagged+blindfolded = OVERRIDE) MCABondageAIController controller = aiControllers.get(villager); if (controller != null) { controller.updateAILevel(); } // Mood change if (isGagged) { MCAMoodManager.getInstance().onGagged(villager); } // Sync to clients syncBondageState(villager); } /** * Called when blindfold state changes. * * @param villager The MCA villager entity * @param isBlindfolded Whether the villager is now blindfolded */ public void onBlindfoldChanged( LivingEntity villager, boolean isBlindfolded ) { if (!MCACompat.isMCAVillager(villager)) return; // Update AI level (gagged+blindfolded = OVERRIDE) MCABondageAIController controller = aiControllers.get(villager); if (controller != null) { controller.updateAILevel(); } // Mood change if (isBlindfolded) { MCAMoodManager.getInstance().onBlindfolded(villager); } // Sync to clients syncBondageState(villager); } /** * Called when any sensory restriction changes (gag/blindfold). * Legacy method for compatibility. * * @param villager The MCA villager entity */ public void onSensoryRestrictionChanged(LivingEntity villager) { if (!MCACompat.isMCAVillager(villager)) return; // Update AI level (gagged+blindfolded = OVERRIDE) MCABondageAIController controller = aiControllers.get(villager); if (controller != null) { controller.updateAILevel(); } // Sync to clients syncBondageState(villager); } // AI CONTROL /** * Get or create AI controller for villager. * * @param villager The MCA villager entity * @return The AI controller (never null for valid MCA villagers) */ public MCABondageAIController getOrCreateAIController( LivingEntity villager ) { return aiControllers.computeIfAbsent( villager, MCABondageAIController::new ); } /** * Get AI controller for villager if it exists. * * @param villager The MCA villager entity * @return The AI controller, or null if none exists */ @Nullable public MCABondageAIController getAIController(LivingEntity villager) { return aiControllers.get(villager); } /** * Get current AI level for villager. * * @param villager The MCA villager entity * @return The AI level (NONE if no controller exists) */ public MCABondageAILevel getAILevel(LivingEntity villager) { MCABondageAIController controller = aiControllers.get(villager); return controller != null ? controller.getCurrentLevel() : MCABondageAILevel.NONE; } /** * Force a specific AI level (for debugging/commands). * * @param villager The MCA villager entity * @param level The AI level to set */ public void setAILevel(LivingEntity villager, MCABondageAILevel level) { MCABondageAIController controller = getOrCreateAIController(villager); controller.setLevel(level); } /** * Check if villager should have restricted AI. * * @param villager The MCA villager entity * @return true if AI is restricted in any way */ public boolean shouldRestrictAI(LivingEntity villager) { return getAILevel(villager) != MCABondageAILevel.NONE; } // SYNC /** * Sync all bondage state to tracking clients. * Delegates to MCANetworkHandler. * * @param villager The MCA villager entity */ public void syncBondageState(LivingEntity villager) { if (villager.level().isClientSide()) return; if (!MCACompat.isMCAVillager(villager)) return; villager .getCapability(MCACompat.MCA_KIDNAPPED) .ifPresent(cap -> { com.tiedup.remake.compat.mca.network.MCANetworkHandler.syncBondageState( villager, cap ); }); } /** * Sync bondage state to a specific player. * Used when player starts tracking the villager. * * @param villager The MCA villager entity * @param tracker The player to sync to */ public void syncBondageStateTo( LivingEntity villager, net.minecraft.server.level.ServerPlayer tracker ) { if (villager.level().isClientSide()) return; if (!MCACompat.isMCAVillager(villager)) return; villager .getCapability(MCACompat.MCA_KIDNAPPED) .ifPresent(cap -> { com.tiedup.remake.compat.mca.network.MCANetworkHandler.syncBondageStateTo( villager, cap, tracker ); }); } // QUERIES /** * Get IRestrainable adapter for MCA villager. * Convenience method that delegates to MCACompat. * * @param villager The MCA villager entity * @return IRestrainable adapter, or null if not an MCA villager */ @Nullable public IRestrainable getKidnappedState(LivingEntity villager) { return MCACompat.getKidnappedState(villager); } /** * Check if entity is a managed MCA villager. * * @param entity The entity to check * @return true if this entity is tracked by the manager */ public boolean isManaged(Entity entity) { if (!(entity instanceof LivingEntity living)) return false; return aiControllers.containsKey(living); } // CLEANUP /** * Remove all tracking for a villager. * Called when villager dies or is removed. * * @param villager The MCA villager entity */ public void removeVillager(LivingEntity villager) { MCABondageAIController controller = aiControllers.remove(villager); if (controller != null) { controller.cleanup(); } } /** * Clear all tracking data. * Called on world unload. */ public void clearAll() { for (MCABondageAIController controller : aiControllers.values()) { controller.cleanup(); } aiControllers.clear(); } }