1. NECK region explicitly blocked in interactLivingEntity() — prevents V2 collars equipping without ownership setup (latent, no JSONs yet) 2. Swap rollback safety: if re-equip fails after swap failure, drop the old bind as item entity instead of losing it silently 3. GaggingComponent: cache GagMaterial at construction time — eliminates valueOf() log spam on every chat message with misconfigured material 4. Dual-bind prevention: check both V1 isTiedUp() AND V2 region occupied in TyingInteractionHelper and PacketSelfBondage to prevent equipping V2 bind on top of V1 bind
124 lines
5.1 KiB
Java
124 lines
5.1 KiB
Java
package com.tiedup.remake.v2.bondage;
|
|
|
|
import com.tiedup.remake.core.SettingsAccessor;
|
|
import com.tiedup.remake.core.TiedUpMod;
|
|
import com.tiedup.remake.state.IBondageState;
|
|
import com.tiedup.remake.state.PlayerBindState;
|
|
import com.tiedup.remake.tasks.TyingTask;
|
|
import com.tiedup.remake.tasks.V2TyingPlayerTask;
|
|
import com.tiedup.remake.util.KidnappedHelper;
|
|
import com.tiedup.remake.v2.BodyRegionV2;
|
|
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
|
|
import com.tiedup.remake.v2.bondage.V2EquipResult;
|
|
import net.minecraft.server.level.ServerPlayer;
|
|
import net.minecraft.world.InteractionHand;
|
|
import net.minecraft.world.InteractionResult;
|
|
import net.minecraft.world.entity.LivingEntity;
|
|
import net.minecraft.world.entity.player.Player;
|
|
import net.minecraft.world.item.ItemStack;
|
|
|
|
/**
|
|
* Handles the tying interaction flow for V2 data-driven ARMS items.
|
|
*
|
|
* <p>Extracted from {@code ItemBind.interactLivingEntity()} to support
|
|
* the same tying task flow for data-driven items.</p>
|
|
*/
|
|
public final class TyingInteractionHelper {
|
|
|
|
private TyingInteractionHelper() {}
|
|
|
|
/**
|
|
* Handle right-click tying of a target entity with a V2 ARMS item.
|
|
* Creates/continues a V2TyingPlayerTask.
|
|
*/
|
|
public static InteractionResult handleTying(
|
|
ServerPlayer player,
|
|
LivingEntity target,
|
|
ItemStack stack,
|
|
InteractionHand hand
|
|
) {
|
|
IBondageState targetState = KidnappedHelper.getKidnappedState(target);
|
|
if (targetState == null) return InteractionResult.PASS;
|
|
|
|
// Kidnapper can't be tied themselves
|
|
IBondageState kidnapperState = KidnappedHelper.getKidnappedState(player);
|
|
if (kidnapperState != null && kidnapperState.isTiedUp()) {
|
|
return InteractionResult.PASS;
|
|
}
|
|
|
|
// Already tied — try to swap (check both V1 state and V2 region to prevent dual-bind)
|
|
if (targetState.isTiedUp() || V2EquipmentHelper.isRegionOccupied(target, BodyRegionV2.ARMS)) {
|
|
if (stack.isEmpty()) return InteractionResult.PASS;
|
|
ItemStack oldBind = V2EquipmentHelper.unequipFromRegion(target, BodyRegionV2.ARMS);
|
|
if (!oldBind.isEmpty()) {
|
|
V2EquipResult result = V2EquipmentHelper.equipItem(target, stack.copy());
|
|
if (result.isSuccess()) {
|
|
stack.shrink(1);
|
|
target.spawnAtLocation(oldBind);
|
|
TiedUpMod.LOGGER.debug("[TyingInteraction] Swapped bind on {}", target.getName().getString());
|
|
return InteractionResult.SUCCESS;
|
|
} else {
|
|
// Equip failed — rollback: re-equip old bind
|
|
V2EquipResult rollback = V2EquipmentHelper.equipItem(target, oldBind);
|
|
if (!rollback.isSuccess()) {
|
|
// Rollback also failed — drop old bind as safety net
|
|
target.spawnAtLocation(oldBind);
|
|
TiedUpMod.LOGGER.warn("[TyingInteraction] Swap AND rollback failed, dropped old bind for {}", target.getName().getString());
|
|
} else {
|
|
TiedUpMod.LOGGER.debug("[TyingInteraction] Swap failed, rolled back old bind on {}", target.getName().getString());
|
|
}
|
|
return InteractionResult.PASS;
|
|
}
|
|
}
|
|
return InteractionResult.PASS;
|
|
}
|
|
|
|
// Distance + line-of-sight (skip for self-tying)
|
|
boolean isSelfTying = player.equals(target);
|
|
if (!isSelfTying) {
|
|
if (player.distanceTo(target) > 4.0 || !player.hasLineOfSight(target)) {
|
|
return InteractionResult.PASS;
|
|
}
|
|
}
|
|
|
|
// Create/continue tying task
|
|
PlayerBindState playerState = PlayerBindState.getInstance(player);
|
|
if (playerState == null) return InteractionResult.PASS;
|
|
|
|
int tyingSeconds = SettingsAccessor.getTyingPlayerTime(player.level().getGameRules());
|
|
|
|
V2TyingPlayerTask newTask = new V2TyingPlayerTask(
|
|
stack.copy(),
|
|
stack,
|
|
targetState,
|
|
target,
|
|
tyingSeconds,
|
|
player.level(),
|
|
player
|
|
);
|
|
|
|
TyingTask currentTask = playerState.getCurrentTyingTask();
|
|
if (currentTask == null
|
|
|| !(currentTask instanceof V2TyingPlayerTask)
|
|
|| !currentTask.isSameTarget(target)
|
|
|| currentTask.isOutdated()
|
|
|| !ItemStack.matches(currentTask.getBind(), stack)) {
|
|
// Start new task (also handles case where existing task is V1 TyingPlayerTask)
|
|
playerState.setCurrentTyingTask(newTask);
|
|
newTask.start();
|
|
} else {
|
|
newTask = (V2TyingPlayerTask) currentTask;
|
|
}
|
|
|
|
newTask.update();
|
|
|
|
if (newTask.isStopped()) {
|
|
// Item already consumed by V2TyingPlayerTask.onComplete() — don't shrink again
|
|
playerState.setCurrentTyingTask(null);
|
|
TiedUpMod.LOGGER.info("[TyingInteraction] {} tied {}", player.getName().getString(), target.getName().getString());
|
|
}
|
|
|
|
return InteractionResult.SUCCESS;
|
|
}
|
|
}
|