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 itemClass; + private final Predicate itemCheck; private final Predicate canEquipCheck; private final BiConsumer equipAction; private GenericBondageDispenseBehavior( - Class 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:

- * - * - * @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:

- - * - - */ - -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;