55 Commits

Author SHA1 Message Date
NotEvil
fd60086322 feat(i18n): complete migration — items, entities, AI goals, GUI screens
119 new translation keys across 3 domains:
- Items/Blocks/Misc (46 keys): tooltips, action messages, trap states
- Entities/AI Goals (55 keys): NPC speech, maid/master/guard messages
- Client GUI (18 keys): widget labels, screen buttons, merchant display

Remaining 119 Component.literal() are all intentional:
- Debug/Admin/Command wands (47) — dev tools, not player-facing
- Entity display names (~25) — dynamic getNpcName() calls
- Empty string roots (~15) — .append() chain bases
- User-typed text (~10) — /me, /pm, /norp chat content
- Runtime data (~12) — StringBuilder, gag muffling, MCA compat
2026-04-16 12:33:13 +02:00
NotEvil
9b2c5dec8e chore(P2): V1 zombie comments cleanup + i18n events/network/items
Zombie comments (14 files):
- Replace references to deleted V1 classes (ItemBind, ItemCollar, IBondageItem,
  BindVariant, etc.) with V2 equivalents or past-tense historical notes
- "V1 path" → "legacy slot-index path" for still-active legacy codepaths
- Clean misleading javadoc that implied V1 code was still present

i18n (13 files, 31 new keys):
- Events: 12 Component.literal() → translatable (labor tools, gag eat,
  death escape, debt, punishment, grace, camp protection, chest, maid)
- Network: 12 Component.literal() → translatable (trader messages,
  lockpick jam, slave freedom warnings)
- Items: 2 Component.literal() → translatable (ItemOwnerTarget tooltips)
2026-04-16 11:20:17 +02:00
NotEvil
371a138b71 fix(P1+P2): swarm review — UX fixes + V1 dead code cleanup
P1 — UX visible:
- Add 4 missing translations (leather_mittens, ball_gag_3d, taser, debug_wand)
- Fix earplugs resistance bucket: "blindfold" → "earplug"
- Split mixin config: core (require=1) + compat MCA (require=0)
- Bump PROTOCOL_VERSION from "1" to "2"
- Add debug_wand item model (no texture — debug only)
- Fix 3 display_name inconsistencies (chain, vine_seed, shock_collar_auto)

P2 — Dead code cleanup:
- Delete IHas3DModelConfig + Model3DConfig (zero imports)
- Remove MovementStyleResolver.resolveV1Fallback() + V1Fallback record + V1 constants
- Remove AnimationTickHandler V1 fallback block + buildAnimationId()
- Document PlayerEquipment.equipInRegion() bypass as intentional (force-equip paths)
2026-04-16 10:58:44 +02:00
NotEvil
d75b74f9f9 fix(P0): 3 exploit fixes from swarm review
- RISK-006: Mittens bypass lock/unlock — add HANDS check in PacketV2SelfLock/Unlock
- RISK-002: Struggle re-roll exploit — reject-if-active in startContinuous*Session()
- RISK-003: Non-V2 locked items bypass conflict resolution — check ILockable before swap
2026-04-16 10:49:04 +02:00
bce0598059 Merge pull request 'chore/audit-uc02-roomtheme-datadriven' (#14) from chore/audit-uc02-roomtheme-datadriven into develop
Reviewed-on: #14
2026-04-16 00:38:58 +00:00
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
360 changed files with 9497 additions and 10715 deletions

1
.gitignore vendored
View File

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

View File

@@ -104,6 +104,7 @@ minecraft {
// Mixin config arg
args '-mixin.config=tiedup.mixins.json'
args '-mixin.config=tiedup-compat.mixins.json'
}
server {
@@ -116,6 +117,7 @@ minecraft {
// Mixin config arg
args '-mixin.config=tiedup.mixins.json'
args '-mixin.config=tiedup-compat.mixins.json'
}
// Additional client instances for multiplayer testing
@@ -155,6 +157,7 @@ sourceSets.main.resources { srcDir 'src/generated/resources' }
mixin {
add sourceSets.main, 'tiedup.refmap.json'
config 'tiedup.mixins.json'
config 'tiedup-compat.mixins.json'
}
repositories {
@@ -263,7 +266,7 @@ tasks.named('jar', Jar).configure {
'Implementation-Version' : project.jar.archiveVersion,
'Implementation-Vendor' : mod_authors,
'Implementation-Timestamp': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"),
'MixinConfigs' : 'tiedup.mixins.json'
'MixinConfigs' : 'tiedup.mixins.json,tiedup-compat.mixins.json'
])
}

View File

@@ -244,7 +244,7 @@ public class BlockKidnapBomb
beTag.contains("collar")
) {
tooltip.add(
Component.literal("Loaded:").withStyle(
Component.translatable("block.tiedup.kidnap_bomb.loaded").withStyle(
ChatFormatting.YELLOW
)
);
@@ -282,12 +282,12 @@ public class BlockKidnapBomb
);
} else {
tooltip.add(
Component.literal("Empty").withStyle(ChatFormatting.GREEN)
Component.translatable("block.tiedup.kidnap_bomb.empty").withStyle(ChatFormatting.GREEN)
);
}
} else {
tooltip.add(
Component.literal("Empty").withStyle(ChatFormatting.GREEN)
Component.translatable("block.tiedup.kidnap_bomb.empty").withStyle(ChatFormatting.GREEN)
);
}
}

View File

@@ -328,7 +328,7 @@ public class BlockRopeTrap extends Block implements EntityBlock, ICanBeLoaded {
// Check if armed
if (beTag.contains("bind")) {
tooltip.add(
Component.literal("Armed").withStyle(
Component.translatable("block.tiedup.trap.armed").withStyle(
ChatFormatting.DARK_RED
)
);
@@ -366,14 +366,14 @@ public class BlockRopeTrap extends Block implements EntityBlock, ICanBeLoaded {
);
} else {
tooltip.add(
Component.literal("Disarmed").withStyle(
Component.translatable("block.tiedup.trap.disarmed").withStyle(
ChatFormatting.GREEN
)
);
}
} else {
tooltip.add(
Component.literal("Disarmed").withStyle(ChatFormatting.GREEN)
Component.translatable("block.tiedup.trap.disarmed").withStyle(ChatFormatting.GREEN)
);
}
}

View File

@@ -200,7 +200,7 @@ public class BlockTrappedChest extends ChestBlock implements ICanBeLoaded {
beTag.contains("collar")
) {
tooltip.add(
Component.literal("Armed").withStyle(
Component.translatable("block.tiedup.trap.armed").withStyle(
ChatFormatting.DARK_RED
)
);
@@ -231,14 +231,14 @@ public class BlockTrappedChest extends ChestBlock implements ICanBeLoaded {
);
} else {
tooltip.add(
Component.literal("Disarmed").withStyle(
Component.translatable("block.tiedup.trap.disarmed").withStyle(
ChatFormatting.GREEN
)
);
}
} else {
tooltip.add(
Component.literal("Disarmed").withStyle(ChatFormatting.GREEN)
Component.translatable("block.tiedup.trap.disarmed").withStyle(ChatFormatting.GREEN)
);
}
}

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

@@ -382,9 +382,8 @@ public class BountyManager extends SavedData {
server
.getPlayerList()
.broadcastSystemMessage(
Component.literal("[Bounty] " + message).withStyle(
ChatFormatting.GOLD
),
Component.translatable("msg.tiedup.bounty.broadcast", message)
.withStyle(ChatFormatting.GOLD),
false
);
}

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);
@@ -250,10 +250,8 @@ public final class CampLifecycleManager {
// Notify prisoner
prisoner.sendSystemMessage(
Component.literal("Your captor has died. You are FREE!").withStyle(
ChatFormatting.GREEN,
ChatFormatting.BOLD
)
Component.translatable("msg.tiedup.camp.captor_died")
.withStyle(ChatFormatting.GREEN, ChatFormatting.BOLD)
);
// Grant grace period (5 minutes = 6000 ticks)
@@ -261,9 +259,8 @@ public final class CampLifecycleManager {
manager.release(prisoner.getUUID(), level.getGameTime(), 6000);
prisoner.sendSystemMessage(
Component.literal(
"You have 5 minutes of protection from kidnappers."
).withStyle(ChatFormatting.AQUA)
Component.translatable("msg.tiedup.camp.grace_period")
.withStyle(ChatFormatting.AQUA)
);
TiedUpMod.LOGGER.info(
@@ -285,8 +282,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

@@ -7,16 +7,13 @@ import com.tiedup.remake.client.animation.PendingAnimationManager;
import com.tiedup.remake.client.animation.context.AnimationContext;
import com.tiedup.remake.client.animation.context.AnimationContextResolver;
import com.tiedup.remake.client.animation.context.RegionBoneMapper;
import com.tiedup.remake.client.animation.util.AnimationIdBuilder;
import com.tiedup.remake.client.events.CellHighlightHandler;
import com.tiedup.remake.client.events.LeashProxyClientHandler;
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.util.HumanChairHelper;
import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.IV2BondageEquipment;
@@ -204,30 +201,11 @@ public class AnimationTickHandler {
context,
allOwnedParts
);
// Clear V1 tracking so transition back works
// Clear legacy tracking so transition back works
AnimationStateRegistry.getLastAnimId().remove(uuid);
} else {
// V1 fallback
if (GltfAnimationApplier.hasActiveState(player)) {
GltfAnimationApplier.clearV2Animation(player);
}
String animId = buildAnimationId(player, state);
String lastId = AnimationStateRegistry.getLastAnimId().get(
uuid
);
if (!animId.equals(lastId)) {
boolean success = BondageAnimationManager.playAnimation(
player,
animId
);
if (success) {
AnimationStateRegistry.getLastAnimId().put(
uuid,
animId
);
}
}
}
// No V2 items with GLB models — nothing to animate.
// Items without data-driven GLB definitions are not animated.
} else if (wasTied) {
// Was tied, now free - stop all animations
if (GltfAnimationApplier.hasActiveState(player)) {
@@ -241,57 +219,6 @@ public class AnimationTickHandler {
AnimationStateRegistry.getLastTiedState().put(uuid, isTied);
}
/**
* Build animation ID from player's current state (V1 path).
*/
private static String buildAnimationId(
Player player,
PlayerBindState state
) {
ItemStack bind = state.getEquipment(BodyRegionV2.ARMS);
PoseType poseType = PoseType.STANDARD;
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);
}
// Derive bound state from V2 regions (works client-side, synced via capability)
boolean armsBound = V2EquipmentHelper.isRegionOccupied(
player,
BodyRegionV2.ARMS
);
boolean legsBound = V2EquipmentHelper.isRegionOccupied(
player,
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);
}
boolean isStruggling = state.isStruggling();
boolean isSneaking = player.isCrouching();
boolean isMoving =
player.getDeltaMovement().horizontalDistanceSqr() > 1e-6;
// Build animation ID with sneak and movement support
return AnimationIdBuilder.build(
poseType,
armsBound,
legsBound,
null,
isStruggling,
true,
isSneaking,
isMoving
);
}
/**
* Player logout event - cleanup animation data.
*/

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;
@@ -81,7 +82,7 @@ public class NpcAnimationTickHandler {
* (base posture) and an item layer (GLB-driven bones). Sitting and kneeling are
* handled by the context resolver, so the V2 path now covers all postures.
*
* <p>V1 fallback: if no V2 GLB model is found, falls back to JSON-based
* <p>Legacy fallback: if no GLB model is found, falls back to JSON-based
* PlayerAnimator animations via {@link BondageAnimationManager}.
*/
private static void updateNpcAnimation(AbstractTiedUpNpc entity) {
@@ -119,7 +120,7 @@ public class NpcAnimationTickHandler {
);
lastNpcAnimId.remove(uuid);
} else {
// V1 fallback: JSON-based PlayerAnimator animations
// Legacy fallback: JSON-based PlayerAnimator animations
if (GltfAnimationApplier.hasActiveState(entity)) {
GltfAnimationApplier.clearV2Animation(entity);
}
@@ -147,7 +148,7 @@ public class NpcAnimationTickHandler {
}
/**
* Build animation ID for an NPC from its current state (V1 path).
* Build animation ID for an NPC from its current state (legacy JSON path).
*/
private static String buildNpcAnimationId(AbstractTiedUpNpc entity) {
// Determine position prefix for SIT/KNEEL poses
@@ -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);
// Legacy 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

@@ -198,12 +198,12 @@ public abstract class BaseAdjustmentScreen extends BaseScreen {
int totalWidth = buttonWidth * 3 + MARGIN_S * 2;
int actionStartX = this.leftPos + (this.imageWidth - totalWidth) / 2;
resetButton = Button.builder(Component.literal("0"), b -> resetValue())
resetButton = Button.builder(Component.translatable("gui.tiedup.adjustment.btn.reset"), b -> resetValue())
.bounds(actionStartX, actionY, buttonWidth, BUTTON_HEIGHT)
.build();
this.addRenderableWidget(resetButton);
decrementButton = Button.builder(Component.literal("-0.25"), b ->
decrementButton = Button.builder(Component.translatable("gui.tiedup.adjustment.btn.decrement"), b ->
slider.decrement()
)
.bounds(
@@ -215,7 +215,7 @@ public abstract class BaseAdjustmentScreen extends BaseScreen {
.build();
this.addRenderableWidget(decrementButton);
incrementButton = Button.builder(Component.literal("+0.25"), b ->
incrementButton = Button.builder(Component.translatable("gui.tiedup.adjustment.btn.increment"), b ->
slider.increment()
)
.bounds(
@@ -235,14 +235,14 @@ public abstract class BaseAdjustmentScreen extends BaseScreen {
int totalWidth = buttonWidth * 3 + MARGIN_S * 2;
int scaleStartX = this.leftPos + (this.imageWidth - totalWidth) / 2;
scaleResetButton = Button.builder(Component.literal("1x"), b ->
scaleResetButton = Button.builder(Component.translatable("gui.tiedup.adjustment.btn.scale_reset"), b ->
applyScale(AdjustmentHelper.DEFAULT_SCALE)
)
.bounds(scaleStartX, scaleY, buttonWidth, BUTTON_HEIGHT)
.build();
this.addRenderableWidget(scaleResetButton);
scaleDecrementButton = Button.builder(Component.literal("-0.1"), b ->
scaleDecrementButton = Button.builder(Component.translatable("gui.tiedup.adjustment.btn.scale_decrement"), b ->
applyScale(currentScaleValue - AdjustmentHelper.SCALE_STEP)
)
.bounds(
@@ -254,7 +254,7 @@ public abstract class BaseAdjustmentScreen extends BaseScreen {
.build();
this.addRenderableWidget(scaleDecrementButton);
scaleIncrementButton = Button.builder(Component.literal("+0.1"), b ->
scaleIncrementButton = Button.builder(Component.translatable("gui.tiedup.adjustment.btn.scale_increment"), b ->
applyScale(currentScaleValue + AdjustmentHelper.SCALE_STEP)
)
.bounds(

View File

@@ -565,7 +565,7 @@ public class CellManagerScreen extends BaseScreen {
if (cell.prisoners.isEmpty()) {
graphics.drawString(
this.font,
Component.literal(" \u2514\u2500 ")
Component.translatable("gui.tiedup.cell_manager.tree_prefix")
.append(
Component.translatable(
"gui.tiedup.cell_manager.label.empty"

View File

@@ -548,12 +548,9 @@ public class CommandWandScreen extends Screen {
}
private void addFollowDistanceCycleButton(int x, int y, int width) {
String modeText = switch (followDistanceMode) {
case "HEEL" -> "H";
case "CLOSE" -> "C";
case "FAR" -> "F";
default -> "?";
};
Component modeLabel = Component.translatable(
"gui.tiedup.command_wand.follow_distance.abbrev." + followDistanceMode.toLowerCase()
);
Component tooltip = Component.translatable(
"gui.tiedup.command_wand.follow_distance.tooltip"
@@ -571,7 +568,7 @@ public class CommandWandScreen extends Screen {
)
);
Button btn = Button.builder(Component.literal(modeText), b ->
Button btn = Button.builder(modeLabel, b ->
cycleFollowDistance()
)
.bounds(x, y, width, BUTTON_HEIGHT)
@@ -857,7 +854,7 @@ public class CommandWandScreen extends Screen {
) {
graphics.renderTooltip(
this.font,
Component.literal((int) hunger + "%"),
Component.translatable("gui.tiedup.command_wand.percent", (int) hunger),
mouseX,
mouseY
);
@@ -871,7 +868,7 @@ public class CommandWandScreen extends Screen {
) {
graphics.renderTooltip(
this.font,
Component.literal((int) rest + "%"),
Component.translatable("gui.tiedup.command_wand.percent", (int) rest),
mouseX,
mouseY
);

View File

@@ -216,24 +216,24 @@ public class MerchantTradingScreen extends BaseScreen {
int goldIngots = countItemInInventory(player, Items.GOLD_INGOT);
int goldNuggets = countItemInInventory(player, Items.GOLD_NUGGET);
Component goldText = Component.literal("Your Gold: ")
Component goldText = Component.translatable("gui.tiedup.merchant.your_gold")
.append(
Component.literal(goldIngots + "x ").withStyle(style ->
Component.translatable("gui.tiedup.merchant.gold_amount", goldIngots).withStyle(style ->
style.withColor(0xFFFFD700)
)
)
.append(
Component.literal("").withStyle(style ->
Component.translatable("gui.tiedup.merchant.gold_icon").withStyle(style ->
style.withColor(0xFFFFD700)
)
)
.append(
Component.literal("+ " + goldNuggets + "x ").withStyle(
Component.translatable("gui.tiedup.merchant.nugget_amount", goldNuggets).withStyle(
style -> style.withColor(0xFFFFA500)
)
)
.append(
Component.literal("").withStyle(style ->
Component.translatable("gui.tiedup.merchant.nugget_icon").withStyle(style ->
style.withColor(0xFFFFA500)
)
);

View File

@@ -53,7 +53,7 @@ public class NpcInventoryScreen
Inventory playerInventory,
Component title
) {
super(menu, playerInventory, Component.literal(menu.getNpcName()));
super(menu, playerInventory, Component.translatable("gui.tiedup.npc_inventory.title_name", menu.getNpcName()));
// Calculate rows from NPC inventory size
this.npcRows = (menu.getNpcSlotCount() + 8) / 9;

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;
@@ -86,7 +86,7 @@ public class ActionPanel extends AbstractWidget {
private int hoveredIndex = -1;
public ActionPanel(int x, int y, int width, int height) {
super(x, y, width, height, Component.literal("Actions"));
super(x, y, width, height, Component.translatable("gui.tiedup.action_panel"));
}
public void setMode(ScreenMode mode) {
@@ -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

@@ -51,7 +51,7 @@ public class ItemPickerOverlay extends AbstractWidget {
private int screenHeight;
public ItemPickerOverlay() {
super(0, 0, 0, 0, Component.literal("Item Picker"));
super(0, 0, 0, 0, Component.translatable("gui.tiedup.item_picker"));
this.active = false;
this.visible = false;
}

View File

@@ -98,7 +98,7 @@ public class RegionTabBar extends AbstractWidget {
private LivingEntity targetEntity;
public RegionTabBar(int x, int y, int width) {
super(x, y, width, TAB_HEIGHT, Component.literal("Tab Bar"));
super(x, y, width, TAB_HEIGHT, Component.translatable("gui.tiedup.tab_bar.label"));
}
public void setOnTabChanged(Consumer<BodyTab> callback) {

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

@@ -40,7 +40,7 @@ public class StatusBarWidget extends AbstractWidget {
private static final int CLOSE_BTN_HEIGHT = 22;
public StatusBarWidget(int x, int y, int width, int height) {
super(x, y, width, height, Component.literal("Status Bar"));
super(x, y, width, height, Component.translatable("gui.tiedup.status_bar"));
}
public void setMode(ActionPanel.ScreenMode mode) {

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

@@ -215,7 +215,7 @@ public class MCACompatEvents {
IBondageState state = MCACompat.getKidnappedState(livingTarget);
if (state != null) {
// Priority: Untie > Ungag > Unblindfold > etc.
// This mimics EntityDamsel logic or ItemBind.interactLivingEntity logic for untying
// This mimics EntityDamsel logic or TyingInteractionHelper logic for untying
// No collar ownership check — any player can untie MCA villagers by design
// (MCA villagers use a separate relationship system, not TiedUp collars)

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 {@code BindVariant.getResistance()} (now removed) 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;
@@ -51,7 +51,7 @@ public class PetRequestManager {
double dist = master.distanceTo(pet);
if (dist > MAX_DISTANCE) {
pet.sendSystemMessage(
Component.literal("You are too far from your Master to talk.")
Component.translatable("entity.tiedup.pet.too_far_to_talk")
);
return;
}
@@ -95,7 +95,7 @@ public class PetRequestManager {
double dist = master.distanceTo(pet);
if (dist > MAX_DISTANCE) {
pet.sendSystemMessage(
Component.literal("You are too far from your Master.")
Component.translatable("entity.tiedup.pet.too_far_from_master")
);
return;
}
@@ -109,7 +109,7 @@ public class PetRequestManager {
// Display what the player "says"
pet.sendSystemMessage(
Component.literal("You: " + request.getPlayerText())
Component.translatable("entity.tiedup.pet.you_say", request.getPlayerText())
);
// Handle specific request
@@ -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,
@@ -644,8 +644,8 @@ public class EntityDamsel
player instanceof net.minecraft.server.level.ServerPlayer sp
) {
sp.displayClientMessage(
Component.literal(
"This NPC needs a collar before you can feed them."
Component.translatable(
"entity.tiedup.damsel.needs_collar_to_feed"
).withStyle(ChatFormatting.RED),
true
);
@@ -653,15 +653,15 @@ 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
) {
sp.displayClientMessage(
Component.literal(
"You don't own this NPC's collar."
Component.translatable(
"entity.tiedup.damsel.not_collar_owner"
).withStyle(ChatFormatting.RED),
true
);
@@ -675,8 +675,8 @@ public class EntityDamsel
player instanceof net.minecraft.server.level.ServerPlayer sp
) {
sp.displayClientMessage(
Component.literal(
"This NPC can't eat that right now."
Component.translatable(
"entity.tiedup.damsel.cant_eat_now"
).withStyle(ChatFormatting.RED),
true
);
@@ -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

@@ -341,7 +341,7 @@ public class EntityLaborGuard extends EntityDamsel {
}
prisoner.sendSystemMessage(
Component.literal("Attacking your guard is punished!").withStyle(
Component.translatable("entity.tiedup.guard.attack_punished").withStyle(
ChatFormatting.DARK_RED
)
);
@@ -529,8 +529,8 @@ public class EntityLaborGuard extends EntityDamsel {
.getPlayer(prisonerUUID);
if (prisoner != null) {
prisoner.sendSystemMessage(
Component.literal(
"Your guard has been eliminated! You are free!"
Component.translatable(
"entity.tiedup.guard.eliminated_free"
).withStyle(ChatFormatting.GREEN, ChatFormatting.BOLD)
);
}

View File

@@ -447,8 +447,8 @@ public class EntityMaid extends EntityKidnapperElite {
player.position().distanceTo(this.position()) <= 50
) {
player.sendSystemMessage(
net.minecraft.network.chat.Component.literal(
"The maid has died. Work is paused. A replacement will arrive in 5 minutes."
net.minecraft.network.chat.Component.translatable(
"entity.tiedup.maid.died_work_paused"
).withStyle(net.minecraft.ChatFormatting.GOLD)
);
}

View File

@@ -606,8 +606,8 @@ public class EntityMaster extends EntityKidnapperElite {
// Send warning message to pet
pet.sendSystemMessage(
Component.literal(
this.getNpcName() + " caught you trying to escape!"
Component.translatable(
"entity.tiedup.master.caught_escaping", this.getNpcName()
).withStyle(Style.EMPTY.withColor(MASTER_NAME_COLOR))
);
}
@@ -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

@@ -200,8 +200,8 @@ public class EntitySlaveTrader extends EntityKidnapperElite {
Component.literal("[" + this.getNpcName() + "] ")
.withStyle(Style.EMPTY.withColor(TRADER_NAME_COLOR))
.append(
Component.literal(
"You don't have a trader token. Leave now, or I'll make you leave."
Component.translatable(
"entity.tiedup.trader.no_token_warning"
).withStyle(ChatFormatting.RED)
)
);
@@ -711,8 +711,8 @@ public class EntitySlaveTrader extends EntityKidnapperElite {
ServerLevel level,
BlockPos campCenter
) {
Component message = Component.literal(
"A slave trader camp has been destroyed!"
Component message = Component.translatable(
"entity.tiedup.trader.camp_destroyed"
).withStyle(ChatFormatting.GOLD, ChatFormatting.BOLD);
// Send to all players within 200 blocks

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

@@ -93,15 +93,15 @@ public class MerchantTrade {
*/
public Component getPriceDisplay() {
if (ingotPrice > 0 && nuggetPrice > 0) {
return Component.literal(
ingotPrice + " gold + " + nuggetPrice + " nuggets"
return Component.translatable(
"entity.tiedup.trade.price_both", ingotPrice, nuggetPrice
);
} else if (ingotPrice > 0) {
return Component.literal(ingotPrice + " gold");
return Component.translatable("entity.tiedup.trade.price_gold", ingotPrice);
} else if (nuggetPrice > 0) {
return Component.literal(nuggetPrice + " nuggets");
return Component.translatable("entity.tiedup.trade.price_nuggets", nuggetPrice);
} else {
return Component.literal("Free");
return Component.translatable("entity.tiedup.trade.price_free");
}
}

View File

@@ -202,8 +202,8 @@ public class GuardMonitorGoal extends Goal {
);
prisoner.sendSystemMessage(
Component.literal(
"You are too far from your guard! Return within 15 seconds or escape will be triggered!"
Component.translatable(
"goal.tiedup.guard_monitor.too_far"
).withStyle(ChatFormatting.RED, ChatFormatting.BOLD)
);
@@ -227,8 +227,8 @@ public class GuardMonitorGoal extends Goal {
);
prisoner.sendSystemMessage(
Component.literal(
"You returned to your guard. Stay close!"
Component.translatable(
"goal.tiedup.guard_monitor.returned"
).withStyle(ChatFormatting.YELLOW)
);
}

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

@@ -604,8 +604,8 @@ public class KidnapperDecideNextActionGoal extends Goal {
);
player.sendSystemMessage(
Component.literal(
"You're still tied up - struggle to break free!"
Component.translatable(
"goal.tiedup.kidnapper_decide.still_tied"
).withStyle(ChatFormatting.YELLOW)
);

View File

@@ -351,14 +351,14 @@ public class KidnapperWaitForBuyerGoal extends Goal {
);
Component announcement = Component.literal("")
.append(Component.literal("[SALE] ").withStyle(ChatFormatting.GOLD))
.append(Component.translatable("goal.tiedup.kidnapper_sale.tag").withStyle(ChatFormatting.GOLD))
.append(
Component.literal(this.kidnapper.getNpcName()).withStyle(
ChatFormatting.RED
)
)
.append(
Component.literal(" is selling ").withStyle(
Component.translatable("goal.tiedup.kidnapper_sale.is_selling").withStyle(
ChatFormatting.YELLOW
)
)
@@ -367,13 +367,13 @@ public class KidnapperWaitForBuyerGoal extends Goal {
ChatFormatting.AQUA
)
)
.append(Component.literal(" for ").withStyle(ChatFormatting.YELLOW))
.append(Component.translatable("goal.tiedup.kidnapper_sale.for").withStyle(ChatFormatting.YELLOW))
.append(
Component.literal(price.toDisplayString()).withStyle(
ChatFormatting.GREEN
)
)
.append(Component.literal(" at ").withStyle(ChatFormatting.YELLOW))
.append(Component.translatable("goal.tiedup.kidnapper_sale.at").withStyle(ChatFormatting.YELLOW))
.append(
Component.literal(location).withStyle(ChatFormatting.WHITE)
);

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 (
@@ -330,9 +331,9 @@ public class MaidDeliverCaptiveGoal extends Goal {
buyerEntity.getName().getString()
);
buyerEntity.sendSystemMessage(
net.minecraft.network.chat.Component.literal(
captiveEntity.getName().getString() +
" is now on your leash."
net.minecraft.network.chat.Component.translatable(
"goal.tiedup.maid_deliver.on_leash",
captiveEntity.getName().getString()
).withStyle(net.minecraft.ChatFormatting.GREEN)
);
} else {
@@ -341,9 +342,9 @@ public class MaidDeliverCaptiveGoal extends Goal {
);
kidnappedState.free(true);
buyerEntity.sendSystemMessage(
net.minecraft.network.chat.Component.literal(
captiveEntity.getName().getString() +
" has been delivered to you."
net.minecraft.network.chat.Component.translatable(
"goal.tiedup.maid_deliver.delivered",
captiveEntity.getName().getString()
).withStyle(net.minecraft.ChatFormatting.GREEN)
);
}

View File

@@ -200,13 +200,13 @@ public class MaidAssignTaskGoal extends Goal {
// Notify prisoner
prisoner.sendSystemMessage(
net.minecraft.network.chat.Component.literal(
"Task assigned: " + task.getDescription()
net.minecraft.network.chat.Component.translatable(
"goal.tiedup.maid_assign.task_assigned", task.getDescription()
).withStyle(net.minecraft.ChatFormatting.YELLOW)
);
prisoner.sendSystemMessage(
net.minecraft.network.chat.Component.literal(
"Reward: " + task.getValue() + " emeralds toward your debt."
net.minecraft.network.chat.Component.translatable(
"goal.tiedup.maid_assign.task_reward", task.getValue()
).withStyle(net.minecraft.ChatFormatting.GRAY)
);
@@ -289,8 +289,8 @@ public class MaidAssignTaskGoal extends Goal {
}
prisoner.sendSystemMessage(
net.minecraft.network.chat.Component.literal(
"Your debt is paid. You are FREE!"
net.minecraft.network.chat.Component.translatable(
"goal.tiedup.maid.debt_paid_free"
).withStyle(
net.minecraft.ChatFormatting.GREEN,
net.minecraft.ChatFormatting.BOLD

View File

@@ -346,9 +346,9 @@ public class MaidExtractGoal extends Goal {
// 7. Notify prisoner
prisoner.sendSystemMessage(
net.minecraft.network.chat.Component.literal(
"You have been extracted for labor. Complete your task: " +
task.getDescription()
net.minecraft.network.chat.Component.translatable(
"goal.tiedup.maid_extract.extracted",
task.getDescription()
).withStyle(net.minecraft.ChatFormatting.YELLOW)
);

View File

@@ -146,8 +146,8 @@ public class MaidIdleGoal extends Goal {
labor.completeTask(currentTime);
prisoner.sendSystemMessage(
net.minecraft.network.chat.Component.literal(
"Task complete! Walk back to camp."
net.minecraft.network.chat.Component.translatable(
"goal.tiedup.maid_idle.task_complete"
).withStyle(net.minecraft.ChatFormatting.GREEN)
);
@@ -207,17 +207,16 @@ public class MaidIdleGoal extends Goal {
// Max punishment - fail task
labor.failTask(currentTime);
prisoner.sendSystemMessage(
net.minecraft.network.chat.Component.literal(
"Task failed due to inactivity! You will be returned to your cell."
net.minecraft.network.chat.Component.translatable(
"goal.tiedup.maid_idle.task_failed"
).withStyle(net.minecraft.ChatFormatting.RED)
);
} else {
// Warning
prisoner.sendSystemMessage(
net.minecraft.network.chat.Component.literal(
"Warning: Work or face punishment! (" +
shockLevel +
"/3)"
net.minecraft.network.chat.Component.translatable(
"goal.tiedup.maid_idle.inactivity_warning",
shockLevel
).withStyle(net.minecraft.ChatFormatting.YELLOW)
);

View File

@@ -165,18 +165,16 @@ public class MaidInitPrisonerGoal extends Goal {
// 6. Notify prisoner
prisoner.sendSystemMessage(
net.minecraft.network.chat.Component.literal(
String.format(
"You have been imprisoned. Your debt: %d emeralds.",
totalRansom
)
net.minecraft.network.chat.Component.translatable(
"goal.tiedup.maid_init.imprisoned",
totalRansom
).withStyle(net.minecraft.ChatFormatting.RED)
);
if (!confiscated.isEmpty()) {
prisoner.sendSystemMessage(
net.minecraft.network.chat.Component.literal(
"Your valuables have been confiscated."
net.minecraft.network.chat.Component.translatable(
"goal.tiedup.maid_init.confiscated"
).withStyle(net.minecraft.ChatFormatting.GRAY)
);
}

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")
)
);
}
@@ -673,8 +671,8 @@ public class MaidReturnGoal extends Goal {
}
prisoner.sendSystemMessage(
net.minecraft.network.chat.Component.literal(
"Your debt is paid. You are FREE!"
net.minecraft.network.chat.Component.translatable(
"goal.tiedup.maid.debt_paid_free"
).withStyle(
net.minecraft.ChatFormatting.GREEN,
net.minecraft.ChatFormatting.BOLD

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

@@ -124,11 +124,10 @@ public class MasterInventoryInspectGoal extends Goal {
ServerPlayer pet = master.getPetPlayer();
if (pet != null && !confiscatedItems.isEmpty()) {
pet.sendSystemMessage(
Component.literal(
master.getNpcName() +
" confiscated " +
confiscatedItems.size() +
" contraband item(s) from you!"
Component.translatable(
"goal.tiedup.master_inspect.confiscated",
master.getNpcName(),
confiscatedItems.size()
).withStyle(
Style.EMPTY.withColor(EntityMaster.MASTER_NAME_COLOR)
)
@@ -180,8 +179,8 @@ public class MasterInventoryInspectGoal extends Goal {
*/
private void performInspection(ServerPlayer pet) {
pet.sendSystemMessage(
Component.literal(
master.getNpcName() + " is inspecting your inventory..."
Component.translatable(
"goal.tiedup.master_inspect.inspecting", master.getNpcName()
).withStyle(Style.EMPTY.withColor(0xFFFF00))
);

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

@@ -331,8 +331,8 @@ public class MasterTaskAssignGoal extends Goal {
// FIX: Use MessageDispatcher for consistency with earplug system
MessageDispatcher.sendChat(
pet,
Component.literal(
master.getNpcName() + ": \"" + message + "\""
Component.translatable(
"goal.tiedup.master_assign.task_speech", master.getNpcName(), message
).withStyle(Style.EMPTY.withColor(EntityMaster.MASTER_NAME_COLOR))
);

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;
@@ -269,8 +269,8 @@ public class MasterTaskWatchGoal extends Goal {
if (remainingSec > 0) {
MessageDispatcher.sendActionBar(
pet,
Component.literal(
task.name() + " - " + remainingSec + "s remaining"
Component.translatable(
"goal.tiedup.master_watch.time_remaining", task.name(), remainingSec
).withStyle(Style.EMPTY.withColor(0xFFAA00))
);
}
@@ -357,8 +357,8 @@ public class MasterTaskWatchGoal extends Goal {
) {
MessageDispatcher.sendChat(
pet,
Component.literal(
master.getNpcName() + ": \"I'm waiting...\""
Component.translatable(
"goal.tiedup.master_watch.speak_waiting", master.getNpcName()
).withStyle(
Style.EMPTY.withColor(
EntityMaster.MASTER_NAME_COLOR
@@ -384,8 +384,8 @@ public class MasterTaskWatchGoal extends Goal {
.nextInt(demandWarnings.length)];
MessageDispatcher.sendChat(
pet,
Component.literal(
master.getNpcName() + ": \"" + warning + "\""
Component.translatable(
"goal.tiedup.master_watch.demand_warning", master.getNpcName(), warning
).withStyle(
Style.EMPTY.withColor(
EntityMaster.MASTER_NAME_COLOR
@@ -431,8 +431,8 @@ public class MasterTaskWatchGoal extends Goal {
String message = messages[master.getRandom().nextInt(messages.length)];
MessageDispatcher.sendChat(
pet,
Component.literal(
master.getNpcName() + ": \"" + message + "\""
Component.translatable(
"goal.tiedup.master_watch.punishment", master.getNpcName(), message
).withStyle(Style.EMPTY.withColor(EntityMaster.MASTER_NAME_COLOR))
);
@@ -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);
@@ -450,8 +450,8 @@ public class NpcGuardCommandGoal extends Goal {
if (player.getUUID().equals(commanderUUID)) {
String slaveName = slave.getNpcName();
player.displayClientMessage(
net.minecraft.network.chat.Component.literal(
npc.getNpcName() + " is chasing " + slaveName + "!"
net.minecraft.network.chat.Component.translatable(
"goal.tiedup.guard_command.chasing", npc.getNpcName(), slaveName
).withStyle(net.minecraft.ChatFormatting.RED),
true // Action bar
);
@@ -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);
}
@@ -545,8 +545,8 @@ public class NpcGuardCommandGoal extends Goal {
// Send alert message (could be enhanced with dialogue system)
String threatName = threat.getName().getString();
player.displayClientMessage(
net.minecraft.network.chat.Component.literal(
npc.getNpcName() + " spotted: " + threatName + "!"
net.minecraft.network.chat.Component.translatable(
"goal.tiedup.guard_command.spotted", npc.getNpcName(), threatName
).withStyle(net.minecraft.ChatFormatting.YELLOW),
true // Action bar
);

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

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