73 Commits

Author SHA1 Message Date
NotEvil
6d56024c7e fix(UC-02): LOW review items — anchored path strip + corner offset doc
- Use substring instead of replace for path cleanup (anchored, no double-strip risk)
- Document that corner_decorations ignores x/z offsets (by design)
2026-04-16 02:32:50 +02:00
NotEvil
8823c671d7 fix(UC-02): clean theme IDs — strip directory prefix and .json suffix
RISK-003: Definition IDs were "tiedup:tiedup_room_themes/oubliette.json"
instead of clean "tiedup:oubliette". Now derived from filename only.
2026-04-16 01:54:03 +02:00
NotEvil
6d9d6b4b81 fix(UC-02): arch review — furniture x/z offsets + ceiling chain fidelity
- Add x_offset/z_offset to PositionedBlock for multi-block furniture clusters
- Furniture items now spread across 2-3 positions (matching original Java code)
- Offsets multiplied by inward direction for correct corner orientation
- Fix has_ceiling_chain: only oubliette has ceiling chains (was true for all 6)
- Fix firstCornerSpecial: oubliette cauldron uses x/z offset +1 (inward diagonal)
- Update parser to read x_offset/z_offset (defaults to 0)
2026-04-16 01:52:31 +02:00
NotEvil
706172fb9a fix(UC-02): QA review — crypt bottom_row + ice decorations fidelity
- BUG-001: CRYPT bottom_row had unreachable mossy_stone_bricks (same f variable
  makes mossy_cobblestone guard trigger first). Fixed weights: 0.20/0.10/0.70
- BUG-003: ICE ceiling ice stalactites (y_offset=10) were missing from JSON
- BUG-002: ICE snow corner layers use middle value (3) as compromise since
  DecorationConfig.PositionedBlock doesn't support random_property
2026-04-16 01:45:04 +02:00
NotEvil
3aaf92b788 feat(UC-02): data-driven room themes — 6 JSON + consumer migration + cleanup
Phase 3: Extract 6 themes into JSON (oubliette, inferno, crypt, ice, sculk, sandstone)
Phase 4: Migrate HangingCagePiece to use RoomThemeRegistry.pickRandomOrFallback()
  - Move 7 shared static methods from RoomTheme into HangingCagePiece
  - Replace per-enum placeDecorations() with generic DecorationConfig-based placement
Phase 5: Delete RoomTheme.java (-1368L)
2026-04-16 01:39:40 +02:00
NotEvil
69f52eacf3 feat(UC-02): data-driven room theme infrastructure (Phase 1+2)
- BlockPalette: weighted random block selection with condition variants
- RoomThemeDefinition: immutable record with wallBlock/floorBlock/etc convenience API
- DecorationConfig: positioned block records for theme-specific decorations
- RoomThemeRegistry: volatile atomic snapshot + pickRandom(weight-based)
- RoomThemeParser: JSON parsing with BlockStateParser + random_property expansion
- RoomThemeReloadListener: scans data/<ns>/tiedup_room_themes/*.json
- Register listener in TiedUpMod.onAddReloadListeners()
2026-04-16 01:30:51 +02:00
a4fc05b503 Merge pull request 'chore/audit-s02-s05-state-cleanup' (#13) from chore/audit-s02-s05-state-cleanup into develop
Reviewed-on: #13
2026-04-15 14:48:42 +00:00
NotEvil
7444853840 fix(S-02): remove unused host field from PlayerMovement (review) 2026-04-15 16:48:22 +02:00
NotEvil
22d79a452b refactor(S-02/S-05): extract PlayerMovement component + fix thread safety
- Extract 11 movement fields from PlayerBindState into PlayerMovement component
- Replace volatile isStruggling/struggleStartTick pair with atomic StruggleSnapshot record
- Remove 5+2 misleading synchronized keywords (different monitors, all server-thread-only)
- Update all 36 MovementStyleManager field accesses to use getMovement() getters/setters
2026-04-15 16:48:22 +02:00
70f85b58a6 Merge pull request 'chore/audit-c01-i18n' (#12) from chore/audit-c01-i18n into develop
Reviewed-on: #12
2026-04-15 14:48:07 +00:00
NotEvil
c34bac11b0 fix(C-01): review fixes — missing keys, duplicate, GPS shock i18n
- Add missing keephead_enabled/disabled translation keys (BUG-001)
- Remove duplicate gui.tiedup.close key at line 403 (RISK-004)
- Fix GPS shock: use null addon + send GPS_ZONE_VIOLATION separately (RISK-001)
2026-04-15 14:08:41 +02:00
NotEvil
fa5cfb913c feat(C-01): i18n main commands — 148 translatable keys
Phase 3: Migrate Component.literal() in all remaining command files.
- NPCCommand (34), CellCommand (33), SocialCommand (16), CollarCommand (25),
  KeyCommand (18), BountyCommand (6), KidnapSetCommand (2), CaptivityDebugCommand (7),
  InventorySubCommand (3), TestAnimSubCommand (2), MasterTestSubCommand (7), DebtSubCommand (8)
- Strip all section sign color codes, use .withStyle(ChatFormatting)
- 148 new keys in en_us.json (command.tiedup.*)
- Debug/dynamic strings intentionally kept as literal
2026-04-15 13:54:26 +02:00
NotEvil
70965c2dda feat(C-01): i18n subcommands — 33 translatable keys
Phase 2: Migrate all Component.literal() in 5 subcommand files.
- BindCommands, GagCommands, BlindfoldCommands, CollarCommands, AccessoryCommands
- Strip \u00a7a section signs, use .withStyle(ChatFormatting.GREEN)
- Add 33 keys to en_us.json (command.tiedup.*)
- Shared error key: command.tiedup.error.no_state
2026-04-15 13:28:02 +02:00
NotEvil
0662739fe0 feat(C-01): i18n SystemMessageManager — 83 translatable keys
Phase 1: Core system message migration to Component.translatable().
- Replace getMessageTemplate() hardcoded strings with getTranslationKey() key derivation
- All send methods now use Component.translatable() with positional args
- Add 83 keys to en_us.json (msg.tiedup.system.*)
- Add sendTranslatable() convenience for external callers with string args
- Migrate 3 external getTemplate() callers (PlayerShockCollar, CellRegistryV2)
- Add resistance_suffix key for sendWithResistance()
2026-04-15 13:24:05 +02:00
ac72f6aae7 Merge pull request 'chore/quickwin-fix' (#11) from chore/quickwin-fix into develop
Reviewed-on: #11
2026-04-15 09:22:08 +00:00
NotEvil
d3bdb026f3 fix: restore missing /tiedup tie command registration (review) 2026-04-15 11:14:28 +02:00
NotEvil
c1e1f56058 refactor: split BondageSubCommand 1207L → 5 focused files (UC-01)
- BindCommands.java: tie, untie (156L)
- GagCommands.java: gag, ungag (140L)
- BlindfoldCommands.java: blindfold, unblind (142L)
- CollarCommands.java: collar, takecollar, enslave, free (288L)
- AccessoryCommands.java: putearplugs, takeearplugs, putclothes,
  takeclothes, fullyrestrain, adjust (469L)
- BondageSubCommand.java: thin delegator (19L)

Zero logic changes — purely mechanical code move.
2026-04-15 11:09:48 +02:00
NotEvil
f945e9449b feat(D-01/E): quickwins — debug toggle, HumanChairHelper move, collar equip
Q1: Remove F9 debug toggle from GltfAnimationApplier + delete dead
    GltfRenderLayer + remove keybind registration

Q2: Move HumanChairHelper from state/ to util/ — pure utility with
    no state dependency. 7 import updates.

Q3: Wire NECK collar equip flow in DataDrivenBondageItem:
    - Target must be tied up (V1 rule preserved)
    - Distance + line-of-sight validation
    - Owner added to NBT before equip via CollarHelper.addOwner()
    - V2EquipmentHelper handles conflict resolution
    - ModSounds.COLLAR_PUT played on success
    - OwnershipComponent.onEquipped registers in CollarRegistry
2026-04-15 10:57:01 +02:00
cc0ce89de5 Merge pull request 'feature/d01-branch-e-resistance' (#10) from feature/d01-branch-e-resistance into develop
Reviewed-on: #10
2026-04-15 01:45:58 +00:00
NotEvil
db407ee68f fix(D-01/E): padlock resistance double-count + knife progress persist (review)
BUG-001: PacketV2StruggleStart only adds padlock resistance bonus on
first session (when resistance >= base). Interrupted sessions that
persisted combined resistance no longer re-add the bonus, preventing
infinite resistance inflation.

BUG-002: PacketV2LockToggle UNLOCK clears knifeCutProgress and
accessoryStruggleResistance NBT keys, preventing partial knife-cut
progress from persisting across lock/unlock cycles.
2026-04-15 03:44:12 +02:00
NotEvil
d6bb030ad7 feat(D-01/E): resistance & lock system rework (E1-E7)
E1: Initialize currentResistance in NBT at equip time from
    ResistanceComponent — eliminates MAX-scan fallback bug

E2: BuiltInLockComponent for organic items (already committed)

E3: canStruggle refactor — new model:
    - ARMS: always struggle-able (no lock gating)
    - Non-ARMS: only if locked OR built-in lock
    - Removed dead isItemLocked() from StruggleState + overrides

E4: canUnequip already handled by BuiltInLockComponent.blocksUnequip()
    via ComponentHolder delegation

E5: Help/assist mechanic deferred (needs UI design)

E6: Removed lock resistance from ILockable (5 methods + NBT key deleted)
    - GenericKnife: new knifeCutProgress NBT for cutting locks
    - StruggleAccessory: accessoryStruggleResistance NBT replaces lock resistance
    - PacketV2StruggleStart: uses config-based padlock resistance
    - All lock/unlock packets cleaned of initializeLockResistance/clearLockResistance

E7: Fixed 3 pre-existing bugs:
    - B2: DataDrivenItemRegistry.clear() synchronized on RELOAD_LOCK
    - B3: V2TyingPlayerTask validates heldStack before equip (prevents duplication)
    - B5: EntityKidnapperMerchant.remove() cleans playerToMerchant map (memory leak)
2026-04-15 03:23:49 +02:00
NotEvil
199bf00aef feat(D-01/E): BuiltInLockComponent for organic items (E2)
- New BuiltInLockComponent: blocksUnequip() returns true (permanent lock)
- ComponentType: add BUILT_IN_LOCK enum value
- 8 organic item JSONs updated (slime, vine, web, tape variants)
- DataDrivenBondageItem: add hasBuiltInLock() static helper
2026-04-15 03:11:15 +02:00
4a3ff438c2 Merge pull request 'feature/d01-branch-d-cleanup' (#9) from feature/d01-branch-d-cleanup into develop
Reviewed-on: #9
2026-04-15 00:54:11 +00:00
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
NotEvil
7bd840705a docs: add components section to ARTIST_GUIDE.md
Documents the 8 available gameplay components (lockable, resistance,
gagging, blinding, shock, gps, choking, adjustable) with config fields,
examples (GPS shock collar, adjustable blindfold), and usage tips.
2026-04-14 02:51:37 +02:00
NotEvil
90bc890b95 fix(D-01): synchronize reload paths and capture snapshot locally (RISK-001, RISK-002) 2026-04-14 02:46:09 +02:00
NotEvil
185ac63a44 feat(D-01): implement 5 remaining components (blinding, shock, gps, choking, adjustable) 2026-04-14 02:37:14 +02:00
NotEvil
dcc8493e5e fix(D-01): pre-built map for O(1) ComponentType.fromKey() lookup (RISK-005)
Replace linear values() scan with a static unmodifiable HashMap lookup.
While only 3 entries currently exist, this establishes the correct
pattern for when more component types are added.
2026-04-14 02:29:46 +02:00
NotEvil
bfcc20d242 fix(D-01): compact constructor defaults null componentConfigs to empty (RISK-004)
Add compact constructor to DataDrivenItemDefinition that defaults null
componentConfigs to Map.of(). This makes the field guaranteed non-null,
allowing removal of null checks in hasComponent() and
DataDrivenItemRegistry.buildComponentHolders().
2026-04-14 02:29:25 +02:00
NotEvil
3a81bb6e12 fix(D-01): clamp component config values to valid ranges (RISK-003)
- LockableComponent: lock_resistance clamped to >= 0
- ResistanceComponent: base resistance clamped to >= 0
- GaggingComponent: comprehension clamped to [0.0, 1.0], range to >= 0.0

Prevents nonsensical negative values from malformed JSON configs.
2026-04-14 02:28:42 +02:00
NotEvil
bb589d44f8 fix(D-01): warn on non-object component config, deep-copy configs (RISK-001, RISK-002)
- Deep-copy JsonObject configs via deepCopy() before storing in the
  definition to prevent external mutation of the parsed JSON tree
- Log a warning when a component config value is not a JsonObject,
  making misconfigured JSON easier to diagnose
2026-04-14 02:27:59 +02:00
NotEvil
456335e0dd fix(D-01): wire LockableComponent.lockResistance via getItemLockResistance() (BUG-003)
- Remove redundant blocksUnequip() from LockableComponent since
  AbstractV2BondageItem.canUnequip() already checks ILockable.isLocked()
- Add DataDrivenBondageItem.getItemLockResistance(ItemStack) that reads
  the per-item lock resistance from the LockableComponent, falling back
  to the global config value when absent
2026-04-14 02:27:37 +02:00
NotEvil
bb209bcd8e fix(D-01): remove dead onWornTick() until V2 tick mechanism exists (BUG-002)
Remove onWornTick() from IItemComponent (default method) and
ComponentHolder (aggregate method). No V2 tick caller invokes these,
so they create a broken contract. Can be re-added when a tick
mechanism is implemented.
2026-04-14 02:26:31 +02:00
NotEvil
1327e3bfc3 fix(D-01): atomic snapshot for registry to prevent torn reads (BUG-001)
Replace two separate volatile fields (DEFINITIONS, COMPONENT_HOLDERS)
with a single RegistrySnapshot record swapped atomically. This prevents
race conditions where a reader thread could see new definitions paired
with stale/empty component holders between the two volatile writes.
2026-04-14 02:25:57 +02:00
NotEvil
dbacef66d5 feat(D-01): add test_component_gag.json demonstrating component system
JSON item using all 3 implemented components: lockable (lock_resistance: 200),
resistance (base: 80), and gagging (comprehension: 0.15, range: 8.0).
2026-04-14 02:03:50 +02:00
NotEvil
231522c68e feat(D-01): implement GaggingComponent with comprehension and range 2026-04-14 02:01:50 +02:00
NotEvil
84f4c3a53f feat(D-01): implement ResistanceComponent, improve stack-aware resistance lookup 2026-04-14 02:01:46 +02:00
NotEvil
caeb4469b1 feat(D-01): implement LockableComponent with configurable lock resistance 2026-04-14 02:01:41 +02:00
NotEvil
3a1f401ccf feat(D-01): delegate DataDrivenBondageItem lifecycle to components
Override onEquipped(), onUnequipped(), and canUnequip() in
DataDrivenBondageItem to delegate to the item's ComponentHolder.
The canUnequip() override preserves the existing lock check from
AbstractV2BondageItem via super.canUnequip().

Add a static getComponent() helper for external code to retrieve
a typed component from any data-driven item stack.
2026-04-14 01:47:19 +02:00
NotEvil
a781dad597 feat(D-01): instantiate ComponentHolder per item definition on reload
Add a parallel COMPONENT_HOLDERS volatile cache to DataDrivenItemRegistry,
rebuilt from raw componentConfigs every time definitions are loaded via
reload() or mergeAll(). Cleared alongside DEFINITIONS in clear().

Two accessor methods allow looking up a ComponentHolder by ItemStack
(reads tiedup_item_id NBT) or by ResourceLocation directly.
2026-04-14 01:46:19 +02:00
NotEvil
750be66d80 feat(D-01): parse component configs from item JSON definitions
Add componentConfigs field (Map<ComponentType, JsonObject>) to
DataDrivenItemDefinition record. The parser now reads an optional
"components" JSON block, resolves each key via ComponentType.fromKey(),
and stores the raw JsonObject configs for later instantiation.
2026-04-14 01:44:19 +02:00
NotEvil
1b70041c36 feat(D-01): add ComponentHolder container for item components 2026-04-14 01:38:09 +02:00
NotEvil
b8a0d839f5 feat(D-01): add ComponentType enum with stub component classes 2026-04-14 01:33:37 +02:00
NotEvil
edfc3c6506 feat(D-01): add IItemComponent interface for data-driven item behaviors 2026-04-14 01:29:24 +02:00
3fe3e16e0a Merge pull request 'feature/item-tooltip-creator' (#4) from feature/item-tooltip-creator into develop
Reviewed-on: #4
2026-04-13 02:01:21 +00:00
NotEvil
e17998933c Add creator field and enriched tooltip for data-driven items
- Add optional "creator" JSON field to display author name in tooltip
- Show body regions, movement style, lock status, and escape difficulty
- Show pose priority and item ID in advanced mode (F3+H)
- Update ARTIST_GUIDE.md field reference and test JSON
2026-04-13 04:00:27 +02:00
3a1082dc38 Merge pull request 'Actualiser README.md' (#3) from notevil-patch-1 into develop
Reviewed-on: #3
2026-04-13 00:59:12 +00:00
310 changed files with 9926 additions and 10122 deletions

1
.gitignore vendored
View File

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

View File

@@ -105,7 +105,7 @@ Some dependencies are included as local JARs in `libs/` because they are not ava
GPL-3.0 with Commons Clause - see [LICENSE](LICENSE) for details.
**TL;DR:** Free to use, modify, and distribute. Cannot be sold or put behind a paywall.
The 3D models are the **property of their creators**; if their names are listed, please ask them for permission.
The 3D models are the **property of their creators**; if their names are listed, please ask them for permission otherwise me.
## Status

View File

@@ -16,7 +16,7 @@
7. [Animations](#animations) — item poses, fallback chain, variants, context animations
8. [Animation Templates](#animation-templates)
9. [Exporting from Blender](#exporting-from-blender)
10. [The JSON Definition](#the-json-definition)
10. [The JSON Definition](#the-json-definition) — field reference, components, pose priority, movement styles
11. [Packaging as a Resource Pack](#packaging-as-a-resource-pack)
12. [Common Mistakes](#common-mistakes)
13. [Examples](#examples)
@@ -762,7 +762,9 @@ The `movement_style` changes how the player physically moves — slower speed, d
| `animations` | string/object | No | `"auto"` (default) or explicit name mapping |
| `movement_style` | string | No | Movement restriction: `"waddle"`, `"shuffle"`, `"hop"`, or `"crawl"` |
| `movement_modifier` | object | No | Override speed/jump for the movement style (requires `movement_style`) |
| `creator` | string | No | Author/creator name, shown in the item tooltip |
| `animation_bones` | object | Yes | Per-animation bone whitelist (see below) |
| `components` | object | No | Gameplay behavior components (see [Components](#components-gameplay-behaviors) below) |
### animation_bones (required)
@@ -784,6 +786,87 @@ At runtime, the effective bones for a given animation clip are computed as the *
This field is **required**. Items without `animation_bones` will be rejected by the parser.
### Components (Gameplay Behaviors)
Components add gameplay behaviors to your item without requiring Java code. Each component is a self-contained module you declare in the `"components"` block of your JSON definition.
**Format:** A JSON object where each key is a component name and each value is the component's configuration (an object, or `true` for defaults).
```json
"components": {
"lockable": { "lock_resistance": 200 },
"resistance": { "base": 150 },
"gagging": { "comprehension": 0.2, "range": 10.0 }
}
```
Items without the `"components"` field work normally — components are entirely optional.
#### Available Components
| Component | Description | Config Fields |
|-----------|-------------|---------------|
| `lockable` | Item can be locked with a padlock. Locked items cannot be unequipped. | `lock_resistance` (int, default: 250) — resistance added by the lock for struggle mechanics |
| `resistance` | Struggle resistance. Higher = harder to escape. | `base` (int, default: 100) — base resistance value |
| `gagging` | Muffles the wearer's speech. | `comprehension` (0.01.0, default: 0.2) — how much speech is understandable. `range` (float, default: 10.0) — max hearing distance in blocks |
| `blinding` | Applies a blindfold overlay to the wearer's screen. | `overlay` (string, optional) — custom overlay texture path. Omit for default |
| `shock` | Item can shock the wearer (manually or automatically). | `damage` (float, default: 2.0) — damage per shock. `auto_interval` (int, default: 0) — ticks between auto-shocks (0 = manual only) |
| `gps` | GPS tracking and safe zone enforcement. | `safe_zone_radius` (int, default: 50) — safe zone in blocks (0 = tracking only). `public_tracking` (bool, default: false) — anyone can track, not just owner |
| `choking` | Drains air, applies darkness/slowness, deals damage when activated. | `air_drain_per_tick` (int, default: 8) — air drained per tick. `non_lethal_for_master` (bool, default: true) — won't kill if worn by a master's pet |
| `adjustable` | Allows Y-offset adjustment via GUI slider. | `default` (float, default: 0.0), `min` (float, default: -4.0), `max` (float, default: 4.0), `step` (float, default: 0.25) — all in pixels (1px = 1/16 block) |
#### Example: Shock Collar with GPS
```json
{
"type": "tiedup:bondage_item",
"display_name": "GPS Shock Collar",
"model": "mycreator:models/gltf/gps_shock_collar.glb",
"regions": ["NECK"],
"animation_bones": {
"idle": []
},
"pose_priority": 10,
"escape_difficulty": 5,
"components": {
"lockable": { "lock_resistance": 300 },
"resistance": { "base": 150 },
"shock": { "damage": 3.0, "auto_interval": 200 },
"gps": { "safe_zone_radius": 50 }
}
}
```
This collar can be locked (300 resistance to break the lock), has 150 base struggle resistance, shocks every 200 ticks (10 seconds) automatically, and enforces a 50-block safe zone.
#### Example: Adjustable Blindfold
```json
{
"type": "tiedup:bondage_item",
"display_name": "Leather Blindfold",
"model": "mycreator:models/gltf/blindfold.glb",
"regions": ["EYES"],
"animation_bones": {
"idle": ["head"]
},
"pose_priority": 10,
"escape_difficulty": 2,
"components": {
"blinding": {},
"resistance": { "base": 80 },
"adjustable": { "min": -2.0, "max": 2.0, "step": 0.5 }
}
}
```
#### Component Tips
- **You can combine any components.** A gag with `gagging` + `lockable` + `resistance` + `adjustable` is perfectly valid.
- **Omit components you don't need.** A decorative collar with no shock/GPS just omits those components entirely.
- **Default values are sensible.** `"lockable": {}` gives you standard lock behavior with default resistance. You only need to specify fields you want to customize.
- **Components don't affect rendering.** They are purely gameplay — your GLB model and animations are independent of which components you use.
### Pose Priority
When multiple items affect the same bones, the highest `pose_priority` wins.

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

@@ -670,13 +670,7 @@ public class CellRegistryV2 extends SavedData {
if (server == null || ownerId == null) return;
ServerPlayer owner = server.getPlayerList().getPlayer(ownerId);
if (owner != null) {
String template = SystemMessageManager.getTemplate(category);
String formattedMessage = String.format(template, prisonerName);
SystemMessageManager.sendToPlayer(
owner,
category,
formattedMessage
);
SystemMessageManager.sendTranslatable(owner, category, prisonerName);
}
}

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,9 +1,9 @@
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.state.HumanChairHelper;
import com.tiedup.remake.v2.bondage.PoseTypeHelper;
import com.tiedup.remake.util.HumanChairHelper;
import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
@@ -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,9 +2,9 @@ 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.state.HumanChairHelper;
import com.tiedup.remake.v2.bondage.PoseTypeHelper;
import com.tiedup.remake.util.HumanChairHelper;
import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2;
import net.minecraft.client.player.AbstractClientPlayer;
@@ -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,9 +14,10 @@ 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.state.HumanChairHelper;
import com.tiedup.remake.v2.bondage.BindModeHelper;
import com.tiedup.remake.v2.bondage.PoseTypeHelper;
import com.tiedup.remake.util.HumanChairHelper;
import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.IV2BondageEquipment;
@@ -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

@@ -13,8 +13,6 @@ import java.util.Set;
import java.util.TreeSet;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import net.minecraft.client.Minecraft;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.LivingEntity;
import net.minecraftforge.api.distmarker.Dist;
@@ -433,52 +431,6 @@ public final class GltfAnimationApplier {
ContextAnimationFactory.clearCache();
}
// LEGACY F9 DEBUG TOGGLE
private static boolean debugEnabled = false;
/**
* Toggle debug mode via F9 key.
* When enabled, applies handcuffs V2 animation (rightArm + leftArm) to the local player
* using STAND_IDLE context. When disabled, clears all V2 animation.
*/
public static void toggle() {
debugEnabled = !debugEnabled;
LOGGER.info(
"[GltfPipeline] Debug toggle: {}",
debugEnabled ? "ON" : "OFF"
);
AbstractClientPlayer player = Minecraft.getInstance().player;
if (player == null) return;
if (debugEnabled) {
ResourceLocation modelLoc = ResourceLocation.fromNamespaceAndPath(
"tiedup",
"models/gltf/v2/handcuffs/cuffs_prototype.glb"
);
Set<String> armParts = Set.of("rightArm", "leftArm");
RegionBoneMapper.BoneOwnership debugOwnership =
new RegionBoneMapper.BoneOwnership(armParts, Set.of());
applyV2Animation(
player,
modelLoc,
null,
AnimationContext.STAND_IDLE,
debugOwnership
);
} else {
clearV2Animation(player);
}
}
/**
* Whether F9 debug mode is currently enabled.
*/
public static boolean isEnabled() {
return debugEnabled;
}
// INTERNAL
/**

View File

@@ -1,11 +1,9 @@
package com.tiedup.remake.client.gltf;
import com.mojang.blaze3d.platform.InputConstants;
import com.tiedup.remake.client.animation.context.ContextAnimationFactory;
import com.tiedup.remake.client.animation.context.ContextGlbRegistry;
import com.tiedup.remake.v2.bondage.client.V2BondageRenderLayer;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemReloadListener;
import net.minecraft.client.KeyMapping;
import net.minecraft.client.renderer.entity.player.PlayerRenderer;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.server.packs.resources.SimplePreparableReloadListener;
@@ -13,8 +11,6 @@ import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.EntityRenderersEvent;
import net.minecraftforge.client.event.RegisterClientReloadListenersEvent;
import net.minecraftforge.client.event.RegisterKeyMappingsEvent;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
@@ -23,24 +19,16 @@ import org.apache.logging.log4j.Logger;
/**
* Forge event registration for the glTF pipeline.
* Registers keybind (F9), render layers, and animation factory.
* Registers render layers and animation factory.
*/
public final class GltfClientSetup {
private static final Logger LOGGER = LogManager.getLogger("GltfPipeline");
private static final String KEY_CATEGORY = "key.categories.tiedup";
static final KeyMapping TOGGLE_KEY = new KeyMapping(
"key.tiedup.gltf_toggle",
InputConstants.Type.KEYSYM,
InputConstants.KEY_F9,
KEY_CATEGORY
);
private GltfClientSetup() {}
/**
* MOD bus event subscribers (FMLClientSetupEvent, RegisterKeyMappings, AddLayers).
* MOD bus event subscribers (FMLClientSetupEvent, AddLayers).
*/
@Mod.EventBusSubscriber(
modid = "tiedup",
@@ -58,21 +46,11 @@ public final class GltfClientSetup {
});
}
@SubscribeEvent
public static void onRegisterKeybindings(
RegisterKeyMappingsEvent event
) {
event.register(TOGGLE_KEY);
LOGGER.info("[GltfPipeline] Keybind registered: F9");
}
@SuppressWarnings("unchecked")
@SubscribeEvent
public static void onAddLayers(EntityRenderersEvent.AddLayers event) {
// Add GltfRenderLayer (prototype/debug with F9 toggle) to player renderers
var defaultRenderer = event.getSkin("default");
if (defaultRenderer instanceof PlayerRenderer playerRenderer) {
playerRenderer.addLayer(new GltfRenderLayer(playerRenderer));
playerRenderer.addLayer(
new V2BondageRenderLayer<>(playerRenderer)
);
@@ -81,10 +59,9 @@ public final class GltfClientSetup {
);
}
// Add both layers to slim player renderer (Alex)
// Add V2 layer to slim player renderer (Alex)
var slimRenderer = event.getSkin("slim");
if (slimRenderer instanceof PlayerRenderer playerRenderer) {
playerRenderer.addLayer(new GltfRenderLayer(playerRenderer));
playerRenderer.addLayer(
new V2BondageRenderLayer<>(playerRenderer)
);
@@ -143,23 +120,4 @@ public final class GltfClientSetup {
}
}
/**
* FORGE bus event subscribers (ClientTickEvent for keybind toggle).
*/
@Mod.EventBusSubscriber(
modid = "tiedup",
bus = Mod.EventBusSubscriber.Bus.FORGE,
value = Dist.CLIENT
)
public static class ForgeBusEvents {
@SubscribeEvent
public static void onClientTick(TickEvent.ClientTickEvent event) {
if (event.phase != TickEvent.Phase.END) return;
while (TOGGLE_KEY.consumeClick()) {
GltfAnimationApplier.toggle();
}
}
}
}

View File

@@ -1,106 +0,0 @@
package com.tiedup.remake.client.gltf;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.Minecraft;
import net.minecraft.client.model.PlayerModel;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.entity.RenderLayerParent;
import net.minecraft.client.renderer.entity.layers.RenderLayer;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.joml.Matrix4f;
/**
* RenderLayer that renders the glTF mesh (handcuffs) on the player.
* Only active when enabled and only renders on the local player.
* <p>
* Uses the live skinning path: reads live skeleton from HumanoidModel
* via {@link GltfLiveBoneReader}, following PlayerAnimator + bendy-lib rotations.
* Falls back to GLB-internal skinning via {@link GltfSkinningEngine} if live reading fails.
*/
@OnlyIn(Dist.CLIENT)
public class GltfRenderLayer
extends RenderLayer<AbstractClientPlayer, PlayerModel<AbstractClientPlayer>>
{
private static final Logger LOGGER = LogManager.getLogger("GltfPipeline");
private static final ResourceLocation CUFFS_MODEL =
ResourceLocation.fromNamespaceAndPath(
"tiedup",
"models/gltf/v2/handcuffs/cuffs_prototype.glb"
);
public GltfRenderLayer(
RenderLayerParent<
AbstractClientPlayer,
PlayerModel<AbstractClientPlayer>
> renderer
) {
super(renderer);
}
/**
* The Y translate offset to place the glTF mesh in the MC PoseStack.
* <p>
* After LivingEntityRenderer's scale(-1,-1,1) + translate(0,-1.501,0),
* the PoseStack origin is at the model top (1.501 blocks above feet), Y-down.
* The glTF mesh (MC-converted) has feet at Y=0 and head at Y≈-1.5.
* Translating by 1.501 maps glTF feet to PoseStack feet and head to top.
*/
private static final float ALIGNMENT_Y = 1.501f;
@Override
public void render(
PoseStack poseStack,
MultiBufferSource buffer,
int packedLight,
AbstractClientPlayer entity,
float limbSwing,
float limbSwingAmount,
float partialTick,
float ageInTicks,
float netHeadYaw,
float headPitch
) {
if (!GltfAnimationApplier.isEnabled()) return;
if (entity != Minecraft.getInstance().player) return;
GltfData data = GltfCache.get(CUFFS_MODEL);
if (data == null) return;
// Live path: read skeleton from HumanoidModel (after PlayerAnimator)
PlayerModel<AbstractClientPlayer> parentModel = this.getParentModel();
Matrix4f[] joints = GltfLiveBoneReader.computeJointMatricesFromModel(
parentModel,
data,
entity
);
if (joints == null) {
// Fallback to GLB-internal path if live reading fails
joints = GltfSkinningEngine.computeJointMatrices(data);
}
poseStack.pushPose();
// Align glTF mesh with MC model (feet-to-feet alignment)
poseStack.translate(0, ALIGNMENT_Y, 0);
GltfMeshRenderer.renderSkinned(
data,
joints,
poseStack,
buffer,
packedLight,
net.minecraft.client.renderer.entity.LivingEntityRenderer.getOverlayCoords(
entity,
0.0f
)
);
poseStack.popPose();
}
}

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

@@ -69,8 +69,8 @@ public class BountyCommand {
// Cannot bounty yourself
if (player.getUUID().equals(target.getUUID())) {
source.sendFailure(
Component.literal(
"You cannot put a bounty on yourself!"
Component.translatable(
"command.tiedup.bounty.cannot_self"
).withStyle(ChatFormatting.RED)
);
return 0;
@@ -80,8 +80,8 @@ public class BountyCommand {
IBondageState playerState = KidnappedHelper.getKidnappedState(player);
if (playerState != null && playerState.isTiedUp()) {
source.sendFailure(
Component.literal(
"You cannot create bounties while tied up!"
Component.translatable(
"command.tiedup.bounty.tied_up"
).withStyle(ChatFormatting.RED)
);
return 0;
@@ -96,8 +96,8 @@ public class BountyCommand {
player.serverLevel().getGameRules()
);
source.sendFailure(
Component.literal(
"Maximum number (" + max + ") of active bounties reached!"
Component.translatable(
"command.tiedup.bounty.max_reached", max
).withStyle(ChatFormatting.RED)
);
return 0;
@@ -107,8 +107,8 @@ public class BountyCommand {
ItemStack heldItem = player.getMainHandItem();
if (heldItem.isEmpty()) {
source.sendFailure(
Component.literal(
"You must hold an item as the reward!"
Component.translatable(
"command.tiedup.bounty.must_hold_item"
).withStyle(ChatFormatting.RED)
);
return 0;
@@ -143,8 +143,8 @@ public class BountyCommand {
// Notify player
source.sendSuccess(
() ->
Component.literal(
"Bounty created on " + target.getName().getString() + "!"
Component.translatable(
"command.tiedup.bounty.created", target.getName().getString()
).withStyle(ChatFormatting.GREEN),
false
);
@@ -153,12 +153,10 @@ public class BountyCommand {
player.server
.getPlayerList()
.broadcastSystemMessage(
Component.literal(
"[Bounty] " +
player.getName().getString() +
" has put a bounty on " +
target.getName().getString() +
"!"
Component.translatable(
"command.tiedup.bounty.broadcast",
player.getName().getString(),
target.getName().getString()
).withStyle(ChatFormatting.GOLD),
false
);

View File

@@ -117,9 +117,9 @@ public class CaptivityDebugCommand {
.getSource()
.sendSuccess(
() ->
Component.literal(
"=== Captivity Debug Info ===\n" + debugInfo
).withStyle(ChatFormatting.YELLOW),
Component.translatable(
"command.tiedup.debug.prisoner_header"
).append(Component.literal("\n" + debugInfo)).withStyle(ChatFormatting.YELLOW),
false
);
@@ -128,7 +128,7 @@ public class CaptivityDebugCommand {
ctx
.getSource()
.sendFailure(
Component.literal("Error: " + e.getMessage()).withStyle(
Component.translatable("command.tiedup.debug.error", e.getMessage()).withStyle(
ChatFormatting.RED
)
);
@@ -149,8 +149,8 @@ public class CaptivityDebugCommand {
.getSource()
.sendSuccess(
() ->
Component.literal(
"Checking captivity system..."
Component.translatable(
"command.tiedup.debug.validate_checking"
).withStyle(ChatFormatting.YELLOW),
true
);
@@ -163,9 +163,7 @@ public class CaptivityDebugCommand {
.getSource()
.sendSuccess(
() ->
Component.literal(debugInfo).withStyle(
ChatFormatting.GREEN
),
Component.literal(debugInfo).withStyle(ChatFormatting.GREEN),
true
);
@@ -174,8 +172,8 @@ public class CaptivityDebugCommand {
ctx
.getSource()
.sendFailure(
Component.literal(
"Error during validation: " + e.getMessage()
Component.translatable(
"command.tiedup.debug.error", e.getMessage()
).withStyle(ChatFormatting.RED)
);
return 0; // Failure
@@ -193,8 +191,8 @@ public class CaptivityDebugCommand {
.getSource()
.sendSuccess(
() ->
Component.literal(
"Repair functionality has been simplified with the new PrisonerManager system."
Component.translatable(
"command.tiedup.debug.repair_simplified"
).withStyle(ChatFormatting.YELLOW),
true
);
@@ -203,8 +201,8 @@ public class CaptivityDebugCommand {
.getSource()
.sendSuccess(
() ->
Component.literal(
"The new system maintains consistency automatically."
Component.translatable(
"command.tiedup.debug.repair_auto"
).withStyle(ChatFormatting.GREEN),
true
);
@@ -214,7 +212,7 @@ public class CaptivityDebugCommand {
ctx
.getSource()
.sendFailure(
Component.literal("Error: " + e.getMessage()).withStyle(
Component.translatable("command.tiedup.debug.error", e.getMessage()).withStyle(
ChatFormatting.RED
)
);
@@ -251,8 +249,8 @@ public class CaptivityDebugCommand {
ctx
.getSource()
.sendFailure(
Component.literal(
"No camp found with ID prefix: " + campIdPrefix
Component.translatable(
"command.tiedup.debug.camp_not_found", campIdPrefix
).withStyle(ChatFormatting.RED)
);
return 0;
@@ -322,7 +320,7 @@ public class CaptivityDebugCommand {
ctx
.getSource()
.sendFailure(
Component.literal("Error: " + e.getMessage()).withStyle(
Component.translatable("command.tiedup.debug.error", e.getMessage()).withStyle(
ChatFormatting.RED
)
);

View File

@@ -12,6 +12,7 @@ import com.tiedup.remake.items.ItemAdminWand;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
import net.minecraft.ChatFormatting;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.commands.arguments.EntityArgument;
@@ -110,7 +111,7 @@ public class CellCommand {
// Must be a player
if (!(source.getEntity() instanceof ServerPlayer player)) {
source.sendFailure(Component.literal("Must be a player"));
source.sendFailure(Component.translatable("command.tiedup.error.must_be_player"));
return 0;
}
@@ -121,9 +122,7 @@ public class CellCommand {
UUID selectedCellId = getSelectedCellFromWand(player);
if (selectedCellId == null) {
source.sendFailure(
Component.literal(
"No cell selected. Use the Admin Wand on a Cell Core first."
)
Component.translatable("command.tiedup.cell.no_selection")
);
return 0;
}
@@ -131,7 +130,7 @@ public class CellCommand {
CellDataV2 cell = registry.getCell(selectedCellId);
if (cell == null) {
source.sendFailure(
Component.literal("Selected cell no longer exists")
Component.translatable("command.tiedup.cell.no_longer_exists")
);
return 0;
}
@@ -142,7 +141,7 @@ public class CellCommand {
existingCell != null && !existingCell.getId().equals(selectedCellId)
) {
source.sendFailure(
Component.literal("Cell name '" + name + "' already exists")
Component.translatable("command.tiedup.cell.name_exists", name)
);
return 0;
}
@@ -161,9 +160,7 @@ public class CellCommand {
source.sendSuccess(
() ->
Component.literal(
"Named cell '" + name + "' and linked to you"
),
Component.translatable("command.tiedup.cell.named", name).withStyle(ChatFormatting.GREEN),
true
);
return 1;
@@ -182,20 +179,20 @@ public class CellCommand {
Collection<CellDataV2> cells = registry.getAllCells();
if (cells.isEmpty()) {
source.sendSuccess(
() -> Component.literal("No cells registered"),
() -> Component.translatable("command.tiedup.cell.none_registered"),
false
);
return 1;
}
source.sendSuccess(
() -> Component.literal("=== Cells (" + cells.size() + ") ==="),
() -> Component.translatable("command.tiedup.cell.list_header", cells.size()).withStyle(ChatFormatting.GOLD),
false
);
for (CellDataV2 cell : cells) {
String info = formatCellInfo(cell, serverLevel);
source.sendSuccess(() -> Component.literal(info), false);
source.sendSuccess(() -> Component.literal(info).withStyle(ChatFormatting.GRAY), false);
}
return 1;
@@ -217,9 +214,7 @@ public class CellCommand {
if (cells.isEmpty()) {
source.sendSuccess(
() ->
Component.literal(
owner.getName().getString() + " has no cells"
),
Component.translatable("command.tiedup.cell.no_cells_for_owner", owner.getName().getString()),
false
);
return 1;
@@ -227,19 +222,17 @@ public class CellCommand {
source.sendSuccess(
() ->
Component.literal(
"=== Cells owned by " +
owner.getName().getString() +
" (" +
cells.size() +
") ==="
),
Component.translatable(
"command.tiedup.cell.list_owner_header",
owner.getName().getString(),
cells.size()
).withStyle(ChatFormatting.GOLD),
false
);
for (CellDataV2 cell : cells) {
String info = formatCellInfo(cell, serverLevel);
source.sendSuccess(() -> Component.literal(info), false);
source.sendSuccess(() -> Component.literal(info).withStyle(ChatFormatting.GRAY), false);
}
return 1;
@@ -257,7 +250,7 @@ public class CellCommand {
// Must be a player
if (!(source.getEntity() instanceof ServerPlayer player)) {
source.sendFailure(Component.literal("Must be a player"));
source.sendFailure(Component.translatable("command.tiedup.error.must_be_player"));
return 0;
}
@@ -267,9 +260,7 @@ public class CellCommand {
UUID selectedCellId = getSelectedCellFromWand(player);
if (selectedCellId == null) {
source.sendFailure(
Component.literal(
"No cell selected. Use the Admin Wand on a Cell Core first."
)
Component.translatable("command.tiedup.cell.no_selection")
);
return 0;
}
@@ -277,7 +268,7 @@ public class CellCommand {
CellDataV2 cell = registry.getCell(selectedCellId);
if (cell == null) {
source.sendFailure(
Component.literal("Selected cell no longer exists")
Component.translatable("command.tiedup.cell.no_longer_exists")
);
return 0;
}
@@ -300,7 +291,7 @@ public class CellCommand {
CellDataV2 cell = registry.getCellByName(name);
if (cell == null) {
source.sendFailure(
Component.literal("Cell '" + name + "' not found")
Component.translatable("command.tiedup.cell.not_found", name)
);
return 0;
}
@@ -321,7 +312,7 @@ public class CellCommand {
// Must be a player
if (!(source.getEntity() instanceof ServerPlayer player)) {
source.sendFailure(Component.literal("Must be a player"));
source.sendFailure(Component.translatable("command.tiedup.error.must_be_player"));
return 0;
}
@@ -331,9 +322,7 @@ public class CellCommand {
UUID selectedCellId = getSelectedCellFromWand(player);
if (selectedCellId == null) {
source.sendFailure(
Component.literal(
"No cell selected. Use the Admin Wand on a Cell Core first."
)
Component.translatable("command.tiedup.cell.no_selection")
);
return 0;
}
@@ -341,7 +330,7 @@ public class CellCommand {
CellDataV2 cell = registry.getCell(selectedCellId);
if (cell == null) {
source.sendFailure(
Component.literal("Selected cell no longer exists")
Component.translatable("command.tiedup.cell.no_longer_exists")
);
return 0;
}
@@ -365,7 +354,7 @@ public class CellCommand {
}
source.sendSuccess(
() -> Component.literal("Deleted cell '" + cellName + "'"),
() -> Component.translatable("command.tiedup.cell.deleted", cellName).withStyle(ChatFormatting.GREEN),
true
);
return 1;
@@ -438,24 +427,16 @@ public class CellCommand {
source.sendSuccess(
() ->
Component.literal(
"Reset " +
finalResetCount +
" spawn markers (found " +
finalSpawnMarkerCount +
" total spawn markers in " +
radius +
" block radius)"
),
Component.translatable(
"command.tiedup.cell.reset_spawns", finalResetCount, finalSpawnMarkerCount, radius
).withStyle(ChatFormatting.GREEN),
true
);
if (resetCount > 0) {
source.sendSuccess(
() ->
Component.literal(
"You can now save the structure - NPCs will spawn when it's placed."
),
Component.translatable("command.tiedup.cell.reset_spawns_hint").withStyle(ChatFormatting.GRAY),
false
);
}
@@ -541,25 +522,25 @@ public class CellCommand {
String nameDisplay =
cell.getName() != null ? cell.getName() : "(unnamed)";
source.sendSuccess(
() -> Component.literal("=== Cell: " + nameDisplay + " ==="),
() -> Component.translatable("command.tiedup.cell.info_header", nameDisplay).withStyle(ChatFormatting.GOLD),
false
);
source.sendSuccess(
() -> Component.literal("ID: " + cell.getId().toString()),
() -> Component.translatable("command.tiedup.cell.info_id", cell.getId().toString()).withStyle(ChatFormatting.GRAY),
false
);
source.sendSuccess(
() -> Component.literal("State: " + cell.getState()),
() -> Component.translatable("command.tiedup.cell.info_state", cell.getState().toString()).withStyle(ChatFormatting.GRAY),
false
);
source.sendSuccess(
() ->
Component.literal(
"Core Position: " + cell.getCorePos().toShortString()
),
Component.translatable(
"command.tiedup.cell.info_core_pos", cell.getCorePos().toShortString()
).withStyle(ChatFormatting.GRAY),
false
);
@@ -567,9 +548,9 @@ public class CellCommand {
if (cell.getSpawnPoint() != null) {
source.sendSuccess(
() ->
Component.literal(
"Spawn Point: " + cell.getSpawnPoint().toShortString()
),
Component.translatable(
"command.tiedup.cell.info_spawn_point", cell.getSpawnPoint().toShortString()
).withStyle(ChatFormatting.GRAY),
false
);
}
@@ -584,18 +565,16 @@ public class CellCommand {
owner != null ? owner.getName().getString() : "(offline)";
source.sendSuccess(
() ->
Component.literal(
"Owner: " +
ownerName +
" (" +
cell.getOwnerId().toString().substring(0, 8) +
"...)"
),
Component.translatable(
"command.tiedup.cell.info_owner",
ownerName,
cell.getOwnerId().toString().substring(0, 8) + "..."
).withStyle(ChatFormatting.GRAY),
false
);
} else {
source.sendSuccess(
() -> Component.literal("Owner: (world-generated)"),
() -> Component.translatable("command.tiedup.cell.info_owner_world").withStyle(ChatFormatting.GRAY),
false
);
}
@@ -603,16 +582,16 @@ public class CellCommand {
// Geometry
source.sendSuccess(
() ->
Component.literal(
"Interior blocks: " + cell.getInteriorBlocks().size()
),
Component.translatable(
"command.tiedup.cell.info_interior", cell.getInteriorBlocks().size()
).withStyle(ChatFormatting.GRAY),
false
);
source.sendSuccess(
() ->
Component.literal(
"Wall blocks: " + cell.getWallBlocks().size()
),
Component.translatable(
"command.tiedup.cell.info_walls", cell.getWallBlocks().size()
).withStyle(ChatFormatting.GRAY),
false
);
@@ -620,16 +599,11 @@ public class CellCommand {
if (!cell.getBreachedPositions().isEmpty()) {
source.sendSuccess(
() ->
Component.literal(
"Breaches: " +
cell.getBreachedPositions().size() +
" (" +
String.format(
"%.1f",
cell.getBreachPercentage() * 100
) +
"%)"
),
Component.translatable(
"command.tiedup.cell.info_breaches",
cell.getBreachedPositions().size(),
String.format("%.1f", cell.getBreachPercentage() * 100)
).withStyle(ChatFormatting.RED),
false
);
}
@@ -637,19 +611,19 @@ public class CellCommand {
// Features
if (!cell.getBeds().isEmpty()) {
source.sendSuccess(
() -> Component.literal("Beds: " + cell.getBeds().size()),
() -> Component.translatable("command.tiedup.cell.info_beds", cell.getBeds().size()).withStyle(ChatFormatting.GRAY),
false
);
}
if (!cell.getAnchors().isEmpty()) {
source.sendSuccess(
() -> Component.literal("Anchors: " + cell.getAnchors().size()),
() -> Component.translatable("command.tiedup.cell.info_anchors", cell.getAnchors().size()).withStyle(ChatFormatting.GRAY),
false
);
}
if (!cell.getDoors().isEmpty()) {
source.sendSuccess(
() -> Component.literal("Doors: " + cell.getDoors().size()),
() -> Component.translatable("command.tiedup.cell.info_doors", cell.getDoors().size()).withStyle(ChatFormatting.GRAY),
false
);
}
@@ -657,9 +631,9 @@ public class CellCommand {
// Prisoners
source.sendSuccess(
() ->
Component.literal(
"Prisoners: " + cell.getPrisonerCount() + "/4"
),
Component.translatable(
"command.tiedup.cell.info_prisoners", cell.getPrisonerCount()
).withStyle(ChatFormatting.GRAY),
false
);
for (UUID prisonerId : cell.getPrisonerIds()) {
@@ -670,7 +644,7 @@ public class CellCommand {
String prisonerName =
prisoner != null ? prisoner.getName().getString() : "(offline)";
source.sendSuccess(
() -> Component.literal(" - " + prisonerName),
() -> Component.literal(" - " + prisonerName).withStyle(ChatFormatting.GRAY),
false
);
}

View File

@@ -7,11 +7,12 @@ 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;
import net.minecraft.ChatFormatting;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.commands.arguments.EntityArgument;
@@ -152,30 +153,26 @@ public class CollarCommand {
ItemStack collar = getPlayerCollar(target);
if (collar.isEmpty()) {
source.sendFailure(
Component.literal(
target.getName().getString() + " does not have a collar"
)
Component.translatable("command.tiedup.collar_cmd.no_collar", target.getName().getString())
);
return 0;
}
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(
"§aClaimed " +
target.getName().getString() +
"'s collar"
),
Component.translatable(
"command.tiedup.collar_cmd.claimed", target.getName().getString()
).withStyle(ChatFormatting.GREEN),
true
);
return 1;
}
}
source.sendFailure(Component.literal("Failed to claim collar"));
source.sendFailure(Component.translatable("command.tiedup.collar_cmd.claim_failed"));
return 0;
}
@@ -187,30 +184,26 @@ public class CollarCommand {
ItemStack collar = getPlayerCollar(target);
if (collar.isEmpty()) {
source.sendFailure(
Component.literal(
target.getName().getString() + " does not have a collar"
)
Component.translatable("command.tiedup.collar_cmd.no_collar", target.getName().getString())
);
return 0;
}
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(
"§aRemoved your ownership from " +
target.getName().getString() +
"'s collar"
),
Component.translatable(
"command.tiedup.collar_cmd.unclaimed", target.getName().getString()
).withStyle(ChatFormatting.GREEN),
true
);
return 1;
}
}
source.sendFailure(Component.literal("Failed to unclaim collar"));
source.sendFailure(Component.translatable("command.tiedup.collar_cmd.unclaim_failed"));
return 0;
}
@@ -223,20 +216,18 @@ public class CollarCommand {
ItemStack collar = getPlayerCollar(target);
if (collar.isEmpty()) {
source.sendFailure(
Component.literal(
target.getName().getString() + " does not have a collar"
)
Component.translatable("command.tiedup.collar_cmd.no_collar", target.getName().getString())
);
return 0;
}
if (collar.getItem() instanceof ItemCollar collarItem) {
collarItem.setNickname(collar, name);
if (CollarHelper.isCollar(collar)) {
CollarHelper.setNickname(collar, name);
source.sendSuccess(
() ->
Component.literal(
"§aSet collar nickname to '" + name + "'"
),
Component.translatable(
"command.tiedup.collar_cmd.renamed", name
).withStyle(ChatFormatting.GREEN),
true
);
return 1;
@@ -254,24 +245,20 @@ public class CollarCommand {
ItemStack collar = getPlayerCollar(target);
if (collar.isEmpty()) {
source.sendFailure(
Component.literal(
target.getName().getString() + " does not have a collar"
)
Component.translatable("command.tiedup.collar_cmd.no_collar", target.getName().getString())
);
return 0;
}
if (collar.getItem() instanceof ItemCollar collarItem) {
collarItem.addOwner(collar, owner);
if (CollarHelper.isCollar(collar)) {
CollarHelper.addOwner(collar, owner);
source.sendSuccess(
() ->
Component.literal(
"§aAdded " +
owner.getName().getString() +
" as owner of " +
target.getName().getString() +
"'s collar"
),
Component.translatable(
"command.tiedup.collar_cmd.owner_added",
owner.getName().getString(),
target.getName().getString()
).withStyle(ChatFormatting.GREEN),
true
);
return 1;
@@ -289,24 +276,20 @@ public class CollarCommand {
ItemStack collar = getPlayerCollar(target);
if (collar.isEmpty()) {
source.sendFailure(
Component.literal(
target.getName().getString() + " does not have a collar"
)
Component.translatable("command.tiedup.collar_cmd.no_collar", target.getName().getString())
);
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(
"§aRemoved " +
owner.getName().getString() +
" as owner of " +
target.getName().getString() +
"'s collar"
),
Component.translatable(
"command.tiedup.collar_cmd.owner_removed",
owner.getName().getString(),
target.getName().getString()
).withStyle(ChatFormatting.GREEN),
true
);
return 1;
@@ -329,9 +312,7 @@ public class CollarCommand {
ItemStack collar = getPlayerCollar(target);
if (collar.isEmpty()) {
source.sendFailure(
Component.literal(
target.getName().getString() + " does not have a collar"
)
Component.translatable("command.tiedup.collar_cmd.no_collar", target.getName().getString())
);
return 0;
}
@@ -343,22 +324,20 @@ public class CollarCommand {
if (cell == null) {
source.sendFailure(
Component.literal("Cell '" + cellName + "' not found")
Component.translatable("command.tiedup.collar_cmd.cell_not_found", cellName)
);
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(
"§aAssigned cell '" +
cellName +
"' to " +
target.getName().getString() +
"'s collar"
),
Component.translatable(
"command.tiedup.collar_cmd.cell_assigned",
cellName,
target.getName().getString()
).withStyle(ChatFormatting.GREEN),
true
);
return 1;
@@ -381,29 +360,27 @@ public class CollarCommand {
ItemStack collar = getPlayerCollar(target);
if (collar.isEmpty()) {
source.sendFailure(
Component.literal(
target.getName().getString() + " does not have a collar"
)
Component.translatable("command.tiedup.collar_cmd.no_collar", target.getName().getString())
);
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")
Component.translatable("command.tiedup.collar_cmd.no_cell_assigned")
);
return 0;
}
// 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);
if (cell == null) {
source.sendFailure(
Component.literal("Assigned cell no longer exists")
Component.translatable("command.tiedup.collar_cmd.cell_deleted")
);
return 0;
}
@@ -420,12 +397,11 @@ public class CollarCommand {
source.sendSuccess(
() ->
Component.literal(
"§aTeleported " +
target.getName().getString() +
" to cell at " +
cell.getCorePos().toShortString()
),
Component.translatable(
"command.tiedup.collar_cmd.teleported",
target.getName().getString(),
cell.getCorePos().toShortString()
).withStyle(ChatFormatting.GREEN),
true
);
return 1;
@@ -442,42 +418,35 @@ public class CollarCommand {
ItemStack collar = getPlayerCollar(target);
if (collar.isEmpty()) {
source.sendFailure(
Component.literal(
target.getName().getString() + " does not have a collar"
)
Component.translatable("command.tiedup.collar_cmd.no_collar", target.getName().getString())
);
return 0;
}
if (collar.getItem() instanceof ItemCollar collarItem) {
if (CollarHelper.isCollar(collar)) {
source.sendSuccess(
() ->
Component.literal(
"§6=== Collar Info for " +
target.getName().getString() +
" ==="
),
Component.translatable(
"command.tiedup.collar_cmd.info_header",
target.getName().getString()
).withStyle(ChatFormatting.GOLD),
false
);
String nickname = collarItem.getNickname(collar);
String nickname = CollarHelper.getNickname(collar);
String nicknameDisplay = (nickname == null || nickname.isEmpty()) ? "None" : nickname;
source.sendSuccess(
() ->
Component.literal(
"§7Nickname: §f" +
(nickname.isEmpty() ? "None" : nickname)
),
Component.translatable("command.tiedup.collar_cmd.info_nickname", nicknameDisplay).withStyle(ChatFormatting.GRAY),
false
);
source.sendSuccess(
() ->
Component.literal(
"§7Has Owner: §f" + collarItem.hasOwner(collar)
),
Component.translatable("command.tiedup.collar_cmd.info_has_owner", String.valueOf(CollarHelper.hasOwner(collar))).withStyle(ChatFormatting.GRAY),
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);
@@ -489,32 +458,33 @@ public class CollarCommand {
: cellId.toString().substring(0, 8) + "...";
source.sendSuccess(
() ->
Component.literal(
"§7Assigned Cell: §a" +
cellDisplay +
" §7@ " +
cell.getCorePos().toShortString()
),
Component.translatable(
"command.tiedup.collar_cmd.info_cell",
cellDisplay,
cell.getCorePos().toShortString()
).withStyle(ChatFormatting.GRAY),
false
);
} else {
source.sendSuccess(
() -> Component.literal("§7Assigned Cell: §c(deleted)"),
() -> Component.translatable("command.tiedup.collar_cmd.info_cell_deleted").withStyle(ChatFormatting.RED),
false
);
}
} else {
source.sendSuccess(
() -> Component.literal("§7Assigned Cell: §fNone"),
() -> Component.translatable("command.tiedup.collar_cmd.info_cell_none").withStyle(ChatFormatting.GRAY),
false
);
}
boolean locked = collar.getItem() instanceof com.tiedup.remake.items.base.ILockable lockable
&& lockable.isLocked(collar);
source.sendSuccess(
() ->
Component.literal(
"§7Locked: §f" + collarItem.isLocked(collar)
),
Component.translatable(
"command.tiedup.collar_cmd.info_locked", String.valueOf(locked)
).withStyle(ChatFormatting.GRAY),
false
);

View File

@@ -42,7 +42,7 @@ public final class CommandHelper {
if (source.getEntity() instanceof ServerPlayer player) {
return Optional.of(player);
}
source.sendFailure(Component.literal("Must be a player"));
source.sendFailure(Component.translatable("command.tiedup.error.must_be_player"));
return Optional.empty();
}

View File

@@ -5,6 +5,7 @@ import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.tiedup.remake.items.ModItems;
import java.util.Optional;
import net.minecraft.ChatFormatting;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.commands.arguments.EntityArgument;
@@ -89,7 +90,7 @@ public class KeyCommand {
ItemStack key = getHeldKey(player);
if (key.isEmpty()) {
source.sendFailure(Component.literal("You must hold a collar key"));
source.sendFailure(Component.translatable("command.tiedup.key.must_hold_key"));
return 0;
}
@@ -101,7 +102,7 @@ public class KeyCommand {
!tag.getUUID(TAG_OWNER).equals(player.getUUID())
) {
source.sendFailure(
Component.literal("This key is already claimed by someone else")
Component.translatable("command.tiedup.key.already_claimed")
);
return 0;
}
@@ -110,7 +111,7 @@ public class KeyCommand {
tag.putString(TAG_OWNER_NAME, player.getName().getString());
source.sendSuccess(
() -> Component.literal("§aYou have claimed this key"),
() -> Component.translatable("command.tiedup.key.claimed").withStyle(ChatFormatting.GREEN),
false
);
@@ -129,19 +130,19 @@ public class KeyCommand {
ItemStack key = getHeldKey(player);
if (key.isEmpty()) {
source.sendFailure(Component.literal("You must hold a collar key"));
source.sendFailure(Component.translatable("command.tiedup.key.must_hold_key"));
return 0;
}
CompoundTag tag = key.getOrCreateTag();
if (!tag.hasUUID(TAG_OWNER)) {
source.sendFailure(Component.literal("This key is not claimed"));
source.sendFailure(Component.translatable("command.tiedup.key.not_claimed"));
return 0;
}
if (!tag.getUUID(TAG_OWNER).equals(player.getUUID())) {
source.sendFailure(Component.literal("You do not own this key"));
source.sendFailure(Component.translatable("command.tiedup.key.not_owner"));
return 0;
}
@@ -149,7 +150,7 @@ public class KeyCommand {
tag.remove(TAG_OWNER_NAME);
source.sendSuccess(
() -> Component.literal("§aYou have unclaimed this key"),
() -> Component.translatable("command.tiedup.key.unclaimed").withStyle(ChatFormatting.GREEN),
false
);
@@ -170,7 +171,7 @@ public class KeyCommand {
ItemStack key = getHeldKey(player);
if (key.isEmpty()) {
source.sendFailure(Component.literal("You must hold a collar key"));
source.sendFailure(Component.translatable("command.tiedup.key.must_hold_key"));
return 0;
}
@@ -181,7 +182,7 @@ public class KeyCommand {
tag.hasUUID(TAG_OWNER) &&
!tag.getUUID(TAG_OWNER).equals(player.getUUID())
) {
source.sendFailure(Component.literal("You do not own this key"));
source.sendFailure(Component.translatable("command.tiedup.key.not_owner"));
return 0;
}
@@ -190,9 +191,9 @@ public class KeyCommand {
source.sendSuccess(
() ->
Component.literal(
"§aAssigned key to " + target.getName().getString()
),
Component.translatable(
"command.tiedup.key.assigned", target.getName().getString()
).withStyle(ChatFormatting.GREEN),
false
);
@@ -211,7 +212,7 @@ public class KeyCommand {
ItemStack key = getHeldKey(player);
if (key.isEmpty()) {
source.sendFailure(Component.literal("You must hold a collar key"));
source.sendFailure(Component.translatable("command.tiedup.key.must_hold_key"));
return 0;
}
@@ -222,7 +223,7 @@ public class KeyCommand {
tag.hasUUID(TAG_OWNER) &&
!tag.getUUID(TAG_OWNER).equals(player.getUUID())
) {
source.sendFailure(Component.literal("You do not own this key"));
source.sendFailure(Component.translatable("command.tiedup.key.not_owner"));
return 0;
}
@@ -231,9 +232,9 @@ public class KeyCommand {
source.sendSuccess(
() ->
Component.literal(
"§aKey is now " + (isPublic ? "public" : "private")
),
Component.translatable(
isPublic ? "command.tiedup.key.now_public" : "command.tiedup.key.now_private"
).withStyle(ChatFormatting.GREEN),
false
);
@@ -252,40 +253,40 @@ public class KeyCommand {
ItemStack key = getHeldKey(player);
if (key.isEmpty()) {
source.sendFailure(Component.literal("You must hold a collar key"));
source.sendFailure(Component.translatable("command.tiedup.key.must_hold_key"));
return 0;
}
CompoundTag tag = key.getOrCreateTag();
source.sendSuccess(
() -> Component.literal("§6=== Key Info ==="),
() -> Component.translatable("command.tiedup.key.info_header").withStyle(ChatFormatting.GOLD),
false
);
String ownerName = tag.getString(TAG_OWNER_NAME);
source.sendSuccess(
() ->
Component.literal(
"§7Owner: §f" +
(ownerName.isEmpty() ? "Not claimed" : ownerName)
),
Component.translatable(
"command.tiedup.key.info_owner",
ownerName.isEmpty() ? "Not claimed" : ownerName
).withStyle(ChatFormatting.GRAY),
false
);
String targetName = tag.getString(TAG_TARGET_NAME);
source.sendSuccess(
() ->
Component.literal(
"§7Assigned to: §f" +
(targetName.isEmpty() ? "Not assigned" : targetName)
),
Component.translatable(
"command.tiedup.key.info_assigned",
targetName.isEmpty() ? "Not assigned" : targetName
).withStyle(ChatFormatting.GRAY),
false
);
boolean isPublic = tag.getBoolean(TAG_PUBLIC);
source.sendSuccess(
() -> Component.literal("§7Public: §f" + (isPublic ? "Yes" : "No")),
() -> Component.translatable("command.tiedup.key.info_public", isPublic ? "Yes" : "No").withStyle(ChatFormatting.GRAY),
false
);

View File

@@ -4,12 +4,11 @@ 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.ChatFormatting;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.network.chat.Component;
@@ -82,53 +81,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 +133,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));
@@ -173,15 +148,27 @@ public class KidnapSetCommand {
int finalGiven = given;
source.sendSuccess(
() ->
Component.literal(
"§aGave kidnap set (" + finalGiven + " item stacks)"
),
Component.translatable(
"command.tiedup.kidnapset.gave", finalGiven
).withStyle(ChatFormatting.GREEN),
true
);
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
@@ -219,13 +206,11 @@ public class KidnapSetCommand {
int finalReloaded = reloaded;
source.sendSuccess(
() ->
Component.literal(
"§aReloaded " +
(type.equals("all") ? "all data files" : type) +
" (" +
finalReloaded +
" files)"
),
Component.translatable(
"command.tiedup.kidnapreload.reloaded",
type.equals("all") ? "all data files" : type,
finalReloaded
).withStyle(ChatFormatting.GREEN),
true
);

View File

@@ -7,14 +7,13 @@ 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;
import net.minecraft.ChatFormatting;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.commands.arguments.EntityArgument;
@@ -201,7 +200,7 @@ public class NPCCommand {
z = player.getZ();
} else {
source.sendFailure(
Component.literal("Must specify a player or be a player")
Component.translatable("command.tiedup.error.must_be_player")
);
return 0;
}
@@ -221,15 +220,15 @@ public class NPCCommand {
source.sendSuccess(
() ->
Component.literal(
"§aSpawned Kidnapper at " + formatPos(x, y, z)
),
Component.translatable(
"command.tiedup.npc.spawned_kidnapper", formatPos(x, y, z)
).withStyle(ChatFormatting.GREEN),
true
);
return 1;
}
source.sendFailure(Component.literal("Failed to spawn Kidnapper"));
source.sendFailure(Component.translatable("command.tiedup.npc.spawn_failed_kidnapper"));
return 0;
}
@@ -247,11 +246,7 @@ public class NPCCommand {
);
if (variant == null) {
source.sendFailure(
Component.literal(
"Unknown elite variant: " +
name +
". Available: suki, carol, athena, evelyn"
)
Component.translatable("command.tiedup.npc.unknown_variant", name)
);
return 0;
}
@@ -268,7 +263,7 @@ public class NPCCommand {
z = player.getZ();
} else {
source.sendFailure(
Component.literal("Must specify a player or be a player")
Component.translatable("command.tiedup.error.must_be_player")
);
return 0;
}
@@ -291,19 +286,16 @@ public class NPCCommand {
source.sendSuccess(
() ->
Component.literal(
"§aSpawned Elite Kidnapper '" +
variant.defaultName() +
"' at " +
formatPos(x, y, z)
),
Component.translatable(
"command.tiedup.npc.spawned_elite", variant.defaultName(), formatPos(x, y, z)
).withStyle(ChatFormatting.GREEN),
true
);
return 1;
}
source.sendFailure(
Component.literal("Failed to spawn Elite Kidnapper")
Component.translatable("command.tiedup.npc.spawn_failed_elite")
);
return 0;
}
@@ -327,7 +319,7 @@ public class NPCCommand {
z = player.getZ();
} else {
source.sendFailure(
Component.literal("Must specify a player or be a player")
Component.translatable("command.tiedup.error.must_be_player")
);
return 0;
}
@@ -348,16 +340,16 @@ public class NPCCommand {
source.sendSuccess(
() ->
Component.literal(
"§aSpawned Archer Kidnapper at " + formatPos(x, y, z)
),
Component.translatable(
"command.tiedup.npc.spawned_archer", formatPos(x, y, z)
).withStyle(ChatFormatting.GREEN),
true
);
return 1;
}
source.sendFailure(
Component.literal("Failed to spawn Archer Kidnapper")
Component.translatable("command.tiedup.npc.spawn_failed_archer")
);
return 0;
}
@@ -381,7 +373,7 @@ public class NPCCommand {
z = player.getZ();
} else {
source.sendFailure(
Component.literal("Must specify a player or be a player")
Component.translatable("command.tiedup.error.must_be_player")
);
return 0;
}
@@ -401,15 +393,15 @@ public class NPCCommand {
source.sendSuccess(
() ->
Component.literal(
"§aSpawned Damsel at " + formatPos(x, y, z)
),
Component.translatable(
"command.tiedup.npc.spawned_damsel", formatPos(x, y, z)
).withStyle(ChatFormatting.GREEN),
true
);
return 1;
}
source.sendFailure(Component.literal("Failed to spawn Damsel"));
source.sendFailure(Component.translatable("command.tiedup.npc.spawn_failed_damsel"));
return 0;
}
@@ -443,9 +435,9 @@ public class NPCCommand {
int finalKilled = killed;
source.sendSuccess(
() ->
Component.literal(
"§aKilled " + finalKilled + " mod NPCs in radius " + radius
),
Component.translatable(
"command.tiedup.npc.killed", finalKilled, radius
).withStyle(ChatFormatting.GREEN),
true
);
@@ -493,7 +485,7 @@ public class NPCCommand {
context
.getSource()
.sendFailure(
Component.literal("No mod NPC found within 10 blocks")
Component.translatable("command.tiedup.npc.no_npc_nearby")
);
return 0;
}
@@ -501,18 +493,18 @@ public class NPCCommand {
if (npc.isTiedUp()) {
context
.getSource()
.sendFailure(Component.literal("NPC is already tied up"));
.sendFailure(Component.translatable("command.tiedup.npc.already_tied"));
return 0;
}
npc.equip(
BodyRegionV2.ARMS,
new ItemStack(ModItems.getBind(BindVariant.ROPES))
DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "ropes"))
);
context
.getSource()
.sendSuccess(
() -> Component.literal("§aTied up " + npc.getKidnappedName()),
() -> Component.translatable("command.tiedup.npc.tied", npc.getKidnappedName()).withStyle(ChatFormatting.GREEN),
true
);
return 1;
@@ -524,7 +516,7 @@ public class NPCCommand {
context
.getSource()
.sendFailure(
Component.literal("No mod NPC found within 10 blocks")
Component.translatable("command.tiedup.npc.no_npc_nearby")
);
return 0;
}
@@ -532,18 +524,18 @@ public class NPCCommand {
if (npc.isGagged()) {
context
.getSource()
.sendFailure(Component.literal("NPC is already gagged"));
.sendFailure(Component.translatable("command.tiedup.npc.already_gagged"));
return 0;
}
npc.equip(
BodyRegionV2.MOUTH,
new ItemStack(ModItems.getGag(GagVariant.CLOTH_GAG))
DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "cloth_gag"))
);
context
.getSource()
.sendSuccess(
() -> Component.literal("§aGagged " + npc.getKidnappedName()),
() -> Component.translatable("command.tiedup.npc.gagged", npc.getKidnappedName()).withStyle(ChatFormatting.GREEN),
true
);
return 1;
@@ -557,7 +549,7 @@ public class NPCCommand {
context
.getSource()
.sendFailure(
Component.literal("No mod NPC found within 10 blocks")
Component.translatable("command.tiedup.npc.no_npc_nearby")
);
return 0;
}
@@ -565,21 +557,20 @@ public class NPCCommand {
if (npc.isBlindfolded()) {
context
.getSource()
.sendFailure(Component.literal("NPC is already blindfolded"));
.sendFailure(Component.translatable("command.tiedup.npc.already_blindfolded"));
return 0;
}
npc.equip(
BodyRegionV2.EYES,
new ItemStack(ModItems.getBlindfold(BlindfoldVariant.CLASSIC))
DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_blindfold"))
);
context
.getSource()
.sendSuccess(
() ->
Component.literal(
"§aBlindfolded " + npc.getKidnappedName()
),
Component.translatable("command.tiedup.npc.blindfolded", npc.getKidnappedName()
).withStyle(ChatFormatting.GREEN),
true
);
return 1;
@@ -591,7 +582,7 @@ public class NPCCommand {
context
.getSource()
.sendFailure(
Component.literal("No mod NPC found within 10 blocks")
Component.translatable("command.tiedup.npc.no_npc_nearby")
);
return 0;
}
@@ -599,18 +590,18 @@ public class NPCCommand {
if (npc.hasCollar()) {
context
.getSource()
.sendFailure(Component.literal("NPC already has a collar"));
.sendFailure(Component.translatable("command.tiedup.npc.already_collared"));
return 0;
}
npc.equip(
BodyRegionV2.NECK,
new ItemStack(ModItems.CLASSIC_COLLAR.get())
DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_collar"))
);
context
.getSource()
.sendSuccess(
() -> Component.literal("§aCollared " + npc.getKidnappedName()),
() -> Component.translatable("command.tiedup.npc.collared", npc.getKidnappedName()).withStyle(ChatFormatting.GREEN),
true
);
return 1;
@@ -622,7 +613,7 @@ public class NPCCommand {
context
.getSource()
.sendFailure(
Component.literal("No mod NPC found within 10 blocks")
Component.translatable("command.tiedup.npc.no_npc_nearby")
);
return 0;
}
@@ -631,7 +622,7 @@ public class NPCCommand {
context
.getSource()
.sendSuccess(
() -> Component.literal("§aUntied " + npc.getKidnappedName()),
() -> Component.translatable("command.tiedup.npc.untied", npc.getKidnappedName()).withStyle(ChatFormatting.GREEN),
true
);
return 1;
@@ -645,7 +636,7 @@ public class NPCCommand {
context
.getSource()
.sendFailure(
Component.literal("No mod NPC found within 10 blocks")
Component.translatable("command.tiedup.npc.no_npc_nearby")
);
return 0;
}
@@ -656,11 +647,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
);
}
@@ -669,9 +660,9 @@ public class NPCCommand {
.getSource()
.sendSuccess(
() ->
Component.literal(
"§aFully restrained " + npc.getKidnappedName()
),
Component.translatable(
"command.tiedup.npc.fully_restrained", npc.getKidnappedName()
).withStyle(ChatFormatting.GREEN),
true
);
return 1;
@@ -685,7 +676,7 @@ public class NPCCommand {
context
.getSource()
.sendFailure(
Component.literal("No mod NPC found within 10 blocks")
Component.translatable("command.tiedup.npc.no_npc_nearby")
);
return 0;
}
@@ -694,68 +685,67 @@ public class NPCCommand {
source.sendSuccess(
() ->
Component.literal(
"§6=== NPC State: " + npc.getKidnappedName() + " ==="
),
Component.translatable(
"command.tiedup.npc.state_header", npc.getKidnappedName()
).withStyle(ChatFormatting.GOLD),
false
);
if (npc instanceof EntityDamsel damsel) {
source.sendSuccess(
() ->
Component.literal("§eVariant: §f" + damsel.getVariantId()),
Component.translatable("command.tiedup.npc.state_variant", damsel.getVariantId()).withStyle(ChatFormatting.YELLOW),
false
);
source.sendSuccess(
() ->
Component.literal(
"§eSlim Arms: " +
(damsel.hasSlimArms() ? "§aYes" : "§7No")
),
Component.translatable("command.tiedup.npc.state_slim_arms",
Component.translatable(damsel.hasSlimArms() ? "command.tiedup.yes" : "command.tiedup.no")
).withStyle(ChatFormatting.YELLOW),
false
);
}
source.sendSuccess(
() ->
Component.literal(
"§eTied Up: " + (npc.isTiedUp() ? "§aYes" : "§7No")
),
Component.translatable("command.tiedup.npc.state_tied",
Component.translatable(npc.isTiedUp() ? "command.tiedup.yes" : "command.tiedup.no")
).withStyle(ChatFormatting.YELLOW),
false
);
source.sendSuccess(
() ->
Component.literal(
"§eGagged: " + (npc.isGagged() ? "§aYes" : "§7No")
),
Component.translatable("command.tiedup.npc.state_gagged",
Component.translatable(npc.isGagged() ? "command.tiedup.yes" : "command.tiedup.no")
).withStyle(ChatFormatting.YELLOW),
false
);
source.sendSuccess(
() ->
Component.literal(
"§eBlindfolded: " + (npc.isBlindfolded() ? "§aYes" : "§7No")
),
Component.translatable("command.tiedup.npc.state_blindfolded",
Component.translatable(npc.isBlindfolded() ? "command.tiedup.yes" : "command.tiedup.no")
).withStyle(ChatFormatting.YELLOW),
false
);
source.sendSuccess(
() ->
Component.literal(
"§eHas Collar: " + (npc.hasCollar() ? "§aYes" : "§7No")
),
Component.translatable("command.tiedup.npc.state_collar",
Component.translatable(npc.hasCollar() ? "command.tiedup.yes" : "command.tiedup.no")
).withStyle(ChatFormatting.YELLOW),
false
);
source.sendSuccess(
() ->
Component.literal(
"§eHas Earplugs: " + (npc.hasEarplugs() ? "§aYes" : "§7No")
),
Component.translatable("command.tiedup.npc.state_earplugs",
Component.translatable(npc.hasEarplugs() ? "command.tiedup.yes" : "command.tiedup.no")
).withStyle(ChatFormatting.YELLOW),
false
);
source.sendSuccess(
() ->
Component.literal(
"§eIs Captive: " + (npc.isCaptive() ? "§aYes" : "§7No")
),
Component.translatable("command.tiedup.npc.state_captive",
Component.translatable(npc.isCaptive() ? "command.tiedup.yes" : "command.tiedup.no")
).withStyle(ChatFormatting.YELLOW),
false
);

View File

@@ -162,7 +162,7 @@ public class SocialCommand {
ServerPlayer target = EntityArgument.getPlayer(context, "player");
if (player.getUUID().equals(target.getUUID())) {
source.sendFailure(Component.literal("You cannot block yourself"));
source.sendFailure(Component.translatable("command.tiedup.social.cannot_block_self"));
return 0;
}
@@ -170,9 +170,7 @@ public class SocialCommand {
if (data.isBlocked(player.getUUID(), target.getUUID())) {
source.sendFailure(
Component.literal(
target.getName().getString() + " is already blocked"
)
Component.translatable("command.tiedup.social.already_blocked", target.getName().getString())
);
return 0;
}
@@ -180,7 +178,7 @@ public class SocialCommand {
data.addBlock(player.getUUID(), target.getUUID());
source.sendSuccess(
() ->
Component.literal("§aBlocked " + target.getName().getString()),
Component.translatable("command.tiedup.social.blocked", target.getName().getString()).withStyle(ChatFormatting.GREEN),
false
);
@@ -208,9 +206,7 @@ public class SocialCommand {
if (!data.isBlocked(player.getUUID(), target.getUUID())) {
source.sendFailure(
Component.literal(
target.getName().getString() + " is not blocked"
)
Component.translatable("command.tiedup.social.not_blocked", target.getName().getString())
);
return 0;
}
@@ -218,9 +214,7 @@ public class SocialCommand {
data.removeBlock(player.getUUID(), target.getUUID());
source.sendSuccess(
() ->
Component.literal(
"§aUnblocked " + target.getName().getString()
),
Component.translatable("command.tiedup.social.unblocked", target.getName().getString()).withStyle(ChatFormatting.GREEN),
false
);
@@ -245,19 +239,13 @@ public class SocialCommand {
if (blocked) {
source.sendSuccess(
() ->
Component.literal(
"§c" + target.getName().getString() + " has blocked you"
),
Component.translatable("command.tiedup.social.has_blocked_you", target.getName().getString()).withStyle(ChatFormatting.RED),
false
);
} else {
source.sendSuccess(
() ->
Component.literal(
"§a" +
target.getName().getString() +
" has not blocked you"
),
Component.translatable("command.tiedup.social.has_not_blocked_you", target.getName().getString()).withStyle(ChatFormatting.GREEN),
false
);
}
@@ -300,11 +288,7 @@ public class SocialCommand {
if (lastUse != null && now - lastUse < NORP_COOLDOWN_MS) {
long remaining = (NORP_COOLDOWN_MS - (now - lastUse)) / 1000;
source.sendFailure(
Component.literal(
"Please wait " +
remaining +
" seconds before using /norp again"
)
Component.translatable("command.tiedup.social.norp_cooldown", remaining)
);
return 0;
}
@@ -315,7 +299,7 @@ public class SocialCommand {
// Broadcast to all players
Component message = Component.literal("")
.append(
Component.literal("[NoRP] ").withStyle(
Component.translatable("command.tiedup.social.norp_prefix").withStyle(
ChatFormatting.RED,
ChatFormatting.BOLD
)
@@ -326,8 +310,8 @@ public class SocialCommand {
)
)
.append(
Component.literal(
" has announced non-consent to current RP"
Component.translatable(
"command.tiedup.social.norp_announcement"
).withStyle(ChatFormatting.RED)
);
@@ -411,7 +395,7 @@ public class SocialCommand {
SocialData data = SocialData.get(sender.serverLevel());
if (data.isBlocked(target.getUUID(), sender.getUUID())) {
source.sendFailure(
Component.literal("This player has blocked you")
Component.translatable("command.tiedup.social.pm_blocked")
);
return 0;
}
@@ -419,8 +403,8 @@ public class SocialCommand {
// Send to target (earplug-aware)
Component toTarget = Component.literal("")
.append(
Component.literal(
"[PM from " + sender.getName().getString() + "] "
Component.translatable(
"command.tiedup.social.pm_from", sender.getName().getString()
).withStyle(ChatFormatting.LIGHT_PURPLE)
)
.append(Component.literal(message).withStyle(ChatFormatting.WHITE));
@@ -429,8 +413,8 @@ public class SocialCommand {
// Confirm to sender (always show - they're the one sending)
Component toSender = Component.literal("")
.append(
Component.literal(
"[PM to " + target.getName().getString() + "] "
Component.translatable(
"command.tiedup.social.pm_to", target.getName().getString()
).withStyle(ChatFormatting.GRAY)
)
.append(Component.literal(message).withStyle(ChatFormatting.WHITE));
@@ -458,15 +442,13 @@ public class SocialCommand {
if (distance == 0) {
source.sendSuccess(
() -> Component.literal("§aTalk area disabled (global chat)"),
() -> Component.translatable("command.tiedup.social.talkarea_disabled").withStyle(ChatFormatting.GREEN),
false
);
} else {
source.sendSuccess(
() ->
Component.literal(
"§aTalk area set to " + distance + " blocks"
),
Component.translatable("command.tiedup.social.talkarea_set", distance).withStyle(ChatFormatting.GREEN),
false
);
}
@@ -490,12 +472,12 @@ public class SocialCommand {
if (talkArea == 0) {
source.sendSuccess(
() ->
Component.literal("Talk area: §edisabled §7(global chat)"),
Component.translatable("command.tiedup.social.talkinfo_disabled").withStyle(ChatFormatting.YELLOW),
false
);
} else {
source.sendSuccess(
() -> Component.literal("Talk area: §e" + talkArea + " blocks"),
() -> Component.translatable("command.tiedup.social.talkinfo_distance", talkArea).withStyle(ChatFormatting.YELLOW),
false
);
}

View File

@@ -0,0 +1,459 @@
package com.tiedup.remake.commands.subcommands;
import com.mojang.brigadier.arguments.FloatArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.tiedup.remake.items.base.AdjustmentHelper;
import com.tiedup.remake.items.ModItems;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.tiedup.remake.commands.CommandHelper;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.core.SystemMessageManager;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import net.minecraft.ChatFormatting;
import net.minecraft.resources.ResourceLocation;
import com.tiedup.remake.state.PlayerBindState;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.commands.arguments.EntityArgument;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.item.ItemStack;
@SuppressWarnings("null")
public class AccessoryCommands {
public static void register(LiteralArgumentBuilder<CommandSourceStack> root) {
// /tiedup putearplugs <player>
root.then(
Commands.literal("putearplugs")
.requires(CommandHelper.REQUIRES_OP)
.then(
Commands.argument(
"player",
EntityArgument.player()
).executes(AccessoryCommands::putearplugs)
)
);
// /tiedup takeearplugs <player>
root.then(
Commands.literal("takeearplugs")
.requires(CommandHelper.REQUIRES_OP)
.then(
Commands.argument(
"player",
EntityArgument.player()
).executes(AccessoryCommands::takeearplugs)
)
);
// /tiedup putclothes <player>
root.then(
Commands.literal("putclothes")
.requires(CommandHelper.REQUIRES_OP)
.then(
Commands.argument(
"player",
EntityArgument.player()
).executes(AccessoryCommands::putclothes)
)
);
// /tiedup takeclothes <player>
root.then(
Commands.literal("takeclothes")
.requires(CommandHelper.REQUIRES_OP)
.then(
Commands.argument(
"player",
EntityArgument.player()
).executes(AccessoryCommands::takeclothes)
)
);
// /tiedup fullyrestrain <player>
root.then(
Commands.literal("fullyrestrain")
.requires(CommandHelper.REQUIRES_OP)
.then(
Commands.argument(
"player",
EntityArgument.player()
).executes(AccessoryCommands::fullyrestrain)
)
);
// /tiedup adjust <player> <type:gag|blindfold|all> <value>
root.then(
Commands.literal("adjust")
.requires(CommandHelper.REQUIRES_OP)
.then(
Commands.argument("player", EntityArgument.player()).then(
Commands.argument("type", StringArgumentType.word())
.suggests((ctx, builder) -> {
builder.suggest("gag");
builder.suggest("blindfold");
builder.suggest("all");
return builder.buildFuture();
})
.then(
Commands.argument(
"value",
FloatArgumentType.floatArg(-4.0f, 4.0f)
).executes(AccessoryCommands::adjust)
)
)
)
);
}
static int putearplugs(CommandContext<CommandSourceStack> context)
throws CommandSyntaxException {
ServerPlayer targetPlayer = EntityArgument.getPlayer(context, "player");
PlayerBindState state = PlayerBindState.getInstance(targetPlayer);
if (state == null) {
context
.getSource()
.sendFailure(Component.translatable("command.tiedup.error.no_state"));
return 0;
}
if (state.hasEarplugs()) {
context
.getSource()
.sendFailure(
Component.translatable(
"command.tiedup.accessory.already_earplugs",
targetPlayer.getName().getString()
)
);
return 0;
}
ItemStack earplugs = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_earplugs"));
state.putEarplugsOn(earplugs);
CommandHelper.syncPlayerState(targetPlayer, state);
context
.getSource()
.sendSuccess(
() ->
Component.translatable(
"command.tiedup.accessory.earplugs_on",
targetPlayer.getName().getString()
).withStyle(ChatFormatting.GREEN),
true
);
SystemMessageManager.sendToTarget(
context.getSource().getEntity(),
targetPlayer,
SystemMessageManager.MessageCategory.EARPLUGS_ON
);
return 1;
}
static int takeearplugs(CommandContext<CommandSourceStack> context)
throws CommandSyntaxException {
ServerPlayer targetPlayer = EntityArgument.getPlayer(context, "player");
PlayerBindState state = PlayerBindState.getInstance(targetPlayer);
if (state == null) {
context
.getSource()
.sendFailure(Component.translatable("command.tiedup.error.no_state"));
return 0;
}
if (!state.hasEarplugs()) {
context
.getSource()
.sendFailure(
Component.translatable(
"command.tiedup.accessory.no_earplugs",
targetPlayer.getName().getString()
)
);
return 0;
}
state.takeEarplugsOff();
CommandHelper.syncPlayerState(targetPlayer, state);
context
.getSource()
.sendSuccess(
() ->
Component.translatable(
"command.tiedup.accessory.earplugs_removed",
targetPlayer.getName().getString()
).withStyle(ChatFormatting.GREEN),
true
);
SystemMessageManager.sendToPlayer(
targetPlayer,
SystemMessageManager.MessageCategory.INFO,
"Your earplugs have been removed!"
);
return 1;
}
static int putclothes(CommandContext<CommandSourceStack> context)
throws CommandSyntaxException {
ServerPlayer targetPlayer = EntityArgument.getPlayer(context, "player");
PlayerBindState state = PlayerBindState.getInstance(targetPlayer);
if (state == null) {
context
.getSource()
.sendFailure(Component.translatable("command.tiedup.error.no_state"));
return 0;
}
if (state.hasClothes()) {
context
.getSource()
.sendFailure(
Component.translatable(
"command.tiedup.accessory.already_clothes",
targetPlayer.getName().getString()
)
);
return 0;
}
ItemStack clothes = new ItemStack(ModItems.CLOTHES.get());
state.putClothesOn(clothes);
CommandHelper.syncPlayerState(targetPlayer, state);
context
.getSource()
.sendSuccess(
() ->
Component.translatable(
"command.tiedup.accessory.clothes_on",
targetPlayer.getName().getString()
).withStyle(ChatFormatting.GREEN),
true
);
return 1;
}
static int takeclothes(CommandContext<CommandSourceStack> context)
throws CommandSyntaxException {
ServerPlayer targetPlayer = EntityArgument.getPlayer(context, "player");
PlayerBindState state = PlayerBindState.getInstance(targetPlayer);
if (state == null) {
context
.getSource()
.sendFailure(Component.translatable("command.tiedup.error.no_state"));
return 0;
}
if (!state.hasClothes()) {
context
.getSource()
.sendFailure(
Component.translatable(
"command.tiedup.accessory.no_clothes",
targetPlayer.getName().getString()
)
);
return 0;
}
ItemStack removed = state.takeClothesOff();
CommandHelper.syncPlayerState(targetPlayer, state);
if (!removed.isEmpty()) {
targetPlayer.drop(removed, false);
}
context
.getSource()
.sendSuccess(
() ->
Component.translatable(
"command.tiedup.accessory.clothes_removed",
targetPlayer.getName().getString()
).withStyle(ChatFormatting.GREEN),
true
);
return 1;
}
static int fullyrestrain(CommandContext<CommandSourceStack> context)
throws CommandSyntaxException {
ServerPlayer targetPlayer = EntityArgument.getPlayer(context, "player");
PlayerBindState state = PlayerBindState.getInstance(targetPlayer);
if (state == null) {
context
.getSource()
.sendFailure(Component.translatable("command.tiedup.error.no_state"));
return 0;
}
int applied = 0;
if (!state.isTiedUp()) {
ItemStack ropes = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "ropes"));
state.putBindOn(ropes);
applied++;
}
if (!state.isGagged()) {
ItemStack gag = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "cloth_gag"));
state.putGagOn(gag);
applied++;
}
if (!state.isBlindfolded()) {
ItemStack blindfold = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_blindfold"));
state.putBlindfoldOn(blindfold);
applied++;
}
if (!state.hasCollar()) {
ItemStack collar = com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem.createStack(new net.minecraft.resources.ResourceLocation("tiedup", "classic_collar"));
if (
context.getSource().getEntity() instanceof ServerPlayer executor
) {
CollarHelper.addOwner(collar, executor);
}
state.putCollarOn(collar);
applied++;
}
if (!state.hasEarplugs()) {
ItemStack earplugs = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_earplugs"));
state.putEarplugsOn(earplugs);
applied++;
}
if (applied == 0) {
context
.getSource()
.sendFailure(
Component.translatable(
"command.tiedup.accessory.already_restrained",
targetPlayer.getName().getString()
)
);
return 0;
}
CommandHelper.syncPlayerState(targetPlayer, state);
int finalApplied = applied;
context
.getSource()
.sendSuccess(
() ->
Component.translatable(
"command.tiedup.accessory.fully_restrained",
targetPlayer.getName().getString(),
finalApplied
).withStyle(ChatFormatting.GREEN),
true
);
SystemMessageManager.sendToPlayer(
targetPlayer,
SystemMessageManager.MessageCategory.INFO,
"You have been fully restrained!"
);
return 1;
}
static int adjust(CommandContext<CommandSourceStack> context)
throws CommandSyntaxException {
ServerPlayer targetPlayer = EntityArgument.getPlayer(context, "player");
String type = StringArgumentType.getString(context, "type");
float value = FloatArgumentType.getFloat(context, "value");
PlayerBindState state = PlayerBindState.getInstance(targetPlayer);
if (state == null) {
context
.getSource()
.sendFailure(Component.translatable("command.tiedup.error.no_state"));
return 0;
}
boolean adjustedGag = false;
boolean adjustedBlindfold = false;
if (type.equals("gag") || type.equals("all")) {
ItemStack gag = state.getEquipment(
com.tiedup.remake.v2.BodyRegionV2.MOUTH
);
if (!gag.isEmpty()) {
AdjustmentHelper.setAdjustment(gag, value);
adjustedGag = true;
}
}
if (type.equals("blindfold") || type.equals("all")) {
ItemStack blindfold = state.getEquipment(
com.tiedup.remake.v2.BodyRegionV2.EYES
);
if (!blindfold.isEmpty()) {
AdjustmentHelper.setAdjustment(blindfold, value);
adjustedBlindfold = true;
}
}
if (
!type.equals("gag") &&
!type.equals("blindfold") &&
!type.equals("all")
) {
context
.getSource()
.sendFailure(
Component.translatable("command.tiedup.accessory.adjust_invalid_type")
);
return 0;
}
if (!adjustedGag && !adjustedBlindfold) {
context
.getSource()
.sendFailure(
Component.translatable(
"command.tiedup.accessory.nothing_to_adjust",
targetPlayer.getName().getString(),
type
)
);
return 0;
}
CommandHelper.syncPlayerState(targetPlayer, state);
String items =
adjustedGag && adjustedBlindfold
? "gag and blindfold"
: adjustedGag
? "gag"
: "blindfold";
String valueStr = String.format("%.2f", value);
context
.getSource()
.sendSuccess(
() ->
Component.translatable(
"command.tiedup.accessory.adjusted",
items,
targetPlayer.getName().getString(),
valueStr
).withStyle(ChatFormatting.GREEN),
true
);
return 1;
}
}

View File

@@ -0,0 +1,166 @@
package com.tiedup.remake.commands.subcommands;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.tiedup.remake.commands.CommandHelper;
import com.tiedup.remake.core.SystemMessageManager;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import net.minecraft.ChatFormatting;
import net.minecraft.resources.ResourceLocation;
import com.tiedup.remake.state.PlayerBindState;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.commands.arguments.EntityArgument;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.item.ItemStack;
@SuppressWarnings("null")
public class BindCommands {
public static void register(LiteralArgumentBuilder<CommandSourceStack> root) {
// /tiedup tie <player>
root.then(
Commands.literal("tie")
.requires(CommandHelper.REQUIRES_OP)
.then(
Commands.argument(
"player",
EntityArgument.player()
).executes(BindCommands::tie)
)
);
// /tiedup untie <player>
root.then(
Commands.literal("untie")
.requires(CommandHelper.REQUIRES_OP)
.then(
Commands.argument(
"player",
EntityArgument.player()
).executes(BindCommands::untie)
)
);
}
static int tie(CommandContext<CommandSourceStack> context)
throws CommandSyntaxException {
ServerPlayer targetPlayer = EntityArgument.getPlayer(context, "player");
PlayerBindState state = PlayerBindState.getInstance(targetPlayer);
if (state == null) {
context
.getSource()
.sendFailure(Component.translatable("command.tiedup.error.no_state"));
return 0;
}
if (state.isTiedUp()) {
context
.getSource()
.sendFailure(
Component.translatable(
"command.tiedup.bind.already_tied",
targetPlayer.getName().getString()
)
);
return 0;
}
ItemStack ropes = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "ropes"));
state.putBindOn(ropes);
CommandHelper.syncPlayerState(targetPlayer, state);
context
.getSource()
.sendSuccess(
() ->
Component.translatable(
"command.tiedup.bind.tied",
targetPlayer.getName().getString()
).withStyle(ChatFormatting.GREEN),
true
);
SystemMessageManager.sendTiedUp(
context.getSource().getEntity(),
targetPlayer
);
return 1;
}
static int untie(CommandContext<CommandSourceStack> context)
throws CommandSyntaxException {
ServerPlayer targetPlayer = EntityArgument.getPlayer(context, "player");
PlayerBindState state = PlayerBindState.getInstance(targetPlayer);
if (state == null) {
context
.getSource()
.sendFailure(Component.translatable("command.tiedup.error.no_state"));
return 0;
}
if (
!state.isTiedUp() &&
!state.isGagged() &&
!state.isBlindfolded() &&
!state.hasCollar()
) {
context
.getSource()
.sendFailure(
Component.translatable(
"command.tiedup.bind.not_restrained",
targetPlayer.getName().getString()
)
);
return 0;
}
boolean removed = false;
if (state.isTiedUp()) {
state.takeBindOff();
removed = true;
}
if (state.isGagged()) {
state.takeGagOff();
removed = true;
}
if (state.isBlindfolded()) {
state.takeBlindfoldOff();
removed = true;
}
if (state.hasCollar()) {
state.takeCollarOff();
removed = true;
}
if (state.hasEarplugs()) {
state.takeEarplugsOff();
removed = true;
}
if (removed) {
CommandHelper.syncPlayerState(targetPlayer, state);
context
.getSource()
.sendSuccess(
() ->
Component.translatable(
"command.tiedup.bind.freed",
targetPlayer.getName().getString()
).withStyle(ChatFormatting.GREEN),
true
);
SystemMessageManager.sendFreed(targetPlayer);
return 1;
}
return 0;
}
}

View File

@@ -0,0 +1,141 @@
package com.tiedup.remake.commands.subcommands;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.tiedup.remake.commands.CommandHelper;
import com.tiedup.remake.core.SystemMessageManager;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import net.minecraft.ChatFormatting;
import net.minecraft.resources.ResourceLocation;
import com.tiedup.remake.state.PlayerBindState;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.commands.arguments.EntityArgument;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.item.ItemStack;
@SuppressWarnings("null")
public class BlindfoldCommands {
public static void register(LiteralArgumentBuilder<CommandSourceStack> root) {
// /tiedup blindfold <player>
root.then(
Commands.literal("blindfold")
.requires(CommandHelper.REQUIRES_OP)
.then(
Commands.argument(
"player",
EntityArgument.player()
).executes(BlindfoldCommands::blindfold)
)
);
// /tiedup unblind <player>
root.then(
Commands.literal("unblind")
.requires(CommandHelper.REQUIRES_OP)
.then(
Commands.argument(
"player",
EntityArgument.player()
).executes(BlindfoldCommands::unblind)
)
);
}
static int blindfold(CommandContext<CommandSourceStack> context)
throws CommandSyntaxException {
ServerPlayer targetPlayer = EntityArgument.getPlayer(context, "player");
PlayerBindState state = PlayerBindState.getInstance(targetPlayer);
if (state == null) {
context
.getSource()
.sendFailure(Component.translatable("command.tiedup.error.no_state"));
return 0;
}
if (state.isBlindfolded()) {
context
.getSource()
.sendFailure(
Component.translatable(
"command.tiedup.blindfold.already_blindfolded",
targetPlayer.getName().getString()
)
);
return 0;
}
ItemStack blindfold = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_blindfold"));
state.putBlindfoldOn(blindfold);
CommandHelper.syncPlayerState(targetPlayer, state);
context
.getSource()
.sendSuccess(
() ->
Component.translatable(
"command.tiedup.blindfold.blindfolded",
targetPlayer.getName().getString()
).withStyle(ChatFormatting.GREEN),
true
);
SystemMessageManager.sendToTarget(
context.getSource().getEntity(),
targetPlayer,
SystemMessageManager.MessageCategory.BLINDFOLDED
);
return 1;
}
static int unblind(CommandContext<CommandSourceStack> context)
throws CommandSyntaxException {
ServerPlayer targetPlayer = EntityArgument.getPlayer(context, "player");
PlayerBindState state = PlayerBindState.getInstance(targetPlayer);
if (state == null) {
context
.getSource()
.sendFailure(Component.translatable("command.tiedup.error.no_state"));
return 0;
}
if (!state.isBlindfolded()) {
context
.getSource()
.sendFailure(
Component.translatable(
"command.tiedup.blindfold.not_blindfolded",
targetPlayer.getName().getString()
)
);
return 0;
}
state.takeBlindfoldOff();
CommandHelper.syncPlayerState(targetPlayer, state);
context
.getSource()
.sendSuccess(
() ->
Component.translatable(
"command.tiedup.blindfold.removed",
targetPlayer.getName().getString()
).withStyle(ChatFormatting.GREEN),
true
);
SystemMessageManager.sendToTarget(
context.getSource().getEntity(),
targetPlayer,
SystemMessageManager.MessageCategory.UNBLINDFOLDED
);
return 1;
}
}

View File

@@ -0,0 +1,286 @@
package com.tiedup.remake.commands.subcommands;
import com.tiedup.remake.network.ModNetwork;
import com.tiedup.remake.network.sync.PacketSyncBindState;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.tiedup.remake.commands.CommandHelper;
import com.tiedup.remake.core.SystemMessageManager;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import net.minecraft.ChatFormatting;
import net.minecraft.resources.ResourceLocation;
import com.tiedup.remake.state.PlayerBindState;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.commands.arguments.EntityArgument;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.item.ItemStack;
@SuppressWarnings("null")
public class CollarCommands {
public static void register(LiteralArgumentBuilder<CommandSourceStack> root) {
// /tiedup collar <player>
root.then(
Commands.literal("collar")
.requires(CommandHelper.REQUIRES_OP)
.then(
Commands.argument(
"player",
EntityArgument.player()
).executes(CollarCommands::collar)
)
);
// /tiedup takecollar <player>
root.then(
Commands.literal("takecollar")
.requires(CommandHelper.REQUIRES_OP)
.then(
Commands.argument(
"player",
EntityArgument.player()
).executes(CollarCommands::takecollar)
)
);
// /tiedup enslave <player>
root.then(
Commands.literal("enslave")
.requires(CommandHelper.REQUIRES_OP)
.then(
Commands.argument(
"player",
EntityArgument.player()
).executes(CollarCommands::enslave)
)
);
// /tiedup free <player>
root.then(
Commands.literal("free")
.requires(CommandHelper.REQUIRES_OP)
.then(
Commands.argument(
"player",
EntityArgument.player()
).executes(CollarCommands::free)
)
);
}
static int collar(CommandContext<CommandSourceStack> context)
throws CommandSyntaxException {
ServerPlayer targetPlayer = EntityArgument.getPlayer(context, "player");
PlayerBindState state = PlayerBindState.getInstance(targetPlayer);
if (state == null) {
context
.getSource()
.sendFailure(Component.translatable("command.tiedup.error.no_state"));
return 0;
}
if (state.hasCollar()) {
context
.getSource()
.sendFailure(
Component.translatable(
"command.tiedup.collar.already_collared",
targetPlayer.getName().getString()
)
);
return 0;
}
ItemStack collar = com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem.createStack(new net.minecraft.resources.ResourceLocation("tiedup", "classic_collar"));
if (context.getSource().getEntity() instanceof ServerPlayer executor) {
CollarHelper.addOwner(collar, executor);
}
state.putCollarOn(collar);
CommandHelper.syncPlayerState(targetPlayer, state);
context
.getSource()
.sendSuccess(
() ->
Component.translatable(
"command.tiedup.collar.collared",
targetPlayer.getName().getString()
).withStyle(ChatFormatting.GREEN),
true
);
SystemMessageManager.sendToTarget(
context.getSource().getEntity(),
targetPlayer,
SystemMessageManager.MessageCategory.COLLARED
);
return 1;
}
static int takecollar(CommandContext<CommandSourceStack> context)
throws CommandSyntaxException {
ServerPlayer targetPlayer = EntityArgument.getPlayer(context, "player");
PlayerBindState state = PlayerBindState.getInstance(targetPlayer);
if (state == null) {
context
.getSource()
.sendFailure(Component.translatable("command.tiedup.error.no_state"));
return 0;
}
if (!state.hasCollar()) {
context
.getSource()
.sendFailure(
Component.translatable(
"command.tiedup.collar.no_collar",
targetPlayer.getName().getString()
)
);
return 0;
}
state.takeCollarOff();
CommandHelper.syncPlayerState(targetPlayer, state);
context
.getSource()
.sendSuccess(
() ->
Component.translatable(
"command.tiedup.collar.removed",
targetPlayer.getName().getString()
).withStyle(ChatFormatting.GREEN),
true
);
SystemMessageManager.sendToTarget(
context.getSource().getEntity(),
targetPlayer,
SystemMessageManager.MessageCategory.UNCOLLARED
);
return 1;
}
static int enslave(CommandContext<CommandSourceStack> context)
throws CommandSyntaxException {
ServerPlayer targetPlayer = EntityArgument.getPlayer(context, "player");
PlayerBindState state = PlayerBindState.getInstance(targetPlayer);
if (state == null) {
context
.getSource()
.sendFailure(Component.translatable("command.tiedup.error.no_state"));
return 0;
}
// First fully restrain
if (!state.isTiedUp()) {
ItemStack ropes = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "ropes"));
state.putBindOn(ropes);
}
if (!state.isGagged()) {
ItemStack gag = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "cloth_gag"));
state.putGagOn(gag);
}
if (!state.isBlindfolded()) {
ItemStack blindfold = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_blindfold"));
state.putBlindfoldOn(blindfold);
}
if (!state.hasCollar()) {
ItemStack collar = com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem.createStack(new net.minecraft.resources.ResourceLocation("tiedup", "classic_collar"));
if (
context.getSource().getEntity() instanceof ServerPlayer executor
) {
CollarHelper.addOwner(collar, executor);
}
state.putCollarOn(collar);
}
if (!state.hasEarplugs()) {
ItemStack earplugs = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_earplugs"));
state.putEarplugsOn(earplugs);
}
// Capture target (this makes them a captive)
if (context.getSource().getEntity() instanceof ServerPlayer master) {
PlayerBindState masterState = PlayerBindState.getInstance(master);
if (masterState != null && masterState.getCaptorManager() != null) {
masterState.getCaptorManager().addCaptive(state);
}
}
CommandHelper.syncPlayerState(targetPlayer, state);
context
.getSource()
.sendSuccess(
() ->
Component.translatable(
"command.tiedup.collar.enslaved",
targetPlayer.getName().getString()
).withStyle(ChatFormatting.GREEN),
true
);
SystemMessageManager.sendEnslaved(
context.getSource().getEntity(),
targetPlayer
);
return 1;
}
static int free(CommandContext<CommandSourceStack> context)
throws CommandSyntaxException {
ServerPlayer targetPlayer = EntityArgument.getPlayer(context, "player");
PlayerBindState state = PlayerBindState.getInstance(targetPlayer);
if (state == null) {
context
.getSource()
.sendFailure(Component.translatable("command.tiedup.error.no_state"));
return 0;
}
if (!state.isCaptive()) {
context
.getSource()
.sendFailure(
Component.translatable(
"command.tiedup.collar.not_captured",
targetPlayer.getName().getString()
)
);
return 0;
}
state.free(true);
PacketSyncBindState statePacket = PacketSyncBindState.fromPlayer(
targetPlayer
);
if (statePacket != null) {
ModNetwork.sendToPlayer(statePacket, targetPlayer);
}
context
.getSource()
.sendSuccess(
() ->
Component.translatable(
"command.tiedup.collar.freed",
targetPlayer.getName().getString()
).withStyle(ChatFormatting.GREEN),
true
);
SystemMessageManager.sendFreed(targetPlayer);
return 1;
}
}

View File

@@ -7,6 +7,7 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.tiedup.remake.commands.CommandHelper;
import com.tiedup.remake.prison.PrisonerManager;
import com.tiedup.remake.prison.RansomRecord;
import net.minecraft.ChatFormatting;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.commands.arguments.EntityArgument;
@@ -77,9 +78,8 @@ public class DebtSubCommand {
.getSource()
.sendSuccess(
() ->
Component.literal(
target.getName().getString() +
" has no debt record."
Component.translatable(
"command.tiedup.debt.no_record", target.getName().getString()
),
false
);
@@ -94,16 +94,11 @@ public class DebtSubCommand {
.getSource()
.sendSuccess(
() ->
Component.literal(
target.getName().getString() +
" \u2014 Debt: " +
total +
" | Paid: " +
paid +
" | Remaining: " +
remaining +
" emeralds"
),
Component.translatable(
"command.tiedup.debt.show",
target.getName().getString(),
total, paid, remaining
).withStyle(ChatFormatting.YELLOW),
false
);
return 1;
@@ -121,9 +116,7 @@ public class DebtSubCommand {
context
.getSource()
.sendFailure(
Component.literal(
target.getName().getString() + " has no debt record."
)
Component.translatable("command.tiedup.debt.no_record", target.getName().getString())
);
return 0;
}
@@ -133,13 +126,10 @@ public class DebtSubCommand {
.getSource()
.sendSuccess(
() ->
Component.literal(
"Set " +
target.getName().getString() +
"'s total debt to " +
amount +
" emeralds."
),
Component.translatable(
"command.tiedup.debt.set",
target.getName().getString(), amount
).withStyle(ChatFormatting.GREEN),
true
);
return 1;
@@ -157,9 +147,7 @@ public class DebtSubCommand {
context
.getSource()
.sendFailure(
Component.literal(
target.getName().getString() + " has no debt record."
)
Component.translatable("command.tiedup.debt.no_record", target.getName().getString())
);
return 0;
}
@@ -169,14 +157,10 @@ public class DebtSubCommand {
.getSource()
.sendSuccess(
() ->
Component.literal(
"Added " +
amount +
" emeralds to " +
target.getName().getString() +
"'s debt. Remaining: " +
ransom.getRemainingDebt()
),
Component.translatable(
"command.tiedup.debt.added",
amount, target.getName().getString(), ransom.getRemainingDebt()
).withStyle(ChatFormatting.GREEN),
true
);
return 1;
@@ -194,9 +178,7 @@ public class DebtSubCommand {
context
.getSource()
.sendFailure(
Component.literal(
target.getName().getString() + " has no debt record."
)
Component.translatable("command.tiedup.debt.no_record", target.getName().getString())
);
return 0;
}
@@ -207,15 +189,10 @@ public class DebtSubCommand {
.getSource()
.sendSuccess(
() ->
Component.literal(
"Removed " +
amount +
" emeralds from " +
target.getName().getString() +
"'s debt. Remaining: " +
ransom.getRemainingDebt() +
(paid ? " (PAID OFF!)" : "")
),
Component.translatable(
paid ? "command.tiedup.debt.removed_paid" : "command.tiedup.debt.removed",
amount, target.getName().getString(), ransom.getRemainingDebt()
).withStyle(ChatFormatting.GREEN),
true
);
return 1;

View File

@@ -0,0 +1,140 @@
package com.tiedup.remake.commands.subcommands;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.tiedup.remake.commands.CommandHelper;
import com.tiedup.remake.core.SystemMessageManager;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import net.minecraft.ChatFormatting;
import net.minecraft.resources.ResourceLocation;
import com.tiedup.remake.state.PlayerBindState;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.commands.arguments.EntityArgument;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.item.ItemStack;
@SuppressWarnings("null")
public class GagCommands {
public static void register(LiteralArgumentBuilder<CommandSourceStack> root) {
// /tiedup gag <player>
root.then(
Commands.literal("gag")
.requires(CommandHelper.REQUIRES_OP)
.then(
Commands.argument(
"player",
EntityArgument.player()
).executes(GagCommands::gag)
)
);
// /tiedup ungag <player>
root.then(
Commands.literal("ungag")
.requires(CommandHelper.REQUIRES_OP)
.then(
Commands.argument(
"player",
EntityArgument.player()
).executes(GagCommands::ungag)
)
);
}
static int gag(CommandContext<CommandSourceStack> context)
throws CommandSyntaxException {
ServerPlayer targetPlayer = EntityArgument.getPlayer(context, "player");
PlayerBindState state = PlayerBindState.getInstance(targetPlayer);
if (state == null) {
context
.getSource()
.sendFailure(Component.translatable("command.tiedup.error.no_state"));
return 0;
}
if (state.isGagged()) {
context
.getSource()
.sendFailure(
Component.translatable(
"command.tiedup.gag.already_gagged",
targetPlayer.getName().getString()
)
);
return 0;
}
ItemStack gag = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "cloth_gag"));
state.putGagOn(gag);
CommandHelper.syncPlayerState(targetPlayer, state);
context
.getSource()
.sendSuccess(
() ->
Component.translatable(
"command.tiedup.gag.gagged",
targetPlayer.getName().getString()
).withStyle(ChatFormatting.GREEN),
true
);
SystemMessageManager.sendGagged(
context.getSource().getEntity(),
targetPlayer
);
return 1;
}
static int ungag(CommandContext<CommandSourceStack> context)
throws CommandSyntaxException {
ServerPlayer targetPlayer = EntityArgument.getPlayer(context, "player");
PlayerBindState state = PlayerBindState.getInstance(targetPlayer);
if (state == null) {
context
.getSource()
.sendFailure(Component.translatable("command.tiedup.error.no_state"));
return 0;
}
if (!state.isGagged()) {
context
.getSource()
.sendFailure(
Component.translatable(
"command.tiedup.gag.not_gagged",
targetPlayer.getName().getString()
)
);
return 0;
}
state.takeGagOff();
CommandHelper.syncPlayerState(targetPlayer, state);
context
.getSource()
.sendSuccess(
() ->
Component.translatable(
"command.tiedup.gag.removed",
targetPlayer.getName().getString()
).withStyle(ChatFormatting.GREEN),
true
);
SystemMessageManager.sendToTarget(
context.getSource().getEntity(),
targetPlayer,
SystemMessageManager.MessageCategory.UNGAGGED
);
return 1;
}
}

View File

@@ -6,6 +6,7 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.tiedup.remake.cells.ConfiscatedInventoryRegistry;
import com.tiedup.remake.commands.CommandHelper;
import com.tiedup.remake.core.SystemMessageManager;
import net.minecraft.ChatFormatting;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.commands.arguments.EntityArgument;
@@ -56,9 +57,8 @@ public class InventorySubCommand {
context
.getSource()
.sendFailure(
Component.literal(
targetPlayer.getName().getString() +
" has no confiscated inventory to restore"
Component.translatable(
"command.tiedup.inventory.no_confiscated", targetPlayer.getName().getString()
)
);
return 0;
@@ -71,10 +71,9 @@ public class InventorySubCommand {
.getSource()
.sendSuccess(
() ->
Component.literal(
"\u00a7aRestored confiscated inventory to " +
targetPlayer.getName().getString()
),
Component.translatable(
"command.tiedup.inventory.restored", targetPlayer.getName().getString()
).withStyle(ChatFormatting.GREEN),
true
);
SystemMessageManager.sendToPlayer(
@@ -88,9 +87,8 @@ public class InventorySubCommand {
context
.getSource()
.sendFailure(
Component.literal(
"Failed to restore inventory for " +
targetPlayer.getName().getString()
Component.translatable(
"command.tiedup.inventory.restore_failed", targetPlayer.getName().getString()
)
);
return 0;

View File

@@ -77,7 +77,7 @@ public class MasterTestSubCommand {
context
.getSource()
.sendFailure(
Component.literal("Failed to create Master entity")
Component.translatable("command.tiedup.master.spawn_failed")
);
return 0;
}
@@ -108,10 +108,8 @@ public class MasterTestSubCommand {
.getSource()
.sendSuccess(
() ->
Component.literal(
"Spawned Master '" +
finalName +
"' \u2014 you are now their pet."
Component.translatable(
"command.tiedup.master.spawned", finalName
),
true
);
@@ -133,7 +131,7 @@ public class MasterTestSubCommand {
context
.getSource()
.sendFailure(
Component.literal("No Master NPC found within 20 blocks")
Component.translatable("command.tiedup.master.no_master_nearby")
);
return 0;
}
@@ -157,8 +155,8 @@ public class MasterTestSubCommand {
.getSource()
.sendSuccess(
() ->
Component.literal(
"Forced " + finalName + " into HUMAN_CHAIR state"
Component.translatable(
"command.tiedup.master.forced_state", finalName, "HUMAN_CHAIR"
),
true
);
@@ -183,7 +181,7 @@ public class MasterTestSubCommand {
context
.getSource()
.sendFailure(
Component.literal("Unknown MasterState: " + taskName)
Component.translatable("command.tiedup.master.unknown_state", taskName)
);
return 0;
}
@@ -193,7 +191,7 @@ public class MasterTestSubCommand {
context
.getSource()
.sendFailure(
Component.literal("No Master NPC found within 20 blocks")
Component.translatable("command.tiedup.master.no_master_nearby")
);
return 0;
}
@@ -217,12 +215,8 @@ public class MasterTestSubCommand {
.getSource()
.sendSuccess(
() ->
Component.literal(
"Forced " +
finalName +
" into " +
targetState.name() +
" state"
Component.translatable(
"command.tiedup.master.forced_state", finalName, targetState.name()
),
true
);

View File

@@ -87,8 +87,8 @@ public class TestAnimSubCommand {
.getSource()
.sendSuccess(
() ->
Component.literal(
"Playing animation '" + anim + "' on " + name
Component.translatable(
"command.tiedup.testanim.playing", anim, name
),
false
);
@@ -116,7 +116,7 @@ public class TestAnimSubCommand {
context
.getSource()
.sendSuccess(
() -> Component.literal("Stopped animation on " + name),
() -> Component.translatable("command.tiedup.testanim.stopped", name),
false
);
return 1;

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

@@ -138,120 +138,17 @@ public class SystemMessageManager {
ERROR, // Generic error
}
// MESSAGE TEMPLATES
// TRANSLATION KEYS
/**
* Get the raw message template for a category.
* Use this when you need to customize the message.
* Get the translation key for a category.
* Keys follow the pattern: msg.tiedup.system.&lt;category_lowercase&gt;
*
* @param category The message category
* @return The template string (may contain %s placeholders)
* @return The translation key (for use with Component.translatable)
*/
public static String getTemplate(MessageCategory category) {
return getMessageTemplate(category);
}
/**
* Get message template for a category.
* Use %s for entity name placeholder.
*/
private static String getMessageTemplate(MessageCategory category) {
return switch (category) {
// Restraint actions
case BEING_TIED -> "%s is tying you up!";
case TIED_UP -> "%s tied you up, you can't move!";
case BEING_GAGGED -> "%s is gagging you!";
case GAGGED -> "%s gagged you, you can't speak!";
case BEING_BLINDFOLDED -> "%s is blindfolding you!";
case BLINDFOLDED -> "%s blindfolded you, you can't see!";
case BEING_COLLARED -> "%s is putting a collar on you!";
case COLLARED -> "%s collared you!";
case EARPLUGS_ON -> "%s put earplugs on you!";
case MITTENS_ON -> "%s put mittens on you!";
case ENSLAVED -> "You have been enslaved by %s!";
// Restraint actions (kidnapper's perspective)
case TYING_TARGET -> "You are tying %s...";
case TIED_TARGET -> "You tied %s!";
case GAGGING_TARGET -> "You are gagging %s...";
case GAGGED_TARGET -> "You gagged %s!";
case BLINDFOLDING_TARGET -> "You are blindfolding %s...";
case BLINDFOLDED_TARGET -> "You blindfolded %s!";
case COLLARING_TARGET -> "You are collaring %s...";
case COLLARED_TARGET -> "You collared %s!";
// Release actions
case UNTIED -> "%s untied you!";
case UNGAGGED -> "%s removed your gag!";
case UNBLINDFOLDED -> "%s removed your blindfold!";
case UNCOLLARED -> "%s removed your collar!";
case FREED -> "You have been freed!";
// Struggle
case STRUGGLE_SUCCESS -> "You feel the ropes loosening...";
case STRUGGLE_FAIL -> "You struggle against the ropes, but they hold tight.";
case STRUGGLE_BROKE_FREE -> "You broke free!";
case STRUGGLE_SHOCKED -> "You were shocked for struggling!";
case STRUGGLE_COLLAR_SUCCESS -> "You manage to damage the lock!";
case STRUGGLE_COLLAR_FAIL -> "You try to reach the lock, but can't get a good grip.";
// Restrictions (Tied)
case CANT_MOVE -> "You can't move while tied!";
case CANT_ATTACK_TIED -> "You can't attack while tied!";
case CANT_USE_ITEM_TIED -> "You can't use items while tied!";
case CANT_OPEN_INVENTORY -> "You can't open inventory while tied!";
case CANT_INTERACT_TIED -> "You can't interact while tied!";
case CANT_SPEAK -> "You can't speak while gagged!";
case CANT_SEE -> "You can't see while blindfolded!";
case CANT_BREAK_TIED -> "You can't break blocks while tied!";
case CANT_PLACE_TIED -> "You can't place blocks while tied!";
case NO_ELYTRA -> "You can't fly with elytra while tied!";
// Restrictions (Mittens)
case CANT_ATTACK_MITTENS -> "You can't attack with mittens on!";
case CANT_USE_ITEM_MITTENS -> "You can't use items with mittens on!";
case CANT_INTERACT_MITTENS -> "You can't interact with mittens on!";
case CANT_BREAK_MITTENS -> "You can't break blocks with mittens on!";
case CANT_PLACE_MITTENS -> "You can't place blocks with mittens on!";
// Slave system
case SLAVE_COMMAND -> "Your master commands: %s";
case SLAVE_SHOCK -> "You've been shocked!";
case GPS_ZONE_VIOLATION -> "You've been shocked! Return back to your allowed area!";
case GPS_OWNER_ALERT -> "ALERT: %s is outside the safe zone!";
case SLAVE_JOB_ASSIGNED -> "Job assigned: bring %s";
case SLAVE_JOB_COMPLETE -> "Job complete! You are free.";
case SLAVE_JOB_FAILED -> "Job failed!";
case SLAVE_JOB_LAST_CHANCE -> "LAST CHANCE! Next failure means death!";
case SLAVE_JOB_KILLED -> "You were executed for failing your task.";
// Tighten
case BINDS_TIGHTENED -> "%s tightened your binds!";
// Tools & Items
case KEY_CLAIMED -> "Key claimed and linked to %s!";
case KEY_NOT_OWNER -> "You don't own this key!";
case KEY_WRONG_TARGET -> "This key doesn't fit this collar!";
case LOCATOR_CLAIMED -> "Locator claimed!";
case LOCATOR_NOT_OWNER -> "You don't own this locator!";
case LOCATOR_DETECTED -> "Target detected: %s";
case SHOCKER_CLAIMED -> "Shocker claimed!";
case SHOCKER_NOT_OWNER -> "You don't own this shocker!";
case SHOCKER_MODE_SET -> "Shocker mode: %s";
case SHOCKER_TRIGGERED -> "Shocked %s!";
case RAG_DRY -> "The rag is dry - soak it first";
case RAG_SOAKED -> "You soaked the rag with chloroform";
case RAG_EVAPORATED -> "The chloroform has evaporated";
// Bounty
case BOUNTY_CREATED -> "Bounty created on %s!";
case BOUNTY_CLAIMED -> "You claimed the bounty on %s!";
case BOUNTY_EXPIRED -> "Bounty on %s expired";
// Cell System
case PRISONER_ARRIVED -> "%s has been placed in your cell";
case PRISONER_ESCAPED -> "%s has escaped from your cell!";
case PRISONER_RELEASED -> "%s has been released from your cell";
case CELL_BREACH -> "Your cell wall has been breached!";
case CELL_ASSIGNED -> "You have been assigned to %s's cell";
case CELL_CREATED -> "Cell created successfully";
case CELL_DELETED -> "Cell deleted";
case CELL_RENAMED -> "Cell renamed to: %s";
// Generic
case INFO -> "%s";
case WARNING -> "%s";
case ERROR -> "%s";
};
public static String getTranslationKey(MessageCategory category) {
return "msg.tiedup.system." + category.name().toLowerCase();
}
/**
@@ -373,11 +270,11 @@ public class SystemMessageManager {
/**
* Send a system message to a player's action bar.
* Uses category template with entity name.
* Uses translatable category with entity name as argument.
*
* @param player The player to send to
* @param category The message category
* @param actor The entity performing the action (for %s replacement)
* @param actor The entity performing the action (for %1$s replacement)
*/
public static void sendToPlayer(
Player player,
@@ -387,14 +284,23 @@ public class SystemMessageManager {
if (player == null) return;
String actorName = actor != null ? getEntityName(actor) : "Someone";
String message = String.format(getMessageTemplate(category), actorName);
MutableComponent component = Component.translatable(
getTranslationKey(category), actorName
).withStyle(style -> style.withColor(getCategoryColor(category)));
sendToPlayer(player, message, getCategoryColor(category));
player.displayClientMessage(component, true);
TiedUpMod.LOGGER.debug(
"[SystemMessage] -> {}: {} ({})",
player.getName().getString(),
getTranslationKey(category),
actorName
);
}
/**
* Send a system message to a player's action bar.
* Uses category template without entity (for messages that don't need one).
* Uses translatable category without arguments.
*
* @param player The player to send to
* @param category The message category
@@ -402,12 +308,22 @@ public class SystemMessageManager {
public static void sendToPlayer(Player player, MessageCategory category) {
if (player == null) return;
String message = getMessageTemplate(category);
sendToPlayer(player, message, getCategoryColor(category));
MutableComponent component = Component.translatable(
getTranslationKey(category)
).withStyle(style -> style.withColor(getCategoryColor(category)));
player.displayClientMessage(component, true);
TiedUpMod.LOGGER.debug(
"[SystemMessage] -> {}: {}",
player.getName().getString(),
getTranslationKey(category)
);
}
/**
* Send a custom system message to a player's action bar.
* Uses literal text (for dynamic/non-translatable messages).
*
* @param player The player to send to
* @param message The message to send
@@ -420,12 +336,10 @@ public class SystemMessageManager {
) {
if (player == null || message == null) return;
// Works on both client and server
MutableComponent component = Component.literal(message).withStyle(
style -> style.withColor(color)
);
// true = action bar (above hotbar), false = chat
player.displayClientMessage(component, true);
TiedUpMod.LOGGER.debug(
@@ -437,6 +351,7 @@ public class SystemMessageManager {
/**
* Send a custom system message to a player's CHAT.
* Uses literal text (for dynamic/non-translatable messages).
*
* @param player The player to send to
* @param message The message to send
@@ -453,7 +368,6 @@ public class SystemMessageManager {
style -> style.withColor(color)
);
// false = chat
player.displayClientMessage(component, false);
TiedUpMod.LOGGER.debug(
@@ -464,19 +378,21 @@ public class SystemMessageManager {
}
/**
* Send a system message to a player's CHAT using a category.
* Send a system message to a player's CHAT using a translatable category.
*/
public static void sendChatToPlayer(
Player player,
MessageCategory category
) {
if (player == null) return;
String message = getMessageTemplate(category);
sendChatToPlayer(player, message, getCategoryColor(category));
MutableComponent component = Component.translatable(
getTranslationKey(category)
).withStyle(style -> style.withColor(getCategoryColor(category)));
player.displayClientMessage(component, false);
}
/**
* Send a system message to a player's CHAT using a category and actor.
* Send a system message to a player's CHAT using a translatable category and actor.
*/
public static void sendChatToPlayer(
Player player,
@@ -485,8 +401,10 @@ public class SystemMessageManager {
) {
if (player == null) return;
String actorName = actor != null ? getEntityName(actor) : "Someone";
String message = String.format(getMessageTemplate(category), actorName);
sendChatToPlayer(player, message, getCategoryColor(category));
MutableComponent component = Component.translatable(
getTranslationKey(category), actorName
).withStyle(style -> style.withColor(getCategoryColor(category)));
player.displayClientMessage(component, false);
}
/**
@@ -494,7 +412,7 @@ public class SystemMessageManager {
*
* @param player The player to send to
* @param category The category (for color)
* @param customMessage The custom message text
* @param customMessage The custom message text (literal, not translatable)
*/
public static void sendToPlayer(
Player player,
@@ -505,7 +423,27 @@ public class SystemMessageManager {
}
/**
* Send a message with resistance info appended.
* Send a translatable message with string arguments.
* Uses the category's translation key and color.
*
* @param player The player to send to
* @param category The message category
* @param args Arguments for the translation (replaces %1$s, %2$s, etc.)
*/
public static void sendTranslatable(
Player player,
MessageCategory category,
Object... args
) {
if (player == null) return;
MutableComponent component = Component.translatable(
getTranslationKey(category), args
).withStyle(style -> style.withColor(getCategoryColor(category)));
player.displayClientMessage(component, true);
}
/**
* Send a translatable message with resistance info appended.
*
* @param player The player to send to
* @param category The message category
@@ -518,9 +456,13 @@ public class SystemMessageManager {
) {
if (player == null) return;
String message =
getMessageTemplate(category) + " (Resistance: " + resistance + ")";
sendToPlayer(player, message, getCategoryColor(category));
MutableComponent component = Component.translatable(
getTranslationKey(category)
).append(
Component.translatable("msg.tiedup.system.resistance_suffix", resistance)
).withStyle(style -> style.withColor(getCategoryColor(category)));
player.displayClientMessage(component, true);
}
// SEND METHODS - TO NEARBY PLAYERS
@@ -695,15 +637,10 @@ public class SystemMessageManager {
*/
public static void sendJobAssigned(Player player, String itemName) {
if (player == null) return;
String message = String.format(
getMessageTemplate(MessageCategory.SLAVE_JOB_ASSIGNED),
itemName
);
sendToPlayer(
player,
message,
getCategoryColor(MessageCategory.SLAVE_JOB_ASSIGNED)
);
MutableComponent component = Component.translatable(
getTranslationKey(MessageCategory.SLAVE_JOB_ASSIGNED), itemName
).withStyle(style -> style.withColor(getCategoryColor(MessageCategory.SLAVE_JOB_ASSIGNED)));
player.displayClientMessage(component, true);
}
// UTILITY

View File

@@ -581,6 +581,14 @@ public class TiedUpMod {
LOGGER.info(
"Registered FurnitureServerReloadListener for data-driven furniture definitions"
);
// Data-driven room theme definitions (server-side, from data/<namespace>/tiedup_room_themes/)
event.addListener(
new com.tiedup.remake.worldgen.RoomThemeReloadListener()
);
LOGGER.info(
"Registered RoomThemeReloadListener for data-driven room themes"
);
}
}
}

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;
@@ -963,6 +889,10 @@ public class EntityKidnapperMerchant extends EntityKidnapperElite {
// Clear trading players to prevent dangling references
if (!this.level().isClientSide) {
int count = tradingPlayers.size();
// Clean up reverse-lookup map BEFORE clearing to prevent memory leak
for (UUID playerUuid : tradingPlayers) {
playerToMerchant.remove(playerUuid);
}
this.tradingPlayers.clear();
if (count > 0) {
TiedUpMod.LOGGER.debug(

View File

@@ -1072,7 +1072,7 @@ public class EntityMaster extends EntityKidnapperElite {
if (
tag != null &&
tag.getBoolean(
com.tiedup.remake.state.HumanChairHelper.NBT_KEY
com.tiedup.remake.util.HumanChairHelper.NBT_KEY
)
) {
bindState.unequip(BodyRegionV2.ARMS);

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,9 +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.ModItems;
import com.tiedup.remake.items.base.BindVariant;
import com.tiedup.remake.state.HumanChairHelper;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import net.minecraft.resources.ResourceLocation;
import com.tiedup.remake.util.HumanChairHelper;
import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2;
import java.util.EnumSet;
@@ -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;
}
@@ -387,26 +384,27 @@ public class GenericKnife extends Item implements IKnife {
return;
}
// Accessory IS locked - reduce lock resistance
// Accessory IS locked - reduce lock resistance via knife cut progress
ILockable lockable = (ILockable) accessory.getItem();
int currentRes = lockable.getCurrentLockResistance(accessory);
int newRes = Math.max(0, currentRes - speed);
lockable.setCurrentLockResistance(accessory, newRes);
int cutProgress = com.tiedup.remake.util.ItemNBTHelper.getInt(accessory, "knifeCutProgress");
int newProgress = cutProgress + speed;
int lockResistance = com.tiedup.remake.core.SettingsAccessor.getPadlockResistance(null);
com.tiedup.remake.util.ItemNBTHelper.setInt(accessory, "knifeCutProgress", newProgress);
TiedUpMod.LOGGER.debug(
"[GenericKnife] {} cutting {} lock: resistance {} -> {}",
"[GenericKnife] {} cutting {} lock: progress {} / {}",
player.getName().getString(),
target,
currentRes,
newRes
newProgress,
lockResistance
);
// Check if lock is destroyed
if (newRes <= 0) {
if (newProgress >= lockResistance) {
// Destroy the lock (remove padlock, clear lock state)
lockable.setLockedByKeyUUID(accessory, null); // Unlocks and clears locked state
lockable.setLockable(accessory, false); // Remove padlock entirely
lockable.clearLockResistance(accessory);
com.tiedup.remake.util.ItemNBTHelper.remove(accessory, "knifeCutProgress");
lockable.setJammed(accessory, false);
state.clearKnifeCutTarget();

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

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