feat(D-01/A): interaction routing + TyingInteractionHelper (A8)
- DataDrivenBondageItem.use(): shift+click cycles bind mode for ARMS items - DataDrivenBondageItem.interactLivingEntity(): region-based routing - ARMS → TyingInteractionHelper (tying task with progress bar) - NECK → deferred to Branch C (no V2 collar JSONs yet) - Other regions → instant equip via parent AbstractV2BondageItem - TyingInteractionHelper: extracted tying flow using V2TyingPlayerTask - Distance/LoS validation, swap if already tied, task lifecycle
This commit is contained in:
@@ -0,0 +1,107 @@
|
|||||||
|
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 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
|
||||||
|
if (targetState.isTiedUp()) {
|
||||||
|
if (stack.isEmpty()) return InteractionResult.PASS;
|
||||||
|
ItemStack oldBind = V2EquipmentHelper.unequipFromRegion(target, BodyRegionV2.ARMS);
|
||||||
|
if (!oldBind.isEmpty()) {
|
||||||
|
V2EquipmentHelper.equipItem(target, stack.copy());
|
||||||
|
stack.shrink(1);
|
||||||
|
target.spawnAtLocation(oldBind);
|
||||||
|
TiedUpMod.LOGGER.debug("[TyingInteraction] Swapped bind on {}", target.getName().getString());
|
||||||
|
return InteractionResult.SUCCESS;
|
||||||
|
}
|
||||||
|
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.isSameTarget(target)
|
||||||
|
|| currentTask.isOutdated()
|
||||||
|
|| !ItemStack.matches(currentTask.getBind(), stack)) {
|
||||||
|
playerState.setCurrentTyingTask(newTask);
|
||||||
|
newTask.start();
|
||||||
|
} else {
|
||||||
|
newTask = (V2TyingPlayerTask) currentTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
newTask.update();
|
||||||
|
|
||||||
|
if (newTask.isStopped()) {
|
||||||
|
stack.shrink(1);
|
||||||
|
playerState.setCurrentTyingTask(null);
|
||||||
|
TiedUpMod.LOGGER.info("[TyingInteraction] {} tied {}", player.getName().getString(), target.getName().getString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return InteractionResult.SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
package com.tiedup.remake.v2.bondage.datadriven;
|
package com.tiedup.remake.v2.bondage.datadriven;
|
||||||
|
|
||||||
|
import com.tiedup.remake.core.TiedUpMod;
|
||||||
import com.tiedup.remake.v2.BodyRegionV2;
|
import com.tiedup.remake.v2.BodyRegionV2;
|
||||||
|
import com.tiedup.remake.v2.bondage.BindModeHelper;
|
||||||
import com.tiedup.remake.v2.bondage.IV2BondageEquipment;
|
import com.tiedup.remake.v2.bondage.IV2BondageEquipment;
|
||||||
|
import com.tiedup.remake.v2.bondage.TyingInteractionHelper;
|
||||||
import com.tiedup.remake.v2.bondage.V2BondageItems;
|
import com.tiedup.remake.v2.bondage.V2BondageItems;
|
||||||
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
|
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
|
||||||
import com.tiedup.remake.v2.bondage.component.ComponentHolder;
|
import com.tiedup.remake.v2.bondage.component.ComponentHolder;
|
||||||
@@ -17,7 +20,13 @@ import java.util.stream.Collectors;
|
|||||||
import net.minecraft.ChatFormatting;
|
import net.minecraft.ChatFormatting;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
|
import net.minecraft.sounds.SoundEvents;
|
||||||
|
import net.minecraft.world.InteractionHand;
|
||||||
|
import net.minecraft.world.InteractionResult;
|
||||||
|
import net.minecraft.world.InteractionResultHolder;
|
||||||
import net.minecraft.world.entity.LivingEntity;
|
import net.minecraft.world.entity.LivingEntity;
|
||||||
|
import net.minecraft.world.entity.player.Player;
|
||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
import net.minecraft.world.item.TooltipFlag;
|
import net.minecraft.world.item.TooltipFlag;
|
||||||
import net.minecraft.world.level.Level;
|
import net.minecraft.world.level.Level;
|
||||||
@@ -116,6 +125,55 @@ public class DataDrivenBondageItem extends AbstractV2BondageItem {
|
|||||||
return def != null && def.supportsColor();
|
return def != null && def.supportsColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===== INTERACTION ROUTING =====
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InteractionResultHolder<ItemStack> use(Level level, Player player, InteractionHand hand) {
|
||||||
|
ItemStack stack = player.getItemInHand(hand);
|
||||||
|
Set<BodyRegionV2> regions = getOccupiedRegions(stack);
|
||||||
|
|
||||||
|
// ARMS items: shift+click cycles bind mode
|
||||||
|
if (regions.contains(BodyRegionV2.ARMS) && player.isShiftKeyDown() && !level.isClientSide) {
|
||||||
|
BindModeHelper.cycleBindModeId(stack);
|
||||||
|
player.playSound(SoundEvents.CHAIN_STEP, 0.5f, 1.2f);
|
||||||
|
player.displayClientMessage(
|
||||||
|
Component.translatable(
|
||||||
|
"tiedup.message.bindmode_changed",
|
||||||
|
Component.translatable(BindModeHelper.getBindModeTranslationKey(stack))
|
||||||
|
),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
return InteractionResultHolder.success(stack);
|
||||||
|
}
|
||||||
|
return super.use(level, player, hand);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InteractionResult interactLivingEntity(
|
||||||
|
ItemStack stack, Player player, LivingEntity target, InteractionHand hand
|
||||||
|
) {
|
||||||
|
// Client: arm swing
|
||||||
|
if (player.level().isClientSide) {
|
||||||
|
return InteractionResult.SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
DataDrivenItemDefinition def = DataDrivenItemRegistry.get(stack);
|
||||||
|
if (def == null) return InteractionResult.PASS;
|
||||||
|
|
||||||
|
Set<BodyRegionV2> regions = def.occupiedRegions();
|
||||||
|
|
||||||
|
// ARMS: tying flow (do NOT call super — avoids double equip)
|
||||||
|
if (regions.contains(BodyRegionV2.ARMS) && player instanceof ServerPlayer serverPlayer) {
|
||||||
|
return TyingInteractionHelper.handleTying(serverPlayer, target, stack, hand);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NECK: collar equip blocked for now — V2 collar JSONs don't exist in Branch A.
|
||||||
|
// Full collar equip flow (add owner, register, sound) wired in Branch C.
|
||||||
|
|
||||||
|
// All other regions (MOUTH, EYES, EARS, HANDS): instant equip via parent
|
||||||
|
return super.interactLivingEntity(stack, player, target, hand);
|
||||||
|
}
|
||||||
|
|
||||||
// ===== IHasResistance IMPLEMENTATION =====
|
// ===== IHasResistance IMPLEMENTATION =====
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
Reference in New Issue
Block a user