package com.tiedup.remake.events.captivity; import com.tiedup.remake.core.SettingsAccessor; import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.entities.LeashProxyEntity; import com.tiedup.remake.state.IBondageState; import com.tiedup.remake.v2.bondage.CollarHelper; import com.tiedup.remake.state.IPlayerLeashAccess; import com.tiedup.remake.state.PlayerBindState; import com.tiedup.remake.util.KidnappedHelper; import com.tiedup.remake.v2.BodyRegionV2; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraftforge.event.TickEvent; import net.minecraftforge.event.entity.player.PlayerInteractEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; /** * Event handler for master-slave (enslavement) interactions. * *

Key Mechanisms

* * *

Leash System

* Uses a proxy-based leash system where a {@link LeashProxyEntity} follows the player * and holds the leash. The player does NOT mount anything - traction is applied via push(). * * @see PlayerBindState#free() * @see LeashProxyEntity * @see IPlayerLeashAccess */ @Mod.EventBusSubscriber(modid = TiedUpMod.MOD_ID) public class PlayerEnslavementHandler { /** * Prevent enslaved players from attacking/breaking their constraints. * Handles Left-Click (Attack) events. */ @SubscribeEvent public static void onPlayerAttackEntity( net.minecraftforge.event.entity.player.AttackEntityEvent event ) { Player player = event.getEntity(); Entity target = event.getTarget(); // Check if player is leashed (enslaved) via proxy system if ( player instanceof IPlayerLeashAccess access && access.tiedup$isLeashed() ) { // Prevent breaking the LeashKnot or the LeashProxy itself if ( target instanceof net.minecraft.world.entity.decoration.LeashFenceKnotEntity || target instanceof LeashProxyEntity ) { event.setCanceled(true); } } } /** * Handle player interactions with other players for enslavement/freeing. * * Uses IBondageState for condition checks (isEnslavable) * Note: Enslavement system (getEnslavedBy, free) remains PlayerBindState-specific */ @SubscribeEvent public static void onPlayerInteractEntity( PlayerInteractEvent.EntityInteract event ) { Player master = event.getEntity(); Entity target = event.getTarget(); // 1. Prevention Logic: Slaves cannot interact with leash knots or proxies if ( master instanceof IPlayerLeashAccess access && access.tiedup$isLeashed() ) { if ( target instanceof net.minecraft.world.entity.decoration.LeashFenceKnotEntity || target instanceof LeashProxyEntity ) { event.setCanceled(true); return; } } // Only handle player-to-player interactions for enslavement logic if (!(target instanceof Player)) { return; } Player slave = (Player) target; // Server-side only if (master.level().isClientSide) { return; } // Only handle MAIN_HAND to avoid double processing if (event.getHand() != net.minecraft.world.InteractionHand.MAIN_HAND) { return; } // Check if enslavement is enabled if ( !SettingsAccessor.isEnslavementEnabled( master.level().getGameRules() ) ) { return; } // Get IBondageState for condition checks IBondageState slaveKidnappedState = KidnappedHelper.getKidnappedState( slave ); if (slaveKidnappedState == null) { return; } // Get PlayerBindState for enslavement operations (Player-only system) PlayerBindState masterState = PlayerBindState.getInstance(master); PlayerBindState slaveState = PlayerBindState.getInstance(slave); if (masterState == null || slaveState == null) { return; } ItemStack heldItem = master.getItemInHand(event.getHand()); // Scenario 1: Enslavement with Lead if (heldItem.is(Items.LEAD)) { // Check if target is enslavable (using IBondageState) if (!slaveKidnappedState.isEnslavable()) { // Exception: collar owner can capture even if not tied boolean canCapture = false; if (slaveKidnappedState.hasCollar()) { ItemStack collar = slaveKidnappedState.getEquipment( BodyRegionV2.NECK ); if (CollarHelper.isCollar(collar)) { if ( CollarHelper .getOwners(collar) .contains(master.getUUID()) ) { canCapture = true; } } } if (!canCapture) { TiedUpMod.LOGGER.debug( "[PlayerEnslavementHandler] {} cannot be enslaved - not tied and not collar owner", slave.getName().getString() ); return; } } if (slaveState.isCaptive()) { TiedUpMod.LOGGER.debug( "[PlayerEnslavementHandler] {} is already captured", slave.getName().getString() ); return; } // Attempt capture boolean success = slaveState.getCapturedBy( masterState.getCaptorManager() ); if (success) { heldItem.shrink(1); TiedUpMod.LOGGER.info( "[PlayerEnslavementHandler] {} enslaved {} (lead consumed)", master.getName().getString(), slave.getName().getString() ); event.setCanceled(true); } else { TiedUpMod.LOGGER.warn( "[PlayerEnslavementHandler] Failed to enslave {} by {}", slave.getName().getString(), master.getName().getString() ); } } // Scenario 2: Freeing with Empty Hand else if (heldItem.isEmpty()) { if (!slaveState.isCaptive()) return; if (slaveState.getCaptor() != masterState.getCaptorManager()) { TiedUpMod.LOGGER.debug( "[PlayerEnslavementHandler] {} tried to free {} but is not the captor", master.getName().getString(), slave.getName().getString() ); return; } slaveState.free(); TiedUpMod.LOGGER.info( "[PlayerEnslavementHandler] {} freed {}", master.getName().getString(), slave.getName().getString() ); event.setCanceled(true); } } /** * Periodic check for enslaved players. * */ @SubscribeEvent public static void onPlayerTick(TickEvent.PlayerTickEvent event) { if (event.phase != TickEvent.Phase.END) return; Player player = event.player; if (player.level().isClientSide) return; if (player.tickCount % 20 != 0) return; PlayerBindState state = PlayerBindState.getInstance(player); if (state == null) return; if (state.isCaptive()) { state.checkStillCaptive(); } state.getCaptorManager().cleanupInvalidCaptives(); } }