feature/d01-branch-a-bridge #6

Merged
NotEvil merged 11 commits from feature/d01-branch-a-bridge into develop 2026-04-14 15:19:21 +00:00

11 Commits

Author SHA1 Message Date
NotEvil
d2d8df31c5 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
a75b89929f 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
Adrien
fe8ef3d1dc 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
Adrien
d1b864c4ec 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
Adrien
47fa37282f 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
Adrien
21c1fc3b57 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
Adrien
c240f48b29 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