Files
TiedUp-/docs/plans/D01-branch-A-bridge-utilities.md
NotEvil 3d61c9e9e6 feat(D-01/C): consumer migration — 85 files migrated to V2 helpers
Phase 1 (state): PlayerBindState, PlayerCaptorManager, PlayerEquipment,
  PlayerDataRetrieval, PlayerLifecycle, PlayerShockCollar, StruggleAccessory
Phase 2 (client): AnimationTickHandler, NpcAnimationTickHandler, 5 render
  handlers, DamselModel, 3 client mixins, SelfBondageInputHandler,
  SlaveManagementScreen, ActionPanel, SlaveEntryWidget, ModKeybindings
Phase 3 (entities): 28 entity/AI files migrated to CollarHelper,
  BindModeHelper, PoseTypeHelper, createStack()
Phase 4 (network): PacketSlaveAction, PacketMasterEquip,
  PacketAssignCellToCollar, PacketNpcCommand, PacketFurnitureForcemount
Phase 5 (events): RestraintTaskTickHandler, PetPlayRestrictionHandler,
  PlayerEnslavementHandler, ChatEventHandler, LaborAttackPunishmentHandler
Phase 6 (commands): BondageSubCommand, CollarCommand, NPCCommand,
  KidnapSetCommand
Phase 7 (compat): MCAKidnappedAdapter, MCA mixins
Phase 8 (misc): GagTalkManager, PetRequestManager, HangingCagePiece,
  BondageItemBlockEntity, TrappedChestBlockEntity, DispenserBehaviors,
  BondageItemLoaderUtility, RestraintApplicator, StruggleSessionManager,
  MovementStyleResolver, CampLifecycleManager

Some files retain dual V1/V2 checks (instanceof V1 || V2Helper) for
coexistence — V1-only branches removed in Branch D.
2026-04-15 00:16:50 +02:00

14 KiB

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 :

// 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
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 :

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