feat(D-01/A): self-bondage region routing (A11)

- handleV2SelfBondage: split into region-based routing
  - NECK → blocked (cannot self-collar)
  - ARMS → handleV2SelfBind (tying task with progress bar)
  - Other → handleV2SelfAccessory (instant equip)
- handleV2SelfAccessory: arms-bound check via BindModeHelper,
  locked check for swap, V2EquipmentHelper for conflict resolution
This commit is contained in:
Adrien
2026-04-14 16:06:01 +02:00
parent c240f48b29
commit 21c1fc3b57

View File

@@ -14,8 +14,10 @@ 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.BindModeHelper;
import com.tiedup.remake.v2.bondage.IV2BondageItem;
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
import com.tiedup.remake.v2.bondage.V2EquipResult;
import java.util.function.Supplier;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.server.level.ServerPlayer;
@@ -222,71 +224,112 @@ public class PacketSelfBondage {
IV2BondageItem v2Item,
IBondageState state
) {
java.util.Set<BodyRegionV2> regions = v2Item.getOccupiedRegions(stack);
// Cannot self-collar
if (regions.contains(BodyRegionV2.NECK)) {
TiedUpMod.LOGGER.debug("[SelfBondage] {} tried to self-collar — blocked", player.getName().getString());
return;
}
// ARMS: tying task with progress bar
if (regions.contains(BodyRegionV2.ARMS)) {
handleV2SelfBind(player, stack, v2Item, state);
return;
}
// Accessories (MOUTH, EYES, EARS, HANDS): instant equip
handleV2SelfAccessory(player, stack, v2Item, state);
}
private static void handleV2SelfBind(
ServerPlayer player,
ItemStack stack,
IV2BondageItem v2Item,
IBondageState state
) {
// Can't self-tie if already tied
if (state.isTiedUp()) {
TiedUpMod.LOGGER.debug("[SelfBondage] {} tried V2 self-tie but already tied", player.getName().getString());
return;
}
// Check if all target regions are already occupied or blocked
boolean allBlocked = true;
for (BodyRegionV2 region : v2Item.getOccupiedRegions(stack)) {
if (
!V2EquipmentHelper.isRegionOccupied(player, region) &&
!V2EquipmentHelper.isRegionBlocked(player, region)
) {
if (!V2EquipmentHelper.isRegionOccupied(player, region)
&& !V2EquipmentHelper.isRegionBlocked(player, region)) {
allBlocked = false;
break;
}
}
if (allBlocked) {
TiedUpMod.LOGGER.debug(
"[SelfBondage] {} tried V2 self-equip but all regions occupied",
player.getName().getString()
);
TiedUpMod.LOGGER.debug("[SelfBondage] {} tried V2 self-equip but all regions occupied", player.getName().getString());
return;
}
PlayerBindState playerState = PlayerBindState.getInstance(player);
if (playerState == null) return;
int tyingSeconds = SettingsAccessor.getTyingPlayerTime(
player.level().getGameRules()
);
int tyingSeconds = SettingsAccessor.getTyingPlayerTime(player.level().getGameRules());
// Create V2 tying task (uses V2EquipmentHelper on completion, NOT putBindOn)
V2TyingPlayerTask newTask = new V2TyingPlayerTask(
stack.copy(), // copy for display/matching
stack, // live reference for consumption
state,
player, // target is self
tyingSeconds,
player.level(),
player // kidnapper is also self
stack.copy(), stack, state, player, tyingSeconds, player.level(), player
);
TyingTask currentTask = playerState.getCurrentTyingTask();
if (
currentTask == null ||
!currentTask.isSameTarget(player) ||
currentTask.isOutdated() ||
!ItemStack.matches(currentTask.getBind(), stack)
) {
// Start new task
if (currentTask == null
|| !currentTask.isSameTarget(player)
|| currentTask.isOutdated()
|| !ItemStack.matches(currentTask.getBind(), stack)) {
playerState.setCurrentTyingTask(newTask);
newTask.start();
TiedUpMod.LOGGER.debug(
"[SelfBondage] {} started V2 self-tying ({} seconds)",
player.getName().getString(),
tyingSeconds
);
} else {
// Continue existing task — just mark active
currentTask.update();
}
// If we started a new task, mark it active too
if (playerState.getCurrentTyingTask() == newTask) {
newTask.update();
}
}
private static void handleV2SelfAccessory(
ServerPlayer player,
ItemStack stack,
IV2BondageItem v2Item,
IBondageState state
) {
// Can't equip accessories if arms are fully bound
ItemStack currentBind = V2EquipmentHelper.getInRegion(player, BodyRegionV2.ARMS);
if (!currentBind.isEmpty() && BindModeHelper.hasArmsBound(currentBind)) {
TiedUpMod.LOGGER.debug("[SelfBondage] {} can't self-accessory — arms bound", player.getName().getString());
return;
}
// Check if region is occupied — try to swap
for (BodyRegionV2 region : v2Item.getOccupiedRegions(stack)) {
if (V2EquipmentHelper.isRegionOccupied(player, region)) {
ItemStack existing = V2EquipmentHelper.getInRegion(player, region);
// Can't swap if locked
if (existing.getItem() instanceof ILockable lockable && lockable.isLocked(existing)) {
TiedUpMod.LOGGER.debug("[SelfBondage] {} can't swap — current is locked", player.getName().getString());
return;
}
}
}
// Equip via V2EquipmentHelper (handles conflict resolution, displaced items)
V2EquipResult result = V2EquipmentHelper.equipItem(player, stack.copy());
if (result.isSuccess()) {
for (ItemStack displaced : result.displaced()) {
player.spawnAtLocation(displaced);
}
stack.shrink(1);
SyncManager.syncInventory(player);
TiedUpMod.LOGGER.info("[SelfBondage] {} self-equipped V2 accessory", player.getName().getString());
}
}
/**
* Handle self-equipping an accessory (gag, blindfold, mittens, earplugs).
* Can be used anytime (no need to be tied).