diff --git a/src/main/java/com/tiedup/remake/network/selfbondage/PacketSelfBondage.java b/src/main/java/com/tiedup/remake/network/selfbondage/PacketSelfBondage.java index 51f48ee..392178f 100644 --- a/src/main/java/com/tiedup/remake/network/selfbondage/PacketSelfBondage.java +++ b/src/main/java/com/tiedup/remake/network/selfbondage/PacketSelfBondage.java @@ -248,8 +248,8 @@ public class PacketSelfBondage { IV2BondageItem v2Item, IBondageState state ) { - // Can't self-tie if already tied - if (state.isTiedUp()) { + // Can't self-tie if already tied (check both V1 state and V2 region to prevent dual-bind) + if (state.isTiedUp() || V2EquipmentHelper.isRegionOccupied(player, BodyRegionV2.ARMS)) { TiedUpMod.LOGGER.debug("[SelfBondage] {} tried V2 self-tie but already tied", player.getName().getString()); return; } diff --git a/src/main/java/com/tiedup/remake/v2/bondage/TyingInteractionHelper.java b/src/main/java/com/tiedup/remake/v2/bondage/TyingInteractionHelper.java index f75a08c..5117419 100644 --- a/src/main/java/com/tiedup/remake/v2/bondage/TyingInteractionHelper.java +++ b/src/main/java/com/tiedup/remake/v2/bondage/TyingInteractionHelper.java @@ -46,8 +46,8 @@ public final class TyingInteractionHelper { return InteractionResult.PASS; } - // Already tied — try to swap - if (targetState.isTiedUp()) { + // 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()) { @@ -59,8 +59,14 @@ public final class TyingInteractionHelper { return InteractionResult.SUCCESS; } else { // Equip failed — rollback: re-equip old bind - V2EquipmentHelper.equipItem(target, oldBind); - TiedUpMod.LOGGER.debug("[TyingInteraction] Swap failed, rolled back old bind on {}", target.getName().getString()); + 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; } } diff --git a/src/main/java/com/tiedup/remake/v2/bondage/component/GaggingComponent.java b/src/main/java/com/tiedup/remake/v2/bondage/component/GaggingComponent.java index e6af614..07e664b 100644 --- a/src/main/java/com/tiedup/remake/v2/bondage/component/GaggingComponent.java +++ b/src/main/java/com/tiedup/remake/v2/bondage/component/GaggingComponent.java @@ -23,11 +23,14 @@ import org.jetbrains.annotations.Nullable; public class GaggingComponent implements IItemComponent { private final @Nullable String material; + private final @Nullable GagMaterial cachedMaterial; private final double comprehensionOverride; private final double rangeOverride; - private GaggingComponent(@Nullable String material, double comprehensionOverride, double rangeOverride) { + private GaggingComponent(@Nullable String material, @Nullable GagMaterial cachedMaterial, + double comprehensionOverride, double rangeOverride) { this.material = material; + this.cachedMaterial = cachedMaterial; this.comprehensionOverride = comprehensionOverride; this.rangeOverride = rangeOverride; } @@ -47,7 +50,16 @@ public class GaggingComponent implements IItemComponent { range = Math.max(0.0, config.get("range").getAsDouble()); } } - return new GaggingComponent(material, comprehension, range); + // Resolve and cache GagMaterial at load time to avoid valueOf() on every chat message + GagMaterial resolved = null; + if (material != null) { + try { + resolved = GagMaterial.valueOf(material.toUpperCase()); + } catch (IllegalArgumentException e) { + TiedUpMod.LOGGER.warn("[GaggingComponent] Unknown gag material '{}' — using defaults", material); + } + } + return new GaggingComponent(material, resolved, comprehension, range); } /** How much of the gagged speech is comprehensible (0.0 = nothing, 1.0 = full). */ @@ -66,15 +78,9 @@ public class GaggingComponent implements IItemComponent { return 10.0; } - /** The gag material enum, or null if not configured or invalid. */ + /** The gag material enum, or null if not configured or invalid. Cached at load time. */ public @Nullable GagMaterial getMaterial() { - if (material == null) return null; - try { - return GagMaterial.valueOf(material.toUpperCase()); - } catch (IllegalArgumentException e) { - TiedUpMod.LOGGER.warn("[GaggingComponent] Unknown gag material: {}", material); - return null; - } + return cachedMaterial; } /** The raw material string from JSON, or null. */ diff --git a/src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenBondageItem.java b/src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenBondageItem.java index 876002a..3bf2e04 100644 --- a/src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenBondageItem.java +++ b/src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenBondageItem.java @@ -167,8 +167,13 @@ public class DataDrivenBondageItem extends AbstractV2BondageItem { 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. + // NECK: blocked until Branch C wires the full collar equip flow + // (add owner to NBT, register in CollarRegistry, play sound, sync). + // Without this, V2 collars equip without ownership — breaking GPS, shock, alerts. + if (regions.contains(BodyRegionV2.NECK)) { + TiedUpMod.LOGGER.debug("[DataDrivenBondageItem] NECK equip blocked — collar flow not wired yet"); + return InteractionResult.PASS; + } // All other regions (MOUTH, EYES, EARS, HANDS): instant equip via parent return super.interactLivingEntity(stack, player, target, hand);