# D-01 Branch A : Bridge Utilities > **Prérequis :** Phase 1 (component system) mergée dans develop. > **Branche :** `feature/d01-branch-a-bridge` > **Objectif :** Créer les utilitaires V2 qui remplacent la logique V1, SANS supprimer de code V1. À la fin de cette branche, le mod compile, les items V1 et V2 coexistent, le struggle fonctionne pour les deux, et les nouveaux helpers sont prêts pour la migration des consommateurs. --- ## Décisions actées - **Stack size** : Stacks de 1 pour tout. Régression acceptée. - **Save compat** : Breaking change. Pas de migration. Mod en alpha. - **Résistance** : Config-driven via `resistanceId`, pas hardcodé en JSON. - **Comprehension/range gags** : Config-driven via `gagMaterial`, délègue à ModConfig au runtime. - **IHasGaggingEffect/IHasBlindingEffect** : DataDrivenBondageItem les implémente en checkant les composants. --- ## Tâches ### A1. Modifier ResistanceComponent — config-driven **Fichier :** `src/main/java/com/tiedup/remake/v2/bondage/component/ResistanceComponent.java` Actuellement stocke un `int baseResistance` hardcodé. Doit stocker un `String resistanceId` et déléguer à `SettingsAccessor.getBindResistance(resistanceId)` au runtime. - Remplacer le champ `baseResistance` par `resistanceId` (String) - `fromJson()` : parse `"id"` au lieu de `"base"` — `"resistance": {"id": "rope"}` - `getBaseResistance()` : `return SettingsAccessor.getBindResistance(resistanceId);` - Garder un fallback `"base"` pour backward compat avec test_component_gag.json (ou le mettre à jour) **Référence :** `SettingsAccessor.getBindResistance(String)` dans `core/SettingsAccessor.java` — normalise les clés et lit depuis ModConfig. --- ### A2. Modifier GaggingComponent — config-driven + GagMaterial **Fichier :** `src/main/java/com/tiedup/remake/v2/bondage/component/GaggingComponent.java` Actuellement stocke `comprehension` et `range` hardcodés. Doit stocker un `String material` et déléguer à `GagMaterial`/ModConfig au runtime. - Ajouter champ `@Nullable String material` - `fromJson()` : parse `"material"` — `"gagging": {"material": "ball"}` - `getComprehension()` : si material != null → `GagMaterial.valueOf(material).getComprehension()` (lit ModConfig). Sinon → fallback au champ hardcodé (compat). - `getRange()` : idem via `GagMaterial.valueOf(material).getTalkRange()` - `getMaterial()` : expose le `GagMaterial` enum pour `GagTalkManager` - Garder les champs `comprehension`/`range` comme overrides optionnels (si présents dans JSON, ils prennent priorité sur le material) **Référence :** `GagMaterial` enum dans `items/base/GagVariant.java` ou `util/GagMaterial.java`. --- ### A3. Ajouter `appendTooltip()` à IItemComponent + ComponentHolder **Fichiers :** - `src/main/java/com/tiedup/remake/v2/bondage/component/IItemComponent.java` - `src/main/java/com/tiedup/remake/v2/bondage/component/ComponentHolder.java` - `src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenBondageItem.java` Ajouter hook tooltip pour que chaque composant contribue des lignes : ```java // IItemComponent default void appendTooltip(ItemStack stack, @Nullable Level level, List tooltip, TooltipFlag flag) {} // ComponentHolder public void appendTooltip(ItemStack stack, @Nullable Level level, List tooltip, TooltipFlag flag) { for (IItemComponent c : components.values()) c.appendTooltip(stack, level, tooltip, flag); } ``` Dans `DataDrivenBondageItem.appendHoverText()` : appeler `holder.appendTooltip(...)`. Implémenter `appendTooltip` dans chaque composant existant : - `LockableComponent` : affiche "Locked" / "Lockable" - `ResistanceComponent` : affiche la résistance en advanced mode (F3+H) - `GaggingComponent` : affiche le type de gag - `BlindingComponent` : rien (pas d'info utile) - `ShockComponent` : affiche "Shock: Manual" ou "Shock: Auto (Xs)" - `GpsComponent` : affiche "GPS Tracking" + zone radius - `ChokingComponent` : affiche "Choking Effect" - `AdjustableComponent` : rien (ajustement est visuel) --- ### A4. Champ `pose_type` + PoseTypeHelper **Fichiers :** - `src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenItemDefinition.java` — ajouter `@Nullable String poseType` - `src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenItemParser.java` — parser `"pose_type"` - Créer `src/main/java/com/tiedup/remake/v2/bondage/PoseTypeHelper.java` ```java public static PoseType getPoseType(ItemStack stack) { // V2: read from data-driven definition DataDrivenItemDefinition def = DataDrivenItemRegistry.get(stack); if (def != null && def.poseType() != null) { try { return PoseType.valueOf(def.poseType()); } catch (IllegalArgumentException e) { return PoseType.STANDARD; } } // V1 fallback: instanceof ItemBind if (stack.getItem() instanceof ItemBind bind) { return bind.getPoseType(); } return PoseType.STANDARD; } ``` **Note mixin :** Les mixins (`MixinPlayerModel`, `MixinCamera`, etc.) appellent `itemBind.getPoseType()`. Ils devront migrer vers `PoseTypeHelper.getPoseType(stack)` en Branch C. Le helper doit être dans un package chargé tôt — `v2/bondage/` est OK car le mod est chargé avant les mixins client. --- ### A5. BindModeHelper **Fichier :** Créer `src/main/java/com/tiedup/remake/v2/bondage/BindModeHelper.java` Méthodes statiques pures NBT (clé `"bindMode"`) : - `hasArmsBound(ItemStack)` → true si mode "arms" ou "full" - `hasLegsBound(ItemStack)` → true si mode "legs" ou "full" - `getBindModeId(ItemStack)` → "full", "arms", ou "legs" - `cycleBindModeId(ItemStack)` → full→arms→legs→full, retourne le nouveau mode - `getBindModeTranslationKey(ItemStack)` → clé i18n - `isBindItem(ItemStack)` → true si l'item a la région ARMS dans sa definition V2, OU est `instanceof ItemBind` **Référence :** `ItemBind.java` lignes 64-160 pour les méthodes statiques existantes. --- ### A6. CollarHelper (complet) **Fichier :** Créer `src/main/java/com/tiedup/remake/v2/bondage/CollarHelper.java` Extraire TOUTES les méthodes NBT de `ItemCollar` (1407 lignes) + 5 sous-classes en méthodes statiques. Sections : **Ownership :** - `isOwner(ItemStack, Player)`, `isOwner(ItemStack, UUID)` - `getOwners(ItemStack)` → Set - `addOwner(ItemStack, UUID, String name)`, `removeOwner(ItemStack, UUID)` - `getBlacklist(ItemStack)`, `addToBlacklist(ItemStack, UUID)`, `removeFromBlacklist(ItemStack, UUID)` - `getWhitelist(ItemStack)`, `addToWhitelist(ItemStack, UUID)`, `removeFromWhitelist(ItemStack, UUID)` **Collar features :** - `isCollar(ItemStack)` → check OwnershipComponent presence OR instanceof ItemCollar - `getNickname(ItemStack)`, `setNickname(ItemStack, String)` - `isKidnappingModeEnabled(ItemStack)`, `setKidnappingModeEnabled(ItemStack, boolean)` - `getCellId(ItemStack)`, `setCellId(ItemStack, UUID)` - `shouldTieToPole(ItemStack)`, `setShouldTieToPole(ItemStack, boolean)` - `shouldWarnMasters(ItemStack)`, `setShouldWarnMasters(ItemStack, boolean)` - `isBondageServiceEnabled(ItemStack)`, `setBondageServiceEnabled(ItemStack, boolean)` - `getServiceSentence(ItemStack)`, `setServiceSentence(ItemStack, String)` **Shock :** - `canShock(ItemStack)` → check ShockComponent presence OR instanceof ItemShockCollar - `isPublic(ItemStack)`, `setPublic(ItemStack, boolean)` - `getShockInterval(ItemStack)` → depuis ShockComponent ou ItemShockCollarAuto **GPS :** - `hasGPS(ItemStack)` → check GpsComponent presence OR instanceof ItemGpsCollar - `hasPublicTracking(ItemStack)`, `setPublicTracking(ItemStack, boolean)` - `getSafeSpots(ItemStack)`, `addSafeSpot(ItemStack, ...)`, `removeSafeSpot(ItemStack, int)` - `isActive(ItemStack)`, `setActive(ItemStack, boolean)` **Choke :** - `isChokeCollar(ItemStack)` → check ChokingComponent presence OR instanceof ItemChokeCollar - `isChoking(ItemStack)`, `setChoking(ItemStack, boolean)` - `isPetPlayMode(ItemStack)`, `setPetPlayMode(ItemStack, boolean)` **Alert suppression :** - `runWithSuppressedAlert(Runnable)` — ThreadLocal, délègue à `ItemCollar` pendant la coexistence - `isRemovalAlertSuppressed()` → lit le ThreadLocal **Référence :** `ItemCollar.java` (1407 lignes), `ItemShockCollar.java` (133 lignes), `ItemGpsCollar.java` (369 lignes), `ItemChokeCollar.java` (154 lignes), `ItemShockCollarAuto.java` (58 lignes). --- ### A7. OwnershipComponent (complet) **Fichier :** Créer `src/main/java/com/tiedup/remake/v2/bondage/component/OwnershipComponent.java` **Modifier :** `ComponentType.java` — ajouter `OWNERSHIP` JSON : `"ownership": {}` Lifecycle hooks : - `onEquipped(stack, entity)` : enregistrer dans CollarRegistry (extraire de `ItemCollar.registerCollarInRegistry`). **Note :** le owner initial (le player qui equip) n'est pas dans la signature `onEquipped(stack, entity)`. Options : lire le owner depuis le NBT du stack (déjà écrit par l'interaction flow), ou passer par un tag temporaire. - `onUnequipped(stack, entity)` : alerter les owners (si pas supprimé via ThreadLocal), désenregistrer du CollarRegistry, reset auto-shock timer. - `appendTooltip` : nickname, lock status, kidnapping mode, cell ID, bondage service, shock status, GPS status. - `blocksUnequip` : si locked via ILockable. - **Override `dropLockOnUnlock()`** : retourner false pour les collars (pas de padlock drop). Note : ceci doit être sur DataDrivenBondageItem, pas sur le composant (ILockable est sur l'Item, pas sur le composant). → DataDrivenBondageItem override `dropLockOnUnlock()` quand OwnershipComponent est présent. --- ### A8. TyingInteractionHelper + DataDrivenBondageItem extensions **Fichier :** Créer `src/main/java/com/tiedup/remake/v2/bondage/TyingInteractionHelper.java` **Modifier :** `src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenBondageItem.java` Extraire le flow tying de `ItemBind.interactLivingEntity()` dans `TyingInteractionHelper` : - Accepte `(ServerPlayer player, LivingEntity target, ItemStack stack, InteractionHand hand)` - Crée TyingPlayerTask, gère la progress bar, consume l'item, equipe via V2 Dans `DataDrivenBondageItem` : - `use()` : si regions contient ARMS → shift+click cycle bind mode (son + message action bar). Server-side only. - `interactLivingEntity()` : routing par région : - ARMS → `TyingInteractionHelper` (tying task flow) - NECK → collar equip flow (add owner, register CollarRegistry, play sound) — extraire de `ItemCollar.interactLivingEntity()` - MOUTH/EYES/EARS/HANDS → instant equip (existant via AbstractV2BondageItem) --- ### A9. Réécrire StruggleBinds/StruggleCollar pour V2 **Fichiers :** - `src/main/java/com/tiedup/remake/state/struggle/StruggleBinds.java` - `src/main/java/com/tiedup/remake/state/struggle/StruggleCollar.java` `StruggleBinds.canStruggle()` : - Actuellement : `instanceof ItemBind` → rejette V2 - Fix : accepter `instanceof IV2BondageItem` avec region ARMS, OU `instanceof ItemBind` - Résistance : si V2 → `ResistanceComponent.getBaseResistance()`. Si V1 → existant via `IHasResistance`. `StruggleCollar` : - Actuellement : `instanceof ItemCollar` → rejette V2 - Fix : accepter items avec `OwnershipComponent` (via `CollarHelper.isCollar(stack)`) - Résistance collar : via `ResistanceComponent` ou `IHasResistance` --- ### A10. DataDrivenBondageItem implémente IHasGaggingEffect/IHasBlindingEffect **Fichier :** `src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenBondageItem.java` Ajouter `implements IHasGaggingEffect, IHasBlindingEffect` sur la classe. Les 7+ call sites qui font `instanceof IHasGaggingEffect` continueront de fonctionner. **Problème :** Ces interfaces sont des markers (pas de méthodes). La simple présence de l'interface signifie "a l'effet". Mais `DataDrivenBondageItem` est un singleton — TOUS les items data-driven auront ces interfaces. **Solution :** Ne PAS implémenter les interfaces marker sur la classe. À la place, lors de la migration Branch C, convertir les call sites vers des component checks. C'est plus propre. → **A10 annulé.** Les call sites migreront en Branch C vers `DataDrivenBondageItem.getComponent(stack, GAGGING, ...) != null`. --- ### A11. Fix PacketSelfBondage — routing par région **Fichier :** `src/main/java/com/tiedup/remake/network/selfbondage/PacketSelfBondage.java` Dans `handleV2SelfBondage()`, ajouter du routing par région : ```java Set regions = v2Item.getOccupiedRegions(stack); if (regions.contains(BodyRegionV2.NECK)) { // Cannot self-collar return; } if (regions.contains(BodyRegionV2.ARMS)) { // Tying task (existing flow) handleV2SelfBind(player, stack, v2Item, state); } else { // Accessories: instant equip (no tying delay) handleV2SelfAccessory(player, stack, v2Item, state); } ``` Créer `handleV2SelfAccessory()` basé sur le pattern V1 `handleSelfAccessory()` (instant equip, swap si déjà équipé, locked check). ### A12. NPC speed reduction **Fichier :** À déterminer (composant ou event handler) `ItemBind.onEquipped()` appelle `RestraintEffectUtils.applyBindSpeedReduction(entity)` pour les NPCs. Cette logique doit survivre à la migration. **Option :** Ajouter dans `DataDrivenBondageItem.onEquipped()` (après le component dispatch) un check : si entity n'est PAS un Player ET l'item a la région ARMS → appeler `RestraintEffectUtils.applyBindSpeedReduction(entity)`. --- ## Vérification - [ ] `make build` — compilation clean - [ ] Struggle fonctionne avec V1 items (ropes, chain, collar) - [ ] Struggle fonctionne avec V2 items (test_component_gag.json ou nouveau test item) - [ ] Self-bondage V2 : ARMS → tying delay, MOUTH → instant, NECK → rejeté - [ ] Tooltips : composants contribuent des lignes - [ ] PoseTypeHelper résout V1 (ItemBind) et V2 (definition.poseType) - [ ] CollarHelper.isOwner() fonctionne sur V1 ET V2 collars - [ ] MCP reindex après la branche