package com.tiedup.remake.client.events; import com.tiedup.remake.network.ModNetwork; import com.tiedup.remake.network.selfbondage.PacketSelfBondage; import com.tiedup.remake.v2.bondage.BindModeHelper; import com.tiedup.remake.v2.bondage.CollarHelper; import com.tiedup.remake.v2.bondage.IV2BondageItem; import com.tiedup.remake.v2.bondage.component.BlindingComponent; import com.tiedup.remake.v2.bondage.component.ComponentType; import com.tiedup.remake.v2.bondage.component.GaggingComponent; import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem; import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemDefinition; import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemRegistry; import net.minecraft.client.Minecraft; import net.minecraft.client.player.LocalPlayer; import net.minecraft.world.InteractionHand; import net.minecraft.world.item.ItemStack; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.event.TickEvent; import net.minecraftforge.event.entity.player.PlayerInteractEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; /** * Client-side event handler for self-bondage input. * * Intercepts left-click when holding bondage items and * sends packets continuously to the server to perform self-bondage. * * Self-bondage items: * - Binds (rope, chain, etc.) - Self-tie (requires holding left-click) * - Gags - Self-gag (if already tied, instant) * - Blindfolds - Self-blindfold (if already tied, instant) * - Mittens - Self-mitten (if already tied, instant) * - Earplugs - Self-earplug (if already tied, instant) * - Collar - NOT ALLOWED (cannot self-collar) */ @Mod.EventBusSubscriber( modid = "tiedup", value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.FORGE ) @OnlyIn(Dist.CLIENT) public class SelfBondageInputHandler { /** Track if we're currently in self-bondage mode */ private static boolean isSelfBondageActive = false; /** The hand we're using for self-bondage */ private static InteractionHand activeHand = null; /** Tick counter for packet sending interval */ private static int tickCounter = 0; /** Send packet every 4 ticks (5 times per second) for smooth progress */ private static final int PACKET_INTERVAL = 4; /** * Handle left-click in empty air - START self-bondage. */ @SubscribeEvent public static void onLeftClickEmpty( PlayerInteractEvent.LeftClickEmpty event ) { startSelfBondage(); } /** * Handle left-click on block - START self-bondage (cancel block breaking). */ @SubscribeEvent public static void onLeftClickBlock( PlayerInteractEvent.LeftClickBlock event ) { if (!event.getLevel().isClientSide()) return; ItemStack stack = event.getItemStack(); if (isSelfBondageItem(stack)) { event.setCanceled(true); startSelfBondage(); } } /** * Start self-bondage mode if holding a bondage item. */ private static void startSelfBondage() { LocalPlayer player = Minecraft.getInstance().player; if (player == null) return; // Check main hand first, then off hand InteractionHand hand = InteractionHand.MAIN_HAND; ItemStack stack = player.getMainHandItem(); if (!isSelfBondageItem(stack)) { stack = player.getOffhandItem(); hand = InteractionHand.OFF_HAND; if (!isSelfBondageItem(stack)) { return; // No bondage item in either hand } } // Start self-bondage mode isSelfBondageActive = true; activeHand = hand; tickCounter = 0; // Send initial packet immediately ModNetwork.sendToServer(new PacketSelfBondage(hand)); } /** * Client tick - continuously send packets while attack button is held. */ @SubscribeEvent public static void onClientTick(TickEvent.ClientTickEvent event) { if (event.phase != TickEvent.Phase.END) return; if (!isSelfBondageActive) return; Minecraft mc = Minecraft.getInstance(); LocalPlayer player = mc.player; // Stop if conditions are no longer valid if (player == null || mc.screen != null) { stopSelfBondage(); return; } // Check if attack button is still held if (!mc.options.keyAttack.isDown()) { stopSelfBondage(); return; } // Check if still holding bondage item in the active hand ItemStack stack = player.getItemInHand(activeHand); if (!isSelfBondageItem(stack)) { stopSelfBondage(); return; } // Send packet at interval for continuous progress tickCounter++; if (tickCounter >= PACKET_INTERVAL) { tickCounter = 0; ModNetwork.sendToServer(new PacketSelfBondage(activeHand)); } } /** * Stop self-bondage mode. */ private static void stopSelfBondage() { isSelfBondageActive = false; activeHand = null; tickCounter = 0; } /** * Check if a stack supports self-bondage. * Collar is explicitly excluded. */ private static boolean isSelfBondageItem(ItemStack stack) { if (stack.isEmpty()) return false; // Collar cannot be self-equipped (V2 ownership component) if (CollarHelper.isCollar(stack)) { return false; } // V2 bondage items support self-bondage (left-click hold with tying duration) if (stack.getItem() instanceof IV2BondageItem) { return true; } // V2 data-driven items: check if it occupies any bondage region DataDrivenItemDefinition def = DataDrivenItemRegistry.get(stack); if (def != null) { return true; } // V1 fallback: bind items return BindModeHelper.isBindItem(stack) || DataDrivenBondageItem.getComponent(stack, ComponentType.GAGGING, GaggingComponent.class) != null || DataDrivenBondageItem.getComponent(stack, ComponentType.BLINDING, BlindingComponent.class) != null; } }