27 Commits

Author SHA1 Message Date
NotEvil
4d128124e6 chore: gitignore docs/ to stop them from being tracked 2026-04-15 02:52:37 +02:00
NotEvil
3df979ceee fix(D-01/D): checkup cleanup — 5 issues resolved
1. LockableComponent: remove duplicate "Lockable" tooltip line
   (ILockable.appendLockTooltip already handles lock status display)
2. ILockable/IHasResistance Javadoc: update @link refs from deleted
   V1 classes to V2 AbstractV2BondageItem/DataDrivenBondageItem
3. SettingsAccessor Javadoc: remove stale BindVariant @link references
4. DataDrivenBondageItem: update NECK block comment (remove branch ref)
5. Delete empty bondage3d/gags/ directory
2026-04-15 02:52:27 +02:00
NotEvil
a572513640 chore(D-01/D): remove dead V1 handleSelfAccessory method 2026-04-15 02:12:05 +02:00
NotEvil
dfa7024e21 fix(D-01/D): blindfold ID mismatch + dispenser V2 registration (review)
KidnapperTheme: fix "mask_blindfold" → "blindfold_mask" across 8 themes.
The incorrect ID produced ghost items with no definition.

DispenserBehaviors: register GenericBondageDispenseBehavior.forAnyDataDriven()
for the V2 DATA_DRIVEN_ITEM singleton. Dispatches by region from the stack's
definition. V1 per-variant registrations were deleted but V2 replacement
was missing.
2026-04-15 02:10:25 +02:00
NotEvil
9302b6ccaf chore: remove docs from branch D tracking 2026-04-15 01:59:16 +02:00
NotEvil
099cd0d984 feat(D-01/D): V1 cleanup — delete 28 files, ~5400 lines removed
D1: ThreadLocal alert suppression moved from ItemCollar to CollarHelper.
    onCollarRemoved() logic (kidnapper alert) moved to CollarHelper.

D2+D3: Deleted 17 V1 item classes + 4 V1-only interfaces:
  ItemBind, ItemGag, ItemBlindfold, ItemCollar, ItemEarplugs, ItemMittens,
  ItemColor, ItemClassicCollar, ItemShockCollar, ItemShockCollarAuto,
  ItemGpsCollar, ItemChokeCollar, ItemHood, ItemMedicalGag,
  IBondageItem, IHasGaggingEffect, IHasBlindingEffect, IAdjustable

D4: KidnapperTheme/KidnapperItemSelector/DispenserBehaviors migrated
    from variant enums to string-based DataDrivenItemRegistry IDs.

D5: Deleted 11 variant enums + Generic* factories + ItemBallGag3D:
  BindVariant, GagVariant, BlindfoldVariant, EarplugsVariant, MittensVariant,
  GenericBind, GenericGag, GenericBlindfold, GenericEarplugs, GenericMittens

D6: ModItems cleaned — all V1 bondage registrations removed.
D7: ModCreativeTabs rewritten — iterates DataDrivenItemRegistry.
D8+D9: All V2 helpers cleaned (V1 fallbacks removed), orphan imports removed.

Zero V1 bondage code references remain (only Javadoc comments).
All bondage items are now data-driven via 47 JSON definitions.
2026-04-15 01:55:16 +02:00
fccb99ef9a Merge pull request 'feature/d01-branch-c-migration' (#8) from feature/d01-branch-c-migration into develop
Reviewed-on: #8
2026-04-14 22:57:05 +00:00
NotEvil
b04497b5a1 chore: remove docs from branch C (should not be tracked here) 2026-04-15 00:55:02 +02:00
NotEvil
3515c89f82 fix(D-01/C): missing sync + worldgen empty registry race (review)
StruggleSessionManager: add V2EquipmentHelper.sync(player) after bind
resistance update to prevent data loss on server restart during struggle

HangingCagePiece: add fallback ResourceLocation arrays for worldgen when
DataDrivenItemRegistry is empty (race with reload listener on initial
world creation). Registry-first with hardcoded fallbacks.
2026-04-15 00:26:07 +02:00
NotEvil
3d61c9e9e6 feat(D-01/C): consumer migration — 85 files migrated to V2 helpers
Phase 1 (state): PlayerBindState, PlayerCaptorManager, PlayerEquipment,
  PlayerDataRetrieval, PlayerLifecycle, PlayerShockCollar, StruggleAccessory
Phase 2 (client): AnimationTickHandler, NpcAnimationTickHandler, 5 render
  handlers, DamselModel, 3 client mixins, SelfBondageInputHandler,
  SlaveManagementScreen, ActionPanel, SlaveEntryWidget, ModKeybindings
Phase 3 (entities): 28 entity/AI files migrated to CollarHelper,
  BindModeHelper, PoseTypeHelper, createStack()
Phase 4 (network): PacketSlaveAction, PacketMasterEquip,
  PacketAssignCellToCollar, PacketNpcCommand, PacketFurnitureForcemount
Phase 5 (events): RestraintTaskTickHandler, PetPlayRestrictionHandler,
  PlayerEnslavementHandler, ChatEventHandler, LaborAttackPunishmentHandler
Phase 6 (commands): BondageSubCommand, CollarCommand, NPCCommand,
  KidnapSetCommand
Phase 7 (compat): MCAKidnappedAdapter, MCA mixins
Phase 8 (misc): GagTalkManager, PetRequestManager, HangingCagePiece,
  BondageItemBlockEntity, TrappedChestBlockEntity, DispenserBehaviors,
  BondageItemLoaderUtility, RestraintApplicator, StruggleSessionManager,
  MovementStyleResolver, CampLifecycleManager

Some files retain dual V1/V2 checks (instanceof V1 || V2Helper) for
coexistence — V1-only branches removed in Branch D.
2026-04-15 00:16:50 +02:00
52d1044e9a Merge pull request 'feature/d01-branch-b-definitions' (#7) from feature/d01-branch-b-definitions into develop
Reviewed-on: #7
2026-04-14 20:23:53 +00:00
NotEvil
530b86a9a7 fix(D-01/B): hood missing MOUTH block + organic items lockable:false
- Hood: add MOUTH to blocked_regions — prevents double gag stacking
- 8 organic items (slime, vine, web, tape): set lockable:false at top
  level for consistency with can_attach_padlock:false
2026-04-14 17:59:27 +02:00
NotEvil
258223bf68 feat(D-01/B): 47 data-driven item definitions + cleanup test files
16 binds: ropes, armbinder, dogbinder, chain, ribbon, slime, vine_seed,
  web_bind, shibari, leather_straps, medical_straps, beam_cuffs,
  duct_tape, straitjacket, wrap, latex_sack

19 gags: cloth_gag, ropes_gag, cleave_gag, ribbon_gag, ball_gag,
  ball_gag_strap, tape_gag, wrap_gag, slime_gag, vine_gag, web_gag,
  panel_gag, beam_panel_gag, chain_panel_gag, latex_gag, tube_gag,
  bite_gag, sponge_gag, baguette_gag

2 blindfolds, 1 earplugs, 1 mittens
5 collars (classic, shock, shock_auto, gps, choke) with ownership component
3 combos (hood, medical_gag, ball_gag_3d)

All items config-driven via ResistanceComponent (id) and GaggingComponent
(material). Organic items have can_attach_padlock: false.

Removed 4 test files (test_component_gag, test_handcuffs, test_leg_cuffs).
2026-04-14 17:52:19 +02:00
NotEvil
679d7033f9 feat(D-01/B): add can_attach_padlock field to data-driven items (B0)
- DataDrivenItemDefinition: add canAttachPadlock boolean (default true)
- DataDrivenItemParser: parse "can_attach_padlock" from JSON
- DataDrivenBondageItem: add static canAttachPadlockTo(stack) method
- AnvilEventHandler: check V2 definition before allowing padlock attach
2026-04-14 17:47:32 +02:00
8bfd97ba57 Merge pull request 'feature/d01-branch-a-bridge' (#6) from feature/d01-branch-a-bridge into develop
Reviewed-on: #6
2026-04-14 15:19:20 +00:00
NotEvil
df56ebb6bc fix(D-01/A): adversarial review fixes — 4 logic bugs
1. NECK region explicitly blocked in interactLivingEntity() — prevents
   V2 collars equipping without ownership setup (latent, no JSONs yet)

2. Swap rollback safety: if re-equip fails after swap failure, drop the
   old bind as item entity instead of losing it silently

3. GaggingComponent: cache GagMaterial at construction time — eliminates
   valueOf() log spam on every chat message with misconfigured material

4. Dual-bind prevention: check both V1 isTiedUp() AND V2 region occupied
   in TyingInteractionHelper and PacketSelfBondage to prevent equipping
   V2 bind on top of V1 bind
2026-04-14 16:48:50 +02:00
NotEvil
b97bdf367e fix(D-01/A): V2 bind/collar resistance completely broken (CRITICAL)
PlayerEquipment.getCurrentBindResistance/setCurrentBindResistance and
getCurrentCollarResistance/setCurrentCollarResistance all checked
instanceof ItemBind/ItemCollar — V2 DataDrivenBondageItem silently
returned 0, making V2 items escapable in 1 struggle roll.

Fix: use instanceof IHasResistance which both V1 and V2 implement.

Also fix StruggleCollar.tighten() to read ResistanceComponent directly
for V2 collars instead of IHasResistance.getBaseResistance(entity)
which triggers the singleton MAX-scan across all equipped items.

Note: isItemLocked() dead code in StruggleState is a PRE-EXISTING bug
(x10 locked penalty never applied) — tracked for separate fix.
2026-04-14 16:44:59 +02:00
NotEvil
eb7f06bfc8 fix(D-01/A): 3 review bugs + null guards (BUG-001, BUG-002, BUG-003, RISK-003)
BUG-001: TyingInteractionHelper swap now checks V2EquipResult — on failure,
rolls back by re-equipping the old bind instead of leaving target untied

BUG-002: OwnershipComponent.onUnequipped no longer double-calls
unregisterWearer — onCollarRemoved already handles it. Suppressed-alert
path calls unregister directly since onCollarRemoved is skipped.

BUG-003: PacketSelfBondage handleV2SelfBind now clears completed tying
task from PlayerBindState to prevent blocking future tying interactions

RISK-003: StruggleCollar get/setResistanceState null-guard on player
2026-04-14 16:38:09 +02:00
NotEvil
5c4e4c2352 fix(D-01/A): double item consumption + unchecked cast in TyingInteractionHelper
QA-001: add instanceof V2TyingPlayerTask guard before cast to prevent
ClassCastException when a V1 TyingPlayerTask was still active

QA-002: remove stack.shrink(1) after tying completion — V2TyingPlayerTask
.onComplete() already consumes the held item via heldStack.shrink(1)
2026-04-14 16:35:05 +02:00
NotEvil
eee4825aba feat(D-01/A): NPC speed reduction for V2 items (A12)
- onEquipped: apply RestraintEffectUtils speed reduction for non-Player
  entities with ARMS region and legs bound. Full immobilization for
  WRAP/LATEX_SACK pose types.
- onUnequipped: remove speed reduction for non-Player entities
- Players use MovementStyleManager (V2 tick-based), not this legacy path
2026-04-14 16:07:41 +02:00
NotEvil
b359c6be35 feat(D-01/A): self-bondage region routing (A11)
- handleV2SelfBondage: split into region-based routing
  - NECK → blocked (cannot self-collar)
  - ARMS → handleV2SelfBind (tying task with progress bar)
  - Other → handleV2SelfAccessory (instant equip)
- handleV2SelfAccessory: arms-bound check via BindModeHelper,
  locked check for swap, V2EquipmentHelper for conflict resolution
2026-04-14 16:06:01 +02:00
NotEvil
19cc69985d feat(D-01/A): V2-aware struggle system (A9)
StruggleBinds:
- canStruggle(): instanceof ItemBind → BindModeHelper.isBindItem()
- isItemLocked(): instanceof ItemBind → instanceof ILockable (fixes R4)
- onAttempt(): instanceof ItemShockCollar → CollarHelper.canShock() (fixes R5)
- tighten(): reads ResistanceComponent directly for V2, avoids MAX scan bug

StruggleCollar:
- getResistanceState/setResistanceState: instanceof ItemCollar → IHasResistance
- canStruggle(): instanceof ItemCollar → CollarHelper.isCollar() + ILockable
- onAttempt(): shock check via CollarHelper.canShock()
- successAction(): unlock via ILockable
- tighten(): resistance via IHasResistance

All V1 items continue working through the same interfaces they already implement.
2026-04-14 15:47:20 +02:00
NotEvil
737a4fd59b feat(D-01/A): interaction routing + TyingInteractionHelper (A8)
- DataDrivenBondageItem.use(): shift+click cycles bind mode for ARMS items
- DataDrivenBondageItem.interactLivingEntity(): region-based routing
  - ARMS → TyingInteractionHelper (tying task with progress bar)
  - NECK → deferred to Branch C (no V2 collar JSONs yet)
  - Other regions → instant equip via parent AbstractV2BondageItem
- TyingInteractionHelper: extracted tying flow using V2TyingPlayerTask
  - Distance/LoS validation, swap if already tied, task lifecycle
2026-04-14 15:35:31 +02:00
NotEvil
b79225d684 feat(D-01/A): OwnershipComponent lifecycle hooks (A7)
- onEquipped: register collar owners in CollarRegistry (server-side only)
- onUnequipped: alert kidnappers + unregister from CollarRegistry
- Guards: client-side check, ServerLevel cast, empty owners skip, try-catch
- appendTooltip: nickname, owner count, shock/GPS/choke capabilities
- Delegates alert suppression to ItemCollar.isRemovalAlertSuppressed()
2026-04-14 15:29:31 +02:00
NotEvil
751bad418d feat(D-01/A): poseType, helpers, OWNERSHIP ComponentType (A4, A5, A6)
- DataDrivenItemDefinition: add poseType field, parsed from JSON "pose_type"
- PoseTypeHelper: resolves PoseType from V2 definition or V1 ItemBind fallback
- BindModeHelper: static bind mode NBT utilities (isBindItem, hasArmsBound,
  hasLegsBound, cycleBindModeId) — works for V1 and V2 items
- CollarHelper: complete static utility class for collar operations with
  dual-path V2/V1 dispatch (ownership, features, shock, GPS, choke, alert)
- ComponentType: add OWNERSHIP enum value
- OwnershipComponent: stub class (lifecycle hooks added in next commit)
2026-04-14 15:23:08 +02:00
NotEvil
b81d3eed95 feat(D-01/A): config-driven components + tooltip hook (A1, A2, A3)
- ResistanceComponent: resistanceId delegates to SettingsAccessor at runtime,
  fallback to hardcoded base for backward compat
- GaggingComponent: material field delegates to GagMaterial enum from ModConfig,
  explicit comprehension/range overrides take priority
- IItemComponent: add default appendTooltip() method
- ComponentHolder: iterate components for tooltip contribution
- 6 components implement appendTooltip (lockable, resistance, gagging, shock,
  gps, choking)
- DataDrivenBondageItem: call holder.appendTooltip() in appendHoverText()
2026-04-14 15:05:48 +02:00
fa4c332a10 Merge pull request 'feature/d01-component-system' (#5) from feature/d01-component-system into develop
Reviewed-on: #5
2026-04-14 00:54:16 +00:00
258 changed files with 5082 additions and 6369 deletions

1
.gitignore vendored
View File

@@ -45,3 +45,4 @@ build_output.log
.DS_Store
Thumbs.db
desktop.ini
docs/

View File

@@ -1,11 +1,14 @@
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;
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 javax.annotation.Nullable;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
@@ -191,55 +194,43 @@ public abstract class BondageItemBlockEntity
@Override
public void readBondageData(CompoundTag tag) {
// Read bind with type validation
// 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
) {
if (!bindStack.isEmpty() && BindModeHelper.isBindItem(bindStack)) {
this.bind = bindStack;
}
}
// Read gag with type validation
// 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) {
if (!gagStack.isEmpty()
&& DataDrivenBondageItem.getComponent(gagStack, ComponentType.GAGGING, GaggingComponent.class) != null) {
this.gag = gagStack;
}
}
// Read blindfold with type validation
// 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
) {
ItemStack blindfoldStack = ItemStack.of(tag.getCompound("blindfold"));
if (!blindfoldStack.isEmpty() && isDataDrivenForRegion(blindfoldStack, BodyRegionV2.EYES)) {
this.blindfold = blindfoldStack;
}
}
// Read earplugs with type validation
// 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
) {
if (!earplugsStack.isEmpty() && isDataDrivenForRegion(earplugsStack, BodyRegionV2.EARS)) {
this.earplugs = earplugsStack;
}
}
// Read collar with type validation
// 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
) {
if (!collarStack.isEmpty() && CollarHelper.isCollar(collarStack)) {
this.collar = collarStack;
}
}
@@ -279,6 +270,14 @@ public abstract class BondageItemBlockEntity
return tag;
}
// V2 HELPERS
/** Check if a stack is a data-driven item occupying the given body region. */
private static boolean isDataDrivenForRegion(ItemStack stack, BodyRegionV2 region) {
DataDrivenItemDefinition def = DataDrivenItemRegistry.get(stack);
return def != null && def.occupiedRegions().contains(region);
}
// NETWORK SYNC
/**

View File

@@ -1,7 +1,14 @@
package com.tiedup.remake.blocks.entity;
import com.tiedup.remake.items.base.*;
import com.tiedup.remake.items.clothes.GenericClothes;
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 javax.annotation.Nullable;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
@@ -45,7 +52,7 @@ public class TrappedChestBlockEntity
@Override
public void setBind(ItemStack stack) {
if (stack.isEmpty() || stack.getItem() instanceof ItemBind) {
if (stack.isEmpty() || BindModeHelper.isBindItem(stack)) {
this.bind = stack;
setChangedAndSync();
}
@@ -58,7 +65,8 @@ 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();
}
@@ -71,7 +79,7 @@ public class TrappedChestBlockEntity
@Override
public void setBlindfold(ItemStack stack) {
if (stack.isEmpty() || stack.getItem() instanceof ItemBlindfold) {
if (stack.isEmpty() || isDataDrivenForRegion(stack, BodyRegionV2.EYES)) {
this.blindfold = stack;
setChangedAndSync();
}
@@ -84,7 +92,7 @@ public class TrappedChestBlockEntity
@Override
public void setEarplugs(ItemStack stack) {
if (stack.isEmpty() || stack.getItem() instanceof ItemEarplugs) {
if (stack.isEmpty() || isDataDrivenForRegion(stack, BodyRegionV2.EARS)) {
this.earplugs = stack;
setChangedAndSync();
}
@@ -97,7 +105,7 @@ public class TrappedChestBlockEntity
@Override
public void setCollar(ItemStack stack) {
if (stack.isEmpty() || stack.getItem() instanceof ItemCollar) {
if (stack.isEmpty() || CollarHelper.isCollar(stack)) {
this.collar = stack;
setChangedAndSync();
}
@@ -183,6 +191,14 @@ public class TrappedChestBlockEntity
writeBondageData(tag);
}
// V2 HELPERS
/** Check if a stack is a data-driven item occupying the given body region. */
private static boolean isDataDrivenForRegion(ItemStack stack, BodyRegionV2 region) {
DataDrivenItemDefinition def = DataDrivenItemRegistry.get(stack);
return def != null && def.occupiedRegions().contains(region);
}
// NETWORK SYNC
/**

View File

@@ -2,8 +2,8 @@ package com.tiedup.remake.cells;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.base.ILockable;
import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.prison.PrisonerManager;
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;
@@ -231,7 +231,7 @@ public final class CampLifecycleManager {
}
// Suppress collar removal alerts - this is a legitimate release (camp death)
ItemCollar.runWithSuppressedAlert(() -> {
CollarHelper.runWithSuppressedAlert(() -> {
// Unlock collar if owned by the dead camp/trader
unlockCollarIfOwnedBy(prisoner, state, traderUUID);
@@ -285,8 +285,8 @@ public final class CampLifecycleManager {
return;
}
if (collar.getItem() instanceof ItemCollar collarItem) {
List<UUID> owners = collarItem.getOwners(collar);
if (CollarHelper.isCollar(collar)) {
List<UUID> owners = CollarHelper.getOwners(collar);
// If the dead trader/camp is an owner, unlock the collar
if (owners.contains(ownerUUID)) {

View File

@@ -3,8 +3,8 @@ package com.tiedup.remake.client;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.GenericBind;
import com.tiedup.remake.items.base.BindVariant;
import com.tiedup.remake.items.base.PoseType;
import com.tiedup.remake.v2.bondage.PoseTypeHelper;
import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
@@ -141,12 +141,7 @@ public class FirstPersonMittensRenderer {
net.minecraft.world.item.ItemStack bindStack =
V2EquipmentHelper.getInRegion(player, BodyRegionV2.ARMS);
if (bindStack.isEmpty()) return false;
if (bindStack.getItem() instanceof GenericBind bind) {
BindVariant variant = bind.getVariant();
return (
variant == BindVariant.WRAP || variant == BindVariant.LATEX_SACK
);
}
return false;
PoseType poseType = PoseTypeHelper.getPoseType(bindStack);
return poseType == PoseType.WRAP || poseType == PoseType.LATEX_SACK;
}
}

View File

@@ -6,7 +6,7 @@ import com.tiedup.remake.client.gui.screens.UnifiedBondageScreen;
import com.tiedup.remake.core.ModConfig;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.base.ILockable;
import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.network.ModNetwork;
import com.tiedup.remake.network.action.PacketForceSeatModifier;
import com.tiedup.remake.network.action.PacketStruggle;
@@ -428,11 +428,8 @@ public class ModKeybindings {
target,
BodyRegionV2.NECK
);
if (
!collarStack.isEmpty() &&
collarStack.getItem() instanceof ItemCollar collar
) {
return collar.isOwner(collarStack, player);
if (!collarStack.isEmpty() && CollarHelper.isCollar(collarStack)) {
return CollarHelper.isOwner(collarStack, player);
}
return false;
}

View File

@@ -1,8 +1,8 @@
package com.tiedup.remake.client.animation.render;
import com.tiedup.remake.core.TiedUpMod;
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.state.HumanChairHelper;
import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2;
@@ -93,14 +93,11 @@ public class DogPoseRenderHandler {
}
ItemStack bindForPose = state.getEquipment(BodyRegionV2.ARMS);
if (
bindForPose.isEmpty() ||
!(bindForPose.getItem() instanceof ItemBind itemBind)
) {
if (bindForPose.isEmpty()) {
return;
}
PoseType bindPoseType = itemBind.getPoseType();
PoseType bindPoseType = PoseTypeHelper.getPoseType(bindForPose);
// Check for humanChairMode NBT override
bindPoseType = HumanChairHelper.resolveEffectivePose(
bindPoseType,

View File

@@ -2,8 +2,8 @@ package com.tiedup.remake.client.animation.render;
import com.tiedup.remake.client.state.PetBedClientState;
import com.tiedup.remake.core.TiedUpMod;
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.state.HumanChairHelper;
import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2;
@@ -83,11 +83,9 @@ public class PetBedRenderHandler {
PlayerBindState state = PlayerBindState.getInstance(player);
if (state == null) return false;
ItemStack bind = state.getEquipment(BodyRegionV2.ARMS);
if (
bind.isEmpty() || !(bind.getItem() instanceof ItemBind itemBind)
) return false;
if (bind.isEmpty()) return false;
PoseType pose = HumanChairHelper.resolveEffectivePose(
itemBind.getPoseType(),
PoseTypeHelper.getPoseType(bind),
bind
);
return pose == PoseType.DOG || pose == PoseType.HUMAN_CHAIR;

View File

@@ -2,8 +2,8 @@ package com.tiedup.remake.client.animation.render;
import com.tiedup.remake.client.renderer.layers.ClothesRenderHelper;
import com.tiedup.remake.core.TiedUpMod;
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.items.clothes.ClothesProperties;
import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2;
@@ -71,10 +71,8 @@ public class PlayerArmHideEventHandler {
// === HIDE ARMS (wrap/latex_sack poses) ===
if (state.hasArmsBound()) {
ItemStack bind = state.getEquipment(BodyRegionV2.ARMS);
if (
!bind.isEmpty() && bind.getItem() instanceof ItemBind itemBind
) {
PoseType poseType = itemBind.getPoseType();
if (!bind.isEmpty()) {
PoseType poseType = PoseTypeHelper.getPoseType(bind);
// Only hide arms for wrap/sack poses (arms are covered by the item)
if (

View File

@@ -14,8 +14,9 @@ import com.tiedup.remake.client.gltf.GltfAnimationApplier;
import com.tiedup.remake.client.state.ClothesClientCache;
import com.tiedup.remake.client.state.MovementStyleClientState;
import com.tiedup.remake.client.state.PetBedClientState;
import com.tiedup.remake.items.base.ItemBind;
import com.tiedup.remake.items.base.PoseType;
import com.tiedup.remake.v2.bondage.BindModeHelper;
import com.tiedup.remake.v2.bondage.PoseTypeHelper;
import com.tiedup.remake.state.HumanChairHelper;
import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2;
@@ -249,14 +250,10 @@ public class AnimationTickHandler {
PlayerBindState state
) {
ItemStack bind = state.getEquipment(BodyRegionV2.ARMS);
PoseType poseType = PoseType.STANDARD;
PoseType poseType = PoseTypeHelper.getPoseType(bind);
if (bind.getItem() instanceof ItemBind itemBind) {
poseType = itemBind.getPoseType();
// Human chair mode: override DOG pose to HUMAN_CHAIR (straight limbs)
poseType = HumanChairHelper.resolveEffectivePose(poseType, bind);
}
// Human chair mode: override DOG pose to HUMAN_CHAIR (straight limbs)
poseType = HumanChairHelper.resolveEffectivePose(poseType, bind);
// Derive bound state from V2 regions (works client-side, synced via capability)
boolean armsBound = V2EquipmentHelper.isRegionOccupied(
@@ -268,10 +265,10 @@ public class AnimationTickHandler {
BodyRegionV2.LEGS
);
// V1 fallback: if no V2 regions are set but player is tied, derive from ItemBind NBT
if (!armsBound && !legsBound && bind.getItem() instanceof ItemBind) {
armsBound = ItemBind.hasArmsBound(bind);
legsBound = ItemBind.hasLegsBound(bind);
// V1 fallback: if no V2 regions are set but player is tied, derive from bind mode NBT
if (!armsBound && !legsBound && BindModeHelper.isBindItem(bind)) {
armsBound = BindModeHelper.hasArmsBound(bind);
legsBound = BindModeHelper.hasLegsBound(bind);
}
boolean isStruggling = state.isStruggling();

View File

@@ -9,8 +9,9 @@ import com.tiedup.remake.client.gltf.GltfAnimationApplier;
import com.tiedup.remake.entities.AbstractTiedUpNpc;
import com.tiedup.remake.entities.EntityMaster;
import com.tiedup.remake.entities.ai.master.MasterState;
import com.tiedup.remake.items.base.ItemBind;
import com.tiedup.remake.items.base.PoseType;
import com.tiedup.remake.v2.bondage.BindModeHelper;
import com.tiedup.remake.v2.bondage.PoseTypeHelper;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.IV2BondageEquipment;
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
@@ -161,13 +162,8 @@ public class NpcAnimationTickHandler {
net.minecraft.world.item.ItemStack bind = entity.getEquipment(
BodyRegionV2.ARMS
);
PoseType poseType = PoseType.STANDARD;
boolean hasBind = false;
if (bind.getItem() instanceof ItemBind itemBind) {
poseType = itemBind.getPoseType();
hasBind = true;
}
PoseType poseType = PoseTypeHelper.getPoseType(bind);
boolean hasBind = BindModeHelper.isBindItem(bind);
// Derive bound state from V2 regions (AbstractTiedUpNpc implements IV2EquipmentHolder)
boolean armsBound = V2EquipmentHelper.isRegionOccupied(
@@ -179,10 +175,10 @@ public class NpcAnimationTickHandler {
BodyRegionV2.LEGS
);
// V1 fallback: if no V2 regions set but NPC has a bind, derive from ItemBind NBT
if (!armsBound && !legsBound && bind.getItem() instanceof ItemBind) {
armsBound = ItemBind.hasArmsBound(bind);
legsBound = ItemBind.hasLegsBound(bind);
// V1 fallback: if no V2 regions set but NPC has a bind, derive from bind mode NBT
if (!armsBound && !legsBound && BindModeHelper.isBindItem(bind)) {
armsBound = BindModeHelper.hasArmsBound(bind);
legsBound = BindModeHelper.hasLegsBound(bind);
}
boolean isStruggling = entity.isStruggling();

View File

@@ -1,8 +1,8 @@
package com.tiedup.remake.client.events;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.ModItems;
import com.tiedup.remake.items.base.BindVariant;
import com.tiedup.remake.items.base.PoseType;
import com.tiedup.remake.v2.bondage.PoseTypeHelper;
import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.util.KidnappedHelper;
import com.tiedup.remake.v2.BodyRegionV2;
@@ -138,7 +138,7 @@ public class LeashProxyClientHandler {
ItemStack bind = state.getEquipment(BodyRegionV2.ARMS);
if (
!bind.isEmpty() &&
bind.getItem() == ModItems.getBind(BindVariant.DOGBINDER)
PoseTypeHelper.getPoseType(bind) == PoseType.DOG
) {
return DOGWALK_Y_OFFSET;
}

View File

@@ -1,13 +1,19 @@
package com.tiedup.remake.client.events;
import com.tiedup.remake.items.base.*;
import com.tiedup.remake.network.ModNetwork;
import com.tiedup.remake.network.selfbondage.PacketSelfBondage;
import com.tiedup.remake.v2.bondage.BindModeHelper;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.v2.bondage.IV2BondageItem;
import com.tiedup.remake.v2.bondage.component.BlindingComponent;
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 net.minecraft.client.Minecraft;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
@@ -70,7 +76,7 @@ public class SelfBondageInputHandler {
if (!event.getLevel().isClientSide()) return;
ItemStack stack = event.getItemStack();
if (isSelfBondageItem(stack.getItem())) {
if (isSelfBondageItem(stack)) {
event.setCanceled(true);
startSelfBondage();
}
@@ -87,11 +93,11 @@ public class SelfBondageInputHandler {
InteractionHand hand = InteractionHand.MAIN_HAND;
ItemStack stack = player.getMainHandItem();
if (!isSelfBondageItem(stack.getItem())) {
if (!isSelfBondageItem(stack)) {
stack = player.getOffhandItem();
hand = InteractionHand.OFF_HAND;
if (!isSelfBondageItem(stack.getItem())) {
if (!isSelfBondageItem(stack)) {
return; // No bondage item in either hand
}
}
@@ -130,7 +136,7 @@ public class SelfBondageInputHandler {
// Check if still holding bondage item in the active hand
ItemStack stack = player.getItemInHand(activeHand);
if (!isSelfBondageItem(stack.getItem())) {
if (!isSelfBondageItem(stack)) {
stopSelfBondage();
return;
}
@@ -153,27 +159,31 @@ public class SelfBondageInputHandler {
}
/**
* Check if an item supports self-bondage.
* Check if a stack supports self-bondage.
* Collar is explicitly excluded.
*/
private static boolean isSelfBondageItem(Item item) {
// Collar cannot be self-equipped (V1 collar guard)
if (item instanceof ItemCollar) {
private static boolean isSelfBondageItem(ItemStack stack) {
if (stack.isEmpty()) return false;
// Collar cannot be self-equipped (V2 ownership component)
if (CollarHelper.isCollar(stack)) {
return false;
}
// V2 bondage items support self-bondage (left-click hold with tying duration)
if (item instanceof IV2BondageItem) {
if (stack.getItem() instanceof IV2BondageItem) {
return true;
}
// V1 bondage items (legacy)
return (
item instanceof ItemBind ||
item instanceof ItemGag ||
item instanceof ItemBlindfold ||
item instanceof ItemMittens ||
item instanceof ItemEarplugs
);
// V2 data-driven items: check if it occupies any bondage region
DataDrivenItemDefinition def = DataDrivenItemRegistry.get(stack);
if (def != null) {
return true;
}
// V1 fallback: bind items
return BindModeHelper.isBindItem(stack)
|| DataDrivenBondageItem.getComponent(stack, ComponentType.GAGGING, GaggingComponent.class) != null
|| DataDrivenBondageItem.getComponent(stack, ComponentType.BLINDING, BlindingComponent.class) != null;
}
}

View File

@@ -3,7 +3,7 @@ package com.tiedup.remake.client.gui.screens;
import com.tiedup.remake.client.gui.util.GuiColors;
import com.tiedup.remake.client.gui.util.GuiLayoutConstants;
import com.tiedup.remake.client.gui.widgets.SlaveEntryWidget;
import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.network.ModNetwork;
import com.tiedup.remake.network.slave.PacketSlaveAction;
import com.tiedup.remake.state.IBondageState;
@@ -145,8 +145,8 @@ public class SlaveManagementScreen extends BaseScreen {
ItemStack collarStack = kidnapped.getEquipment(
BodyRegionV2.NECK
);
if (collarStack.getItem() instanceof ItemCollar collar) {
if (collar.isOwner(collarStack, player)) {
if (CollarHelper.isCollar(collarStack)) {
if (CollarHelper.isOwner(collarStack, player)) {
addSlaveEntry(kidnapped);
addedUUIDs.add(entity.getUUID());
}

View File

@@ -7,7 +7,7 @@ import com.tiedup.remake.items.ItemLockpick;
import com.tiedup.remake.items.ModItems;
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.v2.bondage.CollarHelper;
import com.tiedup.remake.network.ModNetwork;
import com.tiedup.remake.network.action.PacketSetKnifeCutTarget;
import com.tiedup.remake.network.minigame.PacketLockpickMiniGameStart;
@@ -474,10 +474,10 @@ public class ActionPanel extends AbstractWidget {
// Bondage Service toggle (NECK collar only, prison configured)
if (
selectedRegion == BodyRegionV2.NECK &&
selectedItem.getItem() instanceof ItemCollar collar
CollarHelper.isCollar(selectedItem)
) {
if (collar.hasCellAssigned(selectedItem)) {
boolean svcEnabled = collar.isBondageServiceEnabled(
if (CollarHelper.hasCellAssigned(selectedItem)) {
boolean svcEnabled = CollarHelper.isBondageServiceEnabled(
selectedItem
);
String svcKey = svcEnabled

View File

@@ -4,8 +4,7 @@ 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.items.base.ItemCollar;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.v2.BodyRegionV2;
import java.util.ArrayList;
@@ -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
);
@@ -560,33 +558,35 @@ public class SlaveEntryWidget
private boolean hasShockCollar() {
if (!slave.hasCollar()) return false;
ItemStack collar = slave.getEquipment(BodyRegionV2.NECK);
return (
collar.getItem() instanceof ItemCollar itemCollar &&
itemCollar.canShock()
);
return CollarHelper.canShock(collar);
}
private boolean hasGPSCollar() {
if (!slave.hasCollar()) return false;
ItemStack collar = slave.getEquipment(BodyRegionV2.NECK);
return (
collar.getItem() instanceof ItemCollar itemCollar &&
itemCollar.hasGPS()
);
return CollarHelper.hasGPS(collar);
}
private boolean isInAnySafeZone(
ItemGpsCollar gps,
ItemStack collarStack,
LivingEntity entity
) {
if (!gps.isActive(collarStack)) return true;
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;
}
}

View File

@@ -9,8 +9,9 @@ import com.tiedup.remake.entities.AbstractTiedUpNpc;
import com.tiedup.remake.entities.EntityKidnapperArcher;
import com.tiedup.remake.entities.EntityMaster;
import com.tiedup.remake.entities.ai.master.MasterState;
import com.tiedup.remake.items.base.ItemBind;
import com.tiedup.remake.items.base.PoseType;
import com.tiedup.remake.v2.bondage.BindModeHelper;
import com.tiedup.remake.v2.bondage.PoseTypeHelper;
import com.tiedup.remake.items.clothes.GenericClothes;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
@@ -230,11 +231,7 @@ public class DamselModel
if (inPose) {
ItemStack bind = entity.getEquipment(BodyRegionV2.ARMS);
PoseType poseType = PoseType.STANDARD;
if (bind.getItem() instanceof ItemBind itemBind) {
poseType = itemBind.getPoseType();
}
PoseType poseType = PoseTypeHelper.getPoseType(bind);
// Hide arms for wrap/latex_sack poses
if (poseType == PoseType.WRAP || poseType == PoseType.LATEX_SACK) {
@@ -252,9 +249,7 @@ public class DamselModel
PoseType currentPoseType = PoseType.STANDARD;
if (inPose) {
ItemStack bindForPoseType = entity.getEquipment(BodyRegionV2.ARMS);
if (bindForPoseType.getItem() instanceof ItemBind itemBindForType) {
currentPoseType = itemBindForType.getPoseType();
}
currentPoseType = PoseTypeHelper.getPoseType(bindForPoseType);
}
// Check if this is a Master in human chair mode (head should look around freely)
@@ -306,11 +301,7 @@ public class DamselModel
// Animation not yet active (1-frame delay) - apply static pose as fallback
// This ensures immediate visual feedback when bind is applied
ItemStack bind = entity.getEquipment(BodyRegionV2.ARMS);
PoseType fallbackPoseType = PoseType.STANDARD;
if (bind.getItem() instanceof ItemBind itemBind) {
fallbackPoseType = itemBind.getPoseType();
}
PoseType fallbackPoseType = PoseTypeHelper.getPoseType(bind);
// Derive bound state from V2 regions (AbstractTiedUpNpc implements IV2EquipmentHolder)
boolean armsBound = V2EquipmentHelper.isRegionOccupied(
@@ -323,10 +314,10 @@ public class DamselModel
);
if (
!armsBound && !legsBound && bind.getItem() instanceof ItemBind
!armsBound && !legsBound && BindModeHelper.isBindItem(bind)
) {
armsBound = ItemBind.hasArmsBound(bind);
legsBound = ItemBind.hasLegsBound(bind);
armsBound = BindModeHelper.hasArmsBound(bind);
legsBound = BindModeHelper.hasLegsBound(bind);
}
// Apply static pose directly to model parts

View File

@@ -7,8 +7,8 @@ import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.tiedup.remake.cells.CellDataV2;
import com.tiedup.remake.cells.CellRegistryV2;
import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.util.teleport.Position;
import com.tiedup.remake.util.teleport.TeleportHelper;
import com.tiedup.remake.v2.BodyRegionV2;
@@ -160,8 +160,8 @@ public class CollarCommand {
}
if (source.getEntity() instanceof ServerPlayer executor) {
if (collar.getItem() instanceof ItemCollar collarItem) {
collarItem.addOwner(collar, executor);
if (CollarHelper.isCollar(collar)) {
CollarHelper.addOwner(collar, executor);
source.sendSuccess(
() ->
Component.literal(
@@ -195,8 +195,8 @@ public class CollarCommand {
}
if (source.getEntity() instanceof ServerPlayer executor) {
if (collar.getItem() instanceof ItemCollar collarItem) {
collarItem.removeOwner(collar, executor.getUUID());
if (CollarHelper.isCollar(collar)) {
CollarHelper.removeOwner(collar, executor.getUUID());
source.sendSuccess(
() ->
Component.literal(
@@ -230,8 +230,8 @@ public class CollarCommand {
return 0;
}
if (collar.getItem() instanceof ItemCollar collarItem) {
collarItem.setNickname(collar, name);
if (CollarHelper.isCollar(collar)) {
CollarHelper.setNickname(collar, name);
source.sendSuccess(
() ->
Component.literal(
@@ -261,8 +261,8 @@ public class CollarCommand {
return 0;
}
if (collar.getItem() instanceof ItemCollar collarItem) {
collarItem.addOwner(collar, owner);
if (CollarHelper.isCollar(collar)) {
CollarHelper.addOwner(collar, owner);
source.sendSuccess(
() ->
Component.literal(
@@ -296,8 +296,8 @@ public class CollarCommand {
return 0;
}
if (collar.getItem() instanceof ItemCollar collarItem) {
collarItem.removeOwner(collar, owner.getUUID());
if (CollarHelper.isCollar(collar)) {
CollarHelper.removeOwner(collar, owner.getUUID());
source.sendSuccess(
() ->
Component.literal(
@@ -348,8 +348,8 @@ public class CollarCommand {
return 0;
}
if (collar.getItem() instanceof ItemCollar collarItem) {
collarItem.setCellId(collar, cell.getId());
if (CollarHelper.isCollar(collar)) {
CollarHelper.setCellId(collar, cell.getId());
source.sendSuccess(
() ->
Component.literal(
@@ -388,8 +388,8 @@ public class CollarCommand {
return 0;
}
if (collar.getItem() instanceof ItemCollar collarItem) {
if (!collarItem.hasCellAssigned(collar)) {
if (CollarHelper.isCollar(collar)) {
if (!CollarHelper.hasCellAssigned(collar)) {
source.sendFailure(
Component.literal("No cell assigned to collar")
);
@@ -397,7 +397,7 @@ public class CollarCommand {
}
// Get cell position and teleport
java.util.UUID cellId = collarItem.getCellId(collar);
java.util.UUID cellId = CollarHelper.getCellId(collar);
ServerLevel serverLevel = source.getLevel();
CellDataV2 cell = CellRegistryV2.get(serverLevel).getCell(cellId);
@@ -449,7 +449,7 @@ public class CollarCommand {
return 0;
}
if (collar.getItem() instanceof ItemCollar collarItem) {
if (CollarHelper.isCollar(collar)) {
source.sendSuccess(
() ->
Component.literal(
@@ -460,24 +460,24 @@ public class CollarCommand {
false
);
String nickname = collarItem.getNickname(collar);
String nickname = CollarHelper.getNickname(collar);
source.sendSuccess(
() ->
Component.literal(
"§7Nickname: §f" +
(nickname.isEmpty() ? "None" : nickname)
(nickname == null || nickname.isEmpty() ? "None" : nickname)
),
false
);
source.sendSuccess(
() ->
Component.literal(
"§7Has Owner: §f" + collarItem.hasOwner(collar)
"§7Has Owner: §f" + CollarHelper.hasOwner(collar)
),
false
);
// Cell assignment
java.util.UUID cellId = collarItem.getCellId(collar);
java.util.UUID cellId = CollarHelper.getCellId(collar);
if (cellId != null) {
ServerLevel serverLevel = source.getLevel();
CellRegistryV2 registry = CellRegistryV2.get(serverLevel);
@@ -510,10 +510,12 @@ public class CollarCommand {
);
}
boolean locked = collar.getItem() instanceof com.tiedup.remake.items.base.ILockable lockable
&& lockable.isLocked(collar);
source.sendSuccess(
() ->
Component.literal(
"§7Locked: §f" + collarItem.isLocked(collar)
"§7Locked: §f" + locked
),
false
);

View File

@@ -4,12 +4,10 @@ import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.tiedup.remake.items.ModItems;
import com.tiedup.remake.items.base.BindVariant;
import com.tiedup.remake.items.base.BlindfoldVariant;
import com.tiedup.remake.items.base.EarplugsVariant;
import com.tiedup.remake.items.base.GagVariant;
import com.tiedup.remake.items.base.KnifeVariant;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import java.util.Optional;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.network.chat.Component;
@@ -82,53 +80,32 @@ public class KidnapSetCommand {
int given = 0;
// Binds
given += giveItem(
player,
new ItemStack(ModItems.getBind(BindVariant.ROPES), 8)
);
given += giveItem(
player,
new ItemStack(ModItems.getBind(BindVariant.CHAIN), 4)
);
given += giveItem(
player,
new ItemStack(ModItems.getBind(BindVariant.LEATHER_STRAPS), 4)
);
given += giveDataDrivenItems(player, "ropes", 8);
given += giveDataDrivenItems(player, "chain", 4);
given += giveDataDrivenItems(player, "leather_straps", 4);
// Gags
given += giveItem(
player,
new ItemStack(ModItems.getGag(GagVariant.CLOTH_GAG), 4)
);
given += giveItem(
player,
new ItemStack(ModItems.getGag(GagVariant.BALL_GAG), 4)
);
given += giveItem(
player,
new ItemStack(ModItems.getGag(GagVariant.TAPE_GAG), 4)
);
given += giveDataDrivenItems(player, "cloth_gag", 4);
given += giveDataDrivenItems(player, "ball_gag", 4);
given += giveDataDrivenItems(player, "tape_gag", 4);
// Blindfolds
given += giveItem(
player,
new ItemStack(ModItems.getBlindfold(BlindfoldVariant.CLASSIC), 4)
);
given += giveItem(
player,
new ItemStack(ModItems.getBlindfold(BlindfoldVariant.MASK), 2)
);
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(
@@ -155,10 +132,7 @@ public class KidnapSetCommand {
given += giveItem(player, new ItemStack(ModItems.MASTER_KEY.get(), 1));
// Earplugs
given += giveItem(
player,
new ItemStack(ModItems.getEarplugs(EarplugsVariant.CLASSIC), 4)
);
given += giveDataDrivenItems(player, "classic_earplugs", 4);
// Rope arrows
given += giveItem(player, new ItemStack(ModItems.ROPE_ARROW.get(), 16));
@@ -182,6 +156,18 @@ public class KidnapSetCommand {
return finalGiven;
}
private static int giveDataDrivenItems(ServerPlayer player, String itemName, int count) {
int given = 0;
for (int i = 0; i < count; i++) {
ItemStack stack = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", itemName));
if (!stack.isEmpty()) {
giveItem(player, stack);
given++;
}
}
return given;
}
private static int giveItem(ServerPlayer player, ItemStack stack) {
if (!player.getInventory().add(stack)) {
// Drop on ground if inventory full

View File

@@ -7,11 +7,9 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.tiedup.remake.entities.*;
import com.tiedup.remake.entities.skins.EliteKidnapperSkinManager;
import com.tiedup.remake.items.ModItems;
import com.tiedup.remake.items.base.BindVariant;
import com.tiedup.remake.items.base.BlindfoldVariant;
import com.tiedup.remake.items.base.EarplugsVariant;
import com.tiedup.remake.items.base.GagVariant;
import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import net.minecraft.resources.ResourceLocation;
import com.tiedup.remake.v2.BodyRegionV2;
import java.util.List;
import java.util.Optional;
@@ -507,7 +505,7 @@ public class NPCCommand {
npc.equip(
BodyRegionV2.ARMS,
new ItemStack(ModItems.getBind(BindVariant.ROPES))
DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "ropes"))
);
context
.getSource()
@@ -538,7 +536,7 @@ public class NPCCommand {
npc.equip(
BodyRegionV2.MOUTH,
new ItemStack(ModItems.getGag(GagVariant.CLOTH_GAG))
DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "cloth_gag"))
);
context
.getSource()
@@ -571,7 +569,7 @@ public class NPCCommand {
npc.equip(
BodyRegionV2.EYES,
new ItemStack(ModItems.getBlindfold(BlindfoldVariant.CLASSIC))
DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_blindfold"))
);
context
.getSource()
@@ -605,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()
@@ -656,11 +654,11 @@ public class NPCCommand {
com.tiedup.remake.entities.AbstractTiedUpNpc npcEntity
) {
npcEntity.applyBondage(
new ItemStack(ModItems.getBind(BindVariant.ROPES)),
new ItemStack(ModItems.getGag(GagVariant.CLOTH_GAG)),
new ItemStack(ModItems.getBlindfold(BlindfoldVariant.CLASSIC)),
new ItemStack(ModItems.getEarplugs(EarplugsVariant.CLASSIC)),
new ItemStack(ModItems.CLASSIC_COLLAR.get()),
DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "ropes")),
DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "cloth_gag")),
DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_blindfold")),
DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_earplugs")),
DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_collar")),
ItemStack.EMPTY // No clothes
);
}

View File

@@ -9,12 +9,10 @@ import com.tiedup.remake.commands.CommandHelper;
import com.tiedup.remake.core.SystemMessageManager;
import com.tiedup.remake.items.ModItems;
import com.tiedup.remake.items.base.AdjustmentHelper;
import com.tiedup.remake.items.base.BindVariant;
import com.tiedup.remake.items.base.BlindfoldVariant;
import com.tiedup.remake.items.base.EarplugsVariant;
import com.tiedup.remake.items.base.GagVariant;
import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.network.ModNetwork;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import net.minecraft.resources.ResourceLocation;
import com.tiedup.remake.network.sync.PacketSyncBindState;
import com.tiedup.remake.state.PlayerBindState;
import net.minecraft.commands.CommandSourceStack;
@@ -256,7 +254,7 @@ public class BondageSubCommand {
return 0;
}
ItemStack ropes = new ItemStack(ModItems.getBind(BindVariant.ROPES));
ItemStack ropes = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "ropes"));
state.putBindOn(ropes);
CommandHelper.syncPlayerState(targetPlayer, state);
@@ -387,7 +385,7 @@ public class BondageSubCommand {
return 0;
}
ItemStack gag = new ItemStack(ModItems.getGag(GagVariant.CLOTH_GAG));
ItemStack gag = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "cloth_gag"));
state.putGagOn(gag);
CommandHelper.syncPlayerState(targetPlayer, state);
@@ -440,9 +438,7 @@ public class BondageSubCommand {
return 0;
}
ItemStack blindfold = new ItemStack(
ModItems.getBlindfold(BlindfoldVariant.CLASSIC)
);
ItemStack blindfold = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_blindfold"));
state.putBlindfoldOn(blindfold);
CommandHelper.syncPlayerState(targetPlayer, state);
@@ -497,11 +493,10 @@ 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) {
ItemCollar collarItem = (ItemCollar) collar.getItem();
collarItem.addOwner(collar, executor);
CollarHelper.addOwner(collar, executor);
}
state.putCollarOn(collar);
@@ -1021,9 +1016,7 @@ public class BondageSubCommand {
return 0;
}
ItemStack earplugs = new ItemStack(
ModItems.getEarplugs(EarplugsVariant.CLASSIC)
);
ItemStack earplugs = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_earplugs"));
state.putEarplugsOn(earplugs);
CommandHelper.syncPlayerState(targetPlayer, state);
@@ -1067,45 +1060,36 @@ public class BondageSubCommand {
int applied = 0;
if (!state.isTiedUp()) {
ItemStack ropes = new ItemStack(
ModItems.getBind(BindVariant.ROPES)
);
ItemStack ropes = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "ropes"));
state.putBindOn(ropes);
applied++;
}
if (!state.isGagged()) {
ItemStack gag = new ItemStack(
ModItems.getGag(GagVariant.CLOTH_GAG)
);
ItemStack gag = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "cloth_gag"));
state.putGagOn(gag);
applied++;
}
if (!state.isBlindfolded()) {
ItemStack blindfold = new ItemStack(
ModItems.getBlindfold(BlindfoldVariant.CLASSIC)
);
ItemStack blindfold = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_blindfold"));
state.putBlindfoldOn(blindfold);
applied++;
}
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
) {
ItemCollar collarItem = (ItemCollar) collar.getItem();
collarItem.addOwner(collar, executor);
CollarHelper.addOwner(collar, executor);
}
state.putCollarOn(collar);
applied++;
}
if (!state.hasEarplugs()) {
ItemStack earplugs = new ItemStack(
ModItems.getEarplugs(EarplugsVariant.CLASSIC)
);
ItemStack earplugs = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_earplugs"));
state.putEarplugsOn(earplugs);
applied++;
}
@@ -1167,37 +1151,28 @@ public class BondageSubCommand {
// First fully restrain
if (!state.isTiedUp()) {
ItemStack ropes = new ItemStack(
ModItems.getBind(BindVariant.ROPES)
);
ItemStack ropes = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "ropes"));
state.putBindOn(ropes);
}
if (!state.isGagged()) {
ItemStack gag = new ItemStack(
ModItems.getGag(GagVariant.CLOTH_GAG)
);
ItemStack gag = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "cloth_gag"));
state.putGagOn(gag);
}
if (!state.isBlindfolded()) {
ItemStack blindfold = new ItemStack(
ModItems.getBlindfold(BlindfoldVariant.CLASSIC)
);
ItemStack blindfold = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_blindfold"));
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
) {
ItemCollar collarItem = (ItemCollar) collar.getItem();
collarItem.addOwner(collar, executor);
CollarHelper.addOwner(collar, executor);
}
state.putCollarOn(collar);
}
if (!state.hasEarplugs()) {
ItemStack earplugs = new ItemStack(
ModItems.getEarplugs(EarplugsVariant.CLASSIC)
);
ItemStack earplugs = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_earplugs"));
state.putEarplugsOn(earplugs);
}

View File

@@ -2,11 +2,12 @@ package com.tiedup.remake.compat.mca.capability;
import com.tiedup.remake.compat.mca.MCABondageManager;
import com.tiedup.remake.compat.mca.MCACompat;
import com.tiedup.remake.items.base.IHasBlindingEffect;
import com.tiedup.remake.items.base.IHasGaggingEffect;
import com.tiedup.remake.v2.bondage.component.BlindingComponent;
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.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;
@@ -277,16 +278,15 @@ public class MCAKidnappedAdapter implements IRestrainable {
@Override
public boolean hasGaggingEffect() {
ItemStack gag = cap.getGag();
return !gag.isEmpty() && gag.getItem() instanceof IHasGaggingEffect;
if (gag.isEmpty()) return false;
return DataDrivenBondageItem.getComponent(gag, ComponentType.GAGGING, GaggingComponent.class) != null;
}
@Override
public boolean hasBlindingEffect() {
ItemStack blindfold = cap.getBlindfold();
return (
!blindfold.isEmpty() &&
blindfold.getItem() instanceof IHasBlindingEffect
);
if (blindfold.isEmpty()) return false;
return DataDrivenBondageItem.getComponent(blindfold, ComponentType.BLINDING, BlindingComponent.class) != null;
}
@Override

View File

@@ -160,7 +160,7 @@ public class SettingsAccessor {
* <p><b>BUG-003 fix:</b> 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.
*
* <p>Replicates the mapping from
* {@link com.tiedup.remake.items.base.BindVariant#getResistance()} so that
* <p>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")

View File

@@ -3,8 +3,10 @@ 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;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import com.tiedup.remake.util.GagMaterial;
import com.tiedup.remake.util.PhoneticMapper;
import com.tiedup.remake.util.SyllableAnalyzer;
@@ -58,8 +60,11 @@ public class GagTalkManager {
) {
LivingEntity entity = kidnapped.asLivingEntity();
GagMaterial material = GagMaterial.CLOTH;
if (gagStack.getItem() instanceof ItemGag gag) {
material = gag.getGagMaterial();
// V2: check data-driven GaggingComponent first
GaggingComponent gaggingComp = DataDrivenBondageItem.getComponent(
gagStack, ComponentType.GAGGING, GaggingComponent.class);
if (gaggingComp != null && gaggingComp.getMaterial() != null) {
material = gaggingComp.getMaterial();
}
// 1. EFFET DE SUFFOCATION (Si message trop long)
@@ -514,8 +519,13 @@ public class GagTalkManager {
}
GagMaterial material = GagMaterial.CLOTH;
if (gagStack != null && gagStack.getItem() instanceof ItemGag gag) {
material = gag.getGagMaterial();
if (gagStack != null && !gagStack.isEmpty()) {
// V2: check data-driven GaggingComponent first
GaggingComponent comp = DataDrivenBondageItem.getComponent(
gagStack, ComponentType.GAGGING, GaggingComponent.class);
if (comp != null && comp.getMaterial() != null) {
material = comp.getMaterial();
}
}
StringBuilder muffled = new StringBuilder();

View File

@@ -5,9 +5,9 @@ import com.tiedup.remake.dialogue.DialogueBridge;
import com.tiedup.remake.entities.EntityMaster;
import com.tiedup.remake.entities.ai.master.MasterPlaceBlockGoal;
import com.tiedup.remake.entities.ai.master.MasterState;
import com.tiedup.remake.items.ModItems;
import com.tiedup.remake.items.base.BindVariant;
import com.tiedup.remake.network.ModNetwork;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import net.minecraft.resources.ResourceLocation;
import com.tiedup.remake.network.master.PacketOpenPetRequestMenu;
import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2;
@@ -171,9 +171,7 @@ public class PetRequestManager {
// Put dogbind on player (if not already tied)
if (!state.isTiedUp()) {
ItemStack dogbind = new ItemStack(
ModItems.getBind(BindVariant.DOGBINDER)
);
ItemStack dogbind = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "dogbinder"));
state.equip(BodyRegionV2.ARMS, dogbind);
TiedUpMod.LOGGER.debug(
"[PetRequestManager] Equipped dogbind on {} for walk",
@@ -228,7 +226,7 @@ public class PetRequestManager {
}
// Master equips armbinder on pet (classic pet play restraint)
ItemStack bind = new ItemStack(ModItems.getBind(BindVariant.ARMBINDER));
ItemStack bind = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "armbinder"));
state.equip(BodyRegionV2.ARMS, bind);
DialogueBridge.talkTo(master, pet, "petplay.tie_accept");

View File

@@ -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(),

View File

@@ -1,16 +1,21 @@
package com.tiedup.remake.dispenser;
import com.tiedup.remake.items.base.*;
import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.BindModeHelper;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.v2.bondage.component.ComponentType;
import com.tiedup.remake.v2.bondage.component.GaggingComponent;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemDefinition;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemRegistry;
import java.util.function.BiConsumer;
import java.util.function.Predicate;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
/**
* Generic dispenser behavior for equipping bondage items.
* Replaces individual BindDispenseBehavior, GagDispenseBehavior, etc.
* Uses V2 data-driven item detection instead of V1 class checks.
*
* Use factory methods to create instances for each bondage type.
*/
@@ -18,23 +23,23 @@ public class GenericBondageDispenseBehavior
extends EquipBondageDispenseBehavior
{
private final Class<? extends Item> itemClass;
private final Predicate<ItemStack> itemCheck;
private final Predicate<IBondageState> canEquipCheck;
private final BiConsumer<IBondageState, ItemStack> equipAction;
private GenericBondageDispenseBehavior(
Class<? extends Item> itemClass,
Predicate<ItemStack> itemCheck,
Predicate<IBondageState> canEquipCheck,
BiConsumer<IBondageState, ItemStack> 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<BodyRegionV2> 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)
);

View File

@@ -4,7 +4,12 @@ import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.entities.damsel.components.*;
import com.tiedup.remake.entities.skins.Gender;
import com.tiedup.remake.items.base.ILockable;
import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.v2.bondage.PoseTypeHelper;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import com.tiedup.remake.v2.bondage.component.ComponentType;
import com.tiedup.remake.v2.bondage.component.GaggingComponent;
import com.tiedup.remake.v2.bondage.component.BlindingComponent;
import com.tiedup.remake.state.ICaptor;
import com.tiedup.remake.state.IRestrainable;
import com.tiedup.remake.state.IRestrainableEntity;
@@ -455,16 +460,8 @@ public abstract class AbstractTiedUpNpc
*/
public boolean isDogPose() {
ItemStack bind = this.getEquipment(BodyRegionV2.ARMS);
if (
bind.getItem() instanceof
com.tiedup.remake.items.base.ItemBind itemBind
) {
return (
itemBind.getPoseType() ==
com.tiedup.remake.items.base.PoseType.DOG
);
}
return false;
if (bind.isEmpty()) return false;
return PoseTypeHelper.getPoseType(bind) == com.tiedup.remake.items.base.PoseType.DOG;
}
/**
@@ -679,10 +676,8 @@ public abstract class AbstractTiedUpNpc
// Exception: collar owner can leash even if not tied
if (this.hasCollar()) {
ItemStack collar = this.getEquipment(BodyRegionV2.NECK);
if (collar.getItem() instanceof ItemCollar collarItem) {
if (collarItem.getOwners(collar).contains(player.getUUID())) {
return true;
}
if (CollarHelper.isOwner(collar, player)) {
return true;
}
}
@@ -801,20 +796,14 @@ public abstract class AbstractTiedUpNpc
public boolean hasGaggingEffect() {
ItemStack gag = this.getEquipment(BodyRegionV2.MOUTH);
if (gag.isEmpty()) return false;
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;
return (
blindfold.getItem() instanceof
com.tiedup.remake.items.base.IHasBlindingEffect
);
return DataDrivenBondageItem.getComponent(blindfold, ComponentType.BLINDING, BlindingComponent.class) != null;
}
@Override
@@ -990,9 +979,9 @@ public abstract class AbstractTiedUpNpc
ItemStack collar = this.getEquipment(BodyRegionV2.NECK);
if (collar.isEmpty()) return false;
if (!(collar.getItem() instanceof ItemCollar itemCollar)) return false;
if (!CollarHelper.isCollar(collar)) return false;
java.util.UUID cellId = itemCollar.getCellId(collar);
java.util.UUID cellId = CollarHelper.getCellId(collar);
if (cellId == null) return false;
// Get cell position from registry
@@ -1096,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)

View File

@@ -1,10 +1,9 @@
package com.tiedup.remake.entities;
import com.tiedup.remake.core.SystemMessageManager;
import com.tiedup.remake.items.ModItems;
import com.tiedup.remake.items.base.BindVariant;
import com.tiedup.remake.items.base.GagVariant;
import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import net.minecraft.resources.ResourceLocation;
import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.util.MessageDispatcher;
import com.tiedup.remake.util.teleport.Position;
@@ -53,13 +52,10 @@ public class BondageServiceHandler {
if (!npc.hasCollar()) return false;
ItemStack collar = npc.getEquipment(BodyRegionV2.NECK);
if (collar.getItem() instanceof ItemCollar itemCollar) {
return (
itemCollar.hasCellAssigned(collar) &&
itemCollar.isBondageServiceEnabled(collar)
);
}
return false;
return (
CollarHelper.hasCellAssigned(collar) &&
CollarHelper.isBondageServiceEnabled(collar)
);
}
/**
@@ -70,11 +66,9 @@ public class BondageServiceHandler {
public String getMessage() {
if (npc.hasCollar()) {
ItemStack collar = npc.getEquipment(BodyRegionV2.NECK);
if (collar.getItem() instanceof ItemCollar itemCollar) {
String message = itemCollar.getServiceSentence(collar);
if (message != null && !message.isEmpty()) {
return message;
}
String message = CollarHelper.getServiceSentence(collar);
if (message != null && !message.isEmpty()) {
return message;
}
}
return DEFAULT_MESSAGE;
@@ -119,9 +113,9 @@ public class BondageServiceHandler {
*/
private void capturePlayer(Player player) {
ItemStack collar = npc.getEquipment(BodyRegionV2.NECK);
if (!(collar.getItem() instanceof ItemCollar itemCollar)) return;
if (!CollarHelper.isCollar(collar)) return;
java.util.UUID cellId = itemCollar.getCellId(collar);
java.util.UUID cellId = CollarHelper.getCellId(collar);
if (cellId == null) return;
// Get cell position from registry
@@ -141,7 +135,7 @@ public class BondageServiceHandler {
);
// Warn masters if configured
warnOwners(player, itemCollar, collar);
warnOwners(player, collar);
// Get player's kidnapped state
PlayerBindState state = PlayerBindState.getInstance(player);
@@ -149,18 +143,18 @@ public class BondageServiceHandler {
// Apply bondage
state.equip(
BodyRegionV2.ARMS,
new ItemStack(ModItems.getBind(BindVariant.ROPES))
DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "ropes"))
);
state.equip(
BodyRegionV2.MOUTH,
new ItemStack(ModItems.getGag(GagVariant.BALL_GAG))
DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "ball_gag"))
);
// Teleport to cell
state.teleportToPosition(cellPosition);
// Tie to pole if configured on collar
if (itemCollar.shouldTieToPole(collar)) {
if (CollarHelper.shouldTieToPole(collar)) {
state.tieToClosestPole(3);
}
}
@@ -178,10 +172,9 @@ public class BondageServiceHandler {
*/
private void warnOwners(
Player capturedPlayer,
ItemCollar itemCollar,
ItemStack collarStack
) {
if (!itemCollar.shouldWarnMasters(collarStack)) {
if (!CollarHelper.shouldWarnMasters(collarStack)) {
return;
}
@@ -191,7 +184,7 @@ public class BondageServiceHandler {
capturedPlayer.getName().getString() +
" via bondage service!";
for (UUID ownerUUID : itemCollar.getOwners(collarStack)) {
for (UUID ownerUUID : CollarHelper.getOwners(collarStack)) {
Player owner = npc.level().getPlayerByUUID(ownerUUID);
if (owner != null) {
SystemMessageManager.sendChatToPlayer(

View File

@@ -5,7 +5,7 @@ import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.entities.damsel.components.*;
import com.tiedup.remake.entities.skins.DamselSkinManager;
import com.tiedup.remake.entities.skins.Gender;
import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.state.ICaptor;
import com.tiedup.remake.v2.BodyRegionV2;
import java.util.UUID;
@@ -527,8 +527,8 @@ public class EntityDamsel
if (!this.hasCollar()) return false;
ItemStack collar = this.getEquipment(BodyRegionV2.NECK);
if (!(collar.getItem() instanceof ItemCollar collarItem)) return false;
if (!collarItem.getOwners(collar).contains(commander.getUUID())) {
if (!CollarHelper.isCollar(collar)) return false;
if (!CollarHelper.isOwner(collar, commander.getUUID())) {
if (!this.isGagged()) {
com.tiedup.remake.dialogue.EntityDialogueManager.talkByDialogueId(
this,
@@ -653,8 +653,8 @@ public class EntityDamsel
return net.minecraft.world.InteractionResult.FAIL;
}
ItemStack collar = this.getEquipment(BodyRegionV2.NECK);
if (collar.getItem() instanceof ItemCollar collarItem) {
if (!collarItem.isOwner(collar, player)) {
if (CollarHelper.isCollar(collar)) {
if (!CollarHelper.isOwner(collar, player)) {
if (
player instanceof
net.minecraft.server.level.ServerPlayer sp
@@ -693,9 +693,9 @@ public class EntityDamsel
this.hasCollar()
) {
ItemStack collar = this.getEquipment(BodyRegionV2.NECK);
if (collar.getItem() instanceof ItemCollar collarItem) {
if (CollarHelper.isCollar(collar)) {
if (
collarItem.isOwner(collar, player) &&
CollarHelper.isOwner(collar, player) &&
player instanceof
net.minecraft.server.level.ServerPlayer serverPlayer
) {
@@ -822,8 +822,8 @@ public class EntityDamsel
public String getTargetRelation(Player player) {
if (hasCollar()) {
ItemStack collar = getEquipment(BodyRegionV2.NECK);
if (collar.getItem() instanceof ItemCollar collarItem) {
if (collarItem.isOwner(collar, player)) {
if (CollarHelper.isCollar(collar)) {
if (CollarHelper.isOwner(collar, player)) {
return "master";
}
}

View File

@@ -12,7 +12,7 @@ import com.tiedup.remake.entities.kidnapper.components.KidnapperAggressionSystem
import com.tiedup.remake.entities.skins.Gender;
import com.tiedup.remake.entities.skins.KidnapperSkinManager;
import com.tiedup.remake.items.ModItems;
import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.personality.PersonalityType;
import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.state.ICaptor;
@@ -1367,10 +1367,7 @@ public class EntityKidnapper
if (!this.hasCollar()) return false;
ItemStack collar = this.getEquipment(BodyRegionV2.NECK);
if (collar.getItem() instanceof ItemCollar collarItem) {
return collarItem.isOwner(collar, player);
}
return false;
return CollarHelper.isOwner(collar, player);
}
/** Damage reduction multiplier against monsters (50% damage taken) */

View File

@@ -18,6 +18,7 @@ import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.state.ICaptor;
import com.tiedup.remake.util.MessageDispatcher;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
@@ -615,78 +616,10 @@ public class EntityKidnapperMerchant extends EntityKidnapperElite {
private List<ItemStack> collectAllModItems() {
List<ItemStack> items = new ArrayList<>();
// All binds (15)
// Items with colors get multiple variants (one per color)
for (BindVariant variant : BindVariant.values()) {
if (variant.supportsColor()) {
// Add one item per color (16 standard colors)
for (ItemColor color : ItemColor.values()) {
if (
color != ItemColor.CAUTION && color != ItemColor.CLEAR
) {
ItemStack stack = new ItemStack(
ModItems.getBind(variant)
);
KidnapperItemSelector.applyColor(stack, color);
items.add(stack);
}
}
} else {
// No color variants
items.add(new ItemStack(ModItems.getBind(variant)));
}
}
// All gags (19)
for (GagVariant variant : GagVariant.values()) {
if (variant.supportsColor()) {
// Add one item per color
for (ItemColor color : ItemColor.values()) {
// TAPE_GAG can use caution/clear, others only standard colors
if (
variant == GagVariant.TAPE_GAG ||
(color != ItemColor.CAUTION && color != ItemColor.CLEAR)
) {
ItemStack stack = new ItemStack(
ModItems.getGag(variant)
);
KidnapperItemSelector.applyColor(stack, color);
items.add(stack);
}
}
} else {
items.add(new ItemStack(ModItems.getGag(variant)));
}
}
// All blindfolds (2) - BOTH support colors
for (BlindfoldVariant variant : BlindfoldVariant.values()) {
if (variant.supportsColor()) {
// Add one item per color (16 standard colors)
for (ItemColor color : ItemColor.values()) {
if (
color != ItemColor.CAUTION && color != ItemColor.CLEAR
) {
ItemStack stack = new ItemStack(
ModItems.getBlindfold(variant)
);
KidnapperItemSelector.applyColor(stack, color);
items.add(stack);
}
}
} else {
items.add(new ItemStack(ModItems.getBlindfold(variant)));
}
}
// Earplugs - no color support
for (EarplugsVariant variant : EarplugsVariant.values()) {
items.add(new ItemStack(ModItems.getEarplugs(variant)));
}
// Mittens - no color support
for (MittensVariant variant : MittensVariant.values()) {
items.add(new ItemStack(ModItems.getMittens(variant)));
// 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
@@ -694,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;
@@ -749,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()
) {
@@ -764,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;

View File

@@ -94,10 +94,8 @@ public class EntityRopeArrow extends AbstractArrow {
int roll = this.random.nextInt(100) + 1;
if (roll <= bindChance) {
// Success! Bind the target
ItemStack ropeItem = new ItemStack(
ModItems.getBind(
com.tiedup.remake.items.base.BindVariant.ROPES
)
ItemStack ropeItem = com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem.createStack(
new net.minecraft.resources.ResourceLocation("tiedup", "ropes")
);
targetState.equip(BodyRegionV2.ARMS, ropeItem);

View File

@@ -1,7 +1,8 @@
package com.tiedup.remake.entities;
import com.tiedup.remake.items.ModItems;
import com.tiedup.remake.items.base.BindVariant;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import net.minecraft.resources.ResourceLocation;
import com.tiedup.remake.v2.bondage.IV2BondageItem;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.item.ItemStack;
@@ -46,7 +47,7 @@ public class KidnapperCaptureEquipment {
) {
return mainHand;
}
return new ItemStack(ModItems.getBind(BindVariant.ROPES));
return DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "ropes"));
}
/**
@@ -114,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

View File

@@ -1,11 +1,9 @@
package com.tiedup.remake.entities;
import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.util.teleport.Position;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.CollarHelper;
import java.util.List;
import java.util.UUID;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.Nullable;
@@ -31,21 +29,7 @@ public class KidnapperCollarConfig {
this.kidnapper = kidnapper;
}
// COLLAR ITEM ACCESS
/**
* Get the collar item if equipped.
* @return ItemCollar or null if no collar or not an ItemCollar
*/
@Nullable
public ItemCollar getCollarItem() {
ItemStack collar = kidnapper.getEquipment(BodyRegionV2.NECK);
if (collar.isEmpty()) return null;
if (collar.getItem() instanceof ItemCollar itemCollar) {
return itemCollar;
}
return null;
}
// COLLAR STACK ACCESS
/**
* Get the collar ItemStack.
@@ -55,30 +39,29 @@ public class KidnapperCollarConfig {
return kidnapper.getEquipment(BodyRegionV2.NECK);
}
/**
* Check if the kidnapper has a valid collar.
*/
private boolean hasValidCollar() {
return kidnapper.hasCollar() && CollarHelper.isCollar(getCollarStack());
}
// KIDNAPPING MODE
/**
* Check if kidnapping mode is enabled via collar.
*/
public boolean isKidnappingModeEnabled() {
if (!kidnapper.hasCollar()) return false;
ItemCollar itemCollar = getCollarItem();
if (itemCollar == null) return false;
return itemCollar.isKidnappingModeEnabled(getCollarStack());
if (!hasValidCollar()) return false;
return CollarHelper.isKidnappingModeEnabled(getCollarStack());
}
/**
* Check if kidnapping mode is fully ready (enabled + prison set).
*/
public boolean isKidnappingModeReady() {
if (!kidnapper.hasCollar()) return false;
ItemCollar itemCollar = getCollarItem();
if (itemCollar == null) return false;
return itemCollar.isKidnappingModeReady(getCollarStack());
if (!hasValidCollar()) return false;
return CollarHelper.isKidnappingModeReady(getCollarStack());
}
// POSITION GETTERS
@@ -88,20 +71,16 @@ public class KidnapperCollarConfig {
*/
@Nullable
public java.util.UUID getCellId() {
ItemCollar itemCollar = getCollarItem();
if (itemCollar == null) return null;
return itemCollar.getCellId(getCollarStack());
if (!hasValidCollar()) return null;
return CollarHelper.getCellId(getCollarStack());
}
/**
* Check if collar has a cell assigned.
*/
public boolean hasCellAssigned() {
ItemCollar itemCollar = getCollarItem();
if (itemCollar == null) return false;
return itemCollar.hasCellAssigned(getCollarStack());
if (!hasValidCollar()) return false;
return CollarHelper.hasCellAssigned(getCollarStack());
}
// BEHAVIOR FLAGS
@@ -110,20 +89,16 @@ public class KidnapperCollarConfig {
* Check if should warn masters after capturing slave.
*/
public boolean shouldWarnMasters() {
ItemCollar itemCollar = getCollarItem();
if (itemCollar == null) return false;
return itemCollar.shouldWarnMasters(getCollarStack());
if (!hasValidCollar()) return false;
return CollarHelper.shouldWarnMasters(getCollarStack());
}
/**
* Check if should tie slave to pole in prison.
*/
public boolean shouldTieToPole() {
ItemCollar itemCollar = getCollarItem();
if (itemCollar == null) return false;
return itemCollar.shouldTieToPole(getCollarStack());
if (!hasValidCollar()) return false;
return CollarHelper.shouldTieToPole(getCollarStack());
}
// BLACKLIST/WHITELIST
@@ -139,19 +114,18 @@ public class KidnapperCollarConfig {
* </ul>
*/
public boolean isValidKidnappingTarget(Player player) {
ItemCollar itemCollar = getCollarItem();
if (itemCollar == null) return true; // No collar config = everyone is valid
if (!hasValidCollar()) return true; // No collar config = everyone is valid
ItemStack collarStack = getCollarStack();
UUID playerUUID = player.getUUID();
// If whitelist exists and is not empty, player MUST be on it
List<UUID> whitelist = itemCollar.getWhitelist(collarStack);
List<UUID> whitelist = CollarHelper.getWhitelist(collarStack);
if (!whitelist.isEmpty()) {
return whitelist.contains(playerUUID);
}
// Otherwise, check blacklist (blacklisted = not a valid target)
return !itemCollar.isBlacklisted(collarStack, playerUUID);
return !CollarHelper.isBlacklisted(collarStack, playerUUID);
}
}

View File

@@ -1,8 +1,9 @@
package com.tiedup.remake.entities;
import com.tiedup.remake.items.ModItems;
import com.tiedup.remake.items.base.*;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import java.util.Random;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.Nullable;
@@ -11,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%
@@ -78,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(); }
}
/**
@@ -128,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,
@@ -151,9 +121,6 @@ public class KidnapperItemSelector {
return prob;
}
/**
* Internal item selection logic.
*/
private static SelectionResult selectItems(
boolean isElite,
boolean isArcher
@@ -162,126 +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
) {
ItemStack stack = new ItemStack(ModItems.getBind(variant));
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 = new ItemStack(ModItems.getGag(variant));
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 = new ItemStack(ModItems.getBlindfold(variant));
if (
color != null &&
variant.supportsColor() &&
isColorValidForBlindfold(color, variant)
) {
public static ItemStack createItemById(String id, @Nullable ItemColor color) {
ItemStack stack = DataDrivenBondageItem.createStack(
new ResourceLocation("tiedup", id)
);
if (color != null) {
applyColor(stack, color);
}
return stack;
@@ -289,18 +194,20 @@ public class KidnapperItemSelector {
/**
* Create mittens ItemStack.
* Mittens don't have color variants.
*/
public static ItemStack createMittens() {
return new ItemStack(ModItems.getMittens(MittensVariant.LEATHER));
return DataDrivenBondageItem.createStack(
new ResourceLocation("tiedup", "leather_mittens")
);
}
/**
* Create earplugs ItemStack.
* Earplugs don't have color variants.
*/
public static ItemStack createEarplugs() {
return new ItemStack(ModItems.getEarplugs(EarplugsVariant.CLASSIC));
return DataDrivenBondageItem.createStack(
new ResourceLocation("tiedup", "classic_earplugs")
);
}
// COLOR METHODS
@@ -310,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;
@@ -340,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;
}
}

View File

@@ -2,7 +2,7 @@ package com.tiedup.remake.entities;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.ModItems;
import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.state.CollarRegistry;
import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.util.tasks.ItemTask;
@@ -161,16 +161,17 @@ public class KidnapperJobManager {
);
// Put a shock collar on the worker AFTER untie/free
ItemStack shockCollar = new ItemStack(ModItems.SHOCK_COLLAR_AUTO.get());
if (shockCollar.getItem() instanceof ItemCollar collarItem) {
// Add kidnapper as owner so the collar is linked
collarItem.addOwner(
shockCollar,
kidnapper.getUUID(),
kidnapper.getNpcName()
);
// Lock the collar so they can't remove it
shockCollar = collarItem.setLocked(shockCollar, true);
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,
kidnapper.getUUID(),
kidnapper.getNpcName()
);
// Lock the collar so they can't remove it
if (shockCollar.getItem() instanceof com.tiedup.remake.items.base.ILockable lockable) {
shockCollar = lockable.setLocked(shockCollar, true);
}
captive.equip(BodyRegionV2.NECK, shockCollar);

View File

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

View File

@@ -1,7 +1,6 @@
package com.tiedup.remake.entities;
import com.tiedup.remake.items.ModItems;
import com.tiedup.remake.items.base.BindVariant;
import com.tiedup.remake.v2.bondage.PoseTypeHelper;
import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.util.KidnappedHelper;
import com.tiedup.remake.v2.BodyRegionV2;
@@ -108,7 +107,7 @@ public final class LeashProxyEntity extends Turtle {
ItemStack bind = state.getEquipment(BodyRegionV2.ARMS);
if (
!bind.isEmpty() &&
bind.getItem() == ModItems.getBind(BindVariant.DOGBINDER)
PoseTypeHelper.getPoseType(bind) == com.tiedup.remake.items.base.PoseType.DOG
) {
yOffset = 0.35D; // Lower for 4-legged dogwalk pose (back/hip level)
}

View File

@@ -11,7 +11,7 @@ import com.tiedup.remake.entities.EntityKidnapper;
import com.tiedup.remake.entities.EntityKidnapper.CaptivePriority;
import com.tiedup.remake.entities.ai.StuckDetector;
import com.tiedup.remake.entities.ai.WaypointNavigator;
import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.prison.PrisonerManager;
import com.tiedup.remake.prison.PrisonerRecord;
import com.tiedup.remake.prison.PrisonerState;
@@ -260,7 +260,7 @@ public class KidnapperBringToCellGoal extends Goal {
);
IRestrainable captive = this.kidnapper.getCaptive();
if (captive != null) {
ItemCollar.runWithSuppressedAlert(() ->
CollarHelper.runWithSuppressedAlert(() ->
captive.free(false)
);
this.kidnapper.removeCaptive(captive, false);
@@ -1079,9 +1079,9 @@ public class KidnapperBringToCellGoal extends Goal {
ItemStack collar = captive.getEquipment(BodyRegionV2.NECK);
if (
!collar.isEmpty() &&
collar.getItem() instanceof ItemCollar collarItem
CollarHelper.isCollar(collar)
) {
collarItem.setCellId(collar, this.targetCell.getId());
CollarHelper.setCellId(collar, this.targetCell.getId());
}
TiedUpMod.LOGGER.info(

View File

@@ -8,7 +8,7 @@ import com.tiedup.remake.entities.EntityKidnapper;
import com.tiedup.remake.entities.ai.StuckDetector;
import com.tiedup.remake.items.ItemKey;
import com.tiedup.remake.items.ModItems;
import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.network.ModNetwork;
import com.tiedup.remake.network.action.PacketTying;
import com.tiedup.remake.prison.PrisonerManager;
@@ -849,11 +849,8 @@ public class KidnapperCaptureGoal extends Goal {
// If already has collar, check ownership
if (state.hasCollar()) {
ItemStack existingCollar = state.getEquipment(BodyRegionV2.NECK);
if (
existingCollar.getItem() instanceof
com.tiedup.remake.items.base.ItemCollar collarItem
) {
java.util.List<java.util.UUID> owners = collarItem.getOwners(
if (CollarHelper.isCollar(existingCollar)) {
java.util.List<java.util.UUID> owners = CollarHelper.getOwners(
existingCollar
);
if (!owners.contains(this.kidnapper.getUUID())) {
@@ -885,9 +882,9 @@ public class KidnapperCaptureGoal extends Goal {
for (java.util.UUID oldOwner : new java.util.ArrayList<>(
owners
)) {
collarItem.removeOwner(existingCollar, oldOwner);
CollarHelper.removeOwner(existingCollar, oldOwner);
}
collarItem.addOwner(
CollarHelper.addOwner(
existingCollar,
this.kidnapper.getUUID(),
this.kidnapper.getNpcName()
@@ -929,9 +926,9 @@ public class KidnapperCaptureGoal extends Goal {
if (this.captureProgress >= COLLAR_APPLY_TIME) {
// Create a copy of the collar and configure it
ItemStack collarCopy = collar.copy();
if (collarCopy.getItem() instanceof ItemCollar collarItem) {
if (CollarHelper.isCollar(collarCopy)) {
// Add kidnapper as owner
collarItem.addOwner(
CollarHelper.addOwner(
collarCopy,
this.kidnapper.getUUID(),
this.kidnapper.getNpcName()
@@ -941,12 +938,17 @@ public class KidnapperCaptureGoal extends Goal {
ItemStack keyStack = new ItemStack(ModItems.COLLAR_KEY.get());
if (keyStack.getItem() instanceof ItemKey keyItem) {
UUID keyUUID = keyItem.getKeyUUID(keyStack);
collarItem.setLockedByKeyUUID(collarCopy, keyUUID);
// Lock via ILockable interface (collar must implement it)
if (collarCopy.getItem() instanceof com.tiedup.remake.items.base.ILockable lockable) {
lockable.setLockedByKeyUUID(collarCopy, keyUUID);
}
// Store the key on the kidnapper for potential drop on death
this.kidnapper.addCollarKey(keyStack);
} else {
// Fallback: just lock without a key
collarItem.setLocked(collarCopy, true);
if (collarCopy.getItem() instanceof com.tiedup.remake.items.base.ILockable lockable) {
lockable.setLocked(collarCopy, true);
}
}
}

View File

@@ -8,8 +8,8 @@ import com.tiedup.remake.entities.AbstractTiedUpNpc;
import com.tiedup.remake.entities.EntityKidnapper;
import com.tiedup.remake.entities.ai.StuckDetector;
import com.tiedup.remake.entities.ai.WaypointNavigator;
import com.tiedup.remake.items.ModItems;
import com.tiedup.remake.items.base.BindVariant;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import net.minecraft.resources.ResourceLocation;
import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.util.KidnappedHelper;
import com.tiedup.remake.util.KidnapperAIHelper;
@@ -547,9 +547,7 @@ public class KidnapperWalkPrisonerGoal extends Goal {
);
// 3. Change bind to DOGBINDER
ItemStack dogBinder = new ItemStack(
ModItems.getBind(BindVariant.DOGBINDER)
);
ItemStack dogBinder = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "dogbinder"));
this.walkedPrisoner.equip(BodyRegionV2.ARMS, dogBinder);
TiedUpMod.LOGGER.debug(
@@ -738,7 +736,7 @@ public class KidnapperWalkPrisonerGoal extends Goal {
if (currentBind.isEmpty() || !prisoner.isTiedUp()) {
// They freed themselves - put dogbinder back on
ItemStack dogBinder = new ItemStack(
ModItems.getBind(BindVariant.DOGBINDER)
DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "dogbinder")).getItem()
);
prisoner.equip(BodyRegionV2.ARMS, dogBinder);
}

View File

@@ -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 (

View File

@@ -338,10 +338,8 @@ public class MaidReturnGoal extends Goal {
// Fallback: use basic rope if no snapshot
cap.equip(
BodyRegionV2.ARMS,
new ItemStack(
com.tiedup.remake.items.ModItems.getBind(
com.tiedup.remake.items.base.BindVariant.ROPES
)
com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem.createStack(
new net.minecraft.resources.ResourceLocation("tiedup", "ropes")
)
);
}

View File

@@ -3,8 +3,8 @@ package com.tiedup.remake.entities.ai.master;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.dialogue.DialogueBridge;
import com.tiedup.remake.entities.EntityMaster;
import com.tiedup.remake.items.ModItems;
import com.tiedup.remake.items.base.BindVariant;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import net.minecraft.resources.ResourceLocation;
import com.tiedup.remake.state.HumanChairHelper;
import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2;
@@ -394,8 +394,8 @@ public class MasterHumanChairGoal extends Goal {
// Apply invisible dogbind for the pose animation
if (!bindState.isTiedUp()) {
ItemStack dogbind = new ItemStack(
ModItems.getBind(BindVariant.DOGBINDER)
ItemStack dogbind = DataDrivenBondageItem.createStack(
new ResourceLocation("tiedup", "dogbinder")
);
CompoundTag tag = dogbind.getOrCreateTag();
tag.putBoolean(NBT_HUMAN_CHAIR_BIND, true);

View File

@@ -3,12 +3,9 @@ package com.tiedup.remake.entities.ai.master;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.dialogue.DialogueBridge;
import com.tiedup.remake.entities.EntityMaster;
import com.tiedup.remake.items.ItemChokeCollar;
import com.tiedup.remake.items.ModItems;
import com.tiedup.remake.items.base.BindVariant;
import com.tiedup.remake.items.base.BlindfoldVariant;
import com.tiedup.remake.items.base.GagVariant;
import com.tiedup.remake.items.base.MittensVariant;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import net.minecraft.resources.ResourceLocation;
import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.IV2BondageEquipment;
@@ -261,7 +258,7 @@ public class MasterPunishGoal extends Goal {
// CHOKE: only if pet has choke collar
if (bindState != null && bindState.hasCollar()) {
ItemStack collar = bindState.getEquipment(BodyRegionV2.NECK);
if (collar.getItem() instanceof ItemChokeCollar) {
if (CollarHelper.isChokeCollar(collar)) {
available.add(PunishmentType.CHOKE_COLLAR);
}
}
@@ -393,8 +390,8 @@ public class MasterPunishGoal extends Goal {
PlayerBindState bindState = PlayerBindState.getInstance(pet);
if (bindState != null && bindState.hasCollar()) {
ItemStack collar = bindState.getEquipment(BodyRegionV2.NECK);
if (collar.getItem() instanceof ItemChokeCollar chokeCollar) {
chokeCollar.setChoking(collar, true);
if (CollarHelper.isChokeCollar(collar)) {
CollarHelper.setChoking(collar, true);
this.activeChokeCollar = collar;
this.chokeActiveTimer = 1;
@@ -457,12 +454,14 @@ public class MasterPunishGoal extends Goal {
*/
private ItemStack createAccessory(BodyRegionV2 region) {
return switch (region) {
case EYES -> new ItemStack(
ModItems.getBlindfold(BlindfoldVariant.CLASSIC)
case EYES -> DataDrivenBondageItem.createStack(
new ResourceLocation("tiedup", "classic_blindfold")
);
case MOUTH -> new ItemStack(ModItems.getGag(GagVariant.BALL_GAG));
case HANDS -> new ItemStack(
ModItems.getMittens(MittensVariant.LEATHER)
case MOUTH -> DataDrivenBondageItem.createStack(
new ResourceLocation("tiedup", "ball_gag")
);
case HANDS -> DataDrivenBondageItem.createStack(
new ResourceLocation("tiedup", "leather_mittens")
);
default -> ItemStack.EMPTY;
};
@@ -472,8 +471,8 @@ public class MasterPunishGoal extends Goal {
* Apply armbinder as punishment.
*/
private void applyTighten(ServerPlayer pet) {
ItemStack armbinder = new ItemStack(
ModItems.getBind(BindVariant.ARMBINDER)
ItemStack armbinder = DataDrivenBondageItem.createStack(
new ResourceLocation("tiedup", "armbinder")
);
// Mark as temporary
@@ -545,9 +544,9 @@ public class MasterPunishGoal extends Goal {
private void deactivateChoke() {
if (
!activeChokeCollar.isEmpty() &&
activeChokeCollar.getItem() instanceof ItemChokeCollar chokeCollar
CollarHelper.isChokeCollar(activeChokeCollar)
) {
chokeCollar.setChoking(activeChokeCollar, false);
CollarHelper.setChoking(activeChokeCollar, false);
}
this.activeChokeCollar = ItemStack.EMPTY;
this.chokeActiveTimer = 0;

View File

@@ -3,11 +3,8 @@ package com.tiedup.remake.entities.ai.master;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.dialogue.DialogueBridge;
import com.tiedup.remake.entities.EntityMaster;
import com.tiedup.remake.items.ModItems;
import com.tiedup.remake.items.base.BindVariant;
import com.tiedup.remake.items.base.BlindfoldVariant;
import com.tiedup.remake.items.base.GagVariant;
import com.tiedup.remake.items.base.MittensVariant;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import net.minecraft.resources.ResourceLocation;
import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.IV2BondageEquipment;
@@ -17,7 +14,6 @@ import java.util.List;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.ai.goal.Goal;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
/**
@@ -249,15 +245,18 @@ public class MasterRandomEventGoal extends Goal {
* Create a random accessory item for the given body region.
*/
private ItemStack createRandomAccessory(BodyRegionV2 region) {
Item item = switch (region) {
case EYES -> ModItems.getBlindfold(BlindfoldVariant.CLASSIC);
case MOUTH -> ModItems.getGag(GagVariant.BALL_GAG);
case HANDS -> ModItems.getMittens(MittensVariant.LEATHER);
default -> null;
return switch (region) {
case EYES -> DataDrivenBondageItem.createStack(
new ResourceLocation("tiedup", "classic_blindfold")
);
case MOUTH -> DataDrivenBondageItem.createStack(
new ResourceLocation("tiedup", "ball_gag")
);
case HANDS -> DataDrivenBondageItem.createStack(
new ResourceLocation("tiedup", "leather_mittens")
);
default -> ItemStack.EMPTY;
};
if (item == null) return ItemStack.EMPTY;
return new ItemStack(item);
}
/**
@@ -304,8 +303,8 @@ public class MasterRandomEventGoal extends Goal {
// Put pet in dogbind if not already tied
PlayerBindState bindState = PlayerBindState.getInstance(pet);
if (bindState != null && !bindState.isTiedUp()) {
ItemStack dogbind = new ItemStack(
ModItems.getBind(BindVariant.DOGBINDER)
ItemStack dogbind = DataDrivenBondageItem.createStack(
new ResourceLocation("tiedup", "dogbinder")
);
bindState.equip(BodyRegionV2.ARMS, dogbind);
}

View File

@@ -3,7 +3,7 @@ package com.tiedup.remake.entities.ai.master;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.dialogue.DialogueBridge;
import com.tiedup.remake.entities.EntityMaster;
import com.tiedup.remake.items.ItemChokeCollar;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.util.MessageDispatcher;
import com.tiedup.remake.v2.BodyRegionV2;
@@ -441,8 +441,8 @@ public class MasterTaskWatchGoal extends Goal {
if (bindState != null && bindState.hasCollar()) {
ItemStack collar = bindState.getEquipment(BodyRegionV2.NECK);
if (collar.getItem() instanceof ItemChokeCollar chokeCollar) {
chokeCollar.setChoking(collar, true);
if (CollarHelper.isChokeCollar(collar)) {
CollarHelper.setChoking(collar, true);
this.activeChokeCollar = collar;
this.chokeTimer = CHOKE_DURATION;
@@ -475,9 +475,9 @@ public class MasterTaskWatchGoal extends Goal {
private void deactivateChoke() {
if (
!activeChokeCollar.isEmpty() &&
activeChokeCollar.getItem() instanceof ItemChokeCollar chokeCollar
CollarHelper.isChokeCollar(activeChokeCollar)
) {
chokeCollar.setChoking(activeChokeCollar, false);
CollarHelper.setChoking(activeChokeCollar, false);
TiedUpMod.LOGGER.debug(
"[MasterTaskWatchGoal] {} deactivated choke",
master.getNpcName()

View File

@@ -1,7 +1,7 @@
package com.tiedup.remake.entities.ai.personality;
import com.tiedup.remake.entities.EntityDamsel;
import com.tiedup.remake.items.base.ItemBind;
import com.tiedup.remake.v2.bondage.BindModeHelper;
import com.tiedup.remake.personality.NpcCommand;
import com.tiedup.remake.personality.ToolMode;
import com.tiedup.remake.v2.BodyRegionV2;
@@ -506,7 +506,7 @@ public class NpcFollowCommandGoal extends Goal {
if (dist <= ATTACK_RANGE) {
// Try to capture using bind item
ItemStack bindItem = npc.getMainHandItem();
if (bindItem.getItem() instanceof ItemBind) {
if (BindModeHelper.isBindItem(bindItem)) {
// Apply bind to target
captureTarget.equip(BodyRegionV2.ARMS, bindItem.copy());

View File

@@ -1,8 +1,8 @@
package com.tiedup.remake.entities.ai.personality;
import com.tiedup.remake.entities.EntityDamsel;
import com.tiedup.remake.items.base.ItemBind;
import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.v2.bondage.BindModeHelper;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.personality.NpcCommand;
import com.tiedup.remake.personality.PersonalityState;
import com.tiedup.remake.v2.BodyRegionV2;
@@ -426,7 +426,7 @@ public class NpcGuardCommandGoal extends Goal {
NonNullList<ItemStack> inventory = npc.getNpcInventory();
for (int i = 0; i < inventory.size(); i++) {
ItemStack stack = inventory.get(i);
if (stack.getItem() instanceof ItemBind) {
if (BindModeHelper.isBindItem(stack)) {
// Apply bind to slave
slave.equip(BodyRegionV2.ARMS, stack.copy());
stack.shrink(1);
@@ -486,8 +486,8 @@ public class NpcGuardCommandGoal extends Goal {
private UUID getCollarOwnerUUID(EntityDamsel slave) {
if (!slave.hasCollar()) return null;
ItemStack collar = slave.getEquipment(BodyRegionV2.NECK);
if (collar.getItem() instanceof ItemCollar collarItem) {
java.util.List<UUID> owners = collarItem.getOwners(collar);
if (CollarHelper.isCollar(collar)) {
java.util.List<UUID> owners = CollarHelper.getOwners(collar);
if (!owners.isEmpty()) {
return owners.get(0);
}

View File

@@ -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<UUID> ownerUUIDs = collarItem.getOwners(collar);
if (com.tiedup.remake.v2.bondage.CollarHelper.isCollar(collar)) {
List<UUID> ownerUUIDs = com.tiedup.remake.v2.bondage.CollarHelper.getOwners(collar);
if (!ownerUUIDs.isEmpty()) {
// Check if any owner is nearby
List<Player> 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<UUID> ownerUUIDs = collarItem.getOwners(collar);
if (com.tiedup.remake.v2.bondage.CollarHelper.isCollar(collar)) {
List<UUID> ownerUUIDs = com.tiedup.remake.v2.bondage.CollarHelper.getOwners(collar);
if (!ownerUUIDs.isEmpty()) {
return ownerUUIDs.get(0);
}

View File

@@ -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);

View File

@@ -3,7 +3,7 @@ package com.tiedup.remake.entities.damsel.components;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.entities.AbstractTiedUpNpc;
import com.tiedup.remake.entities.BondageServiceHandler;
import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.state.ICaptor;
import com.tiedup.remake.state.IRestrainable;
import com.tiedup.remake.state.IRestrainableEntity;
@@ -565,9 +565,9 @@ public class DamselBondageManager implements IRestrainable {
ItemStack collar = getEquipment(BodyRegionV2.NECK);
if (collar.isEmpty()) return false;
if (!(collar.getItem() instanceof ItemCollar itemCollar)) return false;
if (!CollarHelper.isCollar(collar)) return false;
UUID cellId = itemCollar.getCellId(collar);
UUID cellId = CollarHelper.getCellId(collar);
if (cellId == null) return false;
// Get cell position from registry

View File

@@ -2,7 +2,7 @@ package com.tiedup.remake.entities.damsel.components;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.dialogue.EntityDialogueManager;
import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.personality.*;
import com.tiedup.remake.v2.BodyRegionV2;
import java.util.*;
@@ -180,8 +180,8 @@ public class DamselPersonalitySystem {
UUID masterUUID = null;
if (context.hasCollar()) {
ItemStack collar = context.getEquipment(BodyRegionV2.NECK);
if (collar.getItem() instanceof ItemCollar collarItem) {
List<UUID> owners = collarItem.getOwners(collar);
if (CollarHelper.isCollar(collar)) {
List<UUID> owners = CollarHelper.getOwners(collar);
if (!owners.isEmpty()) {
masterUUID = owners.get(0);
}

View File

@@ -4,7 +4,11 @@ import com.tiedup.remake.core.ModConfig;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.entities.AbstractTiedUpNpc;
import com.tiedup.remake.items.base.ILockable;
import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import com.tiedup.remake.v2.bondage.component.ComponentType;
import com.tiedup.remake.v2.bondage.component.GaggingComponent;
import com.tiedup.remake.v2.bondage.component.BlindingComponent;
import com.tiedup.remake.state.IRestrainableEntity;
import com.tiedup.remake.util.RestraintEffectUtils;
import com.tiedup.remake.util.TiedUpSounds;
@@ -112,19 +116,13 @@ public class NpcEquipmentManager {
public boolean hasGaggingEffect() {
ItemStack gag = getCurrentGag();
if (gag.isEmpty()) return false;
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;
return (
blindfold.getItem() instanceof
com.tiedup.remake.items.base.IHasBlindingEffect
);
return DataDrivenBondageItem.getComponent(blindfold, ComponentType.BLINDING, BlindingComponent.class) != null;
}
public boolean hasKnives() {
@@ -768,8 +766,8 @@ public class NpcEquipmentManager {
public boolean isCollarOwner(Player player) {
if (!hasCollar()) return true;
ItemStack collar = getCurrentCollar();
if (!(collar.getItem() instanceof ItemCollar collarItem)) return true;
return collarItem.isOwner(collar, player);
if (!CollarHelper.isCollar(collar)) return true;
return CollarHelper.isOwner(collar, player);
}
// COERCION

View File

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

View File

@@ -6,7 +6,7 @@ import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.dialogue.EntityDialogueManager;
import com.tiedup.remake.entities.EntityKidnapper;
import com.tiedup.remake.entities.ai.kidnapper.KidnapperState;
import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.prison.PrisonerManager;
import com.tiedup.remake.prison.PrisonerRecord;
import com.tiedup.remake.prison.PrisonerState;
@@ -189,9 +189,9 @@ public class KidnapperCaptiveManager {
// If target has collar, verify we own it
if (target.hasCollar()) {
ItemStack collar = target.getEquipment(BodyRegionV2.NECK);
if (collar.getItem() instanceof ItemCollar collarItem) {
if (CollarHelper.isCollar(collar)) {
// Check if THIS kidnapper is owner
if (!collarItem.getOwners(collar).contains(host.getUUID())) {
if (!CollarHelper.getOwners(collar).contains(host.getUUID())) {
TiedUpMod.LOGGER.debug(
"[KidnapperCaptiveManager] {} can't capture {} - collar owned by someone else",
host.getNpcName(),

View File

@@ -7,7 +7,7 @@ import com.tiedup.remake.entities.EntityKidnapper;
import com.tiedup.remake.entities.EntityKidnapperElite;
import com.tiedup.remake.entities.ai.kidnapper.KidnapperState;
import com.tiedup.remake.items.ModItems;
import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.prison.PrisonerManager;
import com.tiedup.remake.prison.PrisonerRecord;
import com.tiedup.remake.state.IBondageState;
@@ -206,8 +206,8 @@ public class KidnapperTargetSelector {
// Self-collared entities (exploit) are treated as uncolllared — kidnappers ignore self-collars
if (state.hasCollar()) {
ItemStack collar = state.getEquipment(BodyRegionV2.NECK);
if (collar.getItem() instanceof ItemCollar collarItem) {
java.util.List<UUID> owners = collarItem.getOwners(collar);
if (CollarHelper.isCollar(collar)) {
java.util.List<UUID> owners = CollarHelper.getOwners(collar);
// Filter out self-collar (owner == wearer = exploit)
java.util.List<UUID> realOwners = owners
.stream()
@@ -312,8 +312,8 @@ public class KidnapperTargetSelector {
// Other kidnappers' collars are fair game — kidnapper can steal from kidnapper
if (state != null && state.hasCollar()) {
ItemStack collar = state.getEquipment(BodyRegionV2.NECK);
if (collar.getItem() instanceof ItemCollar collarItem) {
java.util.List<UUID> owners = collarItem.getOwners(collar);
if (CollarHelper.isCollar(collar)) {
java.util.List<UUID> owners = CollarHelper.getOwners(collar);
if (!owners.isEmpty() && !owners.contains(host.getUUID())) {
// Check if any owner is a DIFFERENT player (not self-collared, not a kidnapper)
if (host.level() instanceof ServerLevel sl) {

View File

@@ -4,7 +4,7 @@ import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.entities.EntityMaster;
import com.tiedup.remake.entities.ai.master.MasterRandomEventGoal;
import com.tiedup.remake.entities.ai.master.MasterState;
import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.minigame.StruggleSessionManager;
import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.util.KidnappedHelper;
@@ -119,29 +119,34 @@ 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
if (
chokeCollar.getItem() instanceof
com.tiedup.remake.items.ItemChokeCollar collar
com.tiedup.remake.items.base.IHasResistance resistable
) {
collar.setCurrentResistance(chokeCollar, PET_COLLAR_RESISTANCE);
collar.setLocked(chokeCollar, true);
collar.setLockable(chokeCollar, false); // Cannot be lockpicked
collar.setCanBeStruggledOut(chokeCollar, true); // Can struggle, but very hard
// Add this Master as owner
collar.addOwner(chokeCollar, master.getUUID(), master.getNpcName());
// Set NBT flag for pet play mode
chokeCollar.getOrCreateTag().putBoolean("petPlayMode", true);
chokeCollar
.getOrCreateTag()
.putUUID("masterUUID", master.getUUID());
resistable.setCurrentResistance(chokeCollar, PET_COLLAR_RESISTANCE);
resistable.setCanBeStruggledOut(chokeCollar, true); // Can struggle, but very hard
}
if (
chokeCollar.getItem() instanceof
com.tiedup.remake.items.base.ILockable lockable
) {
lockable.setLocked(chokeCollar, true);
lockable.setLockable(chokeCollar, false); // Cannot be lockpicked
}
// Add this Master as owner
CollarHelper.addOwner(chokeCollar, master.getUUID(), master.getNpcName());
// Set NBT flag for pet play mode
CollarHelper.setPetPlayMode(chokeCollar, true);
chokeCollar
.getOrCreateTag()
.putUUID("masterUUID", master.getUUID());
// Replace any existing collar (force removal) with the choke collar
state.replaceEquipment(BodyRegionV2.NECK, chokeCollar, true);
@@ -186,9 +191,9 @@ public class MasterPetManager {
}
// Unlock the collar
if (collarStack.getItem() instanceof ItemCollar collar) {
collar.setLocked(collarStack, false);
collar.setLockable(collarStack, true);
if (collarStack.getItem() instanceof com.tiedup.remake.items.base.ILockable lockable) {
lockable.setLocked(collarStack, false);
lockable.setLockable(collarStack, true);
}
}
@@ -242,8 +247,8 @@ public class MasterPetManager {
);
if (collarStack.isEmpty()) return;
if (collarStack.getItem() instanceof ItemCollar collar) {
collar.setCurrentResistance(collarStack, PET_COLLAR_RESISTANCE);
if (collarStack.getItem() instanceof com.tiedup.remake.items.base.IHasResistance resistable) {
resistable.setCurrentResistance(collarStack, PET_COLLAR_RESISTANCE);
TiedUpMod.LOGGER.debug(
"[MasterPetManager] Reset collar resistance for {}",
pet.getName().getString()

View File

@@ -4,7 +4,7 @@ import com.tiedup.remake.cells.CampLifecycleManager;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.entities.EntityMaid;
import com.tiedup.remake.entities.EntitySlaveTrader;
import com.tiedup.remake.items.base.ItemBind;
import com.tiedup.remake.v2.bondage.BindModeHelper;
import java.util.UUID;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
@@ -40,7 +40,7 @@ public class CampNpcProtectionHandler {
// Check if player is holding restraint item
ItemStack heldItem = player.getItemInHand(event.getHand());
if (!(heldItem.getItem() instanceof ItemBind)) return;
if (!BindModeHelper.isBindItem(heldItem)) return;
// Check if target is trader or maid with active camp
UUID campId = null;

View File

@@ -3,8 +3,8 @@ package com.tiedup.remake.events.captivity;
import com.tiedup.remake.core.SettingsAccessor;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.entities.LeashProxyEntity;
import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.state.IPlayerLeashAccess;
import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.util.KidnappedHelper;
@@ -146,9 +146,9 @@ public class PlayerEnslavementHandler {
ItemStack collar = slaveKidnappedState.getEquipment(
BodyRegionV2.NECK
);
if (collar.getItem() instanceof ItemCollar collarItem) {
if (CollarHelper.isCollar(collar)) {
if (
collarItem
CollarHelper
.getOwners(collar)
.contains(master.getUUID())
) {

View File

@@ -4,8 +4,8 @@ import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.entities.EntityKidnapper;
import com.tiedup.remake.entities.EntityMaid;
import com.tiedup.remake.entities.EntitySlaveTrader;
import com.tiedup.remake.items.base.ItemBind;
import com.tiedup.remake.prison.LaborRecord;
import com.tiedup.remake.v2.bondage.BindModeHelper;
import com.tiedup.remake.prison.PrisonerManager;
import com.tiedup.remake.prison.PrisonerRecord;
import com.tiedup.remake.prison.PrisonerState;
@@ -145,7 +145,7 @@ public class LaborAttackPunishmentHandler {
// Check if player is holding a restraint item (rope, chain, etc.)
ItemStack heldItem = serverPlayer.getItemInHand(hand);
if (!(heldItem.getItem() instanceof ItemBind)) {
if (!BindModeHelper.isBindItem(heldItem)) {
return; // Not a restraint item
}

View File

@@ -2,8 +2,8 @@ package com.tiedup.remake.events.restriction;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.entities.EntityMaster;
import com.tiedup.remake.items.ItemChokeCollar;
import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.blocks.PetBedManager;
import net.minecraft.core.BlockPos;
@@ -284,8 +284,8 @@ public class PetPlayRestrictionHandler {
if (bindState == null || !bindState.hasCollar()) return;
ItemStack collar = bindState.getEquipment(BodyRegionV2.NECK);
if (collar.getItem() instanceof ItemChokeCollar chokeCollar) {
if (chokeCollar.isChoking(collar)) {
if (CollarHelper.isChokeCollar(collar)) {
if (CollarHelper.isChoking(collar)) {
// Apply ChokeEffect (short duration, re-applied each active tick)
if (
!player.hasEffect(

View File

@@ -3,8 +3,8 @@ package com.tiedup.remake.events.restriction;
import com.tiedup.remake.core.SettingsAccessor;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.entities.EntityKidnapper;
import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.minigame.StruggleSessionManager;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.network.ModNetwork;
import com.tiedup.remake.network.personality.PacketSlaveBeingFreed;
import com.tiedup.remake.state.IBondageState;
@@ -642,14 +642,11 @@ public class RestraintTaskTickHandler {
if (!(slave.level() instanceof ServerLevel serverLevel)) return;
ItemStack collar = slave.getEquipment(BodyRegionV2.NECK);
if (
collar.isEmpty() ||
!(collar.getItem() instanceof ItemCollar collarItem)
) {
if (collar.isEmpty() || !CollarHelper.isCollar(collar)) {
return;
}
List<UUID> owners = collarItem.getOwners(collar);
List<UUID> owners = CollarHelper.getOwners(collar);
if (owners.isEmpty()) return;
// Create alert packet

View File

@@ -40,7 +40,11 @@ public class AnvilEventHandler {
// Check if item can have a padlock attached (tape, slime, vine, web cannot)
if (!lockable.canAttachPadlock()) {
return; // Item type cannot have padlock
return; // Item type cannot have padlock (V1)
}
// V2 data-driven items: check definition's can_attach_padlock field
if (!com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem.canAttachPadlockTo(left)) {
return;
}
// Item must not already have a padlock attached

View File

@@ -4,9 +4,11 @@ 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;
import com.tiedup.remake.v2.bondage.component.GaggingComponent;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import com.tiedup.remake.util.KidnappedHelper;
import com.tiedup.remake.util.TiedUpUtils;
import com.tiedup.remake.v2.BodyRegionV2;
@@ -54,12 +56,19 @@ public class ChatEventHandler {
BodyRegionV2.MOUTH
);
if (
!gagStack.isEmpty() &&
gagStack.getItem() instanceof ItemGag gagItem
) {
// V2: check gagging component
GaggingComponent gaggingComp = DataDrivenBondageItem.getComponent(
gagStack, ComponentType.GAGGING, GaggingComponent.class);
boolean isGagItem = gaggingComp != null;
if (!gagStack.isEmpty() && isGagItem) {
String originalMessage = event.getRawText();
GagMaterial material = gagItem.getGagMaterial();
// V2: get material from component, V1 fallback: from ItemGag
GagMaterial material = null;
if (gaggingComp != null) {
material = gaggingComp.getMaterial();
}
// material stays null if no component; GagTalkManager handles null → CLOTH fallback
// 1. Process the message through our GagTalkManager V2
Component muffledMessage = GagTalkManager.processGagMessage(
@@ -83,7 +92,9 @@ public class ChatEventHandler {
.append("> ")
.append(muffledMessage);
double range = material.getTalkRange();
double range = material != null
? material.getTalkRange()
: (gaggingComp != null ? gaggingComp.getRange() : 10.0);
List<ServerPlayer> nearbyPlayers =
TiedUpUtils.getPlayersAround(

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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();
}
}

View File

@@ -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.
*
* <p>Special feature: Can be put in "choke mode" which applies a drowning effect.</p>
* <p>Used by Masters for punishment. The effect simulates choking by reducing air supply,
* which triggers drowning damage if left active for too long.</p>
*
* <p><b>Mechanics:</b></p>
* <ul>
* <li>When choking is active, the wearer's air supply decreases rapidly</li>
* <li>This creates the drowning effect (damage and bubble particles)</li>
* <li>Masters should deactivate the choke before the pet dies</li>
* <li>The choke is controlled by the Master's punishment system</li>
* </ul>
*
* @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<Component> 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;
}
}

View File

@@ -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
);
}
}

View File

@@ -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,

View File

@@ -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.
*
* <p>Mechanics:</p>
* <ul>
* <li><b>Safe Zones:</b> Can store multiple coordinates (SafeSpots) where the wearer is allowed to be.</li>
* <li><b>Auto-Shock:</b> If the wearer is outside ALL active safe zones, they are shocked at intervals.</li>
* <li><b>Master Warning:</b> Masters receive an alert message when a safe zone violation is detected.</li>
* <li><b>Public Tracking:</b> If enabled, allows anyone with a Locator to see distance and direction.</li>
* </ul>
*/
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<Component> 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<SafeSpot> 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<SafeSpot> getSafeSpots(ItemStack stack) {
List<SafeSpot> 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
);
}
}
}

View File

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

View File

@@ -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";
}
}

View File

@@ -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);

View File

@@ -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<UUID> owners = shockCollar.getOwners(collar);
List<UUID> owners = com.tiedup.remake.v2.bondage.CollarHelper.getOwners(collar);
for (UUID ownerId : owners) {
ServerPlayer owner = player
.getServer()

View File

@@ -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";
}
}

View File

@@ -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.
*
* <p>Mechanics:</p>
* <ul>
* <li><b>Remote Shocking:</b> Can be triggered by anyone holding a linked Shocker Controller.</li>
* <li><b>Struggle Penalty:</b> If locked, has a chance to shock the wearer during struggle attempts, interrupting them.</li>
* <li><b>Public Mode:</b> Can be set to public mode, allowing anyone to shock the wearer even if they aren't the owner.</li>
* </ul>
*/
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<Component> 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<ItemStack> 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);
}
}

View File

@@ -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.
*
*
* <p>Mechanics:</p>
* <ul>
* <li><b>Self-Triggering:</b> Has an internal timer stored in NBT that shocks the entity when it reaches 0.</li>
* <li><b>Unstruggable:</b> By default, cannot be escaped via struggle mechanics (requires key).</li>
* </ul>
*/
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;
}
}

View File

@@ -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);
}

View File

@@ -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(

View File

@@ -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<BindVariant, RegistryObject<Item>> 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<GagVariant, RegistryObject<Item>> 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<Item> BALL_GAG_3D = ITEMS.register(
"ball_gag_3d",
ItemBallGag3D::new
);
/**
* All blindfold items (2 variants via BlindfoldVariant enum)
*/
public static final Map<BlindfoldVariant, RegistryObject<Item>> BLINDFOLDS =
registerAllBlindfolds();
/**
* All earplugs items (1 variant via EarplugsVariant enum)
*/
public static final Map<EarplugsVariant, RegistryObject<Item>> 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<KnifeVariant, RegistryObject<Item>> KNIVES =
registerAllKnives();
/**
* All mittens items (1 variant via MittensVariant enum)
*/
public static final Map<MittensVariant, RegistryObject<Item>> 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<Item> MEDICAL_GAG = ITEMS.register(
"medical_gag",
ItemMedicalGag::new
);
// Hood - combo item
public static final RegistryObject<Item> HOOD = ITEMS.register(
"hood",
ItemHood::new
);
// Collars - complex logic
public static final RegistryObject<Item> CLASSIC_COLLAR = ITEMS.register(
"classic_collar",
ItemClassicCollar::new
);
public static final RegistryObject<Item> SHOCK_COLLAR = ITEMS.register(
"shock_collar",
ItemShockCollar::new
);
public static final RegistryObject<Item> SHOCK_COLLAR_AUTO = ITEMS.register(
"shock_collar_auto",
ItemShockCollarAuto::new
);
public static final RegistryObject<Item> GPS_COLLAR = ITEMS.register(
"gps_collar",
ItemGpsCollar::new
);
// Choke Collar - Pet play collar used by Masters
public static final RegistryObject<Item> CHOKE_COLLAR = ITEMS.register(
"choke_collar",
ItemChokeCollar::new
);
// Tools with complex behavior
public static final RegistryObject<Item> 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<Item> ADMIN_WAND = ITEMS.register(
"admin_wand",
ItemAdminWand::new
);
// Cell Key - Universal key for iron bar doors
public static final RegistryObject<Item> 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<Item> TOKEN = ITEMS.register(
"token",
ItemToken::new
@@ -252,72 +162,6 @@ public class ModItems {
// ========== FACTORY METHODS ==========
private static Map<BindVariant, RegistryObject<Item>> registerAllBinds() {
Map<BindVariant, RegistryObject<Item>> 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<GagVariant, RegistryObject<Item>> registerAllGags() {
Map<GagVariant, RegistryObject<Item>> 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<Item>
> registerAllBlindfolds() {
Map<BlindfoldVariant, RegistryObject<Item>> 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<Item>
> registerAllEarplugs() {
Map<EarplugsVariant, RegistryObject<Item>> 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<KnifeVariant, RegistryObject<Item>> registerAllKnives() {
Map<KnifeVariant, RegistryObject<Item>> map = new EnumMap<>(
KnifeVariant.class
@@ -333,62 +177,8 @@ public class ModItems {
return map;
}
private static Map<
MittensVariant,
RegistryObject<Item>
> registerAllMittens() {
Map<MittensVariant, RegistryObject<Item>> 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();
}
}

View File

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

View File

@@ -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.
*
* <p><b>Issue #12 fix:</b> 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;
}
}

View File

@@ -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.
*
* <p><b>Issue #12 fix:</b> 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;
}
}

View File

@@ -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.
*
* <p><b>Issue #12 fix:</b> 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;
}
}

View File

@@ -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.
*
* <p>Note: ItemMedicalGag is NOT included here because it implements
* IHasBlindingEffect (combo item with special behavior).
*
* <p><b>Issue #12 fix:</b> 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;
}
}

View File

@@ -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;
}
}

View File

@@ -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.
*
* <p><b>Issue #12 fix:</b> 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;
}
}

View File

@@ -1,33 +0,0 @@
package com.tiedup.remake.items.base;
/**
* Marker interface for items that have a blinding visual effect.
*
* <p>Items implementing this interface will:
* <ul>
* <li>Apply a screen overlay when worn (client-side)</li>
* <li>Reduce the player's visibility</li>
* <li>Potentially disable certain UI elements</li>
* </ul>
*
* <h2>Usage</h2>
* <pre>{@code
* if (blindfold.getItem() instanceof IHasBlindingEffect) {
* // Apply blinding overlay
* renderBlindingOverlay();
* }
* }</pre>
*
* <h2>Implementations</h2>
* <ul>
* <li>{@link ItemBlindfold} - All blindfold items</li>
* </ul>
*
* <p>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
}

View File

@@ -1,33 +0,0 @@
package com.tiedup.remake.items.base;
/**
* Marker interface for items that have a gagging (speech muffling) effect.
*
* <p>Items implementing this interface will:
* <ul>
* <li>Convert chat messages to "mmpphh" sounds</li>
* <li>Play gagged speech sounds</li>
* <li>Potentially block certain chat commands</li>
* </ul>
*
* <h2>Usage</h2>
* <pre>{@code
* if (gag.getItem() instanceof IHasGaggingEffect) {
* // Convert chat message to gagged speech
* message = GagTalkConverter.convert(message);
* }
* }</pre>
*
* <h2>Implementations</h2>
* <ul>
* <li>{@link ItemGag} - Ball gags, tape gags, cloth gags, etc.</li>
* </ul>
*
* <p>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
}

View File

@@ -22,10 +22,8 @@ import net.minecraft.world.item.ItemStack;
*
* <h2>Implementations</h2>
* <ul>
* <li>{@link ItemBind} - Ropes, chains, straitjackets</li>
* <li>{@link ItemGag} - Ball gags, tape gags, cloth gags</li>
* <li>{@link ItemBlindfold} - Blindfolds</li>
* <li>{@link ItemCollar} - Collars (special: may not be struggleable)</li>
* <li>{@link com.tiedup.remake.v2.bondage.items.AbstractV2BondageItem} - All V2 bondage items</li>
* <li>{@link com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem} - Data-driven items (resistance via ResistanceComponent)</li>
* </ul>
*
* <p>Based on original IHasResistance.java from 1.12.2

View File

@@ -24,11 +24,8 @@ import org.jetbrains.annotations.Nullable;
*
* <h2>Implementations</h2>
* <ul>
* <li>{@link ItemGag} - Gags can be locked</li>
* <li>{@link ItemBlindfold} - Blindfolds can be locked</li>
* <li>{@link ItemCollar} - Collars can be locked</li>
* <li>{@link ItemEarplugs} - Earplugs can be locked</li>
* <li>ItemBind - Binds can be locked (future)</li>
* <li>{@link com.tiedup.remake.v2.bondage.items.AbstractV2BondageItem} - All V2 bondage items</li>
* <li>{@link com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem} - Data-driven items (via AbstractV2BondageItem)</li>
* </ul>
*
* <h2>NBT Storage</h2>

View File

@@ -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.
*
* <p>Implements {@link IHasResistance} for the struggle/escape system.
* <p>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<String, String> 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<ItemStack> 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<Component> 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
}

View File

@@ -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<Component> 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
}

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More