chore: remove docs from branch C (should not be tracked here)

This commit is contained in:
NotEvil
2026-04-15 00:55:02 +02:00
parent 4246d1c663
commit 9e73434a4a
7 changed files with 0 additions and 2144 deletions

View File

@@ -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. |

View File

@@ -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<Component> tooltip, TooltipFlag flag) {}
// ComponentHolder
public void appendTooltip(ItemStack stack, @Nullable Level level, List<Component> 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<UUID>
- `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<BodyRegionV2> 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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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<ComponentType, JsonObject> 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.
*
* <p>Lifecycle: parse config once (from JSON), then tick/query per equipped entity.</p>
*/
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<JsonObject, IItemComponent> factory;
ComponentType(String jsonKey, Function<JsonObject, IItemComponent> 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<ComponentType, IItemComponent> components;
public ComponentHolder(Map<ComponentType, IItemComponent> 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 extends IItemComponent> T get(ComponentType type, Class<T> 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<ComponentType, JsonObject> 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<ComponentType, JsonObject> componentConfigs = new EnumMap<>(ComponentType.class);
if (json.has("components")) {
JsonObject componentsObj = json.getAsJsonObject("components");
for (Map.Entry<String, com.google.gson.JsonElement> 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<ResourceLocation, ComponentHolder> COMPONENT_HOLDERS = Map.of();
```
In the reload/register method, after storing definitions, build component holders:
```java
Map<ResourceLocation, ComponentHolder> holders = new HashMap<>();
for (Map.Entry<ResourceLocation, DataDrivenItemDefinition> entry : newDefinitions.entrySet()) {
DataDrivenItemDefinition def = entry.getValue();
Map<ComponentType, IItemComponent> components = new EnumMap<>(ComponentType.class);
for (Map.Entry<ComponentType, JsonObject> 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 extends IItemComponent> T getComponent(ItemStack stack, ComponentType type, Class<T> 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:
* <pre>{"lockable": true}</pre>
* or
* <pre>{"lockable": {"lock_resistance": 300}}</pre>
*/
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:
* <pre>{"resistance": {"base": 150}}</pre>
*/
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<BodyRegionV2, ItemStack> 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:
* <pre>{"gagging": {"comprehension": 0.2, "range": 10.0}}</pre>
*/
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."
```