From 449178f57b499dee09201ab120e50cc5f9facd9c Mon Sep 17 00:00:00 2001 From: NotEvil Date: Wed, 15 Apr 2026 00:16:50 +0200 Subject: [PATCH 1/3] =?UTF-8?q?feat(D-01/C):=20consumer=20migration=20?= =?UTF-8?q?=E2=80=94=2085=20files=20migrated=20to=20V2=20helpers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- docs/AUDIT.md | 413 +++++++++ docs/plans/D01-branch-A-bridge-utilities.md | 284 ++++++ docs/plans/D01-branch-B-json-definitions.md | 128 +++ docs/plans/D01-branch-C-consumer-migration.md | 107 +++ docs/plans/D01-branch-D-v1-cleanup.md | 188 ++++ docs/plans/D01-branch-E-resistance-rework.md | 155 ++++ .../2026-04-13-d01-phase1-component-system.md | 869 ++++++++++++++++++ .../blocks/entity/BondageItemBlockEntity.java | 54 +- .../entity/TrappedChestBlockEntity.java | 35 +- .../remake/cells/CampLifecycleManager.java | 8 +- .../client/FirstPersonMittensRenderer.java | 13 +- .../tiedup/remake/client/ModKeybindings.java | 9 +- .../render/DogPoseRenderHandler.java | 9 +- .../animation/render/PetBedRenderHandler.java | 8 +- .../render/PlayerArmHideEventHandler.java | 8 +- .../animation/tick/AnimationTickHandler.java | 21 +- .../tick/NpcAnimationTickHandler.java | 20 +- .../events/LeashProxyClientHandler.java | 6 +- .../events/SelfBondageInputHandler.java | 49 +- .../gui/screens/SlaveManagementScreen.java | 6 +- .../client/gui/widgets/ActionPanel.java | 8 +- .../client/gui/widgets/SlaveEntryWidget.java | 14 +- .../remake/client/model/DamselModel.java | 25 +- .../tiedup/remake/commands/CollarCommand.java | 46 +- .../remake/commands/KidnapSetCommand.java | 63 +- .../tiedup/remake/commands/NPCCommand.java | 20 +- .../subcommands/BondageSubCommand.java | 61 +- .../mca/capability/MCAKidnappedAdapter.java | 17 +- .../remake/dialogue/GagTalkManager.java | 21 +- .../conversation/PetRequestManager.java | 10 +- .../remake/entities/AbstractTiedUpNpc.java | 41 +- .../entities/BondageServiceHandler.java | 43 +- .../tiedup/remake/entities/EntityDamsel.java | 18 +- .../remake/entities/EntityKidnapper.java | 7 +- .../entities/EntityKidnapperMerchant.java | 45 +- .../remake/entities/EntityRopeArrow.java | 6 +- .../entities/KidnapperCaptureEquipment.java | 5 +- .../entities/KidnapperCollarConfig.java | 74 +- .../entities/KidnapperItemSelector.java | 23 +- .../remake/entities/KidnapperJobManager.java | 20 +- .../remake/entities/LeashProxyEntity.java | 5 +- .../kidnapper/KidnapperBringToCellGoal.java | 8 +- .../ai/kidnapper/KidnapperCaptureGoal.java | 26 +- .../kidnapper/KidnapperWalkPrisonerGoal.java | 10 +- .../ai/maid/goals/MaidReturnGoal.java | 6 +- .../ai/master/MasterHumanChairGoal.java | 8 +- .../entities/ai/master/MasterPunishGoal.java | 35 +- .../ai/master/MasterRandomEventGoal.java | 31 +- .../ai/master/MasterTaskWatchGoal.java | 10 +- .../ai/personality/NpcFollowCommandGoal.java | 4 +- .../ai/personality/NpcGuardCommandGoal.java | 10 +- .../components/DamselBondageManager.java | 6 +- .../components/DamselPersonalitySystem.java | 6 +- .../components/NpcEquipmentManager.java | 22 +- .../components/KidnapperCaptiveManager.java | 6 +- .../components/KidnapperTargetSelector.java | 10 +- .../master/components/MasterPetManager.java | 45 +- .../events/camp/CampNpcProtectionHandler.java | 4 +- .../captivity/PlayerEnslavementHandler.java | 6 +- .../combat/LaborAttackPunishmentHandler.java | 4 +- .../PetPlayRestrictionHandler.java | 6 +- .../restriction/RestraintTaskTickHandler.java | 9 +- .../events/system/ChatEventHandler.java | 27 +- .../minigame/StruggleSessionManager.java | 21 +- .../mixin/MixinMCAVillagerInteraction.java | 6 +- .../remake/mixin/MixinMCAVillagerLeash.java | 6 +- .../remake/mixin/client/MixinCamera.java | 6 +- .../remake/mixin/client/MixinPlayerModel.java | 6 +- .../MixinVillagerEntityBaseModelMCA.java | 15 +- .../cell/PacketAssignCellToCollar.java | 18 +- .../network/personality/PacketNpcCommand.java | 6 +- .../network/slave/PacketMasterEquip.java | 6 +- .../network/slave/PacketSlaveAction.java | 52 +- .../tiedup/remake/personality/ToolMode.java | 4 +- .../tiedup/remake/state/IBondageState.java | 12 +- .../tiedup/remake/state/PlayerBindState.java | 12 +- .../remake/state/PlayerCaptorManager.java | 6 +- .../state/components/PlayerDataRetrieval.java | 13 +- .../state/components/PlayerEquipment.java | 8 +- .../state/components/PlayerLifecycle.java | 6 +- .../remake/util/BondageItemLoaderUtility.java | 67 +- .../remake/util/RestraintApplicator.java | 12 +- .../movement/MovementStyleResolver.java | 13 +- .../network/PacketFurnitureForcemount.java | 11 +- .../remake/worldgen/HangingCagePiece.java | 95 +- 85 files changed, 2885 insertions(+), 777 deletions(-) create mode 100644 docs/AUDIT.md create mode 100644 docs/plans/D01-branch-A-bridge-utilities.md create mode 100644 docs/plans/D01-branch-B-json-definitions.md create mode 100644 docs/plans/D01-branch-C-consumer-migration.md create mode 100644 docs/plans/D01-branch-D-v1-cleanup.md create mode 100644 docs/plans/D01-branch-E-resistance-rework.md create mode 100644 docs/superpowers/plans/2026-04-13-d01-phase1-component-system.md diff --git a/docs/AUDIT.md b/docs/AUDIT.md new file mode 100644 index 0000000..54a3b77 --- /dev/null +++ b/docs/AUDIT.md @@ -0,0 +1,413 @@ +# TiedUp! — Codebase Audit + +> Audit complet du mod, systeme par systeme, pour consolider la base et la coherence du code. + +--- + +## Objectif + +Passer en revue **chaque systeme** du mod en profondeur pour : +1. **Consolider** — identifier et corriger les incohérences, le code mort, la duplication +2. **Améliorer** — proposer des refactors ciblés là où l'architecture freine le développement +3. **Documenter** — laisser une trace des décisions et de l'état de chaque système + +Ce n'est pas un rewrite. On stabilise ce qui existe. + +--- + +## Règles de l'audit + +- **Système par système**, dans l'ordre de dépendance (fondations d'abord) +- **Deep dive** — on remonte les sources, les dépendants, les call chains via le MCP +- **Pas de changement sans discussion** — on constate, on discute, puis on corrige +- **Pas de sur-ingénierie** — on fixe les vrais problèmes, on ne refactor pas pour le plaisir +- **Reindex MCP** après chaque batch de corrections significatives + +### Processus par système + +Chaque audit suit ce cycle : + +``` +1. EXPLORATION — Lire le code, tracer les dépendances (MCP) +2. CONSTATS — Documenter les problèmes trouvés (dans ce fichier) +3. VÉRIFICATION — Relire, confirmer, pas de faux positifs +4. PROPOSITIONS — Pour chaque constat : + → Fix direct (bug, incohérence simple) + → Amélioration architecturale (refactor ciblé, avec justification) +5. DISCUSSION — Valider avec l'utilisateur avant d'implémenter +6. CORRECTION — Appliquer les changements validés +7. REINDEX — MCP reindex après corrections +``` + +On ne passe au système suivant qu'une fois le cycle terminé. + +--- + +## Ordre d'audit + +| # | Système | Packages | Status | +|---|---------|----------|--------| +| 1 | Core + Registries | `core`, `ModItems`, `ModEntities`, `ModNetwork`, `ModGameRules` | Done | +| 2 | State | `state`, `state/components`, `state/hosts`, `state/struggle` | Done | +| 3 | Items (v1) + V2 Bondage | `items/**`, `v2/bondage/**` | Done | +| 4 | Network | `network/**` | Done | +| 5 | Client Animation + GLTF | `client/animation/**`, `client/gltf` | Done | +| 6 | Furniture | `v2/furniture/**` | Done | +| 7 | Entities + AI | `entities/**` | Done | +| 8 | Events | `events/**` | Done | +| 9 | Dialogue + Personality | `dialogue/**`, `personality` | Done | +| 10 | Cells / Prison / Blocks | `cells`, `prison/**`, `blocks/**` | Done | +| 11 | Compat | `compat/mca/**`, `compat/wildfire/**` | Done | +| 12 | Util + Commands + Worldgen + Resources | `util/**`, `commands/**`, `worldgen`, resources | Done | + +--- + +## Échelle de sévérité + +| Niveau | Signification | Action | +|--------|--------------|--------| +| **Haute** | Bug actif, bloquant, ou dette qui empêche une feature majeure | Corriger avant de continuer | +| **Moyenne** | Incohérence ou fragilité qui va poser problème à terme | Planifier la correction | +| **Basse** | Code smell, naming, organisation — pas urgent | Corriger si on touche le fichier | +| **Cosmétique** | Style, formatting, commentaires | Optionnel | + +--- + +## Constats + +### #1 — Core + Registries + +**Positif :** +- DeferredRegister correct partout +- Factory pattern propre pour les variant items +- SettingsAccessor bridge solide (safeGet, BUG-003 fix) +- Séparation client/server correcte +- Data-driven reload listeners bien câblés + +**Problèmes :** (tous vérifiés ✓) + +| ID | Sévérité | Constat | Fichier(s) | Proposition | +|----|----------|---------|------------|-------------| +| C-01 | Haute | SystemMessageManager : 80+ messages hardcodés en anglais, zero `Component.translatable()` | `core/SystemMessageManager.java` | **Fix** : Remplacer `Component.literal()` par `Component.translatable()` avec clés dans `en_us.json`. Garder l'enum + les couleurs, changer uniquement le transport du texte. | +| C-02 | Moyenne | 25 settings triplés entre ModConfig + ModGameRules + SettingsAccessor. Historique de bug (BUG-001 : defaults désyncés) | `core/ModConfig.java`, `core/ModGameRules.java`, `core/SettingsAccessor.java` | **Archi** : Évaluer si les GameRules sont vraiment utiles (ils dupliquent la config serveur). Si oui, centraliser les defaults dans une seule source. Si non, supprimer les GameRules doublons et garder ModConfig + SettingsAccessor. | +| C-03 | Moyenne | ModNetwork : 74 IDs séquentiels. Pattern standard Forge mais fragile à l'insertion. `PROTOCOL_VERSION` protège partiellement. | `network/ModNetwork.java` | **Pas d'action immédiate** : pattern idiomatique Forge 1.20.1. Bumper `PROTOCOL_VERSION` à chaque ajout/suppression de packet. Documenter cette règle. | +| C-04 | Basse | ChokeEffect importe EntityMaster (core → entities) pour check "non-lethal when master-owned" | `core/ChokeEffect.java` | **Fix** : Extraire le check via un tag NBT ou capability sur le Player, consultable sans dépendre d'EntityMaster. | +| C-05 | Basse | 10 types d'entités NPC utilisent DamselRenderer — nom spécifique pour un usage générique | `core/TiedUpMod.java`, `client/renderer/DamselRenderer.java` | **Fix** : Renommer `DamselRenderer` → `NpcRenderer` ou `HumanoidNpcRenderer`. | +| C-06 | Cosmétique | 47+ FQCNs dans le corps de TiedUpMod au lieu d'imports | `core/TiedUpMod.java` | **Fix** : Remplacer par des imports. Faire quand on touche le fichier. | +| C-07 | Basse | ModConfig.ServerConfig : 127 valeurs configurables, 628 lignes, 20+ catégories dans une classe | `core/ModConfig.java` | **Archi** : Découper en sous-classes par domaine (StruggleConfig, NpcConfig, EconomyConfig, etc.) au prochain refactor config. Pas urgent. | + +### #2 — State + +**Positif :** +- Hiérarchie d'interfaces bien conçue (ISP) : `IRestrainableEntity` → `ICapturable` → `IBondageState` → `IRestrainable` (union) +- Décomposition en 11 components (PlayerEquipment, PlayerStateQuery, etc.) — bonne intention +- `IBondageState` n'expose que l'API V2 region-based — l'interface publique est propre +- `CollarRegistry` et `SocialData` : implémentations `SavedData` propres avec persistence correcte +- `IPlayerLeashAccess` : séparation mixin clean pour le système de laisse +- `PlayerCaptorManager` : thread-safe avec `CopyOnWriteArrayList` et `synchronized` + +**Problèmes :** (tous vérifiés ✓) + +| ID | Sévérité | Constat | Fichier(s) | Proposition | +|----|----------|---------|------------|-------------| +| S-01 | Moyenne | PlayerBindState : god class de façade, 1237 lignes, ~80 méthodes de pure délégation vers 11 components | `state/PlayerBindState.java` | **Archi** : Évaluer si les consommateurs peuvent utiliser les components directement via des accesseurs typés (`state.equipment().putBindOn()`) au lieu de passer par la façade. Réduirait la surface de ~80 méthodes de boilerplate. Risque : gros refactor, beaucoup de call sites. | +| S-02 | Moyenne | 8 champs de mouvement publics (`hopCooldown`, `lastX`, etc.) directement mutés par MovementStyleManager (33 accès directs) | `state/PlayerBindState.java`, `v2/bondage/movement/MovementStyleManager.java` | **Fix** : Extraire dans un `MovementState` component avec getters/setters. MovementStyleManager opère via ce component. | +| S-03 | Basse | API V1 slot-based (`putBindOn`, `takeGagOff`) coexiste avec V2 region-based sur la classe concrète PlayerBindState. L'interface `IBondageState` est propre (V2 only). | `state/PlayerBindState.java` | **Archi** : Marquer les méthodes V1 `@Deprecated` pour guider la migration. Les call sites (commands, etc.) devraient migrer vers `equip(BodyRegionV2)`. Pas urgent car l'interface est déjà propre. | +| S-04 | Basse | `hasLegsBound()` lit le slot ARMS (pas LEGS) — design V1 intentionnel : un seul item "bind" couvre bras+jambes via NBT mode. Pas un bug. | `state/IBondageState.java` | **Pas d'action immédiate** : cohérent avec le système actuel. Documenter le design dans un commentaire. Deviendra un vrai problème quand des items LEGS dédiés seront ajoutés en V2. | +| S-05 | Moyenne | Thread safety incohérente : `volatile` (3 champs), `synchronized` (5 méthodes), rien (le reste). La paire `isStruggling`/`struggleStartTick` peut être observée dans un état inconsistant. | `state/PlayerBindState.java` | **Fix** : Définir une stratégie claire. Les champs accédés cross-thread (mouvement, struggle, captor) doivent être soit volatile soit synchronized. Auditer chaque champ. | +| S-06 | Basse | `HumanChairHelper` dans `state/` mais c'est un utilitaire pur sans lien avec le state. Utilisé par AI, animation, mixins. | `state/HumanChairHelper.java` | **Fix** : Déplacer dans `items/base/` (à côté de `PoseType` dont il dépend) ou `util/`. Faire quand on touche le fichier. | + +### #3 — Items (V1) + V2 Bondage + +**Positif :** +- `IV2BondageItem` bien conçu : multi-region, stack-aware, pose priority, blocked regions +- `V2EquipmentManager` : conflict resolution solide (swap single, supersede global) +- `V2EquipmentHelper` : facade propre pour read/write/sync +- `DataDrivenBondageItem` : singleton + NBT registry pattern intelligent pour items data-driven +- `ILockable` : système lock/jam/key complet et cohérent +- `IHasResistance` : résistance NBT avec migration legacy, bien documentée +- `BodyRegionV2` enum complet (15 régions, global flag) +- Variant enums + factory pattern (BindVariant, GagVariant, etc.) propres + +**Problèmes :** (tous vérifiés ✓) + +| ID | Sévérité | Constat | Fichier(s) | Proposition | +|----|----------|---------|------------|-------------| +| I-01 | ~~Haute~~ | ~~Deux hiérarchies d'interfaces parallèles~~ | | **RÉSOLU** : suppression V1 (voir Décision D-01) | +| I-02 | ~~Haute~~ | ~~V1 items bypassent la conflict resolution V2~~ | | **RÉSOLU** : suppression V1 (voir Décision D-01) | +| I-03 | Moyenne | `DataDrivenBondageItem.getBaseResistance()` scanne tous items équipés et retourne MAX difficulty car `IHasResistance` n'a pas de paramètre ItemStack. Workaround documenté mais approximatif — peut surestimer la résistance. | `v2/bondage/datadriven/DataDrivenBondageItem.java` | **Archi** : Ajouter `getBaseResistance(ItemStack, LivingEntity)` à `IHasResistance` avec default qui délègue à l'ancienne méthode. DataDrivenBondageItem override la version stack-aware. | +| I-04 | ~~Basse~~ | ~~IBondageItem.getBodyRegion() single-region~~ | | **RÉSOLU** : suppression V1 (voir Décision D-01) | +| I-05 | ~~Moyenne~~ | ~~V1 items pas @Deprecated~~ | | **RÉSOLU** : suppression V1 (voir Décision D-01) | + +### Décision D-01 — Suppression totale du système V1 + Composants data-driven + +**Décision :** Le système V1 items est supprimé entièrement. Tous les items deviennent data-driven V2. La logique complexe (shock, GPS, lock, gagging, blinding, resistance, etc.) est extraite en **composants réutilisables** déclarables dans le JSON. + +**Périmètre de suppression :** +- `IBondageItem` (interface) +- `ItemBind`, `ItemGag`, `ItemBlindfold`, `ItemCollar`, `ItemEarplugs`, `ItemMittens` (abstracts) +- `GenericBind`, `GenericGag`, `GenericBlindfold`, `GenericEarplugs`, `GenericMittens` (concrets) +- `BindVariant`, `GagVariant`, `BlindfoldVariant`, `EarplugsVariant`, `MittensVariant` (enums) +- `ItemClassicCollar`, `ItemShockCollar`, `ItemShockCollarAuto`, `ItemGpsCollar`, `ItemChokeCollar` (collars) +- `ItemHood`, `ItemMedicalGag`, `ItemBallGag3D` (combos/special) +- Registrations V1 dans `ModItems` +- `PlayerEquipment.equipInRegion()` → remplacé par `V2EquipmentManager.tryEquip()` + +**Interfaces à conserver / migrer :** +- `ILockable` — conservé, utilisé par V2 items +- `IHasResistance` — conservé, refactoré avec paramètre ItemStack (I-03) +- `IKnife` — conservé (outils, pas des bondage items) +- `IAdjustable` — à évaluer (potentiellement composant) +- `IHasBlindingEffect`, `IHasGaggingEffect` — deviennent des composants + +**Système de composants envisagé :** + +Chaque composant est une logique serveur réutilisable qu'un item data-driven peut déclarer : + +```json +{ + "type": "tiedup:bondage_item", + "display_name": "Shock Collar", + "model": "tiedup:models/gltf/shock_collar.glb", + "regions": ["NECK"], + "components": { + "lockable": true, + "shock": { "auto_interval": 200, "damage": 2.0 }, + "gps": { "safe_zone_radius": 50 }, + "gagging": { "comprehension": 0.2, "range": 10.0 }, + "blinding": { "overlay": "tiedup:textures/overlay/blindfold.png" }, + "resistance": { "base": 150 } + }, + "escape_difficulty": 5, + "pose_priority": 10 +} +``` + +Exemples de composants à extraire de la logique V1 existante : +| Composant | Source V1 | Comportement | +|-----------|-----------|-------------| +| `lockable` | `ILockable` | Lock/unlock, padlock, key matching, jam, lock resistance | +| `resistance` | `IHasResistance` | Struggle resistance, configurable base value | +| `shock` | `ItemShockCollar` | Auto-shock intervals, manual shock, damage | +| `gps` | `ItemGpsCollar` | Safe zone, zone violation detection, owner alerts | +| `gagging` | `IHasGaggingEffect` | Muffled speech, comprehension %, range limit | +| `blinding` | `IHasBlindingEffect` | Blindfold overlay, hardcore mode | +| `choking` | `ItemChokeCollar` | Air drain, darkness, slowness, non-lethal master mode | +| `adjustable` | `IAdjustable` | Tightness level, visual adjustment | + +**Ce refactor est le plus gros chantier identifié par l'audit.** Il fera l'objet d'un plan d'implémentation dédié après la fin de l'audit. + +### #4 — Network + +**Positif :** +- `AbstractClientPacket` / `AbstractPlayerSyncPacket` — bon pattern de base, handle enqueue sur main thread, retry queue pour les players pas encore loaded +- `PacketRateLimiter` — token bucket complet avec catégories (struggle, minigame, action, selfbondage, ui). Thread-safe. Bon anti-spam. +- `SyncManager` — facade centralisée pour sync inventory/state/enslavement/struggle/clothes. Pattern `sendSync()` générique propre. +- `NetworkEventHandler` — gère correctement login sync, start-tracking sync, furniture reconnection, et fix MC-262715 (stale riding state) +- `PacketSlaveAction` — bonnes validations serveur : dimension check, distance check, collar ownership check, GPS permission check +- `PacketSelfBondage` — rate limited, route correctement V2 via `handleV2SelfBondage()` avec conflict check (`isRegionOccupied` + `isRegionBlocked`) + +**Problèmes :** + +| ID | Sévérité | Constat | Fichier(s) | Proposition | +|----|----------|---------|------------|-------------| +| N-01 | Moyenne | `PacketSelfBondage.handle()` contient 5 branches V1 (`instanceof ItemBind/ItemGag/ItemBlindfold/ItemMittens/ItemEarplugs`) qui devront être supprimées avec D-01. La branche V2 (`instanceof IV2BondageItem`) restera seule. | `network/selfbondage/PacketSelfBondage.java` | **Fix D-01** : Supprimer les branches V1, ne garder que la route V2. Simplifie massivement le fichier. | +| N-02 | Moyenne | 4 packets dépendent de `ItemCollar` (V1 class) : `PacketSlaveAction`, `PacketMasterEquip`, `PacketAssignCellToCollar`, `PacketNpcCommand`. La logique collar (ownership, canShock, hasGPS) est couplée à la classe Java. | `network/slave/PacketSlaveAction.java`, `network/slave/PacketMasterEquip.java`, `network/cell/PacketAssignCellToCollar.java`, `network/personality/PacketNpcCommand.java` | **Fix D-01** : Quand ItemCollar migre vers le système composants, ces packets devront checker les composants (ex: `hasComponent("shock")`) au lieu de `instanceof ItemCollar`. | +| N-03 | Basse | `PacketSyncBindState` sync des flags d'état V1 (isTiedUp, isGagged, isBlindfolded, etc.) séparément de `PacketSyncV2Equipment` qui sync le V2 capability. Potentiellement redondant post-suppression V1 — l'état peut être dérivé du V2 equipment. | `network/sync/PacketSyncBindState.java`, `v2/bondage/network/PacketSyncV2Equipment.java` | **Archi post-D-01** : Évaluer si `PacketSyncBindState` peut être supprimé et ses flags dérivés côté client depuis V2 equipment. Réduirait le nombre de packets sync. | +| N-04 | Basse | `SyncManager.syncAllPlayersTo()` envoie 4 packets distincts par joueur (V2Equipment, BindState, Enslavement, Struggle, + Clothes si applicable). Pour un serveur avec N joueurs, un login génère ~4N packets. | `network/sync/SyncManager.java` | **Archi** : Considérer un packet bulk `PacketSyncFullState` qui combine tout en un seul envoi. Pas urgent — 4N packets est acceptable pour les tailles de serveur visées. | +| N-05 | Cosmétique | Pas de `MCABondageManager` dans le package network, mais `PacketSyncMCABondage` existe — la logique MCA bondage sync est split entre `network/sync/` et `compat/mca/`. | `network/sync/PacketSyncMCABondage.java`, `compat/mca/` | **Pas d'action** : Acceptable pour un module de compatibilité. | + +### #5 — Client Animation + GLTF + +**Positif :** +- **Architecture 3 couches propre** : Context layer (pri 40) → Item layer (pri 42) → Furniture layer (pri 43). Priorités claires, bon découplage. +- **BondageAnimationManager** : API unifiée `playAnimation/playDirect/playContext/playFurniture` pour players et NPCs. Gestion des remote players (fallback stack), pending queue pour retry, furniture grace ticks pour éviter les stuck poses. +- **GlbAnimationResolver** : Fallback chain fidèle au ARTIST_GUIDE (FullSitStruggle → SitStruggle → FullStruggle → Struggle → FullIdle → Idle). Support variants (.1, .2) avec random selection. +- **GltfAnimationApplier** : Multi-item composite animation propre. Cache par state key, skip si unchanged. `applyMultiItemV2Animation()` merge les bones de plusieurs items dans un seul AnimationBuilder. +- **ContextGlbRegistry** : Hot-reload des GLB de contexte depuis resource packs. Atomic swap pour thread safety render. +- **AnimationContextResolver** : Résolution claire de contexte (sitting → struggling → movement style → sneaking → walking → idle). Version NPC séparée. +- **GLTF pipeline (12 fichiers)** : Zéro dépendance V1. Parser, cache, skinning engine, mesh renderer, bone mapper, pose converter — tout est V2 natif. + +**Problèmes :** + +| ID | Sévérité | Constat | Fichier(s) | Proposition | +|----|----------|---------|------------|-------------| +| A-01 | Moyenne | 6 fichiers animation dépendent de `ItemBind` et `PoseType` (V1) pour déterminer le type de pose (STANDARD, DOG, HUMAN_CHAIR) et le bind mode (arms/legs/full). | `tick/AnimationTickHandler.java`, `tick/NpcAnimationTickHandler.java`, `render/PlayerArmHideEventHandler.java`, `render/PetBedRenderHandler.java`, `render/DogPoseRenderHandler.java`, `util/AnimationIdBuilder.java` | **Fix D-01** : Quand les items V1 sont supprimés, la pose type et le bind mode doivent venir du système V2 (data-driven definition ou composant). `PoseType` peut être conservé comme enum mais lu depuis `DataDrivenItemDefinition` au lieu de `ItemBind.getPoseType()`. | +| A-02 | Basse | `StaticPoseApplier` dépend de `PoseType` — applique des rotations hardcodées par pose type (V1 fallback pour quand le GLTF n'est pas disponible). | `animation/StaticPoseApplier.java` | **Évaluer D-01** : Si tous les items ont un GLB, le static pose applier devient un fallback pur. Peut être conservé comme sécurité ou supprimé. | +| A-03 | Basse | `GltfAnimationApplier` a un toggle debug F9 hardcodé qui charge un GLB spécifique (`cuffs_prototype.glb`). | `client/gltf/GltfAnimationApplier.java` (l.~350) | **Fix** : Supprimer ou mettre derrière un flag dev. Mineur. | +| A-04 | Cosmétique | Le fallback animation dans `BondageAnimationManager.tryFallbackAnimation()` contient des patterns V1 spécifiques (`_arms_`, `sit_dog_`, `kneel_dog_`). Post-D-01, ces patterns n'existeront plus. | `animation/BondageAnimationManager.java` | **Fix D-01** : Nettoyer les fallbacks V1 obsolètes. Le système GLB a sa propre fallback chain (GlbAnimationResolver). | + +### #6 — Furniture + +**Positif :** +- **Architecture data-driven exemplaire** : `FurnitureDefinition` (record immuable) + `FurnitureRegistry` (volatile atomic swap) + `FurnitureParser` + `FurnitureServerReloadListener`. Exactement le même pattern que les bondage items V2 data-driven. +- **`ISeatProvider`** : interface propre et générique — conçue pour être implémentée par des monstres aussi (ARTIST_GUIDE: "monster seat system"). Bonne anticipation. +- **`SeatDefinition`** : record immuable avec tous les champs du guide artiste (blocked regions, lockable, locked difficulty, item difficulty bonus). +- **`EntityFurniture`** : Entity simple (pas LivingEntity), synced via `IEntityAdditionalSpawnData`. Dimensions variables depuis la definition. Animation state machine (IDLE → OCCUPIED → LOCKING → STRUGGLE → UNLOCKING). Seat assignments persistés en NBT. +- **`FurniturePlacerItem`** : singleton item avec NBT ID, snap-to-wall, floor-only. Même pattern que `DataDrivenBondageItem`. +- **`FurnitureAnimationContext`** : Conversion GLB → KeyframeAnimation avec bones sélectifs (blocked regions only). S'intègre proprement avec la furniture layer (pri 43) de BondageAnimationManager. +- **`FurnitureGltfData`** : Parsing dédié qui sépare furniture armature des Player_* seat skeletons dans un seul GLB. Fidèle à l'ARTIST_GUIDE. +- **Packets** : Rate limited, distance checks, permission checks (collar ownership pour forcemount). +- **Reconnection robuste** : `NetworkEventHandler.handleFurnitureReconnection()` restaure les joueurs locked dans un seat après déconnexion, avec teleport si le meuble n'existe plus. + +**Problèmes :** + +| ID | Sévérité | Constat | Fichier(s) | Proposition | +|----|----------|---------|------------|-------------| +| F-01 | Moyenne | `EntityFurniture` et `PacketFurnitureForcemount` dépendent de `ItemCollar` (V1) pour vérifier collar ownership avant forcemount. | `v2/furniture/EntityFurniture.java`, `v2/furniture/network/PacketFurnitureForcemount.java` | **Fix D-01** : Quand ItemCollar migre vers composants, le check ownership doit utiliser le composant `lockable` ou `collar` au lieu de `instanceof ItemCollar`. | +| F-02 | Basse | `FurnitureAnimationContext.create()` log "V1: skeleton parsing not yet implemented" quand `seatSkeleton` est null. Si le GLB n'a pas de skeleton data parsé, l'animation silencieusement ne se joue pas. | `v2/furniture/client/FurnitureAnimationContext.java` | **Évaluer** : Vérifier que le parser GLB furniture extrait toujours le skeleton. Si oui, le fallback est juste un safety net. Sinon, c'est un bug silencieux. | + +**Verdict : Le système furniture est le plus propre du mod.** Zéro dette architecturale, fidèle au guide artiste, extensible (monster seats prêts). Les deux constats sont mineurs — un couplage V1 qui part avec D-01 et un fallback debug à vérifier. + +### #7 — Entities + AI + +**Hiérarchie d'héritage :** + +``` +PathfinderMob + └─ AbstractTiedUpNpc (1281 lignes, ~100 méthodes) — implements IRestrainable, IAnimatedPlayer, IV2EquipmentHolder + ├─ EntityDamsel (834 lignes) — capturable NPC, personality, dialogue, inventory + │ ├─ EntityDamselShiny — variante rare + │ └─ EntityLaborGuard — garde de prison + └─ EntityKidnapper (2039 lignes, ~170 méthodes) — implements ICaptor, IDialogueSpeaker + └─ EntityKidnapperElite + ├─ EntityKidnapperMerchant — marchand neutre/hostile + ├─ EntityKidnapperArcher — attaque à distance + ├─ EntitySlaveTrader — boss de camp + ├─ EntityMaid — servante du trader + └─ EntityMaster (1192 lignes) — pet play system +``` + +**Positif :** +- **Composant-based decomposition pour Damsel** : `DamselBondageManager`, `DamselPersonalitySystem`, `DamselInventoryManager`, `DamselAIController`, `DamselAnimationController`, `DamselAppearance`, `NpcEquipmentManager`, `NpcCaptivityManager` — 8 components avec interfaces host (`IBondageHost`, `IAIHost`, `IAnimationHost`, etc.). Bonne intention. +- **Composant-based pour Kidnapper** : `KidnapperAggressionSystem`, `KidnapperAlertManager`, `KidnapperAppearance`, `KidnapperCaptiveManager`, `KidnapperCellManager`, `KidnapperCampManager`, `KidnapperStateManager`, `KidnapperSaleManager`, `KidnapperTargetSelector`, `KidnapperDataSerializer` — 10 components avec interfaces host. Très granulaire. +- **AI goals bien séparés** : 80+ goals dédiés par type de NPC. Chaque goal est une classe autonome avec une seule responsabilité (KidnapperCaptureGoal, MasterDogwalkGoal, NpcFarmCommandGoal, etc.). +- **V2 equipment intégré** : `AbstractTiedUpNpc` implémente `IV2EquipmentHolder`, utilise `V2BondageEquipment` directement. Les NPCs sont déjà sur le système V2. +- **State machines Kidnapper** : `KidnapperState` enum avec états clairs (IDLE, HUNTING, CAPTURING, FLEEING, etc.). +- **Master NPC complet** : pet play system avec task manager, state machine, punishment, dogwalk, furniture interaction — complexe mais fonctionnel. + +**Problèmes :** + +| ID | Sévérité | Constat | Fichier(s) | Proposition | +|----|----------|---------|------------|-------------| +| E-01 | Haute | **EntityKidnapper = 2039 lignes**, la plus grosse classe du mod. Malgré la décomposition en 10 components, la classe reste un god class. Elle mélange : ICaptor impl, targeting, capture equipment, sale system, job system, camp system, cell integration, alert system, NBT serialization, display name, dialogue, et des dizaines de getters/helpers. | `entities/EntityKidnapper.java` | **Archi** : Continuer la décomposition. Candidates : extraire le système de vente (`startSale`/`completeSale`/`cancelSale`/`abandonCaptive`) dans un component dédié, extraire le dialogue, extraire le ciblage. Objectif : ramener la classe sous 800 lignes. | +| E-02 | Haute | **AbstractTiedUpNpc = 1281 lignes** avec ~100 méthodes. Même pattern que PlayerBindState (S-01) — c'est une façade de délégation vers les components, mais doit aussi implémenter IRestrainable (30+ méthodes) directement. | `entities/AbstractTiedUpNpc.java` | **Archi** : La taille vient surtout de l'implémentation de IRestrainable. Évaluer si les méthodes bondage peuvent être déléguées à `DamselBondageManager` via un pattern `default` sur IRestrainable (mais IRestrainable est une interface, pas une classe — limité). Ou accepter la taille comme coût de l'implémentation multi-interface. | +| E-03 | Moyenne | **24 fichiers entities** dépendent de V1 item classes (`ItemBind`, `ItemCollar`, `PoseType`, `BindVariant`, etc.). C'est le package le plus impacté par D-01. | 24 fichiers (voir liste grep) | **Fix D-01** : Migration bulk. Les `instanceof ItemBind` deviennent `instanceof IV2BondageItem`, les `ItemCollar` checks deviennent des component checks. `PoseType` et `BindVariant` sont remplacés par des propriétés data-driven. | +| E-04 | Moyenne | **Héritage profond** : EntityMaid → EntityKidnapperElite → EntityKidnapper → AbstractTiedUpNpc → PathfinderMob. 5 niveaux. EntityMaid et EntitySlaveTrader héritent de toute la logique kidnapper (capture, targeting, sale) alors qu'ils n'utilisent pas tout. | `entities/EntityMaid.java`, `entities/EntitySlaveTrader.java` | **Archi** : Envisager une refactorisation vers composition plutôt qu'héritage. La Maid n'est PAS un kidnapper — elle ne devrait pas hériter de `canCapture()`, `getCaptureBindTime()`, etc. Long terme : AbstractTiedUpNpc → EntityDamsel (passive) / EntityKidnapper (hostile), et les autres types composent leurs comportements. Pas urgent mais dette croissante. | +| E-05 | Basse | `EntityDamsel` et `EntityKidnapper` ont des hiérarchies de host interfaces parallèles : `damsel/components/IBondageHost`, `damsel/components/IAIHost` vs `kidnapper/components/IAIHost`, `kidnapper/components/ICaptiveHost`, etc. Certaines pourraient être unifiées. | `entities/damsel/components/*.java`, `entities/kidnapper/components/*.java` | **Pas d'action immédiate** : Les interfaces host sont des contrats internes de chaque sous-arbre. Les unifier créerait un couplage horizontal. Acceptable tel quel. | +| E-06 | Basse | `EntityMaster` (1192 lignes) contient le pet play system complet. Components `MasterPetManager`, `MasterTaskManager`, `MasterStateManager` existent mais la classe orchestre encore beaucoup de logique. | `entities/EntityMaster.java` | **Archi** : Même recommandation que E-01 — continuer la décomposition. Moins urgent car le système est plus cohérent (une seule responsabilité : pet play). | + +### #8 — Events + +**27 handlers, 5722 lignes, 8 domaines** (camp, captivity, combat, interaction, lifecycle, restriction, system). + +**Positif :** Bonne séparation par domaine. La plupart des handlers sont focalisés (85-200 lignes). + +| ID | Sévérité | Constat | Proposition | +|----|----------|---------|-------------| +| EV-01 | Moyenne | `RestraintTaskTickHandler` (675 lignes, 12 @SubscribeEvent) — consolide tous les ticks restraint. | Découper par type de tâche (tying, untying, force-feeding, shock checks). | +| EV-02 | Moyenne | `BondageItemRestrictionHandler` (544 lignes, 12 @SubscribeEvent) — consolide toutes les restrictions. | Découper par type de restriction (legs, arms, gags, etc.). | +| EV-03 | Moyenne | 7/27 handlers importent des classes V1 (`ItemBind`, `ItemCollar`, `ItemGag`, `IKnife`, `ILockable`). | **Fix D-01** : Migrer vers V2 checks (composants). | + +### #9 — Dialogue + Personality + +**31 fichiers, 7625 lignes.** Dialogue 100% data-driven (JSON par personality × speaker type). Personality enum-based (11 types) avec state machine (needs, mood, commands). + +**Positif :** Pipeline de chargement JSON propre (default → personality override → speaker-type). 18 catégories de dialogue. Dépendance unidirectionnelle (dialogue → personality, pas l'inverse). Seulement 3 fichiers importent du V1. + +| ID | Sévérité | Constat | Proposition | +|----|----------|---------|-------------| +| DI-01 | Moyenne | 6 god classes dans dialogue/ (EntityDialogueManager 622l, ConversationManager 564l, GagTalkManager 557l, DialogueLoader 469l, DialogueBridge 463l, DialogueManager 428l). | Acceptable pour la complexité du système. `DialogueBridge` (mapping legacy → new) peut être supprimé après D-01. | +| DI-02 | Basse | `PersonalityState` (709 lignes) — god class conteneur d'état NPC (needs, mood, commands, jobs, home). | Continuer la décomposition si ça grossit. OK pour l'instant. | +| DI-03 | Basse | 3 fichiers importent V1 (`GagTalkManager` → ItemGag, `ToolMode` → ItemBind, `PetRequestManager` → BindVariant). | **Fix D-01** : Migrer. | + +### #10 — Cells / Prison / Blocks + +**41 fichiers, 13329 lignes.** Système SavedData massif (CellRegistryV2, PrisonerManager, CampOwnership, ConfiscatedInventoryRegistry). + +**Positif :** Architecture SavedData correcte. Spatial indexing dans CellRegistryV2. Séparation services (PrisonerService, ItemService, BondageService). + +| ID | Sévérité | Constat | Proposition | +|----|----------|---------|-------------| +| CP-01 | Haute | `PrisonerService` (1058 lignes) — plus grosse classe du package, gère tout le lifecycle prisonnier. | Décomposer : labor, ransom, confiscation pourraient être des services séparés. | +| CP-02 | Moyenne | `MarkerBlockEntity` (1146 lignes) — god class block entity, gère spawning + teleportation + cell deletion. | Extraire la logique spawning et teleportation dans des helpers. | +| CP-03 | Moyenne | `BondageItemBlockEntity` utilise 6 imports V1 (ItemBind, ItemGag, ItemBlindfold, ItemCollar, ItemEarplugs, GenericClothes) pour valider les items dans les trap blocks. | **Fix D-01** : Remplacer par `instanceof IV2BondageItem` + component checks. | +| CP-04 | Basse | `CellRegistryV2` (903 lignes) — gros mais justifié par les index spatiaux. | Acceptable. | + +### #11 — Compat (MCA + Wildfire) + +**24 fichiers, 6536 lignes.** MCA très couplé (21 fichiers, 5 sous-systèmes). Wildfire léger (3 fichiers, rendu only). + +| ID | Sévérité | Constat | Proposition | +|----|----------|---------|-------------| +| CO-01 | Haute | `MCAKidnappedAdapter` (907 lignes) — implémente IRestrainable complet pour les villagers MCA. God class + dépend de V1 items. | **Fix D-01** : Migrer V1 → V2. Décomposer en components comme AbstractTiedUpNpc. | +| CO-02 | Moyenne | `WildfireDamselLayer` (988 lignes) — rendu physique très complexe. | Acceptable pour un système de rendu physique. Pas urgent. | +| CO-03 | Basse | MCA compat utilise WeakHashMap et reflection pour détection — bon pattern de découplage. | Pas d'action. | + +### #12 — Util + Commands + Worldgen + +**55 fichiers, 15475 lignes.** + +| ID | Sévérité | Constat | Proposition | +|----|----------|---------|-------------| +| UC-01 | Haute | `BondageSubCommand` (1232 lignes) — monolithique, contient 16 sous-commandes (tie, untie, gag, collar, etc.) dans un seul fichier. | Découper : TieCommands, GagCommands, CollarCommands, etc. | +| UC-02 | Haute | `RoomTheme` (1368 lignes) — config hardcodée de palettes de blocs pour worldgen. | **Archi** : Externaliser en data-driven (JSON). C'est exactement le type de contenu qui devrait être configurable. | +| UC-03 | Moyenne | 7 fichiers importent V1 items (commands + RestraintApplicator + MCA adapter + HangingCagePiece). | **Fix D-01** : Migrer. | +| UC-04 | Basse | `NPCCommand` (764 lignes) — gros mais focalisé sur le spawning/state NPC. | Acceptable, pourrait split par entity type. | + +--- + +## Bilan Final + +### Statistiques + +- **12/12 systèmes audités** +- **744 classes, 233k lignes** analysées +- **38 constats** documentés (+ 4 résolus par D-01) +- **1 décision architecturale majeure** (D-01 : suppression V1 + composants data-driven) + +### Classement par santé + +| Rang | Système | Verdict | Problèmes | +|------|---------|---------|-----------| +| 1 | Furniture | Exemplaire | 2 mineurs | +| 2 | Animation + GLTF | Excellent | Résidus V1 seulement | +| 3 | Network | Solide | Résidus V1 seulement | +| 4 | Dialogue + Personality | Bon | God classes acceptables | +| 5 | Events | Bon | 2 handlers trop gros | +| 6 | Core | Dette technique | i18n, config triple | +| 7 | State | Croissance organique | God class, thread safety | +| 8 | Cells / Prison | Fonctionnel mais lourd | 11 god classes | +| 9 | Compat | Fonctionnel mais couplé | MCA adapter 907l | +| 10 | Util / Commands / Worldgen | Fonctionnel | BondageSubCmd 1232l, RoomTheme 1368l | +| 11 | Entities + AI | Riche mais massif | 2039l god class, héritage 5 niveaux | +| 12 | Items V1/V2 | **Point critique** | **D-01 : suppression totale V1** | + +### Priorités de correction + +| Priorité | Chantier | Impact | +|----------|----------|--------| +| **P0** | **D-01 : Suppression V1 + composants data-driven** | Élimine la dette #1 du mod. Impacte ~60 fichiers. Requiert un plan dédié. | +| **P1** | C-01 : i18n SystemMessageManager | Requis pour toute traduction du mod. | +| **P1** | UC-02 : RoomTheme → data-driven | 1368 lignes hardcodées de config worldgen. | +| **P2** | E-01/E-02 : Décomposition EntityKidnapper/AbstractTiedUpNpc | 2039 + 1281 lignes. Améliore la maintenabilité entities. | +| **P2** | CP-01 : Décomposition PrisonerService | 1058 lignes. | +| **P2** | UC-01 : Split BondageSubCommand | 1232 lignes en 1 fichier. | +| **P3** | S-02 : Encapsuler MovementState | 8 champs publics mutés directement. | +| **P3** | S-05 : Thread safety cohérente | 3 stratégies sans cohérence dans PlayerBindState. | +| **P3** | C-02 : Unifier Config/GameRules | 25 settings triplés. | +| **P4** | Renommages (C-05 DamselRenderer, C-06 FQCNs) et cleanups cosmétiques. | + +### D-01 Phase 1 — Suivi implémentation + +**Branche :** `feature/d01-component-system` (17 commits, build clean) + +**Review adversariale :** 3 critiques + 5 hauts trouvés et corrigés. + +**Problèmes notés (non bloquants, à traiter lors de la migration Phase 2) :** + +| ID | Constat | Action | +|----|---------|--------| +| SMELL-002 | `GaggingComponent` n'a aucun consommateur — `GagTalkManager` lit `GagMaterial.getComprehension()`, pas le composant. | Lors de la migration Phase 2, faire pointer `GagTalkManager` vers le composant pour les items data-driven. | +| SMELL-003 | Duplication sémantique entre le champ top-level `lockable` (boolean) et le composant `LockableComponent`. Un item doit configurer les deux pour un lock complet. | Lors de la migration Phase 2, le champ `lockable` devrait être dérivé de la présence du composant `lockable`. | +| NOTE-003 | `test_component_gag.json` est dans les resources de production — visible par les joueurs. | Supprimer ou déplacer avant release. OK pour le dev. | \ No newline at end of file diff --git a/docs/plans/D01-branch-A-bridge-utilities.md b/docs/plans/D01-branch-A-bridge-utilities.md new file mode 100644 index 0000000..12dd9b1 --- /dev/null +++ b/docs/plans/D01-branch-A-bridge-utilities.md @@ -0,0 +1,284 @@ +# D-01 Branch A : Bridge Utilities + +> **Prérequis :** Phase 1 (component system) mergée dans develop. +> **Branche :** `feature/d01-branch-a-bridge` +> **Objectif :** Créer les utilitaires V2 qui remplacent la logique V1, SANS supprimer de code V1. À la fin de cette branche, le mod compile, les items V1 et V2 coexistent, le struggle fonctionne pour les deux, et les nouveaux helpers sont prêts pour la migration des consommateurs. + +--- + +## Décisions actées + +- **Stack size** : Stacks de 1 pour tout. Régression acceptée. +- **Save compat** : Breaking change. Pas de migration. Mod en alpha. +- **Résistance** : Config-driven via `resistanceId`, pas hardcodé en JSON. +- **Comprehension/range gags** : Config-driven via `gagMaterial`, délègue à ModConfig au runtime. +- **IHasGaggingEffect/IHasBlindingEffect** : DataDrivenBondageItem les implémente en checkant les composants. + +--- + +## Tâches + +### A1. Modifier ResistanceComponent — config-driven + +**Fichier :** `src/main/java/com/tiedup/remake/v2/bondage/component/ResistanceComponent.java` + +Actuellement stocke un `int baseResistance` hardcodé. Doit stocker un `String resistanceId` et déléguer à `SettingsAccessor.getBindResistance(resistanceId)` au runtime. + +- Remplacer le champ `baseResistance` par `resistanceId` (String) +- `fromJson()` : parse `"id"` au lieu de `"base"` — `"resistance": {"id": "rope"}` +- `getBaseResistance()` : `return SettingsAccessor.getBindResistance(resistanceId);` +- Garder un fallback `"base"` pour backward compat avec test_component_gag.json (ou le mettre à jour) + +**Référence :** `SettingsAccessor.getBindResistance(String)` dans `core/SettingsAccessor.java` — normalise les clés et lit depuis ModConfig. + +--- + +### A2. Modifier GaggingComponent — config-driven + GagMaterial + +**Fichier :** `src/main/java/com/tiedup/remake/v2/bondage/component/GaggingComponent.java` + +Actuellement stocke `comprehension` et `range` hardcodés. Doit stocker un `String material` et déléguer à `GagMaterial`/ModConfig au runtime. + +- Ajouter champ `@Nullable String material` +- `fromJson()` : parse `"material"` — `"gagging": {"material": "ball"}` +- `getComprehension()` : si material != null → `GagMaterial.valueOf(material).getComprehension()` (lit ModConfig). Sinon → fallback au champ hardcodé (compat). +- `getRange()` : idem via `GagMaterial.valueOf(material).getTalkRange()` +- `getMaterial()` : expose le `GagMaterial` enum pour `GagTalkManager` +- Garder les champs `comprehension`/`range` comme overrides optionnels (si présents dans JSON, ils prennent priorité sur le material) + +**Référence :** `GagMaterial` enum dans `items/base/GagVariant.java` ou `util/GagMaterial.java`. + +--- + +### A3. Ajouter `appendTooltip()` à IItemComponent + ComponentHolder + +**Fichiers :** +- `src/main/java/com/tiedup/remake/v2/bondage/component/IItemComponent.java` +- `src/main/java/com/tiedup/remake/v2/bondage/component/ComponentHolder.java` +- `src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenBondageItem.java` + +Ajouter hook tooltip pour que chaque composant contribue des lignes : + +```java +// IItemComponent +default void appendTooltip(ItemStack stack, @Nullable Level level, List tooltip, TooltipFlag flag) {} + +// ComponentHolder +public void appendTooltip(ItemStack stack, @Nullable Level level, List tooltip, TooltipFlag flag) { + for (IItemComponent c : components.values()) c.appendTooltip(stack, level, tooltip, flag); +} +``` + +Dans `DataDrivenBondageItem.appendHoverText()` : appeler `holder.appendTooltip(...)`. + +Implémenter `appendTooltip` dans chaque composant existant : +- `LockableComponent` : affiche "Locked" / "Lockable" +- `ResistanceComponent` : affiche la résistance en advanced mode (F3+H) +- `GaggingComponent` : affiche le type de gag +- `BlindingComponent` : rien (pas d'info utile) +- `ShockComponent` : affiche "Shock: Manual" ou "Shock: Auto (Xs)" +- `GpsComponent` : affiche "GPS Tracking" + zone radius +- `ChokingComponent` : affiche "Choking Effect" +- `AdjustableComponent` : rien (ajustement est visuel) + +--- + +### A4. Champ `pose_type` + PoseTypeHelper + +**Fichiers :** +- `src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenItemDefinition.java` — ajouter `@Nullable String poseType` +- `src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenItemParser.java` — parser `"pose_type"` +- Créer `src/main/java/com/tiedup/remake/v2/bondage/PoseTypeHelper.java` + +```java +public static PoseType getPoseType(ItemStack stack) { + // V2: read from data-driven definition + DataDrivenItemDefinition def = DataDrivenItemRegistry.get(stack); + if (def != null && def.poseType() != null) { + try { return PoseType.valueOf(def.poseType()); } + catch (IllegalArgumentException e) { return PoseType.STANDARD; } + } + // V1 fallback: instanceof ItemBind + if (stack.getItem() instanceof ItemBind bind) { + return bind.getPoseType(); + } + return PoseType.STANDARD; +} +``` + +**Note mixin :** Les mixins (`MixinPlayerModel`, `MixinCamera`, etc.) appellent `itemBind.getPoseType()`. Ils devront migrer vers `PoseTypeHelper.getPoseType(stack)` en Branch C. Le helper doit être dans un package chargé tôt — `v2/bondage/` est OK car le mod est chargé avant les mixins client. + +--- + +### A5. BindModeHelper + +**Fichier :** Créer `src/main/java/com/tiedup/remake/v2/bondage/BindModeHelper.java` + +Méthodes statiques pures NBT (clé `"bindMode"`) : +- `hasArmsBound(ItemStack)` → true si mode "arms" ou "full" +- `hasLegsBound(ItemStack)` → true si mode "legs" ou "full" +- `getBindModeId(ItemStack)` → "full", "arms", ou "legs" +- `cycleBindModeId(ItemStack)` → full→arms→legs→full, retourne le nouveau mode +- `getBindModeTranslationKey(ItemStack)` → clé i18n +- `isBindItem(ItemStack)` → true si l'item a la région ARMS dans sa definition V2, OU est `instanceof ItemBind` + +**Référence :** `ItemBind.java` lignes 64-160 pour les méthodes statiques existantes. + +--- + +### A6. CollarHelper (complet) + +**Fichier :** Créer `src/main/java/com/tiedup/remake/v2/bondage/CollarHelper.java` + +Extraire TOUTES les méthodes NBT de `ItemCollar` (1407 lignes) + 5 sous-classes en méthodes statiques. Sections : + +**Ownership :** +- `isOwner(ItemStack, Player)`, `isOwner(ItemStack, UUID)` +- `getOwners(ItemStack)` → Set +- `addOwner(ItemStack, UUID, String name)`, `removeOwner(ItemStack, UUID)` +- `getBlacklist(ItemStack)`, `addToBlacklist(ItemStack, UUID)`, `removeFromBlacklist(ItemStack, UUID)` +- `getWhitelist(ItemStack)`, `addToWhitelist(ItemStack, UUID)`, `removeFromWhitelist(ItemStack, UUID)` + +**Collar features :** +- `isCollar(ItemStack)` → check OwnershipComponent presence OR instanceof ItemCollar +- `getNickname(ItemStack)`, `setNickname(ItemStack, String)` +- `isKidnappingModeEnabled(ItemStack)`, `setKidnappingModeEnabled(ItemStack, boolean)` +- `getCellId(ItemStack)`, `setCellId(ItemStack, UUID)` +- `shouldTieToPole(ItemStack)`, `setShouldTieToPole(ItemStack, boolean)` +- `shouldWarnMasters(ItemStack)`, `setShouldWarnMasters(ItemStack, boolean)` +- `isBondageServiceEnabled(ItemStack)`, `setBondageServiceEnabled(ItemStack, boolean)` +- `getServiceSentence(ItemStack)`, `setServiceSentence(ItemStack, String)` + +**Shock :** +- `canShock(ItemStack)` → check ShockComponent presence OR instanceof ItemShockCollar +- `isPublic(ItemStack)`, `setPublic(ItemStack, boolean)` +- `getShockInterval(ItemStack)` → depuis ShockComponent ou ItemShockCollarAuto + +**GPS :** +- `hasGPS(ItemStack)` → check GpsComponent presence OR instanceof ItemGpsCollar +- `hasPublicTracking(ItemStack)`, `setPublicTracking(ItemStack, boolean)` +- `getSafeSpots(ItemStack)`, `addSafeSpot(ItemStack, ...)`, `removeSafeSpot(ItemStack, int)` +- `isActive(ItemStack)`, `setActive(ItemStack, boolean)` + +**Choke :** +- `isChokeCollar(ItemStack)` → check ChokingComponent presence OR instanceof ItemChokeCollar +- `isChoking(ItemStack)`, `setChoking(ItemStack, boolean)` +- `isPetPlayMode(ItemStack)`, `setPetPlayMode(ItemStack, boolean)` + +**Alert suppression :** +- `runWithSuppressedAlert(Runnable)` — ThreadLocal, délègue à `ItemCollar` pendant la coexistence +- `isRemovalAlertSuppressed()` → lit le ThreadLocal + +**Référence :** `ItemCollar.java` (1407 lignes), `ItemShockCollar.java` (133 lignes), `ItemGpsCollar.java` (369 lignes), `ItemChokeCollar.java` (154 lignes), `ItemShockCollarAuto.java` (58 lignes). + +--- + +### A7. OwnershipComponent (complet) + +**Fichier :** Créer `src/main/java/com/tiedup/remake/v2/bondage/component/OwnershipComponent.java` +**Modifier :** `ComponentType.java` — ajouter `OWNERSHIP` + +JSON : `"ownership": {}` + +Lifecycle hooks : +- `onEquipped(stack, entity)` : enregistrer dans CollarRegistry (extraire de `ItemCollar.registerCollarInRegistry`). **Note :** le owner initial (le player qui equip) n'est pas dans la signature `onEquipped(stack, entity)`. Options : lire le owner depuis le NBT du stack (déjà écrit par l'interaction flow), ou passer par un tag temporaire. +- `onUnequipped(stack, entity)` : alerter les owners (si pas supprimé via ThreadLocal), désenregistrer du CollarRegistry, reset auto-shock timer. +- `appendTooltip` : nickname, lock status, kidnapping mode, cell ID, bondage service, shock status, GPS status. +- `blocksUnequip` : si locked via ILockable. +- **Override `dropLockOnUnlock()`** : retourner false pour les collars (pas de padlock drop). Note : ceci doit être sur DataDrivenBondageItem, pas sur le composant (ILockable est sur l'Item, pas sur le composant). → DataDrivenBondageItem override `dropLockOnUnlock()` quand OwnershipComponent est présent. + +--- + +### A8. TyingInteractionHelper + DataDrivenBondageItem extensions + +**Fichier :** Créer `src/main/java/com/tiedup/remake/v2/bondage/TyingInteractionHelper.java` +**Modifier :** `src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenBondageItem.java` + +Extraire le flow tying de `ItemBind.interactLivingEntity()` dans `TyingInteractionHelper` : +- Accepte `(ServerPlayer player, LivingEntity target, ItemStack stack, InteractionHand hand)` +- Crée TyingPlayerTask, gère la progress bar, consume l'item, equipe via V2 + +Dans `DataDrivenBondageItem` : +- `use()` : si regions contient ARMS → shift+click cycle bind mode (son + message action bar). Server-side only. +- `interactLivingEntity()` : routing par région : + - ARMS → `TyingInteractionHelper` (tying task flow) + - NECK → collar equip flow (add owner, register CollarRegistry, play sound) — extraire de `ItemCollar.interactLivingEntity()` + - MOUTH/EYES/EARS/HANDS → instant equip (existant via AbstractV2BondageItem) + +--- + +### A9. Réécrire StruggleBinds/StruggleCollar pour V2 + +**Fichiers :** +- `src/main/java/com/tiedup/remake/state/struggle/StruggleBinds.java` +- `src/main/java/com/tiedup/remake/state/struggle/StruggleCollar.java` + +`StruggleBinds.canStruggle()` : +- Actuellement : `instanceof ItemBind` → rejette V2 +- Fix : accepter `instanceof IV2BondageItem` avec region ARMS, OU `instanceof ItemBind` +- Résistance : si V2 → `ResistanceComponent.getBaseResistance()`. Si V1 → existant via `IHasResistance`. + +`StruggleCollar` : +- Actuellement : `instanceof ItemCollar` → rejette V2 +- Fix : accepter items avec `OwnershipComponent` (via `CollarHelper.isCollar(stack)`) +- Résistance collar : via `ResistanceComponent` ou `IHasResistance` + +--- + +### A10. DataDrivenBondageItem implémente IHasGaggingEffect/IHasBlindingEffect + +**Fichier :** `src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenBondageItem.java` + +Ajouter `implements IHasGaggingEffect, IHasBlindingEffect` sur la classe. Les 7+ call sites qui font `instanceof IHasGaggingEffect` continueront de fonctionner. + +**Problème :** Ces interfaces sont des markers (pas de méthodes). La simple présence de l'interface signifie "a l'effet". Mais `DataDrivenBondageItem` est un singleton — TOUS les items data-driven auront ces interfaces. + +**Solution :** Ne PAS implémenter les interfaces marker sur la classe. À la place, lors de la migration Branch C, convertir les call sites vers des component checks. C'est plus propre. + +→ **A10 annulé.** Les call sites migreront en Branch C vers `DataDrivenBondageItem.getComponent(stack, GAGGING, ...) != null`. + +--- + +### A11. Fix PacketSelfBondage — routing par région + +**Fichier :** `src/main/java/com/tiedup/remake/network/selfbondage/PacketSelfBondage.java` + +Dans `handleV2SelfBondage()`, ajouter du routing par région : + +```java +Set regions = v2Item.getOccupiedRegions(stack); +if (regions.contains(BodyRegionV2.NECK)) { + // Cannot self-collar + return; +} +if (regions.contains(BodyRegionV2.ARMS)) { + // Tying task (existing flow) + handleV2SelfBind(player, stack, v2Item, state); +} else { + // Accessories: instant equip (no tying delay) + handleV2SelfAccessory(player, stack, v2Item, state); +} +``` + +Créer `handleV2SelfAccessory()` basé sur le pattern V1 `handleSelfAccessory()` (instant equip, swap si déjà équipé, locked check). + +### A12. NPC speed reduction + +**Fichier :** À déterminer (composant ou event handler) + +`ItemBind.onEquipped()` appelle `RestraintEffectUtils.applyBindSpeedReduction(entity)` pour les NPCs. Cette logique doit survivre à la migration. + +**Option :** Ajouter dans `DataDrivenBondageItem.onEquipped()` (après le component dispatch) un check : si entity n'est PAS un Player ET l'item a la région ARMS → appeler `RestraintEffectUtils.applyBindSpeedReduction(entity)`. + +--- + +## Vérification + +- [ ] `make build` — compilation clean +- [ ] Struggle fonctionne avec V1 items (ropes, chain, collar) +- [ ] Struggle fonctionne avec V2 items (test_component_gag.json ou nouveau test item) +- [ ] Self-bondage V2 : ARMS → tying delay, MOUTH → instant, NECK → rejeté +- [ ] Tooltips : composants contribuent des lignes +- [ ] PoseTypeHelper résout V1 (ItemBind) et V2 (definition.poseType) +- [ ] CollarHelper.isOwner() fonctionne sur V1 ET V2 collars +- [ ] MCP reindex après la branche diff --git a/docs/plans/D01-branch-B-json-definitions.md b/docs/plans/D01-branch-B-json-definitions.md new file mode 100644 index 0000000..8436bcf --- /dev/null +++ b/docs/plans/D01-branch-B-json-definitions.md @@ -0,0 +1,128 @@ +# D-01 Branch B : 46 JSON Item Definitions + +> **Prérequis :** Branch A (bridge utilities) mergée. +> **Branche :** `feature/d01-branch-b-definitions` +> **Objectif :** Créer les définitions JSON data-driven pour les 46 items V1. Chaque item V1 a un équivalent JSON. Les valeurs sont config-driven (pas hardcodées). À la fin, les 46 items V2 existent en parallèle des V1. + +--- + +## Extensions parser nécessaires + +Avant de créer les JSON, vérifier que le parser supporte : +- `pose_type` (string, optionnel) — ajouté en Branch A4 +- `can_attach_padlock` (boolean, default true) — **à ajouter si pas fait en A** +- GaggingComponent `material` — ajouté en Branch A2 +- ResistanceComponent `id` — ajouté en Branch A1 + +**Fichiers :** `DataDrivenItemParser.java`, `DataDrivenItemDefinition.java` + +--- + +## Items à créer + +Tous dans `src/main/resources/data/tiedup/tiedup_items/`. + +### Binds (16 fichiers) + +| Fichier | display_name | resistance id | pose_type | Notes | +|---------|-------------|---------------|-----------|-------| +| `ropes.json` | Ropes | rope | STANDARD | supports_color: true | +| `armbinder.json` | Armbinder | armbinder | STANDARD | | +| `dogbinder.json` | Dogbinder | armbinder | DOG | movement_style: crawl | +| `chain.json` | Chains | chain | STANDARD | | +| `ribbon.json` | Ribbon | ribbon | STANDARD | supports_color: true | +| `slime.json` | Slime Bind | slime | STANDARD | can_attach_padlock: false | +| `vine_seed.json` | Vine Bind | vine | STANDARD | can_attach_padlock: false | +| `web_bind.json` | Web Bind | web | STANDARD | can_attach_padlock: false | +| `shibari.json` | Shibari | rope | STANDARD | supports_color: true | +| `leather_straps.json` | Leather Straps | armbinder | STANDARD | | +| `medical_straps.json` | Medical Straps | armbinder | STANDARD | | +| `beam_cuffs.json` | Beam Cuffs | chain | STANDARD | | +| `duct_tape.json` | Duct Tape | tape | STANDARD | can_attach_padlock: false | +| `straitjacket.json` | Straitjacket | straitjacket | STRAITJACKET | | +| `wrap.json` | Wrap | wrap | WRAP | | +| `latex_sack.json` | Latex Sack | latex_sack | LATEX_SACK | | + +Tous ont : regions `["ARMS"]`, lockable component (sauf organiques), resistance component avec `id`. + +**Référence :** `BindVariant.java` pour les registry names, pose types, supports_color. `ModConfig.ServerConfig` pour les resistance IDs (lignes 413-428). + +### Gags (19 fichiers) + +| Fichier | display_name | gag material | Notes | +|---------|-------------|-------------|-------| +| `cloth_gag.json` | Cloth Gag | cloth | | +| `ropes_gag.json` | Rope Gag | cloth | | +| `cleave_gag.json` | Cleave Gag | cloth | | +| `ribbon_gag.json` | Ribbon Gag | cloth | supports_color: true | +| `ball_gag.json` | Ball Gag | ball | supports_color: true | +| `ball_gag_strap.json` | Ball Gag (Strap) | ball | | +| `tape_gag.json` | Tape Gag | tape | can_attach_padlock: false | +| `wrap_gag.json` | Wrap Gag | tape | | +| `slime_gag.json` | Slime Gag | tape | can_attach_padlock: false | +| `vine_gag.json` | Vine Gag | tape | can_attach_padlock: false | +| `web_gag.json` | Web Gag | tape | can_attach_padlock: false | +| `panel_gag.json` | Panel Gag | panel | | +| `beam_panel_gag.json` | Beam Panel Gag | panel | | +| `chain_panel_gag.json` | Chain Panel Gag | panel | | +| `latex_gag.json` | Latex Gag | latex | | +| `tube_gag.json` | Tube Gag | stuffed | | +| `bite_gag.json` | Bite Gag | bite | | +| `sponge_gag.json` | Sponge Gag | sponge | | +| `baguette_gag.json` | Baguette Gag | baguette | | + +Tous ont : regions `["MOUTH"]`, gagging component avec material, lockable, resistance `{"id": "gag"}`, adjustable. + +**Référence :** `GagVariant.java` pour les materials et registry names. `ModConfig.ServerConfig.gagComprehension/gagRange` pour les valeurs runtime. + +### Blindfolds (2 fichiers) + +| Fichier | display_name | +|---------|-------------| +| `classic_blindfold.json` | Blindfold | +| `blindfold_mask.json` | Blindfold Mask | + +Regions `["EYES"]`, components : blinding, lockable, resistance `{"id": "blindfold"}`, adjustable. + +### Earplugs (1 fichier) + +`classic_earplugs.json` — regions `["EARS"]`, lockable, resistance `{"id": "blindfold"}` (partage la résistance blindfold dans V1). + +### Mittens (1 fichier) + +`leather_mittens.json` — regions `["HANDS"]`, lockable. + +### Collars (5 fichiers) + +| Fichier | display_name | Components spécifiques | +|---------|-------------|----------------------| +| `classic_collar.json` | Classic Collar | ownership, lockable, resistance `{"id": "collar"}` | +| `shock_collar.json` | Shock Collar | + shock `{"damage": 2.0}` | +| `shock_collar_auto.json` | Auto Shock Collar | + shock `{"damage": 2.0, "auto_interval": 200}` | +| `gps_collar.json` | GPS Collar | + gps `{"safe_zone_radius": 50}` | +| `choke_collar.json` | Choke Collar | + choking | + +### Combos (3 fichiers) + +| Fichier | display_name | Regions | Components | +|---------|-------------|---------|-----------| +| `hood.json` | Hood | `["EYES"]` | blinding, gagging `{"material": "cloth"}`, lockable, blocked_regions: `["EYES", "EARS"]` | +| `medical_gag.json` | Medical Gag | `["MOUTH"]` | gagging `{"material": "stuffed"}`, blinding, lockable | +| `ball_gag_3d.json` | Ball Gag 3D | `["MOUTH"]` | gagging `{"material": "ball"}`, lockable, adjustable. Model 3D spécifique. | + +--- + +## Supprimer le fichier test + +Supprimer `src/main/resources/data/tiedup/tiedup_items/test_component_gag.json` (fichier de test Phase 1, plus nécessaire). + +--- + +## Vérification + +- [ ] `make build` — clean +- [ ] `make run` — les 46 items data-driven apparaissent (via `/tiedup give` ou creative tab section data-driven) +- [ ] Résistance = valeur config (changer dans config, vérifier que la résistance change) +- [ ] Gag comprehension = valeur config (changer dans config, vérifier) +- [ ] Collars ownership fonctionne via le nouveau OwnershipComponent +- [ ] Items organiques (slime, vine, web, tape) ne peuvent pas recevoir de padlock diff --git a/docs/plans/D01-branch-C-consumer-migration.md b/docs/plans/D01-branch-C-consumer-migration.md new file mode 100644 index 0000000..14f2413 --- /dev/null +++ b/docs/plans/D01-branch-C-consumer-migration.md @@ -0,0 +1,107 @@ +# D-01 Branch C : Consumer Migration (~97 fichiers) + +> **Prérequis :** Branch A + B mergées. +> **Branche :** `feature/d01-branch-c-migration` +> **Objectif :** Remplacer TOUTES les références V1 (`instanceof ItemBind`, `ItemCollar.isOwner()`, `BindVariant.ROPES`, etc.) par les helpers/composants V2. Les classes V1 existent encore mais ne sont plus référencées. À la fin, `grep -r "instanceof ItemBind\|instanceof ItemGag\|instanceof ItemCollar\|instanceof ItemBlindfold\|instanceof ItemEarplugs\|instanceof ItemMittens\|BindVariant\|GagVariant" src/` retourne ZÉRO résultats (hors items/ lui-même). + +--- + +## Pattern migration + +| V1 | V2 | Notes | +|----|-----|-------| +| `instanceof ItemBind` | `BindModeHelper.isBindItem(stack)` | Ou `instanceof IV2BondageItem` si on a juste besoin de savoir que c'est un bondage item | +| `ItemBind.hasArmsBound(stack)` | `BindModeHelper.hasArmsBound(stack)` | Mêmes NBT keys | +| `ItemBind.hasLegsBound(stack)` | `BindModeHelper.hasLegsBound(stack)` | | +| `ItemBind.getBindModeId(stack)` | `BindModeHelper.getBindModeId(stack)` | | +| `itemBind.getPoseType()` | `PoseTypeHelper.getPoseType(stack)` | | +| `BindVariant.ROPES` / `ModItems.getBind(variant)` | `DataDrivenBondageItem.createStack(rl("tiedup:ropes"))` | **LAZY !** Ne pas appeler dans des static initializers | +| `instanceof ItemCollar` + methods | `CollarHelper.isCollar(stack)` + `CollarHelper.method(stack)` | | +| `instanceof ItemShockCollar` | `CollarHelper.canShock(stack)` | | +| `instanceof ItemGpsCollar` | `CollarHelper.hasGPS(stack)` | | +| `instanceof ItemChokeCollar` | `CollarHelper.isChokeCollar(stack)` | | +| `instanceof IHasGaggingEffect` | `DataDrivenBondageItem.getComponent(stack, GAGGING, GaggingComponent.class) != null` | Pour V2 items. V1 items gardent l'interface pendant la transition. | +| `instanceof IHasBlindingEffect` | `DataDrivenBondageItem.getComponent(stack, BLINDING, BlindingComponent.class) != null` | Idem | +| `instanceof ItemGag` + `getGagMaterial()` | `GaggingComponent comp = getComponent(stack, GAGGING, ...)` + `comp.getMaterial()` | | +| `PoseType` enum direct | Inchangé — l'enum est conservé | | +| `IHasResistance` methods | Inchangé — l'interface est conservée | | +| `ILockable` methods | Inchangé — l'interface est conservée | | + +--- + +## Ordre de migration (critique d'abord) + +### Phase 1 : State core (12 fichiers) + +Ces fichiers sont la fondation — les migrer d'abord assure que le reste fonctionne. + +| Fichier | Changements | +|---------|------------| +| `state/IBondageState.java` | `hasArmsBound()`/`hasLegsBound()` → `BindModeHelper` | +| `state/PlayerBindState.java` | `instanceof ItemCollar` → `CollarHelper`, `instanceof ItemBind` → `BindModeHelper` | +| `state/PlayerCaptorManager.java` | `instanceof ItemCollar` → `CollarHelper.isCollar() + CollarHelper.getOwners()` | +| `state/HumanChairHelper.java` | `PoseType` import OK (conservé) | +| `state/components/PlayerEquipment.java` | **Garder `equipInRegion()` V1 fallback** (migre en Branch D). Remplacer `instanceof ItemBind/ItemCollar` dans resistance methods. | +| `state/components/PlayerDataRetrieval.java` | `instanceof ItemCollar` → `CollarHelper.getNickname()` | +| `state/components/PlayerLifecycle.java` | imports V1 → V2 helpers | +| `state/components/PlayerShockCollar.java` | `instanceof ItemShockCollar` → `CollarHelper.canShock()` | +| `state/struggle/StruggleBinds.java` | Déjà migré en Branch A9 — vérifier | +| `state/struggle/StruggleCollar.java` | Déjà migré en Branch A9 — vérifier | +| `state/struggle/StruggleAccessory.java` | Vérifier | + +### Phase 2 : Client animation/render (12 fichiers) + +| Fichier | Changements | +|---------|------------| +| `client/animation/tick/AnimationTickHandler.java` | `instanceof ItemBind` → `PoseTypeHelper.getPoseType()` + `BindModeHelper` | +| `client/animation/tick/NpcAnimationTickHandler.java` | Idem | +| `client/animation/render/PlayerArmHideEventHandler.java` | `instanceof ItemBind` → `PoseTypeHelper` | +| `client/animation/render/DogPoseRenderHandler.java` | Idem | +| `client/animation/render/PetBedRenderHandler.java` | Idem | +| `client/animation/util/AnimationIdBuilder.java` | `PoseType` import OK (conservé) | +| `client/animation/StaticPoseApplier.java` | `PoseType` import OK | +| `client/model/DamselModel.java` | `PoseType` + `instanceof ItemBind` → helpers | +| `client/FirstPersonMittensRenderer.java` | `BindVariant.ROPES` → lazy createStack | +| `mixin/client/MixinPlayerModel.java` | `instanceof ItemBind` → `PoseTypeHelper` | +| `mixin/client/MixinCamera.java` | Idem | +| `mixin/client/MixinVillagerEntityBaseModelMCA.java` | Idem | + +### Phase 3 : Entity AI goals (15 fichiers) + +Principalement `instanceof ItemBind` → `BindModeHelper`, `ModItems.getBind(variant)` → `createStack(rl)`, `instanceof ItemCollar` → `CollarHelper`. + +### Phase 4 : Network packets (14 fichiers) + +`PacketSelfBondage` déjà migré en A11. Reste : `PacketSlaveAction`, `PacketMasterEquip`, `PacketAssignCellToCollar`, `PacketNpcCommand`, etc. → `CollarHelper`. + +### Phase 5 : Events (8 fichiers) + +`BondageItemRestrictionHandler`, `RestraintTaskTickHandler`, `PlayerEnslavementHandler`, `ChatEventHandler`, etc. + +### Phase 6 : Commands (6 fichiers) + +`BondageSubCommand` (1232 lignes) — le plus gros. `BindVariant` → `createStack()`. `NPCCommand`, `CollarCommand`, `KidnapSetCommand`. + +### Phase 7 : Entity classes (15 fichiers) + +`EntityKidnapper`, `KidnapperCaptureEquipment`, `KidnapperTheme`, `KidnapperItemSelector`, `KidnapperCollarConfig`, etc. + +### Phase 8 : Compat MCA (5 fichiers) + +`MCAKidnappedAdapter` (907 lignes) — `instanceof IHasGaggingEffect/IHasBlindingEffect` → component checks. `instanceof ItemCollar` → `CollarHelper`. + +### Phase 9 : Autres (10 fichiers) + +Dialogue (`GagTalkManager`, `PetRequestManager`), worldgen (`HangingCagePiece`), util (`RestraintApplicator`), blocks (`BondageItemBlockEntity`), dispenser, creative tab. + +--- + +## Vérification + +- [ ] `make build` — clean +- [ ] `grep -r "instanceof ItemBind\b" src/ --include="*.java" | grep -v "items/"` → 0 résultats +- [ ] `grep -r "instanceof ItemGag\b" src/ --include="*.java" | grep -v "items/"` → 0 résultats +- [ ] `grep -r "instanceof ItemCollar\b" src/ --include="*.java" | grep -v "items/"` → 0 résultats +- [ ] `grep -r "BindVariant\b" src/ --include="*.java" | grep -v "items/"` → 0 résultats +- [ ] `grep -r "GagVariant\b" src/ --include="*.java" | grep -v "items/"` → 0 résultats +- [ ] `make run` — le mod fonctionne normalement diff --git a/docs/plans/D01-branch-D-v1-cleanup.md b/docs/plans/D01-branch-D-v1-cleanup.md new file mode 100644 index 0000000..055420b --- /dev/null +++ b/docs/plans/D01-branch-D-v1-cleanup.md @@ -0,0 +1,188 @@ +# D-01 Branch D : V1 Cleanup + +> **Prérequis :** Branch A + B + C mergées. Zero références V1 hors du package `items/`. +> **Branche :** `feature/d01-branch-d-cleanup` +> **Objectif :** Supprimer toutes les classes V1 bondage. Migrer `equipInRegion()` vers le flow V2 complet. Réécrire le creative tab. À la fin, zéro classe V1 bondage dans le mod. + +--- + +## Décisions + +- **Save compat :** Breaking change. Les items V1 dans les inventaires existants seront perdus. Mod en alpha. +- **Pas de MissingMappingsEvent.** Simplement supprimer les registrations. + +--- + +## Tâches + +### D1. Migrer equipInRegion() → V2EquipmentHelper.equipItem() + +**Fichier :** `src/main/java/com/tiedup/remake/state/components/PlayerEquipment.java` + +Maintenant que tous les items sont `DataDrivenBondageItem` (qui implémente `IV2BondageItem`), le bypass direct `setInRegion()` n'est plus nécessaire. + +Remplacer `equipInRegion()` par un appel à `V2EquipmentHelper.equipItem()` qui fait la conflict resolution complète (swap, supersede, blocked regions). + +Vérifier que les méthodes `putBindOn()`, `putGagOn()`, `putCollarOn()`, etc. fonctionnent toujours via le nouveau path. + +--- + +### D2. Supprimer les classes V1 (~30 fichiers) + +**À supprimer :** + +Abstract bases : +- `items/base/ItemBind.java` (637 lignes) +- `items/base/ItemGag.java` (93 lignes) +- `items/base/ItemBlindfold.java` (89 lignes) +- `items/base/ItemCollar.java` (1407 lignes) +- `items/base/ItemEarplugs.java` (90 lignes) +- `items/base/ItemMittens.java` (72 lignes) + +Interfaces V1-only : +- `items/base/IBondageItem.java` (102 lignes) +- `items/base/IHasGaggingEffect.java` (33 lignes) +- `items/base/IHasBlindingEffect.java` (33 lignes) +- `items/base/IAdjustable.java` (49 lignes) +- `items/base/ItemOwnerTarget.java` +- `items/base/ItemColor.java` + +Variant enums : +- `items/base/BindVariant.java` (90 lignes) +- `items/base/GagVariant.java` (163 lignes) +- `items/base/BlindfoldVariant.java` (48 lignes) +- `items/base/EarplugsVariant.java` (33 lignes) +- `items/base/MittensVariant.java` (35 lignes) + +Factory classes : +- `items/GenericBind.java` (68 lignes) +- `items/GenericGag.java` (72 lignes) +- `items/GenericBlindfold.java` (37 lignes) +- `items/GenericEarplugs.java` (37 lignes) +- `items/GenericMittens.java` (37 lignes) + +Collars : +- `items/ItemClassicCollar.java` (21 lignes) +- `items/ItemShockCollar.java` (133 lignes) +- `items/ItemShockCollarAuto.java` (58 lignes) +- `items/ItemGpsCollar.java` (369 lignes) +- `items/ItemChokeCollar.java` (154 lignes) + +Combos : +- `items/ItemHood.java` (35 lignes) +- `items/ItemMedicalGag.java` (24 lignes) +- `items/bondage3d/gags/ItemBallGag3D.java` (78 lignes) +- `items/bondage3d/IHas3DModelConfig.java` +- `items/bondage3d/Model3DConfig.java` + +**À CONSERVER :** +- `items/base/ILockable.java` — utilisé par V2 (AbstractV2BondageItem) +- `items/base/IHasResistance.java` — utilisé par V2 (DataDrivenBondageItem) +- `items/base/IKnife.java` — utilisé par GenericKnife (tool) +- `items/base/PoseType.java` — utilisé par animation system +- `items/base/KnifeVariant.java` — utilisé par GenericKnife (tool) +- `items/base/AdjustmentHelper.java` — utilisé par adjustment packets +- `items/GenericKnife.java` — tool, pas bondage +- `items/clothes/GenericClothes.java` — déjà V2 +- `items/clothes/ClothesProperties.java` +- `items/ModItems.java` — garde les tools, supprime les bondage +- `items/ModCreativeTabs.java` — réécrit (voir D3) +- Tous les tool items (whip, padlock, key, lockpick, taser, etc.) + +--- + +### D3. Réécrire ModItems — retirer les registrations V1 + +**Fichier :** `src/main/java/com/tiedup/remake/items/ModItems.java` + +Supprimer : +- `BINDS` map + `registerAllBinds()` +- `GAGS` map + `registerAllGags()` +- `BLINDFOLDS` map + `registerAllBlindfolds()` +- `EARPLUGS` map + `registerAllEarplugs()` +- `MITTENS` map + `registerAllMittens()` +- `BALL_GAG_3D`, `MEDICAL_GAG`, `HOOD` +- `CLASSIC_COLLAR`, `SHOCK_COLLAR`, `SHOCK_COLLAR_AUTO`, `GPS_COLLAR`, `CHOKE_COLLAR` +- Les helper accessors `getBind()`, `getGag()`, etc. + +Garder : CLOTHES, tous les tools (WHIP, PADLOCK, KEY, etc.), KNIVES, spawn eggs. + +--- + +### D4. Réécrire ModCreativeTabs + +**Fichier :** `src/main/java/com/tiedup/remake/items/ModCreativeTabs.java` + +Remplacer l'itération par variant enums par : + +```java +// Data-driven bondage items +for (DataDrivenItemDefinition def : DataDrivenItemRegistry.getAll()) { + output.accept(DataDrivenBondageItem.createStack(def.id())); +} +``` + +Pour l'ordre : ajouter un champ optionnel `"creative_tab_order"` aux definitions JSON, ou trier par catégorie (regions) puis par nom. + +Pour les couleurs : si l'item a `supports_color`, ajouter les variantes colorées. Utiliser `tint_channels` du definition. + +--- + +### D5. Cleanup PoseTypeHelper — retirer le fallback V1 + +**Fichier :** `src/main/java/com/tiedup/remake/v2/bondage/PoseTypeHelper.java` + +Supprimer le fallback `instanceof ItemBind` dans `getPoseType()`. Ne garder que le path data-driven. + +--- + +### D6. Cleanup CollarHelper — retirer les fallbacks V1 + +**Fichier :** `src/main/java/com/tiedup/remake/v2/bondage/CollarHelper.java` + +Les méthodes comme `isCollar(stack)` checkent `instanceof ItemCollar` en fallback V1. Retirer ces checks. + +--- + +### D7. Cleanup BindModeHelper — retirer le fallback V1 + +Idem — retirer `instanceof ItemBind` fallback dans `isBindItem()`. + +--- + +### D8. Cleanup imports orphelins + +Faire un pass sur tout le projet pour retirer les imports V1 orphelins. + +```bash +grep -rn "import com.tiedup.remake.items.base.ItemBind" src/ --include="*.java" +grep -rn "import com.tiedup.remake.items.base.ItemCollar" src/ --include="*.java" +grep -rn "import com.tiedup.remake.items.base.IBondageItem" src/ --include="*.java" +# etc. — tout doit retourner 0 +``` + +--- + +## Vérification finale + +- [ ] `make build` — clean, zero errors +- [ ] `make run` — le mod démarre, les items apparaissent dans le creative tab +- [ ] `grep -r "items.base.ItemBind\|items.base.ItemGag\|items.base.ItemCollar\|items.base.ItemBlindfold\|items.base.ItemEarplugs\|items.base.ItemMittens\|items.base.IBondageItem\|BindVariant\|GagVariant\|BlindfoldVariant\|EarplugsVariant\|MittensVariant" src/main/java/ --include="*.java"` → **0 résultats** +- [ ] Les items data-driven s'équipent/se déséquipent correctement +- [ ] Le struggle fonctionne (binds + collars) +- [ ] Le self-bondage fonctionne (routing par région) +- [ ] Les collars gardent leur ownership/shock/GPS après equip/unequip +- [ ] Les tooltips affichent toutes les infos composants +- [ ] `equipInRegion()` utilise V2EquipmentManager (conflict resolution active) +- [ ] MCP reindex final + +--- + +## Résultat attendu + +- **~6500 lignes de code V1 supprimées** +- **46 items = 46 fichiers JSON** (data-driven, extensible par resource packs) +- **1 seul Item singleton** (`DataDrivenBondageItem`) +- **8 composants** gèrent toute la logique gameplay +- **3 helpers** (`BindModeHelper`, `PoseTypeHelper`, `CollarHelper`) remplacent les anciennes classes +- **Zero couplage V1** dans le reste du mod diff --git a/docs/plans/D01-branch-E-resistance-rework.md b/docs/plans/D01-branch-E-resistance-rework.md new file mode 100644 index 0000000..750d6d0 --- /dev/null +++ b/docs/plans/D01-branch-E-resistance-rework.md @@ -0,0 +1,155 @@ +# D-01 Branch E : Resistance & Lock System Rework + +> **Prérequis :** Branch D (V1 cleanup) mergée. +> **Branche :** `feature/d01-branch-e-resistance` +> **Objectif :** Redesign complet du système de résistance/lock. + +--- + +## Nouveau modèle + +### Principes + +1. **La résistance vient de l'item** — définie dans le JSON via `ResistanceComponent`, point final. +2. **Le lock est binaire** — on/off. Pas de "lock resistance" séparée. Le lock *active* la nécessité de struggle pour les items non-ARMS. +3. **ARMS = toujours actif** — un bind aux bras nécessite toujours un struggle pour s'en libérer soi-même, locké ou non. +4. **Non-ARMS + pas locké = libre** — un gag/blindfold/collar non-locké peut être retiré librement (par soi-même ou un autre joueur). +5. **Non-ARMS + locké = struggle requis** — le lock active la résistance de l'item. +6. **Un autre joueur peut aider** — retirer un item non-locké sur un autre joueur ne nécessite pas de struggle (aide). + +### Matrice de struggle + +| Région | Locké ? | Self-remove | Autre joueur remove | +|--------|---------|-------------|---------------------| +| ARMS | Non | Struggle (résistance item) | Libre (aide) | +| ARMS | Oui | Struggle (résistance item) | Struggle (résistance item) | +| Non-ARMS | Non | Libre | Libre | +| Non-ARMS | Oui | Struggle (résistance item) | Struggle (résistance item) | + +### Items organiques (slime, vine, web, tape) + +Ces items sont "lockés par nature" — pas de padlock possible mais impossible à retirer sans struggle. + +**Option :** Nouveau composant `BuiltInLockComponent` dans le JSON : +```json +"components": { + "resistance": {"id": "slime"}, + "built_in_lock": {} +} +``` + +`BuiltInLockComponent` : +- `blocksUnequip()` retourne `true` (comme un lock, mais sans padlock) +- `ILockable.canAttachPadlock()` retourne `false` (déjà le cas pour les organiques) +- L'item se comporte comme un ARMS bind : toujours struggle required + +**Alternative :** Flag `"always_locked": true` sur la definition JSON. Plus simple, pas besoin de nouveau composant. + +--- + +## Problèmes actuels que ce rework corrige + +### P1. Singleton MAX scan + +`DataDrivenBondageItem.getBaseResistance(LivingEntity)` retourne le MAX de tous les items data-driven équipés. Un gag résistance 50 hérite de la résistance 200 du chain bind. + +**Fix :** Initialiser `currentResistance` dans le NBT à l'equip depuis `ResistanceComponent.getBaseResistance()`. Plus jamais de fallback au MAX scan runtime. Le `getBaseResistance(LivingEntity)` du singleton devient un no-op/fallback qui n'est plus utilisé par le struggle. + +### P2. isItemLocked() dead code + +`StruggleState.struggle()` ne call jamais `isItemLocked()`. Le x10 penalty n'est jamais appliqué. + +**Fix :** Supprimer le concept de "locked penalty". Avec le nouveau modèle, le lock **active** le struggle, il ne le ralentit pas. Si l'item est locké, il faut struggle avec la résistance complète de l'item. Si non locké (et non-ARMS), pas de struggle du tout. + +### P3. Lock resistance / item resistance déconnectés + +`ILockable.getLockResistance()` vs `IHasResistance.getBaseResistance()` sont deux systèmes indépendants. + +**Fix :** Supprimer `ILockable.getLockResistance()` / `getCurrentLockResistance()` / `setCurrentLockResistance()` / `initializeLockResistance()` / `clearLockResistance()`. La résistance du lockpick minigame utilise directement la résistance de l'item (ou un multiplicateur fixe). + +### P4. Dice-roll ignore le lock + +**Fix :** Avec le nouveau modèle, le dice-roll ne change pas. C'est `canStruggle()` qui gate l'accès : +```java +// StruggleBinds.canStruggle() +// ARMS: toujours struggle-able (self) +return true; + +// StruggleCollar/StruggleAccessory.canStruggle() +// Non-ARMS: seulement si locké +return isLocked(stack) || hasBuiltInLock(stack); +``` + +--- + +## Bugs pré-existants à corriger dans cette branche + +### B1. V1 ItemCollar.onUnequipped() — suppressed path skip unregister + +Quand `isRemovalAlertSuppressed()` est true, `ItemCollar.onUnequipped()` return early SANS appeler `CollarRegistry.unregisterWearer()`. Entrées fantômes persistées. + +**Fichier :** `items/base/ItemCollar.java` lignes 1382-1395 +**Fix :** Ajouter `unregisterWearer()` dans le branch suppressed. + +### B2. DataDrivenItemRegistry.clear() pas synchronisé + +`clear()` écrit `SNAPSHOT = EMPTY` sans acquérir `RELOAD_LOCK`. Race avec `mergeAll()`. + +**Fichier :** `v2/bondage/datadriven/DataDrivenItemRegistry.java` ligne 142 +**Fix :** Synchroniser sur `RELOAD_LOCK`. + +### B3. V2TyingPlayerTask.heldStack reference stale + +Le held item peut être remplacé entre début et fin du tying → item dupliqué. + +**Fichier :** `tasks/V2TyingPlayerTask.java` ligne 80 +**Fix :** Valider `heldStack` non-vide et matching avant equip dans `onComplete()`. + +--- + +## Tâches + +### E1. Initialiser currentResistance à l'equip + +Dans `DataDrivenBondageItem.onEquipped()` et les hooks V1 `onEquipped()` : +- Lire `ResistanceComponent.getBaseResistance()` (ou `IHasResistance.getBaseResistance()` pour V1) +- Écrire immédiatement dans le NBT via `setCurrentResistance(stack, base)` +- Élimine le MAX scan comme source d'initialisation + +### E2. Refactor canStruggle() — nouveau modèle + +- `StruggleBinds.canStruggle()` : ARMS → toujours true (self) si item existe +- Nouveau `StruggleAccessory` (ou refactor de StruggleCollar) : non-ARMS → true seulement si locké ou built-in lock +- Supprimer `isItemLocked()` penalty (dead code de toute façon) + +### E3. "Aide" — remove non-locké par un autre joueur + +Modifier `AbstractV2BondageItem.interactLivingEntity()` : +- Si clic sur un joueur qui porte l'item ET item non-locké ET clicker n'a pas l'item en main → retirer l'item (aide) +- Ou via un packet dédié (clic droit main vide sur joueur attaché) + +### E4. BuiltInLockComponent ou flag `always_locked` + +Pour les items organiques qui ne peuvent pas avoir de padlock mais nécessitent un struggle. + +### E5. Cleanup ILockable — supprimer lock resistance + +Supprimer : `getLockResistance()`, `getCurrentLockResistance()`, `setCurrentLockResistance()`, `initializeLockResistance()`, `clearLockResistance()`. + +Le lockpick minigame utilise la résistance de l'item directement (ou un multiplicateur config). + +### E6. Fix bugs pré-existants (B1, B2, B3) + +--- + +## Vérification + +- [ ] V2 bind résistance 50 + V2 gag résistance 80 : chacun a sa propre résistance (pas MAX) +- [ ] Gag non-locké → retirable sans struggle +- [ ] Gag locké → struggle avec résistance du gag +- [ ] Bind ARMS non-locké → self-struggle requis, autre joueur peut aider (libre) +- [ ] Bind ARMS locké → self-struggle requis, autre joueur aussi struggle +- [ ] Slime bind (built-in lock) → struggle obligatoire, pas de padlock possible +- [ ] `currentResistance` initialisé dans NBT dès l'equip +- [ ] CollarRegistry clean après removals légitimes +- [ ] Pas de duplication d'item via tying task diff --git a/docs/superpowers/plans/2026-04-13-d01-phase1-component-system.md b/docs/superpowers/plans/2026-04-13-d01-phase1-component-system.md new file mode 100644 index 0000000..de7a3be --- /dev/null +++ b/docs/superpowers/plans/2026-04-13-d01-phase1-component-system.md @@ -0,0 +1,869 @@ +# D-01 Phase 1: Data-Driven Item Component System + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Create a reusable component system so data-driven bondage items can declare gameplay behaviors (lockable, shock, GPS, gagging, etc.) in their JSON definition instead of requiring per-item Java classes. + +**Architecture:** Each component is a self-contained behavior module implementing `IItemComponent`. Components are declared in item JSON (`"components": {"shock": {...}}`), parsed by an extended `DataDrivenItemParser`, stored on `DataDrivenItemDefinition`, and ticked/queried via `DataDrivenBondageItem` delegation. The existing `ILockable` and `IHasResistance` interfaces are preserved as shared contracts — components implement them. + +**Tech Stack:** Java 17, Forge 1.20.1, existing V2 data-driven infrastructure (`DataDrivenItemRegistry`, `DataDrivenItemParser`, `DataDrivenItemDefinition`, `DataDrivenBondageItem`) + +**Scope:** This plan builds ONLY the component infrastructure + 3 core components (lockable, resistance, gagging). The remaining 5 components (shock, GPS, blinding, choking, adjustable) follow the same pattern and will be added in subsequent tasks or a follow-up plan. + +--- + +## File Structure + +### New files + +| File | Responsibility | +|------|---------------| +| `v2/bondage/component/IItemComponent.java` | Component interface: lifecycle hooks, tick, query | +| `v2/bondage/component/ComponentType.java` | Enum of all component types with factory methods | +| `v2/bondage/component/ComponentHolder.java` | Container: holds instantiated components for an item stack | +| `v2/bondage/component/LockableComponent.java` | Lock/unlock, padlock, key matching, jam, lock resistance | +| `v2/bondage/component/ResistanceComponent.java` | Struggle resistance with configurable base value | +| `v2/bondage/component/GaggingComponent.java` | Muffled speech, comprehension %, range limit | + +### Modified files + +| File | Changes | +|------|---------| +| `v2/bondage/datadriven/DataDrivenItemDefinition.java` | Add `Map componentConfigs` field | +| `v2/bondage/datadriven/DataDrivenItemParser.java` | Parse `"components"` JSON block | +| `v2/bondage/datadriven/DataDrivenBondageItem.java` | Delegate lifecycle hooks to components, expose `getComponent()` | +| `v2/bondage/datadriven/DataDrivenItemRegistry.java` | Instantiate `ComponentHolder` per definition | + +--- + +## Tasks + +### Task 1: IItemComponent interface + +**Files:** +- Create: `src/main/java/com/tiedup/remake/v2/bondage/component/IItemComponent.java` + +- [ ] **Step 1: Create the component interface** + +```java +package com.tiedup.remake.v2.bondage.component; + +import com.google.gson.JsonObject; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.item.ItemStack; + +/** + * A reusable behavior module for data-driven bondage items. + * Components are declared in JSON and instantiated per item definition. + * + *

Lifecycle: parse config once (from JSON), then tick/query per equipped entity.

+ */ +public interface IItemComponent { + + /** + * Called when the item is equipped on an entity. + * @param stack The equipped item stack + * @param entity The entity wearing the item + */ + default void onEquipped(ItemStack stack, LivingEntity entity) {} + + /** + * Called when the item is unequipped from an entity. + * @param stack The unequipped item stack + * @param entity The entity that was wearing the item + */ + default void onUnequipped(ItemStack stack, LivingEntity entity) {} + + /** + * Called every tick while the item is equipped. + * @param stack The equipped item stack + * @param entity The entity wearing the item + */ + default void onWornTick(ItemStack stack, LivingEntity entity) {} + + /** + * Whether this component prevents the item from being unequipped. + * @param stack The equipped item stack + * @param entity The entity wearing the item + * @return true if unequip should be blocked + */ + default boolean blocksUnequip(ItemStack stack, LivingEntity entity) { + return false; + } +} +``` + +- [ ] **Step 2: Verify file compiles** + +Run: `cd /home/user/Documents/Projet/Open-TiedUp! && make build 2>&1 | tail -5` +Expected: BUILD SUCCESSFUL + +- [ ] **Step 3: Commit** + +```bash +git add src/main/java/com/tiedup/remake/v2/bondage/component/IItemComponent.java +git commit -m "feat(D-01): add IItemComponent interface for data-driven item behaviors" +``` + +--- + +### Task 2: ComponentType enum + +**Files:** +- Create: `src/main/java/com/tiedup/remake/v2/bondage/component/ComponentType.java` + +- [ ] **Step 1: Create the component type registry** + +```java +package com.tiedup.remake.v2.bondage.component; + +import com.google.gson.JsonObject; +import javax.annotation.Nullable; +import java.util.function.Function; + +/** + * All known component types. Each type knows how to instantiate itself from JSON config. + */ +public enum ComponentType { + LOCKABLE("lockable", LockableComponent::fromJson), + RESISTANCE("resistance", ResistanceComponent::fromJson), + GAGGING("gagging", GaggingComponent::fromJson); + // Future: SHOCK, GPS, BLINDING, CHOKING, ADJUSTABLE + + private final String jsonKey; + private final Function factory; + + ComponentType(String jsonKey, Function factory) { + this.jsonKey = jsonKey; + this.factory = factory; + } + + public String getJsonKey() { + return jsonKey; + } + + public IItemComponent create(JsonObject config) { + return factory.apply(config); + } + + /** + * Look up a ComponentType by its JSON key. Returns null if unknown. + */ + @Nullable + public static ComponentType fromKey(String key) { + for (ComponentType type : values()) { + if (type.jsonKey.equals(key)) { + return type; + } + } + return null; + } +} +``` + +Note: This file will not compile yet because `LockableComponent`, `ResistanceComponent`, and `GaggingComponent` don't exist. We'll create stub classes first, then implement them. + +- [ ] **Step 2: Create stub classes so the enum compiles** + +Create three empty stubs (they will be fully implemented in Tasks 4-6): + +`src/main/java/com/tiedup/remake/v2/bondage/component/LockableComponent.java`: +```java +package com.tiedup.remake.v2.bondage.component; + +import com.google.gson.JsonObject; + +public class LockableComponent implements IItemComponent { + private LockableComponent() {} + + public static IItemComponent fromJson(JsonObject config) { + return new LockableComponent(); + } +} +``` + +`src/main/java/com/tiedup/remake/v2/bondage/component/ResistanceComponent.java`: +```java +package com.tiedup.remake.v2.bondage.component; + +import com.google.gson.JsonObject; + +public class ResistanceComponent implements IItemComponent { + private ResistanceComponent() {} + + public static IItemComponent fromJson(JsonObject config) { + return new ResistanceComponent(); + } +} +``` + +`src/main/java/com/tiedup/remake/v2/bondage/component/GaggingComponent.java`: +```java +package com.tiedup.remake.v2.bondage.component; + +import com.google.gson.JsonObject; + +public class GaggingComponent implements IItemComponent { + private GaggingComponent() {} + + public static IItemComponent fromJson(JsonObject config) { + return new GaggingComponent(); + } +} +``` + +- [ ] **Step 3: Verify all files compile** + +Run: `cd /home/user/Documents/Projet/Open-TiedUp! && make build 2>&1 | tail -5` +Expected: BUILD SUCCESSFUL + +- [ ] **Step 4: Commit** + +```bash +git add src/main/java/com/tiedup/remake/v2/bondage/component/ +git commit -m "feat(D-01): add ComponentType enum with stub component classes" +``` + +--- + +### Task 3: ComponentHolder container + +**Files:** +- Create: `src/main/java/com/tiedup/remake/v2/bondage/component/ComponentHolder.java` + +- [ ] **Step 1: Create the component container** + +```java +package com.tiedup.remake.v2.bondage.component; + +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.item.ItemStack; + +import javax.annotation.Nullable; +import java.util.Collections; +import java.util.EnumMap; +import java.util.Map; + +/** + * Holds instantiated components for an item definition. + * Immutable after construction. One per DataDrivenItemDefinition. + */ +public final class ComponentHolder { + + public static final ComponentHolder EMPTY = new ComponentHolder(Map.of()); + + private final Map components; + + public ComponentHolder(Map components) { + this.components = components.isEmpty() + ? Map.of() + : Collections.unmodifiableMap(new EnumMap<>(components)); + } + + /** + * Get a component by type, or null if not present. + */ + @Nullable + public IItemComponent get(ComponentType type) { + return components.get(type); + } + + /** + * Get a component by type, cast to the expected class. + * Returns null if not present or wrong type. + */ + @Nullable + @SuppressWarnings("unchecked") + public T get(ComponentType type, Class clazz) { + IItemComponent component = components.get(type); + if (clazz.isInstance(component)) { + return (T) component; + } + return null; + } + + /** + * Check if a component type is present. + */ + public boolean has(ComponentType type) { + return components.containsKey(type); + } + + /** + * Fire onEquipped for all components. + */ + public void onEquipped(ItemStack stack, LivingEntity entity) { + for (IItemComponent component : components.values()) { + component.onEquipped(stack, entity); + } + } + + /** + * Fire onUnequipped for all components. + */ + public void onUnequipped(ItemStack stack, LivingEntity entity) { + for (IItemComponent component : components.values()) { + component.onUnequipped(stack, entity); + } + } + + /** + * Fire onWornTick for all components. + */ + public void onWornTick(ItemStack stack, LivingEntity entity) { + for (IItemComponent component : components.values()) { + component.onWornTick(stack, entity); + } + } + + /** + * Check if any component blocks unequip. + */ + public boolean blocksUnequip(ItemStack stack, LivingEntity entity) { + for (IItemComponent component : components.values()) { + if (component.blocksUnequip(stack, entity)) { + return true; + } + } + return false; + } + + /** + * Whether this holder has any components. + */ + public boolean isEmpty() { + return components.isEmpty(); + } +} +``` + +- [ ] **Step 2: Verify compilation** + +Run: `cd /home/user/Documents/Projet/Open-TiedUp! && make build 2>&1 | tail -5` +Expected: BUILD SUCCESSFUL + +- [ ] **Step 3: Commit** + +```bash +git add src/main/java/com/tiedup/remake/v2/bondage/component/ComponentHolder.java +git commit -m "feat(D-01): add ComponentHolder container for item components" +``` + +--- + +### Task 4: Integrate components into DataDrivenItemDefinition + Parser + +**Files:** +- Modify: `src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenItemDefinition.java` +- Modify: `src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenItemParser.java` + +- [ ] **Step 1: Add componentConfigs field to DataDrivenItemDefinition** + +Read the current record definition, then add a new field. The record should get a new parameter: + +```java +/** Raw component configs from JSON, keyed by ComponentType. */ +Map componentConfigs +``` + +Add after the last existing field in the record. Also add a convenience method: + +```java +/** + * Whether this definition declares a specific component. + */ +public boolean hasComponent(ComponentType type) { + return componentConfigs != null && componentConfigs.containsKey(type); +} +``` + +- [ ] **Step 2: Parse "components" block in DataDrivenItemParser** + +Read `DataDrivenItemParser.java` and add parsing for the `"components"` JSON field. After parsing all existing fields, add: + +```java +// Parse components +Map componentConfigs = new EnumMap<>(ComponentType.class); +if (json.has("components")) { + JsonObject componentsObj = json.getAsJsonObject("components"); + for (Map.Entry entry : componentsObj.entrySet()) { + ComponentType type = ComponentType.fromKey(entry.getKey()); + if (type != null) { + JsonObject config = entry.getValue().isJsonObject() + ? entry.getValue().getAsJsonObject() + : new JsonObject(); + componentConfigs.put(type, config); + } else { + LOGGER.warn("[DataDrivenItemParser] Unknown component type '{}' in item '{}'", + entry.getKey(), id); + } + } +} +``` + +Pass `componentConfigs` to the `DataDrivenItemDefinition` record constructor. + +- [ ] **Step 3: Update all existing call sites that construct DataDrivenItemDefinition** + +Search for all `new DataDrivenItemDefinition(` calls and add `Map.of()` for the new parameter (for the network sync deserialization path, etc.). + +- [ ] **Step 4: Verify compilation** + +Run: `cd /home/user/Documents/Projet/Open-TiedUp! && make build 2>&1 | tail -5` +Expected: BUILD SUCCESSFUL + +- [ ] **Step 5: Commit** + +```bash +git add src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenItemDefinition.java +git add src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenItemParser.java +git commit -m "feat(D-01): parse component configs from item JSON definitions" +``` + +--- + +### Task 5: Instantiate ComponentHolder in DataDrivenItemRegistry + +**Files:** +- Modify: `src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenItemRegistry.java` + +- [ ] **Step 1: Add ComponentHolder cache** + +Read `DataDrivenItemRegistry.java`. Add a parallel cache that maps `ResourceLocation` to `ComponentHolder`. When definitions are loaded/reloaded, instantiate components from their `componentConfigs`. + +Add field: +```java +private static volatile Map COMPONENT_HOLDERS = Map.of(); +``` + +In the reload/register method, after storing definitions, build component holders: +```java +Map holders = new HashMap<>(); +for (Map.Entry entry : newDefinitions.entrySet()) { + DataDrivenItemDefinition def = entry.getValue(); + Map components = new EnumMap<>(ComponentType.class); + for (Map.Entry compEntry : def.componentConfigs().entrySet()) { + components.put(compEntry.getKey(), compEntry.getKey().create(compEntry.getValue())); + } + holders.put(entry.getKey(), new ComponentHolder(components)); +} +COMPONENT_HOLDERS = Collections.unmodifiableMap(holders); +``` + +Add accessor: +```java +@Nullable +public static ComponentHolder getComponents(ItemStack stack) { + DataDrivenItemDefinition def = get(stack); + if (def == null) return null; + return COMPONENT_HOLDERS.get(def.id()); +} + +@Nullable +public static ComponentHolder getComponents(ResourceLocation id) { + return COMPONENT_HOLDERS.get(id); +} +``` + +- [ ] **Step 2: Verify compilation** + +Run: `cd /home/user/Documents/Projet/Open-TiedUp! && make build 2>&1 | tail -5` +Expected: BUILD SUCCESSFUL + +- [ ] **Step 3: Commit** + +```bash +git add src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenItemRegistry.java +git commit -m "feat(D-01): instantiate ComponentHolder per item definition on reload" +``` + +--- + +### Task 6: Delegate DataDrivenBondageItem lifecycle to components + +**Files:** +- Modify: `src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenBondageItem.java` + +- [ ] **Step 1: Add component delegation in lifecycle hooks** + +Read `DataDrivenBondageItem.java`. In `onEquipped()` and `onUnequipped()`, delegate to components: + +```java +@Override +public void onEquipped(ItemStack stack, LivingEntity entity) { + ComponentHolder holder = DataDrivenItemRegistry.getComponents(stack); + if (holder != null) { + holder.onEquipped(stack, entity); + } +} + +@Override +public void onUnequipped(ItemStack stack, LivingEntity entity) { + ComponentHolder holder = DataDrivenItemRegistry.getComponents(stack); + if (holder != null) { + holder.onUnequipped(stack, entity); + } +} +``` + +Override `canUnequip` to check component blocks: +```java +@Override +public boolean canUnequip(ItemStack stack, LivingEntity entity) { + ComponentHolder holder = DataDrivenItemRegistry.getComponents(stack); + if (holder != null && holder.blocksUnequip(stack, entity)) { + return false; + } + return super.canUnequip(stack, entity); +} +``` + +Add a public static helper for external code to query components: +```java +/** + * Get a specific component from a data-driven item stack. + * @return The component, or null if the item is not data-driven or lacks this component. + */ +@Nullable +public static T getComponent(ItemStack stack, ComponentType type, Class clazz) { + ComponentHolder holder = DataDrivenItemRegistry.getComponents(stack); + if (holder == null) return null; + return holder.get(type, clazz); +} +``` + +- [ ] **Step 2: Verify compilation** + +Run: `cd /home/user/Documents/Projet/Open-TiedUp! && make build 2>&1 | tail -5` +Expected: BUILD SUCCESSFUL + +- [ ] **Step 3: Commit** + +```bash +git add src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenBondageItem.java +git commit -m "feat(D-01): delegate DataDrivenBondageItem lifecycle to components" +``` + +--- + +### Task 7: Implement LockableComponent + +**Files:** +- Modify: `src/main/java/com/tiedup/remake/v2/bondage/component/LockableComponent.java` + +- [ ] **Step 1: Implement full lockable logic** + +Replace the stub with the full implementation. Extract lock behavior from `ILockable` (which remains as a shared interface). The component reads its config from JSON and delegates to `ILockable` default methods on the item stack: + +```java +package com.tiedup.remake.v2.bondage.component; + +import com.google.gson.JsonObject; +import com.tiedup.remake.items.base.ILockable; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.item.ItemStack; + +/** + * Component: lockable behavior for data-driven items. + * Delegates to ILockable interface methods on the item. + * + * JSON config: + *
{"lockable": true}
+ * or + *
{"lockable": {"lock_resistance": 300}}
+ */ +public class LockableComponent implements IItemComponent { + + private final int lockResistance; + + private LockableComponent(int lockResistance) { + this.lockResistance = lockResistance; + } + + public static IItemComponent fromJson(JsonObject config) { + int resistance = 250; // default from SettingsAccessor + if (config.has("lock_resistance")) { + resistance = config.get("lock_resistance").getAsInt(); + } + return new LockableComponent(resistance); + } + + public int getLockResistance() { + return lockResistance; + } + + @Override + public boolean blocksUnequip(ItemStack stack, LivingEntity entity) { + // If item implements ILockable, check if locked + if (stack.getItem() instanceof ILockable lockable) { + return lockable.isLocked(stack); + } + return false; + } +} +``` + +- [ ] **Step 2: Verify compilation** + +Run: `cd /home/user/Documents/Projet/Open-TiedUp! && make build 2>&1 | tail -5` +Expected: BUILD SUCCESSFUL + +- [ ] **Step 3: Commit** + +```bash +git add src/main/java/com/tiedup/remake/v2/bondage/component/LockableComponent.java +git commit -m "feat(D-01): implement LockableComponent with configurable lock resistance" +``` + +--- + +### Task 8: Implement ResistanceComponent + +**Files:** +- Modify: `src/main/java/com/tiedup/remake/v2/bondage/component/ResistanceComponent.java` + +- [ ] **Step 1: Implement resistance logic** + +```java +package com.tiedup.remake.v2.bondage.component; + +import com.google.gson.JsonObject; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.item.ItemStack; + +/** + * Component: struggle resistance for data-driven items. + * Replaces IHasResistance for data-driven items. + * + * JSON config: + *
{"resistance": {"base": 150}}
+ */ +public class ResistanceComponent implements IItemComponent { + + private final int baseResistance; + + private ResistanceComponent(int baseResistance) { + this.baseResistance = baseResistance; + } + + public static IItemComponent fromJson(JsonObject config) { + int base = 100; // default + if (config.has("base")) { + base = config.get("base").getAsInt(); + } + return new ResistanceComponent(base); + } + + /** + * Get the base resistance for this item. + * Used by DataDrivenBondageItem.getBaseResistance() to replace the MAX-scan workaround. + */ + public int getBaseResistance() { + return baseResistance; + } +} +``` + +- [ ] **Step 2: Update DataDrivenBondageItem.getBaseResistance() to use ResistanceComponent** + +In `DataDrivenBondageItem.java`, update `getBaseResistance()`: + +```java +@Override +public int getBaseResistance(LivingEntity entity) { + // Try stack-aware component lookup first (fixes I-03: no more MAX scan) + // Note: This method is called WITHOUT a stack parameter by IHasResistance. + // We still need the MAX scan as fallback until IHasResistance gets a stack-aware method. + if (entity != null) { + IV2BondageEquipment equip = V2EquipmentHelper.getEquipment(entity); + if (equip != null) { + int maxDifficulty = -1; + for (Map.Entry entry : equip.getAllEquipped().entrySet()) { + ItemStack stack = entry.getValue(); + if (stack.getItem() == this) { + // Try component first + ResistanceComponent comp = DataDrivenBondageItem.getComponent( + stack, ComponentType.RESISTANCE, ResistanceComponent.class); + if (comp != null) { + maxDifficulty = Math.max(maxDifficulty, comp.getBaseResistance()); + continue; + } + // Fallback to escape_difficulty from definition + DataDrivenItemDefinition def = DataDrivenItemRegistry.get(stack); + if (def != null) { + maxDifficulty = Math.max(maxDifficulty, def.escapeDifficulty()); + } + } + } + if (maxDifficulty >= 0) return maxDifficulty; + } + } + return 100; +} +``` + +- [ ] **Step 3: Verify compilation** + +Run: `cd /home/user/Documents/Projet/Open-TiedUp! && make build 2>&1 | tail -5` +Expected: BUILD SUCCESSFUL + +- [ ] **Step 4: Commit** + +```bash +git add src/main/java/com/tiedup/remake/v2/bondage/component/ResistanceComponent.java +git add src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenBondageItem.java +git commit -m "feat(D-01): implement ResistanceComponent, fixes I-03 MAX scan for stack-aware items" +``` + +--- + +### Task 9: Implement GaggingComponent + +**Files:** +- Modify: `src/main/java/com/tiedup/remake/v2/bondage/component/GaggingComponent.java` + +- [ ] **Step 1: Implement gagging logic** + +```java +package com.tiedup.remake.v2.bondage.component; + +import com.google.gson.JsonObject; + +/** + * Component: gagging behavior for data-driven items. + * Replaces IHasGaggingEffect for data-driven items. + * + * JSON config: + *
{"gagging": {"comprehension": 0.2, "range": 10.0}}
+ */ +public class GaggingComponent implements IItemComponent { + + private final double comprehension; + private final double range; + + private GaggingComponent(double comprehension, double range) { + this.comprehension = comprehension; + this.range = range; + } + + public static IItemComponent fromJson(JsonObject config) { + double comprehension = 0.2; // default: 20% understandable + double range = 10.0; // default: 10 blocks + if (config.has("comprehension")) { + comprehension = config.get("comprehension").getAsDouble(); + } + if (config.has("range")) { + range = config.get("range").getAsDouble(); + } + return new GaggingComponent(comprehension, range); + } + + /** + * How much of the gagged speech is comprehensible (0.0 = nothing, 1.0 = full). + */ + public double getComprehension() { + return comprehension; + } + + /** + * Maximum range in blocks where muffled speech can be heard. + */ + public double getRange() { + return range; + } +} +``` + +- [ ] **Step 2: Verify compilation** + +Run: `cd /home/user/Documents/Projet/Open-TiedUp! && make build 2>&1 | tail -5` +Expected: BUILD SUCCESSFUL + +- [ ] **Step 3: Commit** + +```bash +git add src/main/java/com/tiedup/remake/v2/bondage/component/GaggingComponent.java +git commit -m "feat(D-01): implement GaggingComponent with comprehension and range" +``` + +--- + +### Task 10: Create a test item JSON using components + +**Files:** +- Create: `src/main/resources/data/tiedup/tiedup_items/test_gag.json` + +- [ ] **Step 1: Create a JSON definition that uses the new component system** + +```json +{ + "type": "tiedup:bondage_item", + "display_name": "Test Ball Gag", + "model": "tiedup:models/gltf/v2/handcuffs/cuffs_prototype.glb", + "regions": ["MOUTH"], + "animation_bones": { + "idle": [] + }, + "pose_priority": 10, + "escape_difficulty": 3, + "lockable": true, + "components": { + "lockable": { + "lock_resistance": 200 + }, + "resistance": { + "base": 80 + }, + "gagging": { + "comprehension": 0.15, + "range": 8.0 + } + } +} +``` + +- [ ] **Step 2: Verify the mod loads without errors** + +Run: `cd /home/user/Documents/Projet/Open-TiedUp! && make build 2>&1 | tail -5` +Expected: BUILD SUCCESSFUL + +Check that the JSON parses by searching for component-related log output in the run logs (manual verification — start the game client with `make run`, check for errors in log). + +- [ ] **Step 3: Commit** + +```bash +git add src/main/resources/data/tiedup/tiedup_items/test_gag.json +git commit -m "feat(D-01): add test_gag.json demonstrating component system" +``` + +--- + +### Task 11: Verify and clean up + +- [ ] **Step 1: Full build verification** + +Run: `cd /home/user/Documents/Projet/Open-TiedUp! && make rebuild 2>&1 | tail -10` +Expected: BUILD SUCCESSFUL with zero errors + +- [ ] **Step 2: Verify no regressions in existing items** + +Existing data-driven items (in `data/tiedup/tiedup_items/`) should continue working without the `"components"` field. The parser should handle missing components gracefully (empty map). + +- [ ] **Step 3: Reindex MCP** + +Run the MCP reindex to update the symbol table with new classes. + +- [ ] **Step 4: Final commit** + +```bash +git add -A +git commit -m "feat(D-01): Phase 1 complete - data-driven item component system + +Adds IItemComponent interface, ComponentType enum, ComponentHolder container, +and 3 core components (LockableComponent, ResistanceComponent, GaggingComponent). + +Components are declared in item JSON 'components' field, parsed by DataDrivenItemParser, +instantiated by DataDrivenItemRegistry, and delegated by DataDrivenBondageItem. + +Existing items without components continue to work unchanged." +``` diff --git a/src/main/java/com/tiedup/remake/blocks/entity/BondageItemBlockEntity.java b/src/main/java/com/tiedup/remake/blocks/entity/BondageItemBlockEntity.java index 8cea85b..279bb5c 100644 --- a/src/main/java/com/tiedup/remake/blocks/entity/BondageItemBlockEntity.java +++ b/src/main/java/com/tiedup/remake/blocks/entity/BondageItemBlockEntity.java @@ -6,6 +6,14 @@ 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 +199,45 @@ public abstract class BondageItemBlockEntity @Override public void readBondageData(CompoundTag tag) { - // Read bind with type validation + // Read bind with type validation (V1 ItemBind or 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() && (bindStack.getItem() instanceof ItemBind || BindModeHelper.isBindItem(bindStack))) { this.bind = bindStack; } } - // Read gag with type validation + // Read gag with type validation (V1 ItemGag or V2 GAGGING component) if (tag.contains("gag")) { ItemStack gagStack = ItemStack.of(tag.getCompound("gag")); - if (!gagStack.isEmpty() && gagStack.getItem() instanceof ItemGag) { + if (!gagStack.isEmpty() && (gagStack.getItem() instanceof ItemGag + || DataDrivenBondageItem.getComponent(gagStack, ComponentType.GAGGING, GaggingComponent.class) != null)) { this.gag = gagStack; } } - // Read blindfold with type validation + // Read blindfold with type validation (V1 ItemBlindfold or 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() && (blindfoldStack.getItem() instanceof ItemBlindfold + || isDataDrivenForRegion(blindfoldStack, BodyRegionV2.EYES))) { this.blindfold = blindfoldStack; } } - // Read earplugs with type validation + // Read earplugs with type validation (V1 ItemEarplugs or 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() && (earplugsStack.getItem() instanceof ItemEarplugs + || isDataDrivenForRegion(earplugsStack, BodyRegionV2.EARS))) { this.earplugs = earplugsStack; } } - // Read collar with type validation + // Read collar with type validation (V1 ItemCollar or V2 collar) if (tag.contains("collar")) { ItemStack collarStack = ItemStack.of(tag.getCompound("collar")); - if ( - !collarStack.isEmpty() && - collarStack.getItem() instanceof ItemCollar - ) { + if (!collarStack.isEmpty() && (collarStack.getItem() instanceof ItemCollar || CollarHelper.isCollar(collarStack))) { this.collar = collarStack; } } @@ -279,6 +277,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 /** diff --git a/src/main/java/com/tiedup/remake/blocks/entity/TrappedChestBlockEntity.java b/src/main/java/com/tiedup/remake/blocks/entity/TrappedChestBlockEntity.java index e4db192..73dd2da 100644 --- a/src/main/java/com/tiedup/remake/blocks/entity/TrappedChestBlockEntity.java +++ b/src/main/java/com/tiedup/remake/blocks/entity/TrappedChestBlockEntity.java @@ -1,7 +1,19 @@ package com.tiedup.remake.blocks.entity; -import com.tiedup.remake.items.base.*; +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; @@ -45,7 +57,7 @@ public class TrappedChestBlockEntity @Override public void setBind(ItemStack stack) { - if (stack.isEmpty() || stack.getItem() instanceof ItemBind) { + if (stack.isEmpty() || stack.getItem() instanceof ItemBind || BindModeHelper.isBindItem(stack)) { this.bind = stack; setChangedAndSync(); } @@ -58,7 +70,8 @@ public class TrappedChestBlockEntity @Override public void setGag(ItemStack stack) { - if (stack.isEmpty() || stack.getItem() instanceof ItemGag) { + if (stack.isEmpty() || stack.getItem() instanceof ItemGag + || DataDrivenBondageItem.getComponent(stack, ComponentType.GAGGING, GaggingComponent.class) != null) { this.gag = stack; setChangedAndSync(); } @@ -71,7 +84,8 @@ public class TrappedChestBlockEntity @Override public void setBlindfold(ItemStack stack) { - if (stack.isEmpty() || stack.getItem() instanceof ItemBlindfold) { + if (stack.isEmpty() || stack.getItem() instanceof ItemBlindfold + || isDataDrivenForRegion(stack, BodyRegionV2.EYES)) { this.blindfold = stack; setChangedAndSync(); } @@ -84,7 +98,8 @@ public class TrappedChestBlockEntity @Override public void setEarplugs(ItemStack stack) { - if (stack.isEmpty() || stack.getItem() instanceof ItemEarplugs) { + if (stack.isEmpty() || stack.getItem() instanceof ItemEarplugs + || isDataDrivenForRegion(stack, BodyRegionV2.EARS)) { this.earplugs = stack; setChangedAndSync(); } @@ -97,7 +112,7 @@ public class TrappedChestBlockEntity @Override public void setCollar(ItemStack stack) { - if (stack.isEmpty() || stack.getItem() instanceof ItemCollar) { + if (stack.isEmpty() || stack.getItem() instanceof ItemCollar || CollarHelper.isCollar(stack)) { this.collar = stack; setChangedAndSync(); } @@ -183,6 +198,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 /** diff --git a/src/main/java/com/tiedup/remake/cells/CampLifecycleManager.java b/src/main/java/com/tiedup/remake/cells/CampLifecycleManager.java index c1bc7e8..948d8ad 100644 --- a/src/main/java/com/tiedup/remake/cells/CampLifecycleManager.java +++ b/src/main/java/com/tiedup/remake/cells/CampLifecycleManager.java @@ -2,8 +2,8 @@ package com.tiedup.remake.cells; import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.items.base.ILockable; -import com.tiedup.remake.items.base.ItemCollar; import com.tiedup.remake.prison.PrisonerManager; +import com.tiedup.remake.v2.bondage.CollarHelper; import com.tiedup.remake.state.IRestrainable; import com.tiedup.remake.util.KidnappedHelper; import com.tiedup.remake.v2.BodyRegionV2; @@ -231,7 +231,7 @@ public final class CampLifecycleManager { } // Suppress collar removal alerts - this is a legitimate release (camp death) - ItemCollar.runWithSuppressedAlert(() -> { + CollarHelper.runWithSuppressedAlert(() -> { // Unlock collar if owned by the dead camp/trader unlockCollarIfOwnedBy(prisoner, state, traderUUID); @@ -285,8 +285,8 @@ public final class CampLifecycleManager { return; } - if (collar.getItem() instanceof ItemCollar collarItem) { - List owners = collarItem.getOwners(collar); + if (CollarHelper.isCollar(collar)) { + List owners = CollarHelper.getOwners(collar); // If the dead trader/camp is an owner, unlock the collar if (owners.contains(ownerUUID)) { diff --git a/src/main/java/com/tiedup/remake/client/FirstPersonMittensRenderer.java b/src/main/java/com/tiedup/remake/client/FirstPersonMittensRenderer.java index 5ab94da..3a7dd40 100644 --- a/src/main/java/com/tiedup/remake/client/FirstPersonMittensRenderer.java +++ b/src/main/java/com/tiedup/remake/client/FirstPersonMittensRenderer.java @@ -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; } } diff --git a/src/main/java/com/tiedup/remake/client/ModKeybindings.java b/src/main/java/com/tiedup/remake/client/ModKeybindings.java index 549c617..11e60d5 100644 --- a/src/main/java/com/tiedup/remake/client/ModKeybindings.java +++ b/src/main/java/com/tiedup/remake/client/ModKeybindings.java @@ -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; } diff --git a/src/main/java/com/tiedup/remake/client/animation/render/DogPoseRenderHandler.java b/src/main/java/com/tiedup/remake/client/animation/render/DogPoseRenderHandler.java index fa799f5..f61f877 100644 --- a/src/main/java/com/tiedup/remake/client/animation/render/DogPoseRenderHandler.java +++ b/src/main/java/com/tiedup/remake/client/animation/render/DogPoseRenderHandler.java @@ -1,8 +1,8 @@ package com.tiedup.remake.client.animation.render; import com.tiedup.remake.core.TiedUpMod; -import com.tiedup.remake.items.base.ItemBind; import com.tiedup.remake.items.base.PoseType; +import com.tiedup.remake.v2.bondage.PoseTypeHelper; import com.tiedup.remake.state.HumanChairHelper; import com.tiedup.remake.state.PlayerBindState; import com.tiedup.remake.v2.BodyRegionV2; @@ -93,14 +93,11 @@ public class DogPoseRenderHandler { } ItemStack bindForPose = state.getEquipment(BodyRegionV2.ARMS); - if ( - bindForPose.isEmpty() || - !(bindForPose.getItem() instanceof ItemBind itemBind) - ) { + if (bindForPose.isEmpty()) { return; } - PoseType bindPoseType = itemBind.getPoseType(); + PoseType bindPoseType = PoseTypeHelper.getPoseType(bindForPose); // Check for humanChairMode NBT override bindPoseType = HumanChairHelper.resolveEffectivePose( bindPoseType, diff --git a/src/main/java/com/tiedup/remake/client/animation/render/PetBedRenderHandler.java b/src/main/java/com/tiedup/remake/client/animation/render/PetBedRenderHandler.java index d057531..53e0bbd 100644 --- a/src/main/java/com/tiedup/remake/client/animation/render/PetBedRenderHandler.java +++ b/src/main/java/com/tiedup/remake/client/animation/render/PetBedRenderHandler.java @@ -2,8 +2,8 @@ package com.tiedup.remake.client.animation.render; import com.tiedup.remake.client.state.PetBedClientState; import com.tiedup.remake.core.TiedUpMod; -import com.tiedup.remake.items.base.ItemBind; import com.tiedup.remake.items.base.PoseType; +import com.tiedup.remake.v2.bondage.PoseTypeHelper; import com.tiedup.remake.state.HumanChairHelper; import com.tiedup.remake.state.PlayerBindState; import com.tiedup.remake.v2.BodyRegionV2; @@ -83,11 +83,9 @@ public class PetBedRenderHandler { PlayerBindState state = PlayerBindState.getInstance(player); if (state == null) return false; ItemStack bind = state.getEquipment(BodyRegionV2.ARMS); - if ( - bind.isEmpty() || !(bind.getItem() instanceof ItemBind itemBind) - ) return false; + if (bind.isEmpty()) return false; PoseType pose = HumanChairHelper.resolveEffectivePose( - itemBind.getPoseType(), + PoseTypeHelper.getPoseType(bind), bind ); return pose == PoseType.DOG || pose == PoseType.HUMAN_CHAIR; diff --git a/src/main/java/com/tiedup/remake/client/animation/render/PlayerArmHideEventHandler.java b/src/main/java/com/tiedup/remake/client/animation/render/PlayerArmHideEventHandler.java index e7773f6..3dd08a7 100644 --- a/src/main/java/com/tiedup/remake/client/animation/render/PlayerArmHideEventHandler.java +++ b/src/main/java/com/tiedup/remake/client/animation/render/PlayerArmHideEventHandler.java @@ -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 ( diff --git a/src/main/java/com/tiedup/remake/client/animation/tick/AnimationTickHandler.java b/src/main/java/com/tiedup/remake/client/animation/tick/AnimationTickHandler.java index 70bc8a2..4b31a49 100644 --- a/src/main/java/com/tiedup/remake/client/animation/tick/AnimationTickHandler.java +++ b/src/main/java/com/tiedup/remake/client/animation/tick/AnimationTickHandler.java @@ -14,8 +14,9 @@ import com.tiedup.remake.client.gltf.GltfAnimationApplier; import com.tiedup.remake.client.state.ClothesClientCache; import com.tiedup.remake.client.state.MovementStyleClientState; import com.tiedup.remake.client.state.PetBedClientState; -import com.tiedup.remake.items.base.ItemBind; import com.tiedup.remake.items.base.PoseType; +import com.tiedup.remake.v2.bondage.BindModeHelper; +import com.tiedup.remake.v2.bondage.PoseTypeHelper; import com.tiedup.remake.state.HumanChairHelper; import com.tiedup.remake.state.PlayerBindState; import com.tiedup.remake.v2.BodyRegionV2; @@ -249,14 +250,10 @@ public class AnimationTickHandler { PlayerBindState state ) { ItemStack bind = state.getEquipment(BodyRegionV2.ARMS); - PoseType poseType = PoseType.STANDARD; + PoseType poseType = PoseTypeHelper.getPoseType(bind); - if (bind.getItem() instanceof ItemBind itemBind) { - poseType = itemBind.getPoseType(); - - // Human chair mode: override DOG pose to HUMAN_CHAIR (straight limbs) - poseType = HumanChairHelper.resolveEffectivePose(poseType, bind); - } + // Human chair mode: override DOG pose to HUMAN_CHAIR (straight limbs) + poseType = HumanChairHelper.resolveEffectivePose(poseType, bind); // Derive bound state from V2 regions (works client-side, synced via capability) boolean armsBound = V2EquipmentHelper.isRegionOccupied( @@ -268,10 +265,10 @@ public class AnimationTickHandler { BodyRegionV2.LEGS ); - // V1 fallback: if no V2 regions are set but player is tied, derive from ItemBind NBT - if (!armsBound && !legsBound && bind.getItem() instanceof ItemBind) { - armsBound = ItemBind.hasArmsBound(bind); - legsBound = ItemBind.hasLegsBound(bind); + // V1 fallback: if no V2 regions are set but player is tied, derive from bind mode NBT + if (!armsBound && !legsBound && BindModeHelper.isBindItem(bind)) { + armsBound = BindModeHelper.hasArmsBound(bind); + legsBound = BindModeHelper.hasLegsBound(bind); } boolean isStruggling = state.isStruggling(); diff --git a/src/main/java/com/tiedup/remake/client/animation/tick/NpcAnimationTickHandler.java b/src/main/java/com/tiedup/remake/client/animation/tick/NpcAnimationTickHandler.java index ae0e805..828c994 100644 --- a/src/main/java/com/tiedup/remake/client/animation/tick/NpcAnimationTickHandler.java +++ b/src/main/java/com/tiedup/remake/client/animation/tick/NpcAnimationTickHandler.java @@ -9,8 +9,9 @@ import com.tiedup.remake.client.gltf.GltfAnimationApplier; import com.tiedup.remake.entities.AbstractTiedUpNpc; import com.tiedup.remake.entities.EntityMaster; import com.tiedup.remake.entities.ai.master.MasterState; -import com.tiedup.remake.items.base.ItemBind; import com.tiedup.remake.items.base.PoseType; +import com.tiedup.remake.v2.bondage.BindModeHelper; +import com.tiedup.remake.v2.bondage.PoseTypeHelper; import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.bondage.IV2BondageEquipment; import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper; @@ -161,13 +162,8 @@ public class NpcAnimationTickHandler { net.minecraft.world.item.ItemStack bind = entity.getEquipment( BodyRegionV2.ARMS ); - PoseType poseType = PoseType.STANDARD; - boolean hasBind = false; - - if (bind.getItem() instanceof ItemBind itemBind) { - poseType = itemBind.getPoseType(); - hasBind = true; - } + PoseType poseType = PoseTypeHelper.getPoseType(bind); + boolean hasBind = BindModeHelper.isBindItem(bind); // Derive bound state from V2 regions (AbstractTiedUpNpc implements IV2EquipmentHolder) boolean armsBound = V2EquipmentHelper.isRegionOccupied( @@ -179,10 +175,10 @@ public class NpcAnimationTickHandler { BodyRegionV2.LEGS ); - // V1 fallback: if no V2 regions set but NPC has a bind, derive from ItemBind NBT - if (!armsBound && !legsBound && bind.getItem() instanceof ItemBind) { - armsBound = ItemBind.hasArmsBound(bind); - legsBound = ItemBind.hasLegsBound(bind); + // V1 fallback: if no V2 regions set but NPC has a bind, derive from bind mode NBT + if (!armsBound && !legsBound && BindModeHelper.isBindItem(bind)) { + armsBound = BindModeHelper.hasArmsBound(bind); + legsBound = BindModeHelper.hasLegsBound(bind); } boolean isStruggling = entity.isStruggling(); diff --git a/src/main/java/com/tiedup/remake/client/events/LeashProxyClientHandler.java b/src/main/java/com/tiedup/remake/client/events/LeashProxyClientHandler.java index 8a3c237..fb1676b 100644 --- a/src/main/java/com/tiedup/remake/client/events/LeashProxyClientHandler.java +++ b/src/main/java/com/tiedup/remake/client/events/LeashProxyClientHandler.java @@ -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; } diff --git a/src/main/java/com/tiedup/remake/client/events/SelfBondageInputHandler.java b/src/main/java/com/tiedup/remake/client/events/SelfBondageInputHandler.java index a06ba1d..b876fbd 100644 --- a/src/main/java/com/tiedup/remake/client/events/SelfBondageInputHandler.java +++ b/src/main/java/com/tiedup/remake/client/events/SelfBondageInputHandler.java @@ -1,13 +1,20 @@ package com.tiedup.remake.client.events; -import com.tiedup.remake.items.base.*; +import com.tiedup.remake.items.base.ItemCollar; 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 +77,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 +94,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 +137,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 +160,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 (V1 collar guard + V2 ownership component) + if (stack.getItem() instanceof ItemCollar || 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; } } diff --git a/src/main/java/com/tiedup/remake/client/gui/screens/SlaveManagementScreen.java b/src/main/java/com/tiedup/remake/client/gui/screens/SlaveManagementScreen.java index e597979..52d895f 100644 --- a/src/main/java/com/tiedup/remake/client/gui/screens/SlaveManagementScreen.java +++ b/src/main/java/com/tiedup/remake/client/gui/screens/SlaveManagementScreen.java @@ -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()); } diff --git a/src/main/java/com/tiedup/remake/client/gui/widgets/ActionPanel.java b/src/main/java/com/tiedup/remake/client/gui/widgets/ActionPanel.java index 9bf0801..72fe69a 100644 --- a/src/main/java/com/tiedup/remake/client/gui/widgets/ActionPanel.java +++ b/src/main/java/com/tiedup/remake/client/gui/widgets/ActionPanel.java @@ -7,7 +7,7 @@ import com.tiedup.remake.items.ItemLockpick; import com.tiedup.remake.items.ModItems; import com.tiedup.remake.items.base.IHasResistance; import com.tiedup.remake.items.base.ILockable; -import com.tiedup.remake.items.base.ItemCollar; +import com.tiedup.remake.v2.bondage.CollarHelper; import com.tiedup.remake.network.ModNetwork; import com.tiedup.remake.network.action.PacketSetKnifeCutTarget; import com.tiedup.remake.network.minigame.PacketLockpickMiniGameStart; @@ -474,10 +474,10 @@ public class ActionPanel extends AbstractWidget { // Bondage Service toggle (NECK collar only, prison configured) if ( selectedRegion == BodyRegionV2.NECK && - selectedItem.getItem() instanceof ItemCollar collar + CollarHelper.isCollar(selectedItem) ) { - if (collar.hasCellAssigned(selectedItem)) { - boolean svcEnabled = collar.isBondageServiceEnabled( + if (CollarHelper.hasCellAssigned(selectedItem)) { + boolean svcEnabled = CollarHelper.isBondageServiceEnabled( selectedItem ); String svcKey = svcEnabled diff --git a/src/main/java/com/tiedup/remake/client/gui/widgets/SlaveEntryWidget.java b/src/main/java/com/tiedup/remake/client/gui/widgets/SlaveEntryWidget.java index 86c610c..00fdb2d 100644 --- a/src/main/java/com/tiedup/remake/client/gui/widgets/SlaveEntryWidget.java +++ b/src/main/java/com/tiedup/remake/client/gui/widgets/SlaveEntryWidget.java @@ -5,7 +5,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; @@ -560,19 +560,13 @@ 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( @@ -580,7 +574,7 @@ public class SlaveEntryWidget ItemStack collarStack, LivingEntity entity ) { - if (!gps.isActive(collarStack)) return true; + if (!CollarHelper.isActive(collarStack)) return true; var safeSpots = gps.getSafeSpots(collarStack); if (safeSpots.isEmpty()) return true; diff --git a/src/main/java/com/tiedup/remake/client/model/DamselModel.java b/src/main/java/com/tiedup/remake/client/model/DamselModel.java index 9668e95..80e1567 100644 --- a/src/main/java/com/tiedup/remake/client/model/DamselModel.java +++ b/src/main/java/com/tiedup/remake/client/model/DamselModel.java @@ -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 diff --git a/src/main/java/com/tiedup/remake/commands/CollarCommand.java b/src/main/java/com/tiedup/remake/commands/CollarCommand.java index fc26120..7384090 100644 --- a/src/main/java/com/tiedup/remake/commands/CollarCommand.java +++ b/src/main/java/com/tiedup/remake/commands/CollarCommand.java @@ -7,8 +7,8 @@ import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.tiedup.remake.cells.CellDataV2; import com.tiedup.remake.cells.CellRegistryV2; -import com.tiedup.remake.items.base.ItemCollar; import com.tiedup.remake.state.PlayerBindState; +import com.tiedup.remake.v2.bondage.CollarHelper; import com.tiedup.remake.util.teleport.Position; import com.tiedup.remake.util.teleport.TeleportHelper; import com.tiedup.remake.v2.BodyRegionV2; @@ -160,8 +160,8 @@ public class CollarCommand { } if (source.getEntity() instanceof ServerPlayer executor) { - if (collar.getItem() instanceof ItemCollar collarItem) { - collarItem.addOwner(collar, executor); + if (CollarHelper.isCollar(collar)) { + CollarHelper.addOwner(collar, executor); source.sendSuccess( () -> Component.literal( @@ -195,8 +195,8 @@ public class CollarCommand { } if (source.getEntity() instanceof ServerPlayer executor) { - if (collar.getItem() instanceof ItemCollar collarItem) { - collarItem.removeOwner(collar, executor.getUUID()); + if (CollarHelper.isCollar(collar)) { + CollarHelper.removeOwner(collar, executor.getUUID()); source.sendSuccess( () -> Component.literal( @@ -230,8 +230,8 @@ public class CollarCommand { return 0; } - if (collar.getItem() instanceof ItemCollar collarItem) { - collarItem.setNickname(collar, name); + if (CollarHelper.isCollar(collar)) { + CollarHelper.setNickname(collar, name); source.sendSuccess( () -> Component.literal( @@ -261,8 +261,8 @@ public class CollarCommand { return 0; } - if (collar.getItem() instanceof ItemCollar collarItem) { - collarItem.addOwner(collar, owner); + if (CollarHelper.isCollar(collar)) { + CollarHelper.addOwner(collar, owner); source.sendSuccess( () -> Component.literal( @@ -296,8 +296,8 @@ public class CollarCommand { return 0; } - if (collar.getItem() instanceof ItemCollar collarItem) { - collarItem.removeOwner(collar, owner.getUUID()); + if (CollarHelper.isCollar(collar)) { + CollarHelper.removeOwner(collar, owner.getUUID()); source.sendSuccess( () -> Component.literal( @@ -348,8 +348,8 @@ public class CollarCommand { return 0; } - if (collar.getItem() instanceof ItemCollar collarItem) { - collarItem.setCellId(collar, cell.getId()); + if (CollarHelper.isCollar(collar)) { + CollarHelper.setCellId(collar, cell.getId()); source.sendSuccess( () -> Component.literal( @@ -388,8 +388,8 @@ public class CollarCommand { return 0; } - if (collar.getItem() instanceof ItemCollar collarItem) { - if (!collarItem.hasCellAssigned(collar)) { + if (CollarHelper.isCollar(collar)) { + if (!CollarHelper.hasCellAssigned(collar)) { source.sendFailure( Component.literal("No cell assigned to collar") ); @@ -397,7 +397,7 @@ public class CollarCommand { } // Get cell position and teleport - java.util.UUID cellId = collarItem.getCellId(collar); + java.util.UUID cellId = CollarHelper.getCellId(collar); ServerLevel serverLevel = source.getLevel(); CellDataV2 cell = CellRegistryV2.get(serverLevel).getCell(cellId); @@ -449,7 +449,7 @@ public class CollarCommand { return 0; } - if (collar.getItem() instanceof ItemCollar collarItem) { + if (CollarHelper.isCollar(collar)) { source.sendSuccess( () -> Component.literal( @@ -460,24 +460,24 @@ public class CollarCommand { false ); - String nickname = collarItem.getNickname(collar); + String nickname = CollarHelper.getNickname(collar); source.sendSuccess( () -> Component.literal( "§7Nickname: §f" + - (nickname.isEmpty() ? "None" : nickname) + (nickname == null || nickname.isEmpty() ? "None" : nickname) ), false ); source.sendSuccess( () -> Component.literal( - "§7Has Owner: §f" + collarItem.hasOwner(collar) + "§7Has Owner: §f" + CollarHelper.hasOwner(collar) ), false ); // Cell assignment - java.util.UUID cellId = collarItem.getCellId(collar); + java.util.UUID cellId = CollarHelper.getCellId(collar); if (cellId != null) { ServerLevel serverLevel = source.getLevel(); CellRegistryV2 registry = CellRegistryV2.get(serverLevel); @@ -510,10 +510,12 @@ public class CollarCommand { ); } + boolean locked = collar.getItem() instanceof com.tiedup.remake.items.base.ILockable lockable + && lockable.isLocked(collar); source.sendSuccess( () -> Component.literal( - "§7Locked: §f" + collarItem.isLocked(collar) + "§7Locked: §f" + locked ), false ); diff --git a/src/main/java/com/tiedup/remake/commands/KidnapSetCommand.java b/src/main/java/com/tiedup/remake/commands/KidnapSetCommand.java index d555007..a269e13 100644 --- a/src/main/java/com/tiedup/remake/commands/KidnapSetCommand.java +++ b/src/main/java/com/tiedup/remake/commands/KidnapSetCommand.java @@ -4,12 +4,10 @@ import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.tiedup.remake.items.ModItems; -import com.tiedup.remake.items.base.BindVariant; -import com.tiedup.remake.items.base.BlindfoldVariant; -import com.tiedup.remake.items.base.EarplugsVariant; -import com.tiedup.remake.items.base.GagVariant; import com.tiedup.remake.items.base.KnifeVariant; +import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem; import java.util.Optional; +import net.minecraft.resources.ResourceLocation; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; import net.minecraft.network.chat.Component; @@ -82,42 +80,18 @@ 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( @@ -155,10 +129,7 @@ public class KidnapSetCommand { given += giveItem(player, new ItemStack(ModItems.MASTER_KEY.get(), 1)); // Earplugs - given += giveItem( - player, - new ItemStack(ModItems.getEarplugs(EarplugsVariant.CLASSIC), 4) - ); + given += giveDataDrivenItems(player, "classic_earplugs", 4); // Rope arrows given += giveItem(player, new ItemStack(ModItems.ROPE_ARROW.get(), 16)); @@ -182,6 +153,18 @@ public class KidnapSetCommand { return finalGiven; } + private static int giveDataDrivenItems(ServerPlayer player, String itemName, int count) { + int given = 0; + for (int i = 0; i < count; i++) { + ItemStack stack = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", itemName)); + if (!stack.isEmpty()) { + giveItem(player, stack); + given++; + } + } + return given; + } + private static int giveItem(ServerPlayer player, ItemStack stack) { if (!player.getInventory().add(stack)) { // Drop on ground if inventory full diff --git a/src/main/java/com/tiedup/remake/commands/NPCCommand.java b/src/main/java/com/tiedup/remake/commands/NPCCommand.java index 88d0251..d2960a1 100644 --- a/src/main/java/com/tiedup/remake/commands/NPCCommand.java +++ b/src/main/java/com/tiedup/remake/commands/NPCCommand.java @@ -7,11 +7,9 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.tiedup.remake.entities.*; import com.tiedup.remake.entities.skins.EliteKidnapperSkinManager; import com.tiedup.remake.items.ModItems; -import com.tiedup.remake.items.base.BindVariant; -import com.tiedup.remake.items.base.BlindfoldVariant; -import com.tiedup.remake.items.base.EarplugsVariant; -import com.tiedup.remake.items.base.GagVariant; import com.tiedup.remake.state.IBondageState; +import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem; +import net.minecraft.resources.ResourceLocation; import com.tiedup.remake.v2.BodyRegionV2; import java.util.List; import java.util.Optional; @@ -507,7 +505,7 @@ public class NPCCommand { npc.equip( BodyRegionV2.ARMS, - new ItemStack(ModItems.getBind(BindVariant.ROPES)) + DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "ropes")) ); context .getSource() @@ -538,7 +536,7 @@ public class NPCCommand { npc.equip( BodyRegionV2.MOUTH, - new ItemStack(ModItems.getGag(GagVariant.CLOTH_GAG)) + DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "cloth_gag")) ); context .getSource() @@ -571,7 +569,7 @@ public class NPCCommand { npc.equip( BodyRegionV2.EYES, - new ItemStack(ModItems.getBlindfold(BlindfoldVariant.CLASSIC)) + DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_blindfold")) ); context .getSource() @@ -656,10 +654,10 @@ 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)), + 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")), new ItemStack(ModItems.CLASSIC_COLLAR.get()), ItemStack.EMPTY // No clothes ); diff --git a/src/main/java/com/tiedup/remake/commands/subcommands/BondageSubCommand.java b/src/main/java/com/tiedup/remake/commands/subcommands/BondageSubCommand.java index 96f85a6..3e53e76 100644 --- a/src/main/java/com/tiedup/remake/commands/subcommands/BondageSubCommand.java +++ b/src/main/java/com/tiedup/remake/commands/subcommands/BondageSubCommand.java @@ -9,12 +9,10 @@ import com.tiedup.remake.commands.CommandHelper; import com.tiedup.remake.core.SystemMessageManager; import com.tiedup.remake.items.ModItems; import com.tiedup.remake.items.base.AdjustmentHelper; -import com.tiedup.remake.items.base.BindVariant; -import com.tiedup.remake.items.base.BlindfoldVariant; -import com.tiedup.remake.items.base.EarplugsVariant; -import com.tiedup.remake.items.base.GagVariant; -import com.tiedup.remake.items.base.ItemCollar; import com.tiedup.remake.network.ModNetwork; +import com.tiedup.remake.v2.bondage.CollarHelper; +import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem; +import net.minecraft.resources.ResourceLocation; import com.tiedup.remake.network.sync.PacketSyncBindState; import com.tiedup.remake.state.PlayerBindState; import net.minecraft.commands.CommandSourceStack; @@ -256,7 +254,7 @@ public class BondageSubCommand { return 0; } - ItemStack ropes = new ItemStack(ModItems.getBind(BindVariant.ROPES)); + ItemStack ropes = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "ropes")); state.putBindOn(ropes); CommandHelper.syncPlayerState(targetPlayer, state); @@ -387,7 +385,7 @@ public class BondageSubCommand { return 0; } - ItemStack gag = new ItemStack(ModItems.getGag(GagVariant.CLOTH_GAG)); + ItemStack gag = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "cloth_gag")); state.putGagOn(gag); CommandHelper.syncPlayerState(targetPlayer, state); @@ -440,9 +438,7 @@ public class BondageSubCommand { return 0; } - ItemStack blindfold = new ItemStack( - ModItems.getBlindfold(BlindfoldVariant.CLASSIC) - ); + ItemStack blindfold = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_blindfold")); state.putBlindfoldOn(blindfold); CommandHelper.syncPlayerState(targetPlayer, state); @@ -500,8 +496,7 @@ public class BondageSubCommand { ItemStack collar = new ItemStack(ModItems.CLASSIC_COLLAR.get()); if (context.getSource().getEntity() instanceof ServerPlayer executor) { - ItemCollar collarItem = (ItemCollar) collar.getItem(); - collarItem.addOwner(collar, executor); + CollarHelper.addOwner(collar, executor); } state.putCollarOn(collar); @@ -1021,9 +1016,7 @@ public class BondageSubCommand { return 0; } - ItemStack earplugs = new ItemStack( - ModItems.getEarplugs(EarplugsVariant.CLASSIC) - ); + ItemStack earplugs = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_earplugs")); state.putEarplugsOn(earplugs); CommandHelper.syncPlayerState(targetPlayer, state); @@ -1067,25 +1060,19 @@ public class BondageSubCommand { int applied = 0; if (!state.isTiedUp()) { - ItemStack ropes = new ItemStack( - ModItems.getBind(BindVariant.ROPES) - ); + ItemStack ropes = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "ropes")); state.putBindOn(ropes); applied++; } if (!state.isGagged()) { - ItemStack gag = new ItemStack( - ModItems.getGag(GagVariant.CLOTH_GAG) - ); + ItemStack gag = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "cloth_gag")); state.putGagOn(gag); applied++; } if (!state.isBlindfolded()) { - ItemStack blindfold = new ItemStack( - ModItems.getBlindfold(BlindfoldVariant.CLASSIC) - ); + ItemStack blindfold = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_blindfold")); state.putBlindfoldOn(blindfold); applied++; } @@ -1095,17 +1082,14 @@ public class BondageSubCommand { if ( context.getSource().getEntity() instanceof ServerPlayer executor ) { - ItemCollar collarItem = (ItemCollar) collar.getItem(); - collarItem.addOwner(collar, executor); + CollarHelper.addOwner(collar, executor); } state.putCollarOn(collar); applied++; } if (!state.hasEarplugs()) { - ItemStack earplugs = new ItemStack( - ModItems.getEarplugs(EarplugsVariant.CLASSIC) - ); + ItemStack earplugs = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_earplugs")); state.putEarplugsOn(earplugs); applied++; } @@ -1167,21 +1151,15 @@ public class BondageSubCommand { // First fully restrain if (!state.isTiedUp()) { - ItemStack ropes = new ItemStack( - ModItems.getBind(BindVariant.ROPES) - ); + ItemStack ropes = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "ropes")); state.putBindOn(ropes); } if (!state.isGagged()) { - ItemStack gag = new ItemStack( - ModItems.getGag(GagVariant.CLOTH_GAG) - ); + ItemStack gag = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "cloth_gag")); state.putGagOn(gag); } if (!state.isBlindfolded()) { - ItemStack blindfold = new ItemStack( - ModItems.getBlindfold(BlindfoldVariant.CLASSIC) - ); + ItemStack blindfold = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_blindfold")); state.putBlindfoldOn(blindfold); } if (!state.hasCollar()) { @@ -1189,15 +1167,12 @@ public class BondageSubCommand { if ( context.getSource().getEntity() instanceof ServerPlayer executor ) { - ItemCollar collarItem = (ItemCollar) collar.getItem(); - collarItem.addOwner(collar, executor); + CollarHelper.addOwner(collar, executor); } state.putCollarOn(collar); } if (!state.hasEarplugs()) { - ItemStack earplugs = new ItemStack( - ModItems.getEarplugs(EarplugsVariant.CLASSIC) - ); + ItemStack earplugs = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_earplugs")); state.putEarplugsOn(earplugs); } diff --git a/src/main/java/com/tiedup/remake/compat/mca/capability/MCAKidnappedAdapter.java b/src/main/java/com/tiedup/remake/compat/mca/capability/MCAKidnappedAdapter.java index cd2c349..2d10de1 100644 --- a/src/main/java/com/tiedup/remake/compat/mca/capability/MCAKidnappedAdapter.java +++ b/src/main/java/com/tiedup/remake/compat/mca/capability/MCAKidnappedAdapter.java @@ -2,8 +2,10 @@ 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; @@ -277,16 +279,17 @@ 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; + if (DataDrivenBondageItem.getComponent(gag, ComponentType.GAGGING, GaggingComponent.class) != null) return true; + return gag.getItem() instanceof com.tiedup.remake.items.base.IHasGaggingEffect; } @Override public boolean hasBlindingEffect() { ItemStack blindfold = cap.getBlindfold(); - return ( - !blindfold.isEmpty() && - blindfold.getItem() instanceof IHasBlindingEffect - ); + if (blindfold.isEmpty()) return false; + if (DataDrivenBondageItem.getComponent(blindfold, ComponentType.BLINDING, BlindingComponent.class) != null) return true; + return blindfold.getItem() instanceof com.tiedup.remake.items.base.IHasBlindingEffect; } @Override diff --git a/src/main/java/com/tiedup/remake/dialogue/GagTalkManager.java b/src/main/java/com/tiedup/remake/dialogue/GagTalkManager.java index b822fa5..585e687 100644 --- a/src/main/java/com/tiedup/remake/dialogue/GagTalkManager.java +++ b/src/main/java/com/tiedup/remake/dialogue/GagTalkManager.java @@ -5,6 +5,9 @@ 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,7 +61,12 @@ public class GagTalkManager { ) { LivingEntity entity = kidnapped.asLivingEntity(); GagMaterial material = GagMaterial.CLOTH; - if (gagStack.getItem() instanceof ItemGag gag) { + // V2: check data-driven GaggingComponent first + GaggingComponent gaggingComp = DataDrivenBondageItem.getComponent( + gagStack, ComponentType.GAGGING, GaggingComponent.class); + if (gaggingComp != null && gaggingComp.getMaterial() != null) { + material = gaggingComp.getMaterial(); + } else if (gagStack.getItem() instanceof ItemGag gag) { material = gag.getGagMaterial(); } @@ -514,8 +522,15 @@ 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(); + } else if (gagStack.getItem() instanceof ItemGag gag) { + material = gag.getGagMaterial(); + } } StringBuilder muffled = new StringBuilder(); diff --git a/src/main/java/com/tiedup/remake/dialogue/conversation/PetRequestManager.java b/src/main/java/com/tiedup/remake/dialogue/conversation/PetRequestManager.java index 85c0770..ba9dd8b 100644 --- a/src/main/java/com/tiedup/remake/dialogue/conversation/PetRequestManager.java +++ b/src/main/java/com/tiedup/remake/dialogue/conversation/PetRequestManager.java @@ -5,9 +5,9 @@ import com.tiedup.remake.dialogue.DialogueBridge; import com.tiedup.remake.entities.EntityMaster; import com.tiedup.remake.entities.ai.master.MasterPlaceBlockGoal; import com.tiedup.remake.entities.ai.master.MasterState; -import com.tiedup.remake.items.ModItems; -import com.tiedup.remake.items.base.BindVariant; import com.tiedup.remake.network.ModNetwork; +import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem; +import net.minecraft.resources.ResourceLocation; import com.tiedup.remake.network.master.PacketOpenPetRequestMenu; import com.tiedup.remake.state.PlayerBindState; import com.tiedup.remake.v2.BodyRegionV2; @@ -171,9 +171,7 @@ public class PetRequestManager { // Put dogbind on player (if not already tied) if (!state.isTiedUp()) { - ItemStack dogbind = new ItemStack( - ModItems.getBind(BindVariant.DOGBINDER) - ); + ItemStack dogbind = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "dogbinder")); state.equip(BodyRegionV2.ARMS, dogbind); TiedUpMod.LOGGER.debug( "[PetRequestManager] Equipped dogbind on {} for walk", @@ -228,7 +226,7 @@ public class PetRequestManager { } // Master equips armbinder on pet (classic pet play restraint) - ItemStack bind = new ItemStack(ModItems.getBind(BindVariant.ARMBINDER)); + ItemStack bind = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "armbinder")); state.equip(BodyRegionV2.ARMS, bind); DialogueBridge.talkTo(master, pet, "petplay.tie_accept"); diff --git a/src/main/java/com/tiedup/remake/entities/AbstractTiedUpNpc.java b/src/main/java/com/tiedup/remake/entities/AbstractTiedUpNpc.java index ded0b0d..85f3945 100644 --- a/src/main/java/com/tiedup/remake/entities/AbstractTiedUpNpc.java +++ b/src/main/java/com/tiedup/remake/entities/AbstractTiedUpNpc.java @@ -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,16 @@ 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 - ); + if (DataDrivenBondageItem.getComponent(gag, ComponentType.GAGGING, GaggingComponent.class) != null) return true; + return gag.getItem() instanceof com.tiedup.remake.items.base.IHasGaggingEffect; } @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 - ); + if (DataDrivenBondageItem.getComponent(blindfold, ComponentType.BLINDING, BlindingComponent.class) != null) return true; + return blindfold.getItem() instanceof com.tiedup.remake.items.base.IHasBlindingEffect; } @Override @@ -990,9 +981,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 diff --git a/src/main/java/com/tiedup/remake/entities/BondageServiceHandler.java b/src/main/java/com/tiedup/remake/entities/BondageServiceHandler.java index 689871b..f8c554d 100644 --- a/src/main/java/com/tiedup/remake/entities/BondageServiceHandler.java +++ b/src/main/java/com/tiedup/remake/entities/BondageServiceHandler.java @@ -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( diff --git a/src/main/java/com/tiedup/remake/entities/EntityDamsel.java b/src/main/java/com/tiedup/remake/entities/EntityDamsel.java index 790a3ac..7cda2a3 100644 --- a/src/main/java/com/tiedup/remake/entities/EntityDamsel.java +++ b/src/main/java/com/tiedup/remake/entities/EntityDamsel.java @@ -5,7 +5,7 @@ import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.entities.damsel.components.*; import com.tiedup.remake.entities.skins.DamselSkinManager; import com.tiedup.remake.entities.skins.Gender; -import com.tiedup.remake.items.base.ItemCollar; +import com.tiedup.remake.v2.bondage.CollarHelper; import com.tiedup.remake.state.ICaptor; import com.tiedup.remake.v2.BodyRegionV2; import java.util.UUID; @@ -527,8 +527,8 @@ public class EntityDamsel if (!this.hasCollar()) return false; ItemStack collar = this.getEquipment(BodyRegionV2.NECK); - if (!(collar.getItem() instanceof ItemCollar collarItem)) return false; - if (!collarItem.getOwners(collar).contains(commander.getUUID())) { + if (!CollarHelper.isCollar(collar)) return false; + if (!CollarHelper.isOwner(collar, commander.getUUID())) { if (!this.isGagged()) { com.tiedup.remake.dialogue.EntityDialogueManager.talkByDialogueId( this, @@ -653,8 +653,8 @@ public class EntityDamsel return net.minecraft.world.InteractionResult.FAIL; } ItemStack collar = this.getEquipment(BodyRegionV2.NECK); - if (collar.getItem() instanceof ItemCollar collarItem) { - if (!collarItem.isOwner(collar, player)) { + if (CollarHelper.isCollar(collar)) { + if (!CollarHelper.isOwner(collar, player)) { if ( player instanceof net.minecraft.server.level.ServerPlayer sp @@ -693,9 +693,9 @@ public class EntityDamsel this.hasCollar() ) { ItemStack collar = this.getEquipment(BodyRegionV2.NECK); - if (collar.getItem() instanceof ItemCollar collarItem) { + if (CollarHelper.isCollar(collar)) { if ( - collarItem.isOwner(collar, player) && + CollarHelper.isOwner(collar, player) && player instanceof net.minecraft.server.level.ServerPlayer serverPlayer ) { @@ -822,8 +822,8 @@ public class EntityDamsel public String getTargetRelation(Player player) { if (hasCollar()) { ItemStack collar = getEquipment(BodyRegionV2.NECK); - if (collar.getItem() instanceof ItemCollar collarItem) { - if (collarItem.isOwner(collar, player)) { + if (CollarHelper.isCollar(collar)) { + if (CollarHelper.isOwner(collar, player)) { return "master"; } } diff --git a/src/main/java/com/tiedup/remake/entities/EntityKidnapper.java b/src/main/java/com/tiedup/remake/entities/EntityKidnapper.java index 21d8129..30f0104 100644 --- a/src/main/java/com/tiedup/remake/entities/EntityKidnapper.java +++ b/src/main/java/com/tiedup/remake/entities/EntityKidnapper.java @@ -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) */ diff --git a/src/main/java/com/tiedup/remake/entities/EntityKidnapperMerchant.java b/src/main/java/com/tiedup/remake/entities/EntityKidnapperMerchant.java index edb0e86..b874d21 100644 --- a/src/main/java/com/tiedup/remake/entities/EntityKidnapperMerchant.java +++ b/src/main/java/com/tiedup/remake/entities/EntityKidnapperMerchant.java @@ -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,82 @@ public class EntityKidnapperMerchant extends EntityKidnapperElite { private List collectAllModItems() { List items = new ArrayList<>(); - // All binds (15) - // Items with colors get multiple variants (one per color) + // All binds — iterate V1 variants, create V2 stacks 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) + ItemStack stack = DataDrivenBondageItem.createStack( + new net.minecraft.resources.ResourceLocation("tiedup", variant.getRegistryName()) ); KidnapperItemSelector.applyColor(stack, color); items.add(stack); } } } else { - // No color variants - items.add(new ItemStack(ModItems.getBind(variant))); + items.add(DataDrivenBondageItem.createStack( + new net.minecraft.resources.ResourceLocation("tiedup", variant.getRegistryName()) + )); } } - // All gags (19) + // All gags 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) + ItemStack stack = DataDrivenBondageItem.createStack( + new net.minecraft.resources.ResourceLocation("tiedup", variant.getRegistryName()) ); KidnapperItemSelector.applyColor(stack, color); items.add(stack); } } } else { - items.add(new ItemStack(ModItems.getGag(variant))); + items.add(DataDrivenBondageItem.createStack( + new net.minecraft.resources.ResourceLocation("tiedup", variant.getRegistryName()) + )); } } - // All blindfolds (2) - BOTH support colors + // All blindfolds 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) + ItemStack stack = DataDrivenBondageItem.createStack( + new net.minecraft.resources.ResourceLocation("tiedup", variant.getRegistryName()) ); KidnapperItemSelector.applyColor(stack, color); items.add(stack); } } } else { - items.add(new ItemStack(ModItems.getBlindfold(variant))); + items.add(DataDrivenBondageItem.createStack( + new net.minecraft.resources.ResourceLocation("tiedup", variant.getRegistryName()) + )); } } // Earplugs - no color support for (EarplugsVariant variant : EarplugsVariant.values()) { - items.add(new ItemStack(ModItems.getEarplugs(variant))); + items.add(DataDrivenBondageItem.createStack( + new net.minecraft.resources.ResourceLocation("tiedup", variant.getRegistryName()) + )); } // Mittens - no color support for (MittensVariant variant : MittensVariant.values()) { - items.add(new ItemStack(ModItems.getMittens(variant))); + items.add(DataDrivenBondageItem.createStack( + new net.minecraft.resources.ResourceLocation("tiedup", variant.getRegistryName()) + )); } // Knives - no color support diff --git a/src/main/java/com/tiedup/remake/entities/EntityRopeArrow.java b/src/main/java/com/tiedup/remake/entities/EntityRopeArrow.java index 5640e32..d9410af 100644 --- a/src/main/java/com/tiedup/remake/entities/EntityRopeArrow.java +++ b/src/main/java/com/tiedup/remake/entities/EntityRopeArrow.java @@ -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); diff --git a/src/main/java/com/tiedup/remake/entities/KidnapperCaptureEquipment.java b/src/main/java/com/tiedup/remake/entities/KidnapperCaptureEquipment.java index 243b53d..dce8890 100644 --- a/src/main/java/com/tiedup/remake/entities/KidnapperCaptureEquipment.java +++ b/src/main/java/com/tiedup/remake/entities/KidnapperCaptureEquipment.java @@ -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")); } /** diff --git a/src/main/java/com/tiedup/remake/entities/KidnapperCollarConfig.java b/src/main/java/com/tiedup/remake/entities/KidnapperCollarConfig.java index 0dc88a4..06112d6 100644 --- a/src/main/java/com/tiedup/remake/entities/KidnapperCollarConfig.java +++ b/src/main/java/com/tiedup/remake/entities/KidnapperCollarConfig.java @@ -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 { * */ 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 whitelist = itemCollar.getWhitelist(collarStack); + List 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); } } diff --git a/src/main/java/com/tiedup/remake/entities/KidnapperItemSelector.java b/src/main/java/com/tiedup/remake/entities/KidnapperItemSelector.java index 90b459f..75a655f 100644 --- a/src/main/java/com/tiedup/remake/entities/KidnapperItemSelector.java +++ b/src/main/java/com/tiedup/remake/entities/KidnapperItemSelector.java @@ -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; @@ -242,7 +243,9 @@ public class KidnapperItemSelector { BindVariant variant, @Nullable ItemColor color ) { - ItemStack stack = new ItemStack(ModItems.getBind(variant)); + ItemStack stack = DataDrivenBondageItem.createStack( + new ResourceLocation("tiedup", variant.getRegistryName()) + ); if (color != null && variant.supportsColor()) { applyColor(stack, color); } @@ -257,7 +260,9 @@ public class KidnapperItemSelector { GagVariant variant, @Nullable ItemColor color ) { - ItemStack stack = new ItemStack(ModItems.getGag(variant)); + ItemStack stack = DataDrivenBondageItem.createStack( + new ResourceLocation("tiedup", variant.getRegistryName()) + ); if ( color != null && variant.supportsColor() && @@ -276,7 +281,9 @@ public class KidnapperItemSelector { BlindfoldVariant variant, @Nullable ItemColor color ) { - ItemStack stack = new ItemStack(ModItems.getBlindfold(variant)); + ItemStack stack = DataDrivenBondageItem.createStack( + new ResourceLocation("tiedup", variant.getRegistryName()) + ); if ( color != null && variant.supportsColor() && @@ -292,7 +299,9 @@ public class KidnapperItemSelector { * 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") + ); } /** @@ -300,7 +309,9 @@ public class KidnapperItemSelector { * 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 diff --git a/src/main/java/com/tiedup/remake/entities/KidnapperJobManager.java b/src/main/java/com/tiedup/remake/entities/KidnapperJobManager.java index 7d2499b..1914b32 100644 --- a/src/main/java/com/tiedup/remake/entities/KidnapperJobManager.java +++ b/src/main/java/com/tiedup/remake/entities/KidnapperJobManager.java @@ -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; @@ -162,15 +162,15 @@ 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); + // 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); diff --git a/src/main/java/com/tiedup/remake/entities/LeashProxyEntity.java b/src/main/java/com/tiedup/remake/entities/LeashProxyEntity.java index 9600210..84d53f9 100644 --- a/src/main/java/com/tiedup/remake/entities/LeashProxyEntity.java +++ b/src/main/java/com/tiedup/remake/entities/LeashProxyEntity.java @@ -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) } diff --git a/src/main/java/com/tiedup/remake/entities/ai/kidnapper/KidnapperBringToCellGoal.java b/src/main/java/com/tiedup/remake/entities/ai/kidnapper/KidnapperBringToCellGoal.java index 43b2550..741d4f9 100644 --- a/src/main/java/com/tiedup/remake/entities/ai/kidnapper/KidnapperBringToCellGoal.java +++ b/src/main/java/com/tiedup/remake/entities/ai/kidnapper/KidnapperBringToCellGoal.java @@ -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( diff --git a/src/main/java/com/tiedup/remake/entities/ai/kidnapper/KidnapperCaptureGoal.java b/src/main/java/com/tiedup/remake/entities/ai/kidnapper/KidnapperCaptureGoal.java index ff9437b..8725141 100644 --- a/src/main/java/com/tiedup/remake/entities/ai/kidnapper/KidnapperCaptureGoal.java +++ b/src/main/java/com/tiedup/remake/entities/ai/kidnapper/KidnapperCaptureGoal.java @@ -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 owners = collarItem.getOwners( + if (CollarHelper.isCollar(existingCollar)) { + java.util.List 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); + } } } diff --git a/src/main/java/com/tiedup/remake/entities/ai/kidnapper/KidnapperWalkPrisonerGoal.java b/src/main/java/com/tiedup/remake/entities/ai/kidnapper/KidnapperWalkPrisonerGoal.java index a0c5534..d035b58 100644 --- a/src/main/java/com/tiedup/remake/entities/ai/kidnapper/KidnapperWalkPrisonerGoal.java +++ b/src/main/java/com/tiedup/remake/entities/ai/kidnapper/KidnapperWalkPrisonerGoal.java @@ -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); } diff --git a/src/main/java/com/tiedup/remake/entities/ai/maid/goals/MaidReturnGoal.java b/src/main/java/com/tiedup/remake/entities/ai/maid/goals/MaidReturnGoal.java index 5d31ded..6128d5e 100644 --- a/src/main/java/com/tiedup/remake/entities/ai/maid/goals/MaidReturnGoal.java +++ b/src/main/java/com/tiedup/remake/entities/ai/maid/goals/MaidReturnGoal.java @@ -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") ) ); } diff --git a/src/main/java/com/tiedup/remake/entities/ai/master/MasterHumanChairGoal.java b/src/main/java/com/tiedup/remake/entities/ai/master/MasterHumanChairGoal.java index 37f0716..9fb0909 100644 --- a/src/main/java/com/tiedup/remake/entities/ai/master/MasterHumanChairGoal.java +++ b/src/main/java/com/tiedup/remake/entities/ai/master/MasterHumanChairGoal.java @@ -3,8 +3,8 @@ package com.tiedup.remake.entities.ai.master; import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.dialogue.DialogueBridge; import com.tiedup.remake.entities.EntityMaster; -import com.tiedup.remake.items.ModItems; -import com.tiedup.remake.items.base.BindVariant; +import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem; +import net.minecraft.resources.ResourceLocation; import com.tiedup.remake.state.HumanChairHelper; import com.tiedup.remake.state.PlayerBindState; import com.tiedup.remake.v2.BodyRegionV2; @@ -394,8 +394,8 @@ public class MasterHumanChairGoal extends Goal { // Apply invisible dogbind for the pose animation if (!bindState.isTiedUp()) { - ItemStack dogbind = new ItemStack( - ModItems.getBind(BindVariant.DOGBINDER) + ItemStack dogbind = DataDrivenBondageItem.createStack( + new ResourceLocation("tiedup", "dogbinder") ); CompoundTag tag = dogbind.getOrCreateTag(); tag.putBoolean(NBT_HUMAN_CHAIR_BIND, true); diff --git a/src/main/java/com/tiedup/remake/entities/ai/master/MasterPunishGoal.java b/src/main/java/com/tiedup/remake/entities/ai/master/MasterPunishGoal.java index 28c97db..d985c62 100644 --- a/src/main/java/com/tiedup/remake/entities/ai/master/MasterPunishGoal.java +++ b/src/main/java/com/tiedup/remake/entities/ai/master/MasterPunishGoal.java @@ -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; diff --git a/src/main/java/com/tiedup/remake/entities/ai/master/MasterRandomEventGoal.java b/src/main/java/com/tiedup/remake/entities/ai/master/MasterRandomEventGoal.java index bec353d..3ca03b7 100644 --- a/src/main/java/com/tiedup/remake/entities/ai/master/MasterRandomEventGoal.java +++ b/src/main/java/com/tiedup/remake/entities/ai/master/MasterRandomEventGoal.java @@ -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); } diff --git a/src/main/java/com/tiedup/remake/entities/ai/master/MasterTaskWatchGoal.java b/src/main/java/com/tiedup/remake/entities/ai/master/MasterTaskWatchGoal.java index 444d754..9931feb 100644 --- a/src/main/java/com/tiedup/remake/entities/ai/master/MasterTaskWatchGoal.java +++ b/src/main/java/com/tiedup/remake/entities/ai/master/MasterTaskWatchGoal.java @@ -3,7 +3,7 @@ package com.tiedup.remake.entities.ai.master; import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.dialogue.DialogueBridge; import com.tiedup.remake.entities.EntityMaster; -import com.tiedup.remake.items.ItemChokeCollar; +import com.tiedup.remake.v2.bondage.CollarHelper; import com.tiedup.remake.state.PlayerBindState; import com.tiedup.remake.util.MessageDispatcher; import com.tiedup.remake.v2.BodyRegionV2; @@ -441,8 +441,8 @@ public class MasterTaskWatchGoal extends Goal { if (bindState != null && bindState.hasCollar()) { ItemStack collar = bindState.getEquipment(BodyRegionV2.NECK); - if (collar.getItem() instanceof ItemChokeCollar chokeCollar) { - chokeCollar.setChoking(collar, true); + if (CollarHelper.isChokeCollar(collar)) { + CollarHelper.setChoking(collar, true); this.activeChokeCollar = collar; this.chokeTimer = CHOKE_DURATION; @@ -475,9 +475,9 @@ public class MasterTaskWatchGoal extends Goal { private void deactivateChoke() { if ( !activeChokeCollar.isEmpty() && - activeChokeCollar.getItem() instanceof ItemChokeCollar chokeCollar + CollarHelper.isChokeCollar(activeChokeCollar) ) { - chokeCollar.setChoking(activeChokeCollar, false); + CollarHelper.setChoking(activeChokeCollar, false); TiedUpMod.LOGGER.debug( "[MasterTaskWatchGoal] {} deactivated choke", master.getNpcName() diff --git a/src/main/java/com/tiedup/remake/entities/ai/personality/NpcFollowCommandGoal.java b/src/main/java/com/tiedup/remake/entities/ai/personality/NpcFollowCommandGoal.java index 33a29f3..a6557b4 100644 --- a/src/main/java/com/tiedup/remake/entities/ai/personality/NpcFollowCommandGoal.java +++ b/src/main/java/com/tiedup/remake/entities/ai/personality/NpcFollowCommandGoal.java @@ -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()); diff --git a/src/main/java/com/tiedup/remake/entities/ai/personality/NpcGuardCommandGoal.java b/src/main/java/com/tiedup/remake/entities/ai/personality/NpcGuardCommandGoal.java index 0f44bb5..1f1f632 100644 --- a/src/main/java/com/tiedup/remake/entities/ai/personality/NpcGuardCommandGoal.java +++ b/src/main/java/com/tiedup/remake/entities/ai/personality/NpcGuardCommandGoal.java @@ -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 inventory = npc.getNpcInventory(); for (int i = 0; i < inventory.size(); i++) { ItemStack stack = inventory.get(i); - if (stack.getItem() instanceof ItemBind) { + if (BindModeHelper.isBindItem(stack)) { // Apply bind to slave slave.equip(BodyRegionV2.ARMS, stack.copy()); stack.shrink(1); @@ -486,8 +486,8 @@ public class NpcGuardCommandGoal extends Goal { private UUID getCollarOwnerUUID(EntityDamsel slave) { if (!slave.hasCollar()) return null; ItemStack collar = slave.getEquipment(BodyRegionV2.NECK); - if (collar.getItem() instanceof ItemCollar collarItem) { - java.util.List owners = collarItem.getOwners(collar); + if (CollarHelper.isCollar(collar)) { + java.util.List owners = CollarHelper.getOwners(collar); if (!owners.isEmpty()) { return owners.get(0); } diff --git a/src/main/java/com/tiedup/remake/entities/damsel/components/DamselBondageManager.java b/src/main/java/com/tiedup/remake/entities/damsel/components/DamselBondageManager.java index 351c61e..073bc11 100644 --- a/src/main/java/com/tiedup/remake/entities/damsel/components/DamselBondageManager.java +++ b/src/main/java/com/tiedup/remake/entities/damsel/components/DamselBondageManager.java @@ -3,7 +3,7 @@ package com.tiedup.remake.entities.damsel.components; import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.entities.AbstractTiedUpNpc; import com.tiedup.remake.entities.BondageServiceHandler; -import com.tiedup.remake.items.base.ItemCollar; +import com.tiedup.remake.v2.bondage.CollarHelper; import com.tiedup.remake.state.ICaptor; import com.tiedup.remake.state.IRestrainable; import com.tiedup.remake.state.IRestrainableEntity; @@ -565,9 +565,9 @@ public class DamselBondageManager implements IRestrainable { ItemStack collar = getEquipment(BodyRegionV2.NECK); if (collar.isEmpty()) return false; - if (!(collar.getItem() instanceof ItemCollar itemCollar)) return false; + if (!CollarHelper.isCollar(collar)) return false; - UUID cellId = itemCollar.getCellId(collar); + UUID cellId = CollarHelper.getCellId(collar); if (cellId == null) return false; // Get cell position from registry diff --git a/src/main/java/com/tiedup/remake/entities/damsel/components/DamselPersonalitySystem.java b/src/main/java/com/tiedup/remake/entities/damsel/components/DamselPersonalitySystem.java index aeff9f9..52d1eee 100644 --- a/src/main/java/com/tiedup/remake/entities/damsel/components/DamselPersonalitySystem.java +++ b/src/main/java/com/tiedup/remake/entities/damsel/components/DamselPersonalitySystem.java @@ -2,7 +2,7 @@ package com.tiedup.remake.entities.damsel.components; import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.dialogue.EntityDialogueManager; -import com.tiedup.remake.items.base.ItemCollar; +import com.tiedup.remake.v2.bondage.CollarHelper; import com.tiedup.remake.personality.*; import com.tiedup.remake.v2.BodyRegionV2; import java.util.*; @@ -180,8 +180,8 @@ public class DamselPersonalitySystem { UUID masterUUID = null; if (context.hasCollar()) { ItemStack collar = context.getEquipment(BodyRegionV2.NECK); - if (collar.getItem() instanceof ItemCollar collarItem) { - List owners = collarItem.getOwners(collar); + if (CollarHelper.isCollar(collar)) { + List owners = CollarHelper.getOwners(collar); if (!owners.isEmpty()) { masterUUID = owners.get(0); } diff --git a/src/main/java/com/tiedup/remake/entities/damsel/components/NpcEquipmentManager.java b/src/main/java/com/tiedup/remake/entities/damsel/components/NpcEquipmentManager.java index e13007d..2d4d1c5 100644 --- a/src/main/java/com/tiedup/remake/entities/damsel/components/NpcEquipmentManager.java +++ b/src/main/java/com/tiedup/remake/entities/damsel/components/NpcEquipmentManager.java @@ -4,7 +4,11 @@ import com.tiedup.remake.core.ModConfig; import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.entities.AbstractTiedUpNpc; import com.tiedup.remake.items.base.ILockable; -import com.tiedup.remake.items.base.ItemCollar; +import com.tiedup.remake.v2.bondage.CollarHelper; +import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem; +import com.tiedup.remake.v2.bondage.component.ComponentType; +import com.tiedup.remake.v2.bondage.component.GaggingComponent; +import com.tiedup.remake.v2.bondage.component.BlindingComponent; import com.tiedup.remake.state.IRestrainableEntity; import com.tiedup.remake.util.RestraintEffectUtils; import com.tiedup.remake.util.TiedUpSounds; @@ -112,19 +116,15 @@ public class NpcEquipmentManager { public boolean hasGaggingEffect() { ItemStack gag = getCurrentGag(); if (gag.isEmpty()) return false; - return ( - gag.getItem() instanceof - com.tiedup.remake.items.base.IHasGaggingEffect - ); + if (DataDrivenBondageItem.getComponent(gag, ComponentType.GAGGING, GaggingComponent.class) != null) return true; + return gag.getItem() instanceof com.tiedup.remake.items.base.IHasGaggingEffect; } public boolean hasBlindingEffect() { ItemStack blindfold = getCurrentBlindfold(); if (blindfold.isEmpty()) return false; - return ( - blindfold.getItem() instanceof - com.tiedup.remake.items.base.IHasBlindingEffect - ); + if (DataDrivenBondageItem.getComponent(blindfold, ComponentType.BLINDING, BlindingComponent.class) != null) return true; + return blindfold.getItem() instanceof com.tiedup.remake.items.base.IHasBlindingEffect; } public boolean hasKnives() { @@ -768,8 +768,8 @@ public class NpcEquipmentManager { public boolean isCollarOwner(Player player) { if (!hasCollar()) return true; ItemStack collar = getCurrentCollar(); - if (!(collar.getItem() instanceof ItemCollar collarItem)) return true; - return collarItem.isOwner(collar, player); + if (!CollarHelper.isCollar(collar)) return true; + return CollarHelper.isOwner(collar, player); } // COERCION diff --git a/src/main/java/com/tiedup/remake/entities/kidnapper/components/KidnapperCaptiveManager.java b/src/main/java/com/tiedup/remake/entities/kidnapper/components/KidnapperCaptiveManager.java index 0143e9e..925e247 100644 --- a/src/main/java/com/tiedup/remake/entities/kidnapper/components/KidnapperCaptiveManager.java +++ b/src/main/java/com/tiedup/remake/entities/kidnapper/components/KidnapperCaptiveManager.java @@ -6,7 +6,7 @@ import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.dialogue.EntityDialogueManager; import com.tiedup.remake.entities.EntityKidnapper; import com.tiedup.remake.entities.ai.kidnapper.KidnapperState; -import com.tiedup.remake.items.base.ItemCollar; +import com.tiedup.remake.v2.bondage.CollarHelper; import com.tiedup.remake.prison.PrisonerManager; import com.tiedup.remake.prison.PrisonerRecord; import com.tiedup.remake.prison.PrisonerState; @@ -189,9 +189,9 @@ public class KidnapperCaptiveManager { // If target has collar, verify we own it if (target.hasCollar()) { ItemStack collar = target.getEquipment(BodyRegionV2.NECK); - if (collar.getItem() instanceof ItemCollar collarItem) { + if (CollarHelper.isCollar(collar)) { // Check if THIS kidnapper is owner - if (!collarItem.getOwners(collar).contains(host.getUUID())) { + if (!CollarHelper.getOwners(collar).contains(host.getUUID())) { TiedUpMod.LOGGER.debug( "[KidnapperCaptiveManager] {} can't capture {} - collar owned by someone else", host.getNpcName(), diff --git a/src/main/java/com/tiedup/remake/entities/kidnapper/components/KidnapperTargetSelector.java b/src/main/java/com/tiedup/remake/entities/kidnapper/components/KidnapperTargetSelector.java index 4f9f659..2d60782 100644 --- a/src/main/java/com/tiedup/remake/entities/kidnapper/components/KidnapperTargetSelector.java +++ b/src/main/java/com/tiedup/remake/entities/kidnapper/components/KidnapperTargetSelector.java @@ -7,7 +7,7 @@ import com.tiedup.remake.entities.EntityKidnapper; import com.tiedup.remake.entities.EntityKidnapperElite; import com.tiedup.remake.entities.ai.kidnapper.KidnapperState; import com.tiedup.remake.items.ModItems; -import com.tiedup.remake.items.base.ItemCollar; +import com.tiedup.remake.v2.bondage.CollarHelper; import com.tiedup.remake.prison.PrisonerManager; import com.tiedup.remake.prison.PrisonerRecord; import com.tiedup.remake.state.IBondageState; @@ -206,8 +206,8 @@ public class KidnapperTargetSelector { // Self-collared entities (exploit) are treated as uncolllared — kidnappers ignore self-collars if (state.hasCollar()) { ItemStack collar = state.getEquipment(BodyRegionV2.NECK); - if (collar.getItem() instanceof ItemCollar collarItem) { - java.util.List owners = collarItem.getOwners(collar); + if (CollarHelper.isCollar(collar)) { + java.util.List owners = CollarHelper.getOwners(collar); // Filter out self-collar (owner == wearer = exploit) java.util.List realOwners = owners .stream() @@ -312,8 +312,8 @@ public class KidnapperTargetSelector { // Other kidnappers' collars are fair game — kidnapper can steal from kidnapper if (state != null && state.hasCollar()) { ItemStack collar = state.getEquipment(BodyRegionV2.NECK); - if (collar.getItem() instanceof ItemCollar collarItem) { - java.util.List owners = collarItem.getOwners(collar); + if (CollarHelper.isCollar(collar)) { + java.util.List owners = CollarHelper.getOwners(collar); if (!owners.isEmpty() && !owners.contains(host.getUUID())) { // Check if any owner is a DIFFERENT player (not self-collared, not a kidnapper) if (host.level() instanceof ServerLevel sl) { diff --git a/src/main/java/com/tiedup/remake/entities/master/components/MasterPetManager.java b/src/main/java/com/tiedup/remake/entities/master/components/MasterPetManager.java index 61d4541..ddd5b01 100644 --- a/src/main/java/com/tiedup/remake/entities/master/components/MasterPetManager.java +++ b/src/main/java/com/tiedup/remake/entities/master/components/MasterPetManager.java @@ -4,7 +4,7 @@ import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.entities.EntityMaster; import com.tiedup.remake.entities.ai.master.MasterRandomEventGoal; import com.tiedup.remake.entities.ai.master.MasterState; -import com.tiedup.remake.items.base.ItemCollar; +import com.tiedup.remake.v2.bondage.CollarHelper; import com.tiedup.remake.minigame.StruggleSessionManager; import com.tiedup.remake.state.IBondageState; import com.tiedup.remake.util.KidnappedHelper; @@ -126,22 +126,27 @@ public class MasterPetManager { // Configure for pet play BEFORE equipping if ( chokeCollar.getItem() instanceof - com.tiedup.remake.items.ItemChokeCollar collar + com.tiedup.remake.items.base.IHasResistance resistable ) { - collar.setCurrentResistance(chokeCollar, PET_COLLAR_RESISTANCE); - collar.setLocked(chokeCollar, true); - collar.setLockable(chokeCollar, false); // Cannot be lockpicked - collar.setCanBeStruggledOut(chokeCollar, true); // Can struggle, but very hard - - // Add this Master as owner - collar.addOwner(chokeCollar, master.getUUID(), master.getNpcName()); - - // Set NBT flag for pet play mode - chokeCollar.getOrCreateTag().putBoolean("petPlayMode", true); - chokeCollar - .getOrCreateTag() - .putUUID("masterUUID", master.getUUID()); + resistable.setCurrentResistance(chokeCollar, PET_COLLAR_RESISTANCE); + resistable.setCanBeStruggledOut(chokeCollar, true); // Can struggle, but very hard } + if ( + chokeCollar.getItem() instanceof + com.tiedup.remake.items.base.ILockable lockable + ) { + lockable.setLocked(chokeCollar, true); + lockable.setLockable(chokeCollar, false); // Cannot be lockpicked + } + + // Add this Master as owner + CollarHelper.addOwner(chokeCollar, master.getUUID(), master.getNpcName()); + + // Set NBT flag for pet play mode + CollarHelper.setPetPlayMode(chokeCollar, true); + chokeCollar + .getOrCreateTag() + .putUUID("masterUUID", master.getUUID()); // Replace any existing collar (force removal) with the choke collar state.replaceEquipment(BodyRegionV2.NECK, chokeCollar, true); @@ -186,9 +191,9 @@ public class MasterPetManager { } // Unlock the collar - if (collarStack.getItem() instanceof ItemCollar collar) { - collar.setLocked(collarStack, false); - collar.setLockable(collarStack, true); + if (collarStack.getItem() instanceof com.tiedup.remake.items.base.ILockable lockable) { + lockable.setLocked(collarStack, false); + lockable.setLockable(collarStack, true); } } @@ -242,8 +247,8 @@ public class MasterPetManager { ); if (collarStack.isEmpty()) return; - if (collarStack.getItem() instanceof ItemCollar collar) { - collar.setCurrentResistance(collarStack, PET_COLLAR_RESISTANCE); + if (collarStack.getItem() instanceof com.tiedup.remake.items.base.IHasResistance resistable) { + resistable.setCurrentResistance(collarStack, PET_COLLAR_RESISTANCE); TiedUpMod.LOGGER.debug( "[MasterPetManager] Reset collar resistance for {}", pet.getName().getString() diff --git a/src/main/java/com/tiedup/remake/events/camp/CampNpcProtectionHandler.java b/src/main/java/com/tiedup/remake/events/camp/CampNpcProtectionHandler.java index adee72d..9162606 100644 --- a/src/main/java/com/tiedup/remake/events/camp/CampNpcProtectionHandler.java +++ b/src/main/java/com/tiedup/remake/events/camp/CampNpcProtectionHandler.java @@ -4,7 +4,7 @@ import com.tiedup.remake.cells.CampLifecycleManager; import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.entities.EntityMaid; import com.tiedup.remake.entities.EntitySlaveTrader; -import com.tiedup.remake.items.base.ItemBind; +import com.tiedup.remake.v2.bondage.BindModeHelper; import java.util.UUID; import net.minecraft.ChatFormatting; import net.minecraft.network.chat.Component; @@ -40,7 +40,7 @@ public class CampNpcProtectionHandler { // Check if player is holding restraint item ItemStack heldItem = player.getItemInHand(event.getHand()); - if (!(heldItem.getItem() instanceof ItemBind)) return; + if (!BindModeHelper.isBindItem(heldItem)) return; // Check if target is trader or maid with active camp UUID campId = null; diff --git a/src/main/java/com/tiedup/remake/events/captivity/PlayerEnslavementHandler.java b/src/main/java/com/tiedup/remake/events/captivity/PlayerEnslavementHandler.java index 4078620..db1547e 100644 --- a/src/main/java/com/tiedup/remake/events/captivity/PlayerEnslavementHandler.java +++ b/src/main/java/com/tiedup/remake/events/captivity/PlayerEnslavementHandler.java @@ -3,8 +3,8 @@ package com.tiedup.remake.events.captivity; import com.tiedup.remake.core.SettingsAccessor; import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.entities.LeashProxyEntity; -import com.tiedup.remake.items.base.ItemCollar; import com.tiedup.remake.state.IBondageState; +import com.tiedup.remake.v2.bondage.CollarHelper; import com.tiedup.remake.state.IPlayerLeashAccess; import com.tiedup.remake.state.PlayerBindState; import com.tiedup.remake.util.KidnappedHelper; @@ -146,9 +146,9 @@ public class PlayerEnslavementHandler { ItemStack collar = slaveKidnappedState.getEquipment( BodyRegionV2.NECK ); - if (collar.getItem() instanceof ItemCollar collarItem) { + if (CollarHelper.isCollar(collar)) { if ( - collarItem + CollarHelper .getOwners(collar) .contains(master.getUUID()) ) { diff --git a/src/main/java/com/tiedup/remake/events/combat/LaborAttackPunishmentHandler.java b/src/main/java/com/tiedup/remake/events/combat/LaborAttackPunishmentHandler.java index 901c57d..9363db1 100644 --- a/src/main/java/com/tiedup/remake/events/combat/LaborAttackPunishmentHandler.java +++ b/src/main/java/com/tiedup/remake/events/combat/LaborAttackPunishmentHandler.java @@ -4,8 +4,8 @@ import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.entities.EntityKidnapper; import com.tiedup.remake.entities.EntityMaid; import com.tiedup.remake.entities.EntitySlaveTrader; -import com.tiedup.remake.items.base.ItemBind; import com.tiedup.remake.prison.LaborRecord; +import com.tiedup.remake.v2.bondage.BindModeHelper; import com.tiedup.remake.prison.PrisonerManager; import com.tiedup.remake.prison.PrisonerRecord; import com.tiedup.remake.prison.PrisonerState; @@ -145,7 +145,7 @@ public class LaborAttackPunishmentHandler { // Check if player is holding a restraint item (rope, chain, etc.) ItemStack heldItem = serverPlayer.getItemInHand(hand); - if (!(heldItem.getItem() instanceof ItemBind)) { + if (!BindModeHelper.isBindItem(heldItem)) { return; // Not a restraint item } diff --git a/src/main/java/com/tiedup/remake/events/restriction/PetPlayRestrictionHandler.java b/src/main/java/com/tiedup/remake/events/restriction/PetPlayRestrictionHandler.java index 46dd127..6d38333 100644 --- a/src/main/java/com/tiedup/remake/events/restriction/PetPlayRestrictionHandler.java +++ b/src/main/java/com/tiedup/remake/events/restriction/PetPlayRestrictionHandler.java @@ -2,8 +2,8 @@ package com.tiedup.remake.events.restriction; import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.entities.EntityMaster; -import com.tiedup.remake.items.ItemChokeCollar; import com.tiedup.remake.state.PlayerBindState; +import com.tiedup.remake.v2.bondage.CollarHelper; import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.blocks.PetBedManager; import net.minecraft.core.BlockPos; @@ -284,8 +284,8 @@ public class PetPlayRestrictionHandler { if (bindState == null || !bindState.hasCollar()) return; ItemStack collar = bindState.getEquipment(BodyRegionV2.NECK); - if (collar.getItem() instanceof ItemChokeCollar chokeCollar) { - if (chokeCollar.isChoking(collar)) { + if (CollarHelper.isChokeCollar(collar)) { + if (CollarHelper.isChoking(collar)) { // Apply ChokeEffect (short duration, re-applied each active tick) if ( !player.hasEffect( diff --git a/src/main/java/com/tiedup/remake/events/restriction/RestraintTaskTickHandler.java b/src/main/java/com/tiedup/remake/events/restriction/RestraintTaskTickHandler.java index b65339b..fe38d36 100644 --- a/src/main/java/com/tiedup/remake/events/restriction/RestraintTaskTickHandler.java +++ b/src/main/java/com/tiedup/remake/events/restriction/RestraintTaskTickHandler.java @@ -3,8 +3,8 @@ package com.tiedup.remake.events.restriction; import com.tiedup.remake.core.SettingsAccessor; import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.entities.EntityKidnapper; -import com.tiedup.remake.items.base.ItemCollar; import com.tiedup.remake.minigame.StruggleSessionManager; +import com.tiedup.remake.v2.bondage.CollarHelper; import com.tiedup.remake.network.ModNetwork; import com.tiedup.remake.network.personality.PacketSlaveBeingFreed; import com.tiedup.remake.state.IBondageState; @@ -642,14 +642,11 @@ public class RestraintTaskTickHandler { if (!(slave.level() instanceof ServerLevel serverLevel)) return; ItemStack collar = slave.getEquipment(BodyRegionV2.NECK); - if ( - collar.isEmpty() || - !(collar.getItem() instanceof ItemCollar collarItem) - ) { + if (collar.isEmpty() || !CollarHelper.isCollar(collar)) { return; } - List owners = collarItem.getOwners(collar); + List owners = CollarHelper.getOwners(collar); if (owners.isEmpty()) return; // Create alert packet diff --git a/src/main/java/com/tiedup/remake/events/system/ChatEventHandler.java b/src/main/java/com/tiedup/remake/events/system/ChatEventHandler.java index 8420334..96ff8d1 100644 --- a/src/main/java/com/tiedup/remake/events/system/ChatEventHandler.java +++ b/src/main/java/com/tiedup/remake/events/system/ChatEventHandler.java @@ -7,6 +7,9 @@ import com.tiedup.remake.dialogue.GagTalkManager; import com.tiedup.remake.items.base.ItemGag; import com.tiedup.remake.state.IBondageState; import com.tiedup.remake.util.GagMaterial; +import com.tiedup.remake.v2.bondage.component.ComponentType; +import com.tiedup.remake.v2.bondage.component.GaggingComponent; +import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem; import com.tiedup.remake.util.KidnappedHelper; import com.tiedup.remake.util.TiedUpUtils; import com.tiedup.remake.v2.BodyRegionV2; @@ -54,12 +57,22 @@ public class ChatEventHandler { BodyRegionV2.MOUTH ); - if ( - !gagStack.isEmpty() && - gagStack.getItem() instanceof ItemGag gagItem - ) { + // V2: check gagging component, V1 fallback: instanceof ItemGag + GaggingComponent gaggingComp = DataDrivenBondageItem.getComponent( + gagStack, ComponentType.GAGGING, GaggingComponent.class); + boolean isGagItem = gaggingComp != null + || gagStack.getItem() instanceof ItemGag; + + if (!gagStack.isEmpty() && isGagItem) { String originalMessage = event.getRawText(); - GagMaterial material = gagItem.getGagMaterial(); + // V2: get material from component, V1 fallback: from ItemGag + GagMaterial material = null; + if (gaggingComp != null) { + material = gaggingComp.getMaterial(); + } + if (material == null && gagStack.getItem() instanceof ItemGag gagItem) { + material = gagItem.getGagMaterial(); + } // 1. Process the message through our GagTalkManager V2 Component muffledMessage = GagTalkManager.processGagMessage( @@ -83,7 +96,9 @@ public class ChatEventHandler { .append("> ") .append(muffledMessage); - double range = material.getTalkRange(); + double range = material != null + ? material.getTalkRange() + : (gaggingComp != null ? gaggingComp.getRange() : 10.0); List nearbyPlayers = TiedUpUtils.getPlayersAround( diff --git a/src/main/java/com/tiedup/remake/minigame/StruggleSessionManager.java b/src/main/java/com/tiedup/remake/minigame/StruggleSessionManager.java index c628c41..eaaae08 100644 --- a/src/main/java/com/tiedup/remake/minigame/StruggleSessionManager.java +++ b/src/main/java/com/tiedup/remake/minigame/StruggleSessionManager.java @@ -1,9 +1,8 @@ package com.tiedup.remake.minigame; import com.tiedup.remake.core.TiedUpMod; -import com.tiedup.remake.items.ItemShockCollar; -import com.tiedup.remake.items.base.ItemBind; -import com.tiedup.remake.items.base.ItemCollar; +import com.tiedup.remake.items.base.IHasResistance; +import com.tiedup.remake.v2.bondage.CollarHelper; import com.tiedup.remake.minigame.ContinuousStruggleMiniGameState.TickResult; import com.tiedup.remake.minigame.ContinuousStruggleMiniGameState.UpdateType; import com.tiedup.remake.network.ModNetwork; @@ -544,11 +543,11 @@ public class StruggleSessionManager { if (collar.isEmpty()) return false; // Only shock collars can trigger during struggle - if (!(collar.getItem() instanceof ItemShockCollar)) return false; + if (!CollarHelper.canShock(collar)) return false; // Must be locked - if (collar.getItem() instanceof ItemCollar collarItem) { - return collarItem.isLocked(collar); + if (collar.getItem() instanceof com.tiedup.remake.items.base.ILockable lockable) { + return lockable.isLocked(collar); } return false; } @@ -643,14 +642,12 @@ public class StruggleSessionManager { player, BodyRegionV2.ARMS ); - if ( - bindStack.isEmpty() || - !(bindStack.getItem() instanceof ItemBind bind) - ) { + if (bindStack.isEmpty()) { return; } - - bind.setCurrentResistance(bindStack, session.getCurrentResistance()); + if (bindStack.getItem() instanceof IHasResistance resistanceItem) { + resistanceItem.setCurrentResistance(bindStack, session.getCurrentResistance()); + } } /** diff --git a/src/main/java/com/tiedup/remake/mixin/MixinMCAVillagerInteraction.java b/src/main/java/com/tiedup/remake/mixin/MixinMCAVillagerInteraction.java index b25ef92..5552eab 100644 --- a/src/main/java/com/tiedup/remake/mixin/MixinMCAVillagerInteraction.java +++ b/src/main/java/com/tiedup/remake/mixin/MixinMCAVillagerInteraction.java @@ -4,7 +4,7 @@ import com.tiedup.remake.compat.mca.MCACompat; import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.items.ItemKey; import com.tiedup.remake.items.ItemMasterKey; -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 com.tiedup.remake.v2.bondage.IV2BondageItem; @@ -182,8 +182,8 @@ public abstract class MixinMCAVillagerInteraction { // Can leash if player is a collar owner if (state.hasCollar()) { ItemStack collar = state.getEquipment(BodyRegionV2.NECK); - if (collar.getItem() instanceof ItemCollar collarItem) { - return collarItem.getOwners(collar).contains(player.getUUID()); + if (CollarHelper.isCollar(collar)) { + return CollarHelper.isOwner(collar, player); } } diff --git a/src/main/java/com/tiedup/remake/mixin/MixinMCAVillagerLeash.java b/src/main/java/com/tiedup/remake/mixin/MixinMCAVillagerLeash.java index a6d11d6..c59c150 100644 --- a/src/main/java/com/tiedup/remake/mixin/MixinMCAVillagerLeash.java +++ b/src/main/java/com/tiedup/remake/mixin/MixinMCAVillagerLeash.java @@ -1,7 +1,7 @@ package com.tiedup.remake.mixin; import com.tiedup.remake.compat.mca.MCACompat; -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 net.minecraft.world.entity.LivingEntity; @@ -71,8 +71,8 @@ public class MixinMCAVillagerLeash { // Can be leashed if player is collar owner if (state.hasCollar()) { ItemStack collar = state.getEquipment(BodyRegionV2.NECK); - if (collar.getItem() instanceof ItemCollar collarItem) { - if (collarItem.getOwners(collar).contains(player.getUUID())) { + if (CollarHelper.isCollar(collar)) { + if (CollarHelper.isOwner(collar, player)) { cir.setReturnValue(true); return; } diff --git a/src/main/java/com/tiedup/remake/mixin/client/MixinCamera.java b/src/main/java/com/tiedup/remake/mixin/client/MixinCamera.java index c7a94a9..814cf5c 100644 --- a/src/main/java/com/tiedup/remake/mixin/client/MixinCamera.java +++ b/src/main/java/com/tiedup/remake/mixin/client/MixinCamera.java @@ -1,7 +1,7 @@ package com.tiedup.remake.mixin.client; -import com.tiedup.remake.items.base.ItemBind; import com.tiedup.remake.items.base.PoseType; +import com.tiedup.remake.v2.bondage.PoseTypeHelper; import com.tiedup.remake.state.HumanChairHelper; import com.tiedup.remake.state.PlayerBindState; import com.tiedup.remake.v2.BodyRegionV2; @@ -57,11 +57,11 @@ public abstract class MixinCamera { } ItemStack bind = state.getEquipment(BodyRegionV2.ARMS); - if (bind.isEmpty() || !(bind.getItem() instanceof ItemBind itemBind)) { + if (bind.isEmpty()) { return; } - if (itemBind.getPoseType() != PoseType.DOG) { + if (PoseTypeHelper.getPoseType(bind) != PoseType.DOG) { return; } diff --git a/src/main/java/com/tiedup/remake/mixin/client/MixinPlayerModel.java b/src/main/java/com/tiedup/remake/mixin/client/MixinPlayerModel.java index 9e3153a..0a37cbc 100644 --- a/src/main/java/com/tiedup/remake/mixin/client/MixinPlayerModel.java +++ b/src/main/java/com/tiedup/remake/mixin/client/MixinPlayerModel.java @@ -2,8 +2,8 @@ package com.tiedup.remake.mixin.client; import com.tiedup.remake.client.animation.render.DogPoseRenderHandler; import com.tiedup.remake.client.animation.util.DogPoseHelper; -import com.tiedup.remake.items.base.ItemBind; import com.tiedup.remake.items.base.PoseType; +import com.tiedup.remake.v2.bondage.PoseTypeHelper; import com.tiedup.remake.state.PlayerBindState; import com.tiedup.remake.v2.BodyRegionV2; import net.minecraft.client.model.PlayerModel; @@ -45,11 +45,11 @@ public class MixinPlayerModel { } ItemStack bind = state.getEquipment(BodyRegionV2.ARMS); - if (bind.isEmpty() || !(bind.getItem() instanceof ItemBind itemBind)) { + if (bind.isEmpty()) { return; } - if (itemBind.getPoseType() != PoseType.DOG) { + if (PoseTypeHelper.getPoseType(bind) != PoseType.DOG) { return; } diff --git a/src/main/java/com/tiedup/remake/mixin/client/MixinVillagerEntityBaseModelMCA.java b/src/main/java/com/tiedup/remake/mixin/client/MixinVillagerEntityBaseModelMCA.java index 044abe1..b86c14a 100644 --- a/src/main/java/com/tiedup/remake/mixin/client/MixinVillagerEntityBaseModelMCA.java +++ b/src/main/java/com/tiedup/remake/mixin/client/MixinVillagerEntityBaseModelMCA.java @@ -4,8 +4,9 @@ import com.tiedup.remake.client.animation.BondageAnimationManager; import com.tiedup.remake.client.animation.StaticPoseApplier; import com.tiedup.remake.client.animation.util.AnimationIdBuilder; import com.tiedup.remake.compat.mca.MCACompat; -import com.tiedup.remake.items.base.ItemBind; import com.tiedup.remake.items.base.PoseType; +import com.tiedup.remake.v2.bondage.BindModeHelper; +import com.tiedup.remake.v2.bondage.PoseTypeHelper; import com.tiedup.remake.state.IBondageState; import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper; @@ -78,11 +79,7 @@ public class MixinVillagerEntityBaseModelMCA { // Get pose info from bind item ItemStack bind = state.getEquipment(BodyRegionV2.ARMS); - PoseType poseType = PoseType.STANDARD; - - if (bind.getItem() instanceof ItemBind itemBind) { - poseType = itemBind.getPoseType(); - } + PoseType poseType = PoseTypeHelper.getPoseType(bind); // Derive bound state from V2 regions, fallback to V1 bind mode NBT boolean armsBound = V2EquipmentHelper.isRegionOccupied( @@ -94,9 +91,9 @@ public class MixinVillagerEntityBaseModelMCA { BodyRegionV2.LEGS ); - if (!armsBound && !legsBound && bind.getItem() instanceof ItemBind) { - armsBound = ItemBind.hasArmsBound(bind); - legsBound = ItemBind.hasLegsBound(bind); + if (!armsBound && !legsBound && BindModeHelper.isBindItem(bind)) { + armsBound = BindModeHelper.hasArmsBound(bind); + legsBound = BindModeHelper.hasLegsBound(bind); } // MCA doesn't track struggling state - use false for now diff --git a/src/main/java/com/tiedup/remake/network/cell/PacketAssignCellToCollar.java b/src/main/java/com/tiedup/remake/network/cell/PacketAssignCellToCollar.java index 1094b8a..5291f01 100644 --- a/src/main/java/com/tiedup/remake/network/cell/PacketAssignCellToCollar.java +++ b/src/main/java/com/tiedup/remake/network/cell/PacketAssignCellToCollar.java @@ -5,8 +5,8 @@ import com.tiedup.remake.cells.CellRegistryV2; import com.tiedup.remake.core.SystemMessageManager; import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.entities.EntityDamsel; -import com.tiedup.remake.items.base.ItemCollar; import com.tiedup.remake.network.PacketRateLimiter; +import com.tiedup.remake.v2.bondage.CollarHelper; import com.tiedup.remake.network.sync.SyncManager; import com.tiedup.remake.personality.PersonalityState; import com.tiedup.remake.state.IBondageState; @@ -14,9 +14,9 @@ import com.tiedup.remake.util.KidnappedHelper; import com.tiedup.remake.v2.BodyRegionV2; import java.util.UUID; import java.util.function.Supplier; +import net.minecraft.nbt.CompoundTag; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.server.level.ServerPlayer; -import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; @@ -96,7 +96,7 @@ public class PacketAssignCellToCollar { } ItemStack collarStack = state.getEquipment(BodyRegionV2.NECK); - if (!(collarStack.getItem() instanceof ItemCollar collar)) { + if (!CollarHelper.isCollar(collarStack)) { TiedUpMod.LOGGER.debug( "[PacketAssignCellToCollar] Invalid collar item" ); @@ -105,7 +105,7 @@ public class PacketAssignCellToCollar { // Security: Verify sender owns the collar (or is admin) if ( - !collar.isOwner(collarStack, sender) && + !CollarHelper.isOwner(collarStack, sender) && !sender.hasPermissions(2) ) { TiedUpMod.LOGGER.debug( @@ -139,7 +139,15 @@ public class PacketAssignCellToCollar { } // Set the cell ID on the collar - collar.setCellId(collarStack, msg.cellId); + if (msg.cellId != null) { + CollarHelper.setCellId(collarStack, msg.cellId); + } else { + // Clear cell assignment + CompoundTag collarTag = collarStack.getTag(); + if (collarTag != null) { + collarTag.remove("cellId"); + } + } // Sync PersonalityState for damsels if (target instanceof EntityDamsel damsel) { diff --git a/src/main/java/com/tiedup/remake/network/personality/PacketNpcCommand.java b/src/main/java/com/tiedup/remake/network/personality/PacketNpcCommand.java index 72adc86..9fe2ef9 100644 --- a/src/main/java/com/tiedup/remake/network/personality/PacketNpcCommand.java +++ b/src/main/java/com/tiedup/remake/network/personality/PacketNpcCommand.java @@ -7,8 +7,8 @@ import com.tiedup.remake.dialogue.EntityDialogueManager.DialogueCategory; import com.tiedup.remake.entities.EntityDamsel; import com.tiedup.remake.items.ItemCommandWand; import com.tiedup.remake.items.ModItems; -import com.tiedup.remake.items.base.ItemCollar; import com.tiedup.remake.network.ModNetwork; +import com.tiedup.remake.v2.bondage.CollarHelper; import com.tiedup.remake.network.PacketRateLimiter; import com.tiedup.remake.personality.JobExperience; import com.tiedup.remake.personality.NpcCommand; @@ -390,11 +390,11 @@ public class PacketNpcCommand { } ItemStack collar = damsel.getEquipment(BodyRegionV2.NECK); - if (!(collar.getItem() instanceof ItemCollar collarItem)) { + if (!CollarHelper.isCollar(collar)) { return false; } - if (!collarItem.getOwners(collar).contains(sender.getUUID())) { + if (!CollarHelper.isOwner(collar, sender)) { SystemMessageManager.sendToPlayer( sender, SystemMessageManager.MessageCategory.ERROR, diff --git a/src/main/java/com/tiedup/remake/network/slave/PacketMasterEquip.java b/src/main/java/com/tiedup/remake/network/slave/PacketMasterEquip.java index daccf5f..5b656ce 100644 --- a/src/main/java/com/tiedup/remake/network/slave/PacketMasterEquip.java +++ b/src/main/java/com/tiedup/remake/network/slave/PacketMasterEquip.java @@ -1,7 +1,7 @@ package com.tiedup.remake.network.slave; -import com.tiedup.remake.items.base.ItemCollar; import com.tiedup.remake.network.PacketRateLimiter; +import com.tiedup.remake.v2.bondage.CollarHelper; import com.tiedup.remake.state.IBondageState; import com.tiedup.remake.util.KidnappedHelper; import com.tiedup.remake.v2.BodyRegionV2; @@ -75,9 +75,9 @@ public class PacketMasterEquip { ); if (targetState == null || !targetState.hasCollar()) return; ItemStack collarStack = targetState.getEquipment(BodyRegionV2.NECK); - if (collarStack.getItem() instanceof ItemCollar collar) { + if (CollarHelper.isCollar(collarStack)) { if ( - !collar.isOwner(collarStack, sender) && + !CollarHelper.isOwner(collarStack, sender) && !sender.hasPermissions(2) ) return; } diff --git a/src/main/java/com/tiedup/remake/network/slave/PacketSlaveAction.java b/src/main/java/com/tiedup/remake/network/slave/PacketSlaveAction.java index 25bfbc8..652beba 100644 --- a/src/main/java/com/tiedup/remake/network/slave/PacketSlaveAction.java +++ b/src/main/java/com/tiedup/remake/network/slave/PacketSlaveAction.java @@ -2,10 +2,8 @@ package com.tiedup.remake.network.slave; import com.tiedup.remake.core.SystemMessageManager; import com.tiedup.remake.core.TiedUpMod; -import com.tiedup.remake.items.ItemGpsCollar; -import com.tiedup.remake.items.ItemShockCollar; -import com.tiedup.remake.items.base.ItemCollar; import com.tiedup.remake.state.IBondageState; +import com.tiedup.remake.v2.bondage.CollarHelper; import com.tiedup.remake.state.IRestrainable; import com.tiedup.remake.state.PlayerBindState; import com.tiedup.remake.state.PlayerCaptorManager; @@ -128,8 +126,8 @@ public class PacketSlaveAction { ItemStack collarStack = kidnapped.getEquipment( BodyRegionV2.NECK ); - if (collarStack.getItem() instanceof ItemCollar collar) { - if (collar.isOwner(collarStack, sender)) { + if (CollarHelper.isCollar(collarStack)) { + if (CollarHelper.isOwner(collarStack, sender)) { targetCaptive = kidnapped; break; } @@ -219,10 +217,7 @@ public class PacketSlaveAction { } ItemStack collarStack = target.getEquipment(BodyRegionV2.NECK); - if ( - !(collarStack.getItem() instanceof ItemCollar collar) || - !collar.canShock() - ) { + if (!CollarHelper.canShock(collarStack)) { SystemMessageManager.sendToPlayer( sender, SystemMessageManager.MessageCategory.ERROR, @@ -232,14 +227,8 @@ public class PacketSlaveAction { } // Check if sender is owner of the collar or collar is public - // FIX: Always check permissions for ANY collar that can shock, not just ItemShockCollar - boolean isOwner = collar.isOwner(collarStack, sender); - boolean isPublic = false; - - // ItemShockCollar has additional "public mode" that allows anyone to shock - if (collarStack.getItem() instanceof ItemShockCollar shockCollar) { - isPublic = shockCollar.isPublic(collarStack); - } + boolean isOwner = CollarHelper.isOwner(collarStack, sender); + boolean isPublic = CollarHelper.isPublicShock(collarStack); if (!isOwner && !isPublic) { SystemMessageManager.sendToPlayer( @@ -285,10 +274,7 @@ public class PacketSlaveAction { } ItemStack collarStack = target.getEquipment(BodyRegionV2.NECK); - if ( - !(collarStack.getItem() instanceof ItemCollar collar) || - !collar.hasGPS() - ) { + if (!CollarHelper.hasGPS(collarStack)) { SystemMessageManager.sendToPlayer( sender, SystemMessageManager.MessageCategory.ERROR, @@ -298,18 +284,16 @@ public class PacketSlaveAction { } // Check permissions - if (collarStack.getItem() instanceof ItemGpsCollar gpsCollar) { - boolean isOwner = collar.isOwner(collarStack, sender); - boolean isPublic = gpsCollar.hasPublicTracking(collarStack); + boolean isOwner = CollarHelper.isOwner(collarStack, sender); + boolean isPublic = CollarHelper.hasPublicTracking(collarStack); - if (!isOwner && !isPublic) { - SystemMessageManager.sendToPlayer( - sender, - SystemMessageManager.MessageCategory.ERROR, - "You don't have permission to track " + name + "!" - ); - return; - } + if (!isOwner && !isPublic) { + SystemMessageManager.sendToPlayer( + sender, + SystemMessageManager.MessageCategory.ERROR, + "You don't have permission to track " + name + "!" + ); + return; } // Check same dimension @@ -367,8 +351,8 @@ public class PacketSlaveAction { } else { // For collar-owned entities, just remove collar ownership ItemStack collarStack = target.getEquipment(BodyRegionV2.NECK); - if (collarStack.getItem() instanceof ItemCollar collar) { - collar.removeOwner(collarStack, sender.getUUID()); + if (CollarHelper.isCollar(collarStack)) { + CollarHelper.removeOwner(collarStack, sender.getUUID()); SystemMessageManager.sendToPlayer( sender, "Released collar control of " + name + "!", diff --git a/src/main/java/com/tiedup/remake/personality/ToolMode.java b/src/main/java/com/tiedup/remake/personality/ToolMode.java index 11e230a..f16e739 100644 --- a/src/main/java/com/tiedup/remake/personality/ToolMode.java +++ b/src/main/java/com/tiedup/remake/personality/ToolMode.java @@ -1,6 +1,6 @@ package com.tiedup.remake.personality; -import com.tiedup.remake.items.base.ItemBind; +import com.tiedup.remake.v2.bondage.BindModeHelper; import net.minecraft.world.item.AxeItem; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.PickaxeItem; @@ -48,7 +48,7 @@ public enum ToolMode { if (item instanceof AxeItem) { return WOODCUTTING; } - if (item instanceof ItemBind) { + if (BindModeHelper.isBindItem(mainHand)) { return CAPTURE; } diff --git a/src/main/java/com/tiedup/remake/state/IBondageState.java b/src/main/java/com/tiedup/remake/state/IBondageState.java index 6245034..73e430b 100644 --- a/src/main/java/com/tiedup/remake/state/IBondageState.java +++ b/src/main/java/com/tiedup/remake/state/IBondageState.java @@ -1,8 +1,8 @@ package com.tiedup.remake.state; import com.tiedup.remake.items.base.ILockable; -import com.tiedup.remake.items.base.ItemBind; import com.tiedup.remake.v2.BodyRegionV2; +import com.tiedup.remake.v2.bondage.BindModeHelper; import java.util.function.Supplier; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; @@ -97,7 +97,7 @@ public interface IBondageState extends ICapturable { if (!isTiedUp()) return false; ItemStack bind = getEquipment(BodyRegionV2.ARMS); if (bind.isEmpty()) return false; - return ItemBind.hasArmsBound(bind); + return BindModeHelper.hasArmsBound(bind); } /** @@ -111,7 +111,7 @@ public interface IBondageState extends ICapturable { if (!isTiedUp()) return false; ItemStack bind = getEquipment(BodyRegionV2.ARMS); if (bind.isEmpty()) return false; - return ItemBind.hasLegsBound(bind); + return BindModeHelper.hasLegsBound(bind); } /** @@ -121,10 +121,10 @@ public interface IBondageState extends ICapturable { * @return "full", "arms", or "legs" */ default String getBindModeId() { - if (!isTiedUp()) return ItemBind.BIND_MODE_FULL; + if (!isTiedUp()) return BindModeHelper.MODE_FULL; ItemStack bind = getEquipment(BodyRegionV2.ARMS); - if (bind.isEmpty()) return ItemBind.BIND_MODE_FULL; - return ItemBind.getBindModeId(bind); + if (bind.isEmpty()) return BindModeHelper.MODE_FULL; + return BindModeHelper.getBindModeId(bind); } /** diff --git a/src/main/java/com/tiedup/remake/state/PlayerBindState.java b/src/main/java/com/tiedup/remake/state/PlayerBindState.java index 77bb59f..a571d9a 100644 --- a/src/main/java/com/tiedup/remake/state/PlayerBindState.java +++ b/src/main/java/com/tiedup/remake/state/PlayerBindState.java @@ -3,8 +3,8 @@ package com.tiedup.remake.state; import com.tiedup.remake.core.ModSounds; import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.entities.LeashProxyEntity; -import com.tiedup.remake.items.base.ItemBind; -import com.tiedup.remake.items.base.ItemCollar; +import com.tiedup.remake.items.base.ILockable; +import com.tiedup.remake.v2.bondage.CollarHelper; import com.tiedup.remake.network.sync.SyncManager; import com.tiedup.remake.state.components.PlayerCaptivity; import com.tiedup.remake.state.components.PlayerClothesPermission; @@ -737,8 +737,8 @@ public class PlayerBindState implements IRestrainable, IPlayerBindStateHost { ItemStack collar = getCurrentCollar(); return ( !collar.isEmpty() && - collar.getItem() instanceof ItemCollar collarItem && - collarItem.isLocked(collar) + collar.getItem() instanceof ILockable lockable && + lockable.isLocked(collar) ); } @@ -1211,9 +1211,9 @@ public class PlayerBindState implements IRestrainable, IPlayerBindStateHost { ItemStack collar = getEquipment(BodyRegionV2.NECK); if ( !collar.isEmpty() && - collar.getItem() instanceof ItemCollar collarItem + collar.getItem() instanceof ILockable lockable ) { - collarItem.setLocked(collar, false); + lockable.setLocked(collar, false); } // Drop all items diff --git a/src/main/java/com/tiedup/remake/state/PlayerCaptorManager.java b/src/main/java/com/tiedup/remake/state/PlayerCaptorManager.java index d247c63..cfc970c 100644 --- a/src/main/java/com/tiedup/remake/state/PlayerCaptorManager.java +++ b/src/main/java/com/tiedup/remake/state/PlayerCaptorManager.java @@ -1,8 +1,8 @@ package com.tiedup.remake.state; import com.tiedup.remake.core.TiedUpMod; -import com.tiedup.remake.items.base.ItemCollar; import com.tiedup.remake.v2.BodyRegionV2; +import com.tiedup.remake.v2.bondage.CollarHelper; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; @@ -144,9 +144,9 @@ public class PlayerCaptorManager implements ICaptor { // Check if target has collar with this captor as owner if (target.hasCollar()) { ItemStack collar = target.getEquipment(BodyRegionV2.NECK); - if (collar.getItem() instanceof ItemCollar collarItem) { + if (CollarHelper.isCollar(collar)) { if ( - collarItem.getOwners(collar).contains(this.captor.getUUID()) + CollarHelper.getOwners(collar).contains(this.captor.getUUID()) ) { return true; } diff --git a/src/main/java/com/tiedup/remake/state/components/PlayerDataRetrieval.java b/src/main/java/com/tiedup/remake/state/components/PlayerDataRetrieval.java index 73d9db0..21fe85e 100644 --- a/src/main/java/com/tiedup/remake/state/components/PlayerDataRetrieval.java +++ b/src/main/java/com/tiedup/remake/state/components/PlayerDataRetrieval.java @@ -1,7 +1,7 @@ package com.tiedup.remake.state.components; -import com.tiedup.remake.items.base.ItemCollar; import com.tiedup.remake.state.hosts.IPlayerBindStateHost; +import com.tiedup.remake.v2.bondage.CollarHelper; import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper; import net.minecraft.world.entity.LivingEntity; @@ -87,12 +87,9 @@ public class PlayerDataRetrieval { Player player = host.getPlayer(); ItemStack collar = getCurrentCollar(); - if ( - !collar.isEmpty() && - collar.getItem() instanceof ItemCollar collarItem - ) { + if (!collar.isEmpty() && CollarHelper.isCollar(collar)) { // Try to get nickname from collar NBT - String nickname = collarItem.getNickname(collar); + String nickname = CollarHelper.getNickname(collar); if (nickname != null && !nickname.isEmpty()) { return nickname; } @@ -109,8 +106,8 @@ public class PlayerDataRetrieval { ItemStack collar = getCurrentCollar(); if (collar.isEmpty()) return false; - if (collar.getItem() instanceof ItemCollar collarItem) { - String nickname = collarItem.getNickname(collar); + if (CollarHelper.isCollar(collar)) { + String nickname = CollarHelper.getNickname(collar); return nickname != null && !nickname.isEmpty(); } return false; diff --git a/src/main/java/com/tiedup/remake/state/components/PlayerEquipment.java b/src/main/java/com/tiedup/remake/state/components/PlayerEquipment.java index 8294d3a..b19dae4 100644 --- a/src/main/java/com/tiedup/remake/state/components/PlayerEquipment.java +++ b/src/main/java/com/tiedup/remake/state/components/PlayerEquipment.java @@ -1,8 +1,6 @@ package com.tiedup.remake.state.components; import com.tiedup.remake.items.base.ILockable; -import com.tiedup.remake.items.base.ItemBind; -import com.tiedup.remake.items.base.ItemCollar; import com.tiedup.remake.state.hosts.IPlayerBindStateHost; import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.bondage.IV2BondageEquipment; @@ -154,9 +152,9 @@ public class PlayerEquipment { ); if (collar.isEmpty()) return ItemStack.EMPTY; - if (collar.getItem() instanceof ItemCollar collarItem) { - if (!force && collarItem.isLocked(collar)) return ItemStack.EMPTY; - collarItem.setLocked(collar, false); + if (collar.getItem() instanceof ILockable lockable) { + if (!force && lockable.isLocked(collar)) return ItemStack.EMPTY; + lockable.setLocked(collar, false); } return V2EquipmentHelper.unequipFromRegion(player, BodyRegionV2.NECK); } diff --git a/src/main/java/com/tiedup/remake/state/components/PlayerLifecycle.java b/src/main/java/com/tiedup/remake/state/components/PlayerLifecycle.java index 55d1cba..180d822 100644 --- a/src/main/java/com/tiedup/remake/state/components/PlayerLifecycle.java +++ b/src/main/java/com/tiedup/remake/state/components/PlayerLifecycle.java @@ -3,8 +3,8 @@ package com.tiedup.remake.state.components; import com.tiedup.remake.cells.CampOwnership; import com.tiedup.remake.cells.CellRegistryV2; import com.tiedup.remake.core.TiedUpMod; -import com.tiedup.remake.items.base.ItemBind; import com.tiedup.remake.state.hosts.IPlayerBindStateHost; +import com.tiedup.remake.v2.bondage.BindModeHelper; import com.tiedup.remake.v2.BodyRegionV2; import java.util.UUID; import net.minecraft.server.level.ServerLevel; @@ -148,7 +148,7 @@ public class PlayerLifecycle { */ private boolean hasLegsBound(ItemStack bind) { if (bind.isEmpty()) return false; - if (!(bind.getItem() instanceof ItemBind)) return false; - return ItemBind.hasLegsBound(bind); + if (!BindModeHelper.isBindItem(bind)) return false; + return BindModeHelper.hasLegsBound(bind); } } diff --git a/src/main/java/com/tiedup/remake/util/BondageItemLoaderUtility.java b/src/main/java/com/tiedup/remake/util/BondageItemLoaderUtility.java index b6c8d8f..b25395b 100644 --- a/src/main/java/com/tiedup/remake/util/BondageItemLoaderUtility.java +++ b/src/main/java/com/tiedup/remake/util/BondageItemLoaderUtility.java @@ -1,7 +1,19 @@ package com.tiedup.remake.util; import com.tiedup.remake.blocks.entity.IBondageItemHolder; -import com.tiedup.remake.items.base.*; +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.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.List; import net.minecraft.ChatFormatting; import net.minecraft.nbt.CompoundTag; @@ -41,36 +53,32 @@ public final class BondageItemLoaderUtility { ItemStack stack, Player player ) { - if (stack.getItem() instanceof ItemBind && holder.getBind().isEmpty()) { + if ((stack.getItem() instanceof ItemBind || BindModeHelper.isBindItem(stack)) && holder.getBind().isEmpty()) { holder.setBind(stack.copyWithCount(1)); if (!player.isCreative()) stack.shrink(1); return true; } - if (stack.getItem() instanceof ItemGag && holder.getGag().isEmpty()) { + if ((stack.getItem() instanceof ItemGag + || DataDrivenBondageItem.getComponent(stack, ComponentType.GAGGING, GaggingComponent.class) != null) + && holder.getGag().isEmpty()) { holder.setGag(stack.copyWithCount(1)); if (!player.isCreative()) stack.shrink(1); return true; } - if ( - stack.getItem() instanceof ItemBlindfold && - holder.getBlindfold().isEmpty() - ) { + if ((stack.getItem() instanceof ItemBlindfold || isDataDrivenForRegion(stack, BodyRegionV2.EYES)) + && holder.getBlindfold().isEmpty()) { holder.setBlindfold(stack.copyWithCount(1)); if (!player.isCreative()) stack.shrink(1); return true; } - if ( - stack.getItem() instanceof ItemEarplugs && - holder.getEarplugs().isEmpty() - ) { + if ((stack.getItem() instanceof ItemEarplugs || isDataDrivenForRegion(stack, BodyRegionV2.EARS)) + && holder.getEarplugs().isEmpty()) { holder.setEarplugs(stack.copyWithCount(1)); if (!player.isCreative()) stack.shrink(1); return true; } - if ( - stack.getItem() instanceof ItemCollar && - holder.getCollar().isEmpty() - ) { + if ((stack.getItem() instanceof ItemCollar || CollarHelper.isCollar(stack)) + && holder.getCollar().isEmpty()) { holder.setCollar(stack.copyWithCount(1)); if (!player.isCreative()) stack.shrink(1); return true; @@ -87,13 +95,28 @@ public final class BondageItemLoaderUtility { * @return true if the item can be loaded into a bondage item holder */ public static boolean isLoadableBondageItem(ItemStack stack) { - return ( - (stack.getItem() instanceof ItemBind) || - (stack.getItem() instanceof ItemGag) || - (stack.getItem() instanceof ItemBlindfold) || - (stack.getItem() instanceof ItemEarplugs) || - (stack.getItem() instanceof ItemCollar) - ); + if (stack.isEmpty()) return false; + // V1 item types + if (stack.getItem() instanceof ItemBind + || stack.getItem() instanceof ItemGag + || stack.getItem() instanceof ItemBlindfold + || stack.getItem() instanceof ItemEarplugs + || stack.getItem() instanceof ItemCollar) { + return true; + } + // V2 data-driven items: bind, gag, blindfold, earplugs, collar + if (BindModeHelper.isBindItem(stack)) return true; + if (DataDrivenBondageItem.getComponent(stack, ComponentType.GAGGING, GaggingComponent.class) != null) return true; + if (isDataDrivenForRegion(stack, BodyRegionV2.EYES)) return true; + if (isDataDrivenForRegion(stack, BodyRegionV2.EARS)) return true; + if (CollarHelper.isCollar(stack)) return true; + return false; + } + + /** 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); } /** diff --git a/src/main/java/com/tiedup/remake/util/RestraintApplicator.java b/src/main/java/com/tiedup/remake/util/RestraintApplicator.java index 28ed1a0..b9f8493 100644 --- a/src/main/java/com/tiedup/remake/util/RestraintApplicator.java +++ b/src/main/java/com/tiedup/remake/util/RestraintApplicator.java @@ -2,9 +2,8 @@ package com.tiedup.remake.util; import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.items.base.IHasResistance; -import com.tiedup.remake.items.base.ItemBind; -import com.tiedup.remake.items.base.ItemCollar; import com.tiedup.remake.state.IBondageState; +import com.tiedup.remake.v2.bondage.CollarHelper; import com.tiedup.remake.v2.BodyRegionV2; import java.util.UUID; import net.minecraft.ChatFormatting; @@ -60,7 +59,7 @@ public final class RestraintApplicator { } // Tighten existing bind - reset resistance to max - if (currentBind.getItem() instanceof ItemBind bindItem) { + if (currentBind.getItem() instanceof IHasResistance bindItem) { int maxResistance = bindItem.getBaseResistance(target); state.setCurrentBindResistance(maxResistance); return true; @@ -286,11 +285,8 @@ public final class RestraintApplicator { ItemStack collarCopy = collar.copy(); // Add owner if provided - if ( - ownerUUID != null && - collarCopy.getItem() instanceof ItemCollar collarItem - ) { - collarItem.addOwner( + if (ownerUUID != null && CollarHelper.isCollar(collarCopy)) { + CollarHelper.addOwner( collarCopy, ownerUUID, ownerName != null ? ownerName : "Unknown" diff --git a/src/main/java/com/tiedup/remake/v2/bondage/movement/MovementStyleResolver.java b/src/main/java/com/tiedup/remake/v2/bondage/movement/MovementStyleResolver.java index d58482e..1c1e2f4 100644 --- a/src/main/java/com/tiedup/remake/v2/bondage/movement/MovementStyleResolver.java +++ b/src/main/java/com/tiedup/remake/v2/bondage/movement/MovementStyleResolver.java @@ -1,7 +1,8 @@ package com.tiedup.remake.v2.bondage.movement; -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.datadriven.DataDrivenItemDefinition; import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemRegistry; @@ -19,7 +20,7 @@ import org.jetbrains.annotations.Nullable; * default speed/jump values. Modifiers from lower-severity items are ignored.

* *

V1 Compatibility (H6 fix)

- *

V1 items ({@link ItemBind}) stored in V2 capability + *

V1 items (ItemBind) stored in V2 capability * do not have data-driven definitions. This resolver provides a fallback that * maps V1 bind mode + pose type to a {@link MovementStyle} with speed values matching * the original V1 behavior, preventing double stacking between the legacy @@ -122,7 +123,7 @@ public final class MovementStyleResolver { // ==================== V1 Fallback ==================== /** - * Attempt to derive a movement style from a V1 {@link ItemBind} item. + * Attempt to derive a movement style from a V1 bind item. * *

Only items with legs bound produce a movement style. The mapping preserves * the original V1 speed values:

@@ -137,15 +138,15 @@ public final class MovementStyleResolver { */ @Nullable private static V1Fallback resolveV1Fallback(ItemStack stack) { - if (!(stack.getItem() instanceof ItemBind bindItem)) { + if (!BindModeHelper.isBindItem(stack)) { return null; } - if (!ItemBind.hasLegsBound(stack)) { + if (!BindModeHelper.hasLegsBound(stack)) { return null; } - PoseType poseType = bindItem.getPoseType(); + PoseType poseType = PoseTypeHelper.getPoseType(stack); return switch (poseType) { case WRAP, LATEX_SACK -> new V1Fallback( diff --git a/src/main/java/com/tiedup/remake/v2/furniture/network/PacketFurnitureForcemount.java b/src/main/java/com/tiedup/remake/v2/furniture/network/PacketFurnitureForcemount.java index 39eef2f..1a09c32 100644 --- a/src/main/java/com/tiedup/remake/v2/furniture/network/PacketFurnitureForcemount.java +++ b/src/main/java/com/tiedup/remake/v2/furniture/network/PacketFurnitureForcemount.java @@ -1,8 +1,8 @@ package com.tiedup.remake.v2.furniture.network; import com.tiedup.remake.core.TiedUpMod; -import com.tiedup.remake.items.base.ItemCollar; import com.tiedup.remake.network.PacketRateLimiter; +import com.tiedup.remake.v2.bondage.CollarHelper; import com.tiedup.remake.state.IBondageState; import com.tiedup.remake.util.KidnappedHelper; import com.tiedup.remake.v2.BodyRegionV2; @@ -28,7 +28,7 @@ import net.minecraftforge.network.NetworkEvent; * Client-to-server packet: master forces a captive onto a furniture seat. * *

The sender must own the captive's collar (verified via - * {@link ItemCollar#isOwner(ItemStack, net.minecraft.world.entity.player.Player)}), + * {@link CollarHelper#isOwner(ItemStack, net.minecraft.world.entity.player.Player)}), * the captive must be alive and within 5 blocks of both sender and furniture, * and the furniture must have an available seat.

* @@ -132,10 +132,7 @@ public class PacketFurnitureForcemount { } ItemStack collarStack = captiveState.getEquipment(BodyRegionV2.NECK); - if ( - collarStack.isEmpty() || - !(collarStack.getItem() instanceof ItemCollar collar) - ) { + if (!CollarHelper.isCollar(collarStack)) { TiedUpMod.LOGGER.debug( "[PacketFurnitureForcemount] Invalid collar item on captive" ); @@ -143,7 +140,7 @@ public class PacketFurnitureForcemount { } // Collar must be owned by sender (or sender has admin permission) - if (!collar.isOwner(collarStack, sender) && !sender.hasPermissions(2)) { + if (!CollarHelper.isOwner(collarStack, sender) && !sender.hasPermissions(2)) { TiedUpMod.LOGGER.debug( "[PacketFurnitureForcemount] {} is not the collar owner of {}", sender.getName().getString(), diff --git a/src/main/java/com/tiedup/remake/worldgen/HangingCagePiece.java b/src/main/java/com/tiedup/remake/worldgen/HangingCagePiece.java index d5c333b..f09e2ad 100644 --- a/src/main/java/com/tiedup/remake/worldgen/HangingCagePiece.java +++ b/src/main/java/com/tiedup/remake/worldgen/HangingCagePiece.java @@ -3,13 +3,15 @@ package com.tiedup.remake.worldgen; import com.tiedup.remake.blocks.ModBlocks; import com.tiedup.remake.blocks.entity.TrappedChestBlockEntity; 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.BlindfoldVariant; -import com.tiedup.remake.items.base.GagVariant; import com.tiedup.remake.v2.V2Blocks; +import com.tiedup.remake.v2.BodyRegionV2; +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 com.tiedup.remake.v2.blocks.PetCageBlock; import com.tiedup.remake.v2.blocks.PetCagePartBlock; +import java.util.List; +import java.util.stream.Collectors; import javax.annotation.Nullable; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; @@ -540,34 +542,35 @@ public class HangingCagePiece extends StructurePiece { BlockEntity be = level.getBlockEntity(chestPos); if (be instanceof TrappedChestBlockEntity trappedChest) { - // Random bind - BindVariant[] bindVariants = BindVariant.values(); - BindVariant chosenBind = bindVariants[random.nextInt( - bindVariants.length - )]; - ItemStack bindStack = new ItemStack(ModItems.getBind(chosenBind)); - trappedChest.setBind(bindStack); - - // Random gag (50% chance) - if (random.nextFloat() < 0.50f) { - GagVariant[] gagVariants = GagVariant.values(); - GagVariant chosenGag = gagVariants[random.nextInt( - gagVariants.length - )]; - ItemStack gagStack = new ItemStack(ModItems.getGag(chosenGag)); - trappedChest.setGag(gagStack); + // Random bind from data-driven ARMS items + List binds = DataDrivenItemRegistry.getAll().stream() + .filter(d -> d.occupiedRegions().contains(BodyRegionV2.ARMS)) + .collect(Collectors.toList()); + if (!binds.isEmpty()) { + DataDrivenItemDefinition chosenBind = binds.get(random.nextInt(binds.size())); + trappedChest.setBind(DataDrivenBondageItem.createStack(chosenBind.id())); } - // Random blindfold (30% chance) + // Random gag from data-driven MOUTH items (50% chance) + if (random.nextFloat() < 0.50f) { + List gags = DataDrivenItemRegistry.getAll().stream() + .filter(d -> d.occupiedRegions().contains(BodyRegionV2.MOUTH)) + .collect(Collectors.toList()); + if (!gags.isEmpty()) { + DataDrivenItemDefinition chosenGag = gags.get(random.nextInt(gags.size())); + trappedChest.setGag(DataDrivenBondageItem.createStack(chosenGag.id())); + } + } + + // Random blindfold from data-driven EYES items (30% chance) if (random.nextFloat() < 0.30f) { - BlindfoldVariant[] bfVariants = BlindfoldVariant.values(); - BlindfoldVariant chosenBf = bfVariants[random.nextInt( - bfVariants.length - )]; - ItemStack bfStack = new ItemStack( - ModItems.getBlindfold(chosenBf) - ); - trappedChest.setBlindfold(bfStack); + List blindfolds = DataDrivenItemRegistry.getAll().stream() + .filter(d -> d.occupiedRegions().contains(BodyRegionV2.EYES)) + .collect(Collectors.toList()); + if (!blindfolds.isEmpty()) { + DataDrivenItemDefinition chosenBf = blindfolds.get(random.nextInt(blindfolds.size())); + trappedChest.setBlindfold(DataDrivenBondageItem.createStack(chosenBf.id())); + } } } } @@ -629,22 +632,26 @@ public class HangingCagePiece extends StructurePiece { entityTag.putUUID("UUID", java.util.UUID.randomUUID()); // Random bind item — the damsel spawns already restrained - BindVariant[] variants = BindVariant.values(); - BindVariant chosenBind = variants[random.nextInt(variants.length)]; - ItemStack bindStack = new ItemStack(ModItems.getBind(chosenBind)); - bindStack.getOrCreateTag().putString("bindMode", "full"); - entityTag.put("Bind", bindStack.save(new CompoundTag())); + List bindDefs = DataDrivenItemRegistry.getAll().stream() + .filter(d -> d.occupiedRegions().contains(BodyRegionV2.ARMS)) + .collect(Collectors.toList()); + if (!bindDefs.isEmpty()) { + DataDrivenItemDefinition chosenBind = bindDefs.get(random.nextInt(bindDefs.size())); + ItemStack bindStack = DataDrivenBondageItem.createStack(chosenBind.id()); + bindStack.getOrCreateTag().putString("bindMode", "full"); + entityTag.put("Bind", bindStack.save(new CompoundTag())); - // Add directly to chunk's pending entity list - ChunkAccess chunk = level.getChunk(masterPos); - if (chunk instanceof ProtoChunk protoChunk) { - protoChunk.addEntity(entityTag); - TiedUpMod.LOGGER.info( - "[HangingCage] Scheduled {} damsel with {} at {}", - shiny ? "shiny" : "regular", - chosenBind.getRegistryName(), - masterPos.toShortString() - ); + // Add directly to chunk's pending entity list + ChunkAccess chunk = level.getChunk(masterPos); + if (chunk instanceof ProtoChunk protoChunk) { + protoChunk.addEntity(entityTag); + TiedUpMod.LOGGER.info( + "[HangingCage] Scheduled {} damsel with {} at {}", + shiny ? "shiny" : "regular", + chosenBind.id(), + masterPos.toShortString() + ); + } } } } -- 2.49.1 From 4246d1c663ed36d5399ce836ce9b7a4a8c58231e Mon Sep 17 00:00:00 2001 From: NotEvil Date: Wed, 15 Apr 2026 00:26:07 +0200 Subject: [PATCH 2/3] 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. --- .../minigame/StruggleSessionManager.java | 1 + .../remake/worldgen/HangingCagePiece.java | 85 ++++++++++--------- 2 files changed, 44 insertions(+), 42 deletions(-) diff --git a/src/main/java/com/tiedup/remake/minigame/StruggleSessionManager.java b/src/main/java/com/tiedup/remake/minigame/StruggleSessionManager.java index eaaae08..258721f 100644 --- a/src/main/java/com/tiedup/remake/minigame/StruggleSessionManager.java +++ b/src/main/java/com/tiedup/remake/minigame/StruggleSessionManager.java @@ -647,6 +647,7 @@ public class StruggleSessionManager { } if (bindStack.getItem() instanceof IHasResistance resistanceItem) { resistanceItem.setCurrentResistance(bindStack, session.getCurrentResistance()); + V2EquipmentHelper.sync(player); } } diff --git a/src/main/java/com/tiedup/remake/worldgen/HangingCagePiece.java b/src/main/java/com/tiedup/remake/worldgen/HangingCagePiece.java index f09e2ad..06c8256 100644 --- a/src/main/java/com/tiedup/remake/worldgen/HangingCagePiece.java +++ b/src/main/java/com/tiedup/remake/worldgen/HangingCagePiece.java @@ -16,6 +16,7 @@ import javax.annotation.Nullable; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceLocation; import net.minecraft.nbt.DoubleTag; import net.minecraft.nbt.FloatTag; import net.minecraft.nbt.ListTag; @@ -543,38 +544,45 @@ public class HangingCagePiece extends StructurePiece { BlockEntity be = level.getBlockEntity(chestPos); if (be instanceof TrappedChestBlockEntity trappedChest) { // Random bind from data-driven ARMS items - List binds = DataDrivenItemRegistry.getAll().stream() - .filter(d -> d.occupiedRegions().contains(BodyRegionV2.ARMS)) - .collect(Collectors.toList()); - if (!binds.isEmpty()) { - DataDrivenItemDefinition chosenBind = binds.get(random.nextInt(binds.size())); - trappedChest.setBind(DataDrivenBondageItem.createStack(chosenBind.id())); - } + trappedChest.setBind(randomItemForRegion(random, BodyRegionV2.ARMS, FALLBACK_BINDS)); - // Random gag from data-driven MOUTH items (50% chance) + // Random gag (50% chance) if (random.nextFloat() < 0.50f) { - List gags = DataDrivenItemRegistry.getAll().stream() - .filter(d -> d.occupiedRegions().contains(BodyRegionV2.MOUTH)) - .collect(Collectors.toList()); - if (!gags.isEmpty()) { - DataDrivenItemDefinition chosenGag = gags.get(random.nextInt(gags.size())); - trappedChest.setGag(DataDrivenBondageItem.createStack(chosenGag.id())); - } + trappedChest.setGag(randomItemForRegion(random, BodyRegionV2.MOUTH, FALLBACK_GAGS)); } - // Random blindfold from data-driven EYES items (30% chance) + // Random blindfold (30% chance) if (random.nextFloat() < 0.30f) { - List blindfolds = DataDrivenItemRegistry.getAll().stream() - .filter(d -> d.occupiedRegions().contains(BodyRegionV2.EYES)) - .collect(Collectors.toList()); - if (!blindfolds.isEmpty()) { - DataDrivenItemDefinition chosenBf = blindfolds.get(random.nextInt(blindfolds.size())); - trappedChest.setBlindfold(DataDrivenBondageItem.createStack(chosenBf.id())); - } + trappedChest.setBlindfold(randomItemForRegion(random, BodyRegionV2.EYES, FALLBACK_BLINDFOLDS)); } } } + // Fallback item IDs for worldgen when DataDrivenItemRegistry is empty (race with reload) + private static final ResourceLocation[] FALLBACK_BINDS = { + new ResourceLocation("tiedup", "ropes"), + new ResourceLocation("tiedup", "chain"), + new ResourceLocation("tiedup", "armbinder") + }; + private static final ResourceLocation[] FALLBACK_GAGS = { + new ResourceLocation("tiedup", "cloth_gag"), + new ResourceLocation("tiedup", "ball_gag") + }; + private static final ResourceLocation[] FALLBACK_BLINDFOLDS = { + new ResourceLocation("tiedup", "classic_blindfold") + }; + + private static ItemStack randomItemForRegion(RandomSource random, BodyRegionV2 region, ResourceLocation[] fallbacks) { + List defs = DataDrivenItemRegistry.getAll().stream() + .filter(d -> d.occupiedRegions().contains(region)) + .collect(Collectors.toList()); + if (!defs.isEmpty()) { + return DataDrivenBondageItem.createStack(defs.get(random.nextInt(defs.size())).id()); + } + // Fallback for worldgen race condition (registry not loaded yet) + return DataDrivenBondageItem.createStack(fallbacks[random.nextInt(fallbacks.length)]); + } + static void safeSetBlock( WorldGenLevel level, BlockPos pos, @@ -632,26 +640,19 @@ public class HangingCagePiece extends StructurePiece { entityTag.putUUID("UUID", java.util.UUID.randomUUID()); // Random bind item — the damsel spawns already restrained - List bindDefs = DataDrivenItemRegistry.getAll().stream() - .filter(d -> d.occupiedRegions().contains(BodyRegionV2.ARMS)) - .collect(Collectors.toList()); - if (!bindDefs.isEmpty()) { - DataDrivenItemDefinition chosenBind = bindDefs.get(random.nextInt(bindDefs.size())); - ItemStack bindStack = DataDrivenBondageItem.createStack(chosenBind.id()); - bindStack.getOrCreateTag().putString("bindMode", "full"); - entityTag.put("Bind", bindStack.save(new CompoundTag())); + ItemStack bindStack = randomItemForRegion(random, BodyRegionV2.ARMS, FALLBACK_BINDS); + bindStack.getOrCreateTag().putString("bindMode", "full"); + entityTag.put("Bind", bindStack.save(new CompoundTag())); - // Add directly to chunk's pending entity list - ChunkAccess chunk = level.getChunk(masterPos); - if (chunk instanceof ProtoChunk protoChunk) { - protoChunk.addEntity(entityTag); - TiedUpMod.LOGGER.info( - "[HangingCage] Scheduled {} damsel with {} at {}", - shiny ? "shiny" : "regular", - chosenBind.id(), - masterPos.toShortString() - ); - } + // Add directly to chunk's pending entity list + ChunkAccess chunk = level.getChunk(masterPos); + if (chunk instanceof ProtoChunk protoChunk) { + protoChunk.addEntity(entityTag); + TiedUpMod.LOGGER.info( + "[HangingCage] Scheduled {} damsel at {}", + shiny ? "shiny" : "regular", + masterPos.toShortString() + ); } } } -- 2.49.1 From 9e73434a4a89d37a01ba4bad6a5ba161a5c80826 Mon Sep 17 00:00:00 2001 From: NotEvil Date: Wed, 15 Apr 2026 00:55:02 +0200 Subject: [PATCH 3/3] chore: remove docs from branch C (should not be tracked here) --- docs/AUDIT.md | 413 --------- docs/plans/D01-branch-A-bridge-utilities.md | 284 ------ docs/plans/D01-branch-B-json-definitions.md | 128 --- docs/plans/D01-branch-C-consumer-migration.md | 107 --- docs/plans/D01-branch-D-v1-cleanup.md | 188 ---- docs/plans/D01-branch-E-resistance-rework.md | 155 ---- .../2026-04-13-d01-phase1-component-system.md | 869 ------------------ 7 files changed, 2144 deletions(-) delete mode 100644 docs/AUDIT.md delete mode 100644 docs/plans/D01-branch-A-bridge-utilities.md delete mode 100644 docs/plans/D01-branch-B-json-definitions.md delete mode 100644 docs/plans/D01-branch-C-consumer-migration.md delete mode 100644 docs/plans/D01-branch-D-v1-cleanup.md delete mode 100644 docs/plans/D01-branch-E-resistance-rework.md delete mode 100644 docs/superpowers/plans/2026-04-13-d01-phase1-component-system.md diff --git a/docs/AUDIT.md b/docs/AUDIT.md deleted file mode 100644 index 54a3b77..0000000 --- a/docs/AUDIT.md +++ /dev/null @@ -1,413 +0,0 @@ -# TiedUp! — Codebase Audit - -> Audit complet du mod, systeme par systeme, pour consolider la base et la coherence du code. - ---- - -## Objectif - -Passer en revue **chaque systeme** du mod en profondeur pour : -1. **Consolider** — identifier et corriger les incohérences, le code mort, la duplication -2. **Améliorer** — proposer des refactors ciblés là où l'architecture freine le développement -3. **Documenter** — laisser une trace des décisions et de l'état de chaque système - -Ce n'est pas un rewrite. On stabilise ce qui existe. - ---- - -## Règles de l'audit - -- **Système par système**, dans l'ordre de dépendance (fondations d'abord) -- **Deep dive** — on remonte les sources, les dépendants, les call chains via le MCP -- **Pas de changement sans discussion** — on constate, on discute, puis on corrige -- **Pas de sur-ingénierie** — on fixe les vrais problèmes, on ne refactor pas pour le plaisir -- **Reindex MCP** après chaque batch de corrections significatives - -### Processus par système - -Chaque audit suit ce cycle : - -``` -1. EXPLORATION — Lire le code, tracer les dépendances (MCP) -2. CONSTATS — Documenter les problèmes trouvés (dans ce fichier) -3. VÉRIFICATION — Relire, confirmer, pas de faux positifs -4. PROPOSITIONS — Pour chaque constat : - → Fix direct (bug, incohérence simple) - → Amélioration architecturale (refactor ciblé, avec justification) -5. DISCUSSION — Valider avec l'utilisateur avant d'implémenter -6. CORRECTION — Appliquer les changements validés -7. REINDEX — MCP reindex après corrections -``` - -On ne passe au système suivant qu'une fois le cycle terminé. - ---- - -## Ordre d'audit - -| # | Système | Packages | Status | -|---|---------|----------|--------| -| 1 | Core + Registries | `core`, `ModItems`, `ModEntities`, `ModNetwork`, `ModGameRules` | Done | -| 2 | State | `state`, `state/components`, `state/hosts`, `state/struggle` | Done | -| 3 | Items (v1) + V2 Bondage | `items/**`, `v2/bondage/**` | Done | -| 4 | Network | `network/**` | Done | -| 5 | Client Animation + GLTF | `client/animation/**`, `client/gltf` | Done | -| 6 | Furniture | `v2/furniture/**` | Done | -| 7 | Entities + AI | `entities/**` | Done | -| 8 | Events | `events/**` | Done | -| 9 | Dialogue + Personality | `dialogue/**`, `personality` | Done | -| 10 | Cells / Prison / Blocks | `cells`, `prison/**`, `blocks/**` | Done | -| 11 | Compat | `compat/mca/**`, `compat/wildfire/**` | Done | -| 12 | Util + Commands + Worldgen + Resources | `util/**`, `commands/**`, `worldgen`, resources | Done | - ---- - -## Échelle de sévérité - -| Niveau | Signification | Action | -|--------|--------------|--------| -| **Haute** | Bug actif, bloquant, ou dette qui empêche une feature majeure | Corriger avant de continuer | -| **Moyenne** | Incohérence ou fragilité qui va poser problème à terme | Planifier la correction | -| **Basse** | Code smell, naming, organisation — pas urgent | Corriger si on touche le fichier | -| **Cosmétique** | Style, formatting, commentaires | Optionnel | - ---- - -## Constats - -### #1 — Core + Registries - -**Positif :** -- DeferredRegister correct partout -- Factory pattern propre pour les variant items -- SettingsAccessor bridge solide (safeGet, BUG-003 fix) -- Séparation client/server correcte -- Data-driven reload listeners bien câblés - -**Problèmes :** (tous vérifiés ✓) - -| ID | Sévérité | Constat | Fichier(s) | Proposition | -|----|----------|---------|------------|-------------| -| C-01 | Haute | SystemMessageManager : 80+ messages hardcodés en anglais, zero `Component.translatable()` | `core/SystemMessageManager.java` | **Fix** : Remplacer `Component.literal()` par `Component.translatable()` avec clés dans `en_us.json`. Garder l'enum + les couleurs, changer uniquement le transport du texte. | -| C-02 | Moyenne | 25 settings triplés entre ModConfig + ModGameRules + SettingsAccessor. Historique de bug (BUG-001 : defaults désyncés) | `core/ModConfig.java`, `core/ModGameRules.java`, `core/SettingsAccessor.java` | **Archi** : Évaluer si les GameRules sont vraiment utiles (ils dupliquent la config serveur). Si oui, centraliser les defaults dans une seule source. Si non, supprimer les GameRules doublons et garder ModConfig + SettingsAccessor. | -| C-03 | Moyenne | ModNetwork : 74 IDs séquentiels. Pattern standard Forge mais fragile à l'insertion. `PROTOCOL_VERSION` protège partiellement. | `network/ModNetwork.java` | **Pas d'action immédiate** : pattern idiomatique Forge 1.20.1. Bumper `PROTOCOL_VERSION` à chaque ajout/suppression de packet. Documenter cette règle. | -| C-04 | Basse | ChokeEffect importe EntityMaster (core → entities) pour check "non-lethal when master-owned" | `core/ChokeEffect.java` | **Fix** : Extraire le check via un tag NBT ou capability sur le Player, consultable sans dépendre d'EntityMaster. | -| C-05 | Basse | 10 types d'entités NPC utilisent DamselRenderer — nom spécifique pour un usage générique | `core/TiedUpMod.java`, `client/renderer/DamselRenderer.java` | **Fix** : Renommer `DamselRenderer` → `NpcRenderer` ou `HumanoidNpcRenderer`. | -| C-06 | Cosmétique | 47+ FQCNs dans le corps de TiedUpMod au lieu d'imports | `core/TiedUpMod.java` | **Fix** : Remplacer par des imports. Faire quand on touche le fichier. | -| C-07 | Basse | ModConfig.ServerConfig : 127 valeurs configurables, 628 lignes, 20+ catégories dans une classe | `core/ModConfig.java` | **Archi** : Découper en sous-classes par domaine (StruggleConfig, NpcConfig, EconomyConfig, etc.) au prochain refactor config. Pas urgent. | - -### #2 — State - -**Positif :** -- Hiérarchie d'interfaces bien conçue (ISP) : `IRestrainableEntity` → `ICapturable` → `IBondageState` → `IRestrainable` (union) -- Décomposition en 11 components (PlayerEquipment, PlayerStateQuery, etc.) — bonne intention -- `IBondageState` n'expose que l'API V2 region-based — l'interface publique est propre -- `CollarRegistry` et `SocialData` : implémentations `SavedData` propres avec persistence correcte -- `IPlayerLeashAccess` : séparation mixin clean pour le système de laisse -- `PlayerCaptorManager` : thread-safe avec `CopyOnWriteArrayList` et `synchronized` - -**Problèmes :** (tous vérifiés ✓) - -| ID | Sévérité | Constat | Fichier(s) | Proposition | -|----|----------|---------|------------|-------------| -| S-01 | Moyenne | PlayerBindState : god class de façade, 1237 lignes, ~80 méthodes de pure délégation vers 11 components | `state/PlayerBindState.java` | **Archi** : Évaluer si les consommateurs peuvent utiliser les components directement via des accesseurs typés (`state.equipment().putBindOn()`) au lieu de passer par la façade. Réduirait la surface de ~80 méthodes de boilerplate. Risque : gros refactor, beaucoup de call sites. | -| S-02 | Moyenne | 8 champs de mouvement publics (`hopCooldown`, `lastX`, etc.) directement mutés par MovementStyleManager (33 accès directs) | `state/PlayerBindState.java`, `v2/bondage/movement/MovementStyleManager.java` | **Fix** : Extraire dans un `MovementState` component avec getters/setters. MovementStyleManager opère via ce component. | -| S-03 | Basse | API V1 slot-based (`putBindOn`, `takeGagOff`) coexiste avec V2 region-based sur la classe concrète PlayerBindState. L'interface `IBondageState` est propre (V2 only). | `state/PlayerBindState.java` | **Archi** : Marquer les méthodes V1 `@Deprecated` pour guider la migration. Les call sites (commands, etc.) devraient migrer vers `equip(BodyRegionV2)`. Pas urgent car l'interface est déjà propre. | -| S-04 | Basse | `hasLegsBound()` lit le slot ARMS (pas LEGS) — design V1 intentionnel : un seul item "bind" couvre bras+jambes via NBT mode. Pas un bug. | `state/IBondageState.java` | **Pas d'action immédiate** : cohérent avec le système actuel. Documenter le design dans un commentaire. Deviendra un vrai problème quand des items LEGS dédiés seront ajoutés en V2. | -| S-05 | Moyenne | Thread safety incohérente : `volatile` (3 champs), `synchronized` (5 méthodes), rien (le reste). La paire `isStruggling`/`struggleStartTick` peut être observée dans un état inconsistant. | `state/PlayerBindState.java` | **Fix** : Définir une stratégie claire. Les champs accédés cross-thread (mouvement, struggle, captor) doivent être soit volatile soit synchronized. Auditer chaque champ. | -| S-06 | Basse | `HumanChairHelper` dans `state/` mais c'est un utilitaire pur sans lien avec le state. Utilisé par AI, animation, mixins. | `state/HumanChairHelper.java` | **Fix** : Déplacer dans `items/base/` (à côté de `PoseType` dont il dépend) ou `util/`. Faire quand on touche le fichier. | - -### #3 — Items (V1) + V2 Bondage - -**Positif :** -- `IV2BondageItem` bien conçu : multi-region, stack-aware, pose priority, blocked regions -- `V2EquipmentManager` : conflict resolution solide (swap single, supersede global) -- `V2EquipmentHelper` : facade propre pour read/write/sync -- `DataDrivenBondageItem` : singleton + NBT registry pattern intelligent pour items data-driven -- `ILockable` : système lock/jam/key complet et cohérent -- `IHasResistance` : résistance NBT avec migration legacy, bien documentée -- `BodyRegionV2` enum complet (15 régions, global flag) -- Variant enums + factory pattern (BindVariant, GagVariant, etc.) propres - -**Problèmes :** (tous vérifiés ✓) - -| ID | Sévérité | Constat | Fichier(s) | Proposition | -|----|----------|---------|------------|-------------| -| I-01 | ~~Haute~~ | ~~Deux hiérarchies d'interfaces parallèles~~ | | **RÉSOLU** : suppression V1 (voir Décision D-01) | -| I-02 | ~~Haute~~ | ~~V1 items bypassent la conflict resolution V2~~ | | **RÉSOLU** : suppression V1 (voir Décision D-01) | -| I-03 | Moyenne | `DataDrivenBondageItem.getBaseResistance()` scanne tous items équipés et retourne MAX difficulty car `IHasResistance` n'a pas de paramètre ItemStack. Workaround documenté mais approximatif — peut surestimer la résistance. | `v2/bondage/datadriven/DataDrivenBondageItem.java` | **Archi** : Ajouter `getBaseResistance(ItemStack, LivingEntity)` à `IHasResistance` avec default qui délègue à l'ancienne méthode. DataDrivenBondageItem override la version stack-aware. | -| I-04 | ~~Basse~~ | ~~IBondageItem.getBodyRegion() single-region~~ | | **RÉSOLU** : suppression V1 (voir Décision D-01) | -| I-05 | ~~Moyenne~~ | ~~V1 items pas @Deprecated~~ | | **RÉSOLU** : suppression V1 (voir Décision D-01) | - -### Décision D-01 — Suppression totale du système V1 + Composants data-driven - -**Décision :** Le système V1 items est supprimé entièrement. Tous les items deviennent data-driven V2. La logique complexe (shock, GPS, lock, gagging, blinding, resistance, etc.) est extraite en **composants réutilisables** déclarables dans le JSON. - -**Périmètre de suppression :** -- `IBondageItem` (interface) -- `ItemBind`, `ItemGag`, `ItemBlindfold`, `ItemCollar`, `ItemEarplugs`, `ItemMittens` (abstracts) -- `GenericBind`, `GenericGag`, `GenericBlindfold`, `GenericEarplugs`, `GenericMittens` (concrets) -- `BindVariant`, `GagVariant`, `BlindfoldVariant`, `EarplugsVariant`, `MittensVariant` (enums) -- `ItemClassicCollar`, `ItemShockCollar`, `ItemShockCollarAuto`, `ItemGpsCollar`, `ItemChokeCollar` (collars) -- `ItemHood`, `ItemMedicalGag`, `ItemBallGag3D` (combos/special) -- Registrations V1 dans `ModItems` -- `PlayerEquipment.equipInRegion()` → remplacé par `V2EquipmentManager.tryEquip()` - -**Interfaces à conserver / migrer :** -- `ILockable` — conservé, utilisé par V2 items -- `IHasResistance` — conservé, refactoré avec paramètre ItemStack (I-03) -- `IKnife` — conservé (outils, pas des bondage items) -- `IAdjustable` — à évaluer (potentiellement composant) -- `IHasBlindingEffect`, `IHasGaggingEffect` — deviennent des composants - -**Système de composants envisagé :** - -Chaque composant est une logique serveur réutilisable qu'un item data-driven peut déclarer : - -```json -{ - "type": "tiedup:bondage_item", - "display_name": "Shock Collar", - "model": "tiedup:models/gltf/shock_collar.glb", - "regions": ["NECK"], - "components": { - "lockable": true, - "shock": { "auto_interval": 200, "damage": 2.0 }, - "gps": { "safe_zone_radius": 50 }, - "gagging": { "comprehension": 0.2, "range": 10.0 }, - "blinding": { "overlay": "tiedup:textures/overlay/blindfold.png" }, - "resistance": { "base": 150 } - }, - "escape_difficulty": 5, - "pose_priority": 10 -} -``` - -Exemples de composants à extraire de la logique V1 existante : -| Composant | Source V1 | Comportement | -|-----------|-----------|-------------| -| `lockable` | `ILockable` | Lock/unlock, padlock, key matching, jam, lock resistance | -| `resistance` | `IHasResistance` | Struggle resistance, configurable base value | -| `shock` | `ItemShockCollar` | Auto-shock intervals, manual shock, damage | -| `gps` | `ItemGpsCollar` | Safe zone, zone violation detection, owner alerts | -| `gagging` | `IHasGaggingEffect` | Muffled speech, comprehension %, range limit | -| `blinding` | `IHasBlindingEffect` | Blindfold overlay, hardcore mode | -| `choking` | `ItemChokeCollar` | Air drain, darkness, slowness, non-lethal master mode | -| `adjustable` | `IAdjustable` | Tightness level, visual adjustment | - -**Ce refactor est le plus gros chantier identifié par l'audit.** Il fera l'objet d'un plan d'implémentation dédié après la fin de l'audit. - -### #4 — Network - -**Positif :** -- `AbstractClientPacket` / `AbstractPlayerSyncPacket` — bon pattern de base, handle enqueue sur main thread, retry queue pour les players pas encore loaded -- `PacketRateLimiter` — token bucket complet avec catégories (struggle, minigame, action, selfbondage, ui). Thread-safe. Bon anti-spam. -- `SyncManager` — facade centralisée pour sync inventory/state/enslavement/struggle/clothes. Pattern `sendSync()` générique propre. -- `NetworkEventHandler` — gère correctement login sync, start-tracking sync, furniture reconnection, et fix MC-262715 (stale riding state) -- `PacketSlaveAction` — bonnes validations serveur : dimension check, distance check, collar ownership check, GPS permission check -- `PacketSelfBondage` — rate limited, route correctement V2 via `handleV2SelfBondage()` avec conflict check (`isRegionOccupied` + `isRegionBlocked`) - -**Problèmes :** - -| ID | Sévérité | Constat | Fichier(s) | Proposition | -|----|----------|---------|------------|-------------| -| N-01 | Moyenne | `PacketSelfBondage.handle()` contient 5 branches V1 (`instanceof ItemBind/ItemGag/ItemBlindfold/ItemMittens/ItemEarplugs`) qui devront être supprimées avec D-01. La branche V2 (`instanceof IV2BondageItem`) restera seule. | `network/selfbondage/PacketSelfBondage.java` | **Fix D-01** : Supprimer les branches V1, ne garder que la route V2. Simplifie massivement le fichier. | -| N-02 | Moyenne | 4 packets dépendent de `ItemCollar` (V1 class) : `PacketSlaveAction`, `PacketMasterEquip`, `PacketAssignCellToCollar`, `PacketNpcCommand`. La logique collar (ownership, canShock, hasGPS) est couplée à la classe Java. | `network/slave/PacketSlaveAction.java`, `network/slave/PacketMasterEquip.java`, `network/cell/PacketAssignCellToCollar.java`, `network/personality/PacketNpcCommand.java` | **Fix D-01** : Quand ItemCollar migre vers le système composants, ces packets devront checker les composants (ex: `hasComponent("shock")`) au lieu de `instanceof ItemCollar`. | -| N-03 | Basse | `PacketSyncBindState` sync des flags d'état V1 (isTiedUp, isGagged, isBlindfolded, etc.) séparément de `PacketSyncV2Equipment` qui sync le V2 capability. Potentiellement redondant post-suppression V1 — l'état peut être dérivé du V2 equipment. | `network/sync/PacketSyncBindState.java`, `v2/bondage/network/PacketSyncV2Equipment.java` | **Archi post-D-01** : Évaluer si `PacketSyncBindState` peut être supprimé et ses flags dérivés côté client depuis V2 equipment. Réduirait le nombre de packets sync. | -| N-04 | Basse | `SyncManager.syncAllPlayersTo()` envoie 4 packets distincts par joueur (V2Equipment, BindState, Enslavement, Struggle, + Clothes si applicable). Pour un serveur avec N joueurs, un login génère ~4N packets. | `network/sync/SyncManager.java` | **Archi** : Considérer un packet bulk `PacketSyncFullState` qui combine tout en un seul envoi. Pas urgent — 4N packets est acceptable pour les tailles de serveur visées. | -| N-05 | Cosmétique | Pas de `MCABondageManager` dans le package network, mais `PacketSyncMCABondage` existe — la logique MCA bondage sync est split entre `network/sync/` et `compat/mca/`. | `network/sync/PacketSyncMCABondage.java`, `compat/mca/` | **Pas d'action** : Acceptable pour un module de compatibilité. | - -### #5 — Client Animation + GLTF - -**Positif :** -- **Architecture 3 couches propre** : Context layer (pri 40) → Item layer (pri 42) → Furniture layer (pri 43). Priorités claires, bon découplage. -- **BondageAnimationManager** : API unifiée `playAnimation/playDirect/playContext/playFurniture` pour players et NPCs. Gestion des remote players (fallback stack), pending queue pour retry, furniture grace ticks pour éviter les stuck poses. -- **GlbAnimationResolver** : Fallback chain fidèle au ARTIST_GUIDE (FullSitStruggle → SitStruggle → FullStruggle → Struggle → FullIdle → Idle). Support variants (.1, .2) avec random selection. -- **GltfAnimationApplier** : Multi-item composite animation propre. Cache par state key, skip si unchanged. `applyMultiItemV2Animation()` merge les bones de plusieurs items dans un seul AnimationBuilder. -- **ContextGlbRegistry** : Hot-reload des GLB de contexte depuis resource packs. Atomic swap pour thread safety render. -- **AnimationContextResolver** : Résolution claire de contexte (sitting → struggling → movement style → sneaking → walking → idle). Version NPC séparée. -- **GLTF pipeline (12 fichiers)** : Zéro dépendance V1. Parser, cache, skinning engine, mesh renderer, bone mapper, pose converter — tout est V2 natif. - -**Problèmes :** - -| ID | Sévérité | Constat | Fichier(s) | Proposition | -|----|----------|---------|------------|-------------| -| A-01 | Moyenne | 6 fichiers animation dépendent de `ItemBind` et `PoseType` (V1) pour déterminer le type de pose (STANDARD, DOG, HUMAN_CHAIR) et le bind mode (arms/legs/full). | `tick/AnimationTickHandler.java`, `tick/NpcAnimationTickHandler.java`, `render/PlayerArmHideEventHandler.java`, `render/PetBedRenderHandler.java`, `render/DogPoseRenderHandler.java`, `util/AnimationIdBuilder.java` | **Fix D-01** : Quand les items V1 sont supprimés, la pose type et le bind mode doivent venir du système V2 (data-driven definition ou composant). `PoseType` peut être conservé comme enum mais lu depuis `DataDrivenItemDefinition` au lieu de `ItemBind.getPoseType()`. | -| A-02 | Basse | `StaticPoseApplier` dépend de `PoseType` — applique des rotations hardcodées par pose type (V1 fallback pour quand le GLTF n'est pas disponible). | `animation/StaticPoseApplier.java` | **Évaluer D-01** : Si tous les items ont un GLB, le static pose applier devient un fallback pur. Peut être conservé comme sécurité ou supprimé. | -| A-03 | Basse | `GltfAnimationApplier` a un toggle debug F9 hardcodé qui charge un GLB spécifique (`cuffs_prototype.glb`). | `client/gltf/GltfAnimationApplier.java` (l.~350) | **Fix** : Supprimer ou mettre derrière un flag dev. Mineur. | -| A-04 | Cosmétique | Le fallback animation dans `BondageAnimationManager.tryFallbackAnimation()` contient des patterns V1 spécifiques (`_arms_`, `sit_dog_`, `kneel_dog_`). Post-D-01, ces patterns n'existeront plus. | `animation/BondageAnimationManager.java` | **Fix D-01** : Nettoyer les fallbacks V1 obsolètes. Le système GLB a sa propre fallback chain (GlbAnimationResolver). | - -### #6 — Furniture - -**Positif :** -- **Architecture data-driven exemplaire** : `FurnitureDefinition` (record immuable) + `FurnitureRegistry` (volatile atomic swap) + `FurnitureParser` + `FurnitureServerReloadListener`. Exactement le même pattern que les bondage items V2 data-driven. -- **`ISeatProvider`** : interface propre et générique — conçue pour être implémentée par des monstres aussi (ARTIST_GUIDE: "monster seat system"). Bonne anticipation. -- **`SeatDefinition`** : record immuable avec tous les champs du guide artiste (blocked regions, lockable, locked difficulty, item difficulty bonus). -- **`EntityFurniture`** : Entity simple (pas LivingEntity), synced via `IEntityAdditionalSpawnData`. Dimensions variables depuis la definition. Animation state machine (IDLE → OCCUPIED → LOCKING → STRUGGLE → UNLOCKING). Seat assignments persistés en NBT. -- **`FurniturePlacerItem`** : singleton item avec NBT ID, snap-to-wall, floor-only. Même pattern que `DataDrivenBondageItem`. -- **`FurnitureAnimationContext`** : Conversion GLB → KeyframeAnimation avec bones sélectifs (blocked regions only). S'intègre proprement avec la furniture layer (pri 43) de BondageAnimationManager. -- **`FurnitureGltfData`** : Parsing dédié qui sépare furniture armature des Player_* seat skeletons dans un seul GLB. Fidèle à l'ARTIST_GUIDE. -- **Packets** : Rate limited, distance checks, permission checks (collar ownership pour forcemount). -- **Reconnection robuste** : `NetworkEventHandler.handleFurnitureReconnection()` restaure les joueurs locked dans un seat après déconnexion, avec teleport si le meuble n'existe plus. - -**Problèmes :** - -| ID | Sévérité | Constat | Fichier(s) | Proposition | -|----|----------|---------|------------|-------------| -| F-01 | Moyenne | `EntityFurniture` et `PacketFurnitureForcemount` dépendent de `ItemCollar` (V1) pour vérifier collar ownership avant forcemount. | `v2/furniture/EntityFurniture.java`, `v2/furniture/network/PacketFurnitureForcemount.java` | **Fix D-01** : Quand ItemCollar migre vers composants, le check ownership doit utiliser le composant `lockable` ou `collar` au lieu de `instanceof ItemCollar`. | -| F-02 | Basse | `FurnitureAnimationContext.create()` log "V1: skeleton parsing not yet implemented" quand `seatSkeleton` est null. Si le GLB n'a pas de skeleton data parsé, l'animation silencieusement ne se joue pas. | `v2/furniture/client/FurnitureAnimationContext.java` | **Évaluer** : Vérifier que le parser GLB furniture extrait toujours le skeleton. Si oui, le fallback est juste un safety net. Sinon, c'est un bug silencieux. | - -**Verdict : Le système furniture est le plus propre du mod.** Zéro dette architecturale, fidèle au guide artiste, extensible (monster seats prêts). Les deux constats sont mineurs — un couplage V1 qui part avec D-01 et un fallback debug à vérifier. - -### #7 — Entities + AI - -**Hiérarchie d'héritage :** - -``` -PathfinderMob - └─ AbstractTiedUpNpc (1281 lignes, ~100 méthodes) — implements IRestrainable, IAnimatedPlayer, IV2EquipmentHolder - ├─ EntityDamsel (834 lignes) — capturable NPC, personality, dialogue, inventory - │ ├─ EntityDamselShiny — variante rare - │ └─ EntityLaborGuard — garde de prison - └─ EntityKidnapper (2039 lignes, ~170 méthodes) — implements ICaptor, IDialogueSpeaker - └─ EntityKidnapperElite - ├─ EntityKidnapperMerchant — marchand neutre/hostile - ├─ EntityKidnapperArcher — attaque à distance - ├─ EntitySlaveTrader — boss de camp - ├─ EntityMaid — servante du trader - └─ EntityMaster (1192 lignes) — pet play system -``` - -**Positif :** -- **Composant-based decomposition pour Damsel** : `DamselBondageManager`, `DamselPersonalitySystem`, `DamselInventoryManager`, `DamselAIController`, `DamselAnimationController`, `DamselAppearance`, `NpcEquipmentManager`, `NpcCaptivityManager` — 8 components avec interfaces host (`IBondageHost`, `IAIHost`, `IAnimationHost`, etc.). Bonne intention. -- **Composant-based pour Kidnapper** : `KidnapperAggressionSystem`, `KidnapperAlertManager`, `KidnapperAppearance`, `KidnapperCaptiveManager`, `KidnapperCellManager`, `KidnapperCampManager`, `KidnapperStateManager`, `KidnapperSaleManager`, `KidnapperTargetSelector`, `KidnapperDataSerializer` — 10 components avec interfaces host. Très granulaire. -- **AI goals bien séparés** : 80+ goals dédiés par type de NPC. Chaque goal est une classe autonome avec une seule responsabilité (KidnapperCaptureGoal, MasterDogwalkGoal, NpcFarmCommandGoal, etc.). -- **V2 equipment intégré** : `AbstractTiedUpNpc` implémente `IV2EquipmentHolder`, utilise `V2BondageEquipment` directement. Les NPCs sont déjà sur le système V2. -- **State machines Kidnapper** : `KidnapperState` enum avec états clairs (IDLE, HUNTING, CAPTURING, FLEEING, etc.). -- **Master NPC complet** : pet play system avec task manager, state machine, punishment, dogwalk, furniture interaction — complexe mais fonctionnel. - -**Problèmes :** - -| ID | Sévérité | Constat | Fichier(s) | Proposition | -|----|----------|---------|------------|-------------| -| E-01 | Haute | **EntityKidnapper = 2039 lignes**, la plus grosse classe du mod. Malgré la décomposition en 10 components, la classe reste un god class. Elle mélange : ICaptor impl, targeting, capture equipment, sale system, job system, camp system, cell integration, alert system, NBT serialization, display name, dialogue, et des dizaines de getters/helpers. | `entities/EntityKidnapper.java` | **Archi** : Continuer la décomposition. Candidates : extraire le système de vente (`startSale`/`completeSale`/`cancelSale`/`abandonCaptive`) dans un component dédié, extraire le dialogue, extraire le ciblage. Objectif : ramener la classe sous 800 lignes. | -| E-02 | Haute | **AbstractTiedUpNpc = 1281 lignes** avec ~100 méthodes. Même pattern que PlayerBindState (S-01) — c'est une façade de délégation vers les components, mais doit aussi implémenter IRestrainable (30+ méthodes) directement. | `entities/AbstractTiedUpNpc.java` | **Archi** : La taille vient surtout de l'implémentation de IRestrainable. Évaluer si les méthodes bondage peuvent être déléguées à `DamselBondageManager` via un pattern `default` sur IRestrainable (mais IRestrainable est une interface, pas une classe — limité). Ou accepter la taille comme coût de l'implémentation multi-interface. | -| E-03 | Moyenne | **24 fichiers entities** dépendent de V1 item classes (`ItemBind`, `ItemCollar`, `PoseType`, `BindVariant`, etc.). C'est le package le plus impacté par D-01. | 24 fichiers (voir liste grep) | **Fix D-01** : Migration bulk. Les `instanceof ItemBind` deviennent `instanceof IV2BondageItem`, les `ItemCollar` checks deviennent des component checks. `PoseType` et `BindVariant` sont remplacés par des propriétés data-driven. | -| E-04 | Moyenne | **Héritage profond** : EntityMaid → EntityKidnapperElite → EntityKidnapper → AbstractTiedUpNpc → PathfinderMob. 5 niveaux. EntityMaid et EntitySlaveTrader héritent de toute la logique kidnapper (capture, targeting, sale) alors qu'ils n'utilisent pas tout. | `entities/EntityMaid.java`, `entities/EntitySlaveTrader.java` | **Archi** : Envisager une refactorisation vers composition plutôt qu'héritage. La Maid n'est PAS un kidnapper — elle ne devrait pas hériter de `canCapture()`, `getCaptureBindTime()`, etc. Long terme : AbstractTiedUpNpc → EntityDamsel (passive) / EntityKidnapper (hostile), et les autres types composent leurs comportements. Pas urgent mais dette croissante. | -| E-05 | Basse | `EntityDamsel` et `EntityKidnapper` ont des hiérarchies de host interfaces parallèles : `damsel/components/IBondageHost`, `damsel/components/IAIHost` vs `kidnapper/components/IAIHost`, `kidnapper/components/ICaptiveHost`, etc. Certaines pourraient être unifiées. | `entities/damsel/components/*.java`, `entities/kidnapper/components/*.java` | **Pas d'action immédiate** : Les interfaces host sont des contrats internes de chaque sous-arbre. Les unifier créerait un couplage horizontal. Acceptable tel quel. | -| E-06 | Basse | `EntityMaster` (1192 lignes) contient le pet play system complet. Components `MasterPetManager`, `MasterTaskManager`, `MasterStateManager` existent mais la classe orchestre encore beaucoup de logique. | `entities/EntityMaster.java` | **Archi** : Même recommandation que E-01 — continuer la décomposition. Moins urgent car le système est plus cohérent (une seule responsabilité : pet play). | - -### #8 — Events - -**27 handlers, 5722 lignes, 8 domaines** (camp, captivity, combat, interaction, lifecycle, restriction, system). - -**Positif :** Bonne séparation par domaine. La plupart des handlers sont focalisés (85-200 lignes). - -| ID | Sévérité | Constat | Proposition | -|----|----------|---------|-------------| -| EV-01 | Moyenne | `RestraintTaskTickHandler` (675 lignes, 12 @SubscribeEvent) — consolide tous les ticks restraint. | Découper par type de tâche (tying, untying, force-feeding, shock checks). | -| EV-02 | Moyenne | `BondageItemRestrictionHandler` (544 lignes, 12 @SubscribeEvent) — consolide toutes les restrictions. | Découper par type de restriction (legs, arms, gags, etc.). | -| EV-03 | Moyenne | 7/27 handlers importent des classes V1 (`ItemBind`, `ItemCollar`, `ItemGag`, `IKnife`, `ILockable`). | **Fix D-01** : Migrer vers V2 checks (composants). | - -### #9 — Dialogue + Personality - -**31 fichiers, 7625 lignes.** Dialogue 100% data-driven (JSON par personality × speaker type). Personality enum-based (11 types) avec state machine (needs, mood, commands). - -**Positif :** Pipeline de chargement JSON propre (default → personality override → speaker-type). 18 catégories de dialogue. Dépendance unidirectionnelle (dialogue → personality, pas l'inverse). Seulement 3 fichiers importent du V1. - -| ID | Sévérité | Constat | Proposition | -|----|----------|---------|-------------| -| DI-01 | Moyenne | 6 god classes dans dialogue/ (EntityDialogueManager 622l, ConversationManager 564l, GagTalkManager 557l, DialogueLoader 469l, DialogueBridge 463l, DialogueManager 428l). | Acceptable pour la complexité du système. `DialogueBridge` (mapping legacy → new) peut être supprimé après D-01. | -| DI-02 | Basse | `PersonalityState` (709 lignes) — god class conteneur d'état NPC (needs, mood, commands, jobs, home). | Continuer la décomposition si ça grossit. OK pour l'instant. | -| DI-03 | Basse | 3 fichiers importent V1 (`GagTalkManager` → ItemGag, `ToolMode` → ItemBind, `PetRequestManager` → BindVariant). | **Fix D-01** : Migrer. | - -### #10 — Cells / Prison / Blocks - -**41 fichiers, 13329 lignes.** Système SavedData massif (CellRegistryV2, PrisonerManager, CampOwnership, ConfiscatedInventoryRegistry). - -**Positif :** Architecture SavedData correcte. Spatial indexing dans CellRegistryV2. Séparation services (PrisonerService, ItemService, BondageService). - -| ID | Sévérité | Constat | Proposition | -|----|----------|---------|-------------| -| CP-01 | Haute | `PrisonerService` (1058 lignes) — plus grosse classe du package, gère tout le lifecycle prisonnier. | Décomposer : labor, ransom, confiscation pourraient être des services séparés. | -| CP-02 | Moyenne | `MarkerBlockEntity` (1146 lignes) — god class block entity, gère spawning + teleportation + cell deletion. | Extraire la logique spawning et teleportation dans des helpers. | -| CP-03 | Moyenne | `BondageItemBlockEntity` utilise 6 imports V1 (ItemBind, ItemGag, ItemBlindfold, ItemCollar, ItemEarplugs, GenericClothes) pour valider les items dans les trap blocks. | **Fix D-01** : Remplacer par `instanceof IV2BondageItem` + component checks. | -| CP-04 | Basse | `CellRegistryV2` (903 lignes) — gros mais justifié par les index spatiaux. | Acceptable. | - -### #11 — Compat (MCA + Wildfire) - -**24 fichiers, 6536 lignes.** MCA très couplé (21 fichiers, 5 sous-systèmes). Wildfire léger (3 fichiers, rendu only). - -| ID | Sévérité | Constat | Proposition | -|----|----------|---------|-------------| -| CO-01 | Haute | `MCAKidnappedAdapter` (907 lignes) — implémente IRestrainable complet pour les villagers MCA. God class + dépend de V1 items. | **Fix D-01** : Migrer V1 → V2. Décomposer en components comme AbstractTiedUpNpc. | -| CO-02 | Moyenne | `WildfireDamselLayer` (988 lignes) — rendu physique très complexe. | Acceptable pour un système de rendu physique. Pas urgent. | -| CO-03 | Basse | MCA compat utilise WeakHashMap et reflection pour détection — bon pattern de découplage. | Pas d'action. | - -### #12 — Util + Commands + Worldgen - -**55 fichiers, 15475 lignes.** - -| ID | Sévérité | Constat | Proposition | -|----|----------|---------|-------------| -| UC-01 | Haute | `BondageSubCommand` (1232 lignes) — monolithique, contient 16 sous-commandes (tie, untie, gag, collar, etc.) dans un seul fichier. | Découper : TieCommands, GagCommands, CollarCommands, etc. | -| UC-02 | Haute | `RoomTheme` (1368 lignes) — config hardcodée de palettes de blocs pour worldgen. | **Archi** : Externaliser en data-driven (JSON). C'est exactement le type de contenu qui devrait être configurable. | -| UC-03 | Moyenne | 7 fichiers importent V1 items (commands + RestraintApplicator + MCA adapter + HangingCagePiece). | **Fix D-01** : Migrer. | -| UC-04 | Basse | `NPCCommand` (764 lignes) — gros mais focalisé sur le spawning/state NPC. | Acceptable, pourrait split par entity type. | - ---- - -## Bilan Final - -### Statistiques - -- **12/12 systèmes audités** -- **744 classes, 233k lignes** analysées -- **38 constats** documentés (+ 4 résolus par D-01) -- **1 décision architecturale majeure** (D-01 : suppression V1 + composants data-driven) - -### Classement par santé - -| Rang | Système | Verdict | Problèmes | -|------|---------|---------|-----------| -| 1 | Furniture | Exemplaire | 2 mineurs | -| 2 | Animation + GLTF | Excellent | Résidus V1 seulement | -| 3 | Network | Solide | Résidus V1 seulement | -| 4 | Dialogue + Personality | Bon | God classes acceptables | -| 5 | Events | Bon | 2 handlers trop gros | -| 6 | Core | Dette technique | i18n, config triple | -| 7 | State | Croissance organique | God class, thread safety | -| 8 | Cells / Prison | Fonctionnel mais lourd | 11 god classes | -| 9 | Compat | Fonctionnel mais couplé | MCA adapter 907l | -| 10 | Util / Commands / Worldgen | Fonctionnel | BondageSubCmd 1232l, RoomTheme 1368l | -| 11 | Entities + AI | Riche mais massif | 2039l god class, héritage 5 niveaux | -| 12 | Items V1/V2 | **Point critique** | **D-01 : suppression totale V1** | - -### Priorités de correction - -| Priorité | Chantier | Impact | -|----------|----------|--------| -| **P0** | **D-01 : Suppression V1 + composants data-driven** | Élimine la dette #1 du mod. Impacte ~60 fichiers. Requiert un plan dédié. | -| **P1** | C-01 : i18n SystemMessageManager | Requis pour toute traduction du mod. | -| **P1** | UC-02 : RoomTheme → data-driven | 1368 lignes hardcodées de config worldgen. | -| **P2** | E-01/E-02 : Décomposition EntityKidnapper/AbstractTiedUpNpc | 2039 + 1281 lignes. Améliore la maintenabilité entities. | -| **P2** | CP-01 : Décomposition PrisonerService | 1058 lignes. | -| **P2** | UC-01 : Split BondageSubCommand | 1232 lignes en 1 fichier. | -| **P3** | S-02 : Encapsuler MovementState | 8 champs publics mutés directement. | -| **P3** | S-05 : Thread safety cohérente | 3 stratégies sans cohérence dans PlayerBindState. | -| **P3** | C-02 : Unifier Config/GameRules | 25 settings triplés. | -| **P4** | Renommages (C-05 DamselRenderer, C-06 FQCNs) et cleanups cosmétiques. | - -### D-01 Phase 1 — Suivi implémentation - -**Branche :** `feature/d01-component-system` (17 commits, build clean) - -**Review adversariale :** 3 critiques + 5 hauts trouvés et corrigés. - -**Problèmes notés (non bloquants, à traiter lors de la migration Phase 2) :** - -| ID | Constat | Action | -|----|---------|--------| -| SMELL-002 | `GaggingComponent` n'a aucun consommateur — `GagTalkManager` lit `GagMaterial.getComprehension()`, pas le composant. | Lors de la migration Phase 2, faire pointer `GagTalkManager` vers le composant pour les items data-driven. | -| SMELL-003 | Duplication sémantique entre le champ top-level `lockable` (boolean) et le composant `LockableComponent`. Un item doit configurer les deux pour un lock complet. | Lors de la migration Phase 2, le champ `lockable` devrait être dérivé de la présence du composant `lockable`. | -| NOTE-003 | `test_component_gag.json` est dans les resources de production — visible par les joueurs. | Supprimer ou déplacer avant release. OK pour le dev. | \ No newline at end of file diff --git a/docs/plans/D01-branch-A-bridge-utilities.md b/docs/plans/D01-branch-A-bridge-utilities.md deleted file mode 100644 index 12dd9b1..0000000 --- a/docs/plans/D01-branch-A-bridge-utilities.md +++ /dev/null @@ -1,284 +0,0 @@ -# D-01 Branch A : Bridge Utilities - -> **Prérequis :** Phase 1 (component system) mergée dans develop. -> **Branche :** `feature/d01-branch-a-bridge` -> **Objectif :** Créer les utilitaires V2 qui remplacent la logique V1, SANS supprimer de code V1. À la fin de cette branche, le mod compile, les items V1 et V2 coexistent, le struggle fonctionne pour les deux, et les nouveaux helpers sont prêts pour la migration des consommateurs. - ---- - -## Décisions actées - -- **Stack size** : Stacks de 1 pour tout. Régression acceptée. -- **Save compat** : Breaking change. Pas de migration. Mod en alpha. -- **Résistance** : Config-driven via `resistanceId`, pas hardcodé en JSON. -- **Comprehension/range gags** : Config-driven via `gagMaterial`, délègue à ModConfig au runtime. -- **IHasGaggingEffect/IHasBlindingEffect** : DataDrivenBondageItem les implémente en checkant les composants. - ---- - -## Tâches - -### A1. Modifier ResistanceComponent — config-driven - -**Fichier :** `src/main/java/com/tiedup/remake/v2/bondage/component/ResistanceComponent.java` - -Actuellement stocke un `int baseResistance` hardcodé. Doit stocker un `String resistanceId` et déléguer à `SettingsAccessor.getBindResistance(resistanceId)` au runtime. - -- Remplacer le champ `baseResistance` par `resistanceId` (String) -- `fromJson()` : parse `"id"` au lieu de `"base"` — `"resistance": {"id": "rope"}` -- `getBaseResistance()` : `return SettingsAccessor.getBindResistance(resistanceId);` -- Garder un fallback `"base"` pour backward compat avec test_component_gag.json (ou le mettre à jour) - -**Référence :** `SettingsAccessor.getBindResistance(String)` dans `core/SettingsAccessor.java` — normalise les clés et lit depuis ModConfig. - ---- - -### A2. Modifier GaggingComponent — config-driven + GagMaterial - -**Fichier :** `src/main/java/com/tiedup/remake/v2/bondage/component/GaggingComponent.java` - -Actuellement stocke `comprehension` et `range` hardcodés. Doit stocker un `String material` et déléguer à `GagMaterial`/ModConfig au runtime. - -- Ajouter champ `@Nullable String material` -- `fromJson()` : parse `"material"` — `"gagging": {"material": "ball"}` -- `getComprehension()` : si material != null → `GagMaterial.valueOf(material).getComprehension()` (lit ModConfig). Sinon → fallback au champ hardcodé (compat). -- `getRange()` : idem via `GagMaterial.valueOf(material).getTalkRange()` -- `getMaterial()` : expose le `GagMaterial` enum pour `GagTalkManager` -- Garder les champs `comprehension`/`range` comme overrides optionnels (si présents dans JSON, ils prennent priorité sur le material) - -**Référence :** `GagMaterial` enum dans `items/base/GagVariant.java` ou `util/GagMaterial.java`. - ---- - -### A3. Ajouter `appendTooltip()` à IItemComponent + ComponentHolder - -**Fichiers :** -- `src/main/java/com/tiedup/remake/v2/bondage/component/IItemComponent.java` -- `src/main/java/com/tiedup/remake/v2/bondage/component/ComponentHolder.java` -- `src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenBondageItem.java` - -Ajouter hook tooltip pour que chaque composant contribue des lignes : - -```java -// IItemComponent -default void appendTooltip(ItemStack stack, @Nullable Level level, List tooltip, TooltipFlag flag) {} - -// ComponentHolder -public void appendTooltip(ItemStack stack, @Nullable Level level, List tooltip, TooltipFlag flag) { - for (IItemComponent c : components.values()) c.appendTooltip(stack, level, tooltip, flag); -} -``` - -Dans `DataDrivenBondageItem.appendHoverText()` : appeler `holder.appendTooltip(...)`. - -Implémenter `appendTooltip` dans chaque composant existant : -- `LockableComponent` : affiche "Locked" / "Lockable" -- `ResistanceComponent` : affiche la résistance en advanced mode (F3+H) -- `GaggingComponent` : affiche le type de gag -- `BlindingComponent` : rien (pas d'info utile) -- `ShockComponent` : affiche "Shock: Manual" ou "Shock: Auto (Xs)" -- `GpsComponent` : affiche "GPS Tracking" + zone radius -- `ChokingComponent` : affiche "Choking Effect" -- `AdjustableComponent` : rien (ajustement est visuel) - ---- - -### A4. Champ `pose_type` + PoseTypeHelper - -**Fichiers :** -- `src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenItemDefinition.java` — ajouter `@Nullable String poseType` -- `src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenItemParser.java` — parser `"pose_type"` -- Créer `src/main/java/com/tiedup/remake/v2/bondage/PoseTypeHelper.java` - -```java -public static PoseType getPoseType(ItemStack stack) { - // V2: read from data-driven definition - DataDrivenItemDefinition def = DataDrivenItemRegistry.get(stack); - if (def != null && def.poseType() != null) { - try { return PoseType.valueOf(def.poseType()); } - catch (IllegalArgumentException e) { return PoseType.STANDARD; } - } - // V1 fallback: instanceof ItemBind - if (stack.getItem() instanceof ItemBind bind) { - return bind.getPoseType(); - } - return PoseType.STANDARD; -} -``` - -**Note mixin :** Les mixins (`MixinPlayerModel`, `MixinCamera`, etc.) appellent `itemBind.getPoseType()`. Ils devront migrer vers `PoseTypeHelper.getPoseType(stack)` en Branch C. Le helper doit être dans un package chargé tôt — `v2/bondage/` est OK car le mod est chargé avant les mixins client. - ---- - -### A5. BindModeHelper - -**Fichier :** Créer `src/main/java/com/tiedup/remake/v2/bondage/BindModeHelper.java` - -Méthodes statiques pures NBT (clé `"bindMode"`) : -- `hasArmsBound(ItemStack)` → true si mode "arms" ou "full" -- `hasLegsBound(ItemStack)` → true si mode "legs" ou "full" -- `getBindModeId(ItemStack)` → "full", "arms", ou "legs" -- `cycleBindModeId(ItemStack)` → full→arms→legs→full, retourne le nouveau mode -- `getBindModeTranslationKey(ItemStack)` → clé i18n -- `isBindItem(ItemStack)` → true si l'item a la région ARMS dans sa definition V2, OU est `instanceof ItemBind` - -**Référence :** `ItemBind.java` lignes 64-160 pour les méthodes statiques existantes. - ---- - -### A6. CollarHelper (complet) - -**Fichier :** Créer `src/main/java/com/tiedup/remake/v2/bondage/CollarHelper.java` - -Extraire TOUTES les méthodes NBT de `ItemCollar` (1407 lignes) + 5 sous-classes en méthodes statiques. Sections : - -**Ownership :** -- `isOwner(ItemStack, Player)`, `isOwner(ItemStack, UUID)` -- `getOwners(ItemStack)` → Set -- `addOwner(ItemStack, UUID, String name)`, `removeOwner(ItemStack, UUID)` -- `getBlacklist(ItemStack)`, `addToBlacklist(ItemStack, UUID)`, `removeFromBlacklist(ItemStack, UUID)` -- `getWhitelist(ItemStack)`, `addToWhitelist(ItemStack, UUID)`, `removeFromWhitelist(ItemStack, UUID)` - -**Collar features :** -- `isCollar(ItemStack)` → check OwnershipComponent presence OR instanceof ItemCollar -- `getNickname(ItemStack)`, `setNickname(ItemStack, String)` -- `isKidnappingModeEnabled(ItemStack)`, `setKidnappingModeEnabled(ItemStack, boolean)` -- `getCellId(ItemStack)`, `setCellId(ItemStack, UUID)` -- `shouldTieToPole(ItemStack)`, `setShouldTieToPole(ItemStack, boolean)` -- `shouldWarnMasters(ItemStack)`, `setShouldWarnMasters(ItemStack, boolean)` -- `isBondageServiceEnabled(ItemStack)`, `setBondageServiceEnabled(ItemStack, boolean)` -- `getServiceSentence(ItemStack)`, `setServiceSentence(ItemStack, String)` - -**Shock :** -- `canShock(ItemStack)` → check ShockComponent presence OR instanceof ItemShockCollar -- `isPublic(ItemStack)`, `setPublic(ItemStack, boolean)` -- `getShockInterval(ItemStack)` → depuis ShockComponent ou ItemShockCollarAuto - -**GPS :** -- `hasGPS(ItemStack)` → check GpsComponent presence OR instanceof ItemGpsCollar -- `hasPublicTracking(ItemStack)`, `setPublicTracking(ItemStack, boolean)` -- `getSafeSpots(ItemStack)`, `addSafeSpot(ItemStack, ...)`, `removeSafeSpot(ItemStack, int)` -- `isActive(ItemStack)`, `setActive(ItemStack, boolean)` - -**Choke :** -- `isChokeCollar(ItemStack)` → check ChokingComponent presence OR instanceof ItemChokeCollar -- `isChoking(ItemStack)`, `setChoking(ItemStack, boolean)` -- `isPetPlayMode(ItemStack)`, `setPetPlayMode(ItemStack, boolean)` - -**Alert suppression :** -- `runWithSuppressedAlert(Runnable)` — ThreadLocal, délègue à `ItemCollar` pendant la coexistence -- `isRemovalAlertSuppressed()` → lit le ThreadLocal - -**Référence :** `ItemCollar.java` (1407 lignes), `ItemShockCollar.java` (133 lignes), `ItemGpsCollar.java` (369 lignes), `ItemChokeCollar.java` (154 lignes), `ItemShockCollarAuto.java` (58 lignes). - ---- - -### A7. OwnershipComponent (complet) - -**Fichier :** Créer `src/main/java/com/tiedup/remake/v2/bondage/component/OwnershipComponent.java` -**Modifier :** `ComponentType.java` — ajouter `OWNERSHIP` - -JSON : `"ownership": {}` - -Lifecycle hooks : -- `onEquipped(stack, entity)` : enregistrer dans CollarRegistry (extraire de `ItemCollar.registerCollarInRegistry`). **Note :** le owner initial (le player qui equip) n'est pas dans la signature `onEquipped(stack, entity)`. Options : lire le owner depuis le NBT du stack (déjà écrit par l'interaction flow), ou passer par un tag temporaire. -- `onUnequipped(stack, entity)` : alerter les owners (si pas supprimé via ThreadLocal), désenregistrer du CollarRegistry, reset auto-shock timer. -- `appendTooltip` : nickname, lock status, kidnapping mode, cell ID, bondage service, shock status, GPS status. -- `blocksUnequip` : si locked via ILockable. -- **Override `dropLockOnUnlock()`** : retourner false pour les collars (pas de padlock drop). Note : ceci doit être sur DataDrivenBondageItem, pas sur le composant (ILockable est sur l'Item, pas sur le composant). → DataDrivenBondageItem override `dropLockOnUnlock()` quand OwnershipComponent est présent. - ---- - -### A8. TyingInteractionHelper + DataDrivenBondageItem extensions - -**Fichier :** Créer `src/main/java/com/tiedup/remake/v2/bondage/TyingInteractionHelper.java` -**Modifier :** `src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenBondageItem.java` - -Extraire le flow tying de `ItemBind.interactLivingEntity()` dans `TyingInteractionHelper` : -- Accepte `(ServerPlayer player, LivingEntity target, ItemStack stack, InteractionHand hand)` -- Crée TyingPlayerTask, gère la progress bar, consume l'item, equipe via V2 - -Dans `DataDrivenBondageItem` : -- `use()` : si regions contient ARMS → shift+click cycle bind mode (son + message action bar). Server-side only. -- `interactLivingEntity()` : routing par région : - - ARMS → `TyingInteractionHelper` (tying task flow) - - NECK → collar equip flow (add owner, register CollarRegistry, play sound) — extraire de `ItemCollar.interactLivingEntity()` - - MOUTH/EYES/EARS/HANDS → instant equip (existant via AbstractV2BondageItem) - ---- - -### A9. Réécrire StruggleBinds/StruggleCollar pour V2 - -**Fichiers :** -- `src/main/java/com/tiedup/remake/state/struggle/StruggleBinds.java` -- `src/main/java/com/tiedup/remake/state/struggle/StruggleCollar.java` - -`StruggleBinds.canStruggle()` : -- Actuellement : `instanceof ItemBind` → rejette V2 -- Fix : accepter `instanceof IV2BondageItem` avec region ARMS, OU `instanceof ItemBind` -- Résistance : si V2 → `ResistanceComponent.getBaseResistance()`. Si V1 → existant via `IHasResistance`. - -`StruggleCollar` : -- Actuellement : `instanceof ItemCollar` → rejette V2 -- Fix : accepter items avec `OwnershipComponent` (via `CollarHelper.isCollar(stack)`) -- Résistance collar : via `ResistanceComponent` ou `IHasResistance` - ---- - -### A10. DataDrivenBondageItem implémente IHasGaggingEffect/IHasBlindingEffect - -**Fichier :** `src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenBondageItem.java` - -Ajouter `implements IHasGaggingEffect, IHasBlindingEffect` sur la classe. Les 7+ call sites qui font `instanceof IHasGaggingEffect` continueront de fonctionner. - -**Problème :** Ces interfaces sont des markers (pas de méthodes). La simple présence de l'interface signifie "a l'effet". Mais `DataDrivenBondageItem` est un singleton — TOUS les items data-driven auront ces interfaces. - -**Solution :** Ne PAS implémenter les interfaces marker sur la classe. À la place, lors de la migration Branch C, convertir les call sites vers des component checks. C'est plus propre. - -→ **A10 annulé.** Les call sites migreront en Branch C vers `DataDrivenBondageItem.getComponent(stack, GAGGING, ...) != null`. - ---- - -### A11. Fix PacketSelfBondage — routing par région - -**Fichier :** `src/main/java/com/tiedup/remake/network/selfbondage/PacketSelfBondage.java` - -Dans `handleV2SelfBondage()`, ajouter du routing par région : - -```java -Set regions = v2Item.getOccupiedRegions(stack); -if (regions.contains(BodyRegionV2.NECK)) { - // Cannot self-collar - return; -} -if (regions.contains(BodyRegionV2.ARMS)) { - // Tying task (existing flow) - handleV2SelfBind(player, stack, v2Item, state); -} else { - // Accessories: instant equip (no tying delay) - handleV2SelfAccessory(player, stack, v2Item, state); -} -``` - -Créer `handleV2SelfAccessory()` basé sur le pattern V1 `handleSelfAccessory()` (instant equip, swap si déjà équipé, locked check). - -### A12. NPC speed reduction - -**Fichier :** À déterminer (composant ou event handler) - -`ItemBind.onEquipped()` appelle `RestraintEffectUtils.applyBindSpeedReduction(entity)` pour les NPCs. Cette logique doit survivre à la migration. - -**Option :** Ajouter dans `DataDrivenBondageItem.onEquipped()` (après le component dispatch) un check : si entity n'est PAS un Player ET l'item a la région ARMS → appeler `RestraintEffectUtils.applyBindSpeedReduction(entity)`. - ---- - -## Vérification - -- [ ] `make build` — compilation clean -- [ ] Struggle fonctionne avec V1 items (ropes, chain, collar) -- [ ] Struggle fonctionne avec V2 items (test_component_gag.json ou nouveau test item) -- [ ] Self-bondage V2 : ARMS → tying delay, MOUTH → instant, NECK → rejeté -- [ ] Tooltips : composants contribuent des lignes -- [ ] PoseTypeHelper résout V1 (ItemBind) et V2 (definition.poseType) -- [ ] CollarHelper.isOwner() fonctionne sur V1 ET V2 collars -- [ ] MCP reindex après la branche diff --git a/docs/plans/D01-branch-B-json-definitions.md b/docs/plans/D01-branch-B-json-definitions.md deleted file mode 100644 index 8436bcf..0000000 --- a/docs/plans/D01-branch-B-json-definitions.md +++ /dev/null @@ -1,128 +0,0 @@ -# D-01 Branch B : 46 JSON Item Definitions - -> **Prérequis :** Branch A (bridge utilities) mergée. -> **Branche :** `feature/d01-branch-b-definitions` -> **Objectif :** Créer les définitions JSON data-driven pour les 46 items V1. Chaque item V1 a un équivalent JSON. Les valeurs sont config-driven (pas hardcodées). À la fin, les 46 items V2 existent en parallèle des V1. - ---- - -## Extensions parser nécessaires - -Avant de créer les JSON, vérifier que le parser supporte : -- `pose_type` (string, optionnel) — ajouté en Branch A4 -- `can_attach_padlock` (boolean, default true) — **à ajouter si pas fait en A** -- GaggingComponent `material` — ajouté en Branch A2 -- ResistanceComponent `id` — ajouté en Branch A1 - -**Fichiers :** `DataDrivenItemParser.java`, `DataDrivenItemDefinition.java` - ---- - -## Items à créer - -Tous dans `src/main/resources/data/tiedup/tiedup_items/`. - -### Binds (16 fichiers) - -| Fichier | display_name | resistance id | pose_type | Notes | -|---------|-------------|---------------|-----------|-------| -| `ropes.json` | Ropes | rope | STANDARD | supports_color: true | -| `armbinder.json` | Armbinder | armbinder | STANDARD | | -| `dogbinder.json` | Dogbinder | armbinder | DOG | movement_style: crawl | -| `chain.json` | Chains | chain | STANDARD | | -| `ribbon.json` | Ribbon | ribbon | STANDARD | supports_color: true | -| `slime.json` | Slime Bind | slime | STANDARD | can_attach_padlock: false | -| `vine_seed.json` | Vine Bind | vine | STANDARD | can_attach_padlock: false | -| `web_bind.json` | Web Bind | web | STANDARD | can_attach_padlock: false | -| `shibari.json` | Shibari | rope | STANDARD | supports_color: true | -| `leather_straps.json` | Leather Straps | armbinder | STANDARD | | -| `medical_straps.json` | Medical Straps | armbinder | STANDARD | | -| `beam_cuffs.json` | Beam Cuffs | chain | STANDARD | | -| `duct_tape.json` | Duct Tape | tape | STANDARD | can_attach_padlock: false | -| `straitjacket.json` | Straitjacket | straitjacket | STRAITJACKET | | -| `wrap.json` | Wrap | wrap | WRAP | | -| `latex_sack.json` | Latex Sack | latex_sack | LATEX_SACK | | - -Tous ont : regions `["ARMS"]`, lockable component (sauf organiques), resistance component avec `id`. - -**Référence :** `BindVariant.java` pour les registry names, pose types, supports_color. `ModConfig.ServerConfig` pour les resistance IDs (lignes 413-428). - -### Gags (19 fichiers) - -| Fichier | display_name | gag material | Notes | -|---------|-------------|-------------|-------| -| `cloth_gag.json` | Cloth Gag | cloth | | -| `ropes_gag.json` | Rope Gag | cloth | | -| `cleave_gag.json` | Cleave Gag | cloth | | -| `ribbon_gag.json` | Ribbon Gag | cloth | supports_color: true | -| `ball_gag.json` | Ball Gag | ball | supports_color: true | -| `ball_gag_strap.json` | Ball Gag (Strap) | ball | | -| `tape_gag.json` | Tape Gag | tape | can_attach_padlock: false | -| `wrap_gag.json` | Wrap Gag | tape | | -| `slime_gag.json` | Slime Gag | tape | can_attach_padlock: false | -| `vine_gag.json` | Vine Gag | tape | can_attach_padlock: false | -| `web_gag.json` | Web Gag | tape | can_attach_padlock: false | -| `panel_gag.json` | Panel Gag | panel | | -| `beam_panel_gag.json` | Beam Panel Gag | panel | | -| `chain_panel_gag.json` | Chain Panel Gag | panel | | -| `latex_gag.json` | Latex Gag | latex | | -| `tube_gag.json` | Tube Gag | stuffed | | -| `bite_gag.json` | Bite Gag | bite | | -| `sponge_gag.json` | Sponge Gag | sponge | | -| `baguette_gag.json` | Baguette Gag | baguette | | - -Tous ont : regions `["MOUTH"]`, gagging component avec material, lockable, resistance `{"id": "gag"}`, adjustable. - -**Référence :** `GagVariant.java` pour les materials et registry names. `ModConfig.ServerConfig.gagComprehension/gagRange` pour les valeurs runtime. - -### Blindfolds (2 fichiers) - -| Fichier | display_name | -|---------|-------------| -| `classic_blindfold.json` | Blindfold | -| `blindfold_mask.json` | Blindfold Mask | - -Regions `["EYES"]`, components : blinding, lockable, resistance `{"id": "blindfold"}`, adjustable. - -### Earplugs (1 fichier) - -`classic_earplugs.json` — regions `["EARS"]`, lockable, resistance `{"id": "blindfold"}` (partage la résistance blindfold dans V1). - -### Mittens (1 fichier) - -`leather_mittens.json` — regions `["HANDS"]`, lockable. - -### Collars (5 fichiers) - -| Fichier | display_name | Components spécifiques | -|---------|-------------|----------------------| -| `classic_collar.json` | Classic Collar | ownership, lockable, resistance `{"id": "collar"}` | -| `shock_collar.json` | Shock Collar | + shock `{"damage": 2.0}` | -| `shock_collar_auto.json` | Auto Shock Collar | + shock `{"damage": 2.0, "auto_interval": 200}` | -| `gps_collar.json` | GPS Collar | + gps `{"safe_zone_radius": 50}` | -| `choke_collar.json` | Choke Collar | + choking | - -### Combos (3 fichiers) - -| Fichier | display_name | Regions | Components | -|---------|-------------|---------|-----------| -| `hood.json` | Hood | `["EYES"]` | blinding, gagging `{"material": "cloth"}`, lockable, blocked_regions: `["EYES", "EARS"]` | -| `medical_gag.json` | Medical Gag | `["MOUTH"]` | gagging `{"material": "stuffed"}`, blinding, lockable | -| `ball_gag_3d.json` | Ball Gag 3D | `["MOUTH"]` | gagging `{"material": "ball"}`, lockable, adjustable. Model 3D spécifique. | - ---- - -## Supprimer le fichier test - -Supprimer `src/main/resources/data/tiedup/tiedup_items/test_component_gag.json` (fichier de test Phase 1, plus nécessaire). - ---- - -## Vérification - -- [ ] `make build` — clean -- [ ] `make run` — les 46 items data-driven apparaissent (via `/tiedup give` ou creative tab section data-driven) -- [ ] Résistance = valeur config (changer dans config, vérifier que la résistance change) -- [ ] Gag comprehension = valeur config (changer dans config, vérifier) -- [ ] Collars ownership fonctionne via le nouveau OwnershipComponent -- [ ] Items organiques (slime, vine, web, tape) ne peuvent pas recevoir de padlock diff --git a/docs/plans/D01-branch-C-consumer-migration.md b/docs/plans/D01-branch-C-consumer-migration.md deleted file mode 100644 index 14f2413..0000000 --- a/docs/plans/D01-branch-C-consumer-migration.md +++ /dev/null @@ -1,107 +0,0 @@ -# D-01 Branch C : Consumer Migration (~97 fichiers) - -> **Prérequis :** Branch A + B mergées. -> **Branche :** `feature/d01-branch-c-migration` -> **Objectif :** Remplacer TOUTES les références V1 (`instanceof ItemBind`, `ItemCollar.isOwner()`, `BindVariant.ROPES`, etc.) par les helpers/composants V2. Les classes V1 existent encore mais ne sont plus référencées. À la fin, `grep -r "instanceof ItemBind\|instanceof ItemGag\|instanceof ItemCollar\|instanceof ItemBlindfold\|instanceof ItemEarplugs\|instanceof ItemMittens\|BindVariant\|GagVariant" src/` retourne ZÉRO résultats (hors items/ lui-même). - ---- - -## Pattern migration - -| V1 | V2 | Notes | -|----|-----|-------| -| `instanceof ItemBind` | `BindModeHelper.isBindItem(stack)` | Ou `instanceof IV2BondageItem` si on a juste besoin de savoir que c'est un bondage item | -| `ItemBind.hasArmsBound(stack)` | `BindModeHelper.hasArmsBound(stack)` | Mêmes NBT keys | -| `ItemBind.hasLegsBound(stack)` | `BindModeHelper.hasLegsBound(stack)` | | -| `ItemBind.getBindModeId(stack)` | `BindModeHelper.getBindModeId(stack)` | | -| `itemBind.getPoseType()` | `PoseTypeHelper.getPoseType(stack)` | | -| `BindVariant.ROPES` / `ModItems.getBind(variant)` | `DataDrivenBondageItem.createStack(rl("tiedup:ropes"))` | **LAZY !** Ne pas appeler dans des static initializers | -| `instanceof ItemCollar` + methods | `CollarHelper.isCollar(stack)` + `CollarHelper.method(stack)` | | -| `instanceof ItemShockCollar` | `CollarHelper.canShock(stack)` | | -| `instanceof ItemGpsCollar` | `CollarHelper.hasGPS(stack)` | | -| `instanceof ItemChokeCollar` | `CollarHelper.isChokeCollar(stack)` | | -| `instanceof IHasGaggingEffect` | `DataDrivenBondageItem.getComponent(stack, GAGGING, GaggingComponent.class) != null` | Pour V2 items. V1 items gardent l'interface pendant la transition. | -| `instanceof IHasBlindingEffect` | `DataDrivenBondageItem.getComponent(stack, BLINDING, BlindingComponent.class) != null` | Idem | -| `instanceof ItemGag` + `getGagMaterial()` | `GaggingComponent comp = getComponent(stack, GAGGING, ...)` + `comp.getMaterial()` | | -| `PoseType` enum direct | Inchangé — l'enum est conservé | | -| `IHasResistance` methods | Inchangé — l'interface est conservée | | -| `ILockable` methods | Inchangé — l'interface est conservée | | - ---- - -## Ordre de migration (critique d'abord) - -### Phase 1 : State core (12 fichiers) - -Ces fichiers sont la fondation — les migrer d'abord assure que le reste fonctionne. - -| Fichier | Changements | -|---------|------------| -| `state/IBondageState.java` | `hasArmsBound()`/`hasLegsBound()` → `BindModeHelper` | -| `state/PlayerBindState.java` | `instanceof ItemCollar` → `CollarHelper`, `instanceof ItemBind` → `BindModeHelper` | -| `state/PlayerCaptorManager.java` | `instanceof ItemCollar` → `CollarHelper.isCollar() + CollarHelper.getOwners()` | -| `state/HumanChairHelper.java` | `PoseType` import OK (conservé) | -| `state/components/PlayerEquipment.java` | **Garder `equipInRegion()` V1 fallback** (migre en Branch D). Remplacer `instanceof ItemBind/ItemCollar` dans resistance methods. | -| `state/components/PlayerDataRetrieval.java` | `instanceof ItemCollar` → `CollarHelper.getNickname()` | -| `state/components/PlayerLifecycle.java` | imports V1 → V2 helpers | -| `state/components/PlayerShockCollar.java` | `instanceof ItemShockCollar` → `CollarHelper.canShock()` | -| `state/struggle/StruggleBinds.java` | Déjà migré en Branch A9 — vérifier | -| `state/struggle/StruggleCollar.java` | Déjà migré en Branch A9 — vérifier | -| `state/struggle/StruggleAccessory.java` | Vérifier | - -### Phase 2 : Client animation/render (12 fichiers) - -| Fichier | Changements | -|---------|------------| -| `client/animation/tick/AnimationTickHandler.java` | `instanceof ItemBind` → `PoseTypeHelper.getPoseType()` + `BindModeHelper` | -| `client/animation/tick/NpcAnimationTickHandler.java` | Idem | -| `client/animation/render/PlayerArmHideEventHandler.java` | `instanceof ItemBind` → `PoseTypeHelper` | -| `client/animation/render/DogPoseRenderHandler.java` | Idem | -| `client/animation/render/PetBedRenderHandler.java` | Idem | -| `client/animation/util/AnimationIdBuilder.java` | `PoseType` import OK (conservé) | -| `client/animation/StaticPoseApplier.java` | `PoseType` import OK | -| `client/model/DamselModel.java` | `PoseType` + `instanceof ItemBind` → helpers | -| `client/FirstPersonMittensRenderer.java` | `BindVariant.ROPES` → lazy createStack | -| `mixin/client/MixinPlayerModel.java` | `instanceof ItemBind` → `PoseTypeHelper` | -| `mixin/client/MixinCamera.java` | Idem | -| `mixin/client/MixinVillagerEntityBaseModelMCA.java` | Idem | - -### Phase 3 : Entity AI goals (15 fichiers) - -Principalement `instanceof ItemBind` → `BindModeHelper`, `ModItems.getBind(variant)` → `createStack(rl)`, `instanceof ItemCollar` → `CollarHelper`. - -### Phase 4 : Network packets (14 fichiers) - -`PacketSelfBondage` déjà migré en A11. Reste : `PacketSlaveAction`, `PacketMasterEquip`, `PacketAssignCellToCollar`, `PacketNpcCommand`, etc. → `CollarHelper`. - -### Phase 5 : Events (8 fichiers) - -`BondageItemRestrictionHandler`, `RestraintTaskTickHandler`, `PlayerEnslavementHandler`, `ChatEventHandler`, etc. - -### Phase 6 : Commands (6 fichiers) - -`BondageSubCommand` (1232 lignes) — le plus gros. `BindVariant` → `createStack()`. `NPCCommand`, `CollarCommand`, `KidnapSetCommand`. - -### Phase 7 : Entity classes (15 fichiers) - -`EntityKidnapper`, `KidnapperCaptureEquipment`, `KidnapperTheme`, `KidnapperItemSelector`, `KidnapperCollarConfig`, etc. - -### Phase 8 : Compat MCA (5 fichiers) - -`MCAKidnappedAdapter` (907 lignes) — `instanceof IHasGaggingEffect/IHasBlindingEffect` → component checks. `instanceof ItemCollar` → `CollarHelper`. - -### Phase 9 : Autres (10 fichiers) - -Dialogue (`GagTalkManager`, `PetRequestManager`), worldgen (`HangingCagePiece`), util (`RestraintApplicator`), blocks (`BondageItemBlockEntity`), dispenser, creative tab. - ---- - -## Vérification - -- [ ] `make build` — clean -- [ ] `grep -r "instanceof ItemBind\b" src/ --include="*.java" | grep -v "items/"` → 0 résultats -- [ ] `grep -r "instanceof ItemGag\b" src/ --include="*.java" | grep -v "items/"` → 0 résultats -- [ ] `grep -r "instanceof ItemCollar\b" src/ --include="*.java" | grep -v "items/"` → 0 résultats -- [ ] `grep -r "BindVariant\b" src/ --include="*.java" | grep -v "items/"` → 0 résultats -- [ ] `grep -r "GagVariant\b" src/ --include="*.java" | grep -v "items/"` → 0 résultats -- [ ] `make run` — le mod fonctionne normalement diff --git a/docs/plans/D01-branch-D-v1-cleanup.md b/docs/plans/D01-branch-D-v1-cleanup.md deleted file mode 100644 index 055420b..0000000 --- a/docs/plans/D01-branch-D-v1-cleanup.md +++ /dev/null @@ -1,188 +0,0 @@ -# D-01 Branch D : V1 Cleanup - -> **Prérequis :** Branch A + B + C mergées. Zero références V1 hors du package `items/`. -> **Branche :** `feature/d01-branch-d-cleanup` -> **Objectif :** Supprimer toutes les classes V1 bondage. Migrer `equipInRegion()` vers le flow V2 complet. Réécrire le creative tab. À la fin, zéro classe V1 bondage dans le mod. - ---- - -## Décisions - -- **Save compat :** Breaking change. Les items V1 dans les inventaires existants seront perdus. Mod en alpha. -- **Pas de MissingMappingsEvent.** Simplement supprimer les registrations. - ---- - -## Tâches - -### D1. Migrer equipInRegion() → V2EquipmentHelper.equipItem() - -**Fichier :** `src/main/java/com/tiedup/remake/state/components/PlayerEquipment.java` - -Maintenant que tous les items sont `DataDrivenBondageItem` (qui implémente `IV2BondageItem`), le bypass direct `setInRegion()` n'est plus nécessaire. - -Remplacer `equipInRegion()` par un appel à `V2EquipmentHelper.equipItem()` qui fait la conflict resolution complète (swap, supersede, blocked regions). - -Vérifier que les méthodes `putBindOn()`, `putGagOn()`, `putCollarOn()`, etc. fonctionnent toujours via le nouveau path. - ---- - -### D2. Supprimer les classes V1 (~30 fichiers) - -**À supprimer :** - -Abstract bases : -- `items/base/ItemBind.java` (637 lignes) -- `items/base/ItemGag.java` (93 lignes) -- `items/base/ItemBlindfold.java` (89 lignes) -- `items/base/ItemCollar.java` (1407 lignes) -- `items/base/ItemEarplugs.java` (90 lignes) -- `items/base/ItemMittens.java` (72 lignes) - -Interfaces V1-only : -- `items/base/IBondageItem.java` (102 lignes) -- `items/base/IHasGaggingEffect.java` (33 lignes) -- `items/base/IHasBlindingEffect.java` (33 lignes) -- `items/base/IAdjustable.java` (49 lignes) -- `items/base/ItemOwnerTarget.java` -- `items/base/ItemColor.java` - -Variant enums : -- `items/base/BindVariant.java` (90 lignes) -- `items/base/GagVariant.java` (163 lignes) -- `items/base/BlindfoldVariant.java` (48 lignes) -- `items/base/EarplugsVariant.java` (33 lignes) -- `items/base/MittensVariant.java` (35 lignes) - -Factory classes : -- `items/GenericBind.java` (68 lignes) -- `items/GenericGag.java` (72 lignes) -- `items/GenericBlindfold.java` (37 lignes) -- `items/GenericEarplugs.java` (37 lignes) -- `items/GenericMittens.java` (37 lignes) - -Collars : -- `items/ItemClassicCollar.java` (21 lignes) -- `items/ItemShockCollar.java` (133 lignes) -- `items/ItemShockCollarAuto.java` (58 lignes) -- `items/ItemGpsCollar.java` (369 lignes) -- `items/ItemChokeCollar.java` (154 lignes) - -Combos : -- `items/ItemHood.java` (35 lignes) -- `items/ItemMedicalGag.java` (24 lignes) -- `items/bondage3d/gags/ItemBallGag3D.java` (78 lignes) -- `items/bondage3d/IHas3DModelConfig.java` -- `items/bondage3d/Model3DConfig.java` - -**À CONSERVER :** -- `items/base/ILockable.java` — utilisé par V2 (AbstractV2BondageItem) -- `items/base/IHasResistance.java` — utilisé par V2 (DataDrivenBondageItem) -- `items/base/IKnife.java` — utilisé par GenericKnife (tool) -- `items/base/PoseType.java` — utilisé par animation system -- `items/base/KnifeVariant.java` — utilisé par GenericKnife (tool) -- `items/base/AdjustmentHelper.java` — utilisé par adjustment packets -- `items/GenericKnife.java` — tool, pas bondage -- `items/clothes/GenericClothes.java` — déjà V2 -- `items/clothes/ClothesProperties.java` -- `items/ModItems.java` — garde les tools, supprime les bondage -- `items/ModCreativeTabs.java` — réécrit (voir D3) -- Tous les tool items (whip, padlock, key, lockpick, taser, etc.) - ---- - -### D3. Réécrire ModItems — retirer les registrations V1 - -**Fichier :** `src/main/java/com/tiedup/remake/items/ModItems.java` - -Supprimer : -- `BINDS` map + `registerAllBinds()` -- `GAGS` map + `registerAllGags()` -- `BLINDFOLDS` map + `registerAllBlindfolds()` -- `EARPLUGS` map + `registerAllEarplugs()` -- `MITTENS` map + `registerAllMittens()` -- `BALL_GAG_3D`, `MEDICAL_GAG`, `HOOD` -- `CLASSIC_COLLAR`, `SHOCK_COLLAR`, `SHOCK_COLLAR_AUTO`, `GPS_COLLAR`, `CHOKE_COLLAR` -- Les helper accessors `getBind()`, `getGag()`, etc. - -Garder : CLOTHES, tous les tools (WHIP, PADLOCK, KEY, etc.), KNIVES, spawn eggs. - ---- - -### D4. Réécrire ModCreativeTabs - -**Fichier :** `src/main/java/com/tiedup/remake/items/ModCreativeTabs.java` - -Remplacer l'itération par variant enums par : - -```java -// Data-driven bondage items -for (DataDrivenItemDefinition def : DataDrivenItemRegistry.getAll()) { - output.accept(DataDrivenBondageItem.createStack(def.id())); -} -``` - -Pour l'ordre : ajouter un champ optionnel `"creative_tab_order"` aux definitions JSON, ou trier par catégorie (regions) puis par nom. - -Pour les couleurs : si l'item a `supports_color`, ajouter les variantes colorées. Utiliser `tint_channels` du definition. - ---- - -### D5. Cleanup PoseTypeHelper — retirer le fallback V1 - -**Fichier :** `src/main/java/com/tiedup/remake/v2/bondage/PoseTypeHelper.java` - -Supprimer le fallback `instanceof ItemBind` dans `getPoseType()`. Ne garder que le path data-driven. - ---- - -### D6. Cleanup CollarHelper — retirer les fallbacks V1 - -**Fichier :** `src/main/java/com/tiedup/remake/v2/bondage/CollarHelper.java` - -Les méthodes comme `isCollar(stack)` checkent `instanceof ItemCollar` en fallback V1. Retirer ces checks. - ---- - -### D7. Cleanup BindModeHelper — retirer le fallback V1 - -Idem — retirer `instanceof ItemBind` fallback dans `isBindItem()`. - ---- - -### D8. Cleanup imports orphelins - -Faire un pass sur tout le projet pour retirer les imports V1 orphelins. - -```bash -grep -rn "import com.tiedup.remake.items.base.ItemBind" src/ --include="*.java" -grep -rn "import com.tiedup.remake.items.base.ItemCollar" src/ --include="*.java" -grep -rn "import com.tiedup.remake.items.base.IBondageItem" src/ --include="*.java" -# etc. — tout doit retourner 0 -``` - ---- - -## Vérification finale - -- [ ] `make build` — clean, zero errors -- [ ] `make run` — le mod démarre, les items apparaissent dans le creative tab -- [ ] `grep -r "items.base.ItemBind\|items.base.ItemGag\|items.base.ItemCollar\|items.base.ItemBlindfold\|items.base.ItemEarplugs\|items.base.ItemMittens\|items.base.IBondageItem\|BindVariant\|GagVariant\|BlindfoldVariant\|EarplugsVariant\|MittensVariant" src/main/java/ --include="*.java"` → **0 résultats** -- [ ] Les items data-driven s'équipent/se déséquipent correctement -- [ ] Le struggle fonctionne (binds + collars) -- [ ] Le self-bondage fonctionne (routing par région) -- [ ] Les collars gardent leur ownership/shock/GPS après equip/unequip -- [ ] Les tooltips affichent toutes les infos composants -- [ ] `equipInRegion()` utilise V2EquipmentManager (conflict resolution active) -- [ ] MCP reindex final - ---- - -## Résultat attendu - -- **~6500 lignes de code V1 supprimées** -- **46 items = 46 fichiers JSON** (data-driven, extensible par resource packs) -- **1 seul Item singleton** (`DataDrivenBondageItem`) -- **8 composants** gèrent toute la logique gameplay -- **3 helpers** (`BindModeHelper`, `PoseTypeHelper`, `CollarHelper`) remplacent les anciennes classes -- **Zero couplage V1** dans le reste du mod diff --git a/docs/plans/D01-branch-E-resistance-rework.md b/docs/plans/D01-branch-E-resistance-rework.md deleted file mode 100644 index 750d6d0..0000000 --- a/docs/plans/D01-branch-E-resistance-rework.md +++ /dev/null @@ -1,155 +0,0 @@ -# D-01 Branch E : Resistance & Lock System Rework - -> **Prérequis :** Branch D (V1 cleanup) mergée. -> **Branche :** `feature/d01-branch-e-resistance` -> **Objectif :** Redesign complet du système de résistance/lock. - ---- - -## Nouveau modèle - -### Principes - -1. **La résistance vient de l'item** — définie dans le JSON via `ResistanceComponent`, point final. -2. **Le lock est binaire** — on/off. Pas de "lock resistance" séparée. Le lock *active* la nécessité de struggle pour les items non-ARMS. -3. **ARMS = toujours actif** — un bind aux bras nécessite toujours un struggle pour s'en libérer soi-même, locké ou non. -4. **Non-ARMS + pas locké = libre** — un gag/blindfold/collar non-locké peut être retiré librement (par soi-même ou un autre joueur). -5. **Non-ARMS + locké = struggle requis** — le lock active la résistance de l'item. -6. **Un autre joueur peut aider** — retirer un item non-locké sur un autre joueur ne nécessite pas de struggle (aide). - -### Matrice de struggle - -| Région | Locké ? | Self-remove | Autre joueur remove | -|--------|---------|-------------|---------------------| -| ARMS | Non | Struggle (résistance item) | Libre (aide) | -| ARMS | Oui | Struggle (résistance item) | Struggle (résistance item) | -| Non-ARMS | Non | Libre | Libre | -| Non-ARMS | Oui | Struggle (résistance item) | Struggle (résistance item) | - -### Items organiques (slime, vine, web, tape) - -Ces items sont "lockés par nature" — pas de padlock possible mais impossible à retirer sans struggle. - -**Option :** Nouveau composant `BuiltInLockComponent` dans le JSON : -```json -"components": { - "resistance": {"id": "slime"}, - "built_in_lock": {} -} -``` - -`BuiltInLockComponent` : -- `blocksUnequip()` retourne `true` (comme un lock, mais sans padlock) -- `ILockable.canAttachPadlock()` retourne `false` (déjà le cas pour les organiques) -- L'item se comporte comme un ARMS bind : toujours struggle required - -**Alternative :** Flag `"always_locked": true` sur la definition JSON. Plus simple, pas besoin de nouveau composant. - ---- - -## Problèmes actuels que ce rework corrige - -### P1. Singleton MAX scan - -`DataDrivenBondageItem.getBaseResistance(LivingEntity)` retourne le MAX de tous les items data-driven équipés. Un gag résistance 50 hérite de la résistance 200 du chain bind. - -**Fix :** Initialiser `currentResistance` dans le NBT à l'equip depuis `ResistanceComponent.getBaseResistance()`. Plus jamais de fallback au MAX scan runtime. Le `getBaseResistance(LivingEntity)` du singleton devient un no-op/fallback qui n'est plus utilisé par le struggle. - -### P2. isItemLocked() dead code - -`StruggleState.struggle()` ne call jamais `isItemLocked()`. Le x10 penalty n'est jamais appliqué. - -**Fix :** Supprimer le concept de "locked penalty". Avec le nouveau modèle, le lock **active** le struggle, il ne le ralentit pas. Si l'item est locké, il faut struggle avec la résistance complète de l'item. Si non locké (et non-ARMS), pas de struggle du tout. - -### P3. Lock resistance / item resistance déconnectés - -`ILockable.getLockResistance()` vs `IHasResistance.getBaseResistance()` sont deux systèmes indépendants. - -**Fix :** Supprimer `ILockable.getLockResistance()` / `getCurrentLockResistance()` / `setCurrentLockResistance()` / `initializeLockResistance()` / `clearLockResistance()`. La résistance du lockpick minigame utilise directement la résistance de l'item (ou un multiplicateur fixe). - -### P4. Dice-roll ignore le lock - -**Fix :** Avec le nouveau modèle, le dice-roll ne change pas. C'est `canStruggle()` qui gate l'accès : -```java -// StruggleBinds.canStruggle() -// ARMS: toujours struggle-able (self) -return true; - -// StruggleCollar/StruggleAccessory.canStruggle() -// Non-ARMS: seulement si locké -return isLocked(stack) || hasBuiltInLock(stack); -``` - ---- - -## Bugs pré-existants à corriger dans cette branche - -### B1. V1 ItemCollar.onUnequipped() — suppressed path skip unregister - -Quand `isRemovalAlertSuppressed()` est true, `ItemCollar.onUnequipped()` return early SANS appeler `CollarRegistry.unregisterWearer()`. Entrées fantômes persistées. - -**Fichier :** `items/base/ItemCollar.java` lignes 1382-1395 -**Fix :** Ajouter `unregisterWearer()` dans le branch suppressed. - -### B2. DataDrivenItemRegistry.clear() pas synchronisé - -`clear()` écrit `SNAPSHOT = EMPTY` sans acquérir `RELOAD_LOCK`. Race avec `mergeAll()`. - -**Fichier :** `v2/bondage/datadriven/DataDrivenItemRegistry.java` ligne 142 -**Fix :** Synchroniser sur `RELOAD_LOCK`. - -### B3. V2TyingPlayerTask.heldStack reference stale - -Le held item peut être remplacé entre début et fin du tying → item dupliqué. - -**Fichier :** `tasks/V2TyingPlayerTask.java` ligne 80 -**Fix :** Valider `heldStack` non-vide et matching avant equip dans `onComplete()`. - ---- - -## Tâches - -### E1. Initialiser currentResistance à l'equip - -Dans `DataDrivenBondageItem.onEquipped()` et les hooks V1 `onEquipped()` : -- Lire `ResistanceComponent.getBaseResistance()` (ou `IHasResistance.getBaseResistance()` pour V1) -- Écrire immédiatement dans le NBT via `setCurrentResistance(stack, base)` -- Élimine le MAX scan comme source d'initialisation - -### E2. Refactor canStruggle() — nouveau modèle - -- `StruggleBinds.canStruggle()` : ARMS → toujours true (self) si item existe -- Nouveau `StruggleAccessory` (ou refactor de StruggleCollar) : non-ARMS → true seulement si locké ou built-in lock -- Supprimer `isItemLocked()` penalty (dead code de toute façon) - -### E3. "Aide" — remove non-locké par un autre joueur - -Modifier `AbstractV2BondageItem.interactLivingEntity()` : -- Si clic sur un joueur qui porte l'item ET item non-locké ET clicker n'a pas l'item en main → retirer l'item (aide) -- Ou via un packet dédié (clic droit main vide sur joueur attaché) - -### E4. BuiltInLockComponent ou flag `always_locked` - -Pour les items organiques qui ne peuvent pas avoir de padlock mais nécessitent un struggle. - -### E5. Cleanup ILockable — supprimer lock resistance - -Supprimer : `getLockResistance()`, `getCurrentLockResistance()`, `setCurrentLockResistance()`, `initializeLockResistance()`, `clearLockResistance()`. - -Le lockpick minigame utilise la résistance de l'item directement (ou un multiplicateur config). - -### E6. Fix bugs pré-existants (B1, B2, B3) - ---- - -## Vérification - -- [ ] V2 bind résistance 50 + V2 gag résistance 80 : chacun a sa propre résistance (pas MAX) -- [ ] Gag non-locké → retirable sans struggle -- [ ] Gag locké → struggle avec résistance du gag -- [ ] Bind ARMS non-locké → self-struggle requis, autre joueur peut aider (libre) -- [ ] Bind ARMS locké → self-struggle requis, autre joueur aussi struggle -- [ ] Slime bind (built-in lock) → struggle obligatoire, pas de padlock possible -- [ ] `currentResistance` initialisé dans NBT dès l'equip -- [ ] CollarRegistry clean après removals légitimes -- [ ] Pas de duplication d'item via tying task diff --git a/docs/superpowers/plans/2026-04-13-d01-phase1-component-system.md b/docs/superpowers/plans/2026-04-13-d01-phase1-component-system.md deleted file mode 100644 index de7a3be..0000000 --- a/docs/superpowers/plans/2026-04-13-d01-phase1-component-system.md +++ /dev/null @@ -1,869 +0,0 @@ -# D-01 Phase 1: Data-Driven Item Component System - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Create a reusable component system so data-driven bondage items can declare gameplay behaviors (lockable, shock, GPS, gagging, etc.) in their JSON definition instead of requiring per-item Java classes. - -**Architecture:** Each component is a self-contained behavior module implementing `IItemComponent`. Components are declared in item JSON (`"components": {"shock": {...}}`), parsed by an extended `DataDrivenItemParser`, stored on `DataDrivenItemDefinition`, and ticked/queried via `DataDrivenBondageItem` delegation. The existing `ILockable` and `IHasResistance` interfaces are preserved as shared contracts — components implement them. - -**Tech Stack:** Java 17, Forge 1.20.1, existing V2 data-driven infrastructure (`DataDrivenItemRegistry`, `DataDrivenItemParser`, `DataDrivenItemDefinition`, `DataDrivenBondageItem`) - -**Scope:** This plan builds ONLY the component infrastructure + 3 core components (lockable, resistance, gagging). The remaining 5 components (shock, GPS, blinding, choking, adjustable) follow the same pattern and will be added in subsequent tasks or a follow-up plan. - ---- - -## File Structure - -### New files - -| File | Responsibility | -|------|---------------| -| `v2/bondage/component/IItemComponent.java` | Component interface: lifecycle hooks, tick, query | -| `v2/bondage/component/ComponentType.java` | Enum of all component types with factory methods | -| `v2/bondage/component/ComponentHolder.java` | Container: holds instantiated components for an item stack | -| `v2/bondage/component/LockableComponent.java` | Lock/unlock, padlock, key matching, jam, lock resistance | -| `v2/bondage/component/ResistanceComponent.java` | Struggle resistance with configurable base value | -| `v2/bondage/component/GaggingComponent.java` | Muffled speech, comprehension %, range limit | - -### Modified files - -| File | Changes | -|------|---------| -| `v2/bondage/datadriven/DataDrivenItemDefinition.java` | Add `Map componentConfigs` field | -| `v2/bondage/datadriven/DataDrivenItemParser.java` | Parse `"components"` JSON block | -| `v2/bondage/datadriven/DataDrivenBondageItem.java` | Delegate lifecycle hooks to components, expose `getComponent()` | -| `v2/bondage/datadriven/DataDrivenItemRegistry.java` | Instantiate `ComponentHolder` per definition | - ---- - -## Tasks - -### Task 1: IItemComponent interface - -**Files:** -- Create: `src/main/java/com/tiedup/remake/v2/bondage/component/IItemComponent.java` - -- [ ] **Step 1: Create the component interface** - -```java -package com.tiedup.remake.v2.bondage.component; - -import com.google.gson.JsonObject; -import net.minecraft.world.entity.LivingEntity; -import net.minecraft.world.item.ItemStack; - -/** - * A reusable behavior module for data-driven bondage items. - * Components are declared in JSON and instantiated per item definition. - * - *

Lifecycle: parse config once (from JSON), then tick/query per equipped entity.

- */ -public interface IItemComponent { - - /** - * Called when the item is equipped on an entity. - * @param stack The equipped item stack - * @param entity The entity wearing the item - */ - default void onEquipped(ItemStack stack, LivingEntity entity) {} - - /** - * Called when the item is unequipped from an entity. - * @param stack The unequipped item stack - * @param entity The entity that was wearing the item - */ - default void onUnequipped(ItemStack stack, LivingEntity entity) {} - - /** - * Called every tick while the item is equipped. - * @param stack The equipped item stack - * @param entity The entity wearing the item - */ - default void onWornTick(ItemStack stack, LivingEntity entity) {} - - /** - * Whether this component prevents the item from being unequipped. - * @param stack The equipped item stack - * @param entity The entity wearing the item - * @return true if unequip should be blocked - */ - default boolean blocksUnequip(ItemStack stack, LivingEntity entity) { - return false; - } -} -``` - -- [ ] **Step 2: Verify file compiles** - -Run: `cd /home/user/Documents/Projet/Open-TiedUp! && make build 2>&1 | tail -5` -Expected: BUILD SUCCESSFUL - -- [ ] **Step 3: Commit** - -```bash -git add src/main/java/com/tiedup/remake/v2/bondage/component/IItemComponent.java -git commit -m "feat(D-01): add IItemComponent interface for data-driven item behaviors" -``` - ---- - -### Task 2: ComponentType enum - -**Files:** -- Create: `src/main/java/com/tiedup/remake/v2/bondage/component/ComponentType.java` - -- [ ] **Step 1: Create the component type registry** - -```java -package com.tiedup.remake.v2.bondage.component; - -import com.google.gson.JsonObject; -import javax.annotation.Nullable; -import java.util.function.Function; - -/** - * All known component types. Each type knows how to instantiate itself from JSON config. - */ -public enum ComponentType { - LOCKABLE("lockable", LockableComponent::fromJson), - RESISTANCE("resistance", ResistanceComponent::fromJson), - GAGGING("gagging", GaggingComponent::fromJson); - // Future: SHOCK, GPS, BLINDING, CHOKING, ADJUSTABLE - - private final String jsonKey; - private final Function factory; - - ComponentType(String jsonKey, Function factory) { - this.jsonKey = jsonKey; - this.factory = factory; - } - - public String getJsonKey() { - return jsonKey; - } - - public IItemComponent create(JsonObject config) { - return factory.apply(config); - } - - /** - * Look up a ComponentType by its JSON key. Returns null if unknown. - */ - @Nullable - public static ComponentType fromKey(String key) { - for (ComponentType type : values()) { - if (type.jsonKey.equals(key)) { - return type; - } - } - return null; - } -} -``` - -Note: This file will not compile yet because `LockableComponent`, `ResistanceComponent`, and `GaggingComponent` don't exist. We'll create stub classes first, then implement them. - -- [ ] **Step 2: Create stub classes so the enum compiles** - -Create three empty stubs (they will be fully implemented in Tasks 4-6): - -`src/main/java/com/tiedup/remake/v2/bondage/component/LockableComponent.java`: -```java -package com.tiedup.remake.v2.bondage.component; - -import com.google.gson.JsonObject; - -public class LockableComponent implements IItemComponent { - private LockableComponent() {} - - public static IItemComponent fromJson(JsonObject config) { - return new LockableComponent(); - } -} -``` - -`src/main/java/com/tiedup/remake/v2/bondage/component/ResistanceComponent.java`: -```java -package com.tiedup.remake.v2.bondage.component; - -import com.google.gson.JsonObject; - -public class ResistanceComponent implements IItemComponent { - private ResistanceComponent() {} - - public static IItemComponent fromJson(JsonObject config) { - return new ResistanceComponent(); - } -} -``` - -`src/main/java/com/tiedup/remake/v2/bondage/component/GaggingComponent.java`: -```java -package com.tiedup.remake.v2.bondage.component; - -import com.google.gson.JsonObject; - -public class GaggingComponent implements IItemComponent { - private GaggingComponent() {} - - public static IItemComponent fromJson(JsonObject config) { - return new GaggingComponent(); - } -} -``` - -- [ ] **Step 3: Verify all files compile** - -Run: `cd /home/user/Documents/Projet/Open-TiedUp! && make build 2>&1 | tail -5` -Expected: BUILD SUCCESSFUL - -- [ ] **Step 4: Commit** - -```bash -git add src/main/java/com/tiedup/remake/v2/bondage/component/ -git commit -m "feat(D-01): add ComponentType enum with stub component classes" -``` - ---- - -### Task 3: ComponentHolder container - -**Files:** -- Create: `src/main/java/com/tiedup/remake/v2/bondage/component/ComponentHolder.java` - -- [ ] **Step 1: Create the component container** - -```java -package com.tiedup.remake.v2.bondage.component; - -import net.minecraft.world.entity.LivingEntity; -import net.minecraft.world.item.ItemStack; - -import javax.annotation.Nullable; -import java.util.Collections; -import java.util.EnumMap; -import java.util.Map; - -/** - * Holds instantiated components for an item definition. - * Immutable after construction. One per DataDrivenItemDefinition. - */ -public final class ComponentHolder { - - public static final ComponentHolder EMPTY = new ComponentHolder(Map.of()); - - private final Map components; - - public ComponentHolder(Map components) { - this.components = components.isEmpty() - ? Map.of() - : Collections.unmodifiableMap(new EnumMap<>(components)); - } - - /** - * Get a component by type, or null if not present. - */ - @Nullable - public IItemComponent get(ComponentType type) { - return components.get(type); - } - - /** - * Get a component by type, cast to the expected class. - * Returns null if not present or wrong type. - */ - @Nullable - @SuppressWarnings("unchecked") - public T get(ComponentType type, Class clazz) { - IItemComponent component = components.get(type); - if (clazz.isInstance(component)) { - return (T) component; - } - return null; - } - - /** - * Check if a component type is present. - */ - public boolean has(ComponentType type) { - return components.containsKey(type); - } - - /** - * Fire onEquipped for all components. - */ - public void onEquipped(ItemStack stack, LivingEntity entity) { - for (IItemComponent component : components.values()) { - component.onEquipped(stack, entity); - } - } - - /** - * Fire onUnequipped for all components. - */ - public void onUnequipped(ItemStack stack, LivingEntity entity) { - for (IItemComponent component : components.values()) { - component.onUnequipped(stack, entity); - } - } - - /** - * Fire onWornTick for all components. - */ - public void onWornTick(ItemStack stack, LivingEntity entity) { - for (IItemComponent component : components.values()) { - component.onWornTick(stack, entity); - } - } - - /** - * Check if any component blocks unequip. - */ - public boolean blocksUnequip(ItemStack stack, LivingEntity entity) { - for (IItemComponent component : components.values()) { - if (component.blocksUnequip(stack, entity)) { - return true; - } - } - return false; - } - - /** - * Whether this holder has any components. - */ - public boolean isEmpty() { - return components.isEmpty(); - } -} -``` - -- [ ] **Step 2: Verify compilation** - -Run: `cd /home/user/Documents/Projet/Open-TiedUp! && make build 2>&1 | tail -5` -Expected: BUILD SUCCESSFUL - -- [ ] **Step 3: Commit** - -```bash -git add src/main/java/com/tiedup/remake/v2/bondage/component/ComponentHolder.java -git commit -m "feat(D-01): add ComponentHolder container for item components" -``` - ---- - -### Task 4: Integrate components into DataDrivenItemDefinition + Parser - -**Files:** -- Modify: `src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenItemDefinition.java` -- Modify: `src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenItemParser.java` - -- [ ] **Step 1: Add componentConfigs field to DataDrivenItemDefinition** - -Read the current record definition, then add a new field. The record should get a new parameter: - -```java -/** Raw component configs from JSON, keyed by ComponentType. */ -Map componentConfigs -``` - -Add after the last existing field in the record. Also add a convenience method: - -```java -/** - * Whether this definition declares a specific component. - */ -public boolean hasComponent(ComponentType type) { - return componentConfigs != null && componentConfigs.containsKey(type); -} -``` - -- [ ] **Step 2: Parse "components" block in DataDrivenItemParser** - -Read `DataDrivenItemParser.java` and add parsing for the `"components"` JSON field. After parsing all existing fields, add: - -```java -// Parse components -Map componentConfigs = new EnumMap<>(ComponentType.class); -if (json.has("components")) { - JsonObject componentsObj = json.getAsJsonObject("components"); - for (Map.Entry entry : componentsObj.entrySet()) { - ComponentType type = ComponentType.fromKey(entry.getKey()); - if (type != null) { - JsonObject config = entry.getValue().isJsonObject() - ? entry.getValue().getAsJsonObject() - : new JsonObject(); - componentConfigs.put(type, config); - } else { - LOGGER.warn("[DataDrivenItemParser] Unknown component type '{}' in item '{}'", - entry.getKey(), id); - } - } -} -``` - -Pass `componentConfigs` to the `DataDrivenItemDefinition` record constructor. - -- [ ] **Step 3: Update all existing call sites that construct DataDrivenItemDefinition** - -Search for all `new DataDrivenItemDefinition(` calls and add `Map.of()` for the new parameter (for the network sync deserialization path, etc.). - -- [ ] **Step 4: Verify compilation** - -Run: `cd /home/user/Documents/Projet/Open-TiedUp! && make build 2>&1 | tail -5` -Expected: BUILD SUCCESSFUL - -- [ ] **Step 5: Commit** - -```bash -git add src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenItemDefinition.java -git add src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenItemParser.java -git commit -m "feat(D-01): parse component configs from item JSON definitions" -``` - ---- - -### Task 5: Instantiate ComponentHolder in DataDrivenItemRegistry - -**Files:** -- Modify: `src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenItemRegistry.java` - -- [ ] **Step 1: Add ComponentHolder cache** - -Read `DataDrivenItemRegistry.java`. Add a parallel cache that maps `ResourceLocation` to `ComponentHolder`. When definitions are loaded/reloaded, instantiate components from their `componentConfigs`. - -Add field: -```java -private static volatile Map COMPONENT_HOLDERS = Map.of(); -``` - -In the reload/register method, after storing definitions, build component holders: -```java -Map holders = new HashMap<>(); -for (Map.Entry entry : newDefinitions.entrySet()) { - DataDrivenItemDefinition def = entry.getValue(); - Map components = new EnumMap<>(ComponentType.class); - for (Map.Entry compEntry : def.componentConfigs().entrySet()) { - components.put(compEntry.getKey(), compEntry.getKey().create(compEntry.getValue())); - } - holders.put(entry.getKey(), new ComponentHolder(components)); -} -COMPONENT_HOLDERS = Collections.unmodifiableMap(holders); -``` - -Add accessor: -```java -@Nullable -public static ComponentHolder getComponents(ItemStack stack) { - DataDrivenItemDefinition def = get(stack); - if (def == null) return null; - return COMPONENT_HOLDERS.get(def.id()); -} - -@Nullable -public static ComponentHolder getComponents(ResourceLocation id) { - return COMPONENT_HOLDERS.get(id); -} -``` - -- [ ] **Step 2: Verify compilation** - -Run: `cd /home/user/Documents/Projet/Open-TiedUp! && make build 2>&1 | tail -5` -Expected: BUILD SUCCESSFUL - -- [ ] **Step 3: Commit** - -```bash -git add src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenItemRegistry.java -git commit -m "feat(D-01): instantiate ComponentHolder per item definition on reload" -``` - ---- - -### Task 6: Delegate DataDrivenBondageItem lifecycle to components - -**Files:** -- Modify: `src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenBondageItem.java` - -- [ ] **Step 1: Add component delegation in lifecycle hooks** - -Read `DataDrivenBondageItem.java`. In `onEquipped()` and `onUnequipped()`, delegate to components: - -```java -@Override -public void onEquipped(ItemStack stack, LivingEntity entity) { - ComponentHolder holder = DataDrivenItemRegistry.getComponents(stack); - if (holder != null) { - holder.onEquipped(stack, entity); - } -} - -@Override -public void onUnequipped(ItemStack stack, LivingEntity entity) { - ComponentHolder holder = DataDrivenItemRegistry.getComponents(stack); - if (holder != null) { - holder.onUnequipped(stack, entity); - } -} -``` - -Override `canUnequip` to check component blocks: -```java -@Override -public boolean canUnequip(ItemStack stack, LivingEntity entity) { - ComponentHolder holder = DataDrivenItemRegistry.getComponents(stack); - if (holder != null && holder.blocksUnequip(stack, entity)) { - return false; - } - return super.canUnequip(stack, entity); -} -``` - -Add a public static helper for external code to query components: -```java -/** - * Get a specific component from a data-driven item stack. - * @return The component, or null if the item is not data-driven or lacks this component. - */ -@Nullable -public static T getComponent(ItemStack stack, ComponentType type, Class clazz) { - ComponentHolder holder = DataDrivenItemRegistry.getComponents(stack); - if (holder == null) return null; - return holder.get(type, clazz); -} -``` - -- [ ] **Step 2: Verify compilation** - -Run: `cd /home/user/Documents/Projet/Open-TiedUp! && make build 2>&1 | tail -5` -Expected: BUILD SUCCESSFUL - -- [ ] **Step 3: Commit** - -```bash -git add src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenBondageItem.java -git commit -m "feat(D-01): delegate DataDrivenBondageItem lifecycle to components" -``` - ---- - -### Task 7: Implement LockableComponent - -**Files:** -- Modify: `src/main/java/com/tiedup/remake/v2/bondage/component/LockableComponent.java` - -- [ ] **Step 1: Implement full lockable logic** - -Replace the stub with the full implementation. Extract lock behavior from `ILockable` (which remains as a shared interface). The component reads its config from JSON and delegates to `ILockable` default methods on the item stack: - -```java -package com.tiedup.remake.v2.bondage.component; - -import com.google.gson.JsonObject; -import com.tiedup.remake.items.base.ILockable; -import net.minecraft.world.entity.LivingEntity; -import net.minecraft.world.item.ItemStack; - -/** - * Component: lockable behavior for data-driven items. - * Delegates to ILockable interface methods on the item. - * - * JSON config: - *
{"lockable": true}
- * or - *
{"lockable": {"lock_resistance": 300}}
- */ -public class LockableComponent implements IItemComponent { - - private final int lockResistance; - - private LockableComponent(int lockResistance) { - this.lockResistance = lockResistance; - } - - public static IItemComponent fromJson(JsonObject config) { - int resistance = 250; // default from SettingsAccessor - if (config.has("lock_resistance")) { - resistance = config.get("lock_resistance").getAsInt(); - } - return new LockableComponent(resistance); - } - - public int getLockResistance() { - return lockResistance; - } - - @Override - public boolean blocksUnequip(ItemStack stack, LivingEntity entity) { - // If item implements ILockable, check if locked - if (stack.getItem() instanceof ILockable lockable) { - return lockable.isLocked(stack); - } - return false; - } -} -``` - -- [ ] **Step 2: Verify compilation** - -Run: `cd /home/user/Documents/Projet/Open-TiedUp! && make build 2>&1 | tail -5` -Expected: BUILD SUCCESSFUL - -- [ ] **Step 3: Commit** - -```bash -git add src/main/java/com/tiedup/remake/v2/bondage/component/LockableComponent.java -git commit -m "feat(D-01): implement LockableComponent with configurable lock resistance" -``` - ---- - -### Task 8: Implement ResistanceComponent - -**Files:** -- Modify: `src/main/java/com/tiedup/remake/v2/bondage/component/ResistanceComponent.java` - -- [ ] **Step 1: Implement resistance logic** - -```java -package com.tiedup.remake.v2.bondage.component; - -import com.google.gson.JsonObject; -import net.minecraft.world.entity.LivingEntity; -import net.minecraft.world.item.ItemStack; - -/** - * Component: struggle resistance for data-driven items. - * Replaces IHasResistance for data-driven items. - * - * JSON config: - *
{"resistance": {"base": 150}}
- */ -public class ResistanceComponent implements IItemComponent { - - private final int baseResistance; - - private ResistanceComponent(int baseResistance) { - this.baseResistance = baseResistance; - } - - public static IItemComponent fromJson(JsonObject config) { - int base = 100; // default - if (config.has("base")) { - base = config.get("base").getAsInt(); - } - return new ResistanceComponent(base); - } - - /** - * Get the base resistance for this item. - * Used by DataDrivenBondageItem.getBaseResistance() to replace the MAX-scan workaround. - */ - public int getBaseResistance() { - return baseResistance; - } -} -``` - -- [ ] **Step 2: Update DataDrivenBondageItem.getBaseResistance() to use ResistanceComponent** - -In `DataDrivenBondageItem.java`, update `getBaseResistance()`: - -```java -@Override -public int getBaseResistance(LivingEntity entity) { - // Try stack-aware component lookup first (fixes I-03: no more MAX scan) - // Note: This method is called WITHOUT a stack parameter by IHasResistance. - // We still need the MAX scan as fallback until IHasResistance gets a stack-aware method. - if (entity != null) { - IV2BondageEquipment equip = V2EquipmentHelper.getEquipment(entity); - if (equip != null) { - int maxDifficulty = -1; - for (Map.Entry entry : equip.getAllEquipped().entrySet()) { - ItemStack stack = entry.getValue(); - if (stack.getItem() == this) { - // Try component first - ResistanceComponent comp = DataDrivenBondageItem.getComponent( - stack, ComponentType.RESISTANCE, ResistanceComponent.class); - if (comp != null) { - maxDifficulty = Math.max(maxDifficulty, comp.getBaseResistance()); - continue; - } - // Fallback to escape_difficulty from definition - DataDrivenItemDefinition def = DataDrivenItemRegistry.get(stack); - if (def != null) { - maxDifficulty = Math.max(maxDifficulty, def.escapeDifficulty()); - } - } - } - if (maxDifficulty >= 0) return maxDifficulty; - } - } - return 100; -} -``` - -- [ ] **Step 3: Verify compilation** - -Run: `cd /home/user/Documents/Projet/Open-TiedUp! && make build 2>&1 | tail -5` -Expected: BUILD SUCCESSFUL - -- [ ] **Step 4: Commit** - -```bash -git add src/main/java/com/tiedup/remake/v2/bondage/component/ResistanceComponent.java -git add src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenBondageItem.java -git commit -m "feat(D-01): implement ResistanceComponent, fixes I-03 MAX scan for stack-aware items" -``` - ---- - -### Task 9: Implement GaggingComponent - -**Files:** -- Modify: `src/main/java/com/tiedup/remake/v2/bondage/component/GaggingComponent.java` - -- [ ] **Step 1: Implement gagging logic** - -```java -package com.tiedup.remake.v2.bondage.component; - -import com.google.gson.JsonObject; - -/** - * Component: gagging behavior for data-driven items. - * Replaces IHasGaggingEffect for data-driven items. - * - * JSON config: - *
{"gagging": {"comprehension": 0.2, "range": 10.0}}
- */ -public class GaggingComponent implements IItemComponent { - - private final double comprehension; - private final double range; - - private GaggingComponent(double comprehension, double range) { - this.comprehension = comprehension; - this.range = range; - } - - public static IItemComponent fromJson(JsonObject config) { - double comprehension = 0.2; // default: 20% understandable - double range = 10.0; // default: 10 blocks - if (config.has("comprehension")) { - comprehension = config.get("comprehension").getAsDouble(); - } - if (config.has("range")) { - range = config.get("range").getAsDouble(); - } - return new GaggingComponent(comprehension, range); - } - - /** - * How much of the gagged speech is comprehensible (0.0 = nothing, 1.0 = full). - */ - public double getComprehension() { - return comprehension; - } - - /** - * Maximum range in blocks where muffled speech can be heard. - */ - public double getRange() { - return range; - } -} -``` - -- [ ] **Step 2: Verify compilation** - -Run: `cd /home/user/Documents/Projet/Open-TiedUp! && make build 2>&1 | tail -5` -Expected: BUILD SUCCESSFUL - -- [ ] **Step 3: Commit** - -```bash -git add src/main/java/com/tiedup/remake/v2/bondage/component/GaggingComponent.java -git commit -m "feat(D-01): implement GaggingComponent with comprehension and range" -``` - ---- - -### Task 10: Create a test item JSON using components - -**Files:** -- Create: `src/main/resources/data/tiedup/tiedup_items/test_gag.json` - -- [ ] **Step 1: Create a JSON definition that uses the new component system** - -```json -{ - "type": "tiedup:bondage_item", - "display_name": "Test Ball Gag", - "model": "tiedup:models/gltf/v2/handcuffs/cuffs_prototype.glb", - "regions": ["MOUTH"], - "animation_bones": { - "idle": [] - }, - "pose_priority": 10, - "escape_difficulty": 3, - "lockable": true, - "components": { - "lockable": { - "lock_resistance": 200 - }, - "resistance": { - "base": 80 - }, - "gagging": { - "comprehension": 0.15, - "range": 8.0 - } - } -} -``` - -- [ ] **Step 2: Verify the mod loads without errors** - -Run: `cd /home/user/Documents/Projet/Open-TiedUp! && make build 2>&1 | tail -5` -Expected: BUILD SUCCESSFUL - -Check that the JSON parses by searching for component-related log output in the run logs (manual verification — start the game client with `make run`, check for errors in log). - -- [ ] **Step 3: Commit** - -```bash -git add src/main/resources/data/tiedup/tiedup_items/test_gag.json -git commit -m "feat(D-01): add test_gag.json demonstrating component system" -``` - ---- - -### Task 11: Verify and clean up - -- [ ] **Step 1: Full build verification** - -Run: `cd /home/user/Documents/Projet/Open-TiedUp! && make rebuild 2>&1 | tail -10` -Expected: BUILD SUCCESSFUL with zero errors - -- [ ] **Step 2: Verify no regressions in existing items** - -Existing data-driven items (in `data/tiedup/tiedup_items/`) should continue working without the `"components"` field. The parser should handle missing components gracefully (empty map). - -- [ ] **Step 3: Reindex MCP** - -Run the MCP reindex to update the symbol table with new classes. - -- [ ] **Step 4: Final commit** - -```bash -git add -A -git commit -m "feat(D-01): Phase 1 complete - data-driven item component system - -Adds IItemComponent interface, ComponentType enum, ComponentHolder container, -and 3 core components (LockableComponent, ResistanceComponent, GaggingComponent). - -Components are declared in item JSON 'components' field, parsed by DataDrivenItemParser, -instantiated by DataDrivenItemRegistry, and delegated by DataDrivenBondageItem. - -Existing items without components continue to work unchanged." -``` -- 2.49.1