Files
TiedUp-/src/main/java/com/tiedup/remake/mixin/MixinMCAVillagerInteraction.java
NotEvil 449178f57b feat(D-01/C): consumer migration — 85 files migrated to V2 helpers
Phase 1 (state): PlayerBindState, PlayerCaptorManager, PlayerEquipment,
  PlayerDataRetrieval, PlayerLifecycle, PlayerShockCollar, StruggleAccessory
Phase 2 (client): AnimationTickHandler, NpcAnimationTickHandler, 5 render
  handlers, DamselModel, 3 client mixins, SelfBondageInputHandler,
  SlaveManagementScreen, ActionPanel, SlaveEntryWidget, ModKeybindings
Phase 3 (entities): 28 entity/AI files migrated to CollarHelper,
  BindModeHelper, PoseTypeHelper, createStack()
Phase 4 (network): PacketSlaveAction, PacketMasterEquip,
  PacketAssignCellToCollar, PacketNpcCommand, PacketFurnitureForcemount
Phase 5 (events): RestraintTaskTickHandler, PetPlayRestrictionHandler,
  PlayerEnslavementHandler, ChatEventHandler, LaborAttackPunishmentHandler
Phase 6 (commands): BondageSubCommand, CollarCommand, NPCCommand,
  KidnapSetCommand
Phase 7 (compat): MCAKidnappedAdapter, MCA mixins
Phase 8 (misc): GagTalkManager, PetRequestManager, HangingCagePiece,
  BondageItemBlockEntity, TrappedChestBlockEntity, DispenserBehaviors,
  BondageItemLoaderUtility, RestraintApplicator, StruggleSessionManager,
  MovementStyleResolver, CampLifecycleManager

Some files retain dual V1/V2 checks (instanceof V1 || V2Helper) for
coexistence — V1-only branches removed in Branch D.
2026-04-15 00:16:50 +02:00

193 lines
6.5 KiB
Java

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.
*
* <p>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.
*
* <p>Priority rules:
* <ol>
* <li>Empty hand + leashed → detach leash</li>
* <li>Holding lead + (tied OR collar owner) → attach leash</li>
* <li>Holding key + collared → call key.interactLivingEntity()</li>
* <li>Shift+click + tied + empty hand → let event handler untie</li>
* <li>Holding bondage item → let item handle tying</li>
* <li>Otherwise → let MCA handle</li>
* </ol>
*
* <p>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<InteractionResult> 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<InteractionResult> 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;
}
}