package com.tiedup.remake.mixin; import com.tiedup.remake.compat.mca.MCACompat; import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.items.ItemKey; import com.tiedup.remake.items.ItemMasterKey; import com.tiedup.remake.v2.bondage.CollarHelper; import com.tiedup.remake.state.IBondageState; import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.bondage.IV2BondageItem; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Pseudo; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; /** * Mixin to manage TiedUp vs MCA interaction priority. * *

Handles TiedUp interactions DIRECTLY instead of just blocking MCA, * because vanilla leash/item handling happens at different points in the * interaction chain that MCA may bypass. * *

Priority rules: *

    *
  1. Empty hand + leashed → detach leash
  2. *
  3. Holding lead + (tied OR collar owner) → attach leash
  4. *
  5. Holding key + collared → call key.interactLivingEntity()
  6. *
  7. Shift+click + tied + empty hand → let event handler untie
  8. *
  9. Holding bondage item → let item handle tying
  10. *
  11. Otherwise → let MCA handle
  12. *
* *

Uses @Pseudo - mixin is optional and will be skipped if MCA is not installed. */ @Pseudo @Mixin(targets = "forge.net.mca.entity.VillagerEntityMCA", remap = false) public abstract class MixinMCAVillagerInteraction { /** * Inject at HEAD of mobInteract to handle TiedUp interactions. * Method: InteractionResult mobInteract(Player, InteractionHand) */ @Inject( method = "m_6071_", at = @At("HEAD"), cancellable = true, remap = false ) private void tiedup$handleMobInteract( Player player, InteractionHand hand, CallbackInfoReturnable cir ) { InteractionResult result = tiedup$handleInteraction(player, hand); if (result != null) { cir.setReturnValue(result); } } /** * Inject at HEAD of interactAt to handle TiedUp interactions BEFORE MCA opens its menu. * Method: InteractionResult interactAt(Player, Vec3, InteractionHand) */ @Inject( method = "m_7111_", at = @At("HEAD"), cancellable = true, remap = false ) private void tiedup$handleInteractAt( Player player, net.minecraft.world.phys.Vec3 vec, InteractionHand hand, CallbackInfoReturnable cir ) { InteractionResult result = tiedup$handleInteraction(player, hand); if (result != null) { cir.setReturnValue(result); } } /** * Common interaction handling logic for both mobInteract and interactAt. * * @param player The player interacting * @param hand The hand used for interaction * @return InteractionResult to return, or null to let MCA handle */ @Unique private InteractionResult tiedup$handleInteraction( Player player, InteractionHand hand ) { Mob mob = (Mob) (Object) this; IBondageState state = MCACompat.getKidnappedState(mob); ItemStack heldItem = player.getItemInHand(hand); boolean isClientSide = player.level().isClientSide; // 1. LEASH DETACHMENT: Empty hand + leashed → drop leash if (mob.isLeashed() && heldItem.isEmpty()) { if (!isClientSide) { mob.dropLeash(true, !player.getAbilities().instabuild); TiedUpMod.LOGGER.debug("[MCA] Detached leash from villager"); } return InteractionResult.sidedSuccess(isClientSide); } // 2. LEASH ATTACHMENT: Holding lead + (tied OR collar owner) if (heldItem.is(Items.LEAD) && !mob.isLeashed()) { if (tiedup$canLeash(player, state)) { if (!isClientSide) { mob.setLeashedTo(player, true); if (!player.getAbilities().instabuild) { heldItem.shrink(1); } TiedUpMod.LOGGER.debug("[MCA] Attached leash to villager"); } return InteractionResult.sidedSuccess(isClientSide); } } // 3. KEY INTERACTION: Key + collared → call key's interactLivingEntity if (state != null && state.hasCollar()) { if (heldItem.getItem() instanceof ItemKey key) { return key.interactLivingEntity(heldItem, player, mob, hand); } if (heldItem.getItem() instanceof ItemMasterKey masterKey) { return masterKey.interactLivingEntity( heldItem, player, mob, hand ); } } // 4. UNTYING: Shift+click + tied + empty hand → let event handler process if ( state != null && state.isTiedUp() && heldItem.isEmpty() && player.isShiftKeyDown() ) { return InteractionResult.PASS; } // 5. BONDAGE ITEM: Let TiedUp item's interactLivingEntity handle tying if (heldItem.getItem() instanceof IV2BondageItem) { return InteractionResult.PASS; } // Let MCA handle return null; } /** * Check if a player can leash this MCA villager. * * @param player The player trying to leash * @param state The villager's IBondageState state (may be null) * @return true if leashing is allowed */ @Unique private boolean tiedup$canLeash(Player player, IBondageState state) { if (state == null) { return false; } // Can leash if villager is tied up if (state.isTiedUp()) { return true; } // Can leash if player is a collar owner if (state.hasCollar()) { ItemStack collar = state.getEquipment(BodyRegionV2.NECK); if (CollarHelper.isCollar(collar)) { return CollarHelper.isOwner(collar, player); } } return false; } }