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.
193 lines
6.5 KiB
Java
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;
|
|
}
|
|
}
|