package com.tiedup.remake.dialogue.conversation; import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.dialogue.DialogueBridge; import com.tiedup.remake.entities.EntityMaster; import com.tiedup.remake.entities.ai.master.MasterPlaceBlockGoal; import com.tiedup.remake.entities.ai.master.MasterState; import com.tiedup.remake.network.ModNetwork; import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem; import net.minecraft.resources.ResourceLocation; import com.tiedup.remake.network.master.PacketOpenPetRequestMenu; import com.tiedup.remake.state.PlayerBindState; import com.tiedup.remake.v2.BodyRegionV2; import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.item.ItemStack; /** * Manager for handling pet requests to their Master NPC. * * This is the server-side handler for player-initiated requests in pet play mode. * Utilizes existing systems: * - PlayerEquipment.equip(BodyRegionV2.ARMS, bind) / takeBindOff() for tie/untie * - MasterState.DOGWALK for walk mode * - MasterPlaceBlockGoal for feeding/resting * - Leash physics via mixin for pulling the player */ public class PetRequestManager { /** Maximum distance for pet request interaction */ private static final double MAX_DISTANCE = 6.0; /** * Open the pet request menu for a pet player. * Sends a packet to the client to display the GUI. * * @param master The master entity * @param pet The pet player */ public static void openRequestMenu(EntityMaster master, ServerPlayer pet) { if (!master.isPetPlayer(pet)) { TiedUpMod.LOGGER.warn( "[PetRequestManager] {} is not a pet of {}", pet.getName().getString(), master.getNpcName() ); return; } // Check distance double dist = master.distanceTo(pet); if (dist > MAX_DISTANCE) { pet.sendSystemMessage( Component.translatable("entity.tiedup.pet.too_far_to_talk") ); return; } // Send packet to open GUI on client ModNetwork.sendToPlayer( new PacketOpenPetRequestMenu(master.getId(), master.getNpcName()), pet ); TiedUpMod.LOGGER.debug( "[PetRequestManager] Opened request menu for {} with {}", pet.getName().getString(), master.getNpcName() ); } /** * Handle a request from the pet player. * Called when the player selects an option from the pet request menu. * * @param master The master entity * @param pet The pet player making the request * @param request The request type */ public static void handleRequest( EntityMaster master, ServerPlayer pet, PetRequest request ) { if (!master.isPetPlayer(pet)) { TiedUpMod.LOGGER.warn( "[PetRequestManager] Rejected request from non-pet {} to {}", pet.getName().getString(), master.getNpcName() ); return; } // Check distance double dist = master.distanceTo(pet); if (dist > MAX_DISTANCE) { pet.sendSystemMessage( Component.translatable("entity.tiedup.pet.too_far_from_master") ); return; } TiedUpMod.LOGGER.info( "[PetRequestManager] {} requested {} from {}", pet.getName().getString(), request.name(), master.getNpcName() ); // Display what the player "says" pet.sendSystemMessage( Component.translatable("entity.tiedup.pet.you_say", request.getPlayerText()) ); // Handle specific request switch (request) { case REQUEST_FOOD -> triggerFeeding(master, pet); case REQUEST_SLEEP -> triggerResting(master, pet); case REQUEST_WALK_PASSIVE -> triggerDogwalk(master, pet, false); case REQUEST_WALK_ACTIVE -> triggerDogwalk(master, pet, true); case REQUEST_TIE -> triggerTie(master, pet); case REQUEST_UNTIE -> triggerUntie(master, pet); case END_CONVERSATION -> endConversation(master, pet); } } /** * Trigger feeding action - Master places bowl for pet. */ private static void triggerFeeding(EntityMaster master, ServerPlayer pet) { DialogueBridge.talkTo(master, pet, "petplay.feeding"); MasterPlaceBlockGoal goal = master.getPlaceBlockGoal(); if (goal != null) { goal.triggerFeeding(); } } /** * Trigger resting action - Master places pet bed for pet. */ private static void triggerResting(EntityMaster master, ServerPlayer pet) { DialogueBridge.talkTo(master, pet, "petplay.resting"); MasterPlaceBlockGoal goal = master.getPlaceBlockGoal(); if (goal != null) { goal.triggerResting(); } } /** * Trigger dogwalk mode. * Puts dogbind on player and attaches leash. * * @param masterLeads If true, Master walks and pulls pet. If false, Master follows pet. */ private static void triggerDogwalk( EntityMaster master, ServerPlayer pet, boolean masterLeads ) { // Get player bind state PlayerBindState state = PlayerBindState.getInstance(pet); if (state == null) { TiedUpMod.LOGGER.warn( "[PetRequestManager] Could not get PlayerBindState for {}", pet.getName().getString() ); return; } // Put dogbind on player (if not already tied) if (!state.isTiedUp()) { ItemStack dogbind = DataDrivenBondageItem.createStack(ResourceLocation.fromNamespaceAndPath("tiedup", "dogbinder")); state.equip(BodyRegionV2.ARMS, dogbind); TiedUpMod.LOGGER.debug( "[PetRequestManager] Equipped dogbind on {} for walk", pet.getName().getString() ); } // Attach leash master.attachLeashToPet(); // Set dogwalk mode master.setDogwalkMode(masterLeads); master.setMasterState(MasterState.DOGWALK); String dialogueId = masterLeads ? "petplay.walk_active" : "petplay.walk_passive"; DialogueBridge.talkTo(master, pet, dialogueId); TiedUpMod.LOGGER.info( "[PetRequestManager] {} entered DOGWALK mode with {} (masterLeads={})", master.getNpcName(), pet.getName().getString(), masterLeads ); } /** * Trigger tie request - Master ties up the pet. */ private static void triggerTie(EntityMaster master, ServerPlayer pet) { // Don't allow tie requests during dogwalk if (master.getStateManager().getCurrentState() == MasterState.DOGWALK) { DialogueBridge.talkTo(master, pet, "petplay.busy"); return; } // Get player bind state PlayerBindState state = PlayerBindState.getInstance(pet); if (state == null) { TiedUpMod.LOGGER.warn( "[PetRequestManager] Could not get PlayerBindState for {}", pet.getName().getString() ); return; } // Check if already tied if (state.isTiedUp()) { DialogueBridge.talkTo(master, pet, "petplay.already_tied"); return; } // Master equips armbinder on pet (classic pet play restraint) ItemStack bind = DataDrivenBondageItem.createStack(ResourceLocation.fromNamespaceAndPath("tiedup", "armbinder")); state.equip(BodyRegionV2.ARMS, bind); DialogueBridge.talkTo(master, pet, "petplay.tie_accept"); TiedUpMod.LOGGER.info( "[PetRequestManager] {} tied up {} with armbinder", master.getNpcName(), pet.getName().getString() ); } /** * Trigger untie request - Master unties the pet. */ private static void triggerUntie(EntityMaster master, ServerPlayer pet) { // Don't allow untie requests during dogwalk if (master.getStateManager().getCurrentState() == MasterState.DOGWALK) { DialogueBridge.talkTo(master, pet, "petplay.busy"); return; } // Get player bind state PlayerBindState state = PlayerBindState.getInstance(pet); if (state == null) { TiedUpMod.LOGGER.warn( "[PetRequestManager] Could not get PlayerBindState for {}", pet.getName().getString() ); return; } // Check if actually tied if (!state.isTiedUp()) { DialogueBridge.talkTo(master, pet, "petplay.not_tied"); return; } // Master removes bind from pet state.unequip(BodyRegionV2.ARMS); DialogueBridge.talkTo(master, pet, "petplay.untie_accept"); TiedUpMod.LOGGER.info( "[PetRequestManager] {} untied {}", master.getNpcName(), pet.getName().getString() ); } /** * End the conversation gracefully. */ private static void endConversation(EntityMaster master, ServerPlayer pet) { DialogueBridge.talkTo(master, pet, "petplay.dismiss"); } }