Commit Graph

108 Commits

Author SHA1 Message Date
NotEvil
a3287b7db8 fix(animation): variant randomness no longer permanently cached + fix FullHead false-positive
Two fixes from the animation audit:

1. Variant cache key now includes the resolved animation name (e.g., Struggle.2).
   Previously, the cache key only used context+parts, so the first random variant
   pick was reused forever. Now each variant gets its own cache entry, and a fresh
   random pick happens each time the context changes.

2. FullHead check changed from contains("Head") to startsWith("gltf_FullHead")
   to prevent false positives on names like FullOverhead or FullAhead.
2026-04-17 04:06:33 +02:00
NotEvil
e56e6dd551 fix(animation): extend resolver fallback chain to include FullHead variants
The resolver now tries FullHead* before Full* at each fallback step.
Example: FullHeadStruggle → FullStruggle → Struggle → FullHeadIdle → FullIdle → Idle

Previously FullHead* names were dead — the resolver never constructed them,
so animations named FullHeadStruggle were unreachable.
2026-04-17 04:06:33 +02:00
NotEvil
806a1e732d feat(animation): add FullHead convention — opt-in head animation in Full animations
FullStruggle, FullWalk etc. animate body+legs but preserve head tracking.
FullHeadStruggle, FullHeadWalk etc. also animate the head.
The 'Head' keyword in the animation name is the opt-in signal.
2026-04-17 04:06:33 +02:00
NotEvil
3d57d83a5b fix(animation): preserve head tracking in Full animations — head never enabled as free bone 2026-04-17 04:06:33 +02:00
NotEvil
229fc66340 fix(animation): free bones only enabled for Full-prefixed animations
Previously, any GLB with keyframes on free bones would animate them,
even for standard animations like Idle. This caused accidental bone
hijacking — e.g., handcuffs freezing the player's head because the
artist keyframed all bones in Blender.

Now the Full prefix (FullIdle, FullStruggle, FullWalk) is enforced:
only Full-prefixed animations can animate free bones. Standard
animations (Idle, Struggle, Walk) only animate owned bones.

This aligns the code with the documented convention in ARTIST_GUIDE.md.
2026-04-17 04:06:33 +02:00
NotEvil
b0766fecc6 feat(validation): show toast notification when GLB errors are detected on reload 2026-04-17 04:06:33 +02:00
NotEvil
9dfd2d1724 fix(validation): reduce log spam + add OOM guard + check correct mesh for WEIGHTS_0
- DataDrivenItemParser: downgrade animation_bones absence log from INFO to
  DEBUG (was spamming for every item without the optional field)
- GlbValidator: read 12-byte GLB header first and reject files declaring
  totalLength > 50 MB before allocating (prevents OOM on malformed GLBs)
- GlbValidator: WEIGHTS_0 check now uses the same mesh selection logic as
  GlbParser (prefer "Item", fallback to last non-Player) instead of
  blindly checking the first mesh
2026-04-17 04:06:33 +02:00
NotEvil
3f6e04edb0 feat(validation): add /tiedup validate client command for GLB diagnostics 2026-04-17 04:06:33 +02:00
NotEvil
ca4cbcad12 feat(validation): add GlbValidationReloadListener — validates GLBs on resource reload 2026-04-17 04:06:33 +02:00
NotEvil
17269f51f8 perf(gltf): add skinning cache — skip re-skinning when pose is unchanged 2026-04-17 04:06:33 +02:00
NotEvil
c0c53f9504 feat(validation): add GlbValidator — structural validation from JSON chunk 2026-04-17 04:06:33 +02:00
NotEvil
eb759fefff feat(validation): add diagnostic data model — GlbDiagnostic, GlbValidationResult, GlbDiagnosticRegistry 2026-04-17 04:06:33 +02:00
NotEvil
8af58b5dd5 feat(gltf): accept custom bones — remove 11-bone filter, simplify joint remap 2026-04-17 04:06:33 +02:00
NotEvil
d7f8bf6c72 fix(gltf): strip armature prefix from bone names in FurnitureGlbParser
Strip pipe-delimited armature prefixes (e.g., "MyRig|body" -> "body")
from bone names in parseSeatSkeleton and remapSeatAnimations, matching
the existing stripping already present in GlbParser. Without this,
isKnownBone checks fail when Blender exports prefixed bone names.
2026-04-17 04:06:33 +02:00
NotEvil
6dad447c05 fix(gltf): select mesh by 'Item' name convention with fallback 2026-04-17 04:06:33 +02:00
NotEvil
7ef85b4e92 feat(datadriven): make animation_bones optional — absent means permissive 2026-04-17 04:06:33 +02:00
NotEvil
ad74d320be feat(gltf): add suggestBoneName + knownBoneNames helpers to GltfBoneMapper 2026-04-17 04:06:33 +02:00
5788f39d9f Merge pull request 'refactor/god-class-decomposition' (#17) from refactor/god-class-decomposition into develop
Reviewed-on: #17
2026-04-16 12:38:55 +00:00
NotEvil
27c86bc831 fix ghost LaborRecord entries for non-working prisoners on escape 2026-04-16 14:36:45 +02:00
NotEvil
f4aa5ffdc5 split PrisonerService + decompose EntityKidnapper
PrisonerService 1057L -> 474L lifecycle + 616L EscapeMonitorService
EntityKidnapper 2035L -> 1727L via LootManager, Dialogue, CaptivePriority extraction
2026-04-16 14:08:52 +02:00
ea14fc2cec Merge pull request 'centralize all ModConfig.SERVER reads through SettingsAccessor' (#16) from chore/audit-c02-config-unification into develop
Reviewed-on: #16
2026-04-16 11:49:22 +00:00
NotEvil
4e136cff96 centralize all ModConfig.SERVER reads through SettingsAccessor
No more direct ModConfig.SERVER access outside SettingsAccessor.
32 new accessor methods, 21 consumer files rerouted.
2026-04-16 13:16:05 +02:00
683eeec11f Merge pull request 'fix/swarm-review-p0-p1-p2' (#15) from fix/swarm-review-p0-p1-p2 into develop
Reviewed-on: #15
2026-04-16 10:37:05 +00:00
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