diff --git a/src/main/java/com/tiedup/remake/entities/EntityKidnapperMerchant.java b/src/main/java/com/tiedup/remake/entities/EntityKidnapperMerchant.java index 1874af7..50c4e62 100644 --- a/src/main/java/com/tiedup/remake/entities/EntityKidnapperMerchant.java +++ b/src/main/java/com/tiedup/remake/entities/EntityKidnapperMerchant.java @@ -889,6 +889,10 @@ public class EntityKidnapperMerchant extends EntityKidnapperElite { // Clear trading players to prevent dangling references if (!this.level().isClientSide) { int count = tradingPlayers.size(); + // Clean up reverse-lookup map BEFORE clearing to prevent memory leak + for (UUID playerUuid : tradingPlayers) { + playerToMerchant.remove(playerUuid); + } this.tradingPlayers.clear(); if (count > 0) { TiedUpMod.LOGGER.debug( diff --git a/src/main/java/com/tiedup/remake/items/GenericKnife.java b/src/main/java/com/tiedup/remake/items/GenericKnife.java index 7018f6d..6fc83fb 100644 --- a/src/main/java/com/tiedup/remake/items/GenericKnife.java +++ b/src/main/java/com/tiedup/remake/items/GenericKnife.java @@ -384,26 +384,27 @@ public class GenericKnife extends Item implements IKnife { return; } - // Accessory IS locked - reduce lock resistance + // Accessory IS locked - reduce lock resistance via knife cut progress ILockable lockable = (ILockable) accessory.getItem(); - int currentRes = lockable.getCurrentLockResistance(accessory); - int newRes = Math.max(0, currentRes - speed); - lockable.setCurrentLockResistance(accessory, newRes); + int cutProgress = com.tiedup.remake.util.ItemNBTHelper.getInt(accessory, "knifeCutProgress"); + int newProgress = cutProgress + speed; + int lockResistance = com.tiedup.remake.core.SettingsAccessor.getPadlockResistance(null); + com.tiedup.remake.util.ItemNBTHelper.setInt(accessory, "knifeCutProgress", newProgress); TiedUpMod.LOGGER.debug( - "[GenericKnife] {} cutting {} lock: resistance {} -> {}", + "[GenericKnife] {} cutting {} lock: progress {} / {}", player.getName().getString(), target, - currentRes, - newRes + newProgress, + lockResistance ); // Check if lock is destroyed - if (newRes <= 0) { + if (newProgress >= lockResistance) { // Destroy the lock (remove padlock, clear lock state) lockable.setLockedByKeyUUID(accessory, null); // Unlocks and clears locked state lockable.setLockable(accessory, false); // Remove padlock entirely - lockable.clearLockResistance(accessory); + com.tiedup.remake.util.ItemNBTHelper.remove(accessory, "knifeCutProgress"); lockable.setJammed(accessory, false); state.clearKnifeCutTarget(); diff --git a/src/main/java/com/tiedup/remake/items/ItemLockpick.java b/src/main/java/com/tiedup/remake/items/ItemLockpick.java index 6814ceb..a3b44db 100644 --- a/src/main/java/com/tiedup/remake/items/ItemLockpick.java +++ b/src/main/java/com/tiedup/remake/items/ItemLockpick.java @@ -214,7 +214,6 @@ public class ItemLockpick extends Item { if (success) { // SUCCESS: Unlock the item, PRESERVE the padlock lockable.setLockedByKeyUUID(targetStack, null); // Unlock - lockable.clearLockResistance(targetStack); // Clear struggle progress // lockable stays true - padlock preserved! SystemMessageManager.sendToPlayer( 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 3ded9e7..d14e90f 100644 --- a/src/main/java/com/tiedup/remake/items/base/ILockable.java +++ b/src/main/java/com/tiedup/remake/items/base/ILockable.java @@ -222,20 +222,6 @@ public interface ILockable { */ String NBT_JAMMED = "jammed"; - /** - * Get the resistance added by the lock for struggle mechanics. - * - *

When locked, this value is added to the item's base resistance. - * Configurable via server config and GameRule.

- * - * @return Lock resistance value (default: 250, configurable) - */ - default int getLockResistance() { - return com.tiedup.remake.core.SettingsAccessor.getPadlockResistance( - null - ); - } - /** * Check if the lock is jammed (lockpick failed critically). * @@ -266,63 +252,6 @@ public interface ILockable { } } - // ========== LOCK RESISTANCE (for struggle) ========== - - /** - * NBT key for current lock resistance during struggle. - */ - String NBT_LOCK_RESISTANCE = "lockResistance"; - - /** - * Get the current lock resistance remaining for struggle. - * Initialized to getLockResistance() (configurable, default 250) when first locked. - * - * @param stack The ItemStack to check - * @return Current lock resistance (0 if not locked or fully struggled) - */ - default int getCurrentLockResistance(ItemStack stack) { - if (stack.isEmpty()) return 0; - - // If locked but no resistance stored yet, initialize it - if ( - isLocked(stack) && - !ItemNBTHelper.contains(stack, NBT_LOCK_RESISTANCE) - ) { - return getLockResistance(); // Configurable via ModConfig - } - - return ItemNBTHelper.getInt(stack, NBT_LOCK_RESISTANCE); - } - - /** - * Set the current lock resistance remaining for struggle. - * - * @param stack The ItemStack to modify - * @param resistance The new resistance value - */ - default void setCurrentLockResistance(ItemStack stack, int resistance) { - ItemNBTHelper.setInt(stack, NBT_LOCK_RESISTANCE, resistance); - } - - /** - * Initialize lock resistance when item is locked. - * Called when setLockedByKeyUUID is called with a non-null UUID. - * - * @param stack The ItemStack to initialize - */ - default void initializeLockResistance(ItemStack stack) { - setCurrentLockResistance(stack, getLockResistance()); - } - - /** - * Clear lock resistance when item is unlocked. - * - * @param stack The ItemStack to clear - */ - default void clearLockResistance(ItemStack stack) { - ItemNBTHelper.remove(stack, NBT_LOCK_RESISTANCE); - } - // ========== LOCK BREAKING (struggle/force) ========== /** @@ -334,7 +263,6 @@ public interface ILockable { *
  • Unlocks the item (lockedByKeyUUID = null)
  • *
  • Removes the lockable flag (no more padlock slot)
  • *
  • Clears any jam state
  • - *
  • Clears stored lock resistance
  • * * * @param stack The ItemStack to break the lock on @@ -344,6 +272,5 @@ public interface ILockable { setLockedByKeyUUID(stack, null); setLockable(stack, false); setJammed(stack, false); - clearLockResistance(stack); } } diff --git a/src/main/java/com/tiedup/remake/items/clothes/GenericClothes.java b/src/main/java/com/tiedup/remake/items/clothes/GenericClothes.java index 706083a..2f1173a 100644 --- a/src/main/java/com/tiedup/remake/items/clothes/GenericClothes.java +++ b/src/main/java/com/tiedup/remake/items/clothes/GenericClothes.java @@ -394,7 +394,6 @@ public class GenericClothes extends Item implements ILockable, IV2BondageItem { stack.getOrCreateTag().putBoolean(NBT_LOCKED, state); if (!state) { // When unlocking, clear lock-related data - clearLockResistance(stack); setJammed(stack, false); } return stack; @@ -439,7 +438,6 @@ public class GenericClothes extends Item implements ILockable, IV2BondageItem { if (keyUUID != null) { tag.putUUID(NBT_LOCKED_BY_KEY_UUID, keyUUID); setLocked(stack, true); - initializeLockResistance(stack); } else { tag.remove(NBT_LOCKED_BY_KEY_UUID); setLocked(stack, false); diff --git a/src/main/java/com/tiedup/remake/minigame/StruggleSessionManager.java b/src/main/java/com/tiedup/remake/minigame/StruggleSessionManager.java index 258721f..9ecdb06 100644 --- a/src/main/java/com/tiedup/remake/minigame/StruggleSessionManager.java +++ b/src/main/java/com/tiedup/remake/minigame/StruggleSessionManager.java @@ -622,18 +622,14 @@ public class StruggleSessionManager { ); if (accessoryStack.isEmpty()) return; - if ( - accessoryStack.getItem() instanceof - com.tiedup.remake.items.base.ILockable lockable - ) { - // Update the lock resistance to match session state - lockable.setCurrentLockResistance( - accessoryStack, - session.getCurrentResistance() - ); - // Sync V2 equipment state - V2EquipmentHelper.sync(player); - } + // Update accessory struggle resistance in dedicated NBT key + com.tiedup.remake.util.ItemNBTHelper.setInt( + accessoryStack, + "accessoryStruggleResistance", + session.getCurrentResistance() + ); + // Sync V2 equipment state + V2EquipmentHelper.sync(player); return; } 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 1505d90..6cf0b64 100644 --- a/src/main/java/com/tiedup/remake/network/minigame/PacketLockpickAttempt.java +++ b/src/main/java/com/tiedup/remake/network/minigame/PacketLockpickAttempt.java @@ -172,47 +172,17 @@ public class PacketLockpickAttempt { !targetStack.isEmpty() && targetStack.getItem() instanceof ILockable lockable ) { - // Get lock resistance BEFORE clearing it - int lockResistance = lockable.getCurrentLockResistance(targetStack); - // Unlock the item lockable.setLockedByKeyUUID(targetStack, null); - lockable.clearLockResistance(targetStack); TiedUpMod.LOGGER.info( - "[PacketLockpickAttempt] Player {} successfully picked lock on {} ({}, resistance was {})", + "[PacketLockpickAttempt] Player {} successfully picked lock on {} ({})", player.getName().getString(), targetStack.getDisplayName().getString(), - targetRegion, - lockResistance + targetRegion ); - // Deduct lock resistance from bind resistance - PlayerBindState state = PlayerBindState.getInstance(player); - if (state != null && state.isTiedUp() && lockResistance > 0) { - int currentBindResistance = state.getCurrentBindResistance(); - int newBindResistance = Math.max( - 0, - currentBindResistance - lockResistance - ); - state.setCurrentBindResistance(newBindResistance); - - TiedUpMod.LOGGER.info( - "[PacketLockpickAttempt] Deducted {} from bind resistance: {} -> {}", - lockResistance, - currentBindResistance, - newBindResistance - ); - - // Check if player escaped (resistance = 0) - if (newBindResistance <= 0) { - state.getStruggleBinds().successActionExternal(state); - TiedUpMod.LOGGER.info( - "[PacketLockpickAttempt] Player {} escaped via lockpick!", - player.getName().getString() - ); - } - } + // Lock is now unlocked -- bind resistance is tracked separately via IHasResistance } // Damage lockpick 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 6b941c7..b40735d 100644 --- a/src/main/java/com/tiedup/remake/network/slave/PacketSlaveItemManage.java +++ b/src/main/java/com/tiedup/remake/network/slave/PacketSlaveItemManage.java @@ -432,26 +432,6 @@ public class PacketSlaveItemManage { // Lock with keyUUID lockable.setLockedByKeyUUID(itemStack, keyUUID); - if ( - region == BodyRegionV2.ARMS && - itemStack.getItem() instanceof com.tiedup.remake.items.base.IHasResistance resistanceItem - ) { - int currentResistance = resistanceItem.getCurrentResistance( - itemStack, - target - ); - int lockResistance = lockable.getLockResistance(); // Configurable via ModConfig - resistanceItem.setCurrentResistance( - itemStack, - currentResistance + lockResistance - ); - TiedUpMod.LOGGER.info( - "[PacketSlaveItemManage] Added {} lock resistance to bind (total: {})", - lockResistance, - currentResistance + lockResistance - ); - } - TiedUpMod.LOGGER.info( "[PacketSlaveItemManage] {} locked {}'s {} with key {}", sender.getName().getString(), @@ -528,7 +508,6 @@ public class PacketSlaveItemManage { // Unlock the item - just unlock, keep padlock attached (can be re-locked) lockable.setLockedByKeyUUID(itemStack, null); - lockable.clearLockResistance(itemStack); // Clear any struggle progress // Note: Padlock is only dropped on REMOVE, not on UNLOCK TiedUpMod.LOGGER.info( 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 b61bbc3..30464d2 100644 --- a/src/main/java/com/tiedup/remake/state/struggle/StruggleAccessory.java +++ b/src/main/java/com/tiedup/remake/state/struggle/StruggleAccessory.java @@ -1,12 +1,15 @@ package com.tiedup.remake.state.struggle; +import com.tiedup.remake.core.SettingsAccessor; import com.tiedup.remake.core.SystemMessageManager; import com.tiedup.remake.core.SystemMessageManager.MessageCategory; import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.items.base.ILockable; import com.tiedup.remake.state.PlayerBindState; +import com.tiedup.remake.util.ItemNBTHelper; import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper; +import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; @@ -32,28 +35,36 @@ public class StruggleAccessory extends StruggleState { this.accessoryRegion = accessoryRegion; } + /** NBT key for accessory struggle progress (replaces removed ILockable lock resistance). */ + private static final String NBT_ACCESSORY_STRUGGLE = "accessoryStruggleResistance"; + /** * Get the current resistance for this accessory. - * Accessories have 0 base resistance - only lock resistance exists. - * Resistance is stored in the item's NBT to persist between attempts. + * Accessories have 0 base resistance -- when locked, resistance comes from + * {@link SettingsAccessor#getPadlockResistance}. Stored in a dedicated NBT + * key on the accessory stack to persist between attempts. * * @param state The player's bind state - * @return Current resistance value (0 if not locked, 250 if locked and not yet struggled) + * @return Current resistance value (0 if not locked) */ @Override protected int getResistanceState(PlayerBindState state) { ItemStack stack = getAccessoryStack(state); if (stack.isEmpty()) return 0; - if (stack.getItem() instanceof ILockable lockable) { - return lockable.getCurrentLockResistance(stack); + if (stack.getItem() instanceof ILockable lockable && lockable.isLocked(stack)) { + // If no struggle progress stored yet, initialize from config + if (!ItemNBTHelper.contains(stack, NBT_ACCESSORY_STRUGGLE)) { + return SettingsAccessor.getPadlockResistance(null); + } + return ItemNBTHelper.getInt(stack, NBT_ACCESSORY_STRUGGLE); } return 0; } /** * Set the current resistance for this accessory struggle. - * Stored in the item's NBT to persist between attempts. + * Stored in a dedicated NBT key on the accessory stack. * * @param state The player's bind state * @param resistance The new resistance value @@ -63,9 +74,7 @@ public class StruggleAccessory extends StruggleState { ItemStack stack = getAccessoryStack(state); if (stack.isEmpty()) return; - if (stack.getItem() instanceof ILockable lockable) { - lockable.setCurrentLockResistance(stack, resistance); - } + ItemNBTHelper.setInt(stack, NBT_ACCESSORY_STRUGGLE, resistance); } /** @@ -87,29 +96,15 @@ public class StruggleAccessory extends StruggleState { return false; } - // Only locked accessories can be struggled against - if (!(stack.getItem() instanceof ILockable lockable)) { + // Only require struggle if LOCKED or has BUILT_IN_LOCK. + // Unlocked accessories without built-in lock can be freely removed (no struggle needed). + boolean isLocked = stack.getItem() instanceof ILockable lockable && lockable.isLocked(stack); + boolean hasBuiltInLock = DataDrivenBondageItem.hasBuiltInLock(stack); + if (!isLocked && !hasBuiltInLock) { return false; } - return lockable.isLocked(stack); - } - - /** - * Check if the accessory is locked. - * - * @param state The player's bind state - * @return true if the accessory is locked - */ - @Override - protected boolean isItemLocked(PlayerBindState state) { - ItemStack stack = getAccessoryStack(state); - if ( - stack.isEmpty() || !(stack.getItem() instanceof ILockable lockable) - ) { - return false; - } - return lockable.isLocked(stack); + return true; } /** 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 833db65..ea72013 100644 --- a/src/main/java/com/tiedup/remake/state/struggle/StruggleBinds.java +++ b/src/main/java/com/tiedup/remake/state/struggle/StruggleBinds.java @@ -1,6 +1,5 @@ package com.tiedup.remake.state.struggle; -import com.tiedup.remake.core.SettingsAccessor; import com.tiedup.remake.core.SystemMessageManager; import com.tiedup.remake.core.SystemMessageManager.MessageCategory; import com.tiedup.remake.core.TiedUpMod; @@ -93,31 +92,6 @@ public class StruggleBinds extends StruggleState { return true; } - /** - * Check if the bind item is locked. - * Used by StruggleState to apply x10 resistance penalty. - * - * @param state The player's bind state - * @return true if the bind is locked - */ - @Override - protected boolean isItemLocked(PlayerBindState state) { - Player player = state.getPlayer(); - if (player == null) return false; - - ItemStack bindStack = V2EquipmentHelper.getInRegion( - player, - BodyRegionV2.ARMS - ); - if (bindStack.isEmpty()) return false; - - // Works for both V1 (ItemBind) and V2 (DataDrivenBondageItem) via ILockable - if (bindStack.getItem() instanceof ILockable lockable) { - return lockable.isLocked(bindStack); - } - return false; - } - /** * Called when the player successfully escapes from their binds. * Drops all bondage items and completely unties the player. 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 fbe2b1d..79ec1ef 100644 --- a/src/main/java/com/tiedup/remake/state/struggle/StruggleCollar.java +++ b/src/main/java/com/tiedup/remake/state/struggle/StruggleCollar.java @@ -107,13 +107,12 @@ public class StruggleCollar extends StruggleState { return false; } - // Check if locked (works for V1 and V2 via ILockable) - if (collar.getItem() instanceof ILockable lockable) { - if (!lockable.isLocked(collar)) { - TiedUpMod.LOGGER.debug("[StruggleCollar] Collar is not locked"); - return false; - } - } else { + // Only require struggle if LOCKED or has BUILT_IN_LOCK. + // Unlocked collars without built-in lock can be freely removed (no struggle needed). + boolean isLocked = collar.getItem() instanceof ILockable lockable && lockable.isLocked(collar); + boolean hasBuiltInLock = DataDrivenBondageItem.hasBuiltInLock(collar); + if (!isLocked && !hasBuiltInLock) { + TiedUpMod.LOGGER.debug("[StruggleCollar] Collar is not locked and has no built-in lock — no struggle needed"); return false; } diff --git a/src/main/java/com/tiedup/remake/state/struggle/StruggleState.java b/src/main/java/com/tiedup/remake/state/struggle/StruggleState.java index 308523a..4cf4122 100644 --- a/src/main/java/com/tiedup/remake/state/struggle/StruggleState.java +++ b/src/main/java/com/tiedup/remake/state/struggle/StruggleState.java @@ -212,16 +212,6 @@ public abstract class StruggleState { */ protected abstract boolean canStruggle(PlayerBindState state); - /** - * Check if the item being struggled against is locked. - * - * @param state The player's bind state - * @return true if the item is locked - */ - protected boolean isItemLocked(PlayerBindState state) { - return false; // Default: not locked, subclasses override - } - /** * Check if debug logging is enabled. * diff --git a/src/main/java/com/tiedup/remake/tasks/V2TyingPlayerTask.java b/src/main/java/com/tiedup/remake/tasks/V2TyingPlayerTask.java index f36f27c..b80349b 100644 --- a/src/main/java/com/tiedup/remake/tasks/V2TyingPlayerTask.java +++ b/src/main/java/com/tiedup/remake/tasks/V2TyingPlayerTask.java @@ -70,6 +70,13 @@ public class V2TyingPlayerTask extends TyingPlayerTask { targetEntity.getName().getString() ); + // Validate heldStack hasn't changed during tying (e.g. player swapped items) + if (heldStack.isEmpty() || !ItemStack.isSameItemSameTags(heldStack, bind)) { + TiedUpMod.LOGGER.warn("[V2TyingPlayerTask] heldStack changed during tying — aborting"); + stop(); + return; + } + // Equip via V2 system V2EquipResult result = V2EquipmentHelper.equipItem(targetEntity, bind); if (result.isSuccess()) { diff --git a/src/main/java/com/tiedup/remake/v2/bondage/component/BuiltInLockComponent.java b/src/main/java/com/tiedup/remake/v2/bondage/component/BuiltInLockComponent.java new file mode 100644 index 0000000..31b1521 --- /dev/null +++ b/src/main/java/com/tiedup/remake/v2/bondage/component/BuiltInLockComponent.java @@ -0,0 +1,27 @@ +package com.tiedup.remake.v2.bondage.component; + +import com.google.gson.JsonObject; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.item.ItemStack; + +/** + * Component: permanent lock for organic items (slime, vine, web, tape). + * + *

    These items cannot have a padlock attached but always require struggle + * to remove — they behave as if permanently locked.

    + * + *

    JSON config: {@code "built_in_lock": {}}

    + */ +public class BuiltInLockComponent implements IItemComponent { + + private BuiltInLockComponent() {} + + public static IItemComponent fromJson(JsonObject config) { + return new BuiltInLockComponent(); + } + + @Override + public boolean blocksUnequip(ItemStack stack, LivingEntity entity) { + return true; + } +} diff --git a/src/main/java/com/tiedup/remake/v2/bondage/component/ComponentType.java b/src/main/java/com/tiedup/remake/v2/bondage/component/ComponentType.java index 198159e..b4c616c 100644 --- a/src/main/java/com/tiedup/remake/v2/bondage/component/ComponentType.java +++ b/src/main/java/com/tiedup/remake/v2/bondage/component/ComponentType.java @@ -16,7 +16,8 @@ public enum ComponentType { GPS("gps", GpsComponent::fromJson), CHOKING("choking", ChokingComponent::fromJson), ADJUSTABLE("adjustable", AdjustableComponent::fromJson), - OWNERSHIP("ownership", OwnershipComponent::fromJson); + OWNERSHIP("ownership", OwnershipComponent::fromJson), + BUILT_IN_LOCK("built_in_lock", BuiltInLockComponent::fromJson); private final String jsonKey; private final Function factory; 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 ef34529..6d3e61c 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 @@ -7,6 +7,7 @@ import com.tiedup.remake.v2.bondage.IV2BondageEquipment; import com.tiedup.remake.v2.bondage.TyingInteractionHelper; import com.tiedup.remake.v2.bondage.V2BondageItems; import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper; +import com.tiedup.remake.v2.bondage.component.BuiltInLockComponent; import com.tiedup.remake.v2.bondage.component.ComponentHolder; import com.tiedup.remake.v2.bondage.component.ComponentType; import com.tiedup.remake.v2.bondage.component.IItemComponent; @@ -355,6 +356,16 @@ public class DataDrivenBondageItem extends AbstractV2BondageItem { holder.onEquipped(stack, entity); } + // Initialize currentResistance in NBT from ResistanceComponent at equip time. + // This avoids the MAX-scan fallback in getBaseResistance(entity) which returns + // the highest resistance across ALL equipped data-driven items. + ResistanceComponent resistComp = getComponent(stack, ComponentType.RESISTANCE, ResistanceComponent.class); + if (resistComp != null) { + com.tiedup.remake.items.base.IHasResistance resistanceItem = + (com.tiedup.remake.items.base.IHasResistance) stack.getItem(); + resistanceItem.setCurrentResistance(stack, resistComp.getBaseResistance()); + } + // NPC speed reduction (players use MovementStyleManager, not this legacy path) if (!(entity instanceof Player)) { Set regions = getOccupiedRegions(stack); @@ -436,6 +447,11 @@ public class DataDrivenBondageItem extends AbstractV2BondageItem { ); } + /** Check if the item has a built-in lock (organic items: slime, vine, web, tape). */ + public static boolean hasBuiltInLock(ItemStack stack) { + return getComponent(stack, ComponentType.BUILT_IN_LOCK, BuiltInLockComponent.class) != null; + } + /** * Stack-aware padlock attachment check for data-driven items. * Returns false for organic items (slime, vine, web, tape) that have diff --git a/src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenItemRegistry.java b/src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenItemRegistry.java index 1c72af3..0be4bb5 100644 --- a/src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenItemRegistry.java +++ b/src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenItemRegistry.java @@ -139,7 +139,9 @@ public final class DataDrivenItemRegistry { * Clear all definitions. Called on world unload or for testing. */ public static void clear() { - SNAPSHOT = RegistrySnapshot.EMPTY; + synchronized (RELOAD_LOCK) { + SNAPSHOT = RegistrySnapshot.EMPTY; + } } // ===== COMPONENT HOLDERS ===== diff --git a/src/main/java/com/tiedup/remake/v2/bondage/network/PacketV2LockToggle.java b/src/main/java/com/tiedup/remake/v2/bondage/network/PacketV2LockToggle.java index 488514a..f989157 100644 --- a/src/main/java/com/tiedup/remake/v2/bondage/network/PacketV2LockToggle.java +++ b/src/main/java/com/tiedup/remake/v2/bondage/network/PacketV2LockToggle.java @@ -129,7 +129,6 @@ public class PacketV2LockToggle { UUID keyUUID = heldKey.getKeyUUID(heldKeyStack); lockable.setLockedByKeyUUID(stack, keyUUID); - lockable.initializeLockResistance(stack); TiedUpMod.LOGGER.debug( "[V2LockToggle] Locked region {} on entity {}", @@ -142,16 +141,18 @@ public class PacketV2LockToggle { if (hasMasterKey) { lockable.setLockedByKeyUUID(stack, null); - lockable.clearLockResistance(stack); } else if (heldKey != null) { UUID keyUUID = heldKey.getKeyUUID(heldKeyStack); if (!lockable.matchesKey(stack, keyUUID)) return; lockable.setLockedByKeyUUID(stack, null); - lockable.clearLockResistance(stack); } else { return; } + // Clear partial knife-cut progress and stale struggle resistance on unlock + com.tiedup.remake.util.ItemNBTHelper.remove(stack, "knifeCutProgress"); + com.tiedup.remake.util.ItemNBTHelper.remove(stack, "accessoryStruggleResistance"); + TiedUpMod.LOGGER.debug( "[V2LockToggle] Unlocked region {} on entity {}", region.name(), diff --git a/src/main/java/com/tiedup/remake/v2/bondage/network/PacketV2StruggleStart.java b/src/main/java/com/tiedup/remake/v2/bondage/network/PacketV2StruggleStart.java index d8ca9a9..66bd586 100644 --- a/src/main/java/com/tiedup/remake/v2/bondage/network/PacketV2StruggleStart.java +++ b/src/main/java/com/tiedup/remake/v2/bondage/network/PacketV2StruggleStart.java @@ -84,7 +84,21 @@ public class PacketV2StruggleStart { if (stack.getItem() instanceof ILockable lockable) { isLocked = lockable.isLocked(stack); if (isLocked) { - resistance += lockable.getCurrentLockResistance(stack); + // Only add padlock bonus on FIRST session (fresh resistance = base). + // If resistance was already decremented and persisted from a prior + // interrupted session, the padlock bonus is already baked in. + int baseResistance = resistance; // getCurrentResistance returns base if no NBT + com.tiedup.remake.v2.bondage.component.ResistanceComponent comp = + com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem.getComponent( + stack, com.tiedup.remake.v2.bondage.component.ComponentType.RESISTANCE, + com.tiedup.remake.v2.bondage.component.ResistanceComponent.class); + if (comp != null) { + baseResistance = comp.getBaseResistance(); + } + if (resistance >= baseResistance) { + // Fresh or fully restored — add padlock bonus + resistance += com.tiedup.remake.core.SettingsAccessor.getPadlockResistance(null); + } } } diff --git a/src/main/resources/assets/tiedup/tiedup_items/duct_tape.json b/src/main/resources/assets/tiedup/tiedup_items/duct_tape.json index 2542047..33f1de4 100644 --- a/src/main/resources/assets/tiedup/tiedup_items/duct_tape.json +++ b/src/main/resources/assets/tiedup/tiedup_items/duct_tape.json @@ -25,6 +25,7 @@ "components": { "resistance": { "id": "tape" - } + }, + "built_in_lock": {} } } \ No newline at end of file diff --git a/src/main/resources/assets/tiedup/tiedup_items/slime.json b/src/main/resources/assets/tiedup/tiedup_items/slime.json index b2f02fa..295c3e0 100644 --- a/src/main/resources/assets/tiedup/tiedup_items/slime.json +++ b/src/main/resources/assets/tiedup/tiedup_items/slime.json @@ -25,6 +25,7 @@ "components": { "resistance": { "id": "slime" - } + }, + "built_in_lock": {} } } \ No newline at end of file diff --git a/src/main/resources/assets/tiedup/tiedup_items/slime_gag.json b/src/main/resources/assets/tiedup/tiedup_items/slime_gag.json index b33a8ae..daa3c31 100644 --- a/src/main/resources/assets/tiedup/tiedup_items/slime_gag.json +++ b/src/main/resources/assets/tiedup/tiedup_items/slime_gag.json @@ -23,6 +23,7 @@ "gagging": { "material": "stuffed" }, - "adjustable": {} + "adjustable": {}, + "built_in_lock": {} } } \ No newline at end of file diff --git a/src/main/resources/assets/tiedup/tiedup_items/tape_gag.json b/src/main/resources/assets/tiedup/tiedup_items/tape_gag.json index b475ad0..0932da9 100644 --- a/src/main/resources/assets/tiedup/tiedup_items/tape_gag.json +++ b/src/main/resources/assets/tiedup/tiedup_items/tape_gag.json @@ -23,6 +23,7 @@ "gagging": { "material": "tape" }, - "adjustable": {} + "adjustable": {}, + "built_in_lock": {} } } \ No newline at end of file diff --git a/src/main/resources/assets/tiedup/tiedup_items/vine_gag.json b/src/main/resources/assets/tiedup/tiedup_items/vine_gag.json index 6076659..2ed6e5d 100644 --- a/src/main/resources/assets/tiedup/tiedup_items/vine_gag.json +++ b/src/main/resources/assets/tiedup/tiedup_items/vine_gag.json @@ -23,6 +23,7 @@ "gagging": { "material": "stuffed" }, - "adjustable": {} + "adjustable": {}, + "built_in_lock": {} } } \ No newline at end of file diff --git a/src/main/resources/assets/tiedup/tiedup_items/vine_seed.json b/src/main/resources/assets/tiedup/tiedup_items/vine_seed.json index e54de28..b99d817 100644 --- a/src/main/resources/assets/tiedup/tiedup_items/vine_seed.json +++ b/src/main/resources/assets/tiedup/tiedup_items/vine_seed.json @@ -25,6 +25,7 @@ "components": { "resistance": { "id": "vine" - } + }, + "built_in_lock": {} } } \ No newline at end of file diff --git a/src/main/resources/assets/tiedup/tiedup_items/web_bind.json b/src/main/resources/assets/tiedup/tiedup_items/web_bind.json index 6e28b72..3885cf6 100644 --- a/src/main/resources/assets/tiedup/tiedup_items/web_bind.json +++ b/src/main/resources/assets/tiedup/tiedup_items/web_bind.json @@ -25,6 +25,7 @@ "components": { "resistance": { "id": "web" - } + }, + "built_in_lock": {} } } \ No newline at end of file diff --git a/src/main/resources/assets/tiedup/tiedup_items/web_gag.json b/src/main/resources/assets/tiedup/tiedup_items/web_gag.json index ef3323a..065bc2e 100644 --- a/src/main/resources/assets/tiedup/tiedup_items/web_gag.json +++ b/src/main/resources/assets/tiedup/tiedup_items/web_gag.json @@ -23,6 +23,7 @@ "gagging": { "material": "stuffed" }, - "adjustable": {} + "adjustable": {}, + "built_in_lock": {} } } \ No newline at end of file diff --git a/src/main/resources/data/tiedup/tiedup_items/duct_tape.json b/src/main/resources/data/tiedup/tiedup_items/duct_tape.json index 2542047..33f1de4 100644 --- a/src/main/resources/data/tiedup/tiedup_items/duct_tape.json +++ b/src/main/resources/data/tiedup/tiedup_items/duct_tape.json @@ -25,6 +25,7 @@ "components": { "resistance": { "id": "tape" - } + }, + "built_in_lock": {} } } \ No newline at end of file diff --git a/src/main/resources/data/tiedup/tiedup_items/slime.json b/src/main/resources/data/tiedup/tiedup_items/slime.json index b2f02fa..295c3e0 100644 --- a/src/main/resources/data/tiedup/tiedup_items/slime.json +++ b/src/main/resources/data/tiedup/tiedup_items/slime.json @@ -25,6 +25,7 @@ "components": { "resistance": { "id": "slime" - } + }, + "built_in_lock": {} } } \ No newline at end of file diff --git a/src/main/resources/data/tiedup/tiedup_items/slime_gag.json b/src/main/resources/data/tiedup/tiedup_items/slime_gag.json index b33a8ae..daa3c31 100644 --- a/src/main/resources/data/tiedup/tiedup_items/slime_gag.json +++ b/src/main/resources/data/tiedup/tiedup_items/slime_gag.json @@ -23,6 +23,7 @@ "gagging": { "material": "stuffed" }, - "adjustable": {} + "adjustable": {}, + "built_in_lock": {} } } \ No newline at end of file diff --git a/src/main/resources/data/tiedup/tiedup_items/tape_gag.json b/src/main/resources/data/tiedup/tiedup_items/tape_gag.json index b475ad0..0932da9 100644 --- a/src/main/resources/data/tiedup/tiedup_items/tape_gag.json +++ b/src/main/resources/data/tiedup/tiedup_items/tape_gag.json @@ -23,6 +23,7 @@ "gagging": { "material": "tape" }, - "adjustable": {} + "adjustable": {}, + "built_in_lock": {} } } \ No newline at end of file diff --git a/src/main/resources/data/tiedup/tiedup_items/vine_gag.json b/src/main/resources/data/tiedup/tiedup_items/vine_gag.json index 6076659..2ed6e5d 100644 --- a/src/main/resources/data/tiedup/tiedup_items/vine_gag.json +++ b/src/main/resources/data/tiedup/tiedup_items/vine_gag.json @@ -23,6 +23,7 @@ "gagging": { "material": "stuffed" }, - "adjustable": {} + "adjustable": {}, + "built_in_lock": {} } } \ No newline at end of file diff --git a/src/main/resources/data/tiedup/tiedup_items/vine_seed.json b/src/main/resources/data/tiedup/tiedup_items/vine_seed.json index e54de28..b99d817 100644 --- a/src/main/resources/data/tiedup/tiedup_items/vine_seed.json +++ b/src/main/resources/data/tiedup/tiedup_items/vine_seed.json @@ -25,6 +25,7 @@ "components": { "resistance": { "id": "vine" - } + }, + "built_in_lock": {} } } \ No newline at end of file diff --git a/src/main/resources/data/tiedup/tiedup_items/web_bind.json b/src/main/resources/data/tiedup/tiedup_items/web_bind.json index 6e28b72..3885cf6 100644 --- a/src/main/resources/data/tiedup/tiedup_items/web_bind.json +++ b/src/main/resources/data/tiedup/tiedup_items/web_bind.json @@ -25,6 +25,7 @@ "components": { "resistance": { "id": "web" - } + }, + "built_in_lock": {} } } \ No newline at end of file diff --git a/src/main/resources/data/tiedup/tiedup_items/web_gag.json b/src/main/resources/data/tiedup/tiedup_items/web_gag.json index ef3323a..065bc2e 100644 --- a/src/main/resources/data/tiedup/tiedup_items/web_gag.json +++ b/src/main/resources/data/tiedup/tiedup_items/web_gag.json @@ -23,6 +23,7 @@ "gagging": { "material": "stuffed" }, - "adjustable": {} + "adjustable": {}, + "built_in_lock": {} } } \ No newline at end of file