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:
NotEvil
2026-04-14 15:35:31 +02:00
parent b79225d684
commit 737a4fd59b
2 changed files with 165 additions and 0 deletions

View File

@@ -1,7 +1,10 @@
package com.tiedup.remake.v2.bondage.datadriven;
import com.tiedup.remake.core.TiedUpMod;
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.TyingInteractionHelper;
import com.tiedup.remake.v2.bondage.V2BondageItems;
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
import com.tiedup.remake.v2.bondage.component.ComponentHolder;
@@ -17,7 +20,13 @@ import java.util.stream.Collectors;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
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.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.level.Level;
@@ -116,6 +125,55 @@ public class DataDrivenBondageItem extends AbstractV2BondageItem {
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 =====
@Override