diff --git a/.gitignore b/.gitignore
index f3b98f0..86a5f23 100644
--- a/.gitignore
+++ b/.gitignore
@@ -45,3 +45,4 @@ build_output.log
.DS_Store
Thumbs.db
desktop.ini
+docs/
diff --git a/src/main/java/com/tiedup/remake/blocks/entity/BondageItemBlockEntity.java b/src/main/java/com/tiedup/remake/blocks/entity/BondageItemBlockEntity.java
index 279bb5c..e02cbce 100644
--- a/src/main/java/com/tiedup/remake/blocks/entity/BondageItemBlockEntity.java
+++ b/src/main/java/com/tiedup/remake/blocks/entity/BondageItemBlockEntity.java
@@ -1,10 +1,5 @@
package com.tiedup.remake.blocks.entity;
-import com.tiedup.remake.items.base.ItemBind;
-import com.tiedup.remake.items.base.ItemBlindfold;
-import com.tiedup.remake.items.base.ItemCollar;
-import com.tiedup.remake.items.base.ItemEarplugs;
-import com.tiedup.remake.items.base.ItemGag;
import com.tiedup.remake.items.clothes.GenericClothes;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.BindModeHelper;
@@ -199,45 +194,43 @@ public abstract class BondageItemBlockEntity
@Override
public void readBondageData(CompoundTag tag) {
- // Read bind with type validation (V1 ItemBind or V2 ARMS-region item)
+ // Read bind with type validation (V2 ARMS-region item)
if (tag.contains("bind")) {
ItemStack bindStack = ItemStack.of(tag.getCompound("bind"));
- if (!bindStack.isEmpty() && (bindStack.getItem() instanceof ItemBind || BindModeHelper.isBindItem(bindStack))) {
+ if (!bindStack.isEmpty() && BindModeHelper.isBindItem(bindStack)) {
this.bind = bindStack;
}
}
- // Read gag with type validation (V1 ItemGag or V2 GAGGING component)
+ // Read gag with type validation (V2 GAGGING component)
if (tag.contains("gag")) {
ItemStack gagStack = ItemStack.of(tag.getCompound("gag"));
- if (!gagStack.isEmpty() && (gagStack.getItem() instanceof ItemGag
- || DataDrivenBondageItem.getComponent(gagStack, ComponentType.GAGGING, GaggingComponent.class) != null)) {
+ if (!gagStack.isEmpty()
+ && DataDrivenBondageItem.getComponent(gagStack, ComponentType.GAGGING, GaggingComponent.class) != null) {
this.gag = gagStack;
}
}
- // Read blindfold with type validation (V1 ItemBlindfold or V2 EYES-region item)
+ // Read blindfold with type validation (V2 EYES-region item)
if (tag.contains("blindfold")) {
ItemStack blindfoldStack = ItemStack.of(tag.getCompound("blindfold"));
- if (!blindfoldStack.isEmpty() && (blindfoldStack.getItem() instanceof ItemBlindfold
- || isDataDrivenForRegion(blindfoldStack, BodyRegionV2.EYES))) {
+ if (!blindfoldStack.isEmpty() && isDataDrivenForRegion(blindfoldStack, BodyRegionV2.EYES)) {
this.blindfold = blindfoldStack;
}
}
- // Read earplugs with type validation (V1 ItemEarplugs or V2 EARS-region item)
+ // Read earplugs with type validation (V2 EARS-region item)
if (tag.contains("earplugs")) {
ItemStack earplugsStack = ItemStack.of(tag.getCompound("earplugs"));
- if (!earplugsStack.isEmpty() && (earplugsStack.getItem() instanceof ItemEarplugs
- || isDataDrivenForRegion(earplugsStack, BodyRegionV2.EARS))) {
+ if (!earplugsStack.isEmpty() && isDataDrivenForRegion(earplugsStack, BodyRegionV2.EARS)) {
this.earplugs = earplugsStack;
}
}
- // Read collar with type validation (V1 ItemCollar or V2 collar)
+ // Read collar with type validation (V2 collar)
if (tag.contains("collar")) {
ItemStack collarStack = ItemStack.of(tag.getCompound("collar"));
- if (!collarStack.isEmpty() && (collarStack.getItem() instanceof ItemCollar || CollarHelper.isCollar(collarStack))) {
+ if (!collarStack.isEmpty() && CollarHelper.isCollar(collarStack)) {
this.collar = collarStack;
}
}
diff --git a/src/main/java/com/tiedup/remake/blocks/entity/TrappedChestBlockEntity.java b/src/main/java/com/tiedup/remake/blocks/entity/TrappedChestBlockEntity.java
index 73dd2da..add7b86 100644
--- a/src/main/java/com/tiedup/remake/blocks/entity/TrappedChestBlockEntity.java
+++ b/src/main/java/com/tiedup/remake/blocks/entity/TrappedChestBlockEntity.java
@@ -1,10 +1,5 @@
package com.tiedup.remake.blocks.entity;
-import com.tiedup.remake.items.base.ItemBind;
-import com.tiedup.remake.items.base.ItemBlindfold;
-import com.tiedup.remake.items.base.ItemCollar;
-import com.tiedup.remake.items.base.ItemEarplugs;
-import com.tiedup.remake.items.base.ItemGag;
import com.tiedup.remake.items.clothes.GenericClothes;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.BindModeHelper;
@@ -57,7 +52,7 @@ public class TrappedChestBlockEntity
@Override
public void setBind(ItemStack stack) {
- if (stack.isEmpty() || stack.getItem() instanceof ItemBind || BindModeHelper.isBindItem(stack)) {
+ if (stack.isEmpty() || BindModeHelper.isBindItem(stack)) {
this.bind = stack;
setChangedAndSync();
}
@@ -70,7 +65,7 @@ public class TrappedChestBlockEntity
@Override
public void setGag(ItemStack stack) {
- if (stack.isEmpty() || stack.getItem() instanceof ItemGag
+ if (stack.isEmpty()
|| DataDrivenBondageItem.getComponent(stack, ComponentType.GAGGING, GaggingComponent.class) != null) {
this.gag = stack;
setChangedAndSync();
@@ -84,8 +79,7 @@ public class TrappedChestBlockEntity
@Override
public void setBlindfold(ItemStack stack) {
- if (stack.isEmpty() || stack.getItem() instanceof ItemBlindfold
- || isDataDrivenForRegion(stack, BodyRegionV2.EYES)) {
+ if (stack.isEmpty() || isDataDrivenForRegion(stack, BodyRegionV2.EYES)) {
this.blindfold = stack;
setChangedAndSync();
}
@@ -98,8 +92,7 @@ public class TrappedChestBlockEntity
@Override
public void setEarplugs(ItemStack stack) {
- if (stack.isEmpty() || stack.getItem() instanceof ItemEarplugs
- || isDataDrivenForRegion(stack, BodyRegionV2.EARS)) {
+ if (stack.isEmpty() || isDataDrivenForRegion(stack, BodyRegionV2.EARS)) {
this.earplugs = stack;
setChangedAndSync();
}
@@ -112,7 +105,7 @@ public class TrappedChestBlockEntity
@Override
public void setCollar(ItemStack stack) {
- if (stack.isEmpty() || stack.getItem() instanceof ItemCollar || CollarHelper.isCollar(stack)) {
+ if (stack.isEmpty() || CollarHelper.isCollar(stack)) {
this.collar = stack;
setChangedAndSync();
}
diff --git a/src/main/java/com/tiedup/remake/client/events/SelfBondageInputHandler.java b/src/main/java/com/tiedup/remake/client/events/SelfBondageInputHandler.java
index b876fbd..79efbb1 100644
--- a/src/main/java/com/tiedup/remake/client/events/SelfBondageInputHandler.java
+++ b/src/main/java/com/tiedup/remake/client/events/SelfBondageInputHandler.java
@@ -1,6 +1,5 @@
package com.tiedup.remake.client.events;
-import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.network.ModNetwork;
import com.tiedup.remake.network.selfbondage.PacketSelfBondage;
import com.tiedup.remake.v2.bondage.BindModeHelper;
@@ -166,8 +165,8 @@ public class SelfBondageInputHandler {
private static boolean isSelfBondageItem(ItemStack stack) {
if (stack.isEmpty()) return false;
- // Collar cannot be self-equipped (V1 collar guard + V2 ownership component)
- if (stack.getItem() instanceof ItemCollar || CollarHelper.isCollar(stack)) {
+ // Collar cannot be self-equipped (V2 ownership component)
+ if (CollarHelper.isCollar(stack)) {
return false;
}
diff --git a/src/main/java/com/tiedup/remake/client/gui/widgets/SlaveEntryWidget.java b/src/main/java/com/tiedup/remake/client/gui/widgets/SlaveEntryWidget.java
index 00fdb2d..ab89a3a 100644
--- a/src/main/java/com/tiedup/remake/client/gui/widgets/SlaveEntryWidget.java
+++ b/src/main/java/com/tiedup/remake/client/gui/widgets/SlaveEntryWidget.java
@@ -4,7 +4,6 @@ import static com.tiedup.remake.client.gui.util.GuiLayoutConstants.*;
import com.google.common.collect.ImmutableList;
import com.tiedup.remake.client.gui.util.GuiColors;
-import com.tiedup.remake.items.ItemGpsCollar;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.v2.BodyRegionV2;
@@ -370,9 +369,8 @@ public class SlaveEntryWidget
// GPS zone status (right of health)
if (hasGPSCollar()) {
ItemStack collarStack = slave.getEquipment(BodyRegionV2.NECK);
- if (collarStack.getItem() instanceof ItemGpsCollar gps) {
+ if (CollarHelper.hasGPS(collarStack)) {
boolean inSafeZone = isInAnySafeZone(
- gps,
collarStack,
entity
);
@@ -570,17 +568,25 @@ public class SlaveEntryWidget
}
private boolean isInAnySafeZone(
- ItemGpsCollar gps,
ItemStack collarStack,
LivingEntity entity
) {
if (!CollarHelper.isActive(collarStack)) return true;
- var safeSpots = gps.getSafeSpots(collarStack);
+ // Read safe spots from NBT
+ net.minecraft.nbt.CompoundTag tag = collarStack.getTag();
+ if (tag == null || !tag.contains("safeSpots", net.minecraft.nbt.Tag.TAG_LIST)) return true;
+ net.minecraft.nbt.ListTag safeSpots = tag.getList("safeSpots", net.minecraft.nbt.Tag.TAG_COMPOUND);
if (safeSpots.isEmpty()) return true;
- for (var spot : safeSpots) {
- if (spot.isInside(entity)) {
+ for (int i = 0; i < safeSpots.size(); i++) {
+ net.minecraft.nbt.CompoundTag spot = safeSpots.getCompound(i);
+ double x = spot.getDouble("x");
+ double y = spot.getDouble("y");
+ double z = spot.getDouble("z");
+ int radius = spot.contains("radius") ? spot.getInt("radius") : 50;
+ double dist = entity.distanceToSqr(x, y, z);
+ if (dist <= (double) radius * radius) {
return true;
}
}
diff --git a/src/main/java/com/tiedup/remake/commands/KidnapSetCommand.java b/src/main/java/com/tiedup/remake/commands/KidnapSetCommand.java
index a269e13..759a819 100644
--- a/src/main/java/com/tiedup/remake/commands/KidnapSetCommand.java
+++ b/src/main/java/com/tiedup/remake/commands/KidnapSetCommand.java
@@ -93,16 +93,19 @@ public class KidnapSetCommand {
given += giveDataDrivenItems(player, "classic_blindfold", 4);
given += giveDataDrivenItems(player, "blindfold_mask", 2);
- // Collars
- given += giveItem(
- player,
- new ItemStack(ModItems.CLASSIC_COLLAR.get(), 4)
- );
- given += giveItem(
- player,
- new ItemStack(ModItems.SHOCK_COLLAR.get(), 2)
- );
- given += giveItem(player, new ItemStack(ModItems.GPS_COLLAR.get(), 2));
+ // Collars (data-driven)
+ ItemStack classicCollars = com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem.createStack(
+ new net.minecraft.resources.ResourceLocation("tiedup", "classic_collar"));
+ classicCollars.setCount(4);
+ given += giveItem(player, classicCollars);
+ ItemStack shockCollars = com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem.createStack(
+ new net.minecraft.resources.ResourceLocation("tiedup", "shock_collar"));
+ shockCollars.setCount(2);
+ given += giveItem(player, shockCollars);
+ ItemStack gpsCollars = com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem.createStack(
+ new net.minecraft.resources.ResourceLocation("tiedup", "gps_collar"));
+ gpsCollars.setCount(2);
+ given += giveItem(player, gpsCollars);
// Tools
given += giveItem(
diff --git a/src/main/java/com/tiedup/remake/commands/NPCCommand.java b/src/main/java/com/tiedup/remake/commands/NPCCommand.java
index d2960a1..22968fb 100644
--- a/src/main/java/com/tiedup/remake/commands/NPCCommand.java
+++ b/src/main/java/com/tiedup/remake/commands/NPCCommand.java
@@ -603,7 +603,7 @@ public class NPCCommand {
npc.equip(
BodyRegionV2.NECK,
- new ItemStack(ModItems.CLASSIC_COLLAR.get())
+ DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_collar"))
);
context
.getSource()
@@ -658,7 +658,7 @@ public class NPCCommand {
DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "cloth_gag")),
DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_blindfold")),
DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_earplugs")),
- new ItemStack(ModItems.CLASSIC_COLLAR.get()),
+ DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_collar")),
ItemStack.EMPTY // No clothes
);
}
diff --git a/src/main/java/com/tiedup/remake/commands/subcommands/BondageSubCommand.java b/src/main/java/com/tiedup/remake/commands/subcommands/BondageSubCommand.java
index 3e53e76..20b8301 100644
--- a/src/main/java/com/tiedup/remake/commands/subcommands/BondageSubCommand.java
+++ b/src/main/java/com/tiedup/remake/commands/subcommands/BondageSubCommand.java
@@ -493,7 +493,7 @@ public class BondageSubCommand {
return 0;
}
- ItemStack collar = new ItemStack(ModItems.CLASSIC_COLLAR.get());
+ ItemStack collar = com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem.createStack(new net.minecraft.resources.ResourceLocation("tiedup", "classic_collar"));
if (context.getSource().getEntity() instanceof ServerPlayer executor) {
CollarHelper.addOwner(collar, executor);
@@ -1078,7 +1078,7 @@ public class BondageSubCommand {
}
if (!state.hasCollar()) {
- ItemStack collar = new ItemStack(ModItems.CLASSIC_COLLAR.get());
+ ItemStack collar = com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem.createStack(new net.minecraft.resources.ResourceLocation("tiedup", "classic_collar"));
if (
context.getSource().getEntity() instanceof ServerPlayer executor
) {
@@ -1163,7 +1163,7 @@ public class BondageSubCommand {
state.putBlindfoldOn(blindfold);
}
if (!state.hasCollar()) {
- ItemStack collar = new ItemStack(ModItems.CLASSIC_COLLAR.get());
+ ItemStack collar = com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem.createStack(new net.minecraft.resources.ResourceLocation("tiedup", "classic_collar"));
if (
context.getSource().getEntity() instanceof ServerPlayer executor
) {
diff --git a/src/main/java/com/tiedup/remake/compat/mca/capability/MCAKidnappedAdapter.java b/src/main/java/com/tiedup/remake/compat/mca/capability/MCAKidnappedAdapter.java
index 2d10de1..698519b 100644
--- a/src/main/java/com/tiedup/remake/compat/mca/capability/MCAKidnappedAdapter.java
+++ b/src/main/java/com/tiedup/remake/compat/mca/capability/MCAKidnappedAdapter.java
@@ -8,7 +8,6 @@ import com.tiedup.remake.v2.bondage.component.GaggingComponent;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import com.tiedup.remake.items.base.IHasResistance;
import com.tiedup.remake.items.base.ILockable;
-import com.tiedup.remake.items.base.ItemBind;
import com.tiedup.remake.state.ICaptor;
import com.tiedup.remake.state.IRestrainable;
import com.tiedup.remake.state.IRestrainableEntity;
@@ -280,16 +279,14 @@ public class MCAKidnappedAdapter implements IRestrainable {
public boolean hasGaggingEffect() {
ItemStack gag = cap.getGag();
if (gag.isEmpty()) return false;
- if (DataDrivenBondageItem.getComponent(gag, ComponentType.GAGGING, GaggingComponent.class) != null) return true;
- return gag.getItem() instanceof com.tiedup.remake.items.base.IHasGaggingEffect;
+ return DataDrivenBondageItem.getComponent(gag, ComponentType.GAGGING, GaggingComponent.class) != null;
}
@Override
public boolean hasBlindingEffect() {
ItemStack blindfold = cap.getBlindfold();
if (blindfold.isEmpty()) return false;
- if (DataDrivenBondageItem.getComponent(blindfold, ComponentType.BLINDING, BlindingComponent.class) != null) return true;
- return blindfold.getItem() instanceof com.tiedup.remake.items.base.IHasBlindingEffect;
+ return DataDrivenBondageItem.getComponent(blindfold, ComponentType.BLINDING, BlindingComponent.class) != null;
}
@Override
diff --git a/src/main/java/com/tiedup/remake/core/SettingsAccessor.java b/src/main/java/com/tiedup/remake/core/SettingsAccessor.java
index 6ad2bd4..aa377c3 100644
--- a/src/main/java/com/tiedup/remake/core/SettingsAccessor.java
+++ b/src/main/java/com/tiedup/remake/core/SettingsAccessor.java
@@ -160,7 +160,7 @@ public class SettingsAccessor {
*
BUG-003 fix: Previously, {@code IHasResistance.getBaseResistance()}
* called {@code ModGameRules.getResistance()} which only knew 4 types (rope, gag,
* blindfold, collar) and returned hardcoded 100 for the other 10 types. Meanwhile
- * {@code BindVariant.getResistance()} read from ModConfig which had all 14 types.
+ * the old BindVariant.getResistance() read from ModConfig which had all 14 types.
* This caused a display-vs-struggle desync (display: 250, struggle: 100).
* Now both paths use this method.
*
@@ -207,8 +207,7 @@ public class SettingsAccessor {
/**
* Normalize a raw bind item name to its config key.
*
- *
Replicates the mapping from
- * {@link com.tiedup.remake.items.base.BindVariant#getResistance()} so that
+ *
Normalizes raw item names to config keys so that
* every call site resolves to the same config entry.
*
* @param bindType Raw item name (e.g., "ropes", "vine_seed", "collar")
diff --git a/src/main/java/com/tiedup/remake/dialogue/GagTalkManager.java b/src/main/java/com/tiedup/remake/dialogue/GagTalkManager.java
index 585e687..f9b27e7 100644
--- a/src/main/java/com/tiedup/remake/dialogue/GagTalkManager.java
+++ b/src/main/java/com/tiedup/remake/dialogue/GagTalkManager.java
@@ -3,7 +3,6 @@ package com.tiedup.remake.dialogue;
import static com.tiedup.remake.util.GameConstants.*;
import com.tiedup.remake.dialogue.EmotionalContext.EmotionType;
-import com.tiedup.remake.items.base.ItemGag;
import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.v2.bondage.component.ComponentType;
import com.tiedup.remake.v2.bondage.component.GaggingComponent;
@@ -66,8 +65,6 @@ public class GagTalkManager {
gagStack, ComponentType.GAGGING, GaggingComponent.class);
if (gaggingComp != null && gaggingComp.getMaterial() != null) {
material = gaggingComp.getMaterial();
- } else if (gagStack.getItem() instanceof ItemGag gag) {
- material = gag.getGagMaterial();
}
// 1. EFFET DE SUFFOCATION (Si message trop long)
@@ -528,8 +525,6 @@ public class GagTalkManager {
gagStack, ComponentType.GAGGING, GaggingComponent.class);
if (comp != null && comp.getMaterial() != null) {
material = comp.getMaterial();
- } else if (gagStack.getItem() instanceof ItemGag gag) {
- material = gag.getGagMaterial();
}
}
diff --git a/src/main/java/com/tiedup/remake/dispenser/DispenserBehaviors.java b/src/main/java/com/tiedup/remake/dispenser/DispenserBehaviors.java
index bb8389a..1fbb23a 100644
--- a/src/main/java/com/tiedup/remake/dispenser/DispenserBehaviors.java
+++ b/src/main/java/com/tiedup/remake/dispenser/DispenserBehaviors.java
@@ -2,16 +2,19 @@ package com.tiedup.remake.dispenser;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.ModItems;
-import com.tiedup.remake.items.base.*;
import net.minecraft.world.level.block.DispenserBlock;
/**
* Registration class for all TiedUp dispenser behaviors.
*
* Allows dispensers to:
- * - Equip bondage items (binds, gags, blindfolds, collars, earplugs, clothes) on entities
+ * - Equip bondage items (via data-driven V2 system) on entities
* - Shoot rope arrows
*
+ * Note: V1 per-variant dispenser registrations have been removed.
+ * Data-driven bondage items use a single universal dispenser behavior
+ * registered via DataDrivenBondageItem system.
+ *
* Based on original behaviors package from 1.12.2
*/
public class DispenserBehaviors {
@@ -25,72 +28,15 @@ public class DispenserBehaviors {
"[DispenserBehaviors] Registering dispenser behaviors..."
);
- registerBindBehaviors();
- registerGagBehaviors();
- registerBlindfoldBehaviors();
- registerCollarBehaviors();
- registerEarplugsBehaviors();
registerClothesBehaviors();
registerRopeArrowBehavior();
+ registerBondageItemBehavior();
TiedUpMod.LOGGER.info(
"[DispenserBehaviors] Dispenser behaviors registered!"
);
}
- private static void registerBindBehaviors() {
- var behavior = GenericBondageDispenseBehavior.forBind();
- for (BindVariant variant : BindVariant.values()) {
- DispenserBlock.registerBehavior(
- ModItems.getBind(variant),
- behavior
- );
- }
- }
-
- private static void registerGagBehaviors() {
- var behavior = GenericBondageDispenseBehavior.forGag();
- for (GagVariant variant : GagVariant.values()) {
- DispenserBlock.registerBehavior(ModItems.getGag(variant), behavior);
- }
- DispenserBlock.registerBehavior(ModItems.MEDICAL_GAG.get(), behavior);
- DispenserBlock.registerBehavior(ModItems.HOOD.get(), behavior);
- }
-
- private static void registerBlindfoldBehaviors() {
- var behavior = GenericBondageDispenseBehavior.forBlindfold();
- for (BlindfoldVariant variant : BlindfoldVariant.values()) {
- DispenserBlock.registerBehavior(
- ModItems.getBlindfold(variant),
- behavior
- );
- }
- }
-
- private static void registerCollarBehaviors() {
- var behavior = GenericBondageDispenseBehavior.forCollar();
- DispenserBlock.registerBehavior(
- ModItems.CLASSIC_COLLAR.get(),
- behavior
- );
- DispenserBlock.registerBehavior(ModItems.SHOCK_COLLAR.get(), behavior);
- DispenserBlock.registerBehavior(
- ModItems.SHOCK_COLLAR_AUTO.get(),
- behavior
- );
- DispenserBlock.registerBehavior(ModItems.GPS_COLLAR.get(), behavior);
- }
-
- private static void registerEarplugsBehaviors() {
- var behavior = GenericBondageDispenseBehavior.forEarplugs();
- for (EarplugsVariant variant : EarplugsVariant.values()) {
- DispenserBlock.registerBehavior(
- ModItems.getEarplugs(variant),
- behavior
- );
- }
- }
-
private static void registerClothesBehaviors() {
DispenserBlock.registerBehavior(
ModItems.CLOTHES.get(),
@@ -98,6 +44,17 @@ public class DispenserBehaviors {
);
}
+ private static void registerBondageItemBehavior() {
+ // Single registration for the V2 data-driven item singleton.
+ // GenericBondageDispenseBehavior inspects the stack's definition to determine behavior.
+ if (com.tiedup.remake.v2.bondage.V2BondageItems.DATA_DRIVEN_ITEM != null) {
+ DispenserBlock.registerBehavior(
+ com.tiedup.remake.v2.bondage.V2BondageItems.DATA_DRIVEN_ITEM.get(),
+ GenericBondageDispenseBehavior.forAnyDataDriven()
+ );
+ }
+ }
+
private static void registerRopeArrowBehavior() {
DispenserBlock.registerBehavior(
ModItems.ROPE_ARROW.get(),
diff --git a/src/main/java/com/tiedup/remake/dispenser/GenericBondageDispenseBehavior.java b/src/main/java/com/tiedup/remake/dispenser/GenericBondageDispenseBehavior.java
index c97b45a..f15143f 100644
--- a/src/main/java/com/tiedup/remake/dispenser/GenericBondageDispenseBehavior.java
+++ b/src/main/java/com/tiedup/remake/dispenser/GenericBondageDispenseBehavior.java
@@ -1,16 +1,21 @@
package com.tiedup.remake.dispenser;
-import com.tiedup.remake.items.base.*;
import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.v2.BodyRegionV2;
+import com.tiedup.remake.v2.bondage.BindModeHelper;
+import com.tiedup.remake.v2.bondage.CollarHelper;
+import com.tiedup.remake.v2.bondage.component.ComponentType;
+import com.tiedup.remake.v2.bondage.component.GaggingComponent;
+import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
+import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemDefinition;
+import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemRegistry;
import java.util.function.BiConsumer;
import java.util.function.Predicate;
-import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
/**
* Generic dispenser behavior for equipping bondage items.
- * Replaces individual BindDispenseBehavior, GagDispenseBehavior, etc.
+ * Uses V2 data-driven item detection instead of V1 class checks.
*
* Use factory methods to create instances for each bondage type.
*/
@@ -18,23 +23,23 @@ public class GenericBondageDispenseBehavior
extends EquipBondageDispenseBehavior
{
- private final Class extends Item> itemClass;
+ private final Predicate itemCheck;
private final Predicate canEquipCheck;
private final BiConsumer equipAction;
private GenericBondageDispenseBehavior(
- Class extends Item> itemClass,
+ Predicate itemCheck,
Predicate canEquipCheck,
BiConsumer equipAction
) {
- this.itemClass = itemClass;
+ this.itemCheck = itemCheck;
this.canEquipCheck = canEquipCheck;
this.equipAction = equipAction;
}
@Override
protected boolean isValidItem(ItemStack stack) {
- return !stack.isEmpty() && itemClass.isInstance(stack.getItem());
+ return !stack.isEmpty() && itemCheck.test(stack);
}
@Override
@@ -51,9 +56,35 @@ public class GenericBondageDispenseBehavior
// Factory Methods
+ /** Universal behavior for the V2 data-driven item singleton. Dispatches by region. */
+ public static GenericBondageDispenseBehavior forAnyDataDriven() {
+ return new GenericBondageDispenseBehavior(
+ stack -> DataDrivenItemRegistry.get(stack) != null,
+ state -> true, // let equip() handle the check
+ (state, stack) -> {
+ DataDrivenItemDefinition def = DataDrivenItemRegistry.get(stack);
+ if (def == null) return;
+ java.util.Set regions = def.occupiedRegions();
+ if (regions.contains(BodyRegionV2.ARMS) && !state.isTiedUp()) {
+ state.equip(BodyRegionV2.ARMS, stack);
+ } else if (regions.contains(BodyRegionV2.MOUTH) && !state.isGagged()) {
+ state.equip(BodyRegionV2.MOUTH, stack);
+ } else if (regions.contains(BodyRegionV2.EYES) && !state.isBlindfolded()) {
+ state.equip(BodyRegionV2.EYES, stack);
+ } else if (regions.contains(BodyRegionV2.NECK) && !state.hasCollar()) {
+ state.equip(BodyRegionV2.NECK, stack);
+ } else if (regions.contains(BodyRegionV2.EARS) && !state.hasEarplugs()) {
+ state.equip(BodyRegionV2.EARS, stack);
+ } else if (regions.contains(BodyRegionV2.HANDS) && !state.hasMittens()) {
+ state.equip(BodyRegionV2.HANDS, stack);
+ }
+ }
+ );
+ }
+
public static GenericBondageDispenseBehavior forBind() {
return new GenericBondageDispenseBehavior(
- ItemBind.class,
+ BindModeHelper::isBindItem,
state -> !state.isTiedUp(),
(s, i) -> s.equip(BodyRegionV2.ARMS, i)
);
@@ -61,7 +92,7 @@ public class GenericBondageDispenseBehavior
public static GenericBondageDispenseBehavior forGag() {
return new GenericBondageDispenseBehavior(
- ItemGag.class,
+ stack -> DataDrivenBondageItem.getComponent(stack, ComponentType.GAGGING, GaggingComponent.class) != null,
state -> !state.isGagged(),
(s, i) -> s.equip(BodyRegionV2.MOUTH, i)
);
@@ -69,7 +100,10 @@ public class GenericBondageDispenseBehavior
public static GenericBondageDispenseBehavior forBlindfold() {
return new GenericBondageDispenseBehavior(
- ItemBlindfold.class,
+ stack -> {
+ DataDrivenItemDefinition def = DataDrivenItemRegistry.get(stack);
+ return def != null && def.occupiedRegions().contains(BodyRegionV2.EYES);
+ },
state -> !state.isBlindfolded(),
(s, i) -> s.equip(BodyRegionV2.EYES, i)
);
@@ -77,7 +111,7 @@ public class GenericBondageDispenseBehavior
public static GenericBondageDispenseBehavior forCollar() {
return new GenericBondageDispenseBehavior(
- ItemCollar.class,
+ CollarHelper::isCollar,
state -> !state.hasCollar(),
(s, i) -> s.equip(BodyRegionV2.NECK, i)
);
@@ -85,7 +119,10 @@ public class GenericBondageDispenseBehavior
public static GenericBondageDispenseBehavior forEarplugs() {
return new GenericBondageDispenseBehavior(
- ItemEarplugs.class,
+ stack -> {
+ DataDrivenItemDefinition def = DataDrivenItemRegistry.get(stack);
+ return def != null && def.occupiedRegions().contains(BodyRegionV2.EARS);
+ },
state -> !state.hasEarplugs(),
(s, i) -> s.equip(BodyRegionV2.EARS, i)
);
diff --git a/src/main/java/com/tiedup/remake/entities/AbstractTiedUpNpc.java b/src/main/java/com/tiedup/remake/entities/AbstractTiedUpNpc.java
index 85f3945..f8d3f83 100644
--- a/src/main/java/com/tiedup/remake/entities/AbstractTiedUpNpc.java
+++ b/src/main/java/com/tiedup/remake/entities/AbstractTiedUpNpc.java
@@ -796,16 +796,14 @@ public abstract class AbstractTiedUpNpc
public boolean hasGaggingEffect() {
ItemStack gag = this.getEquipment(BodyRegionV2.MOUTH);
if (gag.isEmpty()) return false;
- if (DataDrivenBondageItem.getComponent(gag, ComponentType.GAGGING, GaggingComponent.class) != null) return true;
- return gag.getItem() instanceof com.tiedup.remake.items.base.IHasGaggingEffect;
+ return DataDrivenBondageItem.getComponent(gag, ComponentType.GAGGING, GaggingComponent.class) != null;
}
@Override
public boolean hasBlindingEffect() {
ItemStack blindfold = this.getEquipment(BodyRegionV2.EYES);
if (blindfold.isEmpty()) return false;
- if (DataDrivenBondageItem.getComponent(blindfold, ComponentType.BLINDING, BlindingComponent.class) != null) return true;
- return blindfold.getItem() instanceof com.tiedup.remake.items.base.IHasBlindingEffect;
+ return DataDrivenBondageItem.getComponent(blindfold, ComponentType.BLINDING, BlindingComponent.class) != null;
}
@Override
@@ -1087,9 +1085,7 @@ public abstract class AbstractTiedUpNpc
public boolean hasShockCollar() {
ItemStack collar = this.getEquipment(BodyRegionV2.NECK);
if (collar.isEmpty()) return false;
- return (
- collar.getItem() instanceof com.tiedup.remake.items.ItemShockCollar
- );
+ return com.tiedup.remake.v2.bondage.CollarHelper.canShock(collar);
}
// BONDAGE SERVICE (delegated to BondageManager)
diff --git a/src/main/java/com/tiedup/remake/entities/EntityKidnapperMerchant.java b/src/main/java/com/tiedup/remake/entities/EntityKidnapperMerchant.java
index b874d21..1874af7 100644
--- a/src/main/java/com/tiedup/remake/entities/EntityKidnapperMerchant.java
+++ b/src/main/java/com/tiedup/remake/entities/EntityKidnapperMerchant.java
@@ -616,82 +616,10 @@ public class EntityKidnapperMerchant extends EntityKidnapperElite {
private List collectAllModItems() {
List items = new ArrayList<>();
- // All binds — iterate V1 variants, create V2 stacks
- for (BindVariant variant : BindVariant.values()) {
- if (variant.supportsColor()) {
- for (ItemColor color : ItemColor.values()) {
- if (
- color != ItemColor.CAUTION && color != ItemColor.CLEAR
- ) {
- ItemStack stack = DataDrivenBondageItem.createStack(
- new net.minecraft.resources.ResourceLocation("tiedup", variant.getRegistryName())
- );
- KidnapperItemSelector.applyColor(stack, color);
- items.add(stack);
- }
- }
- } else {
- items.add(DataDrivenBondageItem.createStack(
- new net.minecraft.resources.ResourceLocation("tiedup", variant.getRegistryName())
- ));
- }
- }
-
- // All gags
- for (GagVariant variant : GagVariant.values()) {
- if (variant.supportsColor()) {
- for (ItemColor color : ItemColor.values()) {
- if (
- variant == GagVariant.TAPE_GAG ||
- (color != ItemColor.CAUTION && color != ItemColor.CLEAR)
- ) {
- ItemStack stack = DataDrivenBondageItem.createStack(
- new net.minecraft.resources.ResourceLocation("tiedup", variant.getRegistryName())
- );
- KidnapperItemSelector.applyColor(stack, color);
- items.add(stack);
- }
- }
- } else {
- items.add(DataDrivenBondageItem.createStack(
- new net.minecraft.resources.ResourceLocation("tiedup", variant.getRegistryName())
- ));
- }
- }
-
- // All blindfolds
- for (BlindfoldVariant variant : BlindfoldVariant.values()) {
- if (variant.supportsColor()) {
- for (ItemColor color : ItemColor.values()) {
- if (
- color != ItemColor.CAUTION && color != ItemColor.CLEAR
- ) {
- ItemStack stack = DataDrivenBondageItem.createStack(
- new net.minecraft.resources.ResourceLocation("tiedup", variant.getRegistryName())
- );
- KidnapperItemSelector.applyColor(stack, color);
- items.add(stack);
- }
- }
- } else {
- items.add(DataDrivenBondageItem.createStack(
- new net.minecraft.resources.ResourceLocation("tiedup", variant.getRegistryName())
- ));
- }
- }
-
- // Earplugs - no color support
- for (EarplugsVariant variant : EarplugsVariant.values()) {
- items.add(DataDrivenBondageItem.createStack(
- new net.minecraft.resources.ResourceLocation("tiedup", variant.getRegistryName())
- ));
- }
-
- // Mittens - no color support
- for (MittensVariant variant : MittensVariant.values()) {
- items.add(DataDrivenBondageItem.createStack(
- new net.minecraft.resources.ResourceLocation("tiedup", variant.getRegistryName())
- ));
+ // All data-driven bondage items (binds, gags, blindfolds, earplugs, mittens, collars, etc.)
+ for (com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemDefinition def :
+ com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemRegistry.getAll()) {
+ items.add(DataDrivenBondageItem.createStack(def.id()));
}
// Knives - no color support
@@ -699,16 +627,11 @@ public class EntityKidnapperMerchant extends EntityKidnapperElite {
items.add(new ItemStack(ModItems.getKnife(variant)));
}
- // Complex items
- items.add(new ItemStack(ModItems.CLASSIC_COLLAR.get()));
- items.add(new ItemStack(ModItems.SHOCK_COLLAR.get()));
- items.add(new ItemStack(ModItems.GPS_COLLAR.get()));
+ // Tools
items.add(new ItemStack(ModItems.WHIP.get()));
// BLACKLIST: TASER (too powerful)
// BLACKLIST: LOCKPICK (now in guaranteed utilities)
// BLACKLIST: MASTER_KEY (too OP - unlocks everything)
- items.add(new ItemStack(ModItems.MEDICAL_GAG.get()));
- items.add(new ItemStack(ModItems.HOOD.get()));
items.add(new ItemStack(ModItems.CLOTHES.get()));
return items;
@@ -754,13 +677,13 @@ public class EntityKidnapperMerchant extends EntityKidnapperElite {
Item i = item.getItem();
// Tier 4: GPS collar
- if (i == ModItems.GPS_COLLAR.get()) {
+ if (com.tiedup.remake.v2.bondage.CollarHelper.hasGPS(item)) {
return 4;
}
// Tier 3: Shock collar, taser, master key
if (
- i == ModItems.SHOCK_COLLAR.get() ||
+ com.tiedup.remake.v2.bondage.CollarHelper.canShock(item) ||
i == ModItems.TASER.get() ||
i == ModItems.MASTER_KEY.get()
) {
@@ -769,11 +692,9 @@ public class EntityKidnapperMerchant extends EntityKidnapperElite {
// Tier 2: Collars, whip, tools, complex items, clothes
if (
- i == ModItems.CLASSIC_COLLAR.get() ||
+ com.tiedup.remake.v2.bondage.CollarHelper.isCollar(item) ||
i == ModItems.WHIP.get() ||
i == ModItems.LOCKPICK.get() ||
- i == ModItems.MEDICAL_GAG.get() ||
- i == ModItems.HOOD.get() ||
i instanceof GenericClothes
) {
return 2;
diff --git a/src/main/java/com/tiedup/remake/entities/KidnapperCaptureEquipment.java b/src/main/java/com/tiedup/remake/entities/KidnapperCaptureEquipment.java
index dce8890..c941c5f 100644
--- a/src/main/java/com/tiedup/remake/entities/KidnapperCaptureEquipment.java
+++ b/src/main/java/com/tiedup/remake/entities/KidnapperCaptureEquipment.java
@@ -115,7 +115,8 @@ public class KidnapperCaptureEquipment {
@Nullable
public ItemStack getCollarItem() {
// Kidnappers always have a shock collar to mark their captives
- return new ItemStack(ModItems.SHOCK_COLLAR.get());
+ return com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem.createStack(
+ new net.minecraft.resources.ResourceLocation("tiedup", "shock_collar"));
}
// HELD ITEM MANAGEMENT
diff --git a/src/main/java/com/tiedup/remake/entities/KidnapperItemSelector.java b/src/main/java/com/tiedup/remake/entities/KidnapperItemSelector.java
index 75a655f..43a2c17 100644
--- a/src/main/java/com/tiedup/remake/entities/KidnapperItemSelector.java
+++ b/src/main/java/com/tiedup/remake/entities/KidnapperItemSelector.java
@@ -12,6 +12,8 @@ import org.jetbrains.annotations.Nullable;
* Helper class for selecting themed items for kidnappers.
* Handles probability-based item selection and color application.
*
+ * All bondage items are now created via DataDrivenBondageItem.createStack().
+ *
* Item selection order (most to least common):
* 1. Bind (arms) - 100% (always)
* 2. Gag - 50%
@@ -79,33 +81,10 @@ public class KidnapperItemSelector {
this.blindfold = blindfold;
}
- /**
- * Check if this selection has a gag.
- */
- public boolean hasGag() {
- return !gag.isEmpty();
- }
-
- /**
- * Check if this selection has mittens.
- */
- public boolean hasMittens() {
- return !mittens.isEmpty();
- }
-
- /**
- * Check if this selection has earplugs.
- */
- public boolean hasEarplugs() {
- return !earplugs.isEmpty();
- }
-
- /**
- * Check if this selection has a blindfold.
- */
- public boolean hasBlindfold() {
- return !blindfold.isEmpty();
- }
+ public boolean hasGag() { return !gag.isEmpty(); }
+ public boolean hasMittens() { return !mittens.isEmpty(); }
+ public boolean hasEarplugs() { return !earplugs.isEmpty(); }
+ public boolean hasBlindfold() { return !blindfold.isEmpty(); }
}
/**
@@ -129,16 +108,6 @@ public class KidnapperItemSelector {
return selectItems(false, true);
}
- /**
- * Calculate adjusted probability based on kidnapper type.
- *
- * @param baseProb Base probability for the item
- * @param eliteBonus Bonus probability for elite kidnappers
- * @param archerPenalty Penalty probability for archer kidnappers
- * @param isElite Whether the kidnapper is elite
- * @param isArcher Whether the kidnapper is an archer
- * @return Adjusted probability
- */
private static double getAdjustedProbability(
double baseProb,
double eliteBonus,
@@ -152,9 +121,6 @@ public class KidnapperItemSelector {
return prob;
}
- /**
- * Internal item selection logic.
- */
private static SelectionResult selectItems(
boolean isElite,
boolean isArcher
@@ -163,132 +129,64 @@ public class KidnapperItemSelector {
KidnapperTheme theme = KidnapperTheme.getRandomWeighted();
// 2. Select color (if theme supports it)
- // Filter out colors that don't have textures for this theme's bind
ItemColor color = theme.supportsColor()
- ? getValidColorForBind(theme.getBind())
+ ? ItemColor.getRandomStandard()
: null;
// 3. Create bind (always)
- ItemStack bind = createBind(theme.getBind(), color);
+ ItemStack bind = createItemById(theme.getBindId(), color);
- // 4. Roll for gag (randomly selected from theme's compatible gags)
+ // 4. Roll for gag
ItemStack gag = ItemStack.EMPTY;
double gagProb = getAdjustedProbability(
- PROB_GAG,
- ELITE_GAG_BONUS,
- ARCHER_GAG_PENALTY,
- isElite,
- isArcher
+ PROB_GAG, ELITE_GAG_BONUS, ARCHER_GAG_PENALTY, isElite, isArcher
);
if (RANDOM.nextDouble() < gagProb) {
- gag = createGag(theme.getRandomGag(), color);
+ gag = createItemById(theme.getRandomGagId(), color);
}
- // 5. Roll for mittens (same for all themes)
+ // 5. Roll for mittens
ItemStack mittens = ItemStack.EMPTY;
double mittensProb = getAdjustedProbability(
- PROB_MITTENS,
- ELITE_MITTENS_BONUS,
- ARCHER_MITTENS_PENALTY,
- isElite,
- isArcher
+ PROB_MITTENS, ELITE_MITTENS_BONUS, ARCHER_MITTENS_PENALTY, isElite, isArcher
);
if (RANDOM.nextDouble() < mittensProb) {
mittens = createMittens();
}
- // 6. Roll for earplugs (same for all themes)
+ // 6. Roll for earplugs
ItemStack earplugs = ItemStack.EMPTY;
double earplugsProb = getAdjustedProbability(
- PROB_EARPLUGS,
- ELITE_EARPLUGS_BONUS,
- ARCHER_EARPLUGS_PENALTY,
- isElite,
- isArcher
+ PROB_EARPLUGS, ELITE_EARPLUGS_BONUS, ARCHER_EARPLUGS_PENALTY, isElite, isArcher
);
if (RANDOM.nextDouble() < earplugsProb) {
earplugs = createEarplugs();
}
- // 7. Roll for blindfold (last, most restrictive - randomly selected)
+ // 7. Roll for blindfold
ItemStack blindfold = ItemStack.EMPTY;
double blindfoldProb = getAdjustedProbability(
- PROB_BLINDFOLD,
- ELITE_BLINDFOLD_BONUS,
- ARCHER_BLINDFOLD_PENALTY,
- isElite,
- isArcher
+ PROB_BLINDFOLD, ELITE_BLINDFOLD_BONUS, ARCHER_BLINDFOLD_PENALTY, isElite, isArcher
);
if (theme.hasBlindfolds() && RANDOM.nextDouble() < blindfoldProb) {
- blindfold = createBlindfold(theme.getRandomBlindfold(), color);
+ blindfold = createItemById(theme.getRandomBlindfoldId(), color);
}
return new SelectionResult(
- theme,
- color,
- bind,
- gag,
- mittens,
- earplugs,
- blindfold
+ theme, color, bind, gag, mittens, earplugs, blindfold
);
}
// ITEM CREATION METHODS
/**
- * Create a bind ItemStack with optional color.
+ * Create a data-driven bondage item by registry name, with optional color.
*/
- public static ItemStack createBind(
- BindVariant variant,
- @Nullable ItemColor color
- ) {
+ public static ItemStack createItemById(String id, @Nullable ItemColor color) {
ItemStack stack = DataDrivenBondageItem.createStack(
- new ResourceLocation("tiedup", variant.getRegistryName())
+ new ResourceLocation("tiedup", id)
);
- if (color != null && variant.supportsColor()) {
- applyColor(stack, color);
- }
- return stack;
- }
-
- /**
- * Create a gag ItemStack with optional color.
- * Validates that the color has a texture for this gag variant.
- */
- public static ItemStack createGag(
- GagVariant variant,
- @Nullable ItemColor color
- ) {
- ItemStack stack = DataDrivenBondageItem.createStack(
- new ResourceLocation("tiedup", variant.getRegistryName())
- );
- if (
- color != null &&
- variant.supportsColor() &&
- isColorValidForGag(color, variant)
- ) {
- applyColor(stack, color);
- }
- return stack;
- }
-
- /**
- * Create a blindfold ItemStack with optional color.
- * Validates that the color has a texture for this blindfold variant.
- */
- public static ItemStack createBlindfold(
- BlindfoldVariant variant,
- @Nullable ItemColor color
- ) {
- ItemStack stack = DataDrivenBondageItem.createStack(
- new ResourceLocation("tiedup", variant.getRegistryName())
- );
- if (
- color != null &&
- variant.supportsColor() &&
- isColorValidForBlindfold(color, variant)
- ) {
+ if (color != null) {
applyColor(stack, color);
}
return stack;
@@ -296,7 +194,6 @@ public class KidnapperItemSelector {
/**
* Create mittens ItemStack.
- * Mittens don't have color variants.
*/
public static ItemStack createMittens() {
return DataDrivenBondageItem.createStack(
@@ -306,7 +203,6 @@ public class KidnapperItemSelector {
/**
* Create earplugs ItemStack.
- * Earplugs don't have color variants.
*/
public static ItemStack createEarplugs() {
return DataDrivenBondageItem.createStack(
@@ -321,7 +217,6 @@ public class KidnapperItemSelector {
/**
* Apply color NBT to an ItemStack.
- * Sets both the ItemColor name and CustomModelData for model selection.
*/
public static void applyColor(ItemStack stack, ItemColor color) {
if (stack.isEmpty() || color == null) return;
@@ -351,141 +246,9 @@ public class KidnapperItemSelector {
/**
* Get the texture suffix for an item's color.
- * Example: "ropes" + "_red" = "ropes_red"
- * @return The color suffix (e.g., "_red"), or empty string if no color
*/
public static String getColorSuffix(ItemStack stack) {
ItemColor color = getColor(stack);
return color != null ? "_" + color.getName() : "";
}
-
- // COLOR VALIDATION
-
- /**
- * Get a random color that has a texture for the given bind variant.
- * Excludes colors that don't have textures for specific variants.
- */
- public static ItemColor getValidColorForBind(BindVariant variant) {
- ItemColor color;
- int attempts = 0;
- do {
- color = ItemColor.getRandomStandard();
- attempts++;
- // Prevent infinite loop
- if (attempts > 50) break;
- } while (!isColorValidForBind(color, variant));
- return color;
- }
-
- /**
- * Check if a color has a texture for the given bind variant.
- * Returns false for colors without textures.
- */
- public static boolean isColorValidForBind(
- ItemColor color,
- BindVariant variant
- ) {
- if (color == null || variant == null) return true;
-
- // BROWN doesn't have textures for ROPES and SHIBARI
- if (
- color == ItemColor.BROWN &&
- (variant == BindVariant.ROPES || variant == BindVariant.SHIBARI)
- ) {
- return false;
- }
-
- // GRAY doesn't have texture for DUCT_TAPE
- if (color == ItemColor.GRAY && variant == BindVariant.DUCT_TAPE) {
- return false;
- }
-
- return true;
- }
-
- /**
- * Check if a color has a texture for the given gag variant.
- */
- public static boolean isColorValidForGag(
- ItemColor color,
- GagVariant variant
- ) {
- if (color == null || variant == null) return true;
-
- // GRAY doesn't have texture for TAPE_GAG
- if (color == ItemColor.GRAY && variant == GagVariant.TAPE_GAG) {
- return false;
- }
-
- // WHITE doesn't have texture for CLOTH_GAG and CLEAVE_GAG
- if (
- color == ItemColor.WHITE &&
- (variant == GagVariant.CLOTH_GAG ||
- variant == GagVariant.CLEAVE_GAG)
- ) {
- return false;
- }
-
- // RED doesn't have texture for BALL_GAG and BALL_GAG_STRAP
- if (
- color == ItemColor.RED &&
- (variant == GagVariant.BALL_GAG ||
- variant == GagVariant.BALL_GAG_STRAP)
- ) {
- return false;
- }
-
- return true;
- }
-
- /**
- * Check if a color has a texture for the given blindfold variant.
- */
- public static boolean isColorValidForBlindfold(
- ItemColor color,
- BlindfoldVariant variant
- ) {
- if (color == null || variant == null) return true;
-
- // BLACK doesn't have texture for CLASSIC or MASK blindfolds
- if (
- color == ItemColor.BLACK &&
- (variant == BlindfoldVariant.CLASSIC ||
- variant == BlindfoldVariant.MASK)
- ) {
- return false;
- }
-
- return true;
- }
-
- /**
- * Get a random color that has a texture for the given gag variant.
- */
- public static ItemColor getValidColorForGag(GagVariant variant) {
- ItemColor color;
- int attempts = 0;
- do {
- color = ItemColor.getRandomStandard();
- attempts++;
- if (attempts > 50) break;
- } while (!isColorValidForGag(color, variant));
- return color;
- }
-
- /**
- * Get a random color that has a texture for the given blindfold variant.
- */
- public static ItemColor getValidColorForBlindfold(
- BlindfoldVariant variant
- ) {
- ItemColor color;
- int attempts = 0;
- do {
- color = ItemColor.getRandomStandard();
- attempts++;
- if (attempts > 50) break;
- } while (!isColorValidForBlindfold(color, variant));
- return color;
- }
}
diff --git a/src/main/java/com/tiedup/remake/entities/KidnapperJobManager.java b/src/main/java/com/tiedup/remake/entities/KidnapperJobManager.java
index 1914b32..92fe3d4 100644
--- a/src/main/java/com/tiedup/remake/entities/KidnapperJobManager.java
+++ b/src/main/java/com/tiedup/remake/entities/KidnapperJobManager.java
@@ -161,7 +161,8 @@ public class KidnapperJobManager {
);
// Put a shock collar on the worker AFTER untie/free
- ItemStack shockCollar = new ItemStack(ModItems.SHOCK_COLLAR_AUTO.get());
+ ItemStack shockCollar = com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem.createStack(
+ new net.minecraft.resources.ResourceLocation("tiedup", "shock_collar_auto"));
// Add kidnapper as owner so the collar is linked
CollarHelper.addOwner(
shockCollar,
diff --git a/src/main/java/com/tiedup/remake/entities/KidnapperTheme.java b/src/main/java/com/tiedup/remake/entities/KidnapperTheme.java
index 408005c..4dcadee 100644
--- a/src/main/java/com/tiedup/remake/entities/KidnapperTheme.java
+++ b/src/main/java/com/tiedup/remake/entities/KidnapperTheme.java
@@ -1,42 +1,33 @@
package com.tiedup.remake.entities;
-import com.tiedup.remake.items.base.BindVariant;
-import com.tiedup.remake.items.base.BlindfoldVariant;
-import com.tiedup.remake.items.base.GagVariant;
import java.util.Random;
/**
* Defines themed item sets for kidnappers.
- * Each theme groups compatible binds, gags, and blindfolds.
+ * Each theme groups compatible binds, gags, and blindfolds by registry name.
*
* Themes are selected randomly with weighted probabilities.
* Higher weight = more common theme.
*
* Note: Natural themes (SLIME, VINE, WEB) are reserved for monsters.
+ *
+ * Registry names correspond to data-driven bondage item IDs in DataDrivenItemRegistry.
*/
public enum KidnapperTheme {
// === ROPE THEMES (most common) ===
ROPE(
- BindVariant.ROPES,
- new GagVariant[] {
- GagVariant.ROPES_GAG,
- GagVariant.CLOTH_GAG,
- GagVariant.CLEAVE_GAG,
- },
- new BlindfoldVariant[] { BlindfoldVariant.CLASSIC },
+ "ropes",
+ new String[] { "ropes_gag", "cloth_gag", "cleave_gag" },
+ new String[] { "classic_blindfold" },
true, // supportsColor
30 // weight (spawn probability)
),
SHIBARI(
- BindVariant.SHIBARI,
- new GagVariant[] {
- GagVariant.ROPES_GAG,
- GagVariant.CLOTH_GAG,
- GagVariant.RIBBON_GAG,
- },
- new BlindfoldVariant[] { BlindfoldVariant.CLASSIC },
+ "shibari",
+ new String[] { "ropes_gag", "cloth_gag", "ribbon_gag" },
+ new String[] { "classic_blindfold" },
true,
15
),
@@ -44,9 +35,9 @@ public enum KidnapperTheme {
// === TAPE THEME ===
TAPE(
- BindVariant.DUCT_TAPE,
- new GagVariant[] { GagVariant.TAPE_GAG, GagVariant.WRAP_GAG },
- new BlindfoldVariant[] { BlindfoldVariant.MASK },
+ "duct_tape",
+ new String[] { "tape_gag", "wrap_gag" },
+ new String[] { "blindfold_mask" },
true,
20
),
@@ -54,13 +45,9 @@ public enum KidnapperTheme {
// === LEATHER/BDSM THEME ===
LEATHER(
- BindVariant.LEATHER_STRAPS,
- new GagVariant[] {
- GagVariant.BALL_GAG,
- GagVariant.BALL_GAG_STRAP,
- GagVariant.PANEL_GAG,
- },
- new BlindfoldVariant[] { BlindfoldVariant.MASK },
+ "leather_straps",
+ new String[] { "ball_gag", "ball_gag_strap", "panel_gag" },
+ new String[] { "blindfold_mask" },
false,
15
),
@@ -68,12 +55,9 @@ public enum KidnapperTheme {
// === CHAIN THEME ===
CHAIN(
- BindVariant.CHAIN,
- new GagVariant[] {
- GagVariant.CHAIN_PANEL_GAG,
- GagVariant.BALL_GAG_STRAP,
- },
- new BlindfoldVariant[] { BlindfoldVariant.MASK },
+ "chain",
+ new String[] { "chain_panel_gag", "ball_gag_strap" },
+ new String[] { "blindfold_mask" },
false,
10
),
@@ -81,13 +65,9 @@ public enum KidnapperTheme {
// === MEDICAL THEME ===
MEDICAL(
- BindVariant.MEDICAL_STRAPS,
- new GagVariant[] {
- GagVariant.TUBE_GAG,
- GagVariant.SPONGE_GAG,
- GagVariant.BALL_GAG,
- },
- new BlindfoldVariant[] { BlindfoldVariant.MASK },
+ "medical_straps",
+ new String[] { "tube_gag", "sponge_gag", "ball_gag" },
+ new String[] { "blindfold_mask" },
false,
8
),
@@ -95,9 +75,9 @@ public enum KidnapperTheme {
// === SCI-FI/BEAM THEME ===
BEAM(
- BindVariant.BEAM_CUFFS,
- new GagVariant[] { GagVariant.BEAM_PANEL_GAG, GagVariant.LATEX_GAG },
- new BlindfoldVariant[] { BlindfoldVariant.MASK },
+ "beam_cuffs",
+ new String[] { "beam_panel_gag", "latex_gag" },
+ new String[] { "blindfold_mask" },
false,
5
),
@@ -105,9 +85,9 @@ public enum KidnapperTheme {
// === LATEX THEME (rare) ===
LATEX(
- BindVariant.LATEX_SACK,
- new GagVariant[] { GagVariant.LATEX_GAG, GagVariant.TUBE_GAG },
- new BlindfoldVariant[] { BlindfoldVariant.MASK },
+ "latex_sack",
+ new String[] { "latex_gag", "tube_gag" },
+ new String[] { "blindfold_mask" },
false,
3
),
@@ -115,13 +95,9 @@ public enum KidnapperTheme {
// === ASYLUM THEME (rare) ===
ASYLUM(
- BindVariant.STRAITJACKET,
- new GagVariant[] {
- GagVariant.BITE_GAG,
- GagVariant.SPONGE_GAG,
- GagVariant.BALL_GAG,
- },
- new BlindfoldVariant[] { BlindfoldVariant.MASK },
+ "straitjacket",
+ new String[] { "bite_gag", "sponge_gag", "ball_gag" },
+ new String[] { "blindfold_mask" },
false,
5
),
@@ -129,9 +105,9 @@ public enum KidnapperTheme {
// === RIBBON THEME (cute/playful) ===
RIBBON(
- BindVariant.RIBBON,
- new GagVariant[] { GagVariant.RIBBON_GAG, GagVariant.CLOTH_GAG },
- new BlindfoldVariant[] { BlindfoldVariant.CLASSIC },
+ "ribbon",
+ new String[] { "ribbon_gag", "cloth_gag" },
+ new String[] { "classic_blindfold" },
false,
8
),
@@ -139,54 +115,54 @@ public enum KidnapperTheme {
// === WRAP THEME ===
WRAP(
- BindVariant.WRAP,
- new GagVariant[] { GagVariant.WRAP_GAG, GagVariant.TAPE_GAG },
- new BlindfoldVariant[] { BlindfoldVariant.MASK },
+ "wrap",
+ new String[] { "wrap_gag", "tape_gag" },
+ new String[] { "blindfold_mask" },
false,
5
);
private static final Random RANDOM = new Random();
- private final BindVariant bind;
- private final GagVariant[] gags;
- private final BlindfoldVariant[] blindfolds;
+ private final String bindId;
+ private final String[] gagIds;
+ private final String[] blindfoldIds;
private final boolean supportsColor;
private final int weight;
KidnapperTheme(
- BindVariant bind,
- GagVariant[] gags,
- BlindfoldVariant[] blindfolds,
+ String bindId,
+ String[] gagIds,
+ String[] blindfoldIds,
boolean supportsColor,
int weight
) {
- this.bind = bind;
- this.gags = gags;
- this.blindfolds = blindfolds;
+ this.bindId = bindId;
+ this.gagIds = gagIds;
+ this.blindfoldIds = blindfoldIds;
this.supportsColor = supportsColor;
this.weight = weight;
}
/**
- * Get the primary bind for this theme.
+ * Get the primary bind registry name for this theme.
*/
- public BindVariant getBind() {
- return bind;
+ public String getBindId() {
+ return bindId;
}
/**
- * Get compatible gags for this theme (ordered by preference).
+ * Get compatible gag IDs for this theme (ordered by preference).
*/
- public GagVariant[] getGags() {
- return gags;
+ public String[] getGagIds() {
+ return gagIds;
}
/**
- * Get compatible blindfolds for this theme.
+ * Get compatible blindfold IDs for this theme.
*/
- public BlindfoldVariant[] getBlindfolds() {
- return blindfolds;
+ public String[] getBlindfoldIds() {
+ return blindfoldIds;
}
/**
@@ -206,41 +182,40 @@ public enum KidnapperTheme {
}
/**
- * Get primary gag (first in list).
- * Used when only one gag is selected.
+ * Get primary gag ID (first in list).
*/
- public GagVariant getPrimaryGag() {
- return gags.length > 0 ? gags[0] : GagVariant.BALL_GAG;
+ public String getPrimaryGagId() {
+ return gagIds.length > 0 ? gagIds[0] : "ball_gag";
}
/**
- * Get a random gag from this theme's compatible list.
+ * Get a random gag ID from this theme's compatible list.
*/
- public GagVariant getRandomGag() {
- if (gags.length == 0) return GagVariant.BALL_GAG;
- return gags[RANDOM.nextInt(gags.length)];
+ public String getRandomGagId() {
+ if (gagIds.length == 0) return "ball_gag";
+ return gagIds[RANDOM.nextInt(gagIds.length)];
}
/**
- * Get primary blindfold (first in list).
+ * Get primary blindfold ID (first in list).
*/
- public BlindfoldVariant getPrimaryBlindfold() {
- return blindfolds.length > 0 ? blindfolds[0] : BlindfoldVariant.CLASSIC;
+ public String getPrimaryBlindfoldId() {
+ return blindfoldIds.length > 0 ? blindfoldIds[0] : "classic_blindfold";
}
/**
- * Get a random blindfold from this theme's compatible list.
+ * Get a random blindfold ID from this theme's compatible list.
*/
- public BlindfoldVariant getRandomBlindfold() {
- if (blindfolds.length == 0) return BlindfoldVariant.CLASSIC;
- return blindfolds[RANDOM.nextInt(blindfolds.length)];
+ public String getRandomBlindfoldId() {
+ if (blindfoldIds.length == 0) return "classic_blindfold";
+ return blindfoldIds[RANDOM.nextInt(blindfoldIds.length)];
}
/**
* Check if this theme has any blindfolds.
*/
public boolean hasBlindfolds() {
- return blindfolds.length > 0;
+ return blindfoldIds.length > 0;
}
/**
diff --git a/src/main/java/com/tiedup/remake/entities/ai/maid/MaidDeliverCaptiveGoal.java b/src/main/java/com/tiedup/remake/entities/ai/maid/MaidDeliverCaptiveGoal.java
index 4b642db..8b8aabd 100644
--- a/src/main/java/com/tiedup/remake/entities/ai/maid/MaidDeliverCaptiveGoal.java
+++ b/src/main/java/com/tiedup/remake/entities/ai/maid/MaidDeliverCaptiveGoal.java
@@ -229,20 +229,21 @@ public class MaidDeliverCaptiveGoal extends Goal {
kidnappedState.getEquipment(BodyRegionV2.NECK);
if (
!collar.isEmpty() &&
- collar.getItem() instanceof
- com.tiedup.remake.items.base.ItemCollar collarItem
+ com.tiedup.remake.v2.bondage.CollarHelper.isCollar(collar)
) {
for (java.util.UUID ownerId : new java.util.ArrayList<>(
- collarItem.getOwners(collar)
+ com.tiedup.remake.v2.bondage.CollarHelper.getOwners(collar)
)) {
- collarItem.removeOwner(collar, ownerId);
+ com.tiedup.remake.v2.bondage.CollarHelper.removeOwner(collar, ownerId);
}
- collarItem.addOwner(
+ com.tiedup.remake.v2.bondage.CollarHelper.addOwner(
collar,
buyerEntity.getUUID(),
buyerEntity.getName().getString()
);
- collarItem.setLocked(collar, false);
+ if (collar.getItem() instanceof com.tiedup.remake.items.base.ILockable lockable) {
+ lockable.setLocked(collar, false);
+ }
kidnappedState.equip(BodyRegionV2.NECK, collar);
if (
diff --git a/src/main/java/com/tiedup/remake/entities/ai/personality/NpcStruggleGoal.java b/src/main/java/com/tiedup/remake/entities/ai/personality/NpcStruggleGoal.java
index ced4324..c009bfd 100644
--- a/src/main/java/com/tiedup/remake/entities/ai/personality/NpcStruggleGoal.java
+++ b/src/main/java/com/tiedup/remake/entities/ai/personality/NpcStruggleGoal.java
@@ -305,11 +305,8 @@ public class NpcStruggleGoal extends Goal {
ItemStack collar = npc.getEquipment(BodyRegionV2.NECK);
if (collar.isEmpty()) return false;
- if (
- collar.getItem() instanceof
- com.tiedup.remake.items.base.ItemCollar collarItem
- ) {
- List ownerUUIDs = collarItem.getOwners(collar);
+ if (com.tiedup.remake.v2.bondage.CollarHelper.isCollar(collar)) {
+ List ownerUUIDs = com.tiedup.remake.v2.bondage.CollarHelper.getOwners(collar);
if (!ownerUUIDs.isEmpty()) {
// Check if any owner is nearby
List players = npc
@@ -338,11 +335,8 @@ public class NpcStruggleGoal extends Goal {
ItemStack collar = npc.getEquipment(BodyRegionV2.NECK);
if (collar.isEmpty()) return null;
- if (
- collar.getItem() instanceof
- com.tiedup.remake.items.base.ItemCollar collarItem
- ) {
- List ownerUUIDs = collarItem.getOwners(collar);
+ if (com.tiedup.remake.v2.bondage.CollarHelper.isCollar(collar)) {
+ List ownerUUIDs = com.tiedup.remake.v2.bondage.CollarHelper.getOwners(collar);
if (!ownerUUIDs.isEmpty()) {
return ownerUUIDs.get(0);
}
diff --git a/src/main/java/com/tiedup/remake/entities/armorstand/ArmorStandBondageHelper.java b/src/main/java/com/tiedup/remake/entities/armorstand/ArmorStandBondageHelper.java
index 8e8a2a1..b869b77 100644
--- a/src/main/java/com/tiedup/remake/entities/armorstand/ArmorStandBondageHelper.java
+++ b/src/main/java/com/tiedup/remake/entities/armorstand/ArmorStandBondageHelper.java
@@ -1,7 +1,7 @@
package com.tiedup.remake.entities.armorstand;
-import com.tiedup.remake.items.base.ItemBind;
import com.tiedup.remake.items.base.PoseType;
+import com.tiedup.remake.v2.bondage.PoseTypeHelper;
import com.tiedup.remake.v2.BodyRegionV2;
import java.util.Map;
import net.minecraft.core.Rotations;
@@ -185,11 +185,8 @@ public class ArmorStandBondageHelper {
// Save original pose if not already saved
saveOriginalPose(stand);
- // Get pose type from bind
- PoseType poseType = PoseType.STANDARD;
- if (bindStack.getItem() instanceof ItemBind bind) {
- poseType = bind.getPoseType();
- }
+ // Get pose type from bind (V2 data-driven)
+ PoseType poseType = PoseTypeHelper.getPoseType(bindStack);
// Apply pose
applyBondagePose(stand, poseType);
diff --git a/src/main/java/com/tiedup/remake/entities/damsel/components/NpcEquipmentManager.java b/src/main/java/com/tiedup/remake/entities/damsel/components/NpcEquipmentManager.java
index 2d4d1c5..ee0b986 100644
--- a/src/main/java/com/tiedup/remake/entities/damsel/components/NpcEquipmentManager.java
+++ b/src/main/java/com/tiedup/remake/entities/damsel/components/NpcEquipmentManager.java
@@ -116,15 +116,13 @@ public class NpcEquipmentManager {
public boolean hasGaggingEffect() {
ItemStack gag = getCurrentGag();
if (gag.isEmpty()) return false;
- if (DataDrivenBondageItem.getComponent(gag, ComponentType.GAGGING, GaggingComponent.class) != null) return true;
- return gag.getItem() instanceof com.tiedup.remake.items.base.IHasGaggingEffect;
+ return DataDrivenBondageItem.getComponent(gag, ComponentType.GAGGING, GaggingComponent.class) != null;
}
public boolean hasBlindingEffect() {
ItemStack blindfold = getCurrentBlindfold();
if (blindfold.isEmpty()) return false;
- if (DataDrivenBondageItem.getComponent(blindfold, ComponentType.BLINDING, BlindingComponent.class) != null) return true;
- return blindfold.getItem() instanceof com.tiedup.remake.items.base.IHasBlindingEffect;
+ return DataDrivenBondageItem.getComponent(blindfold, ComponentType.BLINDING, BlindingComponent.class) != null;
}
public boolean hasKnives() {
diff --git a/src/main/java/com/tiedup/remake/entities/kidnapper/components/KidnapperAppearance.java b/src/main/java/com/tiedup/remake/entities/kidnapper/components/KidnapperAppearance.java
index 808459a..e5089f3 100644
--- a/src/main/java/com/tiedup/remake/entities/kidnapper/components/KidnapperAppearance.java
+++ b/src/main/java/com/tiedup/remake/entities/kidnapper/components/KidnapperAppearance.java
@@ -361,19 +361,19 @@ public class KidnapperAppearance {
this.itemSelection = new KidnapperItemSelector.SelectionResult(
this.currentTheme,
this.themeColor,
- KidnapperItemSelector.createBind(
- this.currentTheme.getBind(),
+ KidnapperItemSelector.createItemById(
+ this.currentTheme.getBindId(),
this.themeColor
),
- KidnapperItemSelector.createGag(
- this.currentTheme.getPrimaryGag(),
+ KidnapperItemSelector.createItemById(
+ this.currentTheme.getPrimaryGagId(),
this.themeColor
),
KidnapperItemSelector.createMittens(),
KidnapperItemSelector.createEarplugs(),
this.currentTheme.hasBlindfolds()
- ? KidnapperItemSelector.createBlindfold(
- this.currentTheme.getPrimaryBlindfold(),
+ ? KidnapperItemSelector.createItemById(
+ this.currentTheme.getPrimaryBlindfoldId(),
this.themeColor
)
: ItemStack.EMPTY
diff --git a/src/main/java/com/tiedup/remake/entities/master/components/MasterPetManager.java b/src/main/java/com/tiedup/remake/entities/master/components/MasterPetManager.java
index ddd5b01..9fb7325 100644
--- a/src/main/java/com/tiedup/remake/entities/master/components/MasterPetManager.java
+++ b/src/main/java/com/tiedup/remake/entities/master/components/MasterPetManager.java
@@ -119,8 +119,8 @@ public class MasterPetManager {
if (state == null) return;
// Create a choke collar for pet play
- ItemStack chokeCollar = new ItemStack(
- com.tiedup.remake.items.ModItems.CHOKE_COLLAR.get()
+ ItemStack chokeCollar = com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem.createStack(
+ new net.minecraft.resources.ResourceLocation("tiedup", "choke_collar")
);
// Configure for pet play BEFORE equipping
diff --git a/src/main/java/com/tiedup/remake/events/system/ChatEventHandler.java b/src/main/java/com/tiedup/remake/events/system/ChatEventHandler.java
index 96ff8d1..411e808 100644
--- a/src/main/java/com/tiedup/remake/events/system/ChatEventHandler.java
+++ b/src/main/java/com/tiedup/remake/events/system/ChatEventHandler.java
@@ -4,7 +4,6 @@ import com.tiedup.remake.core.SettingsAccessor;
import com.tiedup.remake.core.SystemMessageManager;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.dialogue.GagTalkManager;
-import com.tiedup.remake.items.base.ItemGag;
import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.util.GagMaterial;
import com.tiedup.remake.v2.bondage.component.ComponentType;
@@ -57,11 +56,10 @@ public class ChatEventHandler {
BodyRegionV2.MOUTH
);
- // V2: check gagging component, V1 fallback: instanceof ItemGag
+ // V2: check gagging component
GaggingComponent gaggingComp = DataDrivenBondageItem.getComponent(
gagStack, ComponentType.GAGGING, GaggingComponent.class);
- boolean isGagItem = gaggingComp != null
- || gagStack.getItem() instanceof ItemGag;
+ boolean isGagItem = gaggingComp != null;
if (!gagStack.isEmpty() && isGagItem) {
String originalMessage = event.getRawText();
@@ -70,9 +68,7 @@ public class ChatEventHandler {
if (gaggingComp != null) {
material = gaggingComp.getMaterial();
}
- if (material == null && gagStack.getItem() instanceof ItemGag gagItem) {
- material = gagItem.getGagMaterial();
- }
+ // material stays null if no component; GagTalkManager handles null → CLOTH fallback
// 1. Process the message through our GagTalkManager V2
Component muffledMessage = GagTalkManager.processGagMessage(
diff --git a/src/main/java/com/tiedup/remake/items/GenericBind.java b/src/main/java/com/tiedup/remake/items/GenericBind.java
deleted file mode 100644
index 360658e..0000000
--- a/src/main/java/com/tiedup/remake/items/GenericBind.java
+++ /dev/null
@@ -1,68 +0,0 @@
-package com.tiedup.remake.items;
-
-import com.tiedup.remake.items.base.BindVariant;
-import com.tiedup.remake.items.base.ItemBind;
-import com.tiedup.remake.items.base.PoseType;
-import net.minecraft.world.item.Item;
-
-/**
- * Generic bind item created from BindVariant enum.
- * Replaces individual bind classes (ItemRopes, ItemChain, ItemStraitjacket, etc.)
- *
- * Factory pattern: All bind variants are created using this single class.
- */
-public class GenericBind extends ItemBind {
-
- private final BindVariant variant;
-
- public GenericBind(BindVariant variant) {
- super(new Item.Properties().stacksTo(16));
- this.variant = variant;
- }
-
- @Override
- public String getItemName() {
- return variant.getItemName();
- }
-
- @Override
- public PoseType getPoseType() {
- return variant.getPoseType();
- }
-
- /**
- * Get the variant this bind was created from.
- */
- public BindVariant getVariant() {
- return variant;
- }
-
- /**
- * Get the default resistance value for this bind variant.
- * Note: Actual resistance is managed by GameRules, this is just the configured default.
- */
- public int getDefaultResistance() {
- return variant.getResistance();
- }
-
- /**
- * Check if this bind can have a padlock attached via anvil.
- * Adhesive (tape) and organic (slime, vine, web) binds cannot have padlocks.
- */
- @Override
- public boolean canAttachPadlock() {
- return switch (variant) {
- case DUCT_TAPE, SLIME, VINE_SEED, WEB_BIND -> false;
- default -> true;
- };
- }
-
- /**
- * Get the texture subfolder for this bind variant.
- * Issue #12 fix: Eliminates string checks in renderers.
- */
- @Override
- public String getTextureSubfolder() {
- return variant.getTextureSubfolder();
- }
-}
diff --git a/src/main/java/com/tiedup/remake/items/GenericBlindfold.java b/src/main/java/com/tiedup/remake/items/GenericBlindfold.java
deleted file mode 100644
index b030aae..0000000
--- a/src/main/java/com/tiedup/remake/items/GenericBlindfold.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package com.tiedup.remake.items;
-
-import com.tiedup.remake.items.base.BlindfoldVariant;
-import com.tiedup.remake.items.base.ItemBlindfold;
-import net.minecraft.world.item.Item;
-
-/**
- * Generic blindfold item created from BlindfoldVariant enum.
- * Replaces individual blindfold classes (ItemClassicBlindfold, ItemBlindfoldMask).
- *
- * Factory pattern: All blindfold variants are created using this single class.
- */
-public class GenericBlindfold extends ItemBlindfold {
-
- private final BlindfoldVariant variant;
-
- public GenericBlindfold(BlindfoldVariant variant) {
- super(new Item.Properties().stacksTo(16));
- this.variant = variant;
- }
-
- /**
- * Get the variant this blindfold was created from.
- */
- public BlindfoldVariant getVariant() {
- return variant;
- }
-
- /**
- * Get the texture subfolder for this blindfold variant.
- * Issue #12 fix: Eliminates string checks in renderers.
- */
- @Override
- public String getTextureSubfolder() {
- return variant.getTextureSubfolder();
- }
-}
diff --git a/src/main/java/com/tiedup/remake/items/GenericEarplugs.java b/src/main/java/com/tiedup/remake/items/GenericEarplugs.java
deleted file mode 100644
index 8d6d065..0000000
--- a/src/main/java/com/tiedup/remake/items/GenericEarplugs.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package com.tiedup.remake.items;
-
-import com.tiedup.remake.items.base.EarplugsVariant;
-import com.tiedup.remake.items.base.ItemEarplugs;
-import net.minecraft.world.item.Item;
-
-/**
- * Generic earplugs item created from EarplugsVariant enum.
- * Replaces individual earplugs classes (ItemClassicEarplugs).
- *
- * Factory pattern: All earplugs variants are created using this single class.
- */
-public class GenericEarplugs extends ItemEarplugs {
-
- private final EarplugsVariant variant;
-
- public GenericEarplugs(EarplugsVariant variant) {
- super(new Item.Properties().stacksTo(16));
- this.variant = variant;
- }
-
- /**
- * Get the variant this earplugs was created from.
- */
- public EarplugsVariant getVariant() {
- return variant;
- }
-
- /**
- * Get the texture subfolder for this earplugs variant.
- * Issue #12 fix: Eliminates string checks in renderers.
- */
- @Override
- public String getTextureSubfolder() {
- return variant.getTextureSubfolder();
- }
-}
diff --git a/src/main/java/com/tiedup/remake/items/GenericGag.java b/src/main/java/com/tiedup/remake/items/GenericGag.java
deleted file mode 100644
index 824f96e..0000000
--- a/src/main/java/com/tiedup/remake/items/GenericGag.java
+++ /dev/null
@@ -1,72 +0,0 @@
-package com.tiedup.remake.items;
-
-import com.tiedup.remake.items.base.GagVariant;
-import com.tiedup.remake.items.base.ItemGag;
-import net.minecraft.resources.ResourceLocation;
-import net.minecraft.world.item.Item;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * Generic gag item created from GagVariant enum.
- * Replaces individual gag classes (ItemBallGag, ItemTapeGag, etc.)
- *
- * Factory pattern: All gag variants are created using this single class.
- *
- * Note: ItemMedicalGag is NOT handled by this class because it implements
- * IHasBlindingEffect (combo item with special behavior).
- */
-public class GenericGag extends ItemGag {
-
- private final GagVariant variant;
-
- public GenericGag(GagVariant variant) {
- super(new Item.Properties().stacksTo(16), variant.getMaterial());
- this.variant = variant;
- }
-
- /**
- * Get the variant this gag was created from.
- */
- public GagVariant getVariant() {
- return variant;
- }
-
- /**
- * Check if this gag can have a padlock attached via anvil.
- * Adhesive (tape) and organic (slime, vine, web) gags cannot have padlocks.
- */
- @Override
- public boolean canAttachPadlock() {
- return switch (variant) {
- case TAPE_GAG, SLIME_GAG, VINE_GAG, WEB_GAG -> false;
- default -> true;
- };
- }
-
- /**
- * Get the texture subfolder for this gag variant.
- * Issue #12 fix: Eliminates string checks in renderers.
- */
- @Override
- public String getTextureSubfolder() {
- return variant.getTextureSubfolder();
- }
-
- /**
- * Check if this gag uses a 3D OBJ model.
- */
- @Override
- public boolean uses3DModel() {
- return variant.uses3DModel();
- }
-
- /**
- * Get the 3D model location for this gag.
- */
- @Override
- @Nullable
- public ResourceLocation get3DModelLocation() {
- String path = variant.getModelPath();
- return path != null ? ResourceLocation.tryParse(path) : null;
- }
-}
diff --git a/src/main/java/com/tiedup/remake/items/GenericKnife.java b/src/main/java/com/tiedup/remake/items/GenericKnife.java
index aac49d0..7018f6d 100644
--- a/src/main/java/com/tiedup/remake/items/GenericKnife.java
+++ b/src/main/java/com/tiedup/remake/items/GenericKnife.java
@@ -3,8 +3,8 @@ package com.tiedup.remake.items;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.base.IKnife;
import com.tiedup.remake.items.base.ILockable;
-import com.tiedup.remake.items.base.ItemBind;
import com.tiedup.remake.items.base.KnifeVariant;
+import com.tiedup.remake.v2.bondage.BindModeHelper;
import com.tiedup.remake.network.sync.SyncManager;
import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.state.PlayerBindState;
@@ -306,10 +306,7 @@ public class GenericKnife extends Item implements IKnife {
player,
BodyRegionV2.ARMS
);
- if (
- bindStack.isEmpty() ||
- !(bindStack.getItem() instanceof ItemBind bind)
- ) {
+ if (bindStack.isEmpty() || !BindModeHelper.isBindItem(bindStack)) {
player.stopUsingItem();
return;
}
diff --git a/src/main/java/com/tiedup/remake/items/GenericMittens.java b/src/main/java/com/tiedup/remake/items/GenericMittens.java
deleted file mode 100644
index 5b25a61..0000000
--- a/src/main/java/com/tiedup/remake/items/GenericMittens.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package com.tiedup.remake.items;
-
-import com.tiedup.remake.items.base.ItemMittens;
-import com.tiedup.remake.items.base.MittensVariant;
-import net.minecraft.world.item.Item;
-
-/**
- * Generic mittens item created from MittensVariant enum.
- *
- * Factory pattern: All mittens variants are created using this single class.
- *
- */
-public class GenericMittens extends ItemMittens {
-
- private final MittensVariant variant;
-
- public GenericMittens(MittensVariant variant) {
- super(new Item.Properties().stacksTo(16));
- this.variant = variant;
- }
-
- /**
- * Get the variant this mittens was created from.
- */
- public MittensVariant getVariant() {
- return variant;
- }
-
- /**
- * Get the texture subfolder for this mittens variant.
- * Issue #12 fix: Eliminates string checks in renderers.
- */
- @Override
- public String getTextureSubfolder() {
- return variant.getTextureSubfolder();
- }
-}
diff --git a/src/main/java/com/tiedup/remake/items/ItemChokeCollar.java b/src/main/java/com/tiedup/remake/items/ItemChokeCollar.java
deleted file mode 100644
index b586381..0000000
--- a/src/main/java/com/tiedup/remake/items/ItemChokeCollar.java
+++ /dev/null
@@ -1,154 +0,0 @@
-package com.tiedup.remake.items;
-
-import com.tiedup.remake.items.base.ItemCollar;
-import com.tiedup.remake.items.bondage3d.IHas3DModelConfig;
-import com.tiedup.remake.items.bondage3d.Model3DConfig;
-import java.util.List;
-import java.util.Set;
-import net.minecraft.ChatFormatting;
-import net.minecraft.nbt.CompoundTag;
-import net.minecraft.network.chat.Component;
-import net.minecraft.resources.ResourceLocation;
-import net.minecraft.world.item.Item;
-import net.minecraft.world.item.ItemStack;
-import net.minecraft.world.item.TooltipFlag;
-import net.minecraft.world.level.Level;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * Choke Collar - Pet play collar used by Masters.
- *
- * Special feature: Can be put in "choke mode" which applies a drowning effect.
- * Used by Masters for punishment. The effect simulates choking by reducing air supply,
- * which triggers drowning damage if left active for too long.
- *
- * Mechanics:
- *
- * - When choking is active, the wearer's air supply decreases rapidly
- * - This creates the drowning effect (damage and bubble particles)
- * - Masters should deactivate the choke before the pet dies
- * - The choke is controlled by the Master's punishment system
- *
- *
- * @see com.tiedup.remake.entities.ai.master.MasterPunishGoal
- * @see com.tiedup.remake.events.restriction.PetPlayRestrictionHandler
- */
-public class ItemChokeCollar extends ItemCollar implements IHas3DModelConfig {
-
- private static final String NBT_CHOKING = "choking";
-
- private static final Model3DConfig CONFIG = new Model3DConfig(
- "tiedup:models/obj/choke_collar_leather/model.obj",
- "tiedup:models/obj/choke_collar_leather/texture.png",
- 0.0f,
- 1.47f,
- 0.0f, // Collar band centered at neck level
- 1.0f,
- 0.0f,
- 0.0f,
- 180.0f, // Flip Y to match rendering convention
- Set.of()
- );
-
- public ItemChokeCollar() {
- super(new Item.Properties());
- }
-
- @Override
- public String getItemName() {
- return "choke_collar";
- }
-
- /**
- * Check if choke mode is active.
- *
- * @param stack The collar ItemStack
- * @return true if choking is active
- */
- public boolean isChoking(ItemStack stack) {
- CompoundTag tag = stack.getTag();
- return tag != null && tag.getBoolean(NBT_CHOKING);
- }
-
- /**
- * Set choke mode on/off.
- * When active, applies drowning effect to wearer (handled by PetPlayRestrictionHandler).
- *
- * @param stack The collar ItemStack
- * @param choking true to activate choking, false to deactivate
- */
- public void setChoking(ItemStack stack, boolean choking) {
- stack.getOrCreateTag().putBoolean(NBT_CHOKING, choking);
- }
-
- /**
- * Check if collar is in pet play mode (from Master).
- *
- * @param stack The collar ItemStack
- * @return true if this is a pet play collar
- */
- public boolean isPetPlayMode(ItemStack stack) {
- CompoundTag tag = stack.getTag();
- return tag != null && tag.getBoolean("petPlayMode");
- }
-
- @Override
- public void appendHoverText(
- ItemStack stack,
- @Nullable Level level,
- List tooltip,
- TooltipFlag flag
- ) {
- super.appendHoverText(stack, level, tooltip, flag);
-
- // Show choke status
- if (isChoking(stack)) {
- tooltip.add(
- Component.literal("CHOKING ACTIVE!")
- .withStyle(ChatFormatting.DARK_RED)
- .withStyle(ChatFormatting.BOLD)
- );
- }
-
- // Show pet play mode status
- if (isPetPlayMode(stack)) {
- tooltip.add(
- Component.literal("Pet Play Mode").withStyle(
- ChatFormatting.LIGHT_PURPLE
- )
- );
- }
-
- // Description
- tooltip.add(
- Component.literal("A special collar used for pet play punishment")
- .withStyle(ChatFormatting.DARK_GRAY)
- .withStyle(ChatFormatting.ITALIC)
- );
- }
-
- /**
- * Choke collar cannot shock like shock collar.
- */
- @Override
- public boolean canShock() {
- return false;
- }
-
- // 3D Model Support
-
- @Override
- public boolean uses3DModel() {
- return true;
- }
-
- @Override
- public ResourceLocation get3DModelLocation() {
- return ResourceLocation.tryParse(CONFIG.objPath());
- }
-
- @Override
- public Model3DConfig getModelConfig() {
- return CONFIG;
- }
-}
diff --git a/src/main/java/com/tiedup/remake/items/ItemClassicCollar.java b/src/main/java/com/tiedup/remake/items/ItemClassicCollar.java
deleted file mode 100644
index 3fc120e..0000000
--- a/src/main/java/com/tiedup/remake/items/ItemClassicCollar.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.tiedup.remake.items;
-
-import com.tiedup.remake.items.base.ItemCollar;
-import net.minecraft.world.item.Item;
-
-/**
- * Classic Collar - Basic collar item
- * Standard collar for marking ownership.
- *
- * Based on original ItemCollar from 1.12.2
- * Note: Collars have maxStackSize of 1 (unique items)
- */
-public class ItemClassicCollar extends ItemCollar {
-
- public ItemClassicCollar() {
- super(
- new Item.Properties()
- // stacksTo(1) is set by ItemCollar base class
- );
- }
-}
diff --git a/src/main/java/com/tiedup/remake/items/ItemCommandWand.java b/src/main/java/com/tiedup/remake/items/ItemCommandWand.java
index ac70562..7e9e073 100644
--- a/src/main/java/com/tiedup/remake/items/ItemCommandWand.java
+++ b/src/main/java/com/tiedup/remake/items/ItemCommandWand.java
@@ -5,8 +5,8 @@ import com.tiedup.remake.cells.CellRegistryV2;
import com.tiedup.remake.core.SystemMessageManager;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.entities.EntityDamsel;
-import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.network.ModNetwork;
+import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.network.personality.PacketOpenCommandWandScreen;
import com.tiedup.remake.personality.HomeType;
import com.tiedup.remake.personality.JobExperience;
@@ -349,11 +349,11 @@ public class ItemCommandWand extends Item {
// Get collar and verify ownership
ItemStack collar = damsel.getEquipment(BodyRegionV2.NECK);
- if (!(collar.getItem() instanceof ItemCollar collarItem)) {
+ if (!CollarHelper.isCollar(collar)) {
return InteractionResult.PASS;
}
- if (!collarItem.getOwners(collar).contains(player.getUUID())) {
+ if (!CollarHelper.isOwner(collar, player)) {
SystemMessageManager.sendToPlayer(
player,
SystemMessageManager.MessageCategory.ERROR,
diff --git a/src/main/java/com/tiedup/remake/items/ItemGpsCollar.java b/src/main/java/com/tiedup/remake/items/ItemGpsCollar.java
deleted file mode 100644
index 5396c5e..0000000
--- a/src/main/java/com/tiedup/remake/items/ItemGpsCollar.java
+++ /dev/null
@@ -1,369 +0,0 @@
-package com.tiedup.remake.items;
-
-import com.tiedup.remake.state.IRestrainable;
-import com.tiedup.remake.util.KidnappedHelper;
-import java.util.ArrayList;
-import java.util.List;
-import net.minecraft.ChatFormatting;
-import net.minecraft.nbt.CompoundTag;
-import net.minecraft.nbt.ListTag;
-import net.minecraft.network.chat.Component;
-import net.minecraft.world.entity.Entity;
-import net.minecraft.world.entity.LivingEntity;
-import net.minecraft.world.item.ItemStack;
-import net.minecraft.world.item.TooltipFlag;
-import net.minecraft.world.level.Level;
-import org.jetbrains.annotations.Nullable;
-
-/**
-
- * GPS Collar - Advanced shock collar with tracking and safe zone features.
-
- *
-
- * Mechanics:
-
- *
-
- * - Safe Zones: Can store multiple coordinates (SafeSpots) where the wearer is allowed to be.
-
- * - Auto-Shock: If the wearer is outside ALL active safe zones, they are shocked at intervals.
-
- * - Master Warning: Masters receive an alert message when a safe zone violation is detected.
-
- * - Public Tracking: If enabled, allows anyone with a Locator to see distance and direction.
-
- *
-
- */
-
-public class ItemGpsCollar extends ItemShockCollar {
-
- private static final String NBT_PUBLIC_TRACKING = "publicTracking";
-
- private static final String NBT_GPS_ACTIVE = "gpsActive";
-
- private static final String NBT_SAFE_SPOTS = "gpsSafeSpots";
-
- private static final String NBT_SHOCK_INTERVAL = "gpsShockInterval";
-
- private static final String NBT_WARN_MASTERS = "warn_masters";
-
- private final int defaultInterval;
-
- public ItemGpsCollar() {
- this(200); // 10 seconds default
- }
-
- public ItemGpsCollar(int defaultInterval) {
- super();
- this.defaultInterval = defaultInterval;
- }
-
- @Override
- public boolean hasGPS() {
- return true;
- }
-
- /**
-
- * Renders detailed GPS status, safe zone list, and alert settings in the item tooltip.
-
- */
-
- @Override
- public void appendHoverText(
- ItemStack stack,
- @Nullable Level level,
- List tooltip,
- TooltipFlag flag
- ) {
- super.appendHoverText(stack, level, tooltip, flag);
-
- tooltip.add(
- Component.literal("GPS Enabled").withStyle(
- ChatFormatting.DARK_GREEN
- )
- );
-
- if (hasPublicTracking(stack)) {
- tooltip.add(
- Component.literal("Public Tracking Enabled").withStyle(
- ChatFormatting.GREEN
- )
- );
- }
-
- if (shouldWarnMasters(stack)) {
- tooltip.add(
- Component.literal("Alert Masters on Violation").withStyle(
- ChatFormatting.GOLD
- )
- );
- }
-
- List safeSpots = getSafeSpots(stack);
-
- if (!safeSpots.isEmpty()) {
- tooltip.add(
- Component.literal("GPS Shocks: ")
- .withStyle(ChatFormatting.GREEN)
- .append(
- Component.literal(
- isActive(stack) ? "ENABLED" : "DISABLED"
- ).withStyle(
- isActive(stack)
- ? ChatFormatting.RED
- : ChatFormatting.GRAY
- )
- )
- );
-
- tooltip.add(
- Component.literal(
- "Safe Spots (" + safeSpots.size() + "):"
- ).withStyle(ChatFormatting.GREEN)
- );
-
- for (int i = 0; i < safeSpots.size(); i++) {
- SafeSpot spot = safeSpots.get(i);
-
- tooltip.add(
- Component.literal(
- (spot.active ? "[+] " : "[-] ") +
- (i + 1) +
- ": " +
- spot.x +
- "," +
- spot.y +
- "," +
- spot.z +
- " (Range: " +
- spot.distance +
- "m)"
- ).withStyle(ChatFormatting.GRAY)
- );
- }
- }
- }
-
- public boolean shouldWarnMasters(ItemStack stack) {
- CompoundTag tag = stack.getTag();
-
- // Default to true if tag doesn't exist
-
- return (
- tag == null ||
- !tag.contains(NBT_WARN_MASTERS) ||
- tag.getBoolean(NBT_WARN_MASTERS)
- );
- }
-
- public void setWarnMasters(ItemStack stack, boolean warn) {
- stack.getOrCreateTag().putBoolean(NBT_WARN_MASTERS, warn);
- }
-
- public boolean hasPublicTracking(ItemStack stack) {
- CompoundTag tag = stack.getTag();
-
- return tag != null && tag.getBoolean(NBT_PUBLIC_TRACKING);
- }
-
- public void setPublicTracking(ItemStack stack, boolean publicTracking) {
- stack.getOrCreateTag().putBoolean(NBT_PUBLIC_TRACKING, publicTracking);
- }
-
- public boolean isActive(ItemStack stack) {
- CompoundTag tag = stack.getTag();
-
- // Default to active if tag doesn't exist
-
- return (
- tag == null ||
- !tag.contains(NBT_GPS_ACTIVE) ||
- tag.getBoolean(NBT_GPS_ACTIVE)
- );
- }
-
- public void setActive(ItemStack stack, boolean active) {
- stack.getOrCreateTag().putBoolean(NBT_GPS_ACTIVE, active);
- }
-
- /**
-
- * Parses the NBT List into a Java List of SafeSpot objects.
-
- */
-
- public List getSafeSpots(ItemStack stack) {
- List list = new ArrayList<>();
-
- CompoundTag tag = stack.getTag();
-
- if (tag != null && tag.contains(NBT_SAFE_SPOTS)) {
- ListTag spotList = tag.getList(NBT_SAFE_SPOTS, 10);
-
- for (int i = 0; i < spotList.size(); i++) {
- list.add(new SafeSpot(spotList.getCompound(i)));
- }
- }
-
- return list;
- }
-
- /**
-
- * Adds a new safe zone to the collar's NBT data.
-
- */
-
- public void addSafeSpot(
- ItemStack stack,
- int x,
- int y,
- int z,
- String dimension,
- int distance
- ) {
- CompoundTag tag = stack.getOrCreateTag();
-
- ListTag spotList = tag.getList(NBT_SAFE_SPOTS, 10);
-
- SafeSpot spot = new SafeSpot(x, y, z, dimension, distance, true);
-
- spotList.add(spot.toNBT());
-
- tag.put(NBT_SAFE_SPOTS, spotList);
- }
-
- /**
-
- * Gets frequency of GPS violation shocks.
-
- */
-
- public int getShockInterval(ItemStack stack) {
- CompoundTag tag = stack.getTag();
-
- if (tag != null && tag.contains(NBT_SHOCK_INTERVAL)) {
- return tag.getInt(NBT_SHOCK_INTERVAL);
- }
-
- return defaultInterval;
- }
-
- /**
- */
- @Override
- public void onUnequipped(ItemStack stack, LivingEntity entity) {
- // Use IRestrainable interface instead of Player-only
- IRestrainable state = KidnappedHelper.getKidnappedState(entity);
- if (state != null) {
- state.resetAutoShockTimer();
- }
-
- super.onUnequipped(stack, entity);
- }
-
- /**
-
- * Represents a defined safe zone in the 3D world.
-
- */
-
- public static class SafeSpot {
-
- public int x, y, z;
-
- public String dimension;
-
- public int distance;
-
- public boolean active;
-
- public SafeSpot(
- int x,
- int y,
- int z,
- String dimension,
- int distance,
- boolean active
- ) {
- this.x = x;
-
- this.y = y;
-
- this.z = z;
-
- this.dimension = dimension;
-
- this.distance = distance;
-
- this.active = active;
- }
-
- public SafeSpot(CompoundTag nbt) {
- this.x = nbt.getInt("x");
-
- this.y = nbt.getInt("y");
-
- this.z = nbt.getInt("z");
-
- this.dimension = nbt.getString("dim");
-
- this.distance = nbt.getInt("dist");
-
- this.active = !nbt.contains("active") || nbt.getBoolean("active");
- }
-
- public CompoundTag toNBT() {
- CompoundTag nbt = new CompoundTag();
-
- nbt.putInt("x", x);
-
- nbt.putInt("y", y);
-
- nbt.putInt("z", z);
-
- nbt.putString("dim", dimension);
-
- nbt.putInt("dist", distance);
-
- nbt.putBoolean("active", active);
-
- return nbt;
- }
-
- /**
-
- * Checks if an entity is within the cuboid boundaries of this safe zone.
-
- * Faithful to original 1.12.2 distance logic.
-
- */
-
- public boolean isInside(Entity entity) {
- if (!active) return true;
-
- // LOW FIX: Cross-dimension GPS fix
- // If entity is in a different dimension, consider them as "inside" the zone
- // to prevent false positive shocks when traveling between dimensions
- if (
- !entity
- .level()
- .dimension()
- .location()
- .toString()
- .equals(dimension)
- ) return true; // Changed from false to true
-
- // Cuboid distance check
-
- return (
- Math.abs(entity.getX() - x) < distance &&
- Math.abs(entity.getY() - y) < distance &&
- Math.abs(entity.getZ() - z) < distance
- );
- }
- }
-}
diff --git a/src/main/java/com/tiedup/remake/items/ItemGpsLocator.java b/src/main/java/com/tiedup/remake/items/ItemGpsLocator.java
index dd77a54..85ef45a 100644
--- a/src/main/java/com/tiedup/remake/items/ItemGpsLocator.java
+++ b/src/main/java/com/tiedup/remake/items/ItemGpsLocator.java
@@ -1,8 +1,8 @@
package com.tiedup.remake.items;
import com.tiedup.remake.core.SystemMessageManager;
-import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.items.base.ItemOwnerTarget;
+import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.util.KidnappedHelper;
import com.tiedup.remake.v2.BodyRegionV2;
@@ -91,13 +91,10 @@ public class ItemGpsLocator extends ItemOwnerTarget {
ItemStack collarStack = targetState.getEquipment(
BodyRegionV2.NECK
);
- if (
- collarStack.getItem() instanceof
- ItemGpsCollar collarItem
- ) {
+ if (CollarHelper.hasGPS(collarStack)) {
if (
- collarItem.isOwner(collarStack, player) ||
- collarItem.hasPublicTracking(collarStack)
+ CollarHelper.isOwner(collarStack, player) ||
+ CollarHelper.hasPublicTracking(collarStack)
) {
// Check if same dimension
boolean sameDimension = player
diff --git a/src/main/java/com/tiedup/remake/items/ItemHood.java b/src/main/java/com/tiedup/remake/items/ItemHood.java
deleted file mode 100644
index 96802b8..0000000
--- a/src/main/java/com/tiedup/remake/items/ItemHood.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package com.tiedup.remake.items;
-
-import com.tiedup.remake.items.base.IHasGaggingEffect;
-import com.tiedup.remake.items.base.ItemBlindfold;
-import com.tiedup.remake.util.GagMaterial;
-import net.minecraft.world.item.Item;
-
-/**
- * Hood - Covers the head completely
- * Combines blindfold effect with gagging effect.
- *
- * Extends ItemBlindfold for slot behavior, implements IHasGaggingEffect for speech muffling.
- */
-public class ItemHood extends ItemBlindfold implements IHasGaggingEffect {
-
- private final GagMaterial gagMaterial;
-
- public ItemHood() {
- super(new Item.Properties().stacksTo(16));
- this.gagMaterial = GagMaterial.STUFFED; // Hoods muffle speech like stuffed gags
- }
-
- /**
- * Get the gag material type for speech conversion.
- * @return The gag material (STUFFED for hoods)
- */
- public GagMaterial getGagMaterial() {
- return gagMaterial;
- }
-
- @Override
- public String getTextureSubfolder() {
- return "hoods";
- }
-}
diff --git a/src/main/java/com/tiedup/remake/items/ItemKey.java b/src/main/java/com/tiedup/remake/items/ItemKey.java
index 71c441c..bbebb14 100644
--- a/src/main/java/com/tiedup/remake/items/ItemKey.java
+++ b/src/main/java/com/tiedup/remake/items/ItemKey.java
@@ -259,13 +259,10 @@ public class ItemKey extends ItemOwnerTarget {
ItemStack collarStack = targetState.getEquipment(BodyRegionV2.NECK);
if (collarStack.isEmpty()) return;
- if (
- collarStack.getItem() instanceof
- com.tiedup.remake.items.base.ItemCollar collar
- ) {
+ if (com.tiedup.remake.v2.bondage.CollarHelper.isCollar(collarStack)) {
// Add player as owner to the collar (if not already)
- if (!collar.getOwners(collarStack).contains(player.getUUID())) {
- collar.addOwner(collarStack, player);
+ if (!com.tiedup.remake.v2.bondage.CollarHelper.isOwner(collarStack, player)) {
+ com.tiedup.remake.v2.bondage.CollarHelper.addOwner(collarStack, player);
// Update the collar in the target's inventory
targetState.equip(BodyRegionV2.NECK, collarStack);
diff --git a/src/main/java/com/tiedup/remake/items/ItemLockpick.java b/src/main/java/com/tiedup/remake/items/ItemLockpick.java
index 6318095..6814ceb 100644
--- a/src/main/java/com/tiedup/remake/items/ItemLockpick.java
+++ b/src/main/java/com/tiedup/remake/items/ItemLockpick.java
@@ -332,15 +332,12 @@ public class ItemLockpick extends Item {
);
if (collar.isEmpty()) return;
- if (
- collar.getItem() instanceof
- com.tiedup.remake.items.ItemShockCollar shockCollar
- ) {
+ if (com.tiedup.remake.v2.bondage.CollarHelper.canShock(collar)) {
// Shock the player
state.shockKidnapped(" (Failed lockpick attempt)", 2.0f);
// Notify owners
- notifyOwnersLockpickAttempt(player, collar, shockCollar);
+ notifyOwnersLockpickAttempt(player, collar);
TiedUpMod.LOGGER.info(
"[LOCKPICK] {} was shocked for failed lockpick attempt",
@@ -354,8 +351,7 @@ public class ItemLockpick extends Item {
*/
private static void notifyOwnersLockpickAttempt(
Player player,
- ItemStack collar,
- com.tiedup.remake.items.ItemShockCollar shockCollar
+ ItemStack collar
) {
if (player.getServer() == null) return;
@@ -367,7 +363,7 @@ public class ItemLockpick extends Item {
).withStyle(ChatFormatting.GOLD)
);
- List owners = shockCollar.getOwners(collar);
+ List owners = com.tiedup.remake.v2.bondage.CollarHelper.getOwners(collar);
for (UUID ownerId : owners) {
ServerPlayer owner = player
.getServer()
diff --git a/src/main/java/com/tiedup/remake/items/ItemMedicalGag.java b/src/main/java/com/tiedup/remake/items/ItemMedicalGag.java
deleted file mode 100644
index f4e84a6..0000000
--- a/src/main/java/com/tiedup/remake/items/ItemMedicalGag.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.tiedup.remake.items;
-
-import com.tiedup.remake.items.base.IHasBlindingEffect;
-import com.tiedup.remake.items.base.ItemGag;
-import com.tiedup.remake.util.GagMaterial;
-import net.minecraft.world.item.Item;
-
-/**
- * Medical Gag - Full face medical restraint
- * Combines gag effect with blinding effect.
- *
- * Extends ItemGag for slot behavior, implements IHasBlindingEffect for vision obstruction.
- */
-public class ItemMedicalGag extends ItemGag implements IHasBlindingEffect {
-
- public ItemMedicalGag() {
- super(new Item.Properties().stacksTo(16), GagMaterial.PANEL);
- }
-
- @Override
- public String getTextureSubfolder() {
- return "straps";
- }
-}
diff --git a/src/main/java/com/tiedup/remake/items/ItemShockCollar.java b/src/main/java/com/tiedup/remake/items/ItemShockCollar.java
deleted file mode 100644
index 3a22f36..0000000
--- a/src/main/java/com/tiedup/remake/items/ItemShockCollar.java
+++ /dev/null
@@ -1,133 +0,0 @@
-package com.tiedup.remake.items;
-
-import com.tiedup.remake.core.SystemMessageManager;
-import com.tiedup.remake.items.base.ItemCollar;
-import com.tiedup.remake.state.IBondageState;
-import com.tiedup.remake.util.KidnappedHelper;
-import java.util.List;
-import net.minecraft.ChatFormatting;
-import net.minecraft.nbt.CompoundTag;
-import net.minecraft.network.chat.Component;
-import net.minecraft.world.InteractionHand;
-import net.minecraft.world.InteractionResultHolder;
-import net.minecraft.world.entity.LivingEntity;
-import net.minecraft.world.entity.player.Player;
-import net.minecraft.world.item.Item;
-import net.minecraft.world.item.ItemStack;
-import net.minecraft.world.item.TooltipFlag;
-import net.minecraft.world.level.Level;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * Shock Collar - Advanced collar that can be remotely triggered.
- *
- * Mechanics:
- *
- * - Remote Shocking: Can be triggered by anyone holding a linked Shocker Controller.
- * - Struggle Penalty: If locked, has a chance to shock the wearer during struggle attempts, interrupting them.
- * - Public Mode: Can be set to public mode, allowing anyone to shock the wearer even if they aren't the owner.
- *
- */
-public class ItemShockCollar extends ItemCollar {
-
- private static final String NBT_PUBLIC_MODE = "public_mode";
-
- public ItemShockCollar() {
- super(new Item.Properties());
- }
-
- @Override
- public boolean canShock() {
- return true;
- }
-
- /**
- * Shows current mode (PUBLIC/PRIVATE) and usage instructions in tooltip.
- */
- @Override
- public void appendHoverText(
- ItemStack stack,
- @Nullable Level level,
- List tooltip,
- TooltipFlag flag
- ) {
- super.appendHoverText(stack, level, tooltip, flag);
-
- tooltip.add(
- Component.literal("Shock Feature: ")
- .withStyle(ChatFormatting.YELLOW)
- .append(
- Component.literal(
- isPublic(stack) ? "PUBLIC" : "PRIVATE"
- ).withStyle(
- isPublic(stack)
- ? ChatFormatting.GREEN
- : ChatFormatting.RED
- )
- )
- );
-
- tooltip.add(
- Component.literal("Shift + Right-click to toggle public mode")
- .withStyle(ChatFormatting.DARK_GRAY)
- .withStyle(ChatFormatting.ITALIC)
- );
- }
-
- /**
- * Toggles Public mode when shift-right-clicking in air.
- */
- @Override
- public InteractionResultHolder use(
- Level level,
- Player player,
- InteractionHand hand
- ) {
- ItemStack stack = player.getItemInHand(hand);
-
- if (player.isShiftKeyDown()) {
- if (!level.isClientSide) {
- boolean newState = !isPublic(stack);
- setPublic(stack, newState);
- SystemMessageManager.sendToPlayer(
- player,
- SystemMessageManager.MessageCategory.SHOCKER_MODE_SET,
- (newState ? "PUBLIC" : "PRIVATE")
- );
- }
- return InteractionResultHolder.sidedSuccess(
- stack,
- level.isClientSide()
- );
- }
-
- return super.use(level, player, hand);
- }
-
- /**
- * Handles the risk of shocking the wearer during a struggle attempt.
- *
- * NOTE: For the new continuous struggle mini-game, shock logic is handled
- * directly in MiniGameSessionManager.tickContinuousSessions(). This method
- * is now a no-op that always returns true, kept for API compatibility.
- *
- * @param entity The wearer of the collar
- * @param stack The collar instance
- * @return Always true (shock logic moved to MiniGameSessionManager)
- */
- public boolean notifyStruggle(LivingEntity entity, ItemStack stack) {
- // Shock collar checks during continuous struggle are now handled by
- // MiniGameSessionManager.shouldTriggerShock() with 10% chance every 5 seconds.
- // This method is kept for backwards compatibility but no longer performs the check.
- return true;
- }
-
- public boolean isPublic(ItemStack stack) {
- CompoundTag tag = stack.getTag();
- return tag != null && tag.getBoolean(NBT_PUBLIC_MODE);
- }
-
- public void setPublic(ItemStack stack, boolean publicMode) {
- stack.getOrCreateTag().putBoolean(NBT_PUBLIC_MODE, publicMode);
- }
-}
diff --git a/src/main/java/com/tiedup/remake/items/ItemShockCollarAuto.java b/src/main/java/com/tiedup/remake/items/ItemShockCollarAuto.java
deleted file mode 100644
index 07eb8a4..0000000
--- a/src/main/java/com/tiedup/remake/items/ItemShockCollarAuto.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package com.tiedup.remake.items;
-
-import com.tiedup.remake.state.IRestrainable;
-import com.tiedup.remake.util.KidnappedHelper;
-import net.minecraft.world.entity.LivingEntity;
-import net.minecraft.world.item.ItemStack;
-
-/**
- * Automatic Shock Collar - Shocks the wearer at regular intervals.
- *
- *
- * Mechanics:
- *
- * - Self-Triggering: Has an internal timer stored in NBT that shocks the entity when it reaches 0.
- * - Unstruggable: By default, cannot be escaped via struggle mechanics (requires key).
- *
- */
-public class ItemShockCollarAuto extends ItemShockCollar {
-
- private final int interval;
-
- /**
- * @param interval Frequency of shocks in TICKS (20 ticks = 1 second).
- */
- public ItemShockCollarAuto() {
- this(600); // 30 seconds default
- }
-
- public ItemShockCollarAuto(int interval) {
- super();
- this.interval = interval;
- }
-
- public int getInterval() {
- return interval;
- }
-
- /**
- * Ensures the internal shock timer is cleaned up when the item is removed.
- *
- */
- @Override
- public void onUnequipped(ItemStack stack, LivingEntity entity) {
- IRestrainable state = KidnappedHelper.getKidnappedState(entity);
- if (state != null) {
- state.resetAutoShockTimer();
- }
- super.onUnequipped(stack, entity);
- }
-
- /**
- * Prevents escaping through struggle mechanics for this specific collar type.
- */
- @Override
- public boolean canBeStruggledOut(ItemStack stack) {
- return false;
- }
-}
diff --git a/src/main/java/com/tiedup/remake/items/ItemShockerController.java b/src/main/java/com/tiedup/remake/items/ItemShockerController.java
index 696f13c..9678ebd 100644
--- a/src/main/java/com/tiedup/remake/items/ItemShockerController.java
+++ b/src/main/java/com/tiedup/remake/items/ItemShockerController.java
@@ -3,8 +3,8 @@ package com.tiedup.remake.items;
import com.tiedup.remake.core.ModSounds;
import com.tiedup.remake.core.SystemMessageManager;
import com.tiedup.remake.core.TiedUpMod;
-import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.items.base.ItemOwnerTarget;
+import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.state.IRestrainable;
import com.tiedup.remake.util.KidnappedHelper;
import com.tiedup.remake.v2.BodyRegionV2;
@@ -77,12 +77,11 @@ public class ItemShockerController extends ItemOwnerTarget {
BodyRegionV2.NECK
);
if (
- collar.getItem() instanceof
- ItemCollar collarItem &&
- collarItem.hasNickname(collar)
+ CollarHelper.isCollar(collar) &&
+ CollarHelper.hasNickname(collar)
) {
displayName =
- collarItem.getNickname(collar) +
+ CollarHelper.getNickname(collar) +
" (" +
displayName +
")";
@@ -330,12 +329,10 @@ public class ItemShockerController extends ItemOwnerTarget {
IRestrainable state = KidnappedHelper.getKidnappedState(entity);
if (state != null && state.hasCollar()) {
ItemStack collarStack = state.getEquipment(BodyRegionV2.NECK);
- if (
- collarStack.getItem() instanceof ItemShockCollar collarItem
- ) {
+ if (CollarHelper.canShock(collarStack)) {
if (
- collarItem.getOwners(collarStack).contains(ownerId) ||
- collarItem.isPublic(collarStack)
+ CollarHelper.isOwner(collarStack, ownerId) ||
+ CollarHelper.isPublicShock(collarStack)
) {
targets.add(entity);
}
diff --git a/src/main/java/com/tiedup/remake/items/ModCreativeTabs.java b/src/main/java/com/tiedup/remake/items/ModCreativeTabs.java
index 62a1318..dd33824 100644
--- a/src/main/java/com/tiedup/remake/items/ModCreativeTabs.java
+++ b/src/main/java/com/tiedup/remake/items/ModCreativeTabs.java
@@ -2,9 +2,11 @@ package com.tiedup.remake.items;
import com.tiedup.remake.blocks.ModBlocks;
import com.tiedup.remake.core.TiedUpMod;
-import com.tiedup.remake.entities.KidnapperItemSelector;
import com.tiedup.remake.items.base.*;
import com.tiedup.remake.v2.V2Items;
+import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
+import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemDefinition;
+import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemRegistry;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.chat.Component;
import net.minecraft.world.item.CreativeModeTab;
@@ -16,7 +18,7 @@ import net.minecraftforge.registries.RegistryObject;
* Creative Mode Tabs Registration
* Defines the creative inventory tabs where TiedUp items will appear.
*
- * Updated to use factory pattern with enum-based item registration.
+ * All bondage items are now sourced from DataDrivenItemRegistry.
*/
@SuppressWarnings("null") // Minecraft API guarantees non-null returns
public class ModCreativeTabs {
@@ -28,126 +30,27 @@ public class ModCreativeTabs {
CREATIVE_MODE_TABS.register("tiedup_tab", () ->
CreativeModeTab.builder()
.title(Component.translatable("itemGroup.tiedup"))
- .icon(() -> new ItemStack(ModItems.getBind(BindVariant.ROPES)))
+ .icon(() -> {
+ // Use first data-driven item as icon, or fallback to whip
+ var allDefs = DataDrivenItemRegistry.getAll();
+ if (!allDefs.isEmpty()) {
+ return DataDrivenBondageItem.createStack(allDefs.iterator().next().id());
+ }
+ return new ItemStack(ModItems.WHIP.get());
+ })
.displayItems((parameters, output) -> {
- // ========== BINDS (from enum) ==========
- for (BindVariant variant : BindVariant.values()) {
- // Add base item
- output.accept(ModItems.getBind(variant));
-
- // Add colored variants if supported
- if (variant.supportsColor()) {
- for (ItemColor color : ItemColor.values()) {
- // Skip special colors (caution, clear) except for duct tape
- if (
- color.isSpecial() &&
- variant != BindVariant.DUCT_TAPE
- ) continue;
- // Use validation method to check if color has texture
- if (
- KidnapperItemSelector.isColorValidForBind(
- color,
- variant
- )
- ) {
- output.accept(
- KidnapperItemSelector.createBind(
- variant,
- color
- )
- );
- }
- }
- }
+ // ========== DATA-DRIVEN BONDAGE ITEMS ==========
+ // All binds, gags, blindfolds, earplugs, mittens, collars,
+ // hood, medical gag, etc. are now data-driven
+ for (DataDrivenItemDefinition def : DataDrivenItemRegistry.getAll()) {
+ output.accept(
+ DataDrivenBondageItem.createStack(def.id())
+ );
}
- // ========== GAGS (from enum) ==========
- for (GagVariant variant : GagVariant.values()) {
- // Add base item
- output.accept(ModItems.getGag(variant));
-
- // Add colored variants if supported
- if (variant.supportsColor()) {
- for (ItemColor color : ItemColor.values()) {
- // Skip special colors (caution, clear) except for tape gag
- if (
- color.isSpecial() &&
- variant != GagVariant.TAPE_GAG
- ) continue;
- // Use validation method to check if color has texture
- if (
- KidnapperItemSelector.isColorValidForGag(
- color,
- variant
- )
- ) {
- output.accept(
- KidnapperItemSelector.createGag(
- variant,
- color
- )
- );
- }
- }
- }
- }
-
- // ========== BLINDFOLDS (from enum) ==========
- for (BlindfoldVariant variant : BlindfoldVariant.values()) {
- // Add base item
- output.accept(ModItems.getBlindfold(variant));
-
- // Add colored variants if supported
- if (variant.supportsColor()) {
- for (ItemColor color : ItemColor.values()) {
- // Skip special colors for blindfolds
- if (color.isSpecial()) continue;
- // Use validation method to check if color has texture
- if (
- KidnapperItemSelector.isColorValidForBlindfold(
- color,
- variant
- )
- ) {
- output.accept(
- KidnapperItemSelector.createBlindfold(
- variant,
- color
- )
- );
- }
- }
- }
- }
-
- // Hood (combo item, not in enum)
- output.accept(ModItems.HOOD.get());
-
- // ========== 3D ITEMS ==========
- output.accept(ModItems.BALL_GAG_3D.get());
-
- // ========== COMBO ITEMS ==========
- output.accept(ModItems.MEDICAL_GAG.get());
-
// ========== CLOTHES ==========
output.accept(ModItems.CLOTHES.get());
- // ========== COLLARS ==========
- output.accept(ModItems.CLASSIC_COLLAR.get());
- output.accept(ModItems.CHOKE_COLLAR.get());
- output.accept(ModItems.SHOCK_COLLAR.get());
- output.accept(ModItems.GPS_COLLAR.get());
-
- // ========== EARPLUGS (from enum) ==========
- for (EarplugsVariant variant : EarplugsVariant.values()) {
- output.accept(ModItems.getEarplugs(variant));
- }
-
- // ========== MITTENS (from enum) ==========
- for (MittensVariant variant : MittensVariant.values()) {
- output.accept(ModItems.getMittens(variant));
- }
-
// ========== KNIVES (from enum) ==========
for (KnifeVariant variant : KnifeVariant.values()) {
output.accept(ModItems.getKnife(variant));
@@ -196,15 +99,6 @@ public class ModCreativeTabs {
com.tiedup.remake.v2.bondage.V2BondageItems.V2_HANDCUFFS.get()
);
- // ========== DATA-DRIVEN BONDAGE ITEMS ==========
- for (com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemDefinition def : com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemRegistry.getAll()) {
- output.accept(
- com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem.createStack(
- def.id()
- )
- );
- }
-
// ========== FURNITURE PLACER ITEMS ==========
for (com.tiedup.remake.v2.furniture.FurnitureDefinition def : com.tiedup.remake.v2.furniture.FurnitureRegistry.getAll()) {
output.accept(
diff --git a/src/main/java/com/tiedup/remake/items/ModItems.java b/src/main/java/com/tiedup/remake/items/ModItems.java
index abb8a60..5388d1d 100644
--- a/src/main/java/com/tiedup/remake/items/ModItems.java
+++ b/src/main/java/com/tiedup/remake/items/ModItems.java
@@ -3,7 +3,6 @@ package com.tiedup.remake.items;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.entities.ModEntities;
import com.tiedup.remake.items.base.*;
-import com.tiedup.remake.items.bondage3d.gags.ItemBallGag3D;
import com.tiedup.remake.items.clothes.GenericClothes;
import java.util.EnumMap;
import java.util.Map;
@@ -17,14 +16,9 @@ import net.minecraftforge.registries.RegistryObject;
* Mod Items Registration
* Handles registration of all TiedUp items using DeferredRegister.
*
- * Refactored with Factory Pattern:
- * - Binds, Gags, Blindfolds, Earplugs, Knives use EnumMaps and factory methods
- * - Complex items (collars, whip, chloroform, etc.) remain individual registrations
- *
- * Usage:
- * - ModItems.getBind(BindVariant.ROPES) - Get a specific bind item
- * - ModItems.getGag(GagVariant.BALL_GAG) - Get a specific gag item
- * - ModItems.WHIP.get() - Get complex items directly
+ * V1 bondage items (binds, gags, blindfolds, earplugs, mittens, collars, hood, medical gag)
+ * have been removed. All bondage items are now data-driven via DataDrivenItemRegistry.
+ * Only non-bondage items and knives remain here.
*/
public class ModItems {
@@ -34,42 +28,7 @@ public class ModItems {
TiedUpMod.MOD_ID
);
- // ========== FACTORY-BASED ITEMS ==========
-
- /**
- * All bind items (15 variants via BindVariant enum)
- */
- public static final Map> BINDS =
- registerAllBinds();
-
- /**
- * All gag items (via GagVariant enum)
- * Note: ItemMedicalGag is registered separately as it has special behavior
- * Note: BALL_GAG_3D is a separate 3D item (not in enum)
- */
- public static final Map> GAGS =
- registerAllGags();
-
- /**
- * Ball Gag 3D - Uses 3D OBJ model rendering via dedicated class.
- * This is a separate item from BALL_GAG (which uses 2D textures).
- */
- public static final RegistryObject- BALL_GAG_3D = ITEMS.register(
- "ball_gag_3d",
- ItemBallGag3D::new
- );
-
- /**
- * All blindfold items (2 variants via BlindfoldVariant enum)
- */
- public static final Map> BLINDFOLDS =
- registerAllBlindfolds();
-
- /**
- * All earplugs items (1 variant via EarplugsVariant enum)
- */
- public static final Map> EARPLUGS =
- registerAllEarplugs();
+ // ========== KNIVES (still V1 — not bondage items) ==========
/**
* All knife items (3 variants via KnifeVariant enum)
@@ -77,12 +36,6 @@ public class ModItems {
public static final Map> KNIVES =
registerAllKnives();
- /**
- * All mittens items (1 variant via MittensVariant enum)
- */
- public static final Map> MITTENS =
- registerAllMittens();
-
/**
* Clothes item - uses dynamic textures from URLs.
* Users can create presets via anvil naming.
@@ -92,48 +45,8 @@ public class ModItems {
GenericClothes::new
);
- // ========== COMPLEX ITEMS (individual registrations) ==========
+ // ========== TOOLS ==========
- // Medical gag - combo item with IHasBlindingEffect
- public static final RegistryObject
- MEDICAL_GAG = ITEMS.register(
- "medical_gag",
- ItemMedicalGag::new
- );
-
- // Hood - combo item
- public static final RegistryObject
- HOOD = ITEMS.register(
- "hood",
- ItemHood::new
- );
-
- // Collars - complex logic
- public static final RegistryObject
- CLASSIC_COLLAR = ITEMS.register(
- "classic_collar",
- ItemClassicCollar::new
- );
-
- public static final RegistryObject
- SHOCK_COLLAR = ITEMS.register(
- "shock_collar",
- ItemShockCollar::new
- );
-
- public static final RegistryObject
- SHOCK_COLLAR_AUTO = ITEMS.register(
- "shock_collar_auto",
- ItemShockCollarAuto::new
- );
-
- public static final RegistryObject
- GPS_COLLAR = ITEMS.register(
- "gps_collar",
- ItemGpsCollar::new
- );
-
- // Choke Collar - Pet play collar used by Masters
- public static final RegistryObject
- CHOKE_COLLAR = ITEMS.register(
- "choke_collar",
- ItemChokeCollar::new
- );
-
- // Tools with complex behavior
public static final RegistryObject
- WHIP = ITEMS.register(
"whip",
ItemWhip::new
@@ -213,13 +126,11 @@ public class ModItems {
// ========== CELL SYSTEM ITEMS ==========
- // Admin Wand - Structure marker placement and Cell Core management
public static final RegistryObject
- ADMIN_WAND = ITEMS.register(
"admin_wand",
ItemAdminWand::new
);
- // Cell Key - Universal key for iron bar doors
public static final RegistryObject
- CELL_KEY = ITEMS.register(
"cell_key",
ItemCellKey::new
@@ -227,7 +138,6 @@ public class ModItems {
// ========== SLAVE TRADER SYSTEM ==========
- // Token - Access pass for kidnapper camps
public static final RegistryObject
- TOKEN = ITEMS.register(
"token",
ItemToken::new
@@ -252,72 +162,6 @@ public class ModItems {
// ========== FACTORY METHODS ==========
- private static Map> registerAllBinds() {
- Map> map = new EnumMap<>(
- BindVariant.class
- );
- for (BindVariant variant : BindVariant.values()) {
- map.put(
- variant,
- ITEMS.register(variant.getRegistryName(), () ->
- new GenericBind(variant)
- )
- );
- }
- return map;
- }
-
- private static Map> registerAllGags() {
- Map> map = new EnumMap<>(
- GagVariant.class
- );
- for (GagVariant variant : GagVariant.values()) {
- map.put(
- variant,
- ITEMS.register(variant.getRegistryName(), () ->
- new GenericGag(variant)
- )
- );
- }
- return map;
- }
-
- private static Map<
- BlindfoldVariant,
- RegistryObject
-
- > registerAllBlindfolds() {
- Map> map = new EnumMap<>(
- BlindfoldVariant.class
- );
- for (BlindfoldVariant variant : BlindfoldVariant.values()) {
- map.put(
- variant,
- ITEMS.register(variant.getRegistryName(), () ->
- new GenericBlindfold(variant)
- )
- );
- }
- return map;
- }
-
- private static Map<
- EarplugsVariant,
- RegistryObject
-
- > registerAllEarplugs() {
- Map> map = new EnumMap<>(
- EarplugsVariant.class
- );
- for (EarplugsVariant variant : EarplugsVariant.values()) {
- map.put(
- variant,
- ITEMS.register(variant.getRegistryName(), () ->
- new GenericEarplugs(variant)
- )
- );
- }
- return map;
- }
-
private static Map> registerAllKnives() {
Map> map = new EnumMap<>(
KnifeVariant.class
@@ -333,62 +177,8 @@ public class ModItems {
return map;
}
- private static Map<
- MittensVariant,
- RegistryObject
-
- > registerAllMittens() {
- Map> map = new EnumMap<>(
- MittensVariant.class
- );
- for (MittensVariant variant : MittensVariant.values()) {
- map.put(
- variant,
- ITEMS.register(variant.getRegistryName(), () ->
- new GenericMittens(variant)
- )
- );
- }
- return map;
- }
-
// ========== HELPER ACCESSORS ==========
- /**
- * Get a bind item by variant.
- * @param variant The bind variant
- * @return The bind item
- */
- public static Item getBind(BindVariant variant) {
- return BINDS.get(variant).get();
- }
-
- /**
- * Get a gag item by variant.
- * @param variant The gag variant
- * @return The gag item
- */
- public static Item getGag(GagVariant variant) {
- return GAGS.get(variant).get();
- }
-
- /**
- * Get a blindfold item by variant.
- * @param variant The blindfold variant
- * @return The blindfold item
- */
- public static Item getBlindfold(BlindfoldVariant variant) {
- return BLINDFOLDS.get(variant).get();
- }
-
- /**
- * Get an earplugs item by variant.
- * @param variant The earplugs variant
- * @return The earplugs item
- */
- public static Item getEarplugs(EarplugsVariant variant) {
- return EARPLUGS.get(variant).get();
- }
-
/**
* Get a knife item by variant.
* @param variant The knife variant
@@ -397,13 +187,4 @@ public class ModItems {
public static Item getKnife(KnifeVariant variant) {
return KNIVES.get(variant).get();
}
-
- /**
- * Get a mittens item by variant.
- * @param variant The mittens variant
- * @return The mittens item
- */
- public static Item getMittens(MittensVariant variant) {
- return MITTENS.get(variant).get();
- }
}
diff --git a/src/main/java/com/tiedup/remake/items/base/AdjustmentHelper.java b/src/main/java/com/tiedup/remake/items/base/AdjustmentHelper.java
index e7d6727..e3cbfeb 100644
--- a/src/main/java/com/tiedup/remake/items/base/AdjustmentHelper.java
+++ b/src/main/java/com/tiedup/remake/items/base/AdjustmentHelper.java
@@ -1,5 +1,8 @@
package com.tiedup.remake.items.base;
+import com.tiedup.remake.v2.bondage.component.AdjustableComponent;
+import com.tiedup.remake.v2.bondage.component.ComponentType;
+import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.util.Mth;
import net.minecraft.world.item.ItemStack;
@@ -56,9 +59,12 @@ public class AdjustmentHelper {
return tag.getFloat(NBT_ADJUSTMENT_Y);
}
- // Fallback to item's default adjustment
- if (stack.getItem() instanceof IAdjustable adj) {
- return adj.getDefaultAdjustment();
+ // Fallback to item's default adjustment from V2 AdjustableComponent
+ AdjustableComponent comp = DataDrivenBondageItem.getComponent(
+ stack, ComponentType.ADJUSTABLE, AdjustableComponent.class
+ );
+ if (comp != null) {
+ return comp.getDefaultValue();
}
return DEFAULT_VALUE;
@@ -127,16 +133,15 @@ public class AdjustmentHelper {
* Check if an ItemStack's item supports adjustment.
*
* @param stack The ItemStack to check
- * @return true if the item implements IAdjustable and canBeAdjusted() returns true
+ * @return true if the item has an AdjustableComponent
*/
public static boolean isAdjustable(ItemStack stack) {
if (stack.isEmpty()) {
return false;
}
- if (stack.getItem() instanceof IAdjustable adj) {
- return adj.canBeAdjusted();
- }
- return false;
+ return DataDrivenBondageItem.getComponent(
+ stack, ComponentType.ADJUSTABLE, AdjustableComponent.class
+ ) != null;
}
/**
diff --git a/src/main/java/com/tiedup/remake/items/base/BindVariant.java b/src/main/java/com/tiedup/remake/items/base/BindVariant.java
deleted file mode 100644
index 400b378..0000000
--- a/src/main/java/com/tiedup/remake/items/base/BindVariant.java
+++ /dev/null
@@ -1,90 +0,0 @@
-package com.tiedup.remake.items.base;
-
-/**
- * Enum defining all bind variants with their properties.
- * Used by GenericBind to create bind items via factory pattern.
- *
- *
Issue #12 fix: Added textureSubfolder to eliminate 40+ string checks in renderers.
- */
-public enum BindVariant {
- // Standard binds (PoseType.STANDARD)
- ROPES("ropes", PoseType.STANDARD, true, "ropes"),
- ARMBINDER("armbinder", PoseType.STANDARD, false, "armbinder"),
- DOGBINDER("dogbinder", PoseType.DOG, false, "armbinder"),
- CHAIN("chain", PoseType.STANDARD, false, "chain"),
- RIBBON("ribbon", PoseType.STANDARD, false, "ribbon"),
- SLIME("slime", PoseType.STANDARD, false, "slime"),
- VINE_SEED("vine_seed", PoseType.STANDARD, false, "vine"),
- WEB_BIND("web_bind", PoseType.STANDARD, false, "web"),
- SHIBARI("shibari", PoseType.STANDARD, true, "shibari"),
- LEATHER_STRAPS("leather_straps", PoseType.STANDARD, false, "straps"),
- MEDICAL_STRAPS("medical_straps", PoseType.STANDARD, false, "straps"),
- BEAM_CUFFS("beam_cuffs", PoseType.STANDARD, false, "beam"),
- DUCT_TAPE("duct_tape", PoseType.STANDARD, true, "tape"),
-
- // Pose items (special PoseType)
- STRAITJACKET("straitjacket", PoseType.STRAITJACKET, false, "straitjacket"),
- WRAP("wrap", PoseType.WRAP, false, "wrap"),
- LATEX_SACK("latex_sack", PoseType.LATEX_SACK, false, "latex");
-
- private final String registryName;
- private final PoseType poseType;
- private final boolean supportsColor;
- private final String textureSubfolder;
-
- BindVariant(
- String registryName,
- PoseType poseType,
- boolean supportsColor,
- String textureSubfolder
- ) {
- this.registryName = registryName;
- this.poseType = poseType;
- this.supportsColor = supportsColor;
- this.textureSubfolder = textureSubfolder;
- }
-
- public String getRegistryName() {
- return registryName;
- }
-
- /**
- * Get the configured resistance for this bind variant.
- * Delegates to {@link com.tiedup.remake.core.SettingsAccessor#getBindResistance(String)}.
- */
- public int getResistance() {
- return com.tiedup.remake.core.SettingsAccessor.getBindResistance(
- registryName
- );
- }
-
- public PoseType getPoseType() {
- return poseType;
- }
-
- /**
- * Check if this bind variant supports color variations.
- * Items with colors: ropes, shibari, duct_tape
- */
- public boolean supportsColor() {
- return supportsColor;
- }
-
- /**
- * Get the texture subfolder for this bind variant.
- * Used by renderers to locate texture files.
- *
- * @return Subfolder path under textures/entity/bondage/ (e.g., "ropes", "straps")
- */
- public String getTextureSubfolder() {
- return textureSubfolder;
- }
-
- /**
- * Get the item name used for textures and translations.
- * For most variants this is the same as registryName.
- */
- public String getItemName() {
- return registryName;
- }
-}
diff --git a/src/main/java/com/tiedup/remake/items/base/BlindfoldVariant.java b/src/main/java/com/tiedup/remake/items/base/BlindfoldVariant.java
deleted file mode 100644
index 41cbc36..0000000
--- a/src/main/java/com/tiedup/remake/items/base/BlindfoldVariant.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package com.tiedup.remake.items.base;
-
-/**
- * Enum defining all blindfold variants.
- * Used by GenericBlindfold to create blindfold items via factory pattern.
- *
- *
Issue #12 fix: Added textureSubfolder to eliminate string checks in renderers.
- */
-public enum BlindfoldVariant {
- CLASSIC("classic_blindfold", true, "blindfolds"),
- MASK("blindfold_mask", true, "blindfolds/mask");
-
- private final String registryName;
- private final boolean supportsColor;
- private final String textureSubfolder;
-
- BlindfoldVariant(
- String registryName,
- boolean supportsColor,
- String textureSubfolder
- ) {
- this.registryName = registryName;
- this.supportsColor = supportsColor;
- this.textureSubfolder = textureSubfolder;
- }
-
- public String getRegistryName() {
- return registryName;
- }
-
- /**
- * Check if this blindfold variant supports color variations.
- * Both variants support colors in the original mod.
- */
- public boolean supportsColor() {
- return supportsColor;
- }
-
- /**
- * Get the texture subfolder for this blindfold variant.
- * Used by renderers to locate texture files.
- *
- * @return Subfolder path under textures/entity/bondage/ (e.g., "blindfolds", "blindfolds/mask")
- */
- public String getTextureSubfolder() {
- return textureSubfolder;
- }
-}
diff --git a/src/main/java/com/tiedup/remake/items/base/EarplugsVariant.java b/src/main/java/com/tiedup/remake/items/base/EarplugsVariant.java
deleted file mode 100644
index 97368f9..0000000
--- a/src/main/java/com/tiedup/remake/items/base/EarplugsVariant.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.tiedup.remake.items.base;
-
-/**
- * Enum defining all earplugs variants.
- * Used by GenericEarplugs to create earplugs items via factory pattern.
- *
- *
Issue #12 fix: Added textureSubfolder to eliminate string checks in renderers.
- */
-public enum EarplugsVariant {
- CLASSIC("classic_earplugs", "earplugs");
-
- private final String registryName;
- private final String textureSubfolder;
-
- EarplugsVariant(String registryName, String textureSubfolder) {
- this.registryName = registryName;
- this.textureSubfolder = textureSubfolder;
- }
-
- public String getRegistryName() {
- return registryName;
- }
-
- /**
- * Get the texture subfolder for this earplugs variant.
- * Used by renderers to locate texture files.
- *
- * @return Subfolder path under textures/entity/bondage/ (e.g., "earplugs")
- */
- public String getTextureSubfolder() {
- return textureSubfolder;
- }
-}
diff --git a/src/main/java/com/tiedup/remake/items/base/GagVariant.java b/src/main/java/com/tiedup/remake/items/base/GagVariant.java
deleted file mode 100644
index 4b3e2eb..0000000
--- a/src/main/java/com/tiedup/remake/items/base/GagVariant.java
+++ /dev/null
@@ -1,163 +0,0 @@
-package com.tiedup.remake.items.base;
-
-import com.tiedup.remake.util.GagMaterial;
-
-/**
- * Enum defining all gag variants with their properties.
- * Used by GenericGag to create gag items via factory pattern.
- *
- *
Note: ItemMedicalGag is NOT included here because it implements
- * IHasBlindingEffect (combo item with special behavior).
- *
- *
Issue #12 fix: Added textureSubfolder to eliminate 40+ string checks in renderers.
- */
-public enum GagVariant {
- // Cloth-based gags
- CLOTH_GAG("cloth_gag", GagMaterial.CLOTH, true, "cloth", false, null),
- ROPES_GAG("ropes_gag", GagMaterial.CLOTH, true, "shibari", false, null),
- CLEAVE_GAG("cleave_gag", GagMaterial.CLOTH, true, "cleave", false, null),
- RIBBON_GAG("ribbon_gag", GagMaterial.CLOTH, false, "ribbon", false, null),
-
- // Ball gags - standard 2D texture rendering
- BALL_GAG(
- "ball_gag",
- GagMaterial.BALL,
- true,
- "ballgags/normal",
- false,
- null
- ),
- BALL_GAG_STRAP(
- "ball_gag_strap",
- GagMaterial.BALL,
- true,
- "ballgags/harness",
- false,
- null
- ),
-
- // Tape gags
- TAPE_GAG("tape_gag", GagMaterial.TAPE, true, "tape", false, null),
-
- // Stuffed/filling gags (no colors)
- WRAP_GAG("wrap_gag", GagMaterial.STUFFED, false, "wrap", false, null),
- SLIME_GAG("slime_gag", GagMaterial.STUFFED, false, "slime", false, null),
- VINE_GAG("vine_gag", GagMaterial.STUFFED, false, "vine", false, null),
- WEB_GAG("web_gag", GagMaterial.STUFFED, false, "web", false, null),
-
- // Panel gags (no colors)
- PANEL_GAG(
- "panel_gag",
- GagMaterial.PANEL,
- false,
- "straitjacket",
- false,
- null
- ),
- BEAM_PANEL_GAG(
- "beam_panel_gag",
- GagMaterial.PANEL,
- false,
- "beam",
- false,
- null
- ),
- CHAIN_PANEL_GAG(
- "chain_panel_gag",
- GagMaterial.PANEL,
- false,
- "chain",
- false,
- null
- ),
-
- // Latex gags (no colors)
- LATEX_GAG("latex_gag", GagMaterial.LATEX, false, "latex", false, null),
-
- // Ring/tube gags (no colors)
- TUBE_GAG("tube_gag", GagMaterial.RING, false, "tube", false, null),
-
- // Bite gags (no colors)
- BITE_GAG("bite_gag", GagMaterial.BITE, false, "armbinder", false, null),
-
- // Sponge gags (no colors)
- SPONGE_GAG("sponge_gag", GagMaterial.SPONGE, false, "sponge", false, null),
-
- // Baguette gags (no colors)
- BAGUETTE_GAG(
- "baguette_gag",
- GagMaterial.BAGUETTE,
- false,
- "baguette",
- false,
- null
- );
-
- private final String registryName;
- private final GagMaterial material;
- private final boolean supportsColor;
- private final String textureSubfolder;
- private final boolean uses3DModel;
- private final String modelPath;
-
- GagVariant(
- String registryName,
- GagMaterial material,
- boolean supportsColor,
- String textureSubfolder,
- boolean uses3DModel,
- String modelPath
- ) {
- this.registryName = registryName;
- this.material = material;
- this.supportsColor = supportsColor;
- this.textureSubfolder = textureSubfolder;
- this.uses3DModel = uses3DModel;
- this.modelPath = modelPath;
- }
-
- public String getRegistryName() {
- return registryName;
- }
-
- public GagMaterial getMaterial() {
- return material;
- }
-
- /**
- * Check if this gag variant supports color variations.
- * Items with colors: cloth_gag, ropes_gag, cleave_gag, ribbon_gag,
- * ball_gag, ball_gag_strap, tape_gag
- */
- public boolean supportsColor() {
- return supportsColor;
- }
-
- /**
- * Get the texture subfolder for this gag variant.
- * Used by renderers to locate texture files.
- *
- * @return Subfolder path under textures/entity/bondage/ (e.g., "cloth", "ballgags/normal")
- */
- public String getTextureSubfolder() {
- return textureSubfolder;
- }
-
- /**
- * Check if this gag variant uses a 3D OBJ model.
- *
- * @return true if this variant uses a 3D model
- */
- public boolean uses3DModel() {
- return uses3DModel;
- }
-
- /**
- * Get the model path for 3D rendering.
- *
- * @return ResourceLocation string path (e.g., "tiedup:models/obj/ball_gag.obj"), or null if no 3D model
- */
- public String getModelPath() {
- return modelPath;
- }
-}
diff --git a/src/main/java/com/tiedup/remake/items/base/IAdjustable.java b/src/main/java/com/tiedup/remake/items/base/IAdjustable.java
deleted file mode 100644
index b5b592a..0000000
--- a/src/main/java/com/tiedup/remake/items/base/IAdjustable.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package com.tiedup.remake.items.base;
-
-/**
- * Interface for items that can have their render position adjusted.
- * Typically gags and blindfolds that render on the player's head.
- *
- * Players can adjust the Y position of these items to better fit their skin.
- * Adjustment values are stored in the ItemStack's NBT via AdjustmentHelper.
- */
-public interface IAdjustable {
- /**
- * Whether this item supports position adjustment.
- * @return true if adjustable
- */
- boolean canBeAdjusted();
-
- /**
- * Default Y offset for this item type (in pixels, 1 pixel = 1/16 block).
- * Override for items that need a non-zero default position.
- * @return default adjustment value
- */
- default float getDefaultAdjustment() {
- return 0.0f;
- }
-
- /**
- * Minimum allowed adjustment value (pixels).
- * @return minimum value (typically -4.0)
- */
- default float getMinAdjustment() {
- return -4.0f;
- }
-
- /**
- * Maximum allowed adjustment value (pixels).
- * @return maximum value (typically +4.0)
- */
- default float getMaxAdjustment() {
- return 4.0f;
- }
-
- /**
- * Step size for GUI slider (smaller = more precise).
- * @return step size (typically 0.25)
- */
- default float getAdjustmentStep() {
- return 0.25f;
- }
-}
diff --git a/src/main/java/com/tiedup/remake/items/base/IBondageItem.java b/src/main/java/com/tiedup/remake/items/base/IBondageItem.java
deleted file mode 100644
index 8a099de..0000000
--- a/src/main/java/com/tiedup/remake/items/base/IBondageItem.java
+++ /dev/null
@@ -1,102 +0,0 @@
-package com.tiedup.remake.items.base;
-
-import com.tiedup.remake.v2.BodyRegionV2;
-import net.minecraft.resources.ResourceLocation;
-import net.minecraft.world.entity.LivingEntity;
-import net.minecraft.world.item.ItemStack;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * Interface for all bondage equipment items.
- * Defines the core behavior for items that can be equipped in custom bondage slots.
- *
- * Based on original IExtraBondageItem from 1.12.2
- */
-public interface IBondageItem {
- /**
- * Get the body region this item occupies when equipped.
- * @return The body region
- */
- BodyRegionV2 getBodyRegion();
-
- /**
- * Called every tick while this item is equipped on an entity.
- * @param stack The equipped item stack
- * @param entity The entity wearing the item
- */
- default void onWornTick(ItemStack stack, LivingEntity entity) {
- // Default: do nothing
- }
-
- /**
- * Called when this item is equipped on an entity.
- * @param stack The equipped item stack
- * @param entity The entity wearing the item
- */
- default void onEquipped(ItemStack stack, LivingEntity entity) {
- // Default: do nothing
- }
-
- /**
- * Called when this item is unequipped from an entity.
- * @param stack The unequipped item stack
- * @param entity The entity that was wearing the item
- */
- default void onUnequipped(ItemStack stack, LivingEntity entity) {
- // Default: do nothing
- }
-
- /**
- * Check if this item can be equipped on the given entity.
- * @param stack The item stack to equip
- * @param entity The target entity
- * @return true if the item can be equipped, false otherwise
- */
- default boolean canEquip(ItemStack stack, LivingEntity entity) {
- return true;
- }
-
- /**
- * Check if this item can be unequipped from the given entity.
- * @param stack The equipped item stack
- * @param entity The entity wearing the item
- * @return true if the item can be unequipped, false otherwise
- */
- default boolean canUnequip(ItemStack stack, LivingEntity entity) {
- return true;
- }
-
- /**
- * Get the texture subfolder for this bondage item.
- * Used by renderers to locate texture files.
- *
- *
Issue #12 fix: Eliminates 40+ string checks in renderers by letting
- * each item type declare its own texture subfolder.
- *
- * @return Subfolder path under textures/entity/bondage/ (e.g., "ropes", "ballgags/normal")
- */
- default String getTextureSubfolder() {
- return "misc"; // Fallback for items without explicit subfolder
- }
-
- /**
- * Check if this bondage item uses a 3D OBJ model instead of a flat texture.
- * Items with 3D models will be rendered using ObjModelRenderer.
- *
- * @return true if this item uses a 3D model, false for standard texture rendering
- */
- default boolean uses3DModel() {
- return false;
- }
-
- /**
- * Get the ResourceLocation of the 3D model for this item.
- * Only called if uses3DModel() returns true.
- *
- * @return ResourceLocation pointing to the .obj file, or null if no 3D model
- */
- @Nullable
- default ResourceLocation get3DModelLocation() {
- return null;
- }
-}
diff --git a/src/main/java/com/tiedup/remake/items/base/IHasBlindingEffect.java b/src/main/java/com/tiedup/remake/items/base/IHasBlindingEffect.java
deleted file mode 100644
index cf5890e..0000000
--- a/src/main/java/com/tiedup/remake/items/base/IHasBlindingEffect.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.tiedup.remake.items.base;
-
-/**
- * Marker interface for items that have a blinding visual effect.
- *
- *
Items implementing this interface will:
- *
- * - Apply a screen overlay when worn (client-side)
- * - Reduce the player's visibility
- * - Potentially disable certain UI elements
- *
- *
- * Usage
- * {@code
- * if (blindfold.getItem() instanceof IHasBlindingEffect) {
- * // Apply blinding overlay
- * renderBlindingOverlay();
- * }
- * }
- *
- * Implementations
- *
- * - {@link ItemBlindfold} - All blindfold items
- *
- *
- * Based on original IHasBlindingEffect.java from 1.12.2
- *
- * @see ItemBlindfold
- */
-public interface IHasBlindingEffect {
- // Marker interface - no methods required
- // Presence of this interface indicates the item has a blinding effect
-}
diff --git a/src/main/java/com/tiedup/remake/items/base/IHasGaggingEffect.java b/src/main/java/com/tiedup/remake/items/base/IHasGaggingEffect.java
deleted file mode 100644
index e16ca51..0000000
--- a/src/main/java/com/tiedup/remake/items/base/IHasGaggingEffect.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.tiedup.remake.items.base;
-
-/**
- * Marker interface for items that have a gagging (speech muffling) effect.
- *
- *
Items implementing this interface will:
- *
- * - Convert chat messages to "mmpphh" sounds
- * - Play gagged speech sounds
- * - Potentially block certain chat commands
- *
- *
- * Usage
- * {@code
- * if (gag.getItem() instanceof IHasGaggingEffect) {
- * // Convert chat message to gagged speech
- * message = GagTalkConverter.convert(message);
- * }
- * }
- *
- * Implementations
- *
- * - {@link ItemGag} - Ball gags, tape gags, cloth gags, etc.
- *
- *
- * Based on original ItemGaggingEffect.java from 1.12.2
- *
- * @see ItemGag
- */
-public interface IHasGaggingEffect {
- // Marker interface - no methods required
- // Presence of this interface indicates the item has a gagging effect
-}
diff --git a/src/main/java/com/tiedup/remake/items/base/IHasResistance.java b/src/main/java/com/tiedup/remake/items/base/IHasResistance.java
index 7f26455..26689ad 100644
--- a/src/main/java/com/tiedup/remake/items/base/IHasResistance.java
+++ b/src/main/java/com/tiedup/remake/items/base/IHasResistance.java
@@ -22,10 +22,8 @@ import net.minecraft.world.item.ItemStack;
*
*
Implementations
*
- * - {@link ItemBind} - Ropes, chains, straitjackets
- * - {@link ItemGag} - Ball gags, tape gags, cloth gags
- * - {@link ItemBlindfold} - Blindfolds
- * - {@link ItemCollar} - Collars (special: may not be struggleable)
+ * - {@link com.tiedup.remake.v2.bondage.items.AbstractV2BondageItem} - All V2 bondage items
+ * - {@link com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem} - Data-driven items (resistance via ResistanceComponent)
*
*
* Based on original IHasResistance.java from 1.12.2
diff --git a/src/main/java/com/tiedup/remake/items/base/ILockable.java b/src/main/java/com/tiedup/remake/items/base/ILockable.java
index f1b84e5..3ded9e7 100644
--- a/src/main/java/com/tiedup/remake/items/base/ILockable.java
+++ b/src/main/java/com/tiedup/remake/items/base/ILockable.java
@@ -24,11 +24,8 @@ import org.jetbrains.annotations.Nullable;
*
*
Implementations
*
- * - {@link ItemGag} - Gags can be locked
- * - {@link ItemBlindfold} - Blindfolds can be locked
- * - {@link ItemCollar} - Collars can be locked
- * - {@link ItemEarplugs} - Earplugs can be locked
- * - ItemBind - Binds can be locked (future)
+ * - {@link com.tiedup.remake.v2.bondage.items.AbstractV2BondageItem} - All V2 bondage items
+ * - {@link com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem} - Data-driven items (via AbstractV2BondageItem)
*
*
* NBT Storage
diff --git a/src/main/java/com/tiedup/remake/items/base/ItemBind.java b/src/main/java/com/tiedup/remake/items/base/ItemBind.java
deleted file mode 100644
index 5745ed8..0000000
--- a/src/main/java/com/tiedup/remake/items/base/ItemBind.java
+++ /dev/null
@@ -1,637 +0,0 @@
-package com.tiedup.remake.items.base;
-
-import com.tiedup.remake.core.SettingsAccessor;
-import com.tiedup.remake.core.TiedUpMod;
-import com.tiedup.remake.network.ModNetwork;
-import com.tiedup.remake.network.action.PacketTying;
-import com.tiedup.remake.state.IBondageState;
-import com.tiedup.remake.state.PlayerBindState;
-import com.tiedup.remake.tasks.TyingPlayerTask;
-import com.tiedup.remake.tasks.TyingTask;
-import com.tiedup.remake.util.KidnappedHelper;
-import com.tiedup.remake.util.RestraintEffectUtils;
-import com.tiedup.remake.util.TiedUpSounds;
-import com.tiedup.remake.v2.BodyRegionV2;
-import java.util.List;
-import java.util.UUID;
-import net.minecraft.ChatFormatting;
-import net.minecraft.nbt.CompoundTag;
-import net.minecraft.network.chat.Component;
-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.Item;
-import net.minecraft.world.item.ItemStack;
-import net.minecraft.world.item.TooltipFlag;
-import net.minecraft.world.item.context.UseOnContext;
-import net.minecraft.world.level.Level;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * Base class for binding/restraint items (ropes, chains, straitjacket, etc.)
- * These items restrain a player's movement and actions when equipped.
- *
- * Implements {@link IHasResistance} for the struggle/escape system.
- *
Implements {@link ILockable} for the padlock system.
- *
- * Based on original ItemBind from 1.12.2
- *
- */
-public abstract class ItemBind
- extends Item
- implements IBondageItem, IHasResistance, ILockable
-{
-
- // ========== Leg Binding: Bind Mode NBT Key ==========
- private static final String NBT_BIND_MODE = "bindMode";
-
- public ItemBind(Properties properties) {
- super(properties);
- }
-
- @Override
- public BodyRegionV2 getBodyRegion() {
- return BodyRegionV2.ARMS;
- }
-
- // ========== Leg Binding: Bind Mode Methods ==========
-
- // String constants matching NBT values
- public static final String BIND_MODE_FULL = "full";
- private static final String MODE_FULL = BIND_MODE_FULL;
- private static final String MODE_ARMS = "arms";
- private static final String MODE_LEGS = "legs";
- private static final String[] MODE_CYCLE = {
- MODE_FULL,
- MODE_ARMS,
- MODE_LEGS,
- };
- private static final java.util.Map MODE_TRANSLATION_KEYS =
- java.util.Map.of(
- MODE_FULL,
- "tiedup.bindmode.full",
- MODE_ARMS,
- "tiedup.bindmode.arms",
- MODE_LEGS,
- "tiedup.bindmode.legs"
- );
-
- /**
- * Get the bind mode ID string from the stack's NBT.
- * @param stack The bind ItemStack
- * @return "full", "arms", or "legs" (defaults to "full" if absent)
- */
- public static String getBindModeId(ItemStack stack) {
- if (stack.isEmpty()) return MODE_FULL;
- CompoundTag tag = stack.getTag();
- if (tag == null || !tag.contains(NBT_BIND_MODE)) return MODE_FULL;
- String value = tag.getString(NBT_BIND_MODE);
- if (
- MODE_FULL.equals(value) ||
- MODE_ARMS.equals(value) ||
- MODE_LEGS.equals(value)
- ) {
- return value;
- }
- return MODE_FULL;
- }
-
- /**
- * Check if arms are bound (mode is "arms" or "full").
- * @param stack The bind ItemStack
- * @return true if arms are restrained
- */
- public static boolean hasArmsBound(ItemStack stack) {
- String mode = getBindModeId(stack);
- return MODE_ARMS.equals(mode) || MODE_FULL.equals(mode);
- }
-
- /**
- * Check if legs are bound (mode is "legs" or "full").
- * @param stack The bind ItemStack
- * @return true if legs are restrained
- */
- public static boolean hasLegsBound(ItemStack stack) {
- String mode = getBindModeId(stack);
- return MODE_LEGS.equals(mode) || MODE_FULL.equals(mode);
- }
-
- /**
- * Cycle bind mode: full -> arms -> legs -> full.
- * @param stack The bind ItemStack
- * @return the new mode ID string
- */
- public static String cycleBindModeId(ItemStack stack) {
- String current = getBindModeId(stack);
- String next = MODE_FULL;
- for (int i = 0; i < MODE_CYCLE.length; i++) {
- if (MODE_CYCLE[i].equals(current)) {
- next = MODE_CYCLE[(i + 1) % MODE_CYCLE.length];
- break;
- }
- }
- stack.getOrCreateTag().putString(NBT_BIND_MODE, next);
- return next;
- }
-
- /**
- * Get the translation key for the current bind mode.
- * @param stack The bind ItemStack
- * @return the i18n key for the mode
- */
- public static String getBindModeTranslationKey(ItemStack stack) {
- return MODE_TRANSLATION_KEYS.getOrDefault(
- getBindModeId(stack),
- "tiedup.bindmode.full"
- );
- }
-
- /**
- * Called when player right-clicks in air with bind item.
- * Sneak+click cycles the bind mode.
- */
- @Override
- public InteractionResultHolder use(
- Level level,
- Player player,
- InteractionHand hand
- ) {
- ItemStack stack = player.getItemInHand(hand);
-
- // Sneak+click in air cycles bind mode
- if (player.isShiftKeyDown()) {
- if (!level.isClientSide) {
- String newModeId = cycleBindModeId(stack);
-
- // Play feedback sound
- player.playSound(SoundEvents.CHAIN_STEP, 0.5f, 1.2f);
-
- // Show action bar message
- player.displayClientMessage(
- Component.translatable(
- "tiedup.message.bindmode_changed",
- Component.translatable(getBindModeTranslationKey(stack))
- ),
- true
- );
-
- TiedUpMod.LOGGER.debug(
- "[ItemBind] {} cycled bind mode to {}",
- player.getName().getString(),
- newModeId
- );
- }
- return InteractionResultHolder.sidedSuccess(
- stack,
- level.isClientSide
- );
- }
-
- return super.use(level, player, hand);
- }
-
- @Override
- public void appendHoverText(
- ItemStack stack,
- @Nullable Level level,
- List tooltip,
- TooltipFlag flag
- ) {
- super.appendHoverText(stack, level, tooltip, flag);
-
- // Show bind mode
- tooltip.add(
- Component.translatable(
- "item.tiedup.tooltip.bindmode",
- Component.translatable(getBindModeTranslationKey(stack))
- ).withStyle(ChatFormatting.GRAY)
- );
-
- // Show lock status
- if (isLockable(stack)) {
- if (isLocked(stack)) {
- tooltip.add(
- Component.translatable(
- "item.tiedup.tooltip.locked"
- ).withStyle(ChatFormatting.RED)
- );
- } else {
- tooltip.add(
- Component.translatable(
- "item.tiedup.tooltip.lockable"
- ).withStyle(ChatFormatting.GOLD)
- );
- }
- }
- }
-
- /**
- * Called when the bind is equipped on an entity.
- * Applies movement speed reduction only if legs are bound.
- *
- * Leg Binding: Speed reduction conditional on mode
- * Based on original ItemBind.onEquipped() (1.12.2)
- */
- @Override
- public void onEquipped(ItemStack stack, LivingEntity entity) {
- String modeId = getBindModeId(stack);
-
- // Only apply speed reduction if legs are bound
- if (hasLegsBound(stack)) {
- // H6 fix: For players, speed is handled exclusively by MovementStyleManager
- // (V2 tick-based system) via MovementStyleResolver V1 fallback.
- // Applying V1 RestraintEffectUtils here would cause double stacking (different
- // UUIDs, ADDITION vs MULTIPLY_BASE) leading to quasi-immobility.
- if (entity instanceof Player) {
- TiedUpMod.LOGGER.debug(
- "[ItemBind] Applied bind (mode={}, pose={}) to player {} - speed delegated to MovementStyleManager",
- modeId,
- getPoseType().getAnimationId(),
- entity.getName().getString()
- );
- } else {
- // NPCs: MovementStyleManager only handles ServerPlayer, so NPCs
- // still need the legacy RestraintEffectUtils speed modifier.
- PoseType poseType = getPoseType();
- boolean fullImmobilization =
- poseType == PoseType.WRAP ||
- poseType == PoseType.LATEX_SACK;
-
- RestraintEffectUtils.applyBindSpeedReduction(
- entity,
- fullImmobilization
- );
- TiedUpMod.LOGGER.debug(
- "[ItemBind] Applied bind (mode={}, pose={}) to NPC {} - speed reduced (full={})",
- modeId,
- poseType.getAnimationId(),
- entity.getName().getString(),
- fullImmobilization
- );
- }
- } else {
- TiedUpMod.LOGGER.debug(
- "[ItemBind] Applied bind (mode={}) to {} - no speed reduction",
- modeId,
- entity.getName().getString()
- );
- }
- }
-
- /**
- * Called when the bind is unequipped from an entity.
- * Restores normal movement speed for all entities.
- *
- * Based on original ItemBind.onUnequipped() (1.12.2)
- */
- @Override
- public void onUnequipped(ItemStack stack, LivingEntity entity) {
- // H6 fix: For players, speed cleanup is handled by MovementStyleManager
- // (V2 tick-based system). On the next tick, the resolver will see the item
- // is gone, deactivate the style, and remove the modifier automatically.
- // NPCs still need the legacy RestraintEffectUtils cleanup.
- if (!(entity instanceof Player)) {
- RestraintEffectUtils.removeBindSpeedReduction(entity);
- }
-
- IHasResistance.super.resetCurrentResistance(stack);
-
- TiedUpMod.LOGGER.debug(
- "[ItemBind] Removed bind from {} - speed {} resistance reset",
- entity.getName().getString(),
- entity instanceof Player
- ? "delegated to MovementStyleManager,"
- : "restored,"
- );
- }
-
- // ========== Tying Interaction ==========
-
- /**
- * Called when player right-clicks another entity with this bind item.
- * Starts or continues a tying task to tie up the target entity.
- *
- * - Players: Uses tying task with progress bar
- * - NPCs: Instant bind (no tying mini-game)
- *
- * Based on original ItemBind.itemInteractionForEntity() (1.12.2)
- *
- * @param stack The item stack
- * @param player The player using the item (kidnapper)
- * @param target The entity being interacted with
- * @param hand The hand holding the item
- * @return SUCCESS if tying started/continued, PASS otherwise
- */
- @Override
- public InteractionResult interactLivingEntity(
- ItemStack stack,
- Player player,
- LivingEntity target,
- InteractionHand hand
- ) {
- // Only run on server side
- if (player.level().isClientSide) {
- return InteractionResult.SUCCESS;
- }
-
- IBondageState targetState = KidnappedHelper.getKidnappedState(target);
- if (targetState == null) {
- return InteractionResult.PASS; // Target cannot be restrained
- }
-
- // Get kidnapper state (player using the item)
- IBondageState kidnapperState = KidnappedHelper.getKidnappedState(
- player
- );
- if (kidnapperState == null) {
- return InteractionResult.FAIL;
- }
-
- // Already tied - try to swap binds (if not locked)
- // Check stack.isEmpty() first to prevent accidental unbinding when
- // the original stack was consumed (e.g., rapid clicks after tying completes)
- if (targetState.isTiedUp()) {
- if (stack.isEmpty()) {
- // No bind in hand - can't swap, just pass
- return InteractionResult.PASS;
- }
- ItemStack oldBind = targetState.replaceEquipment(
- BodyRegionV2.ARMS,
- stack.copy(),
- false
- );
- if (!oldBind.isEmpty()) {
- stack.shrink(1);
- targetState.kidnappedDropItem(oldBind);
- TiedUpMod.LOGGER.debug(
- "[ItemBind] Swapped bind on {} - dropped old bind",
- target.getName().getString()
- );
- return InteractionResult.SUCCESS;
- }
- // Locked or failed - can't swap
- return InteractionResult.PASS;
- }
-
- if (kidnapperState.isTiedUp()) {
- TiedUpMod.LOGGER.debug(
- "[ItemBind] {} tried to tie but is tied themselves",
- player.getName().getString()
- );
- return InteractionResult.PASS;
- }
-
- // SECURITY: Distance and line-of-sight validation (skip for self-tying)
- boolean isSelfTying = player.equals(target);
- if (!isSelfTying) {
- double maxTieDistance = 4.0; // Max distance to tie (blocks)
- double distance = player.distanceTo(target);
- if (distance > maxTieDistance) {
- TiedUpMod.LOGGER.warn(
- "[ItemBind] {} tried to tie {} from too far away ({} blocks)",
- player.getName().getString(),
- target.getName().getString(),
- String.format("%.1f", distance)
- );
- return InteractionResult.PASS;
- }
-
- // Check line-of-sight (must be able to see target)
- if (!player.hasLineOfSight(target)) {
- TiedUpMod.LOGGER.warn(
- "[ItemBind] {} tried to tie {} without line of sight",
- player.getName().getString(),
- target.getName().getString()
- );
- return InteractionResult.PASS;
- }
- }
-
- return handleTying(stack, player, target, targetState);
- }
-
- /**
- * Handle tying any target entity (Player or NPC).
- *
- * Uses progress-based system:
- * - update() marks the tick as active
- * - tick() in RestraintTaskTickHandler.onPlayerTick() handles progress increment/decrement
- */
- private InteractionResult handleTying(
- ItemStack stack,
- Player player,
- LivingEntity target,
- IBondageState targetState
- ) {
- // Get kidnapper's state to track the tying task
- PlayerBindState kidnapperState = PlayerBindState.getInstance(player);
- if (kidnapperState == null) {
- return InteractionResult.FAIL;
- }
-
- // Get tying duration from GameRule (default: 5 seconds)
- int tyingSeconds = getTyingDuration(player);
-
- // Get current tying task (if any)
- TyingTask currentTask = kidnapperState.getCurrentTyingTask();
-
- // Check if we should start a new task or continue existing one
- if (
- currentTask == null ||
- !currentTask.isSameTarget(target) ||
- currentTask.isStopped() ||
- !ItemStack.matches(currentTask.getBind(), stack)
- ) {
- // Create new tying task (works for both Players and NPCs)
- TyingPlayerTask newTask = new TyingPlayerTask(
- stack.copy(),
- targetState,
- target,
- tyingSeconds,
- player.level(),
- player // Pass kidnapper for SystemMessage
- );
-
- // FIX: Store the inventory slot for consumption when task completes
- // This prevents duplication AND allows refund if task is cancelled
- int sourceSlot = player.getInventory().selected;
- newTask.setSourceSlot(sourceSlot);
- newTask.setSourcePlayer(player);
-
- // Start new task
- kidnapperState.setCurrentTyingTask(newTask);
- newTask.setUpTargetState(); // Initialize target's restraint state (only for players)
- newTask.start();
- currentTask = newTask;
-
- TiedUpMod.LOGGER.debug(
- "[ItemBind] {} started tying {} ({} seconds, slot={})",
- player.getName().getString(),
- target.getName().getString(),
- tyingSeconds,
- sourceSlot
- );
- } else {
- // Continue existing task - ensure kidnapper is set
- if (currentTask instanceof TyingPlayerTask playerTask) {
- playerTask.setKidnapper(player);
- }
- }
-
- // Mark this tick as active (progress will increase in onPlayerTick)
- // The tick() method in RestraintTaskTickHandler.onPlayerTick handles progress increment/decrement
- currentTask.update();
-
- return InteractionResult.SUCCESS;
- }
-
- /**
- * Called when player right-clicks with the bind item (not targeting an entity).
- * Cancels any ongoing tying task.
- *
- * Based on original ItemBind.onItemRightClick() (1.12.2)
- *
- * @param context The use context
- * @return FAIL to cancel the action
- */
- @Override
- public InteractionResult useOn(UseOnContext context) {
- // Only run on server side
- if (context.getLevel().isClientSide) {
- return InteractionResult.SUCCESS;
- }
-
- Player player = context.getPlayer();
- if (player == null) {
- return InteractionResult.FAIL;
- }
-
- // Cancel any ongoing tying task
- PlayerBindState state = PlayerBindState.getInstance(player);
- if (state == null) {
- return InteractionResult.FAIL;
- }
-
- // Check for active tying task (unified for both players and NPCs)
- TyingTask task = state.getCurrentTyingTask();
- if (task != null) {
- task.stop();
- state.setCurrentTyingTask(null);
-
- LivingEntity target = task.getTargetEntity();
- String targetName =
- target != null ? target.getName().getString() : "???";
- String kidnapperName = player.getName().getString();
-
- // Send cancellation packet to kidnapper
- if (player instanceof ServerPlayer serverPlayer) {
- PacketTying packet = new PacketTying(
- -1,
- task.getMaxSeconds(),
- true,
- targetName
- );
- ModNetwork.sendToPlayer(packet, serverPlayer);
- }
-
- // Send cancellation packet to target (if it's a player)
- if (target instanceof ServerPlayer serverTarget) {
- PacketTying packet = new PacketTying(
- -1,
- task.getMaxSeconds(),
- false,
- kidnapperName
- );
- ModNetwork.sendToPlayer(packet, serverTarget);
- }
-
- TiedUpMod.LOGGER.debug(
- "[ItemBind] {} cancelled tying task",
- player.getName().getString()
- );
- }
-
- return InteractionResult.FAIL;
- }
-
- /**
- * Get the tying duration in seconds from GameRule.
- *
- * @param player The player (for accessing world/GameRules)
- * @return Duration in seconds (default: 5)
- */
- private int getTyingDuration(Player player) {
- return SettingsAccessor.getTyingPlayerTime(
- player.level().getGameRules()
- );
- }
-
- // ========== Resistance System (via IHasResistance) ==========
-
- /**
- * Get the item name for GameRule lookup.
- * Each subclass must implement this to return its identifier (e.g., "rope", "chain", etc.)
- *
- * @return Item name for resistance GameRule lookup
- */
- public abstract String getItemName();
-
- // ========== Pose System ==========
-
- /**
- * Get the pose type for this bind item.
- * Determines which animation/pose is applied when this item is equipped.
- *
- * Override in subclasses for special poses (straitjacket, wrap, latex_sack).
- *
- * @return PoseType for this bind (default: STANDARD)
- */
- public PoseType getPoseType() {
- return PoseType.STANDARD;
- }
-
- /**
- * Implementation of IHasResistance.getResistanceId().
- * Delegates to getItemName() for backward compatibility with subclasses.
- *
- * @return Item identifier for resistance lookup
- */
- @Override
- public String getResistanceId() {
- return getItemName();
- }
-
- /**
- * Called when the entity struggles against this bind.
- * Plays struggle sound and shows message.
- *
- * Based on original ItemBind struggle notification (1.12.2)
- *
- * @param entity The entity struggling
- */
- @Override
- public void notifyStruggle(LivingEntity entity) {
- // Play struggle sound
- TiedUpSounds.playStruggleSound(entity);
-
- // Log the struggle attempt
- TiedUpMod.LOGGER.debug(
- "[ItemBind] {} is struggling against bind",
- entity.getName().getString()
- );
-
- // Notify nearby players if the entity is a player
- if (entity instanceof ServerPlayer serverPlayer) {
- serverPlayer.displayClientMessage(
- Component.translatable("tiedup.message.struggling"),
- true // Action bar
- );
- }
- }
-
- // ILockable implementation inherited from interface default methods
-}
diff --git a/src/main/java/com/tiedup/remake/items/base/ItemBlindfold.java b/src/main/java/com/tiedup/remake/items/base/ItemBlindfold.java
deleted file mode 100644
index f3d2371..0000000
--- a/src/main/java/com/tiedup/remake/items/base/ItemBlindfold.java
+++ /dev/null
@@ -1,89 +0,0 @@
-package com.tiedup.remake.items.base;
-
-import com.tiedup.remake.core.SystemMessageManager;
-import com.tiedup.remake.util.EquipmentInteractionHelper;
-import com.tiedup.remake.v2.BodyRegionV2;
-import java.util.List;
-import net.minecraft.network.chat.Component;
-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.Item;
-import net.minecraft.world.item.ItemStack;
-import net.minecraft.world.item.TooltipFlag;
-import net.minecraft.world.level.Level;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * Base class for blindfold items (classic blindfold, mask, hood, etc.)
- * These items obstruct a player's vision when equipped.
- *
- * Based on original ItemBlindfold from 1.12.2
- *
- */
-public abstract class ItemBlindfold
- extends Item
- implements IBondageItem, IHasBlindingEffect, IAdjustable, ILockable
-{
-
- public ItemBlindfold(Properties properties) {
- super(properties);
- }
-
- @Override
- public BodyRegionV2 getBodyRegion() {
- return BodyRegionV2.EYES;
- }
-
- @Override
- public void appendHoverText(
- ItemStack stack,
- @Nullable Level level,
- List tooltip,
- TooltipFlag flag
- ) {
- super.appendHoverText(stack, level, tooltip, flag);
- appendLockTooltip(stack, tooltip);
- }
-
- /**
- * All blindfolds can be adjusted to better fit player skins.
- * @return true - blindfolds support position adjustment
- */
- @Override
- public boolean canBeAdjusted() {
- return true;
- }
-
- /**
- * Called when player right-clicks another entity with this blindfold.
- * Allows putting blindfold on tied-up entities (Players and NPCs).
- */
- @Override
- public InteractionResult interactLivingEntity(
- ItemStack stack,
- Player player,
- LivingEntity target,
- InteractionHand hand
- ) {
- return EquipmentInteractionHelper.equipOnTarget(
- stack,
- player,
- target,
- state -> state.isBlindfolded(),
- (state, item) -> state.equip(BodyRegionV2.EYES, item),
- (state, item) ->
- state.replaceEquipment(BodyRegionV2.EYES, item, false),
- (p, t) ->
- SystemMessageManager.sendToTarget(
- p,
- t,
- SystemMessageManager.MessageCategory.BLINDFOLDED
- ),
- "ItemBlindfold"
- );
- }
-
- // ILockable implementation inherited from interface default methods
-}
diff --git a/src/main/java/com/tiedup/remake/items/base/ItemCollar.java b/src/main/java/com/tiedup/remake/items/base/ItemCollar.java
deleted file mode 100644
index d026488..0000000
--- a/src/main/java/com/tiedup/remake/items/base/ItemCollar.java
+++ /dev/null
@@ -1,1407 +0,0 @@
-package com.tiedup.remake.items.base;
-
-import com.tiedup.remake.core.SettingsAccessor;
-import com.tiedup.remake.core.SystemMessageManager;
-import com.tiedup.remake.core.TiedUpMod;
-import com.tiedup.remake.network.sync.SyncManager;
-import com.tiedup.remake.state.CollarRegistry;
-import com.tiedup.remake.state.IBondageState;
-import com.tiedup.remake.util.KidnappedHelper;
-import com.tiedup.remake.util.teleport.Position;
-import com.tiedup.remake.v2.BodyRegionV2;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.UUID;
-import net.minecraft.ChatFormatting;
-import net.minecraft.nbt.CompoundTag;
-import net.minecraft.nbt.ListTag;
-import net.minecraft.nbt.Tag;
-import net.minecraft.network.chat.Component;
-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.Item;
-import net.minecraft.world.item.ItemStack;
-import net.minecraft.world.item.TooltipFlag;
-import net.minecraft.world.level.Level;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * Base class for collar items (classic collar, shock collar, GPS collar, etc.)
- * These items mark ownership and can have various special effects.
- *
- * Based on original ItemCollar from 1.12.2
- *
- *
- * Note: Collars have maxStackSize of 1 (unique items)
- */
-public abstract class ItemCollar
- extends Item
- implements IBondageItem, ILockable
-{
-
- // NBT Keys - Basic
- private static final String NBT_OWNERS = "owners";
- private static final String NBT_LOCKED = "locked";
- private static final String NBT_LOCKED_BY_KEY_UUID = "lockedByKeyUUID";
- private static final String NBT_NICKNAME = "nickname";
- private static final String NBT_CURRENT_RESISTANCE = "currentResistance";
- private static final String NBT_CURRENT_RESISTANCE_LEGACY =
- "currentresistance";
- private static final String NBT_CAN_BE_STRUGGLED_OUT = "canBeStruggledOut";
-
- // NBT Keys - Kidnapping Mode
- private static final String NBT_KIDNAPPING_MODE = "kidnappingMode";
- private static final String NBT_TIE_TO_POLE = "tieToPole";
- private static final String NBT_WARN_MASTERS = "warnMasters";
- private static final String NBT_BONDAGE_SERVICE = "bondageservice";
- private static final String NBT_SERVICE_SENTENCE = "servicesentence";
-
- // NBT Keys - Blacklist/Whitelist
- private static final String NBT_BLACKLIST = "blacklist";
- private static final String NBT_WHITELIST = "whitelist";
-
- // NBT Keys - Cell System
- private static final String NBT_CELL_ID = "cellId";
-
- /**
- * ThreadLocal flag to suppress alert on legitimate collar removals.
- * Set this to true before removing a collar legitimately (camp death, ransom paid, etc.)
- * and reset to false afterward.
- *
- * CRITICAL FIX: Use runWithSuppressedAlert() wrapper to ensure proper cleanup.
- */
- private static final ThreadLocal SUPPRESS_REMOVAL_ALERT =
- ThreadLocal.withInitial(() -> false);
-
- /**
- * CRITICAL FIX: Safe wrapper for legitimate collar removals.
- * Automatically handles ThreadLocal lifecycle to prevent memory leaks.
- *
- * Use this instead of manual beginLegitimateRemoval() / endLegitimateRemoval().
- *
- * @param action The action to perform with suppressed alerts
- */
- public static void runWithSuppressedAlert(Runnable action) {
- SUPPRESS_REMOVAL_ALERT.set(true);
- try {
- action.run();
- } finally {
- SUPPRESS_REMOVAL_ALERT.set(false);
- }
- }
-
- /**
- * DEPRECATED: Use runWithSuppressedAlert() instead.
- * Begin a legitimate collar removal that should NOT alert kidnappers.
- * @deprecated Use {@link #runWithSuppressedAlert(Runnable)} to prevent ThreadLocal leaks
- */
- @Deprecated
- public static void beginLegitimateRemoval() {
- SUPPRESS_REMOVAL_ALERT.set(true);
- }
-
- /**
- * DEPRECATED: Use runWithSuppressedAlert() instead.
- * End a legitimate collar removal sequence.
- * @deprecated Use {@link #runWithSuppressedAlert(Runnable)} to prevent ThreadLocal leaks
- */
- @Deprecated
- public static void endLegitimateRemoval() {
- SUPPRESS_REMOVAL_ALERT.set(false);
- }
-
- /**
- * Check if removal alerts are currently suppressed.
- */
- public static boolean isRemovalAlertSuppressed() {
- return SUPPRESS_REMOVAL_ALERT.get();
- }
-
- public ItemCollar(Properties properties) {
- super(properties.stacksTo(1)); // Collars are unique items
- }
-
- @Override
- public void appendHoverText(
- ItemStack stack,
- @Nullable Level level,
- List tooltip,
- TooltipFlag flag
- ) {
- // Nickname
- if (hasNickname(stack)) {
- tooltip.add(
- Component.literal("Nickname: ")
- .withStyle(ChatFormatting.AQUA)
- .append(
- Component.literal(getNickname(stack)).withStyle(
- ChatFormatting.WHITE
- )
- )
- );
- }
-
- // Locked status
- if (isLocked(stack)) {
- tooltip.add(
- Component.literal("Locked").withStyle(ChatFormatting.RED)
- );
- }
-
- // Kidnapping mode
- if (isKidnappingModeEnabled(stack)) {
- tooltip.add(
- Component.literal("Kidnapping Mode: ON").withStyle(
- ChatFormatting.DARK_RED
- )
- );
- }
-
- // Additional flags
- if (shouldTieToPole(stack)) {
- tooltip.add(
- Component.literal("Tie to Pole: ON").withStyle(
- ChatFormatting.GRAY
- )
- );
- }
- if (isBondageServiceEnabled(stack)) {
- tooltip.add(
- Component.literal("Bondage Service: ON").withStyle(
- ChatFormatting.LIGHT_PURPLE
- )
- );
- }
-
- // Cell assignment info
- UUID cellId = getCellId(stack);
- if (cellId != null) {
- tooltip.add(
- Component.literal("Cell: ")
- .withStyle(ChatFormatting.DARK_PURPLE)
- .append(
- Component.literal(
- cellId.toString().substring(0, 8) + "..."
- ).withStyle(ChatFormatting.LIGHT_PURPLE)
- )
- );
- }
- }
-
- public String getNickname(ItemStack stack) {
- CompoundTag tag = stack.getTag();
- if (tag != null && tag.contains(NBT_NICKNAME)) {
- return tag.getString(NBT_NICKNAME);
- }
- return null;
- }
-
- public void setNickname(ItemStack stack, String nickname) {
- stack.getOrCreateTag().putString(NBT_NICKNAME, nickname);
- }
-
- public boolean hasNickname(ItemStack stack) {
- return stack.hasTag() && stack.getTag().contains(NBT_NICKNAME);
- }
-
- @Override
- public BodyRegionV2 getBodyRegion() {
- return BodyRegionV2.NECK;
- }
-
- /**
- * Get the item name for GameRule lookup.
- * Used by SettingsAccessor to find resistance value.
- *
- * @return "collar"
- */
- public String getItemName() {
- return "collar";
- }
-
- /**
- * Check if this collar can shock the wearer.
- * Override in shock collar subclasses.
- *
- * @return true if collar has shock capability
- */
- public boolean canShock() {
- return false;
- }
-
- /**
- * Check if this collar has GPS tracking.
- * Override in GPS collar subclasses.
- *
- * @return true if collar has GPS capability
- */
- public boolean hasGPS() {
- return false;
- }
-
- /**
- * Called when player right-clicks another entity with this collar.
- * Allows putting collar on tied-up entities (Players and NPCs) and adds the player as owner.
- *
- * Based on original ItemCollar.itemInteractionForEntity()
- *
- * @param stack The item stack
- * @param player The player using the item
- * @param target The entity being interacted with
- * @param hand The hand holding the item
- * @return SUCCESS if collar equipped/replaced, PASS otherwise
- */
- @Override
- public InteractionResult interactLivingEntity(
- ItemStack stack,
- Player player,
- LivingEntity target,
- InteractionHand hand
- ) {
- // Server-side only
- if (player.level().isClientSide) {
- return InteractionResult.SUCCESS;
- }
-
- // Check if target can be collared (Player, EntityDamsel, EntityKidnapper)
- IBondageState targetState = KidnappedHelper.getKidnappedState(target);
- if (targetState == null) {
- return InteractionResult.PASS; // Entity cannot be collared
- }
-
- // Must be tied up
- if (!targetState.isTiedUp()) {
- return InteractionResult.PASS;
- }
-
- ItemStack newCollar = stack.copy();
- if (!isOwner(newCollar, player)) {
- addOwner(newCollar, player);
- }
-
- // Case 1: No collar yet - equip new one
- if (!targetState.hasCollar()) {
- targetState.equip(BodyRegionV2.NECK, newCollar);
- stack.shrink(1);
-
- registerCollarInRegistry(target, newCollar, player);
-
- // Send screen message to target
- SystemMessageManager.sendToTarget(
- player,
- target,
- SystemMessageManager.MessageCategory.COLLARED
- );
-
- // Sync equipment to all tracking clients
- if (target instanceof ServerPlayer serverPlayer) {
- SyncManager.syncInventory(serverPlayer);
- }
-
- TiedUpMod.LOGGER.info(
- "[ItemCollar] {} put collar on {}",
- player.getName().getString(),
- target.getName().getString()
- );
-
- // Play custom put sound
- player
- .level()
- .playSound(
- null,
- player.blockPosition(),
- com.tiedup.remake.core.ModSounds.COLLAR_PUT.get(),
- net.minecraft.sounds.SoundSource.PLAYERS,
- 1.0f,
- 1.0f
- );
-
- return InteractionResult.SUCCESS;
- }
- // Case 2: Already has collar - replace it
- else {
- ItemStack oldCollar = targetState.getEquipment(BodyRegionV2.NECK);
- if (
- oldCollar != null && oldCollar.getItem() instanceof ItemCollar
- ) {
- ItemCollar oldCollarItem = (ItemCollar) oldCollar.getItem();
-
- // Check if old collar is locked
- if (oldCollarItem.isLocked(oldCollar)) {
- // Cannot replace locked collar
- TiedUpMod.LOGGER.info(
- "[ItemCollar] {} tried to replace locked collar on {}",
- player.getName().getString(),
- target.getName().getString()
- );
-
- // Send error message to player (kidnapper)
- SystemMessageManager.sendToPlayer(
- player,
- SystemMessageManager.MessageCategory.ERROR,
- "Target's collar is locked! You cannot replace it."
- );
- return InteractionResult.FAIL;
- }
-
- // Old collar not locked - replace it
- ItemStack replacedCollar = targetState.replaceEquipment(
- BodyRegionV2.NECK,
- newCollar,
- false
- );
- if (replacedCollar != null) {
- stack.shrink(1);
- targetState.kidnappedDropItem(replacedCollar);
-
- unregisterCollarFromRegistry(target);
- registerCollarInRegistry(target, newCollar, player);
-
- // Send screen message to target
- SystemMessageManager.sendToTarget(
- player,
- target,
- SystemMessageManager.MessageCategory.COLLARED
- );
-
- // Sync equipment to all tracking clients
- if (target instanceof ServerPlayer serverPlayer) {
- SyncManager.syncInventory(serverPlayer);
- }
-
- TiedUpMod.LOGGER.info(
- "[ItemCollar] {} replaced collar on {}",
- player.getName().getString(),
- target.getName().getString()
- );
-
- return InteractionResult.SUCCESS;
- }
- }
- }
-
- return InteractionResult.PASS;
- }
-
- /**
- * Add an owner to this collar.
- * Owners are stored as UUID + name pairs in NBT.
- *
- * From original ItemCollar.addOwner()
- *
- * @param stack The collar ItemStack
- * @param ownerUUID The owner's UUID
- * @param ownerName The owner's display name
- */
- public void addOwner(ItemStack stack, UUID ownerUUID, String ownerName) {
- if (stack.isEmpty() || ownerUUID == null) {
- return;
- }
-
- CompoundTag tag = stack.getOrCreateTag();
- ListTag owners = tag.contains(NBT_OWNERS)
- ? tag.getList(NBT_OWNERS, Tag.TAG_COMPOUND)
- : new ListTag();
-
- // Check if already owner
- for (int i = 0; i < owners.size(); i++) {
- CompoundTag ownerTag = owners.getCompound(i);
- if (ownerTag.getUUID("uuid").equals(ownerUUID)) {
- TiedUpMod.LOGGER.debug(
- "[ItemCollar] {} is already an owner",
- ownerName
- );
- return;
- }
- }
-
- // Add new owner
- CompoundTag ownerTag = new CompoundTag();
- ownerTag.putUUID("uuid", ownerUUID);
- ownerTag.putString("name", ownerName != null ? ownerName : "Unknown");
- owners.add(ownerTag);
- tag.put(NBT_OWNERS, owners);
-
- TiedUpMod.LOGGER.info("[ItemCollar] Added {} as owner", ownerName);
- }
-
- /**
- * Add an owner to this collar (player version).
- *
- * @param stack The collar ItemStack
- * @param owner The owner player
- */
- public void addOwner(ItemStack stack, Player owner) {
- if (owner == null) {
- return;
- }
- addOwner(stack, owner.getUUID(), owner.getName().getString());
- }
-
- /**
- * Remove an owner from this collar.
- *
- * @param stack The collar ItemStack
- * @param ownerUUID The owner's UUID to remove
- */
- public void removeOwner(ItemStack stack, UUID ownerUUID) {
- if (stack.isEmpty() || ownerUUID == null) {
- return;
- }
-
- CompoundTag tag = stack.getTag();
- if (tag == null || !tag.contains(NBT_OWNERS)) {
- return;
- }
-
- ListTag owners = tag.getList(NBT_OWNERS, Tag.TAG_COMPOUND);
- ListTag newOwners = new ListTag();
-
- for (int i = 0; i < owners.size(); i++) {
- CompoundTag ownerTag = owners.getCompound(i);
- if (!ownerTag.getUUID("uuid").equals(ownerUUID)) {
- newOwners.add(ownerTag);
- }
- }
-
- tag.put(NBT_OWNERS, newOwners);
- TiedUpMod.LOGGER.info("[ItemCollar] Removed owner {}", ownerUUID);
- }
-
- /**
- * Get the list of all owners.
- *
- * @param stack The collar ItemStack
- * @return List of owner UUIDs
- */
- public List getOwners(ItemStack stack) {
- if (stack.isEmpty()) {
- return new ArrayList<>();
- }
-
- CompoundTag tag = stack.getTag();
- if (tag == null || !tag.contains(NBT_OWNERS)) {
- return new ArrayList<>();
- }
-
- ListTag owners = tag.getList(NBT_OWNERS, Tag.TAG_COMPOUND);
- List result = new ArrayList<>();
-
- for (int i = 0; i < owners.size(); i++) {
- result.add(owners.getCompound(i).getUUID("uuid"));
- }
-
- return result;
- }
-
- /**
- * Check if the given player is an owner of this collar.
- * Optimized: uses direct NBT lookup with early-return instead of building list.
- *
- * @param stack The collar ItemStack
- * @param player The player to check
- * @return true if player is an owner
- */
- public boolean isOwner(ItemStack stack, Player player) {
- if (player == null) {
- return false;
- }
- return hasUUIDInList(stack, NBT_OWNERS, player.getUUID());
- }
-
- /**
- * Check if this collar has any owners.
- *
- * @param stack The collar ItemStack
- * @return true if has at least one owner
- */
- public boolean hasOwner(ItemStack stack) {
- return !getOwners(stack).isEmpty();
- }
-
- /**
- * Add a player to this collar's blacklist.
- * Blacklisted players will not be targeted when kidnapping mode is active.
- *
- * @param stack The collar ItemStack
- * @param uuid The player's UUID
- * @param name The player's display name
- */
- public void addToBlacklist(ItemStack stack, UUID uuid, String name) {
- addToList(stack, NBT_BLACKLIST, uuid, name);
- }
-
- /**
- * Add a player to this collar's blacklist (player version).
- */
- public void addToBlacklist(ItemStack stack, Player player) {
- if (player == null) return;
- addToBlacklist(stack, player.getUUID(), player.getName().getString());
- }
-
- /**
- * Remove a player from this collar's blacklist.
- */
- public void removeFromBlacklist(ItemStack stack, UUID uuid) {
- removeFromList(stack, NBT_BLACKLIST, uuid);
- }
-
- /**
- * Get all blacklisted player UUIDs.
- */
- public List getBlacklist(ItemStack stack) {
- return getListUUIDs(stack, NBT_BLACKLIST);
- }
-
- /**
- * Check if a player is blacklisted.
- * Optimized: uses direct NBT lookup with early-return instead of building list.
- */
- public boolean isBlacklisted(ItemStack stack, UUID uuid) {
- return hasUUIDInList(stack, NBT_BLACKLIST, uuid);
- }
-
- /**
- * Check if a player is blacklisted (player version).
- */
- public boolean isBlacklisted(ItemStack stack, Player player) {
- return player != null && isBlacklisted(stack, player.getUUID());
- }
-
- /**
- * Add a player to this collar's whitelist.
- * When whitelist is not empty, ONLY whitelisted players will be targeted.
- *
- * @param stack The collar ItemStack
- * @param uuid The player's UUID
- * @param name The player's display name
- */
- public void addToWhitelist(ItemStack stack, UUID uuid, String name) {
- addToList(stack, NBT_WHITELIST, uuid, name);
- }
-
- /**
- * Add a player to this collar's whitelist (player version).
- */
- public void addToWhitelist(ItemStack stack, Player player) {
- if (player == null) return;
- addToWhitelist(stack, player.getUUID(), player.getName().getString());
- }
-
- /**
- * Remove a player from this collar's whitelist.
- */
- public void removeFromWhitelist(ItemStack stack, UUID uuid) {
- removeFromList(stack, NBT_WHITELIST, uuid);
- }
-
- /**
- * Get all whitelisted player UUIDs.
- */
- public List getWhitelist(ItemStack stack) {
- return getListUUIDs(stack, NBT_WHITELIST);
- }
-
- /**
- * Check if a player is whitelisted.
- * Optimized: uses direct NBT lookup with early-return instead of building list.
- */
- public boolean isWhitelisted(ItemStack stack, UUID uuid) {
- return hasUUIDInList(stack, NBT_WHITELIST, uuid);
- }
-
- /**
- * Check if a player is whitelisted (player version).
- */
- public boolean isWhitelisted(ItemStack stack, Player player) {
- return player != null && isWhitelisted(stack, player.getUUID());
- }
-
- // ========== Helper methods for list management ==========
-
- /**
- * Check if a UUID exists in a specific list (owners, blacklist, whitelist).
- * Optimized O(n) with early-return instead of O(2n) from building list + contains.
- *
- * @param stack The collar ItemStack
- * @param listKey The NBT key for the list
- * @param uuid The UUID to check
- * @return true if UUID is in the list
- */
- private boolean hasUUIDInList(ItemStack stack, String listKey, UUID uuid) {
- if (stack.isEmpty() || uuid == null) return false;
-
- CompoundTag tag = stack.getTag();
- if (tag == null || !tag.contains(listKey)) return false;
-
- ListTag list = tag.getList(listKey, Tag.TAG_COMPOUND);
- for (int i = 0; i < list.size(); i++) {
- if (list.getCompound(i).getUUID("uuid").equals(uuid)) {
- return true; // Early return - no need to build full list
- }
- }
- return false;
- }
-
- private void addToList(
- ItemStack stack,
- String listKey,
- UUID uuid,
- String name
- ) {
- if (stack.isEmpty() || uuid == null) return;
-
- CompoundTag tag = stack.getOrCreateTag();
- ListTag list = tag.contains(listKey)
- ? tag.getList(listKey, Tag.TAG_COMPOUND)
- : new ListTag();
-
- // Check if already in list
- for (int i = 0; i < list.size(); i++) {
- CompoundTag entry = list.getCompound(i);
- if (entry.getUUID("uuid").equals(uuid)) {
- return; // Already in list
- }
- }
-
- // Add new entry
- CompoundTag entry = new CompoundTag();
- entry.putUUID("uuid", uuid);
- entry.putString("name", name != null ? name : "Unknown");
- list.add(entry);
- tag.put(listKey, list);
- }
-
- private void removeFromList(ItemStack stack, String listKey, UUID uuid) {
- if (stack.isEmpty() || uuid == null) return;
-
- CompoundTag tag = stack.getTag();
- if (tag == null || !tag.contains(listKey)) return;
-
- ListTag list = tag.getList(listKey, Tag.TAG_COMPOUND);
- ListTag newList = new ListTag();
-
- for (int i = 0; i < list.size(); i++) {
- CompoundTag entry = list.getCompound(i);
- if (!entry.getUUID("uuid").equals(uuid)) {
- newList.add(entry);
- }
- }
-
- tag.put(listKey, newList);
- }
-
- private List getListUUIDs(ItemStack stack, String listKey) {
- if (stack.isEmpty()) return new ArrayList<>();
-
- CompoundTag tag = stack.getTag();
- if (tag == null || !tag.contains(listKey)) return new ArrayList<>();
-
- ListTag list = tag.getList(listKey, Tag.TAG_COMPOUND);
- List result = new ArrayList<>();
-
- for (int i = 0; i < list.size(); i++) {
- result.add(list.getCompound(i).getUUID("uuid"));
- }
-
- return result;
- }
-
- /**
- * Check if this collar is locked.
- * Locked collars cannot be removed normally.
- *
- *
- * @param stack The collar ItemStack
- * @return true if locked
- */
- @Override
- public boolean isLocked(ItemStack stack) {
- if (stack.isEmpty()) {
- return false;
- }
- CompoundTag tag = stack.getTag();
- return tag != null && tag.getBoolean(NBT_LOCKED);
- }
-
- /**
- * Set the locked state of this collar.
- *
- *
- * @param stack The collar ItemStack
- * @param locked true to lock, false to unlock
- * @return The modified ItemStack for chaining
- */
- @Override
- public ItemStack setLocked(ItemStack stack, boolean locked) {
- if (stack.isEmpty()) {
- return stack;
- }
- stack.getOrCreateTag().putBoolean(NBT_LOCKED, locked);
- TiedUpMod.LOGGER.debug("[ItemCollar] Set locked={}", locked);
- return stack;
- }
-
- /**
- * Check if this collar can be locked.
- * By default, all collars are lockable.
- *
- *
- * @param stack The collar ItemStack
- * @return true if lockable (can accept a padlock)
- */
- @Override
- public boolean isLockable(ItemStack stack) {
- if (stack.isEmpty()) {
- return false;
- }
- CompoundTag tag = stack.getTag();
- // By default, collars are lockable unless explicitly set to false
- return (
- tag == null ||
- !tag.contains("lockable") ||
- tag.getBoolean("lockable")
- );
- }
-
- /**
- * Set whether this collar can be locked (lockable state).
- *
- *
- * @param stack The collar ItemStack
- * @param state true to make lockable, false to prevent locking
- * @return The modified ItemStack for chaining
- */
- @Override
- public ItemStack setLockable(ItemStack stack, boolean state) {
- if (stack.isEmpty()) {
- return stack;
- }
- stack.getOrCreateTag().putBoolean("lockable", state);
- return stack;
- }
-
- /**
- * Check if the padlock should be dropped when unlocking.
- * Collars have a built-in lock mechanism, so no padlock to drop.
- *
- *
- * @return false (no padlock to drop for collars)
- */
- @Override
- public boolean dropLockOnUnlock() {
- return false;
- }
-
- // ========== Key-Lock System ==========
-
- /**
- * Get the UUID of the key that locked this collar.
- *
- * @param stack The collar ItemStack
- * @return The key UUID or null if not locked with a key
- */
- @Override
- @Nullable
- public UUID getLockedByKeyUUID(ItemStack stack) {
- if (stack.isEmpty()) return null;
- CompoundTag tag = stack.getTag();
- if (tag == null || !tag.hasUUID(NBT_LOCKED_BY_KEY_UUID)) return null;
- return tag.getUUID(NBT_LOCKED_BY_KEY_UUID);
- }
-
- /**
- * Set the key UUID that locks this collar.
- * Setting a non-null UUID will also set locked=true.
- * Setting null will unlock the collar.
- *
- * @param stack The collar ItemStack
- * @param keyUUID The key UUID or null to unlock
- */
- @Override
- public void setLockedByKeyUUID(ItemStack stack, @Nullable UUID keyUUID) {
- if (stack.isEmpty()) return;
- CompoundTag tag = stack.getOrCreateTag();
- if (keyUUID == null) {
- tag.remove(NBT_LOCKED_BY_KEY_UUID);
- tag.putBoolean(NBT_LOCKED, false);
- } else {
- tag.putUUID(NBT_LOCKED_BY_KEY_UUID, keyUUID);
- tag.putBoolean(NBT_LOCKED, true);
- }
- }
-
- /**
- * Get the base resistance for this collar from config.
- *
- * BUG-003 fix: Now reads from SettingsAccessor (config) instead of GameRules
- *
- * @param entity The entity (kept for API compatibility)
- * @return Base resistance value from config
- */
- public int getBaseResistance(LivingEntity entity) {
- return SettingsAccessor.getBindResistance(getItemName());
- }
-
- /**
- * Get the current resistance of this collar.
- * Returns base resistance if not yet set.
- *
- *
- * @param stack The collar ItemStack
- * @param entity The entity (for GameRules lookup)
- * @return Current resistance
- */
- public int getCurrentResistance(ItemStack stack, LivingEntity entity) {
- if (stack.isEmpty()) {
- return 0;
- }
-
- CompoundTag tag = stack.getTag();
- if (tag != null) {
- // Check new camelCase key first
- if (tag.contains(NBT_CURRENT_RESISTANCE)) {
- return tag.getInt(NBT_CURRENT_RESISTANCE);
- }
- // Migration: check legacy lowercase key
- else if (tag.contains(NBT_CURRENT_RESISTANCE_LEGACY)) {
- int resistance = tag.getInt(NBT_CURRENT_RESISTANCE_LEGACY);
- // Migrate to new key
- tag.remove(NBT_CURRENT_RESISTANCE_LEGACY);
- if (resistance > 0) {
- tag.putInt(NBT_CURRENT_RESISTANCE, resistance);
- }
- return resistance > 0 ? resistance : getBaseResistance(entity);
- }
- }
-
- // Not set yet - return base resistance
- return getBaseResistance(entity);
- }
-
- /**
- * Set the current resistance of this collar.
- *
- * @param stack The collar ItemStack
- * @param resistance The new resistance value
- */
- public void setCurrentResistance(ItemStack stack, int resistance) {
- if (stack.isEmpty()) {
- return;
- }
- stack.getOrCreateTag().putInt(NBT_CURRENT_RESISTANCE, resistance);
- }
-
- /**
- * Reset the current resistance to base value.
- *
- *
- * @param stack The collar ItemStack
- * @param entity The entity (for GameRules lookup)
- */
- public void resetCurrentResistance(ItemStack stack, LivingEntity entity) {
- setCurrentResistance(stack, getBaseResistance(entity));
- }
-
- /**
- * Check if this collar can be struggled out of.
- *
- * @param stack The collar ItemStack
- * @return true if struggle is enabled
- */
- public boolean canBeStruggledOut(ItemStack stack) {
- if (stack.isEmpty()) {
- return false;
- }
-
- CompoundTag tag = stack.getTag();
- if (tag != null && tag.contains(NBT_CAN_BE_STRUGGLED_OUT)) {
- return tag.getBoolean(NBT_CAN_BE_STRUGGLED_OUT);
- }
-
- return true; // Default: can struggle
- }
-
- /**
- * Set whether this collar can be struggled out of.
- *
- * @param stack The collar ItemStack
- * @param canStruggle true to enable struggle
- */
- public void setCanBeStruggledOut(ItemStack stack, boolean canStruggle) {
- if (stack.isEmpty()) {
- return;
- }
- stack
- .getOrCreateTag()
- .putBoolean(NBT_CAN_BE_STRUGGLED_OUT, canStruggle);
- }
-
- // Cell ID (Assigned Cell)
-
- /**
- * Get the assigned cell ID from this collar.
- *
- * @param stack The collar ItemStack
- * @return Cell UUID or null if not assigned
- */
- @Nullable
- public UUID getCellId(ItemStack stack) {
- if (stack.isEmpty()) {
- return null;
- }
-
- CompoundTag tag = stack.getTag();
- if (tag == null || !tag.hasUUID(NBT_CELL_ID)) {
- return null;
- }
-
- return tag.getUUID(NBT_CELL_ID);
- }
-
- /**
- * Set the assigned cell ID on this collar.
- *
- * @param stack The collar ItemStack
- * @param cellId The cell UUID, or null to clear
- */
- public void setCellId(ItemStack stack, @Nullable UUID cellId) {
- if (stack.isEmpty()) {
- return;
- }
-
- CompoundTag tag = stack.getOrCreateTag();
- if (cellId == null) {
- tag.remove(NBT_CELL_ID);
- } else {
- tag.putUUID(NBT_CELL_ID, cellId);
- }
- }
-
- /**
- * Check if this collar has a cell assigned.
- *
- * @param stack The collar ItemStack
- * @return true if a cell is assigned
- */
- public boolean hasCellAssigned(ItemStack stack) {
- return getCellId(stack) != null;
- }
-
- // Kidnapping Mode
-
- /**
- * Check if kidnapping mode is enabled.
- * Kidnapping mode allows NPC kidnappers to auto-capture and teleport to prison.
- *
- * @param stack The collar ItemStack
- * @return true if kidnapping mode enabled
- */
- public boolean isKidnappingModeEnabled(ItemStack stack) {
- if (stack.isEmpty()) {
- return false;
- }
-
- CompoundTag tag = stack.getTag();
- return tag != null && tag.getBoolean(NBT_KIDNAPPING_MODE);
- }
-
- /**
- * Set kidnapping mode state.
- *
- * @param stack The collar ItemStack
- * @param enabled true to enable kidnapping mode
- */
- public void setKidnappingModeEnabled(ItemStack stack, boolean enabled) {
- if (stack.isEmpty()) {
- return;
- }
-
- stack.getOrCreateTag().putBoolean(NBT_KIDNAPPING_MODE, enabled);
- TiedUpMod.LOGGER.info(
- "[ItemCollar] Kidnapping mode set to {}",
- enabled
- );
- }
-
- /**
- * Check if kidnapping mode is fully configured and ready.
- * Requires: kidnapping mode ON + cell assigned
- *
- * @param stack The collar ItemStack
- * @return true if ready for automated kidnapping
- */
- public boolean isKidnappingModeReady(ItemStack stack) {
- if (!isKidnappingModeEnabled(stack)) return false;
- return hasCellAssigned(stack);
- }
-
- // Tie to Pole (Auto-tie slave in cell)
-
- /**
- * Check if slave should be auto-tied to nearest pole in prison.
- *
- * @param stack The collar ItemStack
- * @return true if should tie to pole
- */
- public boolean shouldTieToPole(ItemStack stack) {
- if (stack.isEmpty()) {
- return false;
- }
-
- CompoundTag tag = stack.getTag();
- return tag != null && tag.getBoolean(NBT_TIE_TO_POLE);
- }
-
- /**
- * Set whether slave should be tied to pole in prison.
- *
- * @param stack The collar ItemStack
- * @param enabled true to enable tie to pole
- */
- public void setTieToPole(ItemStack stack, boolean enabled) {
- if (stack.isEmpty()) {
- return;
- }
-
- stack.getOrCreateTag().putBoolean(NBT_TIE_TO_POLE, enabled);
- }
-
- // Warn Masters
-
- /**
- * Check if owners should be warned when slave is captured.
- *
- * @param stack The collar ItemStack
- * @return true if should warn masters
- */
- public boolean shouldWarnMasters(ItemStack stack) {
- if (stack.isEmpty()) {
- return true; // Default: warn
- }
-
- CompoundTag tag = stack.getTag();
- if (tag != null && tag.contains(NBT_WARN_MASTERS)) {
- return tag.getBoolean(NBT_WARN_MASTERS);
- }
-
- return true; // Default: warn
- }
-
- /**
- * Set whether owners should be warned.
- *
- * @param stack The collar ItemStack
- * @param enabled true to enable warnings
- */
- public void setWarnMasters(ItemStack stack, boolean enabled) {
- if (stack.isEmpty()) {
- return;
- }
-
- stack.getOrCreateTag().putBoolean(NBT_WARN_MASTERS, enabled);
- }
-
- // Bondage Service
-
- /**
- * Check if bondage service is enabled on this collar.
- * Bondage service allows a Damsel to automatically capture attacking players.
- *
- * @param stack The collar ItemStack
- * @return true if bondage service is enabled
- */
- public boolean isBondageServiceEnabled(ItemStack stack) {
- if (stack.isEmpty()) {
- return false;
- }
-
- CompoundTag tag = stack.getTag();
- return tag != null && tag.getBoolean(NBT_BONDAGE_SERVICE);
- }
-
- /**
- * Enable or disable bondage service on this collar.
- *
- * @param stack The collar ItemStack
- * @param enabled true to enable bondage service
- */
- public void setBondageServiceEnabled(ItemStack stack, boolean enabled) {
- if (stack.isEmpty()) {
- return;
- }
-
- stack.getOrCreateTag().putBoolean(NBT_BONDAGE_SERVICE, enabled);
- }
-
- /**
- * Get the custom service sentence message.
- *
- * @param stack The collar ItemStack
- * @return Custom message or null if not set
- */
- @Nullable
- public String getServiceSentence(ItemStack stack) {
- if (stack.isEmpty()) {
- return null;
- }
-
- CompoundTag tag = stack.getTag();
- if (tag != null && tag.contains(NBT_SERVICE_SENTENCE)) {
- return tag.getString(NBT_SERVICE_SENTENCE);
- }
- return null;
- }
-
- /**
- * Set a custom service sentence message.
- *
- * @param stack The collar ItemStack
- * @param sentence The custom message
- */
- public void setServiceSentence(ItemStack stack, String sentence) {
- if (stack.isEmpty()) {
- return;
- }
-
- if (sentence == null || sentence.isEmpty()) {
- stack.getOrCreateTag().remove(NBT_SERVICE_SENTENCE);
- } else {
- stack.getOrCreateTag().putString(NBT_SERVICE_SENTENCE, sentence);
- }
- }
-
- /**
- * Register a collar in the global CollarRegistry.
- * Called when a collar is put on an entity.
- *
- * @param wearer The entity wearing the collar
- * @param collarStack The collar ItemStack
- * @param primaryOwner The player putting the collar on (primary owner)
- */
- private void registerCollarInRegistry(
- LivingEntity wearer,
- ItemStack collarStack,
- Player primaryOwner
- ) {
- if (wearer == null || wearer.level().isClientSide()) {
- return;
- }
-
- // Get server-side registry
- if (
- !(wearer.level() instanceof
- net.minecraft.server.level.ServerLevel serverLevel)
- ) {
- return;
- }
-
- CollarRegistry registry = CollarRegistry.get(serverLevel);
- if (registry == null) {
- return;
- }
-
- // Get all owners from the collar NBT
- java.util.List owners = getOwners(collarStack);
- if (owners.isEmpty() && primaryOwner != null) {
- // If no owners yet, use the player putting the collar on
- owners = java.util.List.of(primaryOwner.getUUID());
- }
-
- // Register all owners
- for (UUID ownerUUID : owners) {
- registry.registerCollar(wearer.getUUID(), ownerUUID);
- }
-
- // Sync to affected owners
- syncRegistryToOwners(serverLevel, owners);
-
- TiedUpMod.LOGGER.debug(
- "[CollarRegistry] Registered {} with {} owners",
- wearer.getName().getString(),
- owners.size()
- );
- }
-
- /**
- * Unregister a collar from the global CollarRegistry.
- * Called when a collar is removed from an entity.
- *
- * @param wearer The entity whose collar is being removed
- */
- private void unregisterCollarFromRegistry(LivingEntity wearer) {
- if (wearer == null || wearer.level().isClientSide()) {
- return;
- }
-
- if (
- !(wearer.level() instanceof
- net.minecraft.server.level.ServerLevel serverLevel)
- ) {
- return;
- }
-
- CollarRegistry registry = CollarRegistry.get(serverLevel);
- if (registry == null) {
- return;
- }
-
- // Get owners before unregistering (for sync)
- java.util.Set owners = registry.getOwners(wearer.getUUID());
-
- // Unregister the wearer
- registry.unregisterWearer(wearer.getUUID());
-
- // Sync to affected owners
- syncRegistryToOwners(serverLevel, owners);
-
- TiedUpMod.LOGGER.debug(
- "[CollarRegistry] Unregistered {}",
- wearer.getName().getString()
- );
- }
-
- // ESCAPE DETECTION: Collar Removal Alert
-
- /**
- * Alert nearby kidnappers when a collar is forcibly removed.
- * This indicates a potential escape attempt.
- *
- * Should be called when:
- * - Player struggles out of a collar
- * - Collar is removed by another player without permission
- * - Collar is broken/picked
- *
- * @param wearer The entity whose collar was removed
- * @param forced Whether the removal was forced (struggle, lockpick, etc.)
- */
- public static void onCollarRemoved(LivingEntity wearer, boolean forced) {
- if (wearer == null || wearer.level().isClientSide()) {
- return;
- }
-
- if (!forced) {
- return; // Only alert on forced removals
- }
-
- if (
- !(wearer.level() instanceof
- net.minecraft.server.level.ServerLevel serverLevel)
- ) {
- return;
- }
-
- TiedUpMod.LOGGER.info(
- "[ItemCollar] {} collar was forcibly removed - alerting kidnappers",
- wearer.getName().getString()
- );
-
- // Unregister from CollarRegistry
- CollarRegistry registry = CollarRegistry.get(serverLevel);
- if (registry != null) {
- registry.unregisterWearer(wearer.getUUID());
- }
-
- // Find and alert nearby kidnappers
- net.minecraft.world.phys.AABB searchBox = wearer
- .getBoundingBox()
- .inflate(50, 20, 50);
- java.util.List kidnappers =
- serverLevel.getEntitiesOfClass(
- com.tiedup.remake.entities.EntityKidnapper.class,
- searchBox
- );
-
- for (com.tiedup.remake.entities.EntityKidnapper kidnapper : kidnappers) {
- // Check if this kidnapper owned the collar
- // For now, alert all kidnappers - they'll check on their own
- if (!kidnapper.hasCaptives() && !kidnapper.isTiedUp()) {
- kidnapper.setAlertTarget(wearer);
- kidnapper.setCurrentState(
- com.tiedup.remake.entities.ai.kidnapper.KidnapperState.ALERT
- );
- kidnapper.broadcastAlert(wearer);
-
- TiedUpMod.LOGGER.debug(
- "[ItemCollar] Alerted kidnapper {} about collar removal",
- kidnapper.getNpcName()
- );
- }
- }
- }
-
- /**
- * Sync the CollarRegistry to specific owners.
- *
- * @param level The server level
- * @param ownerUUIDs The owners to sync to
- */
- private void syncRegistryToOwners(
- net.minecraft.server.level.ServerLevel level,
- java.util.Collection ownerUUIDs
- ) {
- net.minecraft.server.MinecraftServer server = level.getServer();
- if (server == null) {
- return;
- }
-
- CollarRegistry registry = CollarRegistry.get(server);
- if (registry == null) {
- return;
- }
-
- for (UUID ownerUUID : ownerUUIDs) {
- net.minecraft.server.level.ServerPlayer owner = server
- .getPlayerList()
- .getPlayer(ownerUUID);
- if (owner != null) {
- // Send full sync to this owner
- java.util.Set slaves = registry.getSlaves(ownerUUID);
- com.tiedup.remake.network.ModNetwork.sendToPlayer(
- new com.tiedup.remake.network.sync.PacketSyncCollarRegistry(
- slaves
- ),
- owner
- );
- }
- }
- }
-
- // LIFECYCLE HOOKS
-
- /**
- * Called when a collar is unequipped from an entity.
- * Triggers escape detection for forced removals.
- *
- * @param stack The unequipped collar
- * @param entity The entity that was wearing the collar
- */
- @Override
- public void onUnequipped(ItemStack stack, LivingEntity entity) {
- // Check if this is a legitimate removal (camp death, ransom paid, etc.)
- if (isRemovalAlertSuppressed()) {
- TiedUpMod.LOGGER.debug(
- "[ItemCollar] Collar removal for {} - alert suppressed (legitimate removal)",
- entity.getName().getString()
- );
- return;
- }
-
- // Alert kidnappers about the collar removal (forced/escape)
- onCollarRemoved(entity, true);
- }
-
- // TEXTURE SUBFOLDER
-
- /**
- * Get the texture subfolder for collar items.
- * Issue #12 fix: Eliminates string checks in renderers.
- */
- @Override
- public String getTextureSubfolder() {
- return "collars";
- }
-}
diff --git a/src/main/java/com/tiedup/remake/items/base/ItemEarplugs.java b/src/main/java/com/tiedup/remake/items/base/ItemEarplugs.java
deleted file mode 100644
index 9e7ef97..0000000
--- a/src/main/java/com/tiedup/remake/items/base/ItemEarplugs.java
+++ /dev/null
@@ -1,90 +0,0 @@
-package com.tiedup.remake.items.base;
-
-import com.tiedup.remake.core.SystemMessageManager;
-import com.tiedup.remake.util.EquipmentInteractionHelper;
-import com.tiedup.remake.util.TiedUpSounds;
-import com.tiedup.remake.v2.BodyRegionV2;
-import java.util.List;
-import net.minecraft.network.chat.Component;
-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.Item;
-import net.minecraft.world.item.ItemStack;
-import net.minecraft.world.item.TooltipFlag;
-import net.minecraft.world.level.Level;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * Base class for earplug items.
- * These items block or reduce sounds when equipped.
- *
- * Based on original ItemEarplugs from 1.12.2
- *
- * Phase future: Sound blocking effect
- */
-public abstract class ItemEarplugs
- extends Item
- implements IBondageItem, ILockable
-{
-
- public ItemEarplugs(Properties properties) {
- super(properties.stacksTo(16)); // Earplugs can stack to 16
- }
-
- @Override
- public BodyRegionV2 getBodyRegion() {
- return BodyRegionV2.EARS;
- }
-
- @Override
- public void appendHoverText(
- ItemStack stack,
- @Nullable Level level,
- List tooltip,
- TooltipFlag flag
- ) {
- super.appendHoverText(stack, level, tooltip, flag);
- appendLockTooltip(stack, tooltip);
- }
-
- /**
- * Called when player right-clicks another entity with earplugs.
- * Allows putting earplugs on tied-up entities (Players and NPCs).
- */
- @Override
- public InteractionResult interactLivingEntity(
- ItemStack stack,
- Player player,
- LivingEntity target,
- InteractionHand hand
- ) {
- return EquipmentInteractionHelper.equipOnTarget(
- stack,
- player,
- target,
- state -> state.hasEarplugs(),
- (state, item) -> state.equip(BodyRegionV2.EARS, item),
- (state, item) ->
- state.replaceEquipment(BodyRegionV2.EARS, item, false),
- (p, t) ->
- SystemMessageManager.sendToTarget(
- p,
- t,
- SystemMessageManager.MessageCategory.EARPLUGS_ON
- ),
- "ItemEarplugs",
- null, // No pre-equip hook
- (s, p, t, state) -> TiedUpSounds.playEarplugsEquipSound(t), // Post-equip: play sound
- null // No replace check
- );
- }
-
- // Sound blocking implemented in:
- // - client/events/EarplugSoundHandler.java (event interception)
- // - client/MuffledSoundInstance.java (volume/pitch wrapper)
- // - Configurable via ModConfig.CLIENT.earplugVolumeMultiplier
-
- // ILockable implementation inherited from interface default methods
-}
diff --git a/src/main/java/com/tiedup/remake/items/base/ItemGag.java b/src/main/java/com/tiedup/remake/items/base/ItemGag.java
deleted file mode 100644
index a7eee05..0000000
--- a/src/main/java/com/tiedup/remake/items/base/ItemGag.java
+++ /dev/null
@@ -1,93 +0,0 @@
-package com.tiedup.remake.items.base;
-
-import com.tiedup.remake.core.SystemMessageManager;
-import com.tiedup.remake.util.EquipmentInteractionHelper;
-import com.tiedup.remake.util.GagMaterial;
-import com.tiedup.remake.v2.BodyRegionV2;
-import java.util.List;
-import net.minecraft.ChatFormatting;
-import net.minecraft.network.chat.Component;
-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.Item;
-import net.minecraft.world.item.ItemStack;
-import net.minecraft.world.item.TooltipFlag;
-import net.minecraft.world.level.Level;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * Base class for gag items (ball gag, cloth gag, tape, etc.)
- * These items prevent or muffle a player's speech when equipped.
- *
- * Based on original ItemGag from 1.12.2
- *
- */
-public abstract class ItemGag
- extends Item
- implements IBondageItem, IHasGaggingEffect, IAdjustable, ILockable
-{
-
- private final GagMaterial material;
-
- public ItemGag(Properties properties, GagMaterial material) {
- super(properties);
- this.material = material;
- }
-
- public GagMaterial getGagMaterial() {
- return this.material;
- }
-
- @Override
- public BodyRegionV2 getBodyRegion() {
- return BodyRegionV2.MOUTH;
- }
-
- @Override
- public void appendHoverText(
- ItemStack stack,
- @Nullable Level level,
- List tooltip,
- TooltipFlag flag
- ) {
- super.appendHoverText(stack, level, tooltip, flag);
- appendLockTooltip(stack, tooltip);
- }
-
- /**
- * All gags can be adjusted to better fit player skins.
- * @return true - gags support position adjustment
- */
- @Override
- public boolean canBeAdjusted() {
- return true;
- }
-
- /**
- * Called when player right-clicks another entity with this gag.
- * Allows putting gag on tied-up entities (Players and NPCs).
- */
- @Override
- public InteractionResult interactLivingEntity(
- ItemStack stack,
- Player player,
- LivingEntity target,
- InteractionHand hand
- ) {
- return EquipmentInteractionHelper.equipOnTarget(
- stack,
- player,
- target,
- state -> state.isGagged(),
- (state, item) -> state.equip(BodyRegionV2.MOUTH, item),
- (state, item) ->
- state.replaceEquipment(BodyRegionV2.MOUTH, item, false),
- SystemMessageManager::sendGagged,
- "ItemGag"
- );
- }
-
- // ILockable implementation inherited from interface default methods
-}
diff --git a/src/main/java/com/tiedup/remake/items/base/ItemMittens.java b/src/main/java/com/tiedup/remake/items/base/ItemMittens.java
deleted file mode 100644
index 7616782..0000000
--- a/src/main/java/com/tiedup/remake/items/base/ItemMittens.java
+++ /dev/null
@@ -1,72 +0,0 @@
-package com.tiedup.remake.items.base;
-
-import com.tiedup.remake.core.SystemMessageManager;
-import com.tiedup.remake.util.EquipmentInteractionHelper;
-import com.tiedup.remake.v2.BodyRegionV2;
-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.Item;
-import net.minecraft.world.item.ItemStack;
-
-/**
- * Base class for mittens items.
- * These items block hand interactions (mining, placing, using items) when equipped.
- *
- *
- * Restrictions when wearing mittens:
- * - Cannot mine/break blocks
- * - Cannot place blocks
- * - Cannot use items
- * - Cannot attack (0 damage punch allowed)
- *
- * Allowed:
- * - Push buttons/levers
- * - Open doors
- */
-public abstract class ItemMittens
- extends Item
- implements IBondageItem, ILockable
-{
-
- public ItemMittens(Properties properties) {
- super(properties);
- }
-
- @Override
- public BodyRegionV2 getBodyRegion() {
- return BodyRegionV2.HANDS;
- }
-
- /**
- * Called when player right-clicks another entity with these mittens.
- * Allows putting mittens on tied-up entities (Players and NPCs).
- */
- @Override
- public InteractionResult interactLivingEntity(
- ItemStack stack,
- Player player,
- LivingEntity target,
- InteractionHand hand
- ) {
- return EquipmentInteractionHelper.equipOnTarget(
- stack,
- player,
- target,
- state -> state.hasMittens(),
- (state, item) -> state.equip(BodyRegionV2.HANDS, item),
- (state, item) ->
- state.replaceEquipment(BodyRegionV2.HANDS, item, false),
- (p, t) ->
- SystemMessageManager.sendToTarget(
- p,
- t,
- SystemMessageManager.MessageCategory.MITTENS_ON
- ),
- "ItemMittens"
- );
- }
-
- // ILockable implementation inherited from interface default methods
-}
diff --git a/src/main/java/com/tiedup/remake/items/base/ItemOwnerTarget.java b/src/main/java/com/tiedup/remake/items/base/ItemOwnerTarget.java
index 2c4be8e..b368982 100644
--- a/src/main/java/com/tiedup/remake/items/base/ItemOwnerTarget.java
+++ b/src/main/java/com/tiedup/remake/items/base/ItemOwnerTarget.java
@@ -195,11 +195,11 @@ public abstract class ItemOwnerTarget extends Item {
BodyRegionV2.NECK
);
if (
- collar.getItem() instanceof ItemCollar collarItem &&
- collarItem.hasNickname(collar)
+ com.tiedup.remake.v2.bondage.CollarHelper.isCollar(collar) &&
+ com.tiedup.remake.v2.bondage.CollarHelper.hasNickname(collar)
) {
displayName =
- collarItem.getNickname(collar) +
+ com.tiedup.remake.v2.bondage.CollarHelper.getNickname(collar) +
" (" +
displayName +
")";
diff --git a/src/main/java/com/tiedup/remake/items/base/MittensVariant.java b/src/main/java/com/tiedup/remake/items/base/MittensVariant.java
deleted file mode 100644
index 686d286..0000000
--- a/src/main/java/com/tiedup/remake/items/base/MittensVariant.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package com.tiedup.remake.items.base;
-
-/**
- * Enum defining all mittens variants.
- * Used by GenericMittens to create mittens items via factory pattern.
- *
- * Mittens system - blocks hand interactions when equipped.
- *
- *
Issue #12 fix: Added textureSubfolder to eliminate string checks in renderers.
- */
-public enum MittensVariant {
- LEATHER("mittens", "mittens");
-
- private final String registryName;
- private final String textureSubfolder;
-
- MittensVariant(String registryName, String textureSubfolder) {
- this.registryName = registryName;
- this.textureSubfolder = textureSubfolder;
- }
-
- public String getRegistryName() {
- return registryName;
- }
-
- /**
- * Get the texture subfolder for this mittens variant.
- * Used by renderers to locate texture files.
- *
- * @return Subfolder path under textures/entity/bondage/ (e.g., "mittens")
- */
- public String getTextureSubfolder() {
- return textureSubfolder;
- }
-}
diff --git a/src/main/java/com/tiedup/remake/items/bondage3d/gags/ItemBallGag3D.java b/src/main/java/com/tiedup/remake/items/bondage3d/gags/ItemBallGag3D.java
deleted file mode 100644
index 63f79fb..0000000
--- a/src/main/java/com/tiedup/remake/items/bondage3d/gags/ItemBallGag3D.java
+++ /dev/null
@@ -1,78 +0,0 @@
-package com.tiedup.remake.items.bondage3d.gags;
-
-import com.tiedup.remake.items.base.ItemGag;
-import com.tiedup.remake.items.bondage3d.IHas3DModelConfig;
-import com.tiedup.remake.items.bondage3d.Model3DConfig;
-import com.tiedup.remake.util.GagMaterial;
-import java.util.Set;
-import net.minecraft.resources.ResourceLocation;
-import net.minecraft.world.item.Item;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * Ball Gag 3D - Extends ItemGag with 3D OBJ model rendering.
- * All 3D configuration is defined here.
- * Supports color variants via tinting the "Ball" material.
- */
-public class ItemBallGag3D extends ItemGag implements IHas3DModelConfig {
-
- // 3D config with "Ball" material tintable for color variants
- private static final Model3DConfig CONFIG = new Model3DConfig(
- "tiedup:models/obj/ball_gag/model.obj", // OBJ
- "tiedup:models/obj/ball_gag/texture.png", // Explicit texture
- 0.0f, // posX
- 1.55f, // posY
- 0.0f, // posZ
- 1.0f, // scale
- 0.0f,
- 0.0f,
- 180.0f, // rotation
- Set.of("Ball") // Tintable materials (for color variants)
- );
-
- public ItemBallGag3D() {
- super(new Item.Properties().stacksTo(16), GagMaterial.BALL);
- }
-
- // ===== 3D Model Support =====
-
- @Override
- public boolean uses3DModel() {
- return true;
- }
-
- @Override
- @Nullable
- public ResourceLocation get3DModelLocation() {
- return ResourceLocation.tryParse(CONFIG.objPath());
- }
-
- /**
- * Returns the complete 3D configuration for the renderer.
- */
- @Override
- public Model3DConfig getModelConfig() {
- return CONFIG;
- }
-
- /**
- * Explicit texture (if non-null, overrides MTL map_Kd).
- */
- @Nullable
- public ResourceLocation getExplicitTexture() {
- String path = CONFIG.texturePath();
- return path != null ? ResourceLocation.tryParse(path) : null;
- }
-
- // ===== Gag Properties =====
-
- @Override
- public String getTextureSubfolder() {
- return "ballgags/normal"; // Fallback if 3D fails
- }
-
- @Override
- public boolean canAttachPadlock() {
- return true;
- }
-}
diff --git a/src/main/java/com/tiedup/remake/network/action/PacketTighten.java b/src/main/java/com/tiedup/remake/network/action/PacketTighten.java
index 820898b..a745e51 100644
--- a/src/main/java/com/tiedup/remake/network/action/PacketTighten.java
+++ b/src/main/java/com/tiedup/remake/network/action/PacketTighten.java
@@ -166,11 +166,8 @@ public class PacketTighten {
// Check if sender owns collar
if (!hasPermission && closestState.hasCollar()) {
var collarStack = closestState.getEquipment(BodyRegionV2.NECK);
- if (
- collarStack.getItem() instanceof
- com.tiedup.remake.items.base.ItemCollar collar
- ) {
- if (collar.isOwner(collarStack, tightener)) {
+ if (com.tiedup.remake.v2.bondage.CollarHelper.isCollar(collarStack)) {
+ if (com.tiedup.remake.v2.bondage.CollarHelper.isOwner(collarStack, tightener)) {
hasPermission = true;
}
}
diff --git a/src/main/java/com/tiedup/remake/network/item/PacketAdjustItem.java b/src/main/java/com/tiedup/remake/network/item/PacketAdjustItem.java
index 9a9447d..0777e04 100644
--- a/src/main/java/com/tiedup/remake/network/item/PacketAdjustItem.java
+++ b/src/main/java/com/tiedup/remake/network/item/PacketAdjustItem.java
@@ -2,7 +2,6 @@ package com.tiedup.remake.network.item;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.base.AdjustmentHelper;
-import com.tiedup.remake.items.base.IAdjustable;
import com.tiedup.remake.network.sync.SyncManager;
import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2;
@@ -152,7 +151,7 @@ public class PacketAdjustItem {
return;
}
- if (!(stack.getItem() instanceof IAdjustable)) {
+ if (!AdjustmentHelper.isAdjustable(stack)) {
TiedUpMod.LOGGER.warn(
"[PACKET] PacketAdjustItem: Item {} is not adjustable",
stack.getItem()
diff --git a/src/main/java/com/tiedup/remake/network/item/PacketAdjustRemote.java b/src/main/java/com/tiedup/remake/network/item/PacketAdjustRemote.java
index 521a397..cd1d486 100644
--- a/src/main/java/com/tiedup/remake/network/item/PacketAdjustRemote.java
+++ b/src/main/java/com/tiedup/remake/network/item/PacketAdjustRemote.java
@@ -3,7 +3,6 @@ package com.tiedup.remake.network.item;
import com.tiedup.remake.core.SystemMessageManager;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.base.AdjustmentHelper;
-import com.tiedup.remake.items.base.IAdjustable;
import com.tiedup.remake.network.sync.SyncManager;
import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.state.PlayerBindState;
@@ -195,7 +194,7 @@ public class PacketAdjustRemote {
return;
}
- if (!(stack.getItem() instanceof IAdjustable)) {
+ if (!AdjustmentHelper.isAdjustable(stack)) {
TiedUpMod.LOGGER.warn(
"[PACKET] PacketAdjustRemote: Item {} is not adjustable",
stack.getItem()
@@ -255,11 +254,8 @@ public class PacketAdjustRemote {
ItemStack collarStack = kidnapped.getEquipment(
BodyRegionV2.NECK
);
- if (
- collarStack.getItem() instanceof
- com.tiedup.remake.items.base.ItemCollar collar
- ) {
- if (collar.isOwner(collarStack, sender)) {
+ if (com.tiedup.remake.v2.bondage.CollarHelper.isCollar(collarStack)) {
+ if (com.tiedup.remake.v2.bondage.CollarHelper.isOwner(collarStack, sender)) {
return kidnapped;
}
}
diff --git a/src/main/java/com/tiedup/remake/network/minigame/PacketLockpickAttempt.java b/src/main/java/com/tiedup/remake/network/minigame/PacketLockpickAttempt.java
index af45b29..1505d90 100644
--- a/src/main/java/com/tiedup/remake/network/minigame/PacketLockpickAttempt.java
+++ b/src/main/java/com/tiedup/remake/network/minigame/PacketLockpickAttempt.java
@@ -454,9 +454,7 @@ public class PacketLockpickAttempt {
);
if (collar.isEmpty()) return;
- if (
- collar.getItem() instanceof com.tiedup.remake.items.ItemShockCollar
- ) {
+ if (com.tiedup.remake.v2.bondage.CollarHelper.canShock(collar)) {
state.shockKidnapped(" (Failed lockpick attempt)", 2.0f);
TiedUpMod.LOGGER.info(
"[PacketLockpickAttempt] Player {} shocked for failed lockpick",
diff --git a/src/main/java/com/tiedup/remake/network/personality/PacketRequestNpcInventory.java b/src/main/java/com/tiedup/remake/network/personality/PacketRequestNpcInventory.java
index 6c4da6a..80d1d51 100644
--- a/src/main/java/com/tiedup/remake/network/personality/PacketRequestNpcInventory.java
+++ b/src/main/java/com/tiedup/remake/network/personality/PacketRequestNpcInventory.java
@@ -70,11 +70,8 @@ public class PacketRequestNpcInventory {
// Verify player is owner of collar (or NPC has no collar)
if (damsel.hasCollar()) {
ItemStack collar = damsel.getEquipment(BodyRegionV2.NECK);
- if (
- collar.getItem() instanceof
- com.tiedup.remake.items.base.ItemCollar collarItem
- ) {
- if (!collarItem.isOwner(collar, player)) {
+ if (com.tiedup.remake.v2.bondage.CollarHelper.isCollar(collar)) {
+ if (!com.tiedup.remake.v2.bondage.CollarHelper.isOwner(collar, player)) {
TiedUpMod.LOGGER.warn(
"[PacketRequestNpcInventory] Player {} is not owner of NPC collar",
player.getName().getString()
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 392178f..746092c 100644
--- a/src/main/java/com/tiedup/remake/network/selfbondage/PacketSelfBondage.java
+++ b/src/main/java/com/tiedup/remake/network/selfbondage/PacketSelfBondage.java
@@ -2,7 +2,7 @@ package com.tiedup.remake.network.selfbondage;
import com.tiedup.remake.core.SettingsAccessor;
import com.tiedup.remake.core.TiedUpMod;
-import com.tiedup.remake.items.base.*;
+import com.tiedup.remake.items.base.ILockable;
import com.tiedup.remake.network.ModNetwork;
import com.tiedup.remake.network.PacketRateLimiter;
import com.tiedup.remake.network.action.PacketTying;
@@ -79,141 +79,13 @@ public class PacketSelfBondage {
// V2 bondage items — use tying task with V2 equip
if (item instanceof IV2BondageItem v2Item) {
handleV2SelfBondage(player, stack, v2Item, state);
- return;
}
-
- // V1 routes below (legacy)
- if (item instanceof ItemBind bind) {
- handleSelfBind(player, stack, bind, state);
- } else if (item instanceof ItemGag) {
- handleSelfAccessory(
- player,
- stack,
- state,
- "gag",
- s -> s.isGagged(),
- s -> s.getEquipment(BodyRegionV2.MOUTH),
- s -> s.unequip(BodyRegionV2.MOUTH),
- (s, i) -> s.equip(BodyRegionV2.MOUTH, i)
- );
- } else if (item instanceof ItemBlindfold) {
- handleSelfAccessory(
- player,
- stack,
- state,
- "blindfold",
- s -> s.isBlindfolded(),
- s -> s.getEquipment(BodyRegionV2.EYES),
- s -> s.unequip(BodyRegionV2.EYES),
- (s, i) -> s.equip(BodyRegionV2.EYES, i)
- );
- } else if (item instanceof ItemMittens) {
- handleSelfAccessory(
- player,
- stack,
- state,
- "mittens",
- s -> s.hasMittens(),
- s -> s.getEquipment(BodyRegionV2.HANDS),
- s -> s.unequip(BodyRegionV2.HANDS),
- (s, i) -> s.equip(BodyRegionV2.HANDS, i)
- );
- } else if (item instanceof ItemEarplugs) {
- handleSelfAccessory(
- player,
- stack,
- state,
- "earplugs",
- s -> s.hasEarplugs(),
- s -> s.getEquipment(BodyRegionV2.EARS),
- s -> s.unequip(BodyRegionV2.EARS),
- (s, i) -> s.equip(BodyRegionV2.EARS, i)
- );
- }
- // ItemCollar: NOT handled - cannot self-collar
+ // V1 routes removed — all bondage items are now V2/data-driven
});
ctx.get().setPacketHandled(true);
}
- /**
- * Handle self-binding with a bind item (rope, chain, etc.).
- * Uses tying task system - requires holding left-click.
- */
- private static void handleSelfBind(
- ServerPlayer player,
- ItemStack stack,
- ItemBind bind,
- IBondageState state
- ) {
- // Can't self-tie if already tied
- if (state.isTiedUp()) {
- TiedUpMod.LOGGER.debug(
- "[SelfBondage] {} tried to self-tie but is already tied",
- player.getName().getString()
- );
- return;
- }
-
- // Get player's bind state for tying task management
- PlayerBindState playerState = PlayerBindState.getInstance(player);
- if (playerState == null) return;
-
- // Get tying duration from GameRule
- int tyingSeconds = SettingsAccessor.getTyingPlayerTime(
- player.level().getGameRules()
- );
-
- // Create self-tying task (target == kidnapper)
- TyingPlayerTask newTask = new TyingPlayerTask(
- stack.copy(),
- state,
- player, // Target is self
- tyingSeconds,
- player.level(),
- player // Kidnapper is also self
- );
-
- // Get current tying task
- TyingTask currentTask = playerState.getCurrentTyingTask();
-
- // Check if we should start a new task or continue existing one
- if (
- currentTask == null ||
- !currentTask.isSameTarget(player) ||
- currentTask.isOutdated() ||
- !ItemStack.matches(currentTask.getBind(), stack)
- ) {
- // Start new self-tying task
- playerState.setCurrentTyingTask(newTask);
- newTask.start();
-
- TiedUpMod.LOGGER.debug(
- "[SelfBondage] {} started self-tying ({} seconds)",
- player.getName().getString(),
- tyingSeconds
- );
- } else {
- // Continue existing task
- newTask = (TyingPlayerTask) currentTask;
- }
-
- // Update task progress
- newTask.update();
-
- // Check if task completed
- if (newTask.isStopped()) {
- // Self-tying complete! Consume the item
- stack.shrink(1);
- playerState.setCurrentTyingTask(null);
-
- TiedUpMod.LOGGER.info(
- "[SelfBondage] {} successfully self-tied",
- player.getName().getString()
- );
- }
- }
-
/**
* Handle self-bondage with a V2 bondage item.
* Uses V2TyingPlayerTask for progress, V2EquipmentHelper for equip.
@@ -336,75 +208,4 @@ public class PacketSelfBondage {
}
}
- /**
- * Handle self-equipping an accessory (gag, blindfold, mittens, earplugs).
- * Can be used anytime (no need to be tied).
- * Blocked only if arms are fully bound.
- * Supports swapping: if same type already equipped (and not locked), swap them.
- */
- private static void handleSelfAccessory(
- ServerPlayer player,
- ItemStack stack,
- IBondageState state,
- String itemType,
- java.util.function.Predicate isEquipped,
- java.util.function.Function getCurrent,
- java.util.function.Function takeOff,
- java.util.function.BiConsumer putOn
- ) {
- // Can't equip if arms are fully bound (need hands to put on accessories)
- ItemStack currentBind = state.getEquipment(BodyRegionV2.ARMS);
- if (!currentBind.isEmpty()) {
- if (ItemBind.hasArmsBound(currentBind)) {
- TiedUpMod.LOGGER.debug(
- "[SelfBondage] {} can't self-{} - arms are bound",
- player.getName().getString(),
- itemType
- );
- return;
- }
- }
-
- // Already equipped? Try to swap
- if (isEquipped.test(state)) {
- ItemStack currentItem = getCurrent.apply(state);
-
- // Check if current item is locked
- if (
- currentItem.getItem() instanceof ILockable lockable &&
- lockable.isLocked(currentItem)
- ) {
- TiedUpMod.LOGGER.debug(
- "[SelfBondage] {} can't swap {} - current is locked",
- player.getName().getString(),
- itemType
- );
- return;
- }
-
- // Remove current and drop it
- ItemStack removed = takeOff.apply(state);
- if (!removed.isEmpty()) {
- state.kidnappedDropItem(removed);
- TiedUpMod.LOGGER.debug(
- "[SelfBondage] {} swapping {} - dropped old one",
- player.getName().getString(),
- itemType
- );
- }
- }
-
- // Equip new item on self
- putOn.accept(state, stack.copy());
- stack.shrink(1);
-
- // Sync to client
- SyncManager.syncInventory(player);
-
- TiedUpMod.LOGGER.info(
- "[SelfBondage] {} self-equipped {}",
- player.getName().getString(),
- itemType
- );
- }
}
diff --git a/src/main/java/com/tiedup/remake/network/slave/PacketSlaveItemManage.java b/src/main/java/com/tiedup/remake/network/slave/PacketSlaveItemManage.java
index f78c3bd..6b941c7 100644
--- a/src/main/java/com/tiedup/remake/network/slave/PacketSlaveItemManage.java
+++ b/src/main/java/com/tiedup/remake/network/slave/PacketSlaveItemManage.java
@@ -163,12 +163,9 @@ public class PacketSlaveItemManage {
ItemStack collarStack = targetState.getEquipment(
BodyRegionV2.NECK
);
- if (
- collarStack.getItem() instanceof
- com.tiedup.remake.items.base.ItemCollar collar
- ) {
+ if (com.tiedup.remake.v2.bondage.CollarHelper.isCollar(collarStack)) {
if (
- !collar.isOwner(collarStack, sender) &&
+ !com.tiedup.remake.v2.bondage.CollarHelper.isOwner(collarStack, sender) &&
!sender.hasPermissions(2)
) {
TiedUpMod.LOGGER.debug(
@@ -437,15 +434,14 @@ public class PacketSlaveItemManage {
if (
region == BodyRegionV2.ARMS &&
- itemStack.getItem() instanceof
- com.tiedup.remake.items.base.ItemBind bind
+ itemStack.getItem() instanceof com.tiedup.remake.items.base.IHasResistance resistanceItem
) {
- int currentResistance = bind.getCurrentResistance(
+ int currentResistance = resistanceItem.getCurrentResistance(
itemStack,
target
);
int lockResistance = lockable.getLockResistance(); // Configurable via ModConfig
- bind.setCurrentResistance(
+ resistanceItem.setCurrentResistance(
itemStack,
currentResistance + lockResistance
);
@@ -599,8 +595,7 @@ public class PacketSlaveItemManage {
) {
if (
collarStack.isEmpty() ||
- !(collarStack.getItem() instanceof
- com.tiedup.remake.items.base.ItemCollar collar)
+ !com.tiedup.remake.v2.bondage.CollarHelper.isCollar(collarStack)
) {
TiedUpMod.LOGGER.debug(
"[PacketSlaveItemManage] No collar for bondage service toggle"
@@ -609,7 +604,7 @@ public class PacketSlaveItemManage {
}
// Check if cell is configured (required for bondage service)
- if (!collar.hasCellAssigned(collarStack)) {
+ if (!com.tiedup.remake.v2.bondage.CollarHelper.hasCellAssigned(collarStack)) {
TiedUpMod.LOGGER.debug(
"[PacketSlaveItemManage] Cannot enable bondage service: no cell configured"
);
@@ -623,8 +618,8 @@ public class PacketSlaveItemManage {
}
// Toggle bondage service
- boolean currentState = collar.isBondageServiceEnabled(collarStack);
- collar.setBondageServiceEnabled(collarStack, !currentState);
+ boolean currentState = com.tiedup.remake.v2.bondage.CollarHelper.isBondageServiceEnabled(collarStack);
+ com.tiedup.remake.v2.bondage.CollarHelper.setBondageServiceEnabled(collarStack, !currentState);
String newState = !currentState ? "enabled" : "disabled";
TiedUpMod.LOGGER.info(
diff --git a/src/main/java/com/tiedup/remake/network/trader/PacketBuyCaptive.java b/src/main/java/com/tiedup/remake/network/trader/PacketBuyCaptive.java
index 01da680..ac1d891 100644
--- a/src/main/java/com/tiedup/remake/network/trader/PacketBuyCaptive.java
+++ b/src/main/java/com/tiedup/remake/network/trader/PacketBuyCaptive.java
@@ -243,22 +243,23 @@ public class PacketBuyCaptive {
kidnappedState.getEquipment(BodyRegionV2.NECK);
if (
!collar.isEmpty() &&
- collar.getItem() instanceof
- com.tiedup.remake.items.base.ItemCollar collarItem
+ com.tiedup.remake.v2.bondage.CollarHelper.isCollar(collar)
) {
// Remove all existing owners from collar NBT
for (UUID ownerId : new java.util.ArrayList<>(
- collarItem.getOwners(collar)
+ com.tiedup.remake.v2.bondage.CollarHelper.getOwners(collar)
)) {
- collarItem.removeOwner(collar, ownerId);
+ com.tiedup.remake.v2.bondage.CollarHelper.removeOwner(collar, ownerId);
}
// Add buyer as new owner
- collarItem.addOwner(
+ com.tiedup.remake.v2.bondage.CollarHelper.addOwner(
collar,
buyer.getUUID(),
buyer.getName().getString()
);
- collarItem.setLocked(collar, false);
+ if (collar.getItem() instanceof com.tiedup.remake.items.base.ILockable lockable) {
+ lockable.setLocked(collar, false);
+ }
// Re-apply modified collar to persist NBT changes
kidnappedState.equip(BodyRegionV2.NECK, collar);
diff --git a/src/main/java/com/tiedup/remake/prison/service/PrisonerService.java b/src/main/java/com/tiedup/remake/prison/service/PrisonerService.java
index 0c1a32b..9b55d06 100644
--- a/src/main/java/com/tiedup/remake/prison/service/PrisonerService.java
+++ b/src/main/java/com/tiedup/remake/prison/service/PrisonerService.java
@@ -757,10 +757,9 @@ public class PrisonerService {
if (
!collar.isEmpty() &&
- collar.getItem() instanceof
- com.tiedup.remake.items.base.ItemCollar collarItem
+ com.tiedup.remake.v2.bondage.CollarHelper.isCollar(collar)
) {
- List nbtOwners = collarItem.getOwners(collar);
+ List nbtOwners = com.tiedup.remake.v2.bondage.CollarHelper.getOwners(collar);
if (!nbtOwners.isEmpty()) {
for (UUID ownerUUID : nbtOwners) {
collars.registerCollar(playerId, ownerUUID);
diff --git a/src/main/java/com/tiedup/remake/state/components/PlayerShockCollar.java b/src/main/java/com/tiedup/remake/state/components/PlayerShockCollar.java
index 3385eef..42f8b79 100644
--- a/src/main/java/com/tiedup/remake/state/components/PlayerShockCollar.java
+++ b/src/main/java/com/tiedup/remake/state/components/PlayerShockCollar.java
@@ -4,12 +4,14 @@ import com.tiedup.remake.core.ModSounds;
import com.tiedup.remake.core.SystemMessageManager;
import com.tiedup.remake.core.SystemMessageManager.MessageCategory;
import com.tiedup.remake.core.TiedUpMod;
-import com.tiedup.remake.items.ItemGpsCollar;
-import com.tiedup.remake.items.ItemShockCollarAuto;
import com.tiedup.remake.state.hosts.IPlayerBindStateHost;
import com.tiedup.remake.util.GameConstants;
import com.tiedup.remake.util.time.Timer;
import com.tiedup.remake.v2.BodyRegionV2;
+import com.tiedup.remake.v2.bondage.CollarHelper;
+import com.tiedup.remake.v2.bondage.component.ComponentType;
+import com.tiedup.remake.v2.bondage.component.GpsComponent;
+import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import java.util.List;
import java.util.UUID;
import net.minecraft.ChatFormatting;
@@ -128,17 +130,16 @@ public class PlayerShockCollar {
// Flags set inside lock, actions performed outside
boolean shouldShockAuto = false;
boolean shouldShockGPS = false;
- ItemGpsCollar gpsCollar = null;
- ItemStack gpsStack = null;
+ ItemStack gpsStackCopy = null;
synchronized (lockTimerAutoShock) {
ItemStack collarStack = getCurrentCollar();
if (collarStack.isEmpty()) return;
- // Auto-Shock Collar handling
- if (
- collarStack.getItem() instanceof ItemShockCollarAuto collarShock
- ) {
+ // Auto-Shock Collar handling: collar can shock AND has auto interval > 0
+ if (CollarHelper.canShock(collarStack) && CollarHelper.getShockInterval(collarStack) > 0) {
+ int interval = CollarHelper.getShockInterval(collarStack);
+
if (
timerAutoShockCollar != null &&
timerAutoShockCollar.isExpired()
@@ -151,40 +152,38 @@ public class PlayerShockCollar {
timerAutoShockCollar.isExpired()
) {
timerAutoShockCollar = new Timer(
- collarShock.getInterval() /
- GameConstants.TICKS_PER_SECOND,
+ interval / GameConstants.TICKS_PER_SECOND,
player.level()
);
}
}
// GPS Collar handling
- else if (collarStack.getItem() instanceof ItemGpsCollar gps) {
+ else if (CollarHelper.hasGPS(collarStack)) {
if (
- gps.isActive(collarStack) &&
+ CollarHelper.isActive(collarStack) &&
(timerAutoShockCollar == null ||
timerAutoShockCollar.isExpired())
) {
- List safeSpots = gps.getSafeSpots(
- collarStack
+ GpsComponent gpsComp = DataDrivenBondageItem.getComponent(
+ collarStack, ComponentType.GPS, GpsComponent.class
);
- if (safeSpots != null && !safeSpots.isEmpty()) {
- boolean isSafe = false;
- for (ItemGpsCollar.SafeSpot spot : safeSpots) {
- if (spot.isInside(player)) {
- isSafe = true;
- break;
- }
- }
- if (!isSafe) {
- timerAutoShockCollar = new Timer(
- gps.getShockInterval(collarStack) /
- GameConstants.TICKS_PER_SECOND,
- player.level()
- );
- shouldShockGPS = true;
- gpsCollar = gps;
- gpsStack = collarStack.copy();
- }
+ int safeZoneRadius = gpsComp != null ? gpsComp.getSafeZoneRadius() : 50;
+ // Shock interval from ShockComponent (GPS collars also have shock capability)
+ int shockInterval = CollarHelper.getShockInterval(collarStack);
+ if (shockInterval <= 0) shockInterval = 100; // Fallback: 5 seconds
+
+ // Check safe spots from NBT (CollarHelper reads "safeSpots" NBT)
+ // For now, use safeZoneRadius from GpsComponent
+ // GPS safe zone check: if the collar has safe spots in NBT, check them
+ boolean isSafe = isInSafeZone(collarStack, player, safeZoneRadius);
+
+ if (!isSafe) {
+ timerAutoShockCollar = new Timer(
+ shockInterval / GameConstants.TICKS_PER_SECOND,
+ player.level()
+ );
+ shouldShockGPS = true;
+ gpsStackCopy = collarStack.copy();
}
}
}
@@ -195,30 +194,58 @@ public class PlayerShockCollar {
this.shockKidnapped();
}
- if (shouldShockGPS && gpsCollar != null) {
+ if (shouldShockGPS && gpsStackCopy != null) {
this.shockKidnapped(
" Return back to your allowed area!",
GameConstants.DEFAULT_SHOCK_DAMAGE
);
- warnOwnersGPSViolation(gpsCollar, gpsStack);
+ warnOwnersGPSViolation(gpsStackCopy);
}
}
+ /**
+ * Check if the player is inside any safe zone defined on the collar.
+ * Reads safe spots from NBT "safeSpots" ListTag.
+ */
+ private boolean isInSafeZone(ItemStack collarStack, Player player, int defaultRadius) {
+ net.minecraft.nbt.CompoundTag tag = collarStack.getTag();
+ if (tag == null || !tag.contains("safeSpots", net.minecraft.nbt.Tag.TAG_LIST)) {
+ return true; // No safe spots defined = always safe
+ }
+ net.minecraft.nbt.ListTag safeSpots = tag.getList("safeSpots", net.minecraft.nbt.Tag.TAG_COMPOUND);
+ if (safeSpots.isEmpty()) return true;
+
+ for (int i = 0; i < safeSpots.size(); i++) {
+ net.minecraft.nbt.CompoundTag spot = safeSpots.getCompound(i);
+ double x = spot.getDouble("x");
+ double y = spot.getDouble("y");
+ double z = spot.getDouble("z");
+ int radius = spot.contains("radius") ? spot.getInt("radius") : defaultRadius;
+ double dist = player.distanceToSqr(x, y, z);
+ if (dist <= (double) radius * radius) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Sends a global alert to masters when a slave violates their GPS zone.
* Private helper method.
*/
- private void warnOwnersGPSViolation(ItemGpsCollar gps, ItemStack stack) {
+ private void warnOwnersGPSViolation(ItemStack stack) {
Player player = host.getPlayer();
if (player.getServer() == null) return;
+ if (!CollarHelper.shouldWarnMasters(stack)) return;
+
// Format: "ALERT: is outside the safe zone!"
String alertMessage = String.format(
SystemMessageManager.getTemplate(MessageCategory.GPS_OWNER_ALERT),
player.getName().getString()
);
- for (UUID ownerId : gps.getOwners(stack)) {
+ for (UUID ownerId : CollarHelper.getOwners(stack)) {
ServerPlayer owner = player
.getServer()
.getPlayerList()
diff --git a/src/main/java/com/tiedup/remake/state/components/PlayerStateQuery.java b/src/main/java/com/tiedup/remake/state/components/PlayerStateQuery.java
index 8cb10b7..9c50354 100644
--- a/src/main/java/com/tiedup/remake/state/components/PlayerStateQuery.java
+++ b/src/main/java/com/tiedup/remake/state/components/PlayerStateQuery.java
@@ -133,10 +133,9 @@ public class PlayerStateQuery {
BodyRegionV2.MOUTH
);
if (gag.isEmpty()) return false;
- return (
- gag.getItem() instanceof
- com.tiedup.remake.items.base.IHasGaggingEffect
- );
+ return com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem.getComponent(
+ gag, com.tiedup.remake.v2.bondage.component.ComponentType.GAGGING,
+ com.tiedup.remake.v2.bondage.component.GaggingComponent.class) != null;
}
/**
@@ -150,10 +149,9 @@ public class PlayerStateQuery {
BodyRegionV2.EYES
);
if (blindfold.isEmpty()) return false;
- return (
- blindfold.getItem() instanceof
- com.tiedup.remake.items.base.IHasBlindingEffect
- );
+ return com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem.getComponent(
+ blindfold, com.tiedup.remake.v2.bondage.component.ComponentType.BLINDING,
+ com.tiedup.remake.v2.bondage.component.BlindingComponent.class) != null;
}
/**
diff --git a/src/main/java/com/tiedup/remake/state/struggle/StruggleAccessory.java b/src/main/java/com/tiedup/remake/state/struggle/StruggleAccessory.java
index 54edfdc..b61bbc3 100644
--- a/src/main/java/com/tiedup/remake/state/struggle/StruggleAccessory.java
+++ b/src/main/java/com/tiedup/remake/state/struggle/StruggleAccessory.java
@@ -199,10 +199,13 @@ public class StruggleAccessory extends StruggleState {
if (
!collar.isEmpty() &&
- collar.getItem() instanceof
- com.tiedup.remake.items.ItemShockCollar shockCollar
+ com.tiedup.remake.v2.bondage.CollarHelper.canShock(collar)
) {
- return shockCollar.notifyStruggle(player, collar);
+ // V2 shock collar — notify via IHasResistance
+ if (collar.getItem() instanceof com.tiedup.remake.items.base.IHasResistance resistance) {
+ resistance.notifyStruggle(player);
+ }
+ return true;
}
return true; // No collar, proceed normally
}
diff --git a/src/main/java/com/tiedup/remake/state/struggle/StruggleBinds.java b/src/main/java/com/tiedup/remake/state/struggle/StruggleBinds.java
index e1bddf6..833db65 100644
--- a/src/main/java/com/tiedup/remake/state/struggle/StruggleBinds.java
+++ b/src/main/java/com/tiedup/remake/state/struggle/StruggleBinds.java
@@ -6,7 +6,6 @@ import com.tiedup.remake.core.SystemMessageManager.MessageCategory;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.base.IHasResistance;
import com.tiedup.remake.items.base.ILockable;
-import com.tiedup.remake.items.base.ItemBind;
import com.tiedup.remake.state.IPlayerLeashAccess;
import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2;
@@ -155,10 +154,6 @@ public class StruggleBinds extends StruggleState {
);
if (!collar.isEmpty() && CollarHelper.canShock(collar)) {
- // V1 shock collar
- if (collar.getItem() instanceof com.tiedup.remake.items.ItemShockCollar shockCollar) {
- return shockCollar.notifyStruggle(player, collar);
- }
// V2 shock collar — notify via IHasResistance if available
if (collar.getItem() instanceof IHasResistance resistance) {
resistance.notifyStruggle(player);
@@ -339,8 +334,6 @@ public class StruggleBinds extends StruggleState {
);
if (comp != null) {
baseResistance = comp.getBaseResistance();
- } else if (bindStack.getItem() instanceof ItemBind bind) {
- baseResistance = SettingsAccessor.getBindResistance(bind.getItemName());
} else {
baseResistance = 100;
}
diff --git a/src/main/java/com/tiedup/remake/state/struggle/StruggleCollar.java b/src/main/java/com/tiedup/remake/state/struggle/StruggleCollar.java
index f8a6b9c..fbe2b1d 100644
--- a/src/main/java/com/tiedup/remake/state/struggle/StruggleCollar.java
+++ b/src/main/java/com/tiedup/remake/state/struggle/StruggleCollar.java
@@ -4,7 +4,6 @@ import com.tiedup.remake.core.SystemMessageManager.MessageCategory;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.base.IHasResistance;
import com.tiedup.remake.items.base.ILockable;
-import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.CollarHelper;
@@ -139,10 +138,6 @@ public class StruggleCollar extends StruggleState {
ItemStack collar = V2EquipmentHelper.getInRegion(player, BodyRegionV2.NECK);
if (!collar.isEmpty() && CollarHelper.canShock(collar)) {
- // V1 shock collar
- if (collar.getItem() instanceof com.tiedup.remake.items.ItemShockCollar shockCollar) {
- return shockCollar.notifyStruggle(player, collar);
- }
// V2 shock collar — notify via IHasResistance
if (collar.getItem() instanceof IHasResistance resistance) {
resistance.notifyStruggle(player);
diff --git a/src/main/java/com/tiedup/remake/util/BondageItemLoaderUtility.java b/src/main/java/com/tiedup/remake/util/BondageItemLoaderUtility.java
index b25395b..5256363 100644
--- a/src/main/java/com/tiedup/remake/util/BondageItemLoaderUtility.java
+++ b/src/main/java/com/tiedup/remake/util/BondageItemLoaderUtility.java
@@ -1,11 +1,6 @@
package com.tiedup.remake.util;
import com.tiedup.remake.blocks.entity.IBondageItemHolder;
-import com.tiedup.remake.items.base.ItemBind;
-import com.tiedup.remake.items.base.ItemBlindfold;
-import com.tiedup.remake.items.base.ItemCollar;
-import com.tiedup.remake.items.base.ItemEarplugs;
-import com.tiedup.remake.items.base.ItemGag;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.BindModeHelper;
import com.tiedup.remake.v2.bondage.CollarHelper;
@@ -53,31 +48,30 @@ public final class BondageItemLoaderUtility {
ItemStack stack,
Player player
) {
- if ((stack.getItem() instanceof ItemBind || BindModeHelper.isBindItem(stack)) && holder.getBind().isEmpty()) {
+ if (BindModeHelper.isBindItem(stack) && holder.getBind().isEmpty()) {
holder.setBind(stack.copyWithCount(1));
if (!player.isCreative()) stack.shrink(1);
return true;
}
- if ((stack.getItem() instanceof ItemGag
- || DataDrivenBondageItem.getComponent(stack, ComponentType.GAGGING, GaggingComponent.class) != null)
+ if (DataDrivenBondageItem.getComponent(stack, ComponentType.GAGGING, GaggingComponent.class) != null
&& holder.getGag().isEmpty()) {
holder.setGag(stack.copyWithCount(1));
if (!player.isCreative()) stack.shrink(1);
return true;
}
- if ((stack.getItem() instanceof ItemBlindfold || isDataDrivenForRegion(stack, BodyRegionV2.EYES))
+ if (isDataDrivenForRegion(stack, BodyRegionV2.EYES)
&& holder.getBlindfold().isEmpty()) {
holder.setBlindfold(stack.copyWithCount(1));
if (!player.isCreative()) stack.shrink(1);
return true;
}
- if ((stack.getItem() instanceof ItemEarplugs || isDataDrivenForRegion(stack, BodyRegionV2.EARS))
+ if (isDataDrivenForRegion(stack, BodyRegionV2.EARS)
&& holder.getEarplugs().isEmpty()) {
holder.setEarplugs(stack.copyWithCount(1));
if (!player.isCreative()) stack.shrink(1);
return true;
}
- if ((stack.getItem() instanceof ItemCollar || CollarHelper.isCollar(stack))
+ if (CollarHelper.isCollar(stack)
&& holder.getCollar().isEmpty()) {
holder.setCollar(stack.copyWithCount(1));
if (!player.isCreative()) stack.shrink(1);
@@ -96,14 +90,6 @@ public final class BondageItemLoaderUtility {
*/
public static boolean isLoadableBondageItem(ItemStack stack) {
if (stack.isEmpty()) return false;
- // V1 item types
- if (stack.getItem() instanceof ItemBind
- || stack.getItem() instanceof ItemGag
- || stack.getItem() instanceof ItemBlindfold
- || stack.getItem() instanceof ItemEarplugs
- || stack.getItem() instanceof ItemCollar) {
- return true;
- }
// V2 data-driven items: bind, gag, blindfold, earplugs, collar
if (BindModeHelper.isBindItem(stack)) return true;
if (DataDrivenBondageItem.getComponent(stack, ComponentType.GAGGING, GaggingComponent.class) != null) return true;
diff --git a/src/main/java/com/tiedup/remake/v2/bondage/BindModeHelper.java b/src/main/java/com/tiedup/remake/v2/bondage/BindModeHelper.java
index bbfe851..62f6fde 100644
--- a/src/main/java/com/tiedup/remake/v2/bondage/BindModeHelper.java
+++ b/src/main/java/com/tiedup/remake/v2/bondage/BindModeHelper.java
@@ -1,6 +1,5 @@
package com.tiedup.remake.v2.bondage;
-import com.tiedup.remake.items.base.ItemBind;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemDefinition;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemRegistry;
@@ -42,8 +41,8 @@ public final class BindModeHelper {
if (def != null) {
return def.occupiedRegions().contains(BodyRegionV2.ARMS);
}
- // V1 fallback
- return stack.getItem() instanceof ItemBind;
+ // No V2 definition found
+ return false;
}
/**
diff --git a/src/main/java/com/tiedup/remake/v2/bondage/CollarHelper.java b/src/main/java/com/tiedup/remake/v2/bondage/CollarHelper.java
index c85641d..37ba240 100644
--- a/src/main/java/com/tiedup/remake/v2/bondage/CollarHelper.java
+++ b/src/main/java/com/tiedup/remake/v2/bondage/CollarHelper.java
@@ -1,10 +1,9 @@
package com.tiedup.remake.v2.bondage;
-import com.tiedup.remake.items.ItemChokeCollar;
-import com.tiedup.remake.items.ItemGpsCollar;
-import com.tiedup.remake.items.ItemShockCollar;
-import com.tiedup.remake.items.ItemShockCollarAuto;
-import com.tiedup.remake.items.base.ItemCollar;
+import com.tiedup.remake.core.TiedUpMod;
+import com.tiedup.remake.entities.EntityKidnapper;
+import com.tiedup.remake.entities.ai.kidnapper.KidnapperState;
+import com.tiedup.remake.state.CollarRegistry;
import com.tiedup.remake.v2.bondage.component.ChokingComponent;
import com.tiedup.remake.v2.bondage.component.ComponentType;
import com.tiedup.remake.v2.bondage.component.GpsComponent;
@@ -17,8 +16,11 @@ import java.util.UUID;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.Tag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.phys.AABB;
import org.jetbrains.annotations.Nullable;
/**
@@ -29,15 +31,16 @@ public final class CollarHelper {
private CollarHelper() {}
+ // Thread-local flag to suppress collar removal alerts during programmatic unequip
+ private static final ThreadLocal SUPPRESS_REMOVAL_ALERT =
+ ThreadLocal.withInitial(() -> false);
+
// ===== DETECTION =====
- // True if the stack is any kind of collar (V2 ownership component or V1 ItemCollar)
+ // True if the stack is a collar (V2 data-driven item with OwnershipComponent)
public static boolean isCollar(ItemStack stack) {
if (stack.isEmpty()) return false;
- if (DataDrivenBondageItem.getComponent(stack, ComponentType.OWNERSHIP, OwnershipComponent.class) != null) {
- return true;
- }
- return stack.getItem() instanceof ItemCollar;
+ return DataDrivenBondageItem.getComponent(stack, ComponentType.OWNERSHIP, OwnershipComponent.class) != null;
}
// ===== OWNERSHIP (NBT: "owners") =====
@@ -267,13 +270,10 @@ public final class CollarHelper {
// ===== SHOCK =====
- // True if the collar can shock (V2 ShockComponent or V1 ItemShockCollar)
+ // True if the collar can shock (V2 ShockComponent)
public static boolean canShock(ItemStack stack) {
if (stack.isEmpty()) return false;
- if (DataDrivenBondageItem.getComponent(stack, ComponentType.SHOCK, ShockComponent.class) != null) {
- return true;
- }
- return stack.getItem() instanceof ItemShockCollar;
+ return DataDrivenBondageItem.getComponent(stack, ComponentType.SHOCK, ShockComponent.class) != null;
}
public static boolean isPublicShock(ItemStack stack) {
@@ -285,27 +285,21 @@ public final class CollarHelper {
stack.getOrCreateTag().putBoolean("public_mode", publicMode);
}
- // V2: from ShockComponent auto interval, V1: from ItemShockCollarAuto field, else 0
+ // V2: from ShockComponent auto interval, else 0
public static int getShockInterval(ItemStack stack) {
ShockComponent comp = DataDrivenBondageItem.getComponent(
stack, ComponentType.SHOCK, ShockComponent.class
);
if (comp != null) return comp.getAutoInterval();
- if (stack.getItem() instanceof ItemShockCollarAuto auto) {
- return auto.getInterval();
- }
return 0;
}
// ===== GPS =====
- // True if the collar has GPS capabilities
+ // True if the collar has GPS capabilities (V2 GpsComponent)
public static boolean hasGPS(ItemStack stack) {
if (stack.isEmpty()) return false;
- if (DataDrivenBondageItem.getComponent(stack, ComponentType.GPS, GpsComponent.class) != null) {
- return true;
- }
- return stack.getItem() instanceof ItemGpsCollar;
+ return DataDrivenBondageItem.getComponent(stack, ComponentType.GPS, GpsComponent.class) != null;
}
public static boolean hasPublicTracking(ItemStack stack) {
@@ -330,13 +324,10 @@ public final class CollarHelper {
// ===== CHOKE =====
- // True if the collar is a choke collar
+ // True if the collar is a choke collar (V2 ChokingComponent)
public static boolean isChokeCollar(ItemStack stack) {
if (stack.isEmpty()) return false;
- if (DataDrivenBondageItem.getComponent(stack, ComponentType.CHOKING, ChokingComponent.class) != null) {
- return true;
- }
- return stack.getItem() instanceof ItemChokeCollar;
+ return DataDrivenBondageItem.getComponent(stack, ComponentType.CHOKING, ChokingComponent.class) != null;
}
public static boolean isChoking(ItemStack stack) {
@@ -361,11 +352,69 @@ public final class CollarHelper {
// Executes the action with collar removal alerts suppressed
public static void runWithSuppressedAlert(Runnable action) {
- ItemCollar.runWithSuppressedAlert(action);
+ SUPPRESS_REMOVAL_ALERT.set(true);
+ try {
+ action.run();
+ } finally {
+ SUPPRESS_REMOVAL_ALERT.set(false);
+ }
}
// True if removal alerts are currently suppressed (ThreadLocal state)
public static boolean isRemovalAlertSuppressed() {
- return ItemCollar.isRemovalAlertSuppressed();
+ return SUPPRESS_REMOVAL_ALERT.get();
+ }
+
+ // ===== COLLAR REMOVAL =====
+
+ /**
+ * Alert nearby kidnappers when a collar is forcibly removed.
+ * Handles CollarRegistry unregistration and kidnapper alerting.
+ *
+ * @param wearer The entity whose collar was removed
+ * @param forced Whether the removal was forced (struggle, lockpick, etc.)
+ */
+ public static void onCollarRemoved(LivingEntity wearer, boolean forced) {
+ if (wearer == null || wearer.level().isClientSide()) {
+ return;
+ }
+
+ if (!forced) {
+ return; // Only alert on forced removals
+ }
+
+ if (!(wearer.level() instanceof ServerLevel serverLevel)) {
+ return;
+ }
+
+ TiedUpMod.LOGGER.info(
+ "[CollarHelper] {} collar was forcibly removed - alerting kidnappers",
+ wearer.getName().getString()
+ );
+
+ // Unregister from CollarRegistry
+ CollarRegistry registry = CollarRegistry.get(serverLevel);
+ if (registry != null) {
+ registry.unregisterWearer(wearer.getUUID());
+ }
+
+ // Find and alert nearby kidnappers
+ AABB searchBox = wearer.getBoundingBox().inflate(50, 20, 50);
+ List kidnappers = serverLevel.getEntitiesOfClass(
+ EntityKidnapper.class, searchBox
+ );
+
+ for (EntityKidnapper kidnapper : kidnappers) {
+ if (!kidnapper.hasCaptives() && !kidnapper.isTiedUp()) {
+ kidnapper.setAlertTarget(wearer);
+ kidnapper.setCurrentState(KidnapperState.ALERT);
+ kidnapper.broadcastAlert(wearer);
+
+ TiedUpMod.LOGGER.debug(
+ "[CollarHelper] Alerted kidnapper {} about collar removal",
+ kidnapper.getNpcName()
+ );
+ }
+ }
}
}
diff --git a/src/main/java/com/tiedup/remake/v2/bondage/PoseTypeHelper.java b/src/main/java/com/tiedup/remake/v2/bondage/PoseTypeHelper.java
index 5804147..5bad5a8 100644
--- a/src/main/java/com/tiedup/remake/v2/bondage/PoseTypeHelper.java
+++ b/src/main/java/com/tiedup/remake/v2/bondage/PoseTypeHelper.java
@@ -1,6 +1,5 @@
package com.tiedup.remake.v2.bondage;
-import com.tiedup.remake.items.base.ItemBind;
import com.tiedup.remake.items.base.PoseType;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemDefinition;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemRegistry;
@@ -26,10 +25,6 @@ public final class PoseTypeHelper {
return PoseType.STANDARD;
}
}
- // V1 fallback
- if (stack.getItem() instanceof ItemBind bind) {
- return bind.getPoseType();
- }
return PoseType.STANDARD;
}
}
diff --git a/src/main/java/com/tiedup/remake/v2/bondage/component/LockableComponent.java b/src/main/java/com/tiedup/remake/v2/bondage/component/LockableComponent.java
index ef8ddcd..257d2a4 100644
--- a/src/main/java/com/tiedup/remake/v2/bondage/component/LockableComponent.java
+++ b/src/main/java/com/tiedup/remake/v2/bondage/component/LockableComponent.java
@@ -51,7 +51,8 @@ public class LockableComponent implements IItemComponent {
@Override
public void appendTooltip(ItemStack stack, @Nullable Level level, List tooltip, TooltipFlag flag) {
- tooltip.add(Component.translatable("item.tiedup.tooltip.lockable").withStyle(ChatFormatting.GOLD));
+ // Lock status ("Locked"/"Lockable") is handled by ILockable.appendLockTooltip()
+ // called from AbstractV2BondageItem.appendHoverText() — no duplicate here.
if (flag.isAdvanced()) {
tooltip.add(Component.translatable("item.tiedup.tooltip.lock_resistance", lockResistance)
.withStyle(ChatFormatting.DARK_GRAY));
diff --git a/src/main/java/com/tiedup/remake/v2/bondage/component/OwnershipComponent.java b/src/main/java/com/tiedup/remake/v2/bondage/component/OwnershipComponent.java
index 07be4a8..75f3bfd 100644
--- a/src/main/java/com/tiedup/remake/v2/bondage/component/OwnershipComponent.java
+++ b/src/main/java/com/tiedup/remake/v2/bondage/component/OwnershipComponent.java
@@ -2,7 +2,6 @@ package com.tiedup.remake.v2.bondage.component;
import com.google.gson.JsonObject;
import com.tiedup.remake.core.TiedUpMod;
-import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.state.CollarRegistry;
import com.tiedup.remake.v2.bondage.CollarHelper;
import java.util.HashSet;
@@ -71,7 +70,7 @@ public class OwnershipComponent implements IItemComponent {
// onCollarRemoved handles both the alert AND the unregister call internally,
// so we do NOT call registry.unregisterWearer() separately to avoid double unregister.
if (!CollarHelper.isRemovalAlertSuppressed()) {
- ItemCollar.onCollarRemoved(entity, true);
+ CollarHelper.onCollarRemoved(entity, true);
} else {
// Suppressed alert path: still need to unregister, just skip the alert
try {
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 f73ecf4..ef34529 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,11 +167,11 @@ public class DataDrivenBondageItem extends AbstractV2BondageItem {
return TyingInteractionHelper.handleTying(serverPlayer, target, stack, hand);
}
- // 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.
+ // NECK: blocked — collar equip requires owner setup (add owner to NBT,
+ // register in CollarRegistry, play sound, sync) which is not yet wired
+ // through interactLivingEntity. TODO: implement collar equip flow.
if (regions.contains(BodyRegionV2.NECK)) {
- TiedUpMod.LOGGER.debug("[DataDrivenBondageItem] NECK equip blocked — collar flow not wired yet");
+ TiedUpMod.LOGGER.debug("[DataDrivenBondageItem] NECK equip via right-click not yet implemented");
return InteractionResult.PASS;
}
diff --git a/src/main/java/com/tiedup/remake/v2/furniture/EntityFurniture.java b/src/main/java/com/tiedup/remake/v2/furniture/EntityFurniture.java
index 716daee..7de1979 100644
--- a/src/main/java/com/tiedup/remake/v2/furniture/EntityFurniture.java
+++ b/src/main/java/com/tiedup/remake/v2/furniture/EntityFurniture.java
@@ -2,7 +2,7 @@ package com.tiedup.remake.v2.furniture;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.ItemMasterKey;
-import com.tiedup.remake.items.base.ItemCollar;
+import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.state.PlayerCaptorManager;
@@ -713,11 +713,10 @@ public class EntityFurniture
);
if (
collarStack.isEmpty() ||
- !(collarStack.getItem() instanceof
- ItemCollar collar)
+ !CollarHelper.isCollar(collarStack)
) continue;
if (
- !collar.isOwner(collarStack, serverPlayer) &&
+ !CollarHelper.isOwner(collarStack, serverPlayer) &&
!serverPlayer.hasPermissions(2)
) continue;