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.
This commit is contained in:
NotEvil
2026-04-15 00:16:50 +02:00
parent 9f2b06b711
commit 449178f57b
85 changed files with 2885 additions and 777 deletions

413
docs/AUDIT.md Normal file
View File

@@ -0,0 +1,413 @@
# TiedUp! — Codebase Audit
> Audit complet du mod, systeme par systeme, pour consolider la base et la coherence du code.
---
## Objectif
Passer en revue **chaque systeme** du mod en profondeur pour :
1. **Consolider** — identifier et corriger les incohérences, le code mort, la duplication
2. **Améliorer** — proposer des refactors ciblés là où l'architecture freine le développement
3. **Documenter** — laisser une trace des décisions et de l'état de chaque système
Ce n'est pas un rewrite. On stabilise ce qui existe.
---
## Règles de l'audit
- **Système par système**, dans l'ordre de dépendance (fondations d'abord)
- **Deep dive** — on remonte les sources, les dépendants, les call chains via le MCP
- **Pas de changement sans discussion** — on constate, on discute, puis on corrige
- **Pas de sur-ingénierie** — on fixe les vrais problèmes, on ne refactor pas pour le plaisir
- **Reindex MCP** après chaque batch de corrections significatives
### Processus par système
Chaque audit suit ce cycle :
```
1. EXPLORATION — Lire le code, tracer les dépendances (MCP)
2. CONSTATS — Documenter les problèmes trouvés (dans ce fichier)
3. VÉRIFICATION — Relire, confirmer, pas de faux positifs
4. PROPOSITIONS — Pour chaque constat :
→ Fix direct (bug, incohérence simple)
→ Amélioration architecturale (refactor ciblé, avec justification)
5. DISCUSSION — Valider avec l'utilisateur avant d'implémenter
6. CORRECTION — Appliquer les changements validés
7. REINDEX — MCP reindex après corrections
```
On ne passe au système suivant qu'une fois le cycle terminé.
---
## Ordre d'audit
| # | Système | Packages | Status |
|---|---------|----------|--------|
| 1 | Core + Registries | `core`, `ModItems`, `ModEntities`, `ModNetwork`, `ModGameRules` | Done |
| 2 | State | `state`, `state/components`, `state/hosts`, `state/struggle` | Done |
| 3 | Items (v1) + V2 Bondage | `items/**`, `v2/bondage/**` | Done |
| 4 | Network | `network/**` | Done |
| 5 | Client Animation + GLTF | `client/animation/**`, `client/gltf` | Done |
| 6 | Furniture | `v2/furniture/**` | Done |
| 7 | Entities + AI | `entities/**` | Done |
| 8 | Events | `events/**` | Done |
| 9 | Dialogue + Personality | `dialogue/**`, `personality` | Done |
| 10 | Cells / Prison / Blocks | `cells`, `prison/**`, `blocks/**` | Done |
| 11 | Compat | `compat/mca/**`, `compat/wildfire/**` | Done |
| 12 | Util + Commands + Worldgen + Resources | `util/**`, `commands/**`, `worldgen`, resources | Done |
---
## Échelle de sévérité
| Niveau | Signification | Action |
|--------|--------------|--------|
| **Haute** | Bug actif, bloquant, ou dette qui empêche une feature majeure | Corriger avant de continuer |
| **Moyenne** | Incohérence ou fragilité qui va poser problème à terme | Planifier la correction |
| **Basse** | Code smell, naming, organisation — pas urgent | Corriger si on touche le fichier |
| **Cosmétique** | Style, formatting, commentaires | Optionnel |
---
## Constats
### #1 — Core + Registries
**Positif :**
- DeferredRegister correct partout
- Factory pattern propre pour les variant items
- SettingsAccessor bridge solide (safeGet, BUG-003 fix)
- Séparation client/server correcte
- Data-driven reload listeners bien câblés
**Problèmes :** (tous vérifiés ✓)
| ID | Sévérité | Constat | Fichier(s) | Proposition |
|----|----------|---------|------------|-------------|
| C-01 | Haute | SystemMessageManager : 80+ messages hardcodés en anglais, zero `Component.translatable()` | `core/SystemMessageManager.java` | **Fix** : Remplacer `Component.literal()` par `Component.translatable()` avec clés dans `en_us.json`. Garder l'enum + les couleurs, changer uniquement le transport du texte. |
| C-02 | Moyenne | 25 settings triplés entre ModConfig + ModGameRules + SettingsAccessor. Historique de bug (BUG-001 : defaults désyncés) | `core/ModConfig.java`, `core/ModGameRules.java`, `core/SettingsAccessor.java` | **Archi** : Évaluer si les GameRules sont vraiment utiles (ils dupliquent la config serveur). Si oui, centraliser les defaults dans une seule source. Si non, supprimer les GameRules doublons et garder ModConfig + SettingsAccessor. |
| C-03 | Moyenne | ModNetwork : 74 IDs séquentiels. Pattern standard Forge mais fragile à l'insertion. `PROTOCOL_VERSION` protège partiellement. | `network/ModNetwork.java` | **Pas d'action immédiate** : pattern idiomatique Forge 1.20.1. Bumper `PROTOCOL_VERSION` à chaque ajout/suppression de packet. Documenter cette règle. |
| C-04 | Basse | ChokeEffect importe EntityMaster (core → entities) pour check "non-lethal when master-owned" | `core/ChokeEffect.java` | **Fix** : Extraire le check via un tag NBT ou capability sur le Player, consultable sans dépendre d'EntityMaster. |
| C-05 | Basse | 10 types d'entités NPC utilisent DamselRenderer — nom spécifique pour un usage générique | `core/TiedUpMod.java`, `client/renderer/DamselRenderer.java` | **Fix** : Renommer `DamselRenderer``NpcRenderer` ou `HumanoidNpcRenderer`. |
| C-06 | Cosmétique | 47+ FQCNs dans le corps de TiedUpMod au lieu d'imports | `core/TiedUpMod.java` | **Fix** : Remplacer par des imports. Faire quand on touche le fichier. |
| C-07 | Basse | ModConfig.ServerConfig : 127 valeurs configurables, 628 lignes, 20+ catégories dans une classe | `core/ModConfig.java` | **Archi** : Découper en sous-classes par domaine (StruggleConfig, NpcConfig, EconomyConfig, etc.) au prochain refactor config. Pas urgent. |
### #2 — State
**Positif :**
- Hiérarchie d'interfaces bien conçue (ISP) : `IRestrainableEntity``ICapturable``IBondageState``IRestrainable` (union)
- Décomposition en 11 components (PlayerEquipment, PlayerStateQuery, etc.) — bonne intention
- `IBondageState` n'expose que l'API V2 region-based — l'interface publique est propre
- `CollarRegistry` et `SocialData` : implémentations `SavedData` propres avec persistence correcte
- `IPlayerLeashAccess` : séparation mixin clean pour le système de laisse
- `PlayerCaptorManager` : thread-safe avec `CopyOnWriteArrayList` et `synchronized`
**Problèmes :** (tous vérifiés ✓)
| ID | Sévérité | Constat | Fichier(s) | Proposition |
|----|----------|---------|------------|-------------|
| S-01 | Moyenne | PlayerBindState : god class de façade, 1237 lignes, ~80 méthodes de pure délégation vers 11 components | `state/PlayerBindState.java` | **Archi** : Évaluer si les consommateurs peuvent utiliser les components directement via des accesseurs typés (`state.equipment().putBindOn()`) au lieu de passer par la façade. Réduirait la surface de ~80 méthodes de boilerplate. Risque : gros refactor, beaucoup de call sites. |
| S-02 | Moyenne | 8 champs de mouvement publics (`hopCooldown`, `lastX`, etc.) directement mutés par MovementStyleManager (33 accès directs) | `state/PlayerBindState.java`, `v2/bondage/movement/MovementStyleManager.java` | **Fix** : Extraire dans un `MovementState` component avec getters/setters. MovementStyleManager opère via ce component. |
| S-03 | Basse | API V1 slot-based (`putBindOn`, `takeGagOff`) coexiste avec V2 region-based sur la classe concrète PlayerBindState. L'interface `IBondageState` est propre (V2 only). | `state/PlayerBindState.java` | **Archi** : Marquer les méthodes V1 `@Deprecated` pour guider la migration. Les call sites (commands, etc.) devraient migrer vers `equip(BodyRegionV2)`. Pas urgent car l'interface est déjà propre. |
| S-04 | Basse | `hasLegsBound()` lit le slot ARMS (pas LEGS) — design V1 intentionnel : un seul item "bind" couvre bras+jambes via NBT mode. Pas un bug. | `state/IBondageState.java` | **Pas d'action immédiate** : cohérent avec le système actuel. Documenter le design dans un commentaire. Deviendra un vrai problème quand des items LEGS dédiés seront ajoutés en V2. |
| S-05 | Moyenne | Thread safety incohérente : `volatile` (3 champs), `synchronized` (5 méthodes), rien (le reste). La paire `isStruggling`/`struggleStartTick` peut être observée dans un état inconsistant. | `state/PlayerBindState.java` | **Fix** : Définir une stratégie claire. Les champs accédés cross-thread (mouvement, struggle, captor) doivent être soit volatile soit synchronized. Auditer chaque champ. |
| S-06 | Basse | `HumanChairHelper` dans `state/` mais c'est un utilitaire pur sans lien avec le state. Utilisé par AI, animation, mixins. | `state/HumanChairHelper.java` | **Fix** : Déplacer dans `items/base/` (à côté de `PoseType` dont il dépend) ou `util/`. Faire quand on touche le fichier. |
### #3 — Items (V1) + V2 Bondage
**Positif :**
- `IV2BondageItem` bien conçu : multi-region, stack-aware, pose priority, blocked regions
- `V2EquipmentManager` : conflict resolution solide (swap single, supersede global)
- `V2EquipmentHelper` : facade propre pour read/write/sync
- `DataDrivenBondageItem` : singleton + NBT registry pattern intelligent pour items data-driven
- `ILockable` : système lock/jam/key complet et cohérent
- `IHasResistance` : résistance NBT avec migration legacy, bien documentée
- `BodyRegionV2` enum complet (15 régions, global flag)
- Variant enums + factory pattern (BindVariant, GagVariant, etc.) propres
**Problèmes :** (tous vérifiés ✓)
| ID | Sévérité | Constat | Fichier(s) | Proposition |
|----|----------|---------|------------|-------------|
| I-01 | ~~Haute~~ | ~~Deux hiérarchies d'interfaces parallèles~~ | | **RÉSOLU** : suppression V1 (voir Décision D-01) |
| I-02 | ~~Haute~~ | ~~V1 items bypassent la conflict resolution V2~~ | | **RÉSOLU** : suppression V1 (voir Décision D-01) |
| I-03 | Moyenne | `DataDrivenBondageItem.getBaseResistance()` scanne tous items équipés et retourne MAX difficulty car `IHasResistance` n'a pas de paramètre ItemStack. Workaround documenté mais approximatif — peut surestimer la résistance. | `v2/bondage/datadriven/DataDrivenBondageItem.java` | **Archi** : Ajouter `getBaseResistance(ItemStack, LivingEntity)` à `IHasResistance` avec default qui délègue à l'ancienne méthode. DataDrivenBondageItem override la version stack-aware. |
| I-04 | ~~Basse~~ | ~~IBondageItem.getBodyRegion() single-region~~ | | **RÉSOLU** : suppression V1 (voir Décision D-01) |
| I-05 | ~~Moyenne~~ | ~~V1 items pas @Deprecated~~ | | **RÉSOLU** : suppression V1 (voir Décision D-01) |
### Décision D-01 — Suppression totale du système V1 + Composants data-driven
**Décision :** Le système V1 items est supprimé entièrement. Tous les items deviennent data-driven V2. La logique complexe (shock, GPS, lock, gagging, blinding, resistance, etc.) est extraite en **composants réutilisables** déclarables dans le JSON.
**Périmètre de suppression :**
- `IBondageItem` (interface)
- `ItemBind`, `ItemGag`, `ItemBlindfold`, `ItemCollar`, `ItemEarplugs`, `ItemMittens` (abstracts)
- `GenericBind`, `GenericGag`, `GenericBlindfold`, `GenericEarplugs`, `GenericMittens` (concrets)
- `BindVariant`, `GagVariant`, `BlindfoldVariant`, `EarplugsVariant`, `MittensVariant` (enums)
- `ItemClassicCollar`, `ItemShockCollar`, `ItemShockCollarAuto`, `ItemGpsCollar`, `ItemChokeCollar` (collars)
- `ItemHood`, `ItemMedicalGag`, `ItemBallGag3D` (combos/special)
- Registrations V1 dans `ModItems`
- `PlayerEquipment.equipInRegion()` → remplacé par `V2EquipmentManager.tryEquip()`
**Interfaces à conserver / migrer :**
- `ILockable` — conservé, utilisé par V2 items
- `IHasResistance` — conservé, refactoré avec paramètre ItemStack (I-03)
- `IKnife` — conservé (outils, pas des bondage items)
- `IAdjustable` — à évaluer (potentiellement composant)
- `IHasBlindingEffect`, `IHasGaggingEffect` — deviennent des composants
**Système de composants envisagé :**
Chaque composant est une logique serveur réutilisable qu'un item data-driven peut déclarer :
```json
{
"type": "tiedup:bondage_item",
"display_name": "Shock Collar",
"model": "tiedup:models/gltf/shock_collar.glb",
"regions": ["NECK"],
"components": {
"lockable": true,
"shock": { "auto_interval": 200, "damage": 2.0 },
"gps": { "safe_zone_radius": 50 },
"gagging": { "comprehension": 0.2, "range": 10.0 },
"blinding": { "overlay": "tiedup:textures/overlay/blindfold.png" },
"resistance": { "base": 150 }
},
"escape_difficulty": 5,
"pose_priority": 10
}
```
Exemples de composants à extraire de la logique V1 existante :
| Composant | Source V1 | Comportement |
|-----------|-----------|-------------|
| `lockable` | `ILockable` | Lock/unlock, padlock, key matching, jam, lock resistance |
| `resistance` | `IHasResistance` | Struggle resistance, configurable base value |
| `shock` | `ItemShockCollar` | Auto-shock intervals, manual shock, damage |
| `gps` | `ItemGpsCollar` | Safe zone, zone violation detection, owner alerts |
| `gagging` | `IHasGaggingEffect` | Muffled speech, comprehension %, range limit |
| `blinding` | `IHasBlindingEffect` | Blindfold overlay, hardcore mode |
| `choking` | `ItemChokeCollar` | Air drain, darkness, slowness, non-lethal master mode |
| `adjustable` | `IAdjustable` | Tightness level, visual adjustment |
**Ce refactor est le plus gros chantier identifié par l'audit.** Il fera l'objet d'un plan d'implémentation dédié après la fin de l'audit.
### #4 — Network
**Positif :**
- `AbstractClientPacket` / `AbstractPlayerSyncPacket` — bon pattern de base, handle enqueue sur main thread, retry queue pour les players pas encore loaded
- `PacketRateLimiter` — token bucket complet avec catégories (struggle, minigame, action, selfbondage, ui). Thread-safe. Bon anti-spam.
- `SyncManager` — facade centralisée pour sync inventory/state/enslavement/struggle/clothes. Pattern `sendSync()` générique propre.
- `NetworkEventHandler` — gère correctement login sync, start-tracking sync, furniture reconnection, et fix MC-262715 (stale riding state)
- `PacketSlaveAction` — bonnes validations serveur : dimension check, distance check, collar ownership check, GPS permission check
- `PacketSelfBondage` — rate limited, route correctement V2 via `handleV2SelfBondage()` avec conflict check (`isRegionOccupied` + `isRegionBlocked`)
**Problèmes :**
| ID | Sévérité | Constat | Fichier(s) | Proposition |
|----|----------|---------|------------|-------------|
| N-01 | Moyenne | `PacketSelfBondage.handle()` contient 5 branches V1 (`instanceof ItemBind/ItemGag/ItemBlindfold/ItemMittens/ItemEarplugs`) qui devront être supprimées avec D-01. La branche V2 (`instanceof IV2BondageItem`) restera seule. | `network/selfbondage/PacketSelfBondage.java` | **Fix D-01** : Supprimer les branches V1, ne garder que la route V2. Simplifie massivement le fichier. |
| N-02 | Moyenne | 4 packets dépendent de `ItemCollar` (V1 class) : `PacketSlaveAction`, `PacketMasterEquip`, `PacketAssignCellToCollar`, `PacketNpcCommand`. La logique collar (ownership, canShock, hasGPS) est couplée à la classe Java. | `network/slave/PacketSlaveAction.java`, `network/slave/PacketMasterEquip.java`, `network/cell/PacketAssignCellToCollar.java`, `network/personality/PacketNpcCommand.java` | **Fix D-01** : Quand ItemCollar migre vers le système composants, ces packets devront checker les composants (ex: `hasComponent("shock")`) au lieu de `instanceof ItemCollar`. |
| N-03 | Basse | `PacketSyncBindState` sync des flags d'état V1 (isTiedUp, isGagged, isBlindfolded, etc.) séparément de `PacketSyncV2Equipment` qui sync le V2 capability. Potentiellement redondant post-suppression V1 — l'état peut être dérivé du V2 equipment. | `network/sync/PacketSyncBindState.java`, `v2/bondage/network/PacketSyncV2Equipment.java` | **Archi post-D-01** : Évaluer si `PacketSyncBindState` peut être supprimé et ses flags dérivés côté client depuis V2 equipment. Réduirait le nombre de packets sync. |
| N-04 | Basse | `SyncManager.syncAllPlayersTo()` envoie 4 packets distincts par joueur (V2Equipment, BindState, Enslavement, Struggle, + Clothes si applicable). Pour un serveur avec N joueurs, un login génère ~4N packets. | `network/sync/SyncManager.java` | **Archi** : Considérer un packet bulk `PacketSyncFullState` qui combine tout en un seul envoi. Pas urgent — 4N packets est acceptable pour les tailles de serveur visées. |
| N-05 | Cosmétique | Pas de `MCABondageManager` dans le package network, mais `PacketSyncMCABondage` existe — la logique MCA bondage sync est split entre `network/sync/` et `compat/mca/`. | `network/sync/PacketSyncMCABondage.java`, `compat/mca/` | **Pas d'action** : Acceptable pour un module de compatibilité. |
### #5 — Client Animation + GLTF
**Positif :**
- **Architecture 3 couches propre** : Context layer (pri 40) → Item layer (pri 42) → Furniture layer (pri 43). Priorités claires, bon découplage.
- **BondageAnimationManager** : API unifiée `playAnimation/playDirect/playContext/playFurniture` pour players et NPCs. Gestion des remote players (fallback stack), pending queue pour retry, furniture grace ticks pour éviter les stuck poses.
- **GlbAnimationResolver** : Fallback chain fidèle au ARTIST_GUIDE (FullSitStruggle → SitStruggle → FullStruggle → Struggle → FullIdle → Idle). Support variants (.1, .2) avec random selection.
- **GltfAnimationApplier** : Multi-item composite animation propre. Cache par state key, skip si unchanged. `applyMultiItemV2Animation()` merge les bones de plusieurs items dans un seul AnimationBuilder.
- **ContextGlbRegistry** : Hot-reload des GLB de contexte depuis resource packs. Atomic swap pour thread safety render.
- **AnimationContextResolver** : Résolution claire de contexte (sitting → struggling → movement style → sneaking → walking → idle). Version NPC séparée.
- **GLTF pipeline (12 fichiers)** : Zéro dépendance V1. Parser, cache, skinning engine, mesh renderer, bone mapper, pose converter — tout est V2 natif.
**Problèmes :**
| ID | Sévérité | Constat | Fichier(s) | Proposition |
|----|----------|---------|------------|-------------|
| A-01 | Moyenne | 6 fichiers animation dépendent de `ItemBind` et `PoseType` (V1) pour déterminer le type de pose (STANDARD, DOG, HUMAN_CHAIR) et le bind mode (arms/legs/full). | `tick/AnimationTickHandler.java`, `tick/NpcAnimationTickHandler.java`, `render/PlayerArmHideEventHandler.java`, `render/PetBedRenderHandler.java`, `render/DogPoseRenderHandler.java`, `util/AnimationIdBuilder.java` | **Fix D-01** : Quand les items V1 sont supprimés, la pose type et le bind mode doivent venir du système V2 (data-driven definition ou composant). `PoseType` peut être conservé comme enum mais lu depuis `DataDrivenItemDefinition` au lieu de `ItemBind.getPoseType()`. |
| A-02 | Basse | `StaticPoseApplier` dépend de `PoseType` — applique des rotations hardcodées par pose type (V1 fallback pour quand le GLTF n'est pas disponible). | `animation/StaticPoseApplier.java` | **Évaluer D-01** : Si tous les items ont un GLB, le static pose applier devient un fallback pur. Peut être conservé comme sécurité ou supprimé. |
| A-03 | Basse | `GltfAnimationApplier` a un toggle debug F9 hardcodé qui charge un GLB spécifique (`cuffs_prototype.glb`). | `client/gltf/GltfAnimationApplier.java` (l.~350) | **Fix** : Supprimer ou mettre derrière un flag dev. Mineur. |
| A-04 | Cosmétique | Le fallback animation dans `BondageAnimationManager.tryFallbackAnimation()` contient des patterns V1 spécifiques (`_arms_`, `sit_dog_`, `kneel_dog_`). Post-D-01, ces patterns n'existeront plus. | `animation/BondageAnimationManager.java` | **Fix D-01** : Nettoyer les fallbacks V1 obsolètes. Le système GLB a sa propre fallback chain (GlbAnimationResolver). |
### #6 — Furniture
**Positif :**
- **Architecture data-driven exemplaire** : `FurnitureDefinition` (record immuable) + `FurnitureRegistry` (volatile atomic swap) + `FurnitureParser` + `FurnitureServerReloadListener`. Exactement le même pattern que les bondage items V2 data-driven.
- **`ISeatProvider`** : interface propre et générique — conçue pour être implémentée par des monstres aussi (ARTIST_GUIDE: "monster seat system"). Bonne anticipation.
- **`SeatDefinition`** : record immuable avec tous les champs du guide artiste (blocked regions, lockable, locked difficulty, item difficulty bonus).
- **`EntityFurniture`** : Entity simple (pas LivingEntity), synced via `IEntityAdditionalSpawnData`. Dimensions variables depuis la definition. Animation state machine (IDLE → OCCUPIED → LOCKING → STRUGGLE → UNLOCKING). Seat assignments persistés en NBT.
- **`FurniturePlacerItem`** : singleton item avec NBT ID, snap-to-wall, floor-only. Même pattern que `DataDrivenBondageItem`.
- **`FurnitureAnimationContext`** : Conversion GLB → KeyframeAnimation avec bones sélectifs (blocked regions only). S'intègre proprement avec la furniture layer (pri 43) de BondageAnimationManager.
- **`FurnitureGltfData`** : Parsing dédié qui sépare furniture armature des Player_* seat skeletons dans un seul GLB. Fidèle à l'ARTIST_GUIDE.
- **Packets** : Rate limited, distance checks, permission checks (collar ownership pour forcemount).
- **Reconnection robuste** : `NetworkEventHandler.handleFurnitureReconnection()` restaure les joueurs locked dans un seat après déconnexion, avec teleport si le meuble n'existe plus.
**Problèmes :**
| ID | Sévérité | Constat | Fichier(s) | Proposition |
|----|----------|---------|------------|-------------|
| F-01 | Moyenne | `EntityFurniture` et `PacketFurnitureForcemount` dépendent de `ItemCollar` (V1) pour vérifier collar ownership avant forcemount. | `v2/furniture/EntityFurniture.java`, `v2/furniture/network/PacketFurnitureForcemount.java` | **Fix D-01** : Quand ItemCollar migre vers composants, le check ownership doit utiliser le composant `lockable` ou `collar` au lieu de `instanceof ItemCollar`. |
| F-02 | Basse | `FurnitureAnimationContext.create()` log "V1: skeleton parsing not yet implemented" quand `seatSkeleton` est null. Si le GLB n'a pas de skeleton data parsé, l'animation silencieusement ne se joue pas. | `v2/furniture/client/FurnitureAnimationContext.java` | **Évaluer** : Vérifier que le parser GLB furniture extrait toujours le skeleton. Si oui, le fallback est juste un safety net. Sinon, c'est un bug silencieux. |
**Verdict : Le système furniture est le plus propre du mod.** Zéro dette architecturale, fidèle au guide artiste, extensible (monster seats prêts). Les deux constats sont mineurs — un couplage V1 qui part avec D-01 et un fallback debug à vérifier.
### #7 — Entities + AI
**Hiérarchie d'héritage :**
```
PathfinderMob
└─ AbstractTiedUpNpc (1281 lignes, ~100 méthodes) — implements IRestrainable, IAnimatedPlayer, IV2EquipmentHolder
├─ EntityDamsel (834 lignes) — capturable NPC, personality, dialogue, inventory
│ ├─ EntityDamselShiny — variante rare
│ └─ EntityLaborGuard — garde de prison
└─ EntityKidnapper (2039 lignes, ~170 méthodes) — implements ICaptor, IDialogueSpeaker
└─ EntityKidnapperElite
├─ EntityKidnapperMerchant — marchand neutre/hostile
├─ EntityKidnapperArcher — attaque à distance
├─ EntitySlaveTrader — boss de camp
├─ EntityMaid — servante du trader
└─ EntityMaster (1192 lignes) — pet play system
```
**Positif :**
- **Composant-based decomposition pour Damsel** : `DamselBondageManager`, `DamselPersonalitySystem`, `DamselInventoryManager`, `DamselAIController`, `DamselAnimationController`, `DamselAppearance`, `NpcEquipmentManager`, `NpcCaptivityManager` — 8 components avec interfaces host (`IBondageHost`, `IAIHost`, `IAnimationHost`, etc.). Bonne intention.
- **Composant-based pour Kidnapper** : `KidnapperAggressionSystem`, `KidnapperAlertManager`, `KidnapperAppearance`, `KidnapperCaptiveManager`, `KidnapperCellManager`, `KidnapperCampManager`, `KidnapperStateManager`, `KidnapperSaleManager`, `KidnapperTargetSelector`, `KidnapperDataSerializer` — 10 components avec interfaces host. Très granulaire.
- **AI goals bien séparés** : 80+ goals dédiés par type de NPC. Chaque goal est une classe autonome avec une seule responsabilité (KidnapperCaptureGoal, MasterDogwalkGoal, NpcFarmCommandGoal, etc.).
- **V2 equipment intégré** : `AbstractTiedUpNpc` implémente `IV2EquipmentHolder`, utilise `V2BondageEquipment` directement. Les NPCs sont déjà sur le système V2.
- **State machines Kidnapper** : `KidnapperState` enum avec états clairs (IDLE, HUNTING, CAPTURING, FLEEING, etc.).
- **Master NPC complet** : pet play system avec task manager, state machine, punishment, dogwalk, furniture interaction — complexe mais fonctionnel.
**Problèmes :**
| ID | Sévérité | Constat | Fichier(s) | Proposition |
|----|----------|---------|------------|-------------|
| E-01 | Haute | **EntityKidnapper = 2039 lignes**, la plus grosse classe du mod. Malgré la décomposition en 10 components, la classe reste un god class. Elle mélange : ICaptor impl, targeting, capture equipment, sale system, job system, camp system, cell integration, alert system, NBT serialization, display name, dialogue, et des dizaines de getters/helpers. | `entities/EntityKidnapper.java` | **Archi** : Continuer la décomposition. Candidates : extraire le système de vente (`startSale`/`completeSale`/`cancelSale`/`abandonCaptive`) dans un component dédié, extraire le dialogue, extraire le ciblage. Objectif : ramener la classe sous 800 lignes. |
| E-02 | Haute | **AbstractTiedUpNpc = 1281 lignes** avec ~100 méthodes. Même pattern que PlayerBindState (S-01) — c'est une façade de délégation vers les components, mais doit aussi implémenter IRestrainable (30+ méthodes) directement. | `entities/AbstractTiedUpNpc.java` | **Archi** : La taille vient surtout de l'implémentation de IRestrainable. Évaluer si les méthodes bondage peuvent être déléguées à `DamselBondageManager` via un pattern `default` sur IRestrainable (mais IRestrainable est une interface, pas une classe — limité). Ou accepter la taille comme coût de l'implémentation multi-interface. |
| E-03 | Moyenne | **24 fichiers entities** dépendent de V1 item classes (`ItemBind`, `ItemCollar`, `PoseType`, `BindVariant`, etc.). C'est le package le plus impacté par D-01. | 24 fichiers (voir liste grep) | **Fix D-01** : Migration bulk. Les `instanceof ItemBind` deviennent `instanceof IV2BondageItem`, les `ItemCollar` checks deviennent des component checks. `PoseType` et `BindVariant` sont remplacés par des propriétés data-driven. |
| E-04 | Moyenne | **Héritage profond** : EntityMaid → EntityKidnapperElite → EntityKidnapper → AbstractTiedUpNpc → PathfinderMob. 5 niveaux. EntityMaid et EntitySlaveTrader héritent de toute la logique kidnapper (capture, targeting, sale) alors qu'ils n'utilisent pas tout. | `entities/EntityMaid.java`, `entities/EntitySlaveTrader.java` | **Archi** : Envisager une refactorisation vers composition plutôt qu'héritage. La Maid n'est PAS un kidnapper — elle ne devrait pas hériter de `canCapture()`, `getCaptureBindTime()`, etc. Long terme : AbstractTiedUpNpc → EntityDamsel (passive) / EntityKidnapper (hostile), et les autres types composent leurs comportements. Pas urgent mais dette croissante. |
| E-05 | Basse | `EntityDamsel` et `EntityKidnapper` ont des hiérarchies de host interfaces parallèles : `damsel/components/IBondageHost`, `damsel/components/IAIHost` vs `kidnapper/components/IAIHost`, `kidnapper/components/ICaptiveHost`, etc. Certaines pourraient être unifiées. | `entities/damsel/components/*.java`, `entities/kidnapper/components/*.java` | **Pas d'action immédiate** : Les interfaces host sont des contrats internes de chaque sous-arbre. Les unifier créerait un couplage horizontal. Acceptable tel quel. |
| E-06 | Basse | `EntityMaster` (1192 lignes) contient le pet play system complet. Components `MasterPetManager`, `MasterTaskManager`, `MasterStateManager` existent mais la classe orchestre encore beaucoup de logique. | `entities/EntityMaster.java` | **Archi** : Même recommandation que E-01 — continuer la décomposition. Moins urgent car le système est plus cohérent (une seule responsabilité : pet play). |
### #8 — Events
**27 handlers, 5722 lignes, 8 domaines** (camp, captivity, combat, interaction, lifecycle, restriction, system).
**Positif :** Bonne séparation par domaine. La plupart des handlers sont focalisés (85-200 lignes).
| ID | Sévérité | Constat | Proposition |
|----|----------|---------|-------------|
| EV-01 | Moyenne | `RestraintTaskTickHandler` (675 lignes, 12 @SubscribeEvent) — consolide tous les ticks restraint. | Découper par type de tâche (tying, untying, force-feeding, shock checks). |
| EV-02 | Moyenne | `BondageItemRestrictionHandler` (544 lignes, 12 @SubscribeEvent) — consolide toutes les restrictions. | Découper par type de restriction (legs, arms, gags, etc.). |
| EV-03 | Moyenne | 7/27 handlers importent des classes V1 (`ItemBind`, `ItemCollar`, `ItemGag`, `IKnife`, `ILockable`). | **Fix D-01** : Migrer vers V2 checks (composants). |
### #9 — Dialogue + Personality
**31 fichiers, 7625 lignes.** Dialogue 100% data-driven (JSON par personality × speaker type). Personality enum-based (11 types) avec state machine (needs, mood, commands).
**Positif :** Pipeline de chargement JSON propre (default → personality override → speaker-type). 18 catégories de dialogue. Dépendance unidirectionnelle (dialogue → personality, pas l'inverse). Seulement 3 fichiers importent du V1.
| ID | Sévérité | Constat | Proposition |
|----|----------|---------|-------------|
| DI-01 | Moyenne | 6 god classes dans dialogue/ (EntityDialogueManager 622l, ConversationManager 564l, GagTalkManager 557l, DialogueLoader 469l, DialogueBridge 463l, DialogueManager 428l). | Acceptable pour la complexité du système. `DialogueBridge` (mapping legacy → new) peut être supprimé après D-01. |
| DI-02 | Basse | `PersonalityState` (709 lignes) — god class conteneur d'état NPC (needs, mood, commands, jobs, home). | Continuer la décomposition si ça grossit. OK pour l'instant. |
| DI-03 | Basse | 3 fichiers importent V1 (`GagTalkManager` → ItemGag, `ToolMode` → ItemBind, `PetRequestManager` → BindVariant). | **Fix D-01** : Migrer. |
### #10 — Cells / Prison / Blocks
**41 fichiers, 13329 lignes.** Système SavedData massif (CellRegistryV2, PrisonerManager, CampOwnership, ConfiscatedInventoryRegistry).
**Positif :** Architecture SavedData correcte. Spatial indexing dans CellRegistryV2. Séparation services (PrisonerService, ItemService, BondageService).
| ID | Sévérité | Constat | Proposition |
|----|----------|---------|-------------|
| CP-01 | Haute | `PrisonerService` (1058 lignes) — plus grosse classe du package, gère tout le lifecycle prisonnier. | Décomposer : labor, ransom, confiscation pourraient être des services séparés. |
| CP-02 | Moyenne | `MarkerBlockEntity` (1146 lignes) — god class block entity, gère spawning + teleportation + cell deletion. | Extraire la logique spawning et teleportation dans des helpers. |
| CP-03 | Moyenne | `BondageItemBlockEntity` utilise 6 imports V1 (ItemBind, ItemGag, ItemBlindfold, ItemCollar, ItemEarplugs, GenericClothes) pour valider les items dans les trap blocks. | **Fix D-01** : Remplacer par `instanceof IV2BondageItem` + component checks. |
| CP-04 | Basse | `CellRegistryV2` (903 lignes) — gros mais justifié par les index spatiaux. | Acceptable. |
### #11 — Compat (MCA + Wildfire)
**24 fichiers, 6536 lignes.** MCA très couplé (21 fichiers, 5 sous-systèmes). Wildfire léger (3 fichiers, rendu only).
| ID | Sévérité | Constat | Proposition |
|----|----------|---------|-------------|
| CO-01 | Haute | `MCAKidnappedAdapter` (907 lignes) — implémente IRestrainable complet pour les villagers MCA. God class + dépend de V1 items. | **Fix D-01** : Migrer V1 → V2. Décomposer en components comme AbstractTiedUpNpc. |
| CO-02 | Moyenne | `WildfireDamselLayer` (988 lignes) — rendu physique très complexe. | Acceptable pour un système de rendu physique. Pas urgent. |
| CO-03 | Basse | MCA compat utilise WeakHashMap et reflection pour détection — bon pattern de découplage. | Pas d'action. |
### #12 — Util + Commands + Worldgen
**55 fichiers, 15475 lignes.**
| ID | Sévérité | Constat | Proposition |
|----|----------|---------|-------------|
| UC-01 | Haute | `BondageSubCommand` (1232 lignes) — monolithique, contient 16 sous-commandes (tie, untie, gag, collar, etc.) dans un seul fichier. | Découper : TieCommands, GagCommands, CollarCommands, etc. |
| UC-02 | Haute | `RoomTheme` (1368 lignes) — config hardcodée de palettes de blocs pour worldgen. | **Archi** : Externaliser en data-driven (JSON). C'est exactement le type de contenu qui devrait être configurable. |
| UC-03 | Moyenne | 7 fichiers importent V1 items (commands + RestraintApplicator + MCA adapter + HangingCagePiece). | **Fix D-01** : Migrer. |
| UC-04 | Basse | `NPCCommand` (764 lignes) — gros mais focalisé sur le spawning/state NPC. | Acceptable, pourrait split par entity type. |
---
## Bilan Final
### Statistiques
- **12/12 systèmes audités**
- **744 classes, 233k lignes** analysées
- **38 constats** documentés (+ 4 résolus par D-01)
- **1 décision architecturale majeure** (D-01 : suppression V1 + composants data-driven)
### Classement par santé
| Rang | Système | Verdict | Problèmes |
|------|---------|---------|-----------|
| 1 | Furniture | Exemplaire | 2 mineurs |
| 2 | Animation + GLTF | Excellent | Résidus V1 seulement |
| 3 | Network | Solide | Résidus V1 seulement |
| 4 | Dialogue + Personality | Bon | God classes acceptables |
| 5 | Events | Bon | 2 handlers trop gros |
| 6 | Core | Dette technique | i18n, config triple |
| 7 | State | Croissance organique | God class, thread safety |
| 8 | Cells / Prison | Fonctionnel mais lourd | 11 god classes |
| 9 | Compat | Fonctionnel mais couplé | MCA adapter 907l |
| 10 | Util / Commands / Worldgen | Fonctionnel | BondageSubCmd 1232l, RoomTheme 1368l |
| 11 | Entities + AI | Riche mais massif | 2039l god class, héritage 5 niveaux |
| 12 | Items V1/V2 | **Point critique** | **D-01 : suppression totale V1** |
### Priorités de correction
| Priorité | Chantier | Impact |
|----------|----------|--------|
| **P0** | **D-01 : Suppression V1 + composants data-driven** | Élimine la dette #1 du mod. Impacte ~60 fichiers. Requiert un plan dédié. |
| **P1** | C-01 : i18n SystemMessageManager | Requis pour toute traduction du mod. |
| **P1** | UC-02 : RoomTheme → data-driven | 1368 lignes hardcodées de config worldgen. |
| **P2** | E-01/E-02 : Décomposition EntityKidnapper/AbstractTiedUpNpc | 2039 + 1281 lignes. Améliore la maintenabilité entities. |
| **P2** | CP-01 : Décomposition PrisonerService | 1058 lignes. |
| **P2** | UC-01 : Split BondageSubCommand | 1232 lignes en 1 fichier. |
| **P3** | S-02 : Encapsuler MovementState | 8 champs publics mutés directement. |
| **P3** | S-05 : Thread safety cohérente | 3 stratégies sans cohérence dans PlayerBindState. |
| **P3** | C-02 : Unifier Config/GameRules | 25 settings triplés. |
| **P4** | Renommages (C-05 DamselRenderer, C-06 FQCNs) et cleanups cosmétiques. |
### D-01 Phase 1 — Suivi implémentation
**Branche :** `feature/d01-component-system` (17 commits, build clean)
**Review adversariale :** 3 critiques + 5 hauts trouvés et corrigés.
**Problèmes notés (non bloquants, à traiter lors de la migration Phase 2) :**
| ID | Constat | Action |
|----|---------|--------|
| SMELL-002 | `GaggingComponent` n'a aucun consommateur — `GagTalkManager` lit `GagMaterial.getComprehension()`, pas le composant. | Lors de la migration Phase 2, faire pointer `GagTalkManager` vers le composant pour les items data-driven. |
| SMELL-003 | Duplication sémantique entre le champ top-level `lockable` (boolean) et le composant `LockableComponent`. Un item doit configurer les deux pour un lock complet. | Lors de la migration Phase 2, le champ `lockable` devrait être dérivé de la présence du composant `lockable`. |
| NOTE-003 | `test_component_gag.json` est dans les resources de production — visible par les joueurs. | Supprimer ou déplacer avant release. OK pour le dev. |

View File

@@ -0,0 +1,284 @@
# D-01 Branch A : Bridge Utilities
> **Prérequis :** Phase 1 (component system) mergée dans develop.
> **Branche :** `feature/d01-branch-a-bridge`
> **Objectif :** Créer les utilitaires V2 qui remplacent la logique V1, SANS supprimer de code V1. À la fin de cette branche, le mod compile, les items V1 et V2 coexistent, le struggle fonctionne pour les deux, et les nouveaux helpers sont prêts pour la migration des consommateurs.
---
## Décisions actées
- **Stack size** : Stacks de 1 pour tout. Régression acceptée.
- **Save compat** : Breaking change. Pas de migration. Mod en alpha.
- **Résistance** : Config-driven via `resistanceId`, pas hardcodé en JSON.
- **Comprehension/range gags** : Config-driven via `gagMaterial`, délègue à ModConfig au runtime.
- **IHasGaggingEffect/IHasBlindingEffect** : DataDrivenBondageItem les implémente en checkant les composants.
---
## Tâches
### A1. Modifier ResistanceComponent — config-driven
**Fichier :** `src/main/java/com/tiedup/remake/v2/bondage/component/ResistanceComponent.java`
Actuellement stocke un `int baseResistance` hardcodé. Doit stocker un `String resistanceId` et déléguer à `SettingsAccessor.getBindResistance(resistanceId)` au runtime.
- Remplacer le champ `baseResistance` par `resistanceId` (String)
- `fromJson()` : parse `"id"` au lieu de `"base"``"resistance": {"id": "rope"}`
- `getBaseResistance()` : `return SettingsAccessor.getBindResistance(resistanceId);`
- Garder un fallback `"base"` pour backward compat avec test_component_gag.json (ou le mettre à jour)
**Référence :** `SettingsAccessor.getBindResistance(String)` dans `core/SettingsAccessor.java` — normalise les clés et lit depuis ModConfig.
---
### A2. Modifier GaggingComponent — config-driven + GagMaterial
**Fichier :** `src/main/java/com/tiedup/remake/v2/bondage/component/GaggingComponent.java`
Actuellement stocke `comprehension` et `range` hardcodés. Doit stocker un `String material` et déléguer à `GagMaterial`/ModConfig au runtime.
- Ajouter champ `@Nullable String material`
- `fromJson()` : parse `"material"``"gagging": {"material": "ball"}`
- `getComprehension()` : si material != null → `GagMaterial.valueOf(material).getComprehension()` (lit ModConfig). Sinon → fallback au champ hardcodé (compat).
- `getRange()` : idem via `GagMaterial.valueOf(material).getTalkRange()`
- `getMaterial()` : expose le `GagMaterial` enum pour `GagTalkManager`
- Garder les champs `comprehension`/`range` comme overrides optionnels (si présents dans JSON, ils prennent priorité sur le material)
**Référence :** `GagMaterial` enum dans `items/base/GagVariant.java` ou `util/GagMaterial.java`.
---
### A3. Ajouter `appendTooltip()` à IItemComponent + ComponentHolder
**Fichiers :**
- `src/main/java/com/tiedup/remake/v2/bondage/component/IItemComponent.java`
- `src/main/java/com/tiedup/remake/v2/bondage/component/ComponentHolder.java`
- `src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenBondageItem.java`
Ajouter hook tooltip pour que chaque composant contribue des lignes :
```java
// IItemComponent
default void appendTooltip(ItemStack stack, @Nullable Level level, List<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

@@ -0,0 +1,128 @@
# D-01 Branch B : 46 JSON Item Definitions
> **Prérequis :** Branch A (bridge utilities) mergée.
> **Branche :** `feature/d01-branch-b-definitions`
> **Objectif :** Créer les définitions JSON data-driven pour les 46 items V1. Chaque item V1 a un équivalent JSON. Les valeurs sont config-driven (pas hardcodées). À la fin, les 46 items V2 existent en parallèle des V1.
---
## Extensions parser nécessaires
Avant de créer les JSON, vérifier que le parser supporte :
- `pose_type` (string, optionnel) — ajouté en Branch A4
- `can_attach_padlock` (boolean, default true) — **à ajouter si pas fait en A**
- GaggingComponent `material` — ajouté en Branch A2
- ResistanceComponent `id` — ajouté en Branch A1
**Fichiers :** `DataDrivenItemParser.java`, `DataDrivenItemDefinition.java`
---
## Items à créer
Tous dans `src/main/resources/data/tiedup/tiedup_items/`.
### Binds (16 fichiers)
| Fichier | display_name | resistance id | pose_type | Notes |
|---------|-------------|---------------|-----------|-------|
| `ropes.json` | Ropes | rope | STANDARD | supports_color: true |
| `armbinder.json` | Armbinder | armbinder | STANDARD | |
| `dogbinder.json` | Dogbinder | armbinder | DOG | movement_style: crawl |
| `chain.json` | Chains | chain | STANDARD | |
| `ribbon.json` | Ribbon | ribbon | STANDARD | supports_color: true |
| `slime.json` | Slime Bind | slime | STANDARD | can_attach_padlock: false |
| `vine_seed.json` | Vine Bind | vine | STANDARD | can_attach_padlock: false |
| `web_bind.json` | Web Bind | web | STANDARD | can_attach_padlock: false |
| `shibari.json` | Shibari | rope | STANDARD | supports_color: true |
| `leather_straps.json` | Leather Straps | armbinder | STANDARD | |
| `medical_straps.json` | Medical Straps | armbinder | STANDARD | |
| `beam_cuffs.json` | Beam Cuffs | chain | STANDARD | |
| `duct_tape.json` | Duct Tape | tape | STANDARD | can_attach_padlock: false |
| `straitjacket.json` | Straitjacket | straitjacket | STRAITJACKET | |
| `wrap.json` | Wrap | wrap | WRAP | |
| `latex_sack.json` | Latex Sack | latex_sack | LATEX_SACK | |
Tous ont : regions `["ARMS"]`, lockable component (sauf organiques), resistance component avec `id`.
**Référence :** `BindVariant.java` pour les registry names, pose types, supports_color. `ModConfig.ServerConfig` pour les resistance IDs (lignes 413-428).
### Gags (19 fichiers)
| Fichier | display_name | gag material | Notes |
|---------|-------------|-------------|-------|
| `cloth_gag.json` | Cloth Gag | cloth | |
| `ropes_gag.json` | Rope Gag | cloth | |
| `cleave_gag.json` | Cleave Gag | cloth | |
| `ribbon_gag.json` | Ribbon Gag | cloth | supports_color: true |
| `ball_gag.json` | Ball Gag | ball | supports_color: true |
| `ball_gag_strap.json` | Ball Gag (Strap) | ball | |
| `tape_gag.json` | Tape Gag | tape | can_attach_padlock: false |
| `wrap_gag.json` | Wrap Gag | tape | |
| `slime_gag.json` | Slime Gag | tape | can_attach_padlock: false |
| `vine_gag.json` | Vine Gag | tape | can_attach_padlock: false |
| `web_gag.json` | Web Gag | tape | can_attach_padlock: false |
| `panel_gag.json` | Panel Gag | panel | |
| `beam_panel_gag.json` | Beam Panel Gag | panel | |
| `chain_panel_gag.json` | Chain Panel Gag | panel | |
| `latex_gag.json` | Latex Gag | latex | |
| `tube_gag.json` | Tube Gag | stuffed | |
| `bite_gag.json` | Bite Gag | bite | |
| `sponge_gag.json` | Sponge Gag | sponge | |
| `baguette_gag.json` | Baguette Gag | baguette | |
Tous ont : regions `["MOUTH"]`, gagging component avec material, lockable, resistance `{"id": "gag"}`, adjustable.
**Référence :** `GagVariant.java` pour les materials et registry names. `ModConfig.ServerConfig.gagComprehension/gagRange` pour les valeurs runtime.
### Blindfolds (2 fichiers)
| Fichier | display_name |
|---------|-------------|
| `classic_blindfold.json` | Blindfold |
| `blindfold_mask.json` | Blindfold Mask |
Regions `["EYES"]`, components : blinding, lockable, resistance `{"id": "blindfold"}`, adjustable.
### Earplugs (1 fichier)
`classic_earplugs.json` — regions `["EARS"]`, lockable, resistance `{"id": "blindfold"}` (partage la résistance blindfold dans V1).
### Mittens (1 fichier)
`leather_mittens.json` — regions `["HANDS"]`, lockable.
### Collars (5 fichiers)
| Fichier | display_name | Components spécifiques |
|---------|-------------|----------------------|
| `classic_collar.json` | Classic Collar | ownership, lockable, resistance `{"id": "collar"}` |
| `shock_collar.json` | Shock Collar | + shock `{"damage": 2.0}` |
| `shock_collar_auto.json` | Auto Shock Collar | + shock `{"damage": 2.0, "auto_interval": 200}` |
| `gps_collar.json` | GPS Collar | + gps `{"safe_zone_radius": 50}` |
| `choke_collar.json` | Choke Collar | + choking |
### Combos (3 fichiers)
| Fichier | display_name | Regions | Components |
|---------|-------------|---------|-----------|
| `hood.json` | Hood | `["EYES"]` | blinding, gagging `{"material": "cloth"}`, lockable, blocked_regions: `["EYES", "EARS"]` |
| `medical_gag.json` | Medical Gag | `["MOUTH"]` | gagging `{"material": "stuffed"}`, blinding, lockable |
| `ball_gag_3d.json` | Ball Gag 3D | `["MOUTH"]` | gagging `{"material": "ball"}`, lockable, adjustable. Model 3D spécifique. |
---
## Supprimer le fichier test
Supprimer `src/main/resources/data/tiedup/tiedup_items/test_component_gag.json` (fichier de test Phase 1, plus nécessaire).
---
## Vérification
- [ ] `make build` — clean
- [ ] `make run` — les 46 items data-driven apparaissent (via `/tiedup give` ou creative tab section data-driven)
- [ ] Résistance = valeur config (changer dans config, vérifier que la résistance change)
- [ ] Gag comprehension = valeur config (changer dans config, vérifier)
- [ ] Collars ownership fonctionne via le nouveau OwnershipComponent
- [ ] Items organiques (slime, vine, web, tape) ne peuvent pas recevoir de padlock

View File

@@ -0,0 +1,107 @@
# D-01 Branch C : Consumer Migration (~97 fichiers)
> **Prérequis :** Branch A + B mergées.
> **Branche :** `feature/d01-branch-c-migration`
> **Objectif :** Remplacer TOUTES les références V1 (`instanceof ItemBind`, `ItemCollar.isOwner()`, `BindVariant.ROPES`, etc.) par les helpers/composants V2. Les classes V1 existent encore mais ne sont plus référencées. À la fin, `grep -r "instanceof ItemBind\|instanceof ItemGag\|instanceof ItemCollar\|instanceof ItemBlindfold\|instanceof ItemEarplugs\|instanceof ItemMittens\|BindVariant\|GagVariant" src/` retourne ZÉRO résultats (hors items/ lui-même).
---
## Pattern migration
| V1 | V2 | Notes |
|----|-----|-------|
| `instanceof ItemBind` | `BindModeHelper.isBindItem(stack)` | Ou `instanceof IV2BondageItem` si on a juste besoin de savoir que c'est un bondage item |
| `ItemBind.hasArmsBound(stack)` | `BindModeHelper.hasArmsBound(stack)` | Mêmes NBT keys |
| `ItemBind.hasLegsBound(stack)` | `BindModeHelper.hasLegsBound(stack)` | |
| `ItemBind.getBindModeId(stack)` | `BindModeHelper.getBindModeId(stack)` | |
| `itemBind.getPoseType()` | `PoseTypeHelper.getPoseType(stack)` | |
| `BindVariant.ROPES` / `ModItems.getBind(variant)` | `DataDrivenBondageItem.createStack(rl("tiedup:ropes"))` | **LAZY !** Ne pas appeler dans des static initializers |
| `instanceof ItemCollar` + methods | `CollarHelper.isCollar(stack)` + `CollarHelper.method(stack)` | |
| `instanceof ItemShockCollar` | `CollarHelper.canShock(stack)` | |
| `instanceof ItemGpsCollar` | `CollarHelper.hasGPS(stack)` | |
| `instanceof ItemChokeCollar` | `CollarHelper.isChokeCollar(stack)` | |
| `instanceof IHasGaggingEffect` | `DataDrivenBondageItem.getComponent(stack, GAGGING, GaggingComponent.class) != null` | Pour V2 items. V1 items gardent l'interface pendant la transition. |
| `instanceof IHasBlindingEffect` | `DataDrivenBondageItem.getComponent(stack, BLINDING, BlindingComponent.class) != null` | Idem |
| `instanceof ItemGag` + `getGagMaterial()` | `GaggingComponent comp = getComponent(stack, GAGGING, ...)` + `comp.getMaterial()` | |
| `PoseType` enum direct | Inchangé — l'enum est conservé | |
| `IHasResistance` methods | Inchangé — l'interface est conservée | |
| `ILockable` methods | Inchangé — l'interface est conservée | |
---
## Ordre de migration (critique d'abord)
### Phase 1 : State core (12 fichiers)
Ces fichiers sont la fondation — les migrer d'abord assure que le reste fonctionne.
| Fichier | Changements |
|---------|------------|
| `state/IBondageState.java` | `hasArmsBound()`/`hasLegsBound()``BindModeHelper` |
| `state/PlayerBindState.java` | `instanceof ItemCollar``CollarHelper`, `instanceof ItemBind``BindModeHelper` |
| `state/PlayerCaptorManager.java` | `instanceof ItemCollar``CollarHelper.isCollar() + CollarHelper.getOwners()` |
| `state/HumanChairHelper.java` | `PoseType` import OK (conservé) |
| `state/components/PlayerEquipment.java` | **Garder `equipInRegion()` V1 fallback** (migre en Branch D). Remplacer `instanceof ItemBind/ItemCollar` dans resistance methods. |
| `state/components/PlayerDataRetrieval.java` | `instanceof ItemCollar``CollarHelper.getNickname()` |
| `state/components/PlayerLifecycle.java` | imports V1 → V2 helpers |
| `state/components/PlayerShockCollar.java` | `instanceof ItemShockCollar``CollarHelper.canShock()` |
| `state/struggle/StruggleBinds.java` | Déjà migré en Branch A9 — vérifier |
| `state/struggle/StruggleCollar.java` | Déjà migré en Branch A9 — vérifier |
| `state/struggle/StruggleAccessory.java` | Vérifier |
### Phase 2 : Client animation/render (12 fichiers)
| Fichier | Changements |
|---------|------------|
| `client/animation/tick/AnimationTickHandler.java` | `instanceof ItemBind``PoseTypeHelper.getPoseType()` + `BindModeHelper` |
| `client/animation/tick/NpcAnimationTickHandler.java` | Idem |
| `client/animation/render/PlayerArmHideEventHandler.java` | `instanceof ItemBind``PoseTypeHelper` |
| `client/animation/render/DogPoseRenderHandler.java` | Idem |
| `client/animation/render/PetBedRenderHandler.java` | Idem |
| `client/animation/util/AnimationIdBuilder.java` | `PoseType` import OK (conservé) |
| `client/animation/StaticPoseApplier.java` | `PoseType` import OK |
| `client/model/DamselModel.java` | `PoseType` + `instanceof ItemBind` → helpers |
| `client/FirstPersonMittensRenderer.java` | `BindVariant.ROPES` → lazy createStack |
| `mixin/client/MixinPlayerModel.java` | `instanceof ItemBind``PoseTypeHelper` |
| `mixin/client/MixinCamera.java` | Idem |
| `mixin/client/MixinVillagerEntityBaseModelMCA.java` | Idem |
### Phase 3 : Entity AI goals (15 fichiers)
Principalement `instanceof ItemBind``BindModeHelper`, `ModItems.getBind(variant)``createStack(rl)`, `instanceof ItemCollar``CollarHelper`.
### Phase 4 : Network packets (14 fichiers)
`PacketSelfBondage` déjà migré en A11. Reste : `PacketSlaveAction`, `PacketMasterEquip`, `PacketAssignCellToCollar`, `PacketNpcCommand`, etc. → `CollarHelper`.
### Phase 5 : Events (8 fichiers)
`BondageItemRestrictionHandler`, `RestraintTaskTickHandler`, `PlayerEnslavementHandler`, `ChatEventHandler`, etc.
### Phase 6 : Commands (6 fichiers)
`BondageSubCommand` (1232 lignes) — le plus gros. `BindVariant``createStack()`. `NPCCommand`, `CollarCommand`, `KidnapSetCommand`.
### Phase 7 : Entity classes (15 fichiers)
`EntityKidnapper`, `KidnapperCaptureEquipment`, `KidnapperTheme`, `KidnapperItemSelector`, `KidnapperCollarConfig`, etc.
### Phase 8 : Compat MCA (5 fichiers)
`MCAKidnappedAdapter` (907 lignes) — `instanceof IHasGaggingEffect/IHasBlindingEffect` → component checks. `instanceof ItemCollar``CollarHelper`.
### Phase 9 : Autres (10 fichiers)
Dialogue (`GagTalkManager`, `PetRequestManager`), worldgen (`HangingCagePiece`), util (`RestraintApplicator`), blocks (`BondageItemBlockEntity`), dispenser, creative tab.
---
## Vérification
- [ ] `make build` — clean
- [ ] `grep -r "instanceof ItemBind\b" src/ --include="*.java" | grep -v "items/"` → 0 résultats
- [ ] `grep -r "instanceof ItemGag\b" src/ --include="*.java" | grep -v "items/"` → 0 résultats
- [ ] `grep -r "instanceof ItemCollar\b" src/ --include="*.java" | grep -v "items/"` → 0 résultats
- [ ] `grep -r "BindVariant\b" src/ --include="*.java" | grep -v "items/"` → 0 résultats
- [ ] `grep -r "GagVariant\b" src/ --include="*.java" | grep -v "items/"` → 0 résultats
- [ ] `make run` — le mod fonctionne normalement

View File

@@ -0,0 +1,188 @@
# D-01 Branch D : V1 Cleanup
> **Prérequis :** Branch A + B + C mergées. Zero références V1 hors du package `items/`.
> **Branche :** `feature/d01-branch-d-cleanup`
> **Objectif :** Supprimer toutes les classes V1 bondage. Migrer `equipInRegion()` vers le flow V2 complet. Réécrire le creative tab. À la fin, zéro classe V1 bondage dans le mod.
---
## Décisions
- **Save compat :** Breaking change. Les items V1 dans les inventaires existants seront perdus. Mod en alpha.
- **Pas de MissingMappingsEvent.** Simplement supprimer les registrations.
---
## Tâches
### D1. Migrer equipInRegion() → V2EquipmentHelper.equipItem()
**Fichier :** `src/main/java/com/tiedup/remake/state/components/PlayerEquipment.java`
Maintenant que tous les items sont `DataDrivenBondageItem` (qui implémente `IV2BondageItem`), le bypass direct `setInRegion()` n'est plus nécessaire.
Remplacer `equipInRegion()` par un appel à `V2EquipmentHelper.equipItem()` qui fait la conflict resolution complète (swap, supersede, blocked regions).
Vérifier que les méthodes `putBindOn()`, `putGagOn()`, `putCollarOn()`, etc. fonctionnent toujours via le nouveau path.
---
### D2. Supprimer les classes V1 (~30 fichiers)
**À supprimer :**
Abstract bases :
- `items/base/ItemBind.java` (637 lignes)
- `items/base/ItemGag.java` (93 lignes)
- `items/base/ItemBlindfold.java` (89 lignes)
- `items/base/ItemCollar.java` (1407 lignes)
- `items/base/ItemEarplugs.java` (90 lignes)
- `items/base/ItemMittens.java` (72 lignes)
Interfaces V1-only :
- `items/base/IBondageItem.java` (102 lignes)
- `items/base/IHasGaggingEffect.java` (33 lignes)
- `items/base/IHasBlindingEffect.java` (33 lignes)
- `items/base/IAdjustable.java` (49 lignes)
- `items/base/ItemOwnerTarget.java`
- `items/base/ItemColor.java`
Variant enums :
- `items/base/BindVariant.java` (90 lignes)
- `items/base/GagVariant.java` (163 lignes)
- `items/base/BlindfoldVariant.java` (48 lignes)
- `items/base/EarplugsVariant.java` (33 lignes)
- `items/base/MittensVariant.java` (35 lignes)
Factory classes :
- `items/GenericBind.java` (68 lignes)
- `items/GenericGag.java` (72 lignes)
- `items/GenericBlindfold.java` (37 lignes)
- `items/GenericEarplugs.java` (37 lignes)
- `items/GenericMittens.java` (37 lignes)
Collars :
- `items/ItemClassicCollar.java` (21 lignes)
- `items/ItemShockCollar.java` (133 lignes)
- `items/ItemShockCollarAuto.java` (58 lignes)
- `items/ItemGpsCollar.java` (369 lignes)
- `items/ItemChokeCollar.java` (154 lignes)
Combos :
- `items/ItemHood.java` (35 lignes)
- `items/ItemMedicalGag.java` (24 lignes)
- `items/bondage3d/gags/ItemBallGag3D.java` (78 lignes)
- `items/bondage3d/IHas3DModelConfig.java`
- `items/bondage3d/Model3DConfig.java`
**À CONSERVER :**
- `items/base/ILockable.java` — utilisé par V2 (AbstractV2BondageItem)
- `items/base/IHasResistance.java` — utilisé par V2 (DataDrivenBondageItem)
- `items/base/IKnife.java` — utilisé par GenericKnife (tool)
- `items/base/PoseType.java` — utilisé par animation system
- `items/base/KnifeVariant.java` — utilisé par GenericKnife (tool)
- `items/base/AdjustmentHelper.java` — utilisé par adjustment packets
- `items/GenericKnife.java` — tool, pas bondage
- `items/clothes/GenericClothes.java` — déjà V2
- `items/clothes/ClothesProperties.java`
- `items/ModItems.java` — garde les tools, supprime les bondage
- `items/ModCreativeTabs.java` — réécrit (voir D3)
- Tous les tool items (whip, padlock, key, lockpick, taser, etc.)
---
### D3. Réécrire ModItems — retirer les registrations V1
**Fichier :** `src/main/java/com/tiedup/remake/items/ModItems.java`
Supprimer :
- `BINDS` map + `registerAllBinds()`
- `GAGS` map + `registerAllGags()`
- `BLINDFOLDS` map + `registerAllBlindfolds()`
- `EARPLUGS` map + `registerAllEarplugs()`
- `MITTENS` map + `registerAllMittens()`
- `BALL_GAG_3D`, `MEDICAL_GAG`, `HOOD`
- `CLASSIC_COLLAR`, `SHOCK_COLLAR`, `SHOCK_COLLAR_AUTO`, `GPS_COLLAR`, `CHOKE_COLLAR`
- Les helper accessors `getBind()`, `getGag()`, etc.
Garder : CLOTHES, tous les tools (WHIP, PADLOCK, KEY, etc.), KNIVES, spawn eggs.
---
### D4. Réécrire ModCreativeTabs
**Fichier :** `src/main/java/com/tiedup/remake/items/ModCreativeTabs.java`
Remplacer l'itération par variant enums par :
```java
// Data-driven bondage items
for (DataDrivenItemDefinition def : DataDrivenItemRegistry.getAll()) {
output.accept(DataDrivenBondageItem.createStack(def.id()));
}
```
Pour l'ordre : ajouter un champ optionnel `"creative_tab_order"` aux definitions JSON, ou trier par catégorie (regions) puis par nom.
Pour les couleurs : si l'item a `supports_color`, ajouter les variantes colorées. Utiliser `tint_channels` du definition.
---
### D5. Cleanup PoseTypeHelper — retirer le fallback V1
**Fichier :** `src/main/java/com/tiedup/remake/v2/bondage/PoseTypeHelper.java`
Supprimer le fallback `instanceof ItemBind` dans `getPoseType()`. Ne garder que le path data-driven.
---
### D6. Cleanup CollarHelper — retirer les fallbacks V1
**Fichier :** `src/main/java/com/tiedup/remake/v2/bondage/CollarHelper.java`
Les méthodes comme `isCollar(stack)` checkent `instanceof ItemCollar` en fallback V1. Retirer ces checks.
---
### D7. Cleanup BindModeHelper — retirer le fallback V1
Idem — retirer `instanceof ItemBind` fallback dans `isBindItem()`.
---
### D8. Cleanup imports orphelins
Faire un pass sur tout le projet pour retirer les imports V1 orphelins.
```bash
grep -rn "import com.tiedup.remake.items.base.ItemBind" src/ --include="*.java"
grep -rn "import com.tiedup.remake.items.base.ItemCollar" src/ --include="*.java"
grep -rn "import com.tiedup.remake.items.base.IBondageItem" src/ --include="*.java"
# etc. — tout doit retourner 0
```
---
## Vérification finale
- [ ] `make build` — clean, zero errors
- [ ] `make run` — le mod démarre, les items apparaissent dans le creative tab
- [ ] `grep -r "items.base.ItemBind\|items.base.ItemGag\|items.base.ItemCollar\|items.base.ItemBlindfold\|items.base.ItemEarplugs\|items.base.ItemMittens\|items.base.IBondageItem\|BindVariant\|GagVariant\|BlindfoldVariant\|EarplugsVariant\|MittensVariant" src/main/java/ --include="*.java"`**0 résultats**
- [ ] Les items data-driven s'équipent/se déséquipent correctement
- [ ] Le struggle fonctionne (binds + collars)
- [ ] Le self-bondage fonctionne (routing par région)
- [ ] Les collars gardent leur ownership/shock/GPS après equip/unequip
- [ ] Les tooltips affichent toutes les infos composants
- [ ] `equipInRegion()` utilise V2EquipmentManager (conflict resolution active)
- [ ] MCP reindex final
---
## Résultat attendu
- **~6500 lignes de code V1 supprimées**
- **46 items = 46 fichiers JSON** (data-driven, extensible par resource packs)
- **1 seul Item singleton** (`DataDrivenBondageItem`)
- **8 composants** gèrent toute la logique gameplay
- **3 helpers** (`BindModeHelper`, `PoseTypeHelper`, `CollarHelper`) remplacent les anciennes classes
- **Zero couplage V1** dans le reste du mod

View File

@@ -0,0 +1,155 @@
# D-01 Branch E : Resistance & Lock System Rework
> **Prérequis :** Branch D (V1 cleanup) mergée.
> **Branche :** `feature/d01-branch-e-resistance`
> **Objectif :** Redesign complet du système de résistance/lock.
---
## Nouveau modèle
### Principes
1. **La résistance vient de l'item** — définie dans le JSON via `ResistanceComponent`, point final.
2. **Le lock est binaire** — on/off. Pas de "lock resistance" séparée. Le lock *active* la nécessité de struggle pour les items non-ARMS.
3. **ARMS = toujours actif** — un bind aux bras nécessite toujours un struggle pour s'en libérer soi-même, locké ou non.
4. **Non-ARMS + pas locké = libre** — un gag/blindfold/collar non-locké peut être retiré librement (par soi-même ou un autre joueur).
5. **Non-ARMS + locké = struggle requis** — le lock active la résistance de l'item.
6. **Un autre joueur peut aider** — retirer un item non-locké sur un autre joueur ne nécessite pas de struggle (aide).
### Matrice de struggle
| Région | Locké ? | Self-remove | Autre joueur remove |
|--------|---------|-------------|---------------------|
| ARMS | Non | Struggle (résistance item) | Libre (aide) |
| ARMS | Oui | Struggle (résistance item) | Struggle (résistance item) |
| Non-ARMS | Non | Libre | Libre |
| Non-ARMS | Oui | Struggle (résistance item) | Struggle (résistance item) |
### Items organiques (slime, vine, web, tape)
Ces items sont "lockés par nature" — pas de padlock possible mais impossible à retirer sans struggle.
**Option :** Nouveau composant `BuiltInLockComponent` dans le JSON :
```json
"components": {
"resistance": {"id": "slime"},
"built_in_lock": {}
}
```
`BuiltInLockComponent` :
- `blocksUnequip()` retourne `true` (comme un lock, mais sans padlock)
- `ILockable.canAttachPadlock()` retourne `false` (déjà le cas pour les organiques)
- L'item se comporte comme un ARMS bind : toujours struggle required
**Alternative :** Flag `"always_locked": true` sur la definition JSON. Plus simple, pas besoin de nouveau composant.
---
## Problèmes actuels que ce rework corrige
### P1. Singleton MAX scan
`DataDrivenBondageItem.getBaseResistance(LivingEntity)` retourne le MAX de tous les items data-driven équipés. Un gag résistance 50 hérite de la résistance 200 du chain bind.
**Fix :** Initialiser `currentResistance` dans le NBT à l'equip depuis `ResistanceComponent.getBaseResistance()`. Plus jamais de fallback au MAX scan runtime. Le `getBaseResistance(LivingEntity)` du singleton devient un no-op/fallback qui n'est plus utilisé par le struggle.
### P2. isItemLocked() dead code
`StruggleState.struggle()` ne call jamais `isItemLocked()`. Le x10 penalty n'est jamais appliqué.
**Fix :** Supprimer le concept de "locked penalty". Avec le nouveau modèle, le lock **active** le struggle, il ne le ralentit pas. Si l'item est locké, il faut struggle avec la résistance complète de l'item. Si non locké (et non-ARMS), pas de struggle du tout.
### P3. Lock resistance / item resistance déconnectés
`ILockable.getLockResistance()` vs `IHasResistance.getBaseResistance()` sont deux systèmes indépendants.
**Fix :** Supprimer `ILockable.getLockResistance()` / `getCurrentLockResistance()` / `setCurrentLockResistance()` / `initializeLockResistance()` / `clearLockResistance()`. La résistance du lockpick minigame utilise directement la résistance de l'item (ou un multiplicateur fixe).
### P4. Dice-roll ignore le lock
**Fix :** Avec le nouveau modèle, le dice-roll ne change pas. C'est `canStruggle()` qui gate l'accès :
```java
// StruggleBinds.canStruggle()
// ARMS: toujours struggle-able (self)
return true;
// StruggleCollar/StruggleAccessory.canStruggle()
// Non-ARMS: seulement si locké
return isLocked(stack) || hasBuiltInLock(stack);
```
---
## Bugs pré-existants à corriger dans cette branche
### B1. V1 ItemCollar.onUnequipped() — suppressed path skip unregister
Quand `isRemovalAlertSuppressed()` est true, `ItemCollar.onUnequipped()` return early SANS appeler `CollarRegistry.unregisterWearer()`. Entrées fantômes persistées.
**Fichier :** `items/base/ItemCollar.java` lignes 1382-1395
**Fix :** Ajouter `unregisterWearer()` dans le branch suppressed.
### B2. DataDrivenItemRegistry.clear() pas synchronisé
`clear()` écrit `SNAPSHOT = EMPTY` sans acquérir `RELOAD_LOCK`. Race avec `mergeAll()`.
**Fichier :** `v2/bondage/datadriven/DataDrivenItemRegistry.java` ligne 142
**Fix :** Synchroniser sur `RELOAD_LOCK`.
### B3. V2TyingPlayerTask.heldStack reference stale
Le held item peut être remplacé entre début et fin du tying → item dupliqué.
**Fichier :** `tasks/V2TyingPlayerTask.java` ligne 80
**Fix :** Valider `heldStack` non-vide et matching avant equip dans `onComplete()`.
---
## Tâches
### E1. Initialiser currentResistance à l'equip
Dans `DataDrivenBondageItem.onEquipped()` et les hooks V1 `onEquipped()` :
- Lire `ResistanceComponent.getBaseResistance()` (ou `IHasResistance.getBaseResistance()` pour V1)
- Écrire immédiatement dans le NBT via `setCurrentResistance(stack, base)`
- Élimine le MAX scan comme source d'initialisation
### E2. Refactor canStruggle() — nouveau modèle
- `StruggleBinds.canStruggle()` : ARMS → toujours true (self) si item existe
- Nouveau `StruggleAccessory` (ou refactor de StruggleCollar) : non-ARMS → true seulement si locké ou built-in lock
- Supprimer `isItemLocked()` penalty (dead code de toute façon)
### E3. "Aide" — remove non-locké par un autre joueur
Modifier `AbstractV2BondageItem.interactLivingEntity()` :
- Si clic sur un joueur qui porte l'item ET item non-locké ET clicker n'a pas l'item en main → retirer l'item (aide)
- Ou via un packet dédié (clic droit main vide sur joueur attaché)
### E4. BuiltInLockComponent ou flag `always_locked`
Pour les items organiques qui ne peuvent pas avoir de padlock mais nécessitent un struggle.
### E5. Cleanup ILockable — supprimer lock resistance
Supprimer : `getLockResistance()`, `getCurrentLockResistance()`, `setCurrentLockResistance()`, `initializeLockResistance()`, `clearLockResistance()`.
Le lockpick minigame utilise la résistance de l'item directement (ou un multiplicateur config).
### E6. Fix bugs pré-existants (B1, B2, B3)
---
## Vérification
- [ ] V2 bind résistance 50 + V2 gag résistance 80 : chacun a sa propre résistance (pas MAX)
- [ ] Gag non-locké → retirable sans struggle
- [ ] Gag locké → struggle avec résistance du gag
- [ ] Bind ARMS non-locké → self-struggle requis, autre joueur peut aider (libre)
- [ ] Bind ARMS locké → self-struggle requis, autre joueur aussi struggle
- [ ] Slime bind (built-in lock) → struggle obligatoire, pas de padlock possible
- [ ] `currentResistance` initialisé dans NBT dès l'equip
- [ ] CollarRegistry clean après removals légitimes
- [ ] Pas de duplication d'item via tying task

View File

@@ -0,0 +1,869 @@
# D-01 Phase 1: Data-Driven Item Component System
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Create a reusable component system so data-driven bondage items can declare gameplay behaviors (lockable, shock, GPS, gagging, etc.) in their JSON definition instead of requiring per-item Java classes.
**Architecture:** Each component is a self-contained behavior module implementing `IItemComponent`. Components are declared in item JSON (`"components": {"shock": {...}}`), parsed by an extended `DataDrivenItemParser`, stored on `DataDrivenItemDefinition`, and ticked/queried via `DataDrivenBondageItem` delegation. The existing `ILockable` and `IHasResistance` interfaces are preserved as shared contracts — components implement them.
**Tech Stack:** Java 17, Forge 1.20.1, existing V2 data-driven infrastructure (`DataDrivenItemRegistry`, `DataDrivenItemParser`, `DataDrivenItemDefinition`, `DataDrivenBondageItem`)
**Scope:** This plan builds ONLY the component infrastructure + 3 core components (lockable, resistance, gagging). The remaining 5 components (shock, GPS, blinding, choking, adjustable) follow the same pattern and will be added in subsequent tasks or a follow-up plan.
---
## File Structure
### New files
| File | Responsibility |
|------|---------------|
| `v2/bondage/component/IItemComponent.java` | Component interface: lifecycle hooks, tick, query |
| `v2/bondage/component/ComponentType.java` | Enum of all component types with factory methods |
| `v2/bondage/component/ComponentHolder.java` | Container: holds instantiated components for an item stack |
| `v2/bondage/component/LockableComponent.java` | Lock/unlock, padlock, key matching, jam, lock resistance |
| `v2/bondage/component/ResistanceComponent.java` | Struggle resistance with configurable base value |
| `v2/bondage/component/GaggingComponent.java` | Muffled speech, comprehension %, range limit |
### Modified files
| File | Changes |
|------|---------|
| `v2/bondage/datadriven/DataDrivenItemDefinition.java` | Add `Map<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."
```

View File

@@ -6,6 +6,14 @@ import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.items.base.ItemEarplugs; import com.tiedup.remake.items.base.ItemEarplugs;
import com.tiedup.remake.items.base.ItemGag; import com.tiedup.remake.items.base.ItemGag;
import com.tiedup.remake.items.clothes.GenericClothes; import com.tiedup.remake.items.clothes.GenericClothes;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.BindModeHelper;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.v2.bondage.component.ComponentType;
import com.tiedup.remake.v2.bondage.component.GaggingComponent;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemDefinition;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemRegistry;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
@@ -191,55 +199,45 @@ public abstract class BondageItemBlockEntity
@Override @Override
public void readBondageData(CompoundTag tag) { public void readBondageData(CompoundTag tag) {
// Read bind with type validation // Read bind with type validation (V1 ItemBind or V2 ARMS-region item)
if (tag.contains("bind")) { if (tag.contains("bind")) {
ItemStack bindStack = ItemStack.of(tag.getCompound("bind")); ItemStack bindStack = ItemStack.of(tag.getCompound("bind"));
if ( if (!bindStack.isEmpty() && (bindStack.getItem() instanceof ItemBind || BindModeHelper.isBindItem(bindStack))) {
!bindStack.isEmpty() && bindStack.getItem() instanceof ItemBind
) {
this.bind = bindStack; this.bind = bindStack;
} }
} }
// Read gag with type validation // Read gag with type validation (V1 ItemGag or V2 GAGGING component)
if (tag.contains("gag")) { if (tag.contains("gag")) {
ItemStack gagStack = ItemStack.of(tag.getCompound("gag")); ItemStack gagStack = ItemStack.of(tag.getCompound("gag"));
if (!gagStack.isEmpty() && gagStack.getItem() instanceof ItemGag) { if (!gagStack.isEmpty() && (gagStack.getItem() instanceof ItemGag
|| DataDrivenBondageItem.getComponent(gagStack, ComponentType.GAGGING, GaggingComponent.class) != null)) {
this.gag = gagStack; this.gag = gagStack;
} }
} }
// Read blindfold with type validation // Read blindfold with type validation (V1 ItemBlindfold or V2 EYES-region item)
if (tag.contains("blindfold")) { if (tag.contains("blindfold")) {
ItemStack blindfoldStack = ItemStack.of( ItemStack blindfoldStack = ItemStack.of(tag.getCompound("blindfold"));
tag.getCompound("blindfold") if (!blindfoldStack.isEmpty() && (blindfoldStack.getItem() instanceof ItemBlindfold
); || isDataDrivenForRegion(blindfoldStack, BodyRegionV2.EYES))) {
if (
!blindfoldStack.isEmpty() &&
blindfoldStack.getItem() instanceof ItemBlindfold
) {
this.blindfold = blindfoldStack; this.blindfold = blindfoldStack;
} }
} }
// Read earplugs with type validation // Read earplugs with type validation (V1 ItemEarplugs or V2 EARS-region item)
if (tag.contains("earplugs")) { if (tag.contains("earplugs")) {
ItemStack earplugsStack = ItemStack.of(tag.getCompound("earplugs")); ItemStack earplugsStack = ItemStack.of(tag.getCompound("earplugs"));
if ( if (!earplugsStack.isEmpty() && (earplugsStack.getItem() instanceof ItemEarplugs
!earplugsStack.isEmpty() && || isDataDrivenForRegion(earplugsStack, BodyRegionV2.EARS))) {
earplugsStack.getItem() instanceof ItemEarplugs
) {
this.earplugs = earplugsStack; this.earplugs = earplugsStack;
} }
} }
// Read collar with type validation // Read collar with type validation (V1 ItemCollar or V2 collar)
if (tag.contains("collar")) { if (tag.contains("collar")) {
ItemStack collarStack = ItemStack.of(tag.getCompound("collar")); ItemStack collarStack = ItemStack.of(tag.getCompound("collar"));
if ( if (!collarStack.isEmpty() && (collarStack.getItem() instanceof ItemCollar || CollarHelper.isCollar(collarStack))) {
!collarStack.isEmpty() &&
collarStack.getItem() instanceof ItemCollar
) {
this.collar = collarStack; this.collar = collarStack;
} }
} }
@@ -279,6 +277,14 @@ public abstract class BondageItemBlockEntity
return tag; return tag;
} }
// V2 HELPERS
/** Check if a stack is a data-driven item occupying the given body region. */
private static boolean isDataDrivenForRegion(ItemStack stack, BodyRegionV2 region) {
DataDrivenItemDefinition def = DataDrivenItemRegistry.get(stack);
return def != null && def.occupiedRegions().contains(region);
}
// NETWORK SYNC // NETWORK SYNC
/** /**

View File

@@ -1,7 +1,19 @@
package com.tiedup.remake.blocks.entity; package com.tiedup.remake.blocks.entity;
import com.tiedup.remake.items.base.*; import com.tiedup.remake.items.base.ItemBind;
import com.tiedup.remake.items.base.ItemBlindfold;
import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.items.base.ItemEarplugs;
import com.tiedup.remake.items.base.ItemGag;
import com.tiedup.remake.items.clothes.GenericClothes; import com.tiedup.remake.items.clothes.GenericClothes;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.BindModeHelper;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.v2.bondage.component.ComponentType;
import com.tiedup.remake.v2.bondage.component.GaggingComponent;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemDefinition;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemRegistry;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
@@ -45,7 +57,7 @@ public class TrappedChestBlockEntity
@Override @Override
public void setBind(ItemStack stack) { public void setBind(ItemStack stack) {
if (stack.isEmpty() || stack.getItem() instanceof ItemBind) { if (stack.isEmpty() || stack.getItem() instanceof ItemBind || BindModeHelper.isBindItem(stack)) {
this.bind = stack; this.bind = stack;
setChangedAndSync(); setChangedAndSync();
} }
@@ -58,7 +70,8 @@ public class TrappedChestBlockEntity
@Override @Override
public void setGag(ItemStack stack) { public void setGag(ItemStack stack) {
if (stack.isEmpty() || stack.getItem() instanceof ItemGag) { if (stack.isEmpty() || stack.getItem() instanceof ItemGag
|| DataDrivenBondageItem.getComponent(stack, ComponentType.GAGGING, GaggingComponent.class) != null) {
this.gag = stack; this.gag = stack;
setChangedAndSync(); setChangedAndSync();
} }
@@ -71,7 +84,8 @@ public class TrappedChestBlockEntity
@Override @Override
public void setBlindfold(ItemStack stack) { public void setBlindfold(ItemStack stack) {
if (stack.isEmpty() || stack.getItem() instanceof ItemBlindfold) { if (stack.isEmpty() || stack.getItem() instanceof ItemBlindfold
|| isDataDrivenForRegion(stack, BodyRegionV2.EYES)) {
this.blindfold = stack; this.blindfold = stack;
setChangedAndSync(); setChangedAndSync();
} }
@@ -84,7 +98,8 @@ public class TrappedChestBlockEntity
@Override @Override
public void setEarplugs(ItemStack stack) { public void setEarplugs(ItemStack stack) {
if (stack.isEmpty() || stack.getItem() instanceof ItemEarplugs) { if (stack.isEmpty() || stack.getItem() instanceof ItemEarplugs
|| isDataDrivenForRegion(stack, BodyRegionV2.EARS)) {
this.earplugs = stack; this.earplugs = stack;
setChangedAndSync(); setChangedAndSync();
} }
@@ -97,7 +112,7 @@ public class TrappedChestBlockEntity
@Override @Override
public void setCollar(ItemStack stack) { public void setCollar(ItemStack stack) {
if (stack.isEmpty() || stack.getItem() instanceof ItemCollar) { if (stack.isEmpty() || stack.getItem() instanceof ItemCollar || CollarHelper.isCollar(stack)) {
this.collar = stack; this.collar = stack;
setChangedAndSync(); setChangedAndSync();
} }
@@ -183,6 +198,14 @@ public class TrappedChestBlockEntity
writeBondageData(tag); writeBondageData(tag);
} }
// V2 HELPERS
/** Check if a stack is a data-driven item occupying the given body region. */
private static boolean isDataDrivenForRegion(ItemStack stack, BodyRegionV2 region) {
DataDrivenItemDefinition def = DataDrivenItemRegistry.get(stack);
return def != null && def.occupiedRegions().contains(region);
}
// NETWORK SYNC // NETWORK SYNC
/** /**

View File

@@ -2,8 +2,8 @@ package com.tiedup.remake.cells;
import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.base.ILockable; import com.tiedup.remake.items.base.ILockable;
import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.prison.PrisonerManager; import com.tiedup.remake.prison.PrisonerManager;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.state.IRestrainable; import com.tiedup.remake.state.IRestrainable;
import com.tiedup.remake.util.KidnappedHelper; import com.tiedup.remake.util.KidnappedHelper;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
@@ -231,7 +231,7 @@ public final class CampLifecycleManager {
} }
// Suppress collar removal alerts - this is a legitimate release (camp death) // Suppress collar removal alerts - this is a legitimate release (camp death)
ItemCollar.runWithSuppressedAlert(() -> { CollarHelper.runWithSuppressedAlert(() -> {
// Unlock collar if owned by the dead camp/trader // Unlock collar if owned by the dead camp/trader
unlockCollarIfOwnedBy(prisoner, state, traderUUID); unlockCollarIfOwnedBy(prisoner, state, traderUUID);
@@ -285,8 +285,8 @@ public final class CampLifecycleManager {
return; return;
} }
if (collar.getItem() instanceof ItemCollar collarItem) { if (CollarHelper.isCollar(collar)) {
List<UUID> owners = collarItem.getOwners(collar); List<UUID> owners = CollarHelper.getOwners(collar);
// If the dead trader/camp is an owner, unlock the collar // If the dead trader/camp is an owner, unlock the collar
if (owners.contains(ownerUUID)) { if (owners.contains(ownerUUID)) {

View File

@@ -3,8 +3,8 @@ package com.tiedup.remake.client;
import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer; import com.mojang.blaze3d.vertex.VertexConsumer;
import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.GenericBind; import com.tiedup.remake.items.base.PoseType;
import com.tiedup.remake.items.base.BindVariant; import com.tiedup.remake.v2.bondage.PoseTypeHelper;
import com.tiedup.remake.state.PlayerBindState; import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper; import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
@@ -141,12 +141,7 @@ public class FirstPersonMittensRenderer {
net.minecraft.world.item.ItemStack bindStack = net.minecraft.world.item.ItemStack bindStack =
V2EquipmentHelper.getInRegion(player, BodyRegionV2.ARMS); V2EquipmentHelper.getInRegion(player, BodyRegionV2.ARMS);
if (bindStack.isEmpty()) return false; if (bindStack.isEmpty()) return false;
if (bindStack.getItem() instanceof GenericBind bind) { PoseType poseType = PoseTypeHelper.getPoseType(bindStack);
BindVariant variant = bind.getVariant(); return poseType == PoseType.WRAP || poseType == PoseType.LATEX_SACK;
return (
variant == BindVariant.WRAP || variant == BindVariant.LATEX_SACK
);
}
return false;
} }
} }

View File

@@ -6,7 +6,7 @@ import com.tiedup.remake.client.gui.screens.UnifiedBondageScreen;
import com.tiedup.remake.core.ModConfig; import com.tiedup.remake.core.ModConfig;
import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.base.ILockable; import com.tiedup.remake.items.base.ILockable;
import com.tiedup.remake.items.base.ItemCollar; import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.network.ModNetwork; import com.tiedup.remake.network.ModNetwork;
import com.tiedup.remake.network.action.PacketForceSeatModifier; import com.tiedup.remake.network.action.PacketForceSeatModifier;
import com.tiedup.remake.network.action.PacketStruggle; import com.tiedup.remake.network.action.PacketStruggle;
@@ -428,11 +428,8 @@ public class ModKeybindings {
target, target,
BodyRegionV2.NECK BodyRegionV2.NECK
); );
if ( if (!collarStack.isEmpty() && CollarHelper.isCollar(collarStack)) {
!collarStack.isEmpty() && return CollarHelper.isOwner(collarStack, player);
collarStack.getItem() instanceof ItemCollar collar
) {
return collar.isOwner(collarStack, player);
} }
return false; return false;
} }

View File

@@ -1,8 +1,8 @@
package com.tiedup.remake.client.animation.render; package com.tiedup.remake.client.animation.render;
import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.base.ItemBind;
import com.tiedup.remake.items.base.PoseType; import com.tiedup.remake.items.base.PoseType;
import com.tiedup.remake.v2.bondage.PoseTypeHelper;
import com.tiedup.remake.state.HumanChairHelper; import com.tiedup.remake.state.HumanChairHelper;
import com.tiedup.remake.state.PlayerBindState; import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
@@ -93,14 +93,11 @@ public class DogPoseRenderHandler {
} }
ItemStack bindForPose = state.getEquipment(BodyRegionV2.ARMS); ItemStack bindForPose = state.getEquipment(BodyRegionV2.ARMS);
if ( if (bindForPose.isEmpty()) {
bindForPose.isEmpty() ||
!(bindForPose.getItem() instanceof ItemBind itemBind)
) {
return; return;
} }
PoseType bindPoseType = itemBind.getPoseType(); PoseType bindPoseType = PoseTypeHelper.getPoseType(bindForPose);
// Check for humanChairMode NBT override // Check for humanChairMode NBT override
bindPoseType = HumanChairHelper.resolveEffectivePose( bindPoseType = HumanChairHelper.resolveEffectivePose(
bindPoseType, bindPoseType,

View File

@@ -2,8 +2,8 @@ package com.tiedup.remake.client.animation.render;
import com.tiedup.remake.client.state.PetBedClientState; import com.tiedup.remake.client.state.PetBedClientState;
import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.base.ItemBind;
import com.tiedup.remake.items.base.PoseType; import com.tiedup.remake.items.base.PoseType;
import com.tiedup.remake.v2.bondage.PoseTypeHelper;
import com.tiedup.remake.state.HumanChairHelper; import com.tiedup.remake.state.HumanChairHelper;
import com.tiedup.remake.state.PlayerBindState; import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
@@ -83,11 +83,9 @@ public class PetBedRenderHandler {
PlayerBindState state = PlayerBindState.getInstance(player); PlayerBindState state = PlayerBindState.getInstance(player);
if (state == null) return false; if (state == null) return false;
ItemStack bind = state.getEquipment(BodyRegionV2.ARMS); ItemStack bind = state.getEquipment(BodyRegionV2.ARMS);
if ( if (bind.isEmpty()) return false;
bind.isEmpty() || !(bind.getItem() instanceof ItemBind itemBind)
) return false;
PoseType pose = HumanChairHelper.resolveEffectivePose( PoseType pose = HumanChairHelper.resolveEffectivePose(
itemBind.getPoseType(), PoseTypeHelper.getPoseType(bind),
bind bind
); );
return pose == PoseType.DOG || pose == PoseType.HUMAN_CHAIR; return pose == PoseType.DOG || pose == PoseType.HUMAN_CHAIR;

View File

@@ -2,8 +2,8 @@ package com.tiedup.remake.client.animation.render;
import com.tiedup.remake.client.renderer.layers.ClothesRenderHelper; import com.tiedup.remake.client.renderer.layers.ClothesRenderHelper;
import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.base.ItemBind;
import com.tiedup.remake.items.base.PoseType; import com.tiedup.remake.items.base.PoseType;
import com.tiedup.remake.v2.bondage.PoseTypeHelper;
import com.tiedup.remake.items.clothes.ClothesProperties; import com.tiedup.remake.items.clothes.ClothesProperties;
import com.tiedup.remake.state.PlayerBindState; import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
@@ -71,10 +71,8 @@ public class PlayerArmHideEventHandler {
// === HIDE ARMS (wrap/latex_sack poses) === // === HIDE ARMS (wrap/latex_sack poses) ===
if (state.hasArmsBound()) { if (state.hasArmsBound()) {
ItemStack bind = state.getEquipment(BodyRegionV2.ARMS); ItemStack bind = state.getEquipment(BodyRegionV2.ARMS);
if ( if (!bind.isEmpty()) {
!bind.isEmpty() && bind.getItem() instanceof ItemBind itemBind PoseType poseType = PoseTypeHelper.getPoseType(bind);
) {
PoseType poseType = itemBind.getPoseType();
// Only hide arms for wrap/sack poses (arms are covered by the item) // Only hide arms for wrap/sack poses (arms are covered by the item)
if ( if (

View File

@@ -14,8 +14,9 @@ import com.tiedup.remake.client.gltf.GltfAnimationApplier;
import com.tiedup.remake.client.state.ClothesClientCache; import com.tiedup.remake.client.state.ClothesClientCache;
import com.tiedup.remake.client.state.MovementStyleClientState; import com.tiedup.remake.client.state.MovementStyleClientState;
import com.tiedup.remake.client.state.PetBedClientState; import com.tiedup.remake.client.state.PetBedClientState;
import com.tiedup.remake.items.base.ItemBind;
import com.tiedup.remake.items.base.PoseType; import com.tiedup.remake.items.base.PoseType;
import com.tiedup.remake.v2.bondage.BindModeHelper;
import com.tiedup.remake.v2.bondage.PoseTypeHelper;
import com.tiedup.remake.state.HumanChairHelper; import com.tiedup.remake.state.HumanChairHelper;
import com.tiedup.remake.state.PlayerBindState; import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
@@ -249,14 +250,10 @@ public class AnimationTickHandler {
PlayerBindState state PlayerBindState state
) { ) {
ItemStack bind = state.getEquipment(BodyRegionV2.ARMS); ItemStack bind = state.getEquipment(BodyRegionV2.ARMS);
PoseType poseType = PoseType.STANDARD; PoseType poseType = PoseTypeHelper.getPoseType(bind);
if (bind.getItem() instanceof ItemBind itemBind) { // Human chair mode: override DOG pose to HUMAN_CHAIR (straight limbs)
poseType = itemBind.getPoseType(); poseType = HumanChairHelper.resolveEffectivePose(poseType, bind);
// Human chair mode: override DOG pose to HUMAN_CHAIR (straight limbs)
poseType = HumanChairHelper.resolveEffectivePose(poseType, bind);
}
// Derive bound state from V2 regions (works client-side, synced via capability) // Derive bound state from V2 regions (works client-side, synced via capability)
boolean armsBound = V2EquipmentHelper.isRegionOccupied( boolean armsBound = V2EquipmentHelper.isRegionOccupied(
@@ -268,10 +265,10 @@ public class AnimationTickHandler {
BodyRegionV2.LEGS BodyRegionV2.LEGS
); );
// V1 fallback: if no V2 regions are set but player is tied, derive from ItemBind NBT // V1 fallback: if no V2 regions are set but player is tied, derive from bind mode NBT
if (!armsBound && !legsBound && bind.getItem() instanceof ItemBind) { if (!armsBound && !legsBound && BindModeHelper.isBindItem(bind)) {
armsBound = ItemBind.hasArmsBound(bind); armsBound = BindModeHelper.hasArmsBound(bind);
legsBound = ItemBind.hasLegsBound(bind); legsBound = BindModeHelper.hasLegsBound(bind);
} }
boolean isStruggling = state.isStruggling(); boolean isStruggling = state.isStruggling();

View File

@@ -9,8 +9,9 @@ import com.tiedup.remake.client.gltf.GltfAnimationApplier;
import com.tiedup.remake.entities.AbstractTiedUpNpc; import com.tiedup.remake.entities.AbstractTiedUpNpc;
import com.tiedup.remake.entities.EntityMaster; import com.tiedup.remake.entities.EntityMaster;
import com.tiedup.remake.entities.ai.master.MasterState; import com.tiedup.remake.entities.ai.master.MasterState;
import com.tiedup.remake.items.base.ItemBind;
import com.tiedup.remake.items.base.PoseType; import com.tiedup.remake.items.base.PoseType;
import com.tiedup.remake.v2.bondage.BindModeHelper;
import com.tiedup.remake.v2.bondage.PoseTypeHelper;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.IV2BondageEquipment; import com.tiedup.remake.v2.bondage.IV2BondageEquipment;
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper; import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
@@ -161,13 +162,8 @@ public class NpcAnimationTickHandler {
net.minecraft.world.item.ItemStack bind = entity.getEquipment( net.minecraft.world.item.ItemStack bind = entity.getEquipment(
BodyRegionV2.ARMS BodyRegionV2.ARMS
); );
PoseType poseType = PoseType.STANDARD; PoseType poseType = PoseTypeHelper.getPoseType(bind);
boolean hasBind = false; boolean hasBind = BindModeHelper.isBindItem(bind);
if (bind.getItem() instanceof ItemBind itemBind) {
poseType = itemBind.getPoseType();
hasBind = true;
}
// Derive bound state from V2 regions (AbstractTiedUpNpc implements IV2EquipmentHolder) // Derive bound state from V2 regions (AbstractTiedUpNpc implements IV2EquipmentHolder)
boolean armsBound = V2EquipmentHelper.isRegionOccupied( boolean armsBound = V2EquipmentHelper.isRegionOccupied(
@@ -179,10 +175,10 @@ public class NpcAnimationTickHandler {
BodyRegionV2.LEGS BodyRegionV2.LEGS
); );
// V1 fallback: if no V2 regions set but NPC has a bind, derive from ItemBind NBT // V1 fallback: if no V2 regions set but NPC has a bind, derive from bind mode NBT
if (!armsBound && !legsBound && bind.getItem() instanceof ItemBind) { if (!armsBound && !legsBound && BindModeHelper.isBindItem(bind)) {
armsBound = ItemBind.hasArmsBound(bind); armsBound = BindModeHelper.hasArmsBound(bind);
legsBound = ItemBind.hasLegsBound(bind); legsBound = BindModeHelper.hasLegsBound(bind);
} }
boolean isStruggling = entity.isStruggling(); boolean isStruggling = entity.isStruggling();

View File

@@ -1,8 +1,8 @@
package com.tiedup.remake.client.events; package com.tiedup.remake.client.events;
import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.ModItems; import com.tiedup.remake.items.base.PoseType;
import com.tiedup.remake.items.base.BindVariant; import com.tiedup.remake.v2.bondage.PoseTypeHelper;
import com.tiedup.remake.state.IBondageState; import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.util.KidnappedHelper; import com.tiedup.remake.util.KidnappedHelper;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
@@ -138,7 +138,7 @@ public class LeashProxyClientHandler {
ItemStack bind = state.getEquipment(BodyRegionV2.ARMS); ItemStack bind = state.getEquipment(BodyRegionV2.ARMS);
if ( if (
!bind.isEmpty() && !bind.isEmpty() &&
bind.getItem() == ModItems.getBind(BindVariant.DOGBINDER) PoseTypeHelper.getPoseType(bind) == PoseType.DOG
) { ) {
return DOGWALK_Y_OFFSET; return DOGWALK_Y_OFFSET;
} }

View File

@@ -1,13 +1,20 @@
package com.tiedup.remake.client.events; package com.tiedup.remake.client.events;
import com.tiedup.remake.items.base.*; import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.network.ModNetwork; import com.tiedup.remake.network.ModNetwork;
import com.tiedup.remake.network.selfbondage.PacketSelfBondage; import com.tiedup.remake.network.selfbondage.PacketSelfBondage;
import com.tiedup.remake.v2.bondage.BindModeHelper;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.v2.bondage.IV2BondageItem; import com.tiedup.remake.v2.bondage.IV2BondageItem;
import com.tiedup.remake.v2.bondage.component.BlindingComponent;
import com.tiedup.remake.v2.bondage.component.ComponentType;
import com.tiedup.remake.v2.bondage.component.GaggingComponent;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemDefinition;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemRegistry;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.player.LocalPlayer; import net.minecraft.client.player.LocalPlayer;
import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionHand;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.api.distmarker.OnlyIn;
@@ -70,7 +77,7 @@ public class SelfBondageInputHandler {
if (!event.getLevel().isClientSide()) return; if (!event.getLevel().isClientSide()) return;
ItemStack stack = event.getItemStack(); ItemStack stack = event.getItemStack();
if (isSelfBondageItem(stack.getItem())) { if (isSelfBondageItem(stack)) {
event.setCanceled(true); event.setCanceled(true);
startSelfBondage(); startSelfBondage();
} }
@@ -87,11 +94,11 @@ public class SelfBondageInputHandler {
InteractionHand hand = InteractionHand.MAIN_HAND; InteractionHand hand = InteractionHand.MAIN_HAND;
ItemStack stack = player.getMainHandItem(); ItemStack stack = player.getMainHandItem();
if (!isSelfBondageItem(stack.getItem())) { if (!isSelfBondageItem(stack)) {
stack = player.getOffhandItem(); stack = player.getOffhandItem();
hand = InteractionHand.OFF_HAND; hand = InteractionHand.OFF_HAND;
if (!isSelfBondageItem(stack.getItem())) { if (!isSelfBondageItem(stack)) {
return; // No bondage item in either hand return; // No bondage item in either hand
} }
} }
@@ -130,7 +137,7 @@ public class SelfBondageInputHandler {
// Check if still holding bondage item in the active hand // Check if still holding bondage item in the active hand
ItemStack stack = player.getItemInHand(activeHand); ItemStack stack = player.getItemInHand(activeHand);
if (!isSelfBondageItem(stack.getItem())) { if (!isSelfBondageItem(stack)) {
stopSelfBondage(); stopSelfBondage();
return; return;
} }
@@ -153,27 +160,31 @@ public class SelfBondageInputHandler {
} }
/** /**
* Check if an item supports self-bondage. * Check if a stack supports self-bondage.
* Collar is explicitly excluded. * Collar is explicitly excluded.
*/ */
private static boolean isSelfBondageItem(Item item) { private static boolean isSelfBondageItem(ItemStack stack) {
// Collar cannot be self-equipped (V1 collar guard) if (stack.isEmpty()) return false;
if (item instanceof ItemCollar) {
// Collar cannot be self-equipped (V1 collar guard + V2 ownership component)
if (stack.getItem() instanceof ItemCollar || CollarHelper.isCollar(stack)) {
return false; return false;
} }
// V2 bondage items support self-bondage (left-click hold with tying duration) // V2 bondage items support self-bondage (left-click hold with tying duration)
if (item instanceof IV2BondageItem) { if (stack.getItem() instanceof IV2BondageItem) {
return true; return true;
} }
// V1 bondage items (legacy) // V2 data-driven items: check if it occupies any bondage region
return ( DataDrivenItemDefinition def = DataDrivenItemRegistry.get(stack);
item instanceof ItemBind || if (def != null) {
item instanceof ItemGag || return true;
item instanceof ItemBlindfold || }
item instanceof ItemMittens ||
item instanceof ItemEarplugs // V1 fallback: bind items
); return BindModeHelper.isBindItem(stack)
|| DataDrivenBondageItem.getComponent(stack, ComponentType.GAGGING, GaggingComponent.class) != null
|| DataDrivenBondageItem.getComponent(stack, ComponentType.BLINDING, BlindingComponent.class) != null;
} }
} }

View File

@@ -3,7 +3,7 @@ package com.tiedup.remake.client.gui.screens;
import com.tiedup.remake.client.gui.util.GuiColors; import com.tiedup.remake.client.gui.util.GuiColors;
import com.tiedup.remake.client.gui.util.GuiLayoutConstants; import com.tiedup.remake.client.gui.util.GuiLayoutConstants;
import com.tiedup.remake.client.gui.widgets.SlaveEntryWidget; import com.tiedup.remake.client.gui.widgets.SlaveEntryWidget;
import com.tiedup.remake.items.base.ItemCollar; import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.network.ModNetwork; import com.tiedup.remake.network.ModNetwork;
import com.tiedup.remake.network.slave.PacketSlaveAction; import com.tiedup.remake.network.slave.PacketSlaveAction;
import com.tiedup.remake.state.IBondageState; import com.tiedup.remake.state.IBondageState;
@@ -145,8 +145,8 @@ public class SlaveManagementScreen extends BaseScreen {
ItemStack collarStack = kidnapped.getEquipment( ItemStack collarStack = kidnapped.getEquipment(
BodyRegionV2.NECK BodyRegionV2.NECK
); );
if (collarStack.getItem() instanceof ItemCollar collar) { if (CollarHelper.isCollar(collarStack)) {
if (collar.isOwner(collarStack, player)) { if (CollarHelper.isOwner(collarStack, player)) {
addSlaveEntry(kidnapped); addSlaveEntry(kidnapped);
addedUUIDs.add(entity.getUUID()); addedUUIDs.add(entity.getUUID());
} }

View File

@@ -7,7 +7,7 @@ import com.tiedup.remake.items.ItemLockpick;
import com.tiedup.remake.items.ModItems; import com.tiedup.remake.items.ModItems;
import com.tiedup.remake.items.base.IHasResistance; import com.tiedup.remake.items.base.IHasResistance;
import com.tiedup.remake.items.base.ILockable; import com.tiedup.remake.items.base.ILockable;
import com.tiedup.remake.items.base.ItemCollar; import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.network.ModNetwork; import com.tiedup.remake.network.ModNetwork;
import com.tiedup.remake.network.action.PacketSetKnifeCutTarget; import com.tiedup.remake.network.action.PacketSetKnifeCutTarget;
import com.tiedup.remake.network.minigame.PacketLockpickMiniGameStart; import com.tiedup.remake.network.minigame.PacketLockpickMiniGameStart;
@@ -474,10 +474,10 @@ public class ActionPanel extends AbstractWidget {
// Bondage Service toggle (NECK collar only, prison configured) // Bondage Service toggle (NECK collar only, prison configured)
if ( if (
selectedRegion == BodyRegionV2.NECK && selectedRegion == BodyRegionV2.NECK &&
selectedItem.getItem() instanceof ItemCollar collar CollarHelper.isCollar(selectedItem)
) { ) {
if (collar.hasCellAssigned(selectedItem)) { if (CollarHelper.hasCellAssigned(selectedItem)) {
boolean svcEnabled = collar.isBondageServiceEnabled( boolean svcEnabled = CollarHelper.isBondageServiceEnabled(
selectedItem selectedItem
); );
String svcKey = svcEnabled String svcKey = svcEnabled

View File

@@ -5,7 +5,7 @@ import static com.tiedup.remake.client.gui.util.GuiLayoutConstants.*;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.tiedup.remake.client.gui.util.GuiColors; import com.tiedup.remake.client.gui.util.GuiColors;
import com.tiedup.remake.items.ItemGpsCollar; import com.tiedup.remake.items.ItemGpsCollar;
import com.tiedup.remake.items.base.ItemCollar; import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.state.IBondageState; import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
import java.util.ArrayList; import java.util.ArrayList;
@@ -560,19 +560,13 @@ public class SlaveEntryWidget
private boolean hasShockCollar() { private boolean hasShockCollar() {
if (!slave.hasCollar()) return false; if (!slave.hasCollar()) return false;
ItemStack collar = slave.getEquipment(BodyRegionV2.NECK); ItemStack collar = slave.getEquipment(BodyRegionV2.NECK);
return ( return CollarHelper.canShock(collar);
collar.getItem() instanceof ItemCollar itemCollar &&
itemCollar.canShock()
);
} }
private boolean hasGPSCollar() { private boolean hasGPSCollar() {
if (!slave.hasCollar()) return false; if (!slave.hasCollar()) return false;
ItemStack collar = slave.getEquipment(BodyRegionV2.NECK); ItemStack collar = slave.getEquipment(BodyRegionV2.NECK);
return ( return CollarHelper.hasGPS(collar);
collar.getItem() instanceof ItemCollar itemCollar &&
itemCollar.hasGPS()
);
} }
private boolean isInAnySafeZone( private boolean isInAnySafeZone(
@@ -580,7 +574,7 @@ public class SlaveEntryWidget
ItemStack collarStack, ItemStack collarStack,
LivingEntity entity LivingEntity entity
) { ) {
if (!gps.isActive(collarStack)) return true; if (!CollarHelper.isActive(collarStack)) return true;
var safeSpots = gps.getSafeSpots(collarStack); var safeSpots = gps.getSafeSpots(collarStack);
if (safeSpots.isEmpty()) return true; if (safeSpots.isEmpty()) return true;

View File

@@ -9,8 +9,9 @@ import com.tiedup.remake.entities.AbstractTiedUpNpc;
import com.tiedup.remake.entities.EntityKidnapperArcher; import com.tiedup.remake.entities.EntityKidnapperArcher;
import com.tiedup.remake.entities.EntityMaster; import com.tiedup.remake.entities.EntityMaster;
import com.tiedup.remake.entities.ai.master.MasterState; import com.tiedup.remake.entities.ai.master.MasterState;
import com.tiedup.remake.items.base.ItemBind;
import com.tiedup.remake.items.base.PoseType; import com.tiedup.remake.items.base.PoseType;
import com.tiedup.remake.v2.bondage.BindModeHelper;
import com.tiedup.remake.v2.bondage.PoseTypeHelper;
import com.tiedup.remake.items.clothes.GenericClothes; import com.tiedup.remake.items.clothes.GenericClothes;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper; import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
@@ -230,11 +231,7 @@ public class DamselModel
if (inPose) { if (inPose) {
ItemStack bind = entity.getEquipment(BodyRegionV2.ARMS); ItemStack bind = entity.getEquipment(BodyRegionV2.ARMS);
PoseType poseType = PoseType.STANDARD; PoseType poseType = PoseTypeHelper.getPoseType(bind);
if (bind.getItem() instanceof ItemBind itemBind) {
poseType = itemBind.getPoseType();
}
// Hide arms for wrap/latex_sack poses // Hide arms for wrap/latex_sack poses
if (poseType == PoseType.WRAP || poseType == PoseType.LATEX_SACK) { if (poseType == PoseType.WRAP || poseType == PoseType.LATEX_SACK) {
@@ -252,9 +249,7 @@ public class DamselModel
PoseType currentPoseType = PoseType.STANDARD; PoseType currentPoseType = PoseType.STANDARD;
if (inPose) { if (inPose) {
ItemStack bindForPoseType = entity.getEquipment(BodyRegionV2.ARMS); ItemStack bindForPoseType = entity.getEquipment(BodyRegionV2.ARMS);
if (bindForPoseType.getItem() instanceof ItemBind itemBindForType) { currentPoseType = PoseTypeHelper.getPoseType(bindForPoseType);
currentPoseType = itemBindForType.getPoseType();
}
} }
// Check if this is a Master in human chair mode (head should look around freely) // Check if this is a Master in human chair mode (head should look around freely)
@@ -306,11 +301,7 @@ public class DamselModel
// Animation not yet active (1-frame delay) - apply static pose as fallback // Animation not yet active (1-frame delay) - apply static pose as fallback
// This ensures immediate visual feedback when bind is applied // This ensures immediate visual feedback when bind is applied
ItemStack bind = entity.getEquipment(BodyRegionV2.ARMS); ItemStack bind = entity.getEquipment(BodyRegionV2.ARMS);
PoseType fallbackPoseType = PoseType.STANDARD; PoseType fallbackPoseType = PoseTypeHelper.getPoseType(bind);
if (bind.getItem() instanceof ItemBind itemBind) {
fallbackPoseType = itemBind.getPoseType();
}
// Derive bound state from V2 regions (AbstractTiedUpNpc implements IV2EquipmentHolder) // Derive bound state from V2 regions (AbstractTiedUpNpc implements IV2EquipmentHolder)
boolean armsBound = V2EquipmentHelper.isRegionOccupied( boolean armsBound = V2EquipmentHelper.isRegionOccupied(
@@ -323,10 +314,10 @@ public class DamselModel
); );
if ( if (
!armsBound && !legsBound && bind.getItem() instanceof ItemBind !armsBound && !legsBound && BindModeHelper.isBindItem(bind)
) { ) {
armsBound = ItemBind.hasArmsBound(bind); armsBound = BindModeHelper.hasArmsBound(bind);
legsBound = ItemBind.hasLegsBound(bind); legsBound = BindModeHelper.hasLegsBound(bind);
} }
// Apply static pose directly to model parts // Apply static pose directly to model parts

View File

@@ -7,8 +7,8 @@ import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.tiedup.remake.cells.CellDataV2; import com.tiedup.remake.cells.CellDataV2;
import com.tiedup.remake.cells.CellRegistryV2; import com.tiedup.remake.cells.CellRegistryV2;
import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.state.PlayerBindState; import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.util.teleport.Position; import com.tiedup.remake.util.teleport.Position;
import com.tiedup.remake.util.teleport.TeleportHelper; import com.tiedup.remake.util.teleport.TeleportHelper;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
@@ -160,8 +160,8 @@ public class CollarCommand {
} }
if (source.getEntity() instanceof ServerPlayer executor) { if (source.getEntity() instanceof ServerPlayer executor) {
if (collar.getItem() instanceof ItemCollar collarItem) { if (CollarHelper.isCollar(collar)) {
collarItem.addOwner(collar, executor); CollarHelper.addOwner(collar, executor);
source.sendSuccess( source.sendSuccess(
() -> () ->
Component.literal( Component.literal(
@@ -195,8 +195,8 @@ public class CollarCommand {
} }
if (source.getEntity() instanceof ServerPlayer executor) { if (source.getEntity() instanceof ServerPlayer executor) {
if (collar.getItem() instanceof ItemCollar collarItem) { if (CollarHelper.isCollar(collar)) {
collarItem.removeOwner(collar, executor.getUUID()); CollarHelper.removeOwner(collar, executor.getUUID());
source.sendSuccess( source.sendSuccess(
() -> () ->
Component.literal( Component.literal(
@@ -230,8 +230,8 @@ public class CollarCommand {
return 0; return 0;
} }
if (collar.getItem() instanceof ItemCollar collarItem) { if (CollarHelper.isCollar(collar)) {
collarItem.setNickname(collar, name); CollarHelper.setNickname(collar, name);
source.sendSuccess( source.sendSuccess(
() -> () ->
Component.literal( Component.literal(
@@ -261,8 +261,8 @@ public class CollarCommand {
return 0; return 0;
} }
if (collar.getItem() instanceof ItemCollar collarItem) { if (CollarHelper.isCollar(collar)) {
collarItem.addOwner(collar, owner); CollarHelper.addOwner(collar, owner);
source.sendSuccess( source.sendSuccess(
() -> () ->
Component.literal( Component.literal(
@@ -296,8 +296,8 @@ public class CollarCommand {
return 0; return 0;
} }
if (collar.getItem() instanceof ItemCollar collarItem) { if (CollarHelper.isCollar(collar)) {
collarItem.removeOwner(collar, owner.getUUID()); CollarHelper.removeOwner(collar, owner.getUUID());
source.sendSuccess( source.sendSuccess(
() -> () ->
Component.literal( Component.literal(
@@ -348,8 +348,8 @@ public class CollarCommand {
return 0; return 0;
} }
if (collar.getItem() instanceof ItemCollar collarItem) { if (CollarHelper.isCollar(collar)) {
collarItem.setCellId(collar, cell.getId()); CollarHelper.setCellId(collar, cell.getId());
source.sendSuccess( source.sendSuccess(
() -> () ->
Component.literal( Component.literal(
@@ -388,8 +388,8 @@ public class CollarCommand {
return 0; return 0;
} }
if (collar.getItem() instanceof ItemCollar collarItem) { if (CollarHelper.isCollar(collar)) {
if (!collarItem.hasCellAssigned(collar)) { if (!CollarHelper.hasCellAssigned(collar)) {
source.sendFailure( source.sendFailure(
Component.literal("No cell assigned to collar") Component.literal("No cell assigned to collar")
); );
@@ -397,7 +397,7 @@ public class CollarCommand {
} }
// Get cell position and teleport // Get cell position and teleport
java.util.UUID cellId = collarItem.getCellId(collar); java.util.UUID cellId = CollarHelper.getCellId(collar);
ServerLevel serverLevel = source.getLevel(); ServerLevel serverLevel = source.getLevel();
CellDataV2 cell = CellRegistryV2.get(serverLevel).getCell(cellId); CellDataV2 cell = CellRegistryV2.get(serverLevel).getCell(cellId);
@@ -449,7 +449,7 @@ public class CollarCommand {
return 0; return 0;
} }
if (collar.getItem() instanceof ItemCollar collarItem) { if (CollarHelper.isCollar(collar)) {
source.sendSuccess( source.sendSuccess(
() -> () ->
Component.literal( Component.literal(
@@ -460,24 +460,24 @@ public class CollarCommand {
false false
); );
String nickname = collarItem.getNickname(collar); String nickname = CollarHelper.getNickname(collar);
source.sendSuccess( source.sendSuccess(
() -> () ->
Component.literal( Component.literal(
"§7Nickname: §f" + "§7Nickname: §f" +
(nickname.isEmpty() ? "None" : nickname) (nickname == null || nickname.isEmpty() ? "None" : nickname)
), ),
false false
); );
source.sendSuccess( source.sendSuccess(
() -> () ->
Component.literal( Component.literal(
"§7Has Owner: §f" + collarItem.hasOwner(collar) "§7Has Owner: §f" + CollarHelper.hasOwner(collar)
), ),
false false
); );
// Cell assignment // Cell assignment
java.util.UUID cellId = collarItem.getCellId(collar); java.util.UUID cellId = CollarHelper.getCellId(collar);
if (cellId != null) { if (cellId != null) {
ServerLevel serverLevel = source.getLevel(); ServerLevel serverLevel = source.getLevel();
CellRegistryV2 registry = CellRegistryV2.get(serverLevel); CellRegistryV2 registry = CellRegistryV2.get(serverLevel);
@@ -510,10 +510,12 @@ public class CollarCommand {
); );
} }
boolean locked = collar.getItem() instanceof com.tiedup.remake.items.base.ILockable lockable
&& lockable.isLocked(collar);
source.sendSuccess( source.sendSuccess(
() -> () ->
Component.literal( Component.literal(
"§7Locked: §f" + collarItem.isLocked(collar) "§7Locked: §f" + locked
), ),
false false
); );

View File

@@ -4,12 +4,10 @@ import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.tiedup.remake.items.ModItems; import com.tiedup.remake.items.ModItems;
import com.tiedup.remake.items.base.BindVariant;
import com.tiedup.remake.items.base.BlindfoldVariant;
import com.tiedup.remake.items.base.EarplugsVariant;
import com.tiedup.remake.items.base.GagVariant;
import com.tiedup.remake.items.base.KnifeVariant; import com.tiedup.remake.items.base.KnifeVariant;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import java.util.Optional; import java.util.Optional;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands; import net.minecraft.commands.Commands;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
@@ -82,42 +80,18 @@ public class KidnapSetCommand {
int given = 0; int given = 0;
// Binds // Binds
given += giveItem( given += giveDataDrivenItems(player, "ropes", 8);
player, given += giveDataDrivenItems(player, "chain", 4);
new ItemStack(ModItems.getBind(BindVariant.ROPES), 8) given += giveDataDrivenItems(player, "leather_straps", 4);
);
given += giveItem(
player,
new ItemStack(ModItems.getBind(BindVariant.CHAIN), 4)
);
given += giveItem(
player,
new ItemStack(ModItems.getBind(BindVariant.LEATHER_STRAPS), 4)
);
// Gags // Gags
given += giveItem( given += giveDataDrivenItems(player, "cloth_gag", 4);
player, given += giveDataDrivenItems(player, "ball_gag", 4);
new ItemStack(ModItems.getGag(GagVariant.CLOTH_GAG), 4) given += giveDataDrivenItems(player, "tape_gag", 4);
);
given += giveItem(
player,
new ItemStack(ModItems.getGag(GagVariant.BALL_GAG), 4)
);
given += giveItem(
player,
new ItemStack(ModItems.getGag(GagVariant.TAPE_GAG), 4)
);
// Blindfolds // Blindfolds
given += giveItem( given += giveDataDrivenItems(player, "classic_blindfold", 4);
player, given += giveDataDrivenItems(player, "blindfold_mask", 2);
new ItemStack(ModItems.getBlindfold(BlindfoldVariant.CLASSIC), 4)
);
given += giveItem(
player,
new ItemStack(ModItems.getBlindfold(BlindfoldVariant.MASK), 2)
);
// Collars // Collars
given += giveItem( given += giveItem(
@@ -155,10 +129,7 @@ public class KidnapSetCommand {
given += giveItem(player, new ItemStack(ModItems.MASTER_KEY.get(), 1)); given += giveItem(player, new ItemStack(ModItems.MASTER_KEY.get(), 1));
// Earplugs // Earplugs
given += giveItem( given += giveDataDrivenItems(player, "classic_earplugs", 4);
player,
new ItemStack(ModItems.getEarplugs(EarplugsVariant.CLASSIC), 4)
);
// Rope arrows // Rope arrows
given += giveItem(player, new ItemStack(ModItems.ROPE_ARROW.get(), 16)); given += giveItem(player, new ItemStack(ModItems.ROPE_ARROW.get(), 16));
@@ -182,6 +153,18 @@ public class KidnapSetCommand {
return finalGiven; return finalGiven;
} }
private static int giveDataDrivenItems(ServerPlayer player, String itemName, int count) {
int given = 0;
for (int i = 0; i < count; i++) {
ItemStack stack = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", itemName));
if (!stack.isEmpty()) {
giveItem(player, stack);
given++;
}
}
return given;
}
private static int giveItem(ServerPlayer player, ItemStack stack) { private static int giveItem(ServerPlayer player, ItemStack stack) {
if (!player.getInventory().add(stack)) { if (!player.getInventory().add(stack)) {
// Drop on ground if inventory full // Drop on ground if inventory full

View File

@@ -7,11 +7,9 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.tiedup.remake.entities.*; import com.tiedup.remake.entities.*;
import com.tiedup.remake.entities.skins.EliteKidnapperSkinManager; import com.tiedup.remake.entities.skins.EliteKidnapperSkinManager;
import com.tiedup.remake.items.ModItems; import com.tiedup.remake.items.ModItems;
import com.tiedup.remake.items.base.BindVariant;
import com.tiedup.remake.items.base.BlindfoldVariant;
import com.tiedup.remake.items.base.EarplugsVariant;
import com.tiedup.remake.items.base.GagVariant;
import com.tiedup.remake.state.IBondageState; import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import net.minecraft.resources.ResourceLocation;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@@ -507,7 +505,7 @@ public class NPCCommand {
npc.equip( npc.equip(
BodyRegionV2.ARMS, BodyRegionV2.ARMS,
new ItemStack(ModItems.getBind(BindVariant.ROPES)) DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "ropes"))
); );
context context
.getSource() .getSource()
@@ -538,7 +536,7 @@ public class NPCCommand {
npc.equip( npc.equip(
BodyRegionV2.MOUTH, BodyRegionV2.MOUTH,
new ItemStack(ModItems.getGag(GagVariant.CLOTH_GAG)) DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "cloth_gag"))
); );
context context
.getSource() .getSource()
@@ -571,7 +569,7 @@ public class NPCCommand {
npc.equip( npc.equip(
BodyRegionV2.EYES, BodyRegionV2.EYES,
new ItemStack(ModItems.getBlindfold(BlindfoldVariant.CLASSIC)) DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_blindfold"))
); );
context context
.getSource() .getSource()
@@ -656,10 +654,10 @@ public class NPCCommand {
com.tiedup.remake.entities.AbstractTiedUpNpc npcEntity com.tiedup.remake.entities.AbstractTiedUpNpc npcEntity
) { ) {
npcEntity.applyBondage( npcEntity.applyBondage(
new ItemStack(ModItems.getBind(BindVariant.ROPES)), DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "ropes")),
new ItemStack(ModItems.getGag(GagVariant.CLOTH_GAG)), DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "cloth_gag")),
new ItemStack(ModItems.getBlindfold(BlindfoldVariant.CLASSIC)), DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_blindfold")),
new ItemStack(ModItems.getEarplugs(EarplugsVariant.CLASSIC)), DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_earplugs")),
new ItemStack(ModItems.CLASSIC_COLLAR.get()), new ItemStack(ModItems.CLASSIC_COLLAR.get()),
ItemStack.EMPTY // No clothes ItemStack.EMPTY // No clothes
); );

View File

@@ -9,12 +9,10 @@ import com.tiedup.remake.commands.CommandHelper;
import com.tiedup.remake.core.SystemMessageManager; import com.tiedup.remake.core.SystemMessageManager;
import com.tiedup.remake.items.ModItems; import com.tiedup.remake.items.ModItems;
import com.tiedup.remake.items.base.AdjustmentHelper; import com.tiedup.remake.items.base.AdjustmentHelper;
import com.tiedup.remake.items.base.BindVariant;
import com.tiedup.remake.items.base.BlindfoldVariant;
import com.tiedup.remake.items.base.EarplugsVariant;
import com.tiedup.remake.items.base.GagVariant;
import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.network.ModNetwork; import com.tiedup.remake.network.ModNetwork;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import net.minecraft.resources.ResourceLocation;
import com.tiedup.remake.network.sync.PacketSyncBindState; import com.tiedup.remake.network.sync.PacketSyncBindState;
import com.tiedup.remake.state.PlayerBindState; import com.tiedup.remake.state.PlayerBindState;
import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.CommandSourceStack;
@@ -256,7 +254,7 @@ public class BondageSubCommand {
return 0; return 0;
} }
ItemStack ropes = new ItemStack(ModItems.getBind(BindVariant.ROPES)); ItemStack ropes = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "ropes"));
state.putBindOn(ropes); state.putBindOn(ropes);
CommandHelper.syncPlayerState(targetPlayer, state); CommandHelper.syncPlayerState(targetPlayer, state);
@@ -387,7 +385,7 @@ public class BondageSubCommand {
return 0; return 0;
} }
ItemStack gag = new ItemStack(ModItems.getGag(GagVariant.CLOTH_GAG)); ItemStack gag = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "cloth_gag"));
state.putGagOn(gag); state.putGagOn(gag);
CommandHelper.syncPlayerState(targetPlayer, state); CommandHelper.syncPlayerState(targetPlayer, state);
@@ -440,9 +438,7 @@ public class BondageSubCommand {
return 0; return 0;
} }
ItemStack blindfold = new ItemStack( ItemStack blindfold = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_blindfold"));
ModItems.getBlindfold(BlindfoldVariant.CLASSIC)
);
state.putBlindfoldOn(blindfold); state.putBlindfoldOn(blindfold);
CommandHelper.syncPlayerState(targetPlayer, state); CommandHelper.syncPlayerState(targetPlayer, state);
@@ -500,8 +496,7 @@ public class BondageSubCommand {
ItemStack collar = new ItemStack(ModItems.CLASSIC_COLLAR.get()); ItemStack collar = new ItemStack(ModItems.CLASSIC_COLLAR.get());
if (context.getSource().getEntity() instanceof ServerPlayer executor) { if (context.getSource().getEntity() instanceof ServerPlayer executor) {
ItemCollar collarItem = (ItemCollar) collar.getItem(); CollarHelper.addOwner(collar, executor);
collarItem.addOwner(collar, executor);
} }
state.putCollarOn(collar); state.putCollarOn(collar);
@@ -1021,9 +1016,7 @@ public class BondageSubCommand {
return 0; return 0;
} }
ItemStack earplugs = new ItemStack( ItemStack earplugs = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_earplugs"));
ModItems.getEarplugs(EarplugsVariant.CLASSIC)
);
state.putEarplugsOn(earplugs); state.putEarplugsOn(earplugs);
CommandHelper.syncPlayerState(targetPlayer, state); CommandHelper.syncPlayerState(targetPlayer, state);
@@ -1067,25 +1060,19 @@ public class BondageSubCommand {
int applied = 0; int applied = 0;
if (!state.isTiedUp()) { if (!state.isTiedUp()) {
ItemStack ropes = new ItemStack( ItemStack ropes = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "ropes"));
ModItems.getBind(BindVariant.ROPES)
);
state.putBindOn(ropes); state.putBindOn(ropes);
applied++; applied++;
} }
if (!state.isGagged()) { if (!state.isGagged()) {
ItemStack gag = new ItemStack( ItemStack gag = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "cloth_gag"));
ModItems.getGag(GagVariant.CLOTH_GAG)
);
state.putGagOn(gag); state.putGagOn(gag);
applied++; applied++;
} }
if (!state.isBlindfolded()) { if (!state.isBlindfolded()) {
ItemStack blindfold = new ItemStack( ItemStack blindfold = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_blindfold"));
ModItems.getBlindfold(BlindfoldVariant.CLASSIC)
);
state.putBlindfoldOn(blindfold); state.putBlindfoldOn(blindfold);
applied++; applied++;
} }
@@ -1095,17 +1082,14 @@ public class BondageSubCommand {
if ( if (
context.getSource().getEntity() instanceof ServerPlayer executor context.getSource().getEntity() instanceof ServerPlayer executor
) { ) {
ItemCollar collarItem = (ItemCollar) collar.getItem(); CollarHelper.addOwner(collar, executor);
collarItem.addOwner(collar, executor);
} }
state.putCollarOn(collar); state.putCollarOn(collar);
applied++; applied++;
} }
if (!state.hasEarplugs()) { if (!state.hasEarplugs()) {
ItemStack earplugs = new ItemStack( ItemStack earplugs = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_earplugs"));
ModItems.getEarplugs(EarplugsVariant.CLASSIC)
);
state.putEarplugsOn(earplugs); state.putEarplugsOn(earplugs);
applied++; applied++;
} }
@@ -1167,21 +1151,15 @@ public class BondageSubCommand {
// First fully restrain // First fully restrain
if (!state.isTiedUp()) { if (!state.isTiedUp()) {
ItemStack ropes = new ItemStack( ItemStack ropes = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "ropes"));
ModItems.getBind(BindVariant.ROPES)
);
state.putBindOn(ropes); state.putBindOn(ropes);
} }
if (!state.isGagged()) { if (!state.isGagged()) {
ItemStack gag = new ItemStack( ItemStack gag = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "cloth_gag"));
ModItems.getGag(GagVariant.CLOTH_GAG)
);
state.putGagOn(gag); state.putGagOn(gag);
} }
if (!state.isBlindfolded()) { if (!state.isBlindfolded()) {
ItemStack blindfold = new ItemStack( ItemStack blindfold = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_blindfold"));
ModItems.getBlindfold(BlindfoldVariant.CLASSIC)
);
state.putBlindfoldOn(blindfold); state.putBlindfoldOn(blindfold);
} }
if (!state.hasCollar()) { if (!state.hasCollar()) {
@@ -1189,15 +1167,12 @@ public class BondageSubCommand {
if ( if (
context.getSource().getEntity() instanceof ServerPlayer executor context.getSource().getEntity() instanceof ServerPlayer executor
) { ) {
ItemCollar collarItem = (ItemCollar) collar.getItem(); CollarHelper.addOwner(collar, executor);
collarItem.addOwner(collar, executor);
} }
state.putCollarOn(collar); state.putCollarOn(collar);
} }
if (!state.hasEarplugs()) { if (!state.hasEarplugs()) {
ItemStack earplugs = new ItemStack( ItemStack earplugs = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_earplugs"));
ModItems.getEarplugs(EarplugsVariant.CLASSIC)
);
state.putEarplugsOn(earplugs); state.putEarplugsOn(earplugs);
} }

View File

@@ -2,8 +2,10 @@ package com.tiedup.remake.compat.mca.capability;
import com.tiedup.remake.compat.mca.MCABondageManager; import com.tiedup.remake.compat.mca.MCABondageManager;
import com.tiedup.remake.compat.mca.MCACompat; import com.tiedup.remake.compat.mca.MCACompat;
import com.tiedup.remake.items.base.IHasBlindingEffect; import com.tiedup.remake.v2.bondage.component.BlindingComponent;
import com.tiedup.remake.items.base.IHasGaggingEffect; import com.tiedup.remake.v2.bondage.component.ComponentType;
import com.tiedup.remake.v2.bondage.component.GaggingComponent;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import com.tiedup.remake.items.base.IHasResistance; import com.tiedup.remake.items.base.IHasResistance;
import com.tiedup.remake.items.base.ILockable; import com.tiedup.remake.items.base.ILockable;
import com.tiedup.remake.items.base.ItemBind; import com.tiedup.remake.items.base.ItemBind;
@@ -277,16 +279,17 @@ public class MCAKidnappedAdapter implements IRestrainable {
@Override @Override
public boolean hasGaggingEffect() { public boolean hasGaggingEffect() {
ItemStack gag = cap.getGag(); ItemStack gag = cap.getGag();
return !gag.isEmpty() && gag.getItem() instanceof IHasGaggingEffect; if (gag.isEmpty()) return false;
if (DataDrivenBondageItem.getComponent(gag, ComponentType.GAGGING, GaggingComponent.class) != null) return true;
return gag.getItem() instanceof com.tiedup.remake.items.base.IHasGaggingEffect;
} }
@Override @Override
public boolean hasBlindingEffect() { public boolean hasBlindingEffect() {
ItemStack blindfold = cap.getBlindfold(); ItemStack blindfold = cap.getBlindfold();
return ( if (blindfold.isEmpty()) return false;
!blindfold.isEmpty() && if (DataDrivenBondageItem.getComponent(blindfold, ComponentType.BLINDING, BlindingComponent.class) != null) return true;
blindfold.getItem() instanceof IHasBlindingEffect return blindfold.getItem() instanceof com.tiedup.remake.items.base.IHasBlindingEffect;
);
} }
@Override @Override

View File

@@ -5,6 +5,9 @@ import static com.tiedup.remake.util.GameConstants.*;
import com.tiedup.remake.dialogue.EmotionalContext.EmotionType; import com.tiedup.remake.dialogue.EmotionalContext.EmotionType;
import com.tiedup.remake.items.base.ItemGag; import com.tiedup.remake.items.base.ItemGag;
import com.tiedup.remake.state.IBondageState; import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.v2.bondage.component.ComponentType;
import com.tiedup.remake.v2.bondage.component.GaggingComponent;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import com.tiedup.remake.util.GagMaterial; import com.tiedup.remake.util.GagMaterial;
import com.tiedup.remake.util.PhoneticMapper; import com.tiedup.remake.util.PhoneticMapper;
import com.tiedup.remake.util.SyllableAnalyzer; import com.tiedup.remake.util.SyllableAnalyzer;
@@ -58,7 +61,12 @@ public class GagTalkManager {
) { ) {
LivingEntity entity = kidnapped.asLivingEntity(); LivingEntity entity = kidnapped.asLivingEntity();
GagMaterial material = GagMaterial.CLOTH; GagMaterial material = GagMaterial.CLOTH;
if (gagStack.getItem() instanceof ItemGag gag) { // V2: check data-driven GaggingComponent first
GaggingComponent gaggingComp = DataDrivenBondageItem.getComponent(
gagStack, ComponentType.GAGGING, GaggingComponent.class);
if (gaggingComp != null && gaggingComp.getMaterial() != null) {
material = gaggingComp.getMaterial();
} else if (gagStack.getItem() instanceof ItemGag gag) {
material = gag.getGagMaterial(); material = gag.getGagMaterial();
} }
@@ -514,8 +522,15 @@ public class GagTalkManager {
} }
GagMaterial material = GagMaterial.CLOTH; GagMaterial material = GagMaterial.CLOTH;
if (gagStack != null && gagStack.getItem() instanceof ItemGag gag) { if (gagStack != null && !gagStack.isEmpty()) {
material = gag.getGagMaterial(); // V2: check data-driven GaggingComponent first
GaggingComponent comp = DataDrivenBondageItem.getComponent(
gagStack, ComponentType.GAGGING, GaggingComponent.class);
if (comp != null && comp.getMaterial() != null) {
material = comp.getMaterial();
} else if (gagStack.getItem() instanceof ItemGag gag) {
material = gag.getGagMaterial();
}
} }
StringBuilder muffled = new StringBuilder(); StringBuilder muffled = new StringBuilder();

View File

@@ -5,9 +5,9 @@ import com.tiedup.remake.dialogue.DialogueBridge;
import com.tiedup.remake.entities.EntityMaster; import com.tiedup.remake.entities.EntityMaster;
import com.tiedup.remake.entities.ai.master.MasterPlaceBlockGoal; import com.tiedup.remake.entities.ai.master.MasterPlaceBlockGoal;
import com.tiedup.remake.entities.ai.master.MasterState; import com.tiedup.remake.entities.ai.master.MasterState;
import com.tiedup.remake.items.ModItems;
import com.tiedup.remake.items.base.BindVariant;
import com.tiedup.remake.network.ModNetwork; import com.tiedup.remake.network.ModNetwork;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import net.minecraft.resources.ResourceLocation;
import com.tiedup.remake.network.master.PacketOpenPetRequestMenu; import com.tiedup.remake.network.master.PacketOpenPetRequestMenu;
import com.tiedup.remake.state.PlayerBindState; import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
@@ -171,9 +171,7 @@ public class PetRequestManager {
// Put dogbind on player (if not already tied) // Put dogbind on player (if not already tied)
if (!state.isTiedUp()) { if (!state.isTiedUp()) {
ItemStack dogbind = new ItemStack( ItemStack dogbind = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "dogbinder"));
ModItems.getBind(BindVariant.DOGBINDER)
);
state.equip(BodyRegionV2.ARMS, dogbind); state.equip(BodyRegionV2.ARMS, dogbind);
TiedUpMod.LOGGER.debug( TiedUpMod.LOGGER.debug(
"[PetRequestManager] Equipped dogbind on {} for walk", "[PetRequestManager] Equipped dogbind on {} for walk",
@@ -228,7 +226,7 @@ public class PetRequestManager {
} }
// Master equips armbinder on pet (classic pet play restraint) // Master equips armbinder on pet (classic pet play restraint)
ItemStack bind = new ItemStack(ModItems.getBind(BindVariant.ARMBINDER)); ItemStack bind = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "armbinder"));
state.equip(BodyRegionV2.ARMS, bind); state.equip(BodyRegionV2.ARMS, bind);
DialogueBridge.talkTo(master, pet, "petplay.tie_accept"); DialogueBridge.talkTo(master, pet, "petplay.tie_accept");

View File

@@ -4,7 +4,12 @@ import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.entities.damsel.components.*; import com.tiedup.remake.entities.damsel.components.*;
import com.tiedup.remake.entities.skins.Gender; import com.tiedup.remake.entities.skins.Gender;
import com.tiedup.remake.items.base.ILockable; import com.tiedup.remake.items.base.ILockable;
import com.tiedup.remake.items.base.ItemCollar; import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.v2.bondage.PoseTypeHelper;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import com.tiedup.remake.v2.bondage.component.ComponentType;
import com.tiedup.remake.v2.bondage.component.GaggingComponent;
import com.tiedup.remake.v2.bondage.component.BlindingComponent;
import com.tiedup.remake.state.ICaptor; import com.tiedup.remake.state.ICaptor;
import com.tiedup.remake.state.IRestrainable; import com.tiedup.remake.state.IRestrainable;
import com.tiedup.remake.state.IRestrainableEntity; import com.tiedup.remake.state.IRestrainableEntity;
@@ -455,16 +460,8 @@ public abstract class AbstractTiedUpNpc
*/ */
public boolean isDogPose() { public boolean isDogPose() {
ItemStack bind = this.getEquipment(BodyRegionV2.ARMS); ItemStack bind = this.getEquipment(BodyRegionV2.ARMS);
if ( if (bind.isEmpty()) return false;
bind.getItem() instanceof return PoseTypeHelper.getPoseType(bind) == com.tiedup.remake.items.base.PoseType.DOG;
com.tiedup.remake.items.base.ItemBind itemBind
) {
return (
itemBind.getPoseType() ==
com.tiedup.remake.items.base.PoseType.DOG
);
}
return false;
} }
/** /**
@@ -679,10 +676,8 @@ public abstract class AbstractTiedUpNpc
// Exception: collar owner can leash even if not tied // Exception: collar owner can leash even if not tied
if (this.hasCollar()) { if (this.hasCollar()) {
ItemStack collar = this.getEquipment(BodyRegionV2.NECK); ItemStack collar = this.getEquipment(BodyRegionV2.NECK);
if (collar.getItem() instanceof ItemCollar collarItem) { if (CollarHelper.isOwner(collar, player)) {
if (collarItem.getOwners(collar).contains(player.getUUID())) { return true;
return true;
}
} }
} }
@@ -801,20 +796,16 @@ public abstract class AbstractTiedUpNpc
public boolean hasGaggingEffect() { public boolean hasGaggingEffect() {
ItemStack gag = this.getEquipment(BodyRegionV2.MOUTH); ItemStack gag = this.getEquipment(BodyRegionV2.MOUTH);
if (gag.isEmpty()) return false; if (gag.isEmpty()) return false;
return ( if (DataDrivenBondageItem.getComponent(gag, ComponentType.GAGGING, GaggingComponent.class) != null) return true;
gag.getItem() instanceof return gag.getItem() instanceof com.tiedup.remake.items.base.IHasGaggingEffect;
com.tiedup.remake.items.base.IHasGaggingEffect
);
} }
@Override @Override
public boolean hasBlindingEffect() { public boolean hasBlindingEffect() {
ItemStack blindfold = this.getEquipment(BodyRegionV2.EYES); ItemStack blindfold = this.getEquipment(BodyRegionV2.EYES);
if (blindfold.isEmpty()) return false; if (blindfold.isEmpty()) return false;
return ( if (DataDrivenBondageItem.getComponent(blindfold, ComponentType.BLINDING, BlindingComponent.class) != null) return true;
blindfold.getItem() instanceof return blindfold.getItem() instanceof com.tiedup.remake.items.base.IHasBlindingEffect;
com.tiedup.remake.items.base.IHasBlindingEffect
);
} }
@Override @Override
@@ -990,9 +981,9 @@ public abstract class AbstractTiedUpNpc
ItemStack collar = this.getEquipment(BodyRegionV2.NECK); ItemStack collar = this.getEquipment(BodyRegionV2.NECK);
if (collar.isEmpty()) return false; if (collar.isEmpty()) return false;
if (!(collar.getItem() instanceof ItemCollar itemCollar)) return false; if (!CollarHelper.isCollar(collar)) return false;
java.util.UUID cellId = itemCollar.getCellId(collar); java.util.UUID cellId = CollarHelper.getCellId(collar);
if (cellId == null) return false; if (cellId == null) return false;
// Get cell position from registry // Get cell position from registry

View File

@@ -1,10 +1,9 @@
package com.tiedup.remake.entities; package com.tiedup.remake.entities;
import com.tiedup.remake.core.SystemMessageManager; import com.tiedup.remake.core.SystemMessageManager;
import com.tiedup.remake.items.ModItems; import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.items.base.BindVariant; import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import com.tiedup.remake.items.base.GagVariant; import net.minecraft.resources.ResourceLocation;
import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.state.PlayerBindState; import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.util.MessageDispatcher; import com.tiedup.remake.util.MessageDispatcher;
import com.tiedup.remake.util.teleport.Position; import com.tiedup.remake.util.teleport.Position;
@@ -53,13 +52,10 @@ public class BondageServiceHandler {
if (!npc.hasCollar()) return false; if (!npc.hasCollar()) return false;
ItemStack collar = npc.getEquipment(BodyRegionV2.NECK); ItemStack collar = npc.getEquipment(BodyRegionV2.NECK);
if (collar.getItem() instanceof ItemCollar itemCollar) { return (
return ( CollarHelper.hasCellAssigned(collar) &&
itemCollar.hasCellAssigned(collar) && CollarHelper.isBondageServiceEnabled(collar)
itemCollar.isBondageServiceEnabled(collar) );
);
}
return false;
} }
/** /**
@@ -70,11 +66,9 @@ public class BondageServiceHandler {
public String getMessage() { public String getMessage() {
if (npc.hasCollar()) { if (npc.hasCollar()) {
ItemStack collar = npc.getEquipment(BodyRegionV2.NECK); ItemStack collar = npc.getEquipment(BodyRegionV2.NECK);
if (collar.getItem() instanceof ItemCollar itemCollar) { String message = CollarHelper.getServiceSentence(collar);
String message = itemCollar.getServiceSentence(collar); if (message != null && !message.isEmpty()) {
if (message != null && !message.isEmpty()) { return message;
return message;
}
} }
} }
return DEFAULT_MESSAGE; return DEFAULT_MESSAGE;
@@ -119,9 +113,9 @@ public class BondageServiceHandler {
*/ */
private void capturePlayer(Player player) { private void capturePlayer(Player player) {
ItemStack collar = npc.getEquipment(BodyRegionV2.NECK); ItemStack collar = npc.getEquipment(BodyRegionV2.NECK);
if (!(collar.getItem() instanceof ItemCollar itemCollar)) return; if (!CollarHelper.isCollar(collar)) return;
java.util.UUID cellId = itemCollar.getCellId(collar); java.util.UUID cellId = CollarHelper.getCellId(collar);
if (cellId == null) return; if (cellId == null) return;
// Get cell position from registry // Get cell position from registry
@@ -141,7 +135,7 @@ public class BondageServiceHandler {
); );
// Warn masters if configured // Warn masters if configured
warnOwners(player, itemCollar, collar); warnOwners(player, collar);
// Get player's kidnapped state // Get player's kidnapped state
PlayerBindState state = PlayerBindState.getInstance(player); PlayerBindState state = PlayerBindState.getInstance(player);
@@ -149,18 +143,18 @@ public class BondageServiceHandler {
// Apply bondage // Apply bondage
state.equip( state.equip(
BodyRegionV2.ARMS, BodyRegionV2.ARMS,
new ItemStack(ModItems.getBind(BindVariant.ROPES)) DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "ropes"))
); );
state.equip( state.equip(
BodyRegionV2.MOUTH, BodyRegionV2.MOUTH,
new ItemStack(ModItems.getGag(GagVariant.BALL_GAG)) DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "ball_gag"))
); );
// Teleport to cell // Teleport to cell
state.teleportToPosition(cellPosition); state.teleportToPosition(cellPosition);
// Tie to pole if configured on collar // Tie to pole if configured on collar
if (itemCollar.shouldTieToPole(collar)) { if (CollarHelper.shouldTieToPole(collar)) {
state.tieToClosestPole(3); state.tieToClosestPole(3);
} }
} }
@@ -178,10 +172,9 @@ public class BondageServiceHandler {
*/ */
private void warnOwners( private void warnOwners(
Player capturedPlayer, Player capturedPlayer,
ItemCollar itemCollar,
ItemStack collarStack ItemStack collarStack
) { ) {
if (!itemCollar.shouldWarnMasters(collarStack)) { if (!CollarHelper.shouldWarnMasters(collarStack)) {
return; return;
} }
@@ -191,7 +184,7 @@ public class BondageServiceHandler {
capturedPlayer.getName().getString() + capturedPlayer.getName().getString() +
" via bondage service!"; " via bondage service!";
for (UUID ownerUUID : itemCollar.getOwners(collarStack)) { for (UUID ownerUUID : CollarHelper.getOwners(collarStack)) {
Player owner = npc.level().getPlayerByUUID(ownerUUID); Player owner = npc.level().getPlayerByUUID(ownerUUID);
if (owner != null) { if (owner != null) {
SystemMessageManager.sendChatToPlayer( SystemMessageManager.sendChatToPlayer(

View File

@@ -5,7 +5,7 @@ import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.entities.damsel.components.*; import com.tiedup.remake.entities.damsel.components.*;
import com.tiedup.remake.entities.skins.DamselSkinManager; import com.tiedup.remake.entities.skins.DamselSkinManager;
import com.tiedup.remake.entities.skins.Gender; import com.tiedup.remake.entities.skins.Gender;
import com.tiedup.remake.items.base.ItemCollar; import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.state.ICaptor; import com.tiedup.remake.state.ICaptor;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
import java.util.UUID; import java.util.UUID;
@@ -527,8 +527,8 @@ public class EntityDamsel
if (!this.hasCollar()) return false; if (!this.hasCollar()) return false;
ItemStack collar = this.getEquipment(BodyRegionV2.NECK); ItemStack collar = this.getEquipment(BodyRegionV2.NECK);
if (!(collar.getItem() instanceof ItemCollar collarItem)) return false; if (!CollarHelper.isCollar(collar)) return false;
if (!collarItem.getOwners(collar).contains(commander.getUUID())) { if (!CollarHelper.isOwner(collar, commander.getUUID())) {
if (!this.isGagged()) { if (!this.isGagged()) {
com.tiedup.remake.dialogue.EntityDialogueManager.talkByDialogueId( com.tiedup.remake.dialogue.EntityDialogueManager.talkByDialogueId(
this, this,
@@ -653,8 +653,8 @@ public class EntityDamsel
return net.minecraft.world.InteractionResult.FAIL; return net.minecraft.world.InteractionResult.FAIL;
} }
ItemStack collar = this.getEquipment(BodyRegionV2.NECK); ItemStack collar = this.getEquipment(BodyRegionV2.NECK);
if (collar.getItem() instanceof ItemCollar collarItem) { if (CollarHelper.isCollar(collar)) {
if (!collarItem.isOwner(collar, player)) { if (!CollarHelper.isOwner(collar, player)) {
if ( if (
player instanceof player instanceof
net.minecraft.server.level.ServerPlayer sp net.minecraft.server.level.ServerPlayer sp
@@ -693,9 +693,9 @@ public class EntityDamsel
this.hasCollar() this.hasCollar()
) { ) {
ItemStack collar = this.getEquipment(BodyRegionV2.NECK); ItemStack collar = this.getEquipment(BodyRegionV2.NECK);
if (collar.getItem() instanceof ItemCollar collarItem) { if (CollarHelper.isCollar(collar)) {
if ( if (
collarItem.isOwner(collar, player) && CollarHelper.isOwner(collar, player) &&
player instanceof player instanceof
net.minecraft.server.level.ServerPlayer serverPlayer net.minecraft.server.level.ServerPlayer serverPlayer
) { ) {
@@ -822,8 +822,8 @@ public class EntityDamsel
public String getTargetRelation(Player player) { public String getTargetRelation(Player player) {
if (hasCollar()) { if (hasCollar()) {
ItemStack collar = getEquipment(BodyRegionV2.NECK); ItemStack collar = getEquipment(BodyRegionV2.NECK);
if (collar.getItem() instanceof ItemCollar collarItem) { if (CollarHelper.isCollar(collar)) {
if (collarItem.isOwner(collar, player)) { if (CollarHelper.isOwner(collar, player)) {
return "master"; return "master";
} }
} }

View File

@@ -12,7 +12,7 @@ import com.tiedup.remake.entities.kidnapper.components.KidnapperAggressionSystem
import com.tiedup.remake.entities.skins.Gender; import com.tiedup.remake.entities.skins.Gender;
import com.tiedup.remake.entities.skins.KidnapperSkinManager; import com.tiedup.remake.entities.skins.KidnapperSkinManager;
import com.tiedup.remake.items.ModItems; import com.tiedup.remake.items.ModItems;
import com.tiedup.remake.items.base.ItemCollar; import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.personality.PersonalityType; import com.tiedup.remake.personality.PersonalityType;
import com.tiedup.remake.state.IBondageState; import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.state.ICaptor; import com.tiedup.remake.state.ICaptor;
@@ -1367,10 +1367,7 @@ public class EntityKidnapper
if (!this.hasCollar()) return false; if (!this.hasCollar()) return false;
ItemStack collar = this.getEquipment(BodyRegionV2.NECK); ItemStack collar = this.getEquipment(BodyRegionV2.NECK);
if (collar.getItem() instanceof ItemCollar collarItem) { return CollarHelper.isOwner(collar, player);
return collarItem.isOwner(collar, player);
}
return false;
} }
/** Damage reduction multiplier against monsters (50% damage taken) */ /** Damage reduction multiplier against monsters (50% damage taken) */

View File

@@ -18,6 +18,7 @@ import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.state.ICaptor; import com.tiedup.remake.state.ICaptor;
import com.tiedup.remake.util.MessageDispatcher; import com.tiedup.remake.util.MessageDispatcher;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
@@ -615,78 +616,82 @@ public class EntityKidnapperMerchant extends EntityKidnapperElite {
private List<ItemStack> collectAllModItems() { private List<ItemStack> collectAllModItems() {
List<ItemStack> items = new ArrayList<>(); List<ItemStack> items = new ArrayList<>();
// All binds (15) // All binds — iterate V1 variants, create V2 stacks
// Items with colors get multiple variants (one per color)
for (BindVariant variant : BindVariant.values()) { for (BindVariant variant : BindVariant.values()) {
if (variant.supportsColor()) { if (variant.supportsColor()) {
// Add one item per color (16 standard colors)
for (ItemColor color : ItemColor.values()) { for (ItemColor color : ItemColor.values()) {
if ( if (
color != ItemColor.CAUTION && color != ItemColor.CLEAR color != ItemColor.CAUTION && color != ItemColor.CLEAR
) { ) {
ItemStack stack = new ItemStack( ItemStack stack = DataDrivenBondageItem.createStack(
ModItems.getBind(variant) new net.minecraft.resources.ResourceLocation("tiedup", variant.getRegistryName())
); );
KidnapperItemSelector.applyColor(stack, color); KidnapperItemSelector.applyColor(stack, color);
items.add(stack); items.add(stack);
} }
} }
} else { } else {
// No color variants items.add(DataDrivenBondageItem.createStack(
items.add(new ItemStack(ModItems.getBind(variant))); new net.minecraft.resources.ResourceLocation("tiedup", variant.getRegistryName())
));
} }
} }
// All gags (19) // All gags
for (GagVariant variant : GagVariant.values()) { for (GagVariant variant : GagVariant.values()) {
if (variant.supportsColor()) { if (variant.supportsColor()) {
// Add one item per color
for (ItemColor color : ItemColor.values()) { for (ItemColor color : ItemColor.values()) {
// TAPE_GAG can use caution/clear, others only standard colors
if ( if (
variant == GagVariant.TAPE_GAG || variant == GagVariant.TAPE_GAG ||
(color != ItemColor.CAUTION && color != ItemColor.CLEAR) (color != ItemColor.CAUTION && color != ItemColor.CLEAR)
) { ) {
ItemStack stack = new ItemStack( ItemStack stack = DataDrivenBondageItem.createStack(
ModItems.getGag(variant) new net.minecraft.resources.ResourceLocation("tiedup", variant.getRegistryName())
); );
KidnapperItemSelector.applyColor(stack, color); KidnapperItemSelector.applyColor(stack, color);
items.add(stack); items.add(stack);
} }
} }
} else { } else {
items.add(new ItemStack(ModItems.getGag(variant))); items.add(DataDrivenBondageItem.createStack(
new net.minecraft.resources.ResourceLocation("tiedup", variant.getRegistryName())
));
} }
} }
// All blindfolds (2) - BOTH support colors // All blindfolds
for (BlindfoldVariant variant : BlindfoldVariant.values()) { for (BlindfoldVariant variant : BlindfoldVariant.values()) {
if (variant.supportsColor()) { if (variant.supportsColor()) {
// Add one item per color (16 standard colors)
for (ItemColor color : ItemColor.values()) { for (ItemColor color : ItemColor.values()) {
if ( if (
color != ItemColor.CAUTION && color != ItemColor.CLEAR color != ItemColor.CAUTION && color != ItemColor.CLEAR
) { ) {
ItemStack stack = new ItemStack( ItemStack stack = DataDrivenBondageItem.createStack(
ModItems.getBlindfold(variant) new net.minecraft.resources.ResourceLocation("tiedup", variant.getRegistryName())
); );
KidnapperItemSelector.applyColor(stack, color); KidnapperItemSelector.applyColor(stack, color);
items.add(stack); items.add(stack);
} }
} }
} else { } else {
items.add(new ItemStack(ModItems.getBlindfold(variant))); items.add(DataDrivenBondageItem.createStack(
new net.minecraft.resources.ResourceLocation("tiedup", variant.getRegistryName())
));
} }
} }
// Earplugs - no color support // Earplugs - no color support
for (EarplugsVariant variant : EarplugsVariant.values()) { for (EarplugsVariant variant : EarplugsVariant.values()) {
items.add(new ItemStack(ModItems.getEarplugs(variant))); items.add(DataDrivenBondageItem.createStack(
new net.minecraft.resources.ResourceLocation("tiedup", variant.getRegistryName())
));
} }
// Mittens - no color support // Mittens - no color support
for (MittensVariant variant : MittensVariant.values()) { for (MittensVariant variant : MittensVariant.values()) {
items.add(new ItemStack(ModItems.getMittens(variant))); items.add(DataDrivenBondageItem.createStack(
new net.minecraft.resources.ResourceLocation("tiedup", variant.getRegistryName())
));
} }
// Knives - no color support // Knives - no color support

View File

@@ -94,10 +94,8 @@ public class EntityRopeArrow extends AbstractArrow {
int roll = this.random.nextInt(100) + 1; int roll = this.random.nextInt(100) + 1;
if (roll <= bindChance) { if (roll <= bindChance) {
// Success! Bind the target // Success! Bind the target
ItemStack ropeItem = new ItemStack( ItemStack ropeItem = com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem.createStack(
ModItems.getBind( new net.minecraft.resources.ResourceLocation("tiedup", "ropes")
com.tiedup.remake.items.base.BindVariant.ROPES
)
); );
targetState.equip(BodyRegionV2.ARMS, ropeItem); targetState.equip(BodyRegionV2.ARMS, ropeItem);

View File

@@ -1,7 +1,8 @@
package com.tiedup.remake.entities; package com.tiedup.remake.entities;
import com.tiedup.remake.items.ModItems; import com.tiedup.remake.items.ModItems;
import com.tiedup.remake.items.base.BindVariant; import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import net.minecraft.resources.ResourceLocation;
import com.tiedup.remake.v2.bondage.IV2BondageItem; import com.tiedup.remake.v2.bondage.IV2BondageItem;
import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
@@ -46,7 +47,7 @@ public class KidnapperCaptureEquipment {
) { ) {
return mainHand; return mainHand;
} }
return new ItemStack(ModItems.getBind(BindVariant.ROPES)); return DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "ropes"));
} }
/** /**

View File

@@ -1,11 +1,9 @@
package com.tiedup.remake.entities; package com.tiedup.remake.entities;
import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.util.teleport.Position;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.CollarHelper;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -31,21 +29,7 @@ public class KidnapperCollarConfig {
this.kidnapper = kidnapper; this.kidnapper = kidnapper;
} }
// COLLAR ITEM ACCESS // COLLAR STACK ACCESS
/**
* Get the collar item if equipped.
* @return ItemCollar or null if no collar or not an ItemCollar
*/
@Nullable
public ItemCollar getCollarItem() {
ItemStack collar = kidnapper.getEquipment(BodyRegionV2.NECK);
if (collar.isEmpty()) return null;
if (collar.getItem() instanceof ItemCollar itemCollar) {
return itemCollar;
}
return null;
}
/** /**
* Get the collar ItemStack. * Get the collar ItemStack.
@@ -55,30 +39,29 @@ public class KidnapperCollarConfig {
return kidnapper.getEquipment(BodyRegionV2.NECK); return kidnapper.getEquipment(BodyRegionV2.NECK);
} }
/**
* Check if the kidnapper has a valid collar.
*/
private boolean hasValidCollar() {
return kidnapper.hasCollar() && CollarHelper.isCollar(getCollarStack());
}
// KIDNAPPING MODE // KIDNAPPING MODE
/** /**
* Check if kidnapping mode is enabled via collar. * Check if kidnapping mode is enabled via collar.
*/ */
public boolean isKidnappingModeEnabled() { public boolean isKidnappingModeEnabled() {
if (!kidnapper.hasCollar()) return false; if (!hasValidCollar()) return false;
return CollarHelper.isKidnappingModeEnabled(getCollarStack());
ItemCollar itemCollar = getCollarItem();
if (itemCollar == null) return false;
return itemCollar.isKidnappingModeEnabled(getCollarStack());
} }
/** /**
* Check if kidnapping mode is fully ready (enabled + prison set). * Check if kidnapping mode is fully ready (enabled + prison set).
*/ */
public boolean isKidnappingModeReady() { public boolean isKidnappingModeReady() {
if (!kidnapper.hasCollar()) return false; if (!hasValidCollar()) return false;
return CollarHelper.isKidnappingModeReady(getCollarStack());
ItemCollar itemCollar = getCollarItem();
if (itemCollar == null) return false;
return itemCollar.isKidnappingModeReady(getCollarStack());
} }
// POSITION GETTERS // POSITION GETTERS
@@ -88,20 +71,16 @@ public class KidnapperCollarConfig {
*/ */
@Nullable @Nullable
public java.util.UUID getCellId() { public java.util.UUID getCellId() {
ItemCollar itemCollar = getCollarItem(); if (!hasValidCollar()) return null;
if (itemCollar == null) return null; return CollarHelper.getCellId(getCollarStack());
return itemCollar.getCellId(getCollarStack());
} }
/** /**
* Check if collar has a cell assigned. * Check if collar has a cell assigned.
*/ */
public boolean hasCellAssigned() { public boolean hasCellAssigned() {
ItemCollar itemCollar = getCollarItem(); if (!hasValidCollar()) return false;
if (itemCollar == null) return false; return CollarHelper.hasCellAssigned(getCollarStack());
return itemCollar.hasCellAssigned(getCollarStack());
} }
// BEHAVIOR FLAGS // BEHAVIOR FLAGS
@@ -110,20 +89,16 @@ public class KidnapperCollarConfig {
* Check if should warn masters after capturing slave. * Check if should warn masters after capturing slave.
*/ */
public boolean shouldWarnMasters() { public boolean shouldWarnMasters() {
ItemCollar itemCollar = getCollarItem(); if (!hasValidCollar()) return false;
if (itemCollar == null) return false; return CollarHelper.shouldWarnMasters(getCollarStack());
return itemCollar.shouldWarnMasters(getCollarStack());
} }
/** /**
* Check if should tie slave to pole in prison. * Check if should tie slave to pole in prison.
*/ */
public boolean shouldTieToPole() { public boolean shouldTieToPole() {
ItemCollar itemCollar = getCollarItem(); if (!hasValidCollar()) return false;
if (itemCollar == null) return false; return CollarHelper.shouldTieToPole(getCollarStack());
return itemCollar.shouldTieToPole(getCollarStack());
} }
// BLACKLIST/WHITELIST // BLACKLIST/WHITELIST
@@ -139,19 +114,18 @@ public class KidnapperCollarConfig {
* </ul> * </ul>
*/ */
public boolean isValidKidnappingTarget(Player player) { public boolean isValidKidnappingTarget(Player player) {
ItemCollar itemCollar = getCollarItem(); if (!hasValidCollar()) return true; // No collar config = everyone is valid
if (itemCollar == null) return true; // No collar config = everyone is valid
ItemStack collarStack = getCollarStack(); ItemStack collarStack = getCollarStack();
UUID playerUUID = player.getUUID(); UUID playerUUID = player.getUUID();
// If whitelist exists and is not empty, player MUST be on it // If whitelist exists and is not empty, player MUST be on it
List<UUID> whitelist = itemCollar.getWhitelist(collarStack); List<UUID> whitelist = CollarHelper.getWhitelist(collarStack);
if (!whitelist.isEmpty()) { if (!whitelist.isEmpty()) {
return whitelist.contains(playerUUID); return whitelist.contains(playerUUID);
} }
// Otherwise, check blacklist (blacklisted = not a valid target) // Otherwise, check blacklist (blacklisted = not a valid target)
return !itemCollar.isBlacklisted(collarStack, playerUUID); return !CollarHelper.isBlacklisted(collarStack, playerUUID);
} }
} }

View File

@@ -1,8 +1,9 @@
package com.tiedup.remake.entities; package com.tiedup.remake.entities;
import com.tiedup.remake.items.ModItems;
import com.tiedup.remake.items.base.*; import com.tiedup.remake.items.base.*;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import java.util.Random; import java.util.Random;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -242,7 +243,9 @@ public class KidnapperItemSelector {
BindVariant variant, BindVariant variant,
@Nullable ItemColor color @Nullable ItemColor color
) { ) {
ItemStack stack = new ItemStack(ModItems.getBind(variant)); ItemStack stack = DataDrivenBondageItem.createStack(
new ResourceLocation("tiedup", variant.getRegistryName())
);
if (color != null && variant.supportsColor()) { if (color != null && variant.supportsColor()) {
applyColor(stack, color); applyColor(stack, color);
} }
@@ -257,7 +260,9 @@ public class KidnapperItemSelector {
GagVariant variant, GagVariant variant,
@Nullable ItemColor color @Nullable ItemColor color
) { ) {
ItemStack stack = new ItemStack(ModItems.getGag(variant)); ItemStack stack = DataDrivenBondageItem.createStack(
new ResourceLocation("tiedup", variant.getRegistryName())
);
if ( if (
color != null && color != null &&
variant.supportsColor() && variant.supportsColor() &&
@@ -276,7 +281,9 @@ public class KidnapperItemSelector {
BlindfoldVariant variant, BlindfoldVariant variant,
@Nullable ItemColor color @Nullable ItemColor color
) { ) {
ItemStack stack = new ItemStack(ModItems.getBlindfold(variant)); ItemStack stack = DataDrivenBondageItem.createStack(
new ResourceLocation("tiedup", variant.getRegistryName())
);
if ( if (
color != null && color != null &&
variant.supportsColor() && variant.supportsColor() &&
@@ -292,7 +299,9 @@ public class KidnapperItemSelector {
* Mittens don't have color variants. * Mittens don't have color variants.
*/ */
public static ItemStack createMittens() { public static ItemStack createMittens() {
return new ItemStack(ModItems.getMittens(MittensVariant.LEATHER)); return DataDrivenBondageItem.createStack(
new ResourceLocation("tiedup", "leather_mittens")
);
} }
/** /**
@@ -300,7 +309,9 @@ public class KidnapperItemSelector {
* Earplugs don't have color variants. * Earplugs don't have color variants.
*/ */
public static ItemStack createEarplugs() { public static ItemStack createEarplugs() {
return new ItemStack(ModItems.getEarplugs(EarplugsVariant.CLASSIC)); return DataDrivenBondageItem.createStack(
new ResourceLocation("tiedup", "classic_earplugs")
);
} }
// COLOR METHODS // COLOR METHODS

View File

@@ -2,7 +2,7 @@ package com.tiedup.remake.entities;
import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.ModItems; import com.tiedup.remake.items.ModItems;
import com.tiedup.remake.items.base.ItemCollar; import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.state.CollarRegistry; import com.tiedup.remake.state.CollarRegistry;
import com.tiedup.remake.state.IBondageState; import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.util.tasks.ItemTask; import com.tiedup.remake.util.tasks.ItemTask;
@@ -162,15 +162,15 @@ public class KidnapperJobManager {
// Put a shock collar on the worker AFTER untie/free // Put a shock collar on the worker AFTER untie/free
ItemStack shockCollar = new ItemStack(ModItems.SHOCK_COLLAR_AUTO.get()); ItemStack shockCollar = new ItemStack(ModItems.SHOCK_COLLAR_AUTO.get());
if (shockCollar.getItem() instanceof ItemCollar collarItem) { // Add kidnapper as owner so the collar is linked
// Add kidnapper as owner so the collar is linked CollarHelper.addOwner(
collarItem.addOwner( shockCollar,
shockCollar, kidnapper.getUUID(),
kidnapper.getUUID(), kidnapper.getNpcName()
kidnapper.getNpcName() );
); // Lock the collar so they can't remove it
// Lock the collar so they can't remove it if (shockCollar.getItem() instanceof com.tiedup.remake.items.base.ILockable lockable) {
shockCollar = collarItem.setLocked(shockCollar, true); shockCollar = lockable.setLocked(shockCollar, true);
} }
captive.equip(BodyRegionV2.NECK, shockCollar); captive.equip(BodyRegionV2.NECK, shockCollar);

View File

@@ -1,7 +1,6 @@
package com.tiedup.remake.entities; package com.tiedup.remake.entities;
import com.tiedup.remake.items.ModItems; import com.tiedup.remake.v2.bondage.PoseTypeHelper;
import com.tiedup.remake.items.base.BindVariant;
import com.tiedup.remake.state.IBondageState; import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.util.KidnappedHelper; import com.tiedup.remake.util.KidnappedHelper;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
@@ -108,7 +107,7 @@ public final class LeashProxyEntity extends Turtle {
ItemStack bind = state.getEquipment(BodyRegionV2.ARMS); ItemStack bind = state.getEquipment(BodyRegionV2.ARMS);
if ( if (
!bind.isEmpty() && !bind.isEmpty() &&
bind.getItem() == ModItems.getBind(BindVariant.DOGBINDER) PoseTypeHelper.getPoseType(bind) == com.tiedup.remake.items.base.PoseType.DOG
) { ) {
yOffset = 0.35D; // Lower for 4-legged dogwalk pose (back/hip level) yOffset = 0.35D; // Lower for 4-legged dogwalk pose (back/hip level)
} }

View File

@@ -11,7 +11,7 @@ import com.tiedup.remake.entities.EntityKidnapper;
import com.tiedup.remake.entities.EntityKidnapper.CaptivePriority; import com.tiedup.remake.entities.EntityKidnapper.CaptivePriority;
import com.tiedup.remake.entities.ai.StuckDetector; import com.tiedup.remake.entities.ai.StuckDetector;
import com.tiedup.remake.entities.ai.WaypointNavigator; import com.tiedup.remake.entities.ai.WaypointNavigator;
import com.tiedup.remake.items.base.ItemCollar; import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.prison.PrisonerManager; import com.tiedup.remake.prison.PrisonerManager;
import com.tiedup.remake.prison.PrisonerRecord; import com.tiedup.remake.prison.PrisonerRecord;
import com.tiedup.remake.prison.PrisonerState; import com.tiedup.remake.prison.PrisonerState;
@@ -260,7 +260,7 @@ public class KidnapperBringToCellGoal extends Goal {
); );
IRestrainable captive = this.kidnapper.getCaptive(); IRestrainable captive = this.kidnapper.getCaptive();
if (captive != null) { if (captive != null) {
ItemCollar.runWithSuppressedAlert(() -> CollarHelper.runWithSuppressedAlert(() ->
captive.free(false) captive.free(false)
); );
this.kidnapper.removeCaptive(captive, false); this.kidnapper.removeCaptive(captive, false);
@@ -1079,9 +1079,9 @@ public class KidnapperBringToCellGoal extends Goal {
ItemStack collar = captive.getEquipment(BodyRegionV2.NECK); ItemStack collar = captive.getEquipment(BodyRegionV2.NECK);
if ( if (
!collar.isEmpty() && !collar.isEmpty() &&
collar.getItem() instanceof ItemCollar collarItem CollarHelper.isCollar(collar)
) { ) {
collarItem.setCellId(collar, this.targetCell.getId()); CollarHelper.setCellId(collar, this.targetCell.getId());
} }
TiedUpMod.LOGGER.info( TiedUpMod.LOGGER.info(

View File

@@ -8,7 +8,7 @@ import com.tiedup.remake.entities.EntityKidnapper;
import com.tiedup.remake.entities.ai.StuckDetector; import com.tiedup.remake.entities.ai.StuckDetector;
import com.tiedup.remake.items.ItemKey; import com.tiedup.remake.items.ItemKey;
import com.tiedup.remake.items.ModItems; import com.tiedup.remake.items.ModItems;
import com.tiedup.remake.items.base.ItemCollar; import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.network.ModNetwork; import com.tiedup.remake.network.ModNetwork;
import com.tiedup.remake.network.action.PacketTying; import com.tiedup.remake.network.action.PacketTying;
import com.tiedup.remake.prison.PrisonerManager; import com.tiedup.remake.prison.PrisonerManager;
@@ -849,11 +849,8 @@ public class KidnapperCaptureGoal extends Goal {
// If already has collar, check ownership // If already has collar, check ownership
if (state.hasCollar()) { if (state.hasCollar()) {
ItemStack existingCollar = state.getEquipment(BodyRegionV2.NECK); ItemStack existingCollar = state.getEquipment(BodyRegionV2.NECK);
if ( if (CollarHelper.isCollar(existingCollar)) {
existingCollar.getItem() instanceof java.util.List<java.util.UUID> owners = CollarHelper.getOwners(
com.tiedup.remake.items.base.ItemCollar collarItem
) {
java.util.List<java.util.UUID> owners = collarItem.getOwners(
existingCollar existingCollar
); );
if (!owners.contains(this.kidnapper.getUUID())) { if (!owners.contains(this.kidnapper.getUUID())) {
@@ -885,9 +882,9 @@ public class KidnapperCaptureGoal extends Goal {
for (java.util.UUID oldOwner : new java.util.ArrayList<>( for (java.util.UUID oldOwner : new java.util.ArrayList<>(
owners owners
)) { )) {
collarItem.removeOwner(existingCollar, oldOwner); CollarHelper.removeOwner(existingCollar, oldOwner);
} }
collarItem.addOwner( CollarHelper.addOwner(
existingCollar, existingCollar,
this.kidnapper.getUUID(), this.kidnapper.getUUID(),
this.kidnapper.getNpcName() this.kidnapper.getNpcName()
@@ -929,9 +926,9 @@ public class KidnapperCaptureGoal extends Goal {
if (this.captureProgress >= COLLAR_APPLY_TIME) { if (this.captureProgress >= COLLAR_APPLY_TIME) {
// Create a copy of the collar and configure it // Create a copy of the collar and configure it
ItemStack collarCopy = collar.copy(); ItemStack collarCopy = collar.copy();
if (collarCopy.getItem() instanceof ItemCollar collarItem) { if (CollarHelper.isCollar(collarCopy)) {
// Add kidnapper as owner // Add kidnapper as owner
collarItem.addOwner( CollarHelper.addOwner(
collarCopy, collarCopy,
this.kidnapper.getUUID(), this.kidnapper.getUUID(),
this.kidnapper.getNpcName() this.kidnapper.getNpcName()
@@ -941,12 +938,17 @@ public class KidnapperCaptureGoal extends Goal {
ItemStack keyStack = new ItemStack(ModItems.COLLAR_KEY.get()); ItemStack keyStack = new ItemStack(ModItems.COLLAR_KEY.get());
if (keyStack.getItem() instanceof ItemKey keyItem) { if (keyStack.getItem() instanceof ItemKey keyItem) {
UUID keyUUID = keyItem.getKeyUUID(keyStack); UUID keyUUID = keyItem.getKeyUUID(keyStack);
collarItem.setLockedByKeyUUID(collarCopy, keyUUID); // Lock via ILockable interface (collar must implement it)
if (collarCopy.getItem() instanceof com.tiedup.remake.items.base.ILockable lockable) {
lockable.setLockedByKeyUUID(collarCopy, keyUUID);
}
// Store the key on the kidnapper for potential drop on death // Store the key on the kidnapper for potential drop on death
this.kidnapper.addCollarKey(keyStack); this.kidnapper.addCollarKey(keyStack);
} else { } else {
// Fallback: just lock without a key // Fallback: just lock without a key
collarItem.setLocked(collarCopy, true); if (collarCopy.getItem() instanceof com.tiedup.remake.items.base.ILockable lockable) {
lockable.setLocked(collarCopy, true);
}
} }
} }

View File

@@ -8,8 +8,8 @@ import com.tiedup.remake.entities.AbstractTiedUpNpc;
import com.tiedup.remake.entities.EntityKidnapper; import com.tiedup.remake.entities.EntityKidnapper;
import com.tiedup.remake.entities.ai.StuckDetector; import com.tiedup.remake.entities.ai.StuckDetector;
import com.tiedup.remake.entities.ai.WaypointNavigator; import com.tiedup.remake.entities.ai.WaypointNavigator;
import com.tiedup.remake.items.ModItems; import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import com.tiedup.remake.items.base.BindVariant; import net.minecraft.resources.ResourceLocation;
import com.tiedup.remake.state.IBondageState; import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.util.KidnappedHelper; import com.tiedup.remake.util.KidnappedHelper;
import com.tiedup.remake.util.KidnapperAIHelper; import com.tiedup.remake.util.KidnapperAIHelper;
@@ -547,9 +547,7 @@ public class KidnapperWalkPrisonerGoal extends Goal {
); );
// 3. Change bind to DOGBINDER // 3. Change bind to DOGBINDER
ItemStack dogBinder = new ItemStack( ItemStack dogBinder = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "dogbinder"));
ModItems.getBind(BindVariant.DOGBINDER)
);
this.walkedPrisoner.equip(BodyRegionV2.ARMS, dogBinder); this.walkedPrisoner.equip(BodyRegionV2.ARMS, dogBinder);
TiedUpMod.LOGGER.debug( TiedUpMod.LOGGER.debug(
@@ -738,7 +736,7 @@ public class KidnapperWalkPrisonerGoal extends Goal {
if (currentBind.isEmpty() || !prisoner.isTiedUp()) { if (currentBind.isEmpty() || !prisoner.isTiedUp()) {
// They freed themselves - put dogbinder back on // They freed themselves - put dogbinder back on
ItemStack dogBinder = new ItemStack( ItemStack dogBinder = new ItemStack(
ModItems.getBind(BindVariant.DOGBINDER) DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "dogbinder")).getItem()
); );
prisoner.equip(BodyRegionV2.ARMS, dogBinder); prisoner.equip(BodyRegionV2.ARMS, dogBinder);
} }

View File

@@ -338,10 +338,8 @@ public class MaidReturnGoal extends Goal {
// Fallback: use basic rope if no snapshot // Fallback: use basic rope if no snapshot
cap.equip( cap.equip(
BodyRegionV2.ARMS, BodyRegionV2.ARMS,
new ItemStack( com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem.createStack(
com.tiedup.remake.items.ModItems.getBind( new net.minecraft.resources.ResourceLocation("tiedup", "ropes")
com.tiedup.remake.items.base.BindVariant.ROPES
)
) )
); );
} }

View File

@@ -3,8 +3,8 @@ package com.tiedup.remake.entities.ai.master;
import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.dialogue.DialogueBridge; import com.tiedup.remake.dialogue.DialogueBridge;
import com.tiedup.remake.entities.EntityMaster; import com.tiedup.remake.entities.EntityMaster;
import com.tiedup.remake.items.ModItems; import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import com.tiedup.remake.items.base.BindVariant; import net.minecraft.resources.ResourceLocation;
import com.tiedup.remake.state.HumanChairHelper; import com.tiedup.remake.state.HumanChairHelper;
import com.tiedup.remake.state.PlayerBindState; import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
@@ -394,8 +394,8 @@ public class MasterHumanChairGoal extends Goal {
// Apply invisible dogbind for the pose animation // Apply invisible dogbind for the pose animation
if (!bindState.isTiedUp()) { if (!bindState.isTiedUp()) {
ItemStack dogbind = new ItemStack( ItemStack dogbind = DataDrivenBondageItem.createStack(
ModItems.getBind(BindVariant.DOGBINDER) new ResourceLocation("tiedup", "dogbinder")
); );
CompoundTag tag = dogbind.getOrCreateTag(); CompoundTag tag = dogbind.getOrCreateTag();
tag.putBoolean(NBT_HUMAN_CHAIR_BIND, true); tag.putBoolean(NBT_HUMAN_CHAIR_BIND, true);

View File

@@ -3,12 +3,9 @@ package com.tiedup.remake.entities.ai.master;
import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.dialogue.DialogueBridge; import com.tiedup.remake.dialogue.DialogueBridge;
import com.tiedup.remake.entities.EntityMaster; import com.tiedup.remake.entities.EntityMaster;
import com.tiedup.remake.items.ItemChokeCollar; import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.items.ModItems; import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import com.tiedup.remake.items.base.BindVariant; import net.minecraft.resources.ResourceLocation;
import com.tiedup.remake.items.base.BlindfoldVariant;
import com.tiedup.remake.items.base.GagVariant;
import com.tiedup.remake.items.base.MittensVariant;
import com.tiedup.remake.state.PlayerBindState; import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.IV2BondageEquipment; import com.tiedup.remake.v2.bondage.IV2BondageEquipment;
@@ -261,7 +258,7 @@ public class MasterPunishGoal extends Goal {
// CHOKE: only if pet has choke collar // CHOKE: only if pet has choke collar
if (bindState != null && bindState.hasCollar()) { if (bindState != null && bindState.hasCollar()) {
ItemStack collar = bindState.getEquipment(BodyRegionV2.NECK); ItemStack collar = bindState.getEquipment(BodyRegionV2.NECK);
if (collar.getItem() instanceof ItemChokeCollar) { if (CollarHelper.isChokeCollar(collar)) {
available.add(PunishmentType.CHOKE_COLLAR); available.add(PunishmentType.CHOKE_COLLAR);
} }
} }
@@ -393,8 +390,8 @@ public class MasterPunishGoal extends Goal {
PlayerBindState bindState = PlayerBindState.getInstance(pet); PlayerBindState bindState = PlayerBindState.getInstance(pet);
if (bindState != null && bindState.hasCollar()) { if (bindState != null && bindState.hasCollar()) {
ItemStack collar = bindState.getEquipment(BodyRegionV2.NECK); ItemStack collar = bindState.getEquipment(BodyRegionV2.NECK);
if (collar.getItem() instanceof ItemChokeCollar chokeCollar) { if (CollarHelper.isChokeCollar(collar)) {
chokeCollar.setChoking(collar, true); CollarHelper.setChoking(collar, true);
this.activeChokeCollar = collar; this.activeChokeCollar = collar;
this.chokeActiveTimer = 1; this.chokeActiveTimer = 1;
@@ -457,12 +454,14 @@ public class MasterPunishGoal extends Goal {
*/ */
private ItemStack createAccessory(BodyRegionV2 region) { private ItemStack createAccessory(BodyRegionV2 region) {
return switch (region) { return switch (region) {
case EYES -> new ItemStack( case EYES -> DataDrivenBondageItem.createStack(
ModItems.getBlindfold(BlindfoldVariant.CLASSIC) new ResourceLocation("tiedup", "classic_blindfold")
); );
case MOUTH -> new ItemStack(ModItems.getGag(GagVariant.BALL_GAG)); case MOUTH -> DataDrivenBondageItem.createStack(
case HANDS -> new ItemStack( new ResourceLocation("tiedup", "ball_gag")
ModItems.getMittens(MittensVariant.LEATHER) );
case HANDS -> DataDrivenBondageItem.createStack(
new ResourceLocation("tiedup", "leather_mittens")
); );
default -> ItemStack.EMPTY; default -> ItemStack.EMPTY;
}; };
@@ -472,8 +471,8 @@ public class MasterPunishGoal extends Goal {
* Apply armbinder as punishment. * Apply armbinder as punishment.
*/ */
private void applyTighten(ServerPlayer pet) { private void applyTighten(ServerPlayer pet) {
ItemStack armbinder = new ItemStack( ItemStack armbinder = DataDrivenBondageItem.createStack(
ModItems.getBind(BindVariant.ARMBINDER) new ResourceLocation("tiedup", "armbinder")
); );
// Mark as temporary // Mark as temporary
@@ -545,9 +544,9 @@ public class MasterPunishGoal extends Goal {
private void deactivateChoke() { private void deactivateChoke() {
if ( if (
!activeChokeCollar.isEmpty() && !activeChokeCollar.isEmpty() &&
activeChokeCollar.getItem() instanceof ItemChokeCollar chokeCollar CollarHelper.isChokeCollar(activeChokeCollar)
) { ) {
chokeCollar.setChoking(activeChokeCollar, false); CollarHelper.setChoking(activeChokeCollar, false);
} }
this.activeChokeCollar = ItemStack.EMPTY; this.activeChokeCollar = ItemStack.EMPTY;
this.chokeActiveTimer = 0; this.chokeActiveTimer = 0;

View File

@@ -3,11 +3,8 @@ package com.tiedup.remake.entities.ai.master;
import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.dialogue.DialogueBridge; import com.tiedup.remake.dialogue.DialogueBridge;
import com.tiedup.remake.entities.EntityMaster; import com.tiedup.remake.entities.EntityMaster;
import com.tiedup.remake.items.ModItems; import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import com.tiedup.remake.items.base.BindVariant; import net.minecraft.resources.ResourceLocation;
import com.tiedup.remake.items.base.BlindfoldVariant;
import com.tiedup.remake.items.base.GagVariant;
import com.tiedup.remake.items.base.MittensVariant;
import com.tiedup.remake.state.PlayerBindState; import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.IV2BondageEquipment; import com.tiedup.remake.v2.bondage.IV2BondageEquipment;
@@ -17,7 +14,6 @@ import java.util.List;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.ai.goal.Goal; import net.minecraft.world.entity.ai.goal.Goal;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
/** /**
@@ -249,15 +245,18 @@ public class MasterRandomEventGoal extends Goal {
* Create a random accessory item for the given body region. * Create a random accessory item for the given body region.
*/ */
private ItemStack createRandomAccessory(BodyRegionV2 region) { private ItemStack createRandomAccessory(BodyRegionV2 region) {
Item item = switch (region) { return switch (region) {
case EYES -> ModItems.getBlindfold(BlindfoldVariant.CLASSIC); case EYES -> DataDrivenBondageItem.createStack(
case MOUTH -> ModItems.getGag(GagVariant.BALL_GAG); new ResourceLocation("tiedup", "classic_blindfold")
case HANDS -> ModItems.getMittens(MittensVariant.LEATHER); );
default -> null; case MOUTH -> DataDrivenBondageItem.createStack(
new ResourceLocation("tiedup", "ball_gag")
);
case HANDS -> DataDrivenBondageItem.createStack(
new ResourceLocation("tiedup", "leather_mittens")
);
default -> ItemStack.EMPTY;
}; };
if (item == null) return ItemStack.EMPTY;
return new ItemStack(item);
} }
/** /**
@@ -304,8 +303,8 @@ public class MasterRandomEventGoal extends Goal {
// Put pet in dogbind if not already tied // Put pet in dogbind if not already tied
PlayerBindState bindState = PlayerBindState.getInstance(pet); PlayerBindState bindState = PlayerBindState.getInstance(pet);
if (bindState != null && !bindState.isTiedUp()) { if (bindState != null && !bindState.isTiedUp()) {
ItemStack dogbind = new ItemStack( ItemStack dogbind = DataDrivenBondageItem.createStack(
ModItems.getBind(BindVariant.DOGBINDER) new ResourceLocation("tiedup", "dogbinder")
); );
bindState.equip(BodyRegionV2.ARMS, dogbind); bindState.equip(BodyRegionV2.ARMS, dogbind);
} }

View File

@@ -3,7 +3,7 @@ package com.tiedup.remake.entities.ai.master;
import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.dialogue.DialogueBridge; import com.tiedup.remake.dialogue.DialogueBridge;
import com.tiedup.remake.entities.EntityMaster; import com.tiedup.remake.entities.EntityMaster;
import com.tiedup.remake.items.ItemChokeCollar; import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.state.PlayerBindState; import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.util.MessageDispatcher; import com.tiedup.remake.util.MessageDispatcher;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
@@ -441,8 +441,8 @@ public class MasterTaskWatchGoal extends Goal {
if (bindState != null && bindState.hasCollar()) { if (bindState != null && bindState.hasCollar()) {
ItemStack collar = bindState.getEquipment(BodyRegionV2.NECK); ItemStack collar = bindState.getEquipment(BodyRegionV2.NECK);
if (collar.getItem() instanceof ItemChokeCollar chokeCollar) { if (CollarHelper.isChokeCollar(collar)) {
chokeCollar.setChoking(collar, true); CollarHelper.setChoking(collar, true);
this.activeChokeCollar = collar; this.activeChokeCollar = collar;
this.chokeTimer = CHOKE_DURATION; this.chokeTimer = CHOKE_DURATION;
@@ -475,9 +475,9 @@ public class MasterTaskWatchGoal extends Goal {
private void deactivateChoke() { private void deactivateChoke() {
if ( if (
!activeChokeCollar.isEmpty() && !activeChokeCollar.isEmpty() &&
activeChokeCollar.getItem() instanceof ItemChokeCollar chokeCollar CollarHelper.isChokeCollar(activeChokeCollar)
) { ) {
chokeCollar.setChoking(activeChokeCollar, false); CollarHelper.setChoking(activeChokeCollar, false);
TiedUpMod.LOGGER.debug( TiedUpMod.LOGGER.debug(
"[MasterTaskWatchGoal] {} deactivated choke", "[MasterTaskWatchGoal] {} deactivated choke",
master.getNpcName() master.getNpcName()

View File

@@ -1,7 +1,7 @@
package com.tiedup.remake.entities.ai.personality; package com.tiedup.remake.entities.ai.personality;
import com.tiedup.remake.entities.EntityDamsel; import com.tiedup.remake.entities.EntityDamsel;
import com.tiedup.remake.items.base.ItemBind; import com.tiedup.remake.v2.bondage.BindModeHelper;
import com.tiedup.remake.personality.NpcCommand; import com.tiedup.remake.personality.NpcCommand;
import com.tiedup.remake.personality.ToolMode; import com.tiedup.remake.personality.ToolMode;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
@@ -506,7 +506,7 @@ public class NpcFollowCommandGoal extends Goal {
if (dist <= ATTACK_RANGE) { if (dist <= ATTACK_RANGE) {
// Try to capture using bind item // Try to capture using bind item
ItemStack bindItem = npc.getMainHandItem(); ItemStack bindItem = npc.getMainHandItem();
if (bindItem.getItem() instanceof ItemBind) { if (BindModeHelper.isBindItem(bindItem)) {
// Apply bind to target // Apply bind to target
captureTarget.equip(BodyRegionV2.ARMS, bindItem.copy()); captureTarget.equip(BodyRegionV2.ARMS, bindItem.copy());

View File

@@ -1,8 +1,8 @@
package com.tiedup.remake.entities.ai.personality; package com.tiedup.remake.entities.ai.personality;
import com.tiedup.remake.entities.EntityDamsel; import com.tiedup.remake.entities.EntityDamsel;
import com.tiedup.remake.items.base.ItemBind; import com.tiedup.remake.v2.bondage.BindModeHelper;
import com.tiedup.remake.items.base.ItemCollar; import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.personality.NpcCommand; import com.tiedup.remake.personality.NpcCommand;
import com.tiedup.remake.personality.PersonalityState; import com.tiedup.remake.personality.PersonalityState;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
@@ -426,7 +426,7 @@ public class NpcGuardCommandGoal extends Goal {
NonNullList<ItemStack> inventory = npc.getNpcInventory(); NonNullList<ItemStack> inventory = npc.getNpcInventory();
for (int i = 0; i < inventory.size(); i++) { for (int i = 0; i < inventory.size(); i++) {
ItemStack stack = inventory.get(i); ItemStack stack = inventory.get(i);
if (stack.getItem() instanceof ItemBind) { if (BindModeHelper.isBindItem(stack)) {
// Apply bind to slave // Apply bind to slave
slave.equip(BodyRegionV2.ARMS, stack.copy()); slave.equip(BodyRegionV2.ARMS, stack.copy());
stack.shrink(1); stack.shrink(1);
@@ -486,8 +486,8 @@ public class NpcGuardCommandGoal extends Goal {
private UUID getCollarOwnerUUID(EntityDamsel slave) { private UUID getCollarOwnerUUID(EntityDamsel slave) {
if (!slave.hasCollar()) return null; if (!slave.hasCollar()) return null;
ItemStack collar = slave.getEquipment(BodyRegionV2.NECK); ItemStack collar = slave.getEquipment(BodyRegionV2.NECK);
if (collar.getItem() instanceof ItemCollar collarItem) { if (CollarHelper.isCollar(collar)) {
java.util.List<UUID> owners = collarItem.getOwners(collar); java.util.List<UUID> owners = CollarHelper.getOwners(collar);
if (!owners.isEmpty()) { if (!owners.isEmpty()) {
return owners.get(0); return owners.get(0);
} }

View File

@@ -3,7 +3,7 @@ package com.tiedup.remake.entities.damsel.components;
import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.entities.AbstractTiedUpNpc; import com.tiedup.remake.entities.AbstractTiedUpNpc;
import com.tiedup.remake.entities.BondageServiceHandler; import com.tiedup.remake.entities.BondageServiceHandler;
import com.tiedup.remake.items.base.ItemCollar; import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.state.ICaptor; import com.tiedup.remake.state.ICaptor;
import com.tiedup.remake.state.IRestrainable; import com.tiedup.remake.state.IRestrainable;
import com.tiedup.remake.state.IRestrainableEntity; import com.tiedup.remake.state.IRestrainableEntity;
@@ -565,9 +565,9 @@ public class DamselBondageManager implements IRestrainable {
ItemStack collar = getEquipment(BodyRegionV2.NECK); ItemStack collar = getEquipment(BodyRegionV2.NECK);
if (collar.isEmpty()) return false; if (collar.isEmpty()) return false;
if (!(collar.getItem() instanceof ItemCollar itemCollar)) return false; if (!CollarHelper.isCollar(collar)) return false;
UUID cellId = itemCollar.getCellId(collar); UUID cellId = CollarHelper.getCellId(collar);
if (cellId == null) return false; if (cellId == null) return false;
// Get cell position from registry // Get cell position from registry

View File

@@ -2,7 +2,7 @@ package com.tiedup.remake.entities.damsel.components;
import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.dialogue.EntityDialogueManager; import com.tiedup.remake.dialogue.EntityDialogueManager;
import com.tiedup.remake.items.base.ItemCollar; import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.personality.*; import com.tiedup.remake.personality.*;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
import java.util.*; import java.util.*;
@@ -180,8 +180,8 @@ public class DamselPersonalitySystem {
UUID masterUUID = null; UUID masterUUID = null;
if (context.hasCollar()) { if (context.hasCollar()) {
ItemStack collar = context.getEquipment(BodyRegionV2.NECK); ItemStack collar = context.getEquipment(BodyRegionV2.NECK);
if (collar.getItem() instanceof ItemCollar collarItem) { if (CollarHelper.isCollar(collar)) {
List<UUID> owners = collarItem.getOwners(collar); List<UUID> owners = CollarHelper.getOwners(collar);
if (!owners.isEmpty()) { if (!owners.isEmpty()) {
masterUUID = owners.get(0); masterUUID = owners.get(0);
} }

View File

@@ -4,7 +4,11 @@ import com.tiedup.remake.core.ModConfig;
import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.entities.AbstractTiedUpNpc; import com.tiedup.remake.entities.AbstractTiedUpNpc;
import com.tiedup.remake.items.base.ILockable; import com.tiedup.remake.items.base.ILockable;
import com.tiedup.remake.items.base.ItemCollar; import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import com.tiedup.remake.v2.bondage.component.ComponentType;
import com.tiedup.remake.v2.bondage.component.GaggingComponent;
import com.tiedup.remake.v2.bondage.component.BlindingComponent;
import com.tiedup.remake.state.IRestrainableEntity; import com.tiedup.remake.state.IRestrainableEntity;
import com.tiedup.remake.util.RestraintEffectUtils; import com.tiedup.remake.util.RestraintEffectUtils;
import com.tiedup.remake.util.TiedUpSounds; import com.tiedup.remake.util.TiedUpSounds;
@@ -112,19 +116,15 @@ public class NpcEquipmentManager {
public boolean hasGaggingEffect() { public boolean hasGaggingEffect() {
ItemStack gag = getCurrentGag(); ItemStack gag = getCurrentGag();
if (gag.isEmpty()) return false; if (gag.isEmpty()) return false;
return ( if (DataDrivenBondageItem.getComponent(gag, ComponentType.GAGGING, GaggingComponent.class) != null) return true;
gag.getItem() instanceof return gag.getItem() instanceof com.tiedup.remake.items.base.IHasGaggingEffect;
com.tiedup.remake.items.base.IHasGaggingEffect
);
} }
public boolean hasBlindingEffect() { public boolean hasBlindingEffect() {
ItemStack blindfold = getCurrentBlindfold(); ItemStack blindfold = getCurrentBlindfold();
if (blindfold.isEmpty()) return false; if (blindfold.isEmpty()) return false;
return ( if (DataDrivenBondageItem.getComponent(blindfold, ComponentType.BLINDING, BlindingComponent.class) != null) return true;
blindfold.getItem() instanceof return blindfold.getItem() instanceof com.tiedup.remake.items.base.IHasBlindingEffect;
com.tiedup.remake.items.base.IHasBlindingEffect
);
} }
public boolean hasKnives() { public boolean hasKnives() {
@@ -768,8 +768,8 @@ public class NpcEquipmentManager {
public boolean isCollarOwner(Player player) { public boolean isCollarOwner(Player player) {
if (!hasCollar()) return true; if (!hasCollar()) return true;
ItemStack collar = getCurrentCollar(); ItemStack collar = getCurrentCollar();
if (!(collar.getItem() instanceof ItemCollar collarItem)) return true; if (!CollarHelper.isCollar(collar)) return true;
return collarItem.isOwner(collar, player); return CollarHelper.isOwner(collar, player);
} }
// COERCION // COERCION

View File

@@ -6,7 +6,7 @@ import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.dialogue.EntityDialogueManager; import com.tiedup.remake.dialogue.EntityDialogueManager;
import com.tiedup.remake.entities.EntityKidnapper; import com.tiedup.remake.entities.EntityKidnapper;
import com.tiedup.remake.entities.ai.kidnapper.KidnapperState; import com.tiedup.remake.entities.ai.kidnapper.KidnapperState;
import com.tiedup.remake.items.base.ItemCollar; import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.prison.PrisonerManager; import com.tiedup.remake.prison.PrisonerManager;
import com.tiedup.remake.prison.PrisonerRecord; import com.tiedup.remake.prison.PrisonerRecord;
import com.tiedup.remake.prison.PrisonerState; import com.tiedup.remake.prison.PrisonerState;
@@ -189,9 +189,9 @@ public class KidnapperCaptiveManager {
// If target has collar, verify we own it // If target has collar, verify we own it
if (target.hasCollar()) { if (target.hasCollar()) {
ItemStack collar = target.getEquipment(BodyRegionV2.NECK); ItemStack collar = target.getEquipment(BodyRegionV2.NECK);
if (collar.getItem() instanceof ItemCollar collarItem) { if (CollarHelper.isCollar(collar)) {
// Check if THIS kidnapper is owner // Check if THIS kidnapper is owner
if (!collarItem.getOwners(collar).contains(host.getUUID())) { if (!CollarHelper.getOwners(collar).contains(host.getUUID())) {
TiedUpMod.LOGGER.debug( TiedUpMod.LOGGER.debug(
"[KidnapperCaptiveManager] {} can't capture {} - collar owned by someone else", "[KidnapperCaptiveManager] {} can't capture {} - collar owned by someone else",
host.getNpcName(), host.getNpcName(),

View File

@@ -7,7 +7,7 @@ import com.tiedup.remake.entities.EntityKidnapper;
import com.tiedup.remake.entities.EntityKidnapperElite; import com.tiedup.remake.entities.EntityKidnapperElite;
import com.tiedup.remake.entities.ai.kidnapper.KidnapperState; import com.tiedup.remake.entities.ai.kidnapper.KidnapperState;
import com.tiedup.remake.items.ModItems; import com.tiedup.remake.items.ModItems;
import com.tiedup.remake.items.base.ItemCollar; import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.prison.PrisonerManager; import com.tiedup.remake.prison.PrisonerManager;
import com.tiedup.remake.prison.PrisonerRecord; import com.tiedup.remake.prison.PrisonerRecord;
import com.tiedup.remake.state.IBondageState; import com.tiedup.remake.state.IBondageState;
@@ -206,8 +206,8 @@ public class KidnapperTargetSelector {
// Self-collared entities (exploit) are treated as uncolllared — kidnappers ignore self-collars // Self-collared entities (exploit) are treated as uncolllared — kidnappers ignore self-collars
if (state.hasCollar()) { if (state.hasCollar()) {
ItemStack collar = state.getEquipment(BodyRegionV2.NECK); ItemStack collar = state.getEquipment(BodyRegionV2.NECK);
if (collar.getItem() instanceof ItemCollar collarItem) { if (CollarHelper.isCollar(collar)) {
java.util.List<UUID> owners = collarItem.getOwners(collar); java.util.List<UUID> owners = CollarHelper.getOwners(collar);
// Filter out self-collar (owner == wearer = exploit) // Filter out self-collar (owner == wearer = exploit)
java.util.List<UUID> realOwners = owners java.util.List<UUID> realOwners = owners
.stream() .stream()
@@ -312,8 +312,8 @@ public class KidnapperTargetSelector {
// Other kidnappers' collars are fair game — kidnapper can steal from kidnapper // Other kidnappers' collars are fair game — kidnapper can steal from kidnapper
if (state != null && state.hasCollar()) { if (state != null && state.hasCollar()) {
ItemStack collar = state.getEquipment(BodyRegionV2.NECK); ItemStack collar = state.getEquipment(BodyRegionV2.NECK);
if (collar.getItem() instanceof ItemCollar collarItem) { if (CollarHelper.isCollar(collar)) {
java.util.List<UUID> owners = collarItem.getOwners(collar); java.util.List<UUID> owners = CollarHelper.getOwners(collar);
if (!owners.isEmpty() && !owners.contains(host.getUUID())) { if (!owners.isEmpty() && !owners.contains(host.getUUID())) {
// Check if any owner is a DIFFERENT player (not self-collared, not a kidnapper) // Check if any owner is a DIFFERENT player (not self-collared, not a kidnapper)
if (host.level() instanceof ServerLevel sl) { if (host.level() instanceof ServerLevel sl) {

View File

@@ -4,7 +4,7 @@ import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.entities.EntityMaster; import com.tiedup.remake.entities.EntityMaster;
import com.tiedup.remake.entities.ai.master.MasterRandomEventGoal; import com.tiedup.remake.entities.ai.master.MasterRandomEventGoal;
import com.tiedup.remake.entities.ai.master.MasterState; import com.tiedup.remake.entities.ai.master.MasterState;
import com.tiedup.remake.items.base.ItemCollar; import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.minigame.StruggleSessionManager; import com.tiedup.remake.minigame.StruggleSessionManager;
import com.tiedup.remake.state.IBondageState; import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.util.KidnappedHelper; import com.tiedup.remake.util.KidnappedHelper;
@@ -126,22 +126,27 @@ public class MasterPetManager {
// Configure for pet play BEFORE equipping // Configure for pet play BEFORE equipping
if ( if (
chokeCollar.getItem() instanceof chokeCollar.getItem() instanceof
com.tiedup.remake.items.ItemChokeCollar collar com.tiedup.remake.items.base.IHasResistance resistable
) { ) {
collar.setCurrentResistance(chokeCollar, PET_COLLAR_RESISTANCE); resistable.setCurrentResistance(chokeCollar, PET_COLLAR_RESISTANCE);
collar.setLocked(chokeCollar, true); resistable.setCanBeStruggledOut(chokeCollar, true); // Can struggle, but very hard
collar.setLockable(chokeCollar, false); // Cannot be lockpicked
collar.setCanBeStruggledOut(chokeCollar, true); // Can struggle, but very hard
// Add this Master as owner
collar.addOwner(chokeCollar, master.getUUID(), master.getNpcName());
// Set NBT flag for pet play mode
chokeCollar.getOrCreateTag().putBoolean("petPlayMode", true);
chokeCollar
.getOrCreateTag()
.putUUID("masterUUID", master.getUUID());
} }
if (
chokeCollar.getItem() instanceof
com.tiedup.remake.items.base.ILockable lockable
) {
lockable.setLocked(chokeCollar, true);
lockable.setLockable(chokeCollar, false); // Cannot be lockpicked
}
// Add this Master as owner
CollarHelper.addOwner(chokeCollar, master.getUUID(), master.getNpcName());
// Set NBT flag for pet play mode
CollarHelper.setPetPlayMode(chokeCollar, true);
chokeCollar
.getOrCreateTag()
.putUUID("masterUUID", master.getUUID());
// Replace any existing collar (force removal) with the choke collar // Replace any existing collar (force removal) with the choke collar
state.replaceEquipment(BodyRegionV2.NECK, chokeCollar, true); state.replaceEquipment(BodyRegionV2.NECK, chokeCollar, true);
@@ -186,9 +191,9 @@ public class MasterPetManager {
} }
// Unlock the collar // Unlock the collar
if (collarStack.getItem() instanceof ItemCollar collar) { if (collarStack.getItem() instanceof com.tiedup.remake.items.base.ILockable lockable) {
collar.setLocked(collarStack, false); lockable.setLocked(collarStack, false);
collar.setLockable(collarStack, true); lockable.setLockable(collarStack, true);
} }
} }
@@ -242,8 +247,8 @@ public class MasterPetManager {
); );
if (collarStack.isEmpty()) return; if (collarStack.isEmpty()) return;
if (collarStack.getItem() instanceof ItemCollar collar) { if (collarStack.getItem() instanceof com.tiedup.remake.items.base.IHasResistance resistable) {
collar.setCurrentResistance(collarStack, PET_COLLAR_RESISTANCE); resistable.setCurrentResistance(collarStack, PET_COLLAR_RESISTANCE);
TiedUpMod.LOGGER.debug( TiedUpMod.LOGGER.debug(
"[MasterPetManager] Reset collar resistance for {}", "[MasterPetManager] Reset collar resistance for {}",
pet.getName().getString() pet.getName().getString()

View File

@@ -4,7 +4,7 @@ import com.tiedup.remake.cells.CampLifecycleManager;
import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.entities.EntityMaid; import com.tiedup.remake.entities.EntityMaid;
import com.tiedup.remake.entities.EntitySlaveTrader; import com.tiedup.remake.entities.EntitySlaveTrader;
import com.tiedup.remake.items.base.ItemBind; import com.tiedup.remake.v2.bondage.BindModeHelper;
import java.util.UUID; import java.util.UUID;
import net.minecraft.ChatFormatting; import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
@@ -40,7 +40,7 @@ public class CampNpcProtectionHandler {
// Check if player is holding restraint item // Check if player is holding restraint item
ItemStack heldItem = player.getItemInHand(event.getHand()); ItemStack heldItem = player.getItemInHand(event.getHand());
if (!(heldItem.getItem() instanceof ItemBind)) return; if (!BindModeHelper.isBindItem(heldItem)) return;
// Check if target is trader or maid with active camp // Check if target is trader or maid with active camp
UUID campId = null; UUID campId = null;

View File

@@ -3,8 +3,8 @@ package com.tiedup.remake.events.captivity;
import com.tiedup.remake.core.SettingsAccessor; import com.tiedup.remake.core.SettingsAccessor;
import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.entities.LeashProxyEntity; import com.tiedup.remake.entities.LeashProxyEntity;
import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.state.IBondageState; import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.state.IPlayerLeashAccess; import com.tiedup.remake.state.IPlayerLeashAccess;
import com.tiedup.remake.state.PlayerBindState; import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.util.KidnappedHelper; import com.tiedup.remake.util.KidnappedHelper;
@@ -146,9 +146,9 @@ public class PlayerEnslavementHandler {
ItemStack collar = slaveKidnappedState.getEquipment( ItemStack collar = slaveKidnappedState.getEquipment(
BodyRegionV2.NECK BodyRegionV2.NECK
); );
if (collar.getItem() instanceof ItemCollar collarItem) { if (CollarHelper.isCollar(collar)) {
if ( if (
collarItem CollarHelper
.getOwners(collar) .getOwners(collar)
.contains(master.getUUID()) .contains(master.getUUID())
) { ) {

View File

@@ -4,8 +4,8 @@ import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.entities.EntityKidnapper; import com.tiedup.remake.entities.EntityKidnapper;
import com.tiedup.remake.entities.EntityMaid; import com.tiedup.remake.entities.EntityMaid;
import com.tiedup.remake.entities.EntitySlaveTrader; import com.tiedup.remake.entities.EntitySlaveTrader;
import com.tiedup.remake.items.base.ItemBind;
import com.tiedup.remake.prison.LaborRecord; import com.tiedup.remake.prison.LaborRecord;
import com.tiedup.remake.v2.bondage.BindModeHelper;
import com.tiedup.remake.prison.PrisonerManager; import com.tiedup.remake.prison.PrisonerManager;
import com.tiedup.remake.prison.PrisonerRecord; import com.tiedup.remake.prison.PrisonerRecord;
import com.tiedup.remake.prison.PrisonerState; import com.tiedup.remake.prison.PrisonerState;
@@ -145,7 +145,7 @@ public class LaborAttackPunishmentHandler {
// Check if player is holding a restraint item (rope, chain, etc.) // Check if player is holding a restraint item (rope, chain, etc.)
ItemStack heldItem = serverPlayer.getItemInHand(hand); ItemStack heldItem = serverPlayer.getItemInHand(hand);
if (!(heldItem.getItem() instanceof ItemBind)) { if (!BindModeHelper.isBindItem(heldItem)) {
return; // Not a restraint item return; // Not a restraint item
} }

View File

@@ -2,8 +2,8 @@ package com.tiedup.remake.events.restriction;
import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.entities.EntityMaster; import com.tiedup.remake.entities.EntityMaster;
import com.tiedup.remake.items.ItemChokeCollar;
import com.tiedup.remake.state.PlayerBindState; import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.blocks.PetBedManager; import com.tiedup.remake.v2.blocks.PetBedManager;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
@@ -284,8 +284,8 @@ public class PetPlayRestrictionHandler {
if (bindState == null || !bindState.hasCollar()) return; if (bindState == null || !bindState.hasCollar()) return;
ItemStack collar = bindState.getEquipment(BodyRegionV2.NECK); ItemStack collar = bindState.getEquipment(BodyRegionV2.NECK);
if (collar.getItem() instanceof ItemChokeCollar chokeCollar) { if (CollarHelper.isChokeCollar(collar)) {
if (chokeCollar.isChoking(collar)) { if (CollarHelper.isChoking(collar)) {
// Apply ChokeEffect (short duration, re-applied each active tick) // Apply ChokeEffect (short duration, re-applied each active tick)
if ( if (
!player.hasEffect( !player.hasEffect(

View File

@@ -3,8 +3,8 @@ package com.tiedup.remake.events.restriction;
import com.tiedup.remake.core.SettingsAccessor; import com.tiedup.remake.core.SettingsAccessor;
import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.entities.EntityKidnapper; import com.tiedup.remake.entities.EntityKidnapper;
import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.minigame.StruggleSessionManager; import com.tiedup.remake.minigame.StruggleSessionManager;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.network.ModNetwork; import com.tiedup.remake.network.ModNetwork;
import com.tiedup.remake.network.personality.PacketSlaveBeingFreed; import com.tiedup.remake.network.personality.PacketSlaveBeingFreed;
import com.tiedup.remake.state.IBondageState; import com.tiedup.remake.state.IBondageState;
@@ -642,14 +642,11 @@ public class RestraintTaskTickHandler {
if (!(slave.level() instanceof ServerLevel serverLevel)) return; if (!(slave.level() instanceof ServerLevel serverLevel)) return;
ItemStack collar = slave.getEquipment(BodyRegionV2.NECK); ItemStack collar = slave.getEquipment(BodyRegionV2.NECK);
if ( if (collar.isEmpty() || !CollarHelper.isCollar(collar)) {
collar.isEmpty() ||
!(collar.getItem() instanceof ItemCollar collarItem)
) {
return; return;
} }
List<UUID> owners = collarItem.getOwners(collar); List<UUID> owners = CollarHelper.getOwners(collar);
if (owners.isEmpty()) return; if (owners.isEmpty()) return;
// Create alert packet // Create alert packet

View File

@@ -7,6 +7,9 @@ import com.tiedup.remake.dialogue.GagTalkManager;
import com.tiedup.remake.items.base.ItemGag; import com.tiedup.remake.items.base.ItemGag;
import com.tiedup.remake.state.IBondageState; import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.util.GagMaterial; import com.tiedup.remake.util.GagMaterial;
import com.tiedup.remake.v2.bondage.component.ComponentType;
import com.tiedup.remake.v2.bondage.component.GaggingComponent;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import com.tiedup.remake.util.KidnappedHelper; import com.tiedup.remake.util.KidnappedHelper;
import com.tiedup.remake.util.TiedUpUtils; import com.tiedup.remake.util.TiedUpUtils;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
@@ -54,12 +57,22 @@ public class ChatEventHandler {
BodyRegionV2.MOUTH BodyRegionV2.MOUTH
); );
if ( // V2: check gagging component, V1 fallback: instanceof ItemGag
!gagStack.isEmpty() && GaggingComponent gaggingComp = DataDrivenBondageItem.getComponent(
gagStack.getItem() instanceof ItemGag gagItem gagStack, ComponentType.GAGGING, GaggingComponent.class);
) { boolean isGagItem = gaggingComp != null
|| gagStack.getItem() instanceof ItemGag;
if (!gagStack.isEmpty() && isGagItem) {
String originalMessage = event.getRawText(); String originalMessage = event.getRawText();
GagMaterial material = gagItem.getGagMaterial(); // V2: get material from component, V1 fallback: from ItemGag
GagMaterial material = null;
if (gaggingComp != null) {
material = gaggingComp.getMaterial();
}
if (material == null && gagStack.getItem() instanceof ItemGag gagItem) {
material = gagItem.getGagMaterial();
}
// 1. Process the message through our GagTalkManager V2 // 1. Process the message through our GagTalkManager V2
Component muffledMessage = GagTalkManager.processGagMessage( Component muffledMessage = GagTalkManager.processGagMessage(
@@ -83,7 +96,9 @@ public class ChatEventHandler {
.append("> ") .append("> ")
.append(muffledMessage); .append(muffledMessage);
double range = material.getTalkRange(); double range = material != null
? material.getTalkRange()
: (gaggingComp != null ? gaggingComp.getRange() : 10.0);
List<ServerPlayer> nearbyPlayers = List<ServerPlayer> nearbyPlayers =
TiedUpUtils.getPlayersAround( TiedUpUtils.getPlayersAround(

View File

@@ -1,9 +1,8 @@
package com.tiedup.remake.minigame; package com.tiedup.remake.minigame;
import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.ItemShockCollar; import com.tiedup.remake.items.base.IHasResistance;
import com.tiedup.remake.items.base.ItemBind; import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.minigame.ContinuousStruggleMiniGameState.TickResult; import com.tiedup.remake.minigame.ContinuousStruggleMiniGameState.TickResult;
import com.tiedup.remake.minigame.ContinuousStruggleMiniGameState.UpdateType; import com.tiedup.remake.minigame.ContinuousStruggleMiniGameState.UpdateType;
import com.tiedup.remake.network.ModNetwork; import com.tiedup.remake.network.ModNetwork;
@@ -544,11 +543,11 @@ public class StruggleSessionManager {
if (collar.isEmpty()) return false; if (collar.isEmpty()) return false;
// Only shock collars can trigger during struggle // Only shock collars can trigger during struggle
if (!(collar.getItem() instanceof ItemShockCollar)) return false; if (!CollarHelper.canShock(collar)) return false;
// Must be locked // Must be locked
if (collar.getItem() instanceof ItemCollar collarItem) { if (collar.getItem() instanceof com.tiedup.remake.items.base.ILockable lockable) {
return collarItem.isLocked(collar); return lockable.isLocked(collar);
} }
return false; return false;
} }
@@ -643,14 +642,12 @@ public class StruggleSessionManager {
player, player,
BodyRegionV2.ARMS BodyRegionV2.ARMS
); );
if ( if (bindStack.isEmpty()) {
bindStack.isEmpty() ||
!(bindStack.getItem() instanceof ItemBind bind)
) {
return; return;
} }
if (bindStack.getItem() instanceof IHasResistance resistanceItem) {
bind.setCurrentResistance(bindStack, session.getCurrentResistance()); resistanceItem.setCurrentResistance(bindStack, session.getCurrentResistance());
}
} }
/** /**

View File

@@ -4,7 +4,7 @@ import com.tiedup.remake.compat.mca.MCACompat;
import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.ItemKey; import com.tiedup.remake.items.ItemKey;
import com.tiedup.remake.items.ItemMasterKey; import com.tiedup.remake.items.ItemMasterKey;
import com.tiedup.remake.items.base.ItemCollar; import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.state.IBondageState; import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.IV2BondageItem; import com.tiedup.remake.v2.bondage.IV2BondageItem;
@@ -182,8 +182,8 @@ public abstract class MixinMCAVillagerInteraction {
// Can leash if player is a collar owner // Can leash if player is a collar owner
if (state.hasCollar()) { if (state.hasCollar()) {
ItemStack collar = state.getEquipment(BodyRegionV2.NECK); ItemStack collar = state.getEquipment(BodyRegionV2.NECK);
if (collar.getItem() instanceof ItemCollar collarItem) { if (CollarHelper.isCollar(collar)) {
return collarItem.getOwners(collar).contains(player.getUUID()); return CollarHelper.isOwner(collar, player);
} }
} }

View File

@@ -1,7 +1,7 @@
package com.tiedup.remake.mixin; package com.tiedup.remake.mixin;
import com.tiedup.remake.compat.mca.MCACompat; import com.tiedup.remake.compat.mca.MCACompat;
import com.tiedup.remake.items.base.ItemCollar; import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.state.IBondageState; import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.LivingEntity;
@@ -71,8 +71,8 @@ public class MixinMCAVillagerLeash {
// Can be leashed if player is collar owner // Can be leashed if player is collar owner
if (state.hasCollar()) { if (state.hasCollar()) {
ItemStack collar = state.getEquipment(BodyRegionV2.NECK); ItemStack collar = state.getEquipment(BodyRegionV2.NECK);
if (collar.getItem() instanceof ItemCollar collarItem) { if (CollarHelper.isCollar(collar)) {
if (collarItem.getOwners(collar).contains(player.getUUID())) { if (CollarHelper.isOwner(collar, player)) {
cir.setReturnValue(true); cir.setReturnValue(true);
return; return;
} }

View File

@@ -1,7 +1,7 @@
package com.tiedup.remake.mixin.client; package com.tiedup.remake.mixin.client;
import com.tiedup.remake.items.base.ItemBind;
import com.tiedup.remake.items.base.PoseType; import com.tiedup.remake.items.base.PoseType;
import com.tiedup.remake.v2.bondage.PoseTypeHelper;
import com.tiedup.remake.state.HumanChairHelper; import com.tiedup.remake.state.HumanChairHelper;
import com.tiedup.remake.state.PlayerBindState; import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
@@ -57,11 +57,11 @@ public abstract class MixinCamera {
} }
ItemStack bind = state.getEquipment(BodyRegionV2.ARMS); ItemStack bind = state.getEquipment(BodyRegionV2.ARMS);
if (bind.isEmpty() || !(bind.getItem() instanceof ItemBind itemBind)) { if (bind.isEmpty()) {
return; return;
} }
if (itemBind.getPoseType() != PoseType.DOG) { if (PoseTypeHelper.getPoseType(bind) != PoseType.DOG) {
return; return;
} }

View File

@@ -2,8 +2,8 @@ package com.tiedup.remake.mixin.client;
import com.tiedup.remake.client.animation.render.DogPoseRenderHandler; import com.tiedup.remake.client.animation.render.DogPoseRenderHandler;
import com.tiedup.remake.client.animation.util.DogPoseHelper; import com.tiedup.remake.client.animation.util.DogPoseHelper;
import com.tiedup.remake.items.base.ItemBind;
import com.tiedup.remake.items.base.PoseType; import com.tiedup.remake.items.base.PoseType;
import com.tiedup.remake.v2.bondage.PoseTypeHelper;
import com.tiedup.remake.state.PlayerBindState; import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
import net.minecraft.client.model.PlayerModel; import net.minecraft.client.model.PlayerModel;
@@ -45,11 +45,11 @@ public class MixinPlayerModel {
} }
ItemStack bind = state.getEquipment(BodyRegionV2.ARMS); ItemStack bind = state.getEquipment(BodyRegionV2.ARMS);
if (bind.isEmpty() || !(bind.getItem() instanceof ItemBind itemBind)) { if (bind.isEmpty()) {
return; return;
} }
if (itemBind.getPoseType() != PoseType.DOG) { if (PoseTypeHelper.getPoseType(bind) != PoseType.DOG) {
return; return;
} }

View File

@@ -4,8 +4,9 @@ import com.tiedup.remake.client.animation.BondageAnimationManager;
import com.tiedup.remake.client.animation.StaticPoseApplier; import com.tiedup.remake.client.animation.StaticPoseApplier;
import com.tiedup.remake.client.animation.util.AnimationIdBuilder; import com.tiedup.remake.client.animation.util.AnimationIdBuilder;
import com.tiedup.remake.compat.mca.MCACompat; import com.tiedup.remake.compat.mca.MCACompat;
import com.tiedup.remake.items.base.ItemBind;
import com.tiedup.remake.items.base.PoseType; import com.tiedup.remake.items.base.PoseType;
import com.tiedup.remake.v2.bondage.BindModeHelper;
import com.tiedup.remake.v2.bondage.PoseTypeHelper;
import com.tiedup.remake.state.IBondageState; import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper; import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
@@ -78,11 +79,7 @@ public class MixinVillagerEntityBaseModelMCA<T extends LivingEntity> {
// Get pose info from bind item // Get pose info from bind item
ItemStack bind = state.getEquipment(BodyRegionV2.ARMS); ItemStack bind = state.getEquipment(BodyRegionV2.ARMS);
PoseType poseType = PoseType.STANDARD; PoseType poseType = PoseTypeHelper.getPoseType(bind);
if (bind.getItem() instanceof ItemBind itemBind) {
poseType = itemBind.getPoseType();
}
// Derive bound state from V2 regions, fallback to V1 bind mode NBT // Derive bound state from V2 regions, fallback to V1 bind mode NBT
boolean armsBound = V2EquipmentHelper.isRegionOccupied( boolean armsBound = V2EquipmentHelper.isRegionOccupied(
@@ -94,9 +91,9 @@ public class MixinVillagerEntityBaseModelMCA<T extends LivingEntity> {
BodyRegionV2.LEGS BodyRegionV2.LEGS
); );
if (!armsBound && !legsBound && bind.getItem() instanceof ItemBind) { if (!armsBound && !legsBound && BindModeHelper.isBindItem(bind)) {
armsBound = ItemBind.hasArmsBound(bind); armsBound = BindModeHelper.hasArmsBound(bind);
legsBound = ItemBind.hasLegsBound(bind); legsBound = BindModeHelper.hasLegsBound(bind);
} }
// MCA doesn't track struggling state - use false for now // MCA doesn't track struggling state - use false for now

View File

@@ -5,8 +5,8 @@ import com.tiedup.remake.cells.CellRegistryV2;
import com.tiedup.remake.core.SystemMessageManager; import com.tiedup.remake.core.SystemMessageManager;
import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.entities.EntityDamsel; import com.tiedup.remake.entities.EntityDamsel;
import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.network.PacketRateLimiter; import com.tiedup.remake.network.PacketRateLimiter;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.network.sync.SyncManager; import com.tiedup.remake.network.sync.SyncManager;
import com.tiedup.remake.personality.PersonalityState; import com.tiedup.remake.personality.PersonalityState;
import com.tiedup.remake.state.IBondageState; import com.tiedup.remake.state.IBondageState;
@@ -14,9 +14,9 @@ import com.tiedup.remake.util.KidnappedHelper;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
import java.util.UUID; import java.util.UUID;
import java.util.function.Supplier; import java.util.function.Supplier;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
@@ -96,7 +96,7 @@ public class PacketAssignCellToCollar {
} }
ItemStack collarStack = state.getEquipment(BodyRegionV2.NECK); ItemStack collarStack = state.getEquipment(BodyRegionV2.NECK);
if (!(collarStack.getItem() instanceof ItemCollar collar)) { if (!CollarHelper.isCollar(collarStack)) {
TiedUpMod.LOGGER.debug( TiedUpMod.LOGGER.debug(
"[PacketAssignCellToCollar] Invalid collar item" "[PacketAssignCellToCollar] Invalid collar item"
); );
@@ -105,7 +105,7 @@ public class PacketAssignCellToCollar {
// Security: Verify sender owns the collar (or is admin) // Security: Verify sender owns the collar (or is admin)
if ( if (
!collar.isOwner(collarStack, sender) && !CollarHelper.isOwner(collarStack, sender) &&
!sender.hasPermissions(2) !sender.hasPermissions(2)
) { ) {
TiedUpMod.LOGGER.debug( TiedUpMod.LOGGER.debug(
@@ -139,7 +139,15 @@ public class PacketAssignCellToCollar {
} }
// Set the cell ID on the collar // Set the cell ID on the collar
collar.setCellId(collarStack, msg.cellId); if (msg.cellId != null) {
CollarHelper.setCellId(collarStack, msg.cellId);
} else {
// Clear cell assignment
CompoundTag collarTag = collarStack.getTag();
if (collarTag != null) {
collarTag.remove("cellId");
}
}
// Sync PersonalityState for damsels // Sync PersonalityState for damsels
if (target instanceof EntityDamsel damsel) { if (target instanceof EntityDamsel damsel) {

View File

@@ -7,8 +7,8 @@ import com.tiedup.remake.dialogue.EntityDialogueManager.DialogueCategory;
import com.tiedup.remake.entities.EntityDamsel; import com.tiedup.remake.entities.EntityDamsel;
import com.tiedup.remake.items.ItemCommandWand; import com.tiedup.remake.items.ItemCommandWand;
import com.tiedup.remake.items.ModItems; import com.tiedup.remake.items.ModItems;
import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.network.ModNetwork; import com.tiedup.remake.network.ModNetwork;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.network.PacketRateLimiter; import com.tiedup.remake.network.PacketRateLimiter;
import com.tiedup.remake.personality.JobExperience; import com.tiedup.remake.personality.JobExperience;
import com.tiedup.remake.personality.NpcCommand; import com.tiedup.remake.personality.NpcCommand;
@@ -390,11 +390,11 @@ public class PacketNpcCommand {
} }
ItemStack collar = damsel.getEquipment(BodyRegionV2.NECK); ItemStack collar = damsel.getEquipment(BodyRegionV2.NECK);
if (!(collar.getItem() instanceof ItemCollar collarItem)) { if (!CollarHelper.isCollar(collar)) {
return false; return false;
} }
if (!collarItem.getOwners(collar).contains(sender.getUUID())) { if (!CollarHelper.isOwner(collar, sender)) {
SystemMessageManager.sendToPlayer( SystemMessageManager.sendToPlayer(
sender, sender,
SystemMessageManager.MessageCategory.ERROR, SystemMessageManager.MessageCategory.ERROR,

View File

@@ -1,7 +1,7 @@
package com.tiedup.remake.network.slave; package com.tiedup.remake.network.slave;
import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.network.PacketRateLimiter; import com.tiedup.remake.network.PacketRateLimiter;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.state.IBondageState; import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.util.KidnappedHelper; import com.tiedup.remake.util.KidnappedHelper;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
@@ -75,9 +75,9 @@ public class PacketMasterEquip {
); );
if (targetState == null || !targetState.hasCollar()) return; if (targetState == null || !targetState.hasCollar()) return;
ItemStack collarStack = targetState.getEquipment(BodyRegionV2.NECK); ItemStack collarStack = targetState.getEquipment(BodyRegionV2.NECK);
if (collarStack.getItem() instanceof ItemCollar collar) { if (CollarHelper.isCollar(collarStack)) {
if ( if (
!collar.isOwner(collarStack, sender) && !CollarHelper.isOwner(collarStack, sender) &&
!sender.hasPermissions(2) !sender.hasPermissions(2)
) return; ) return;
} }

View File

@@ -2,10 +2,8 @@ package com.tiedup.remake.network.slave;
import com.tiedup.remake.core.SystemMessageManager; import com.tiedup.remake.core.SystemMessageManager;
import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.ItemGpsCollar;
import com.tiedup.remake.items.ItemShockCollar;
import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.state.IBondageState; import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.state.IRestrainable; import com.tiedup.remake.state.IRestrainable;
import com.tiedup.remake.state.PlayerBindState; import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.state.PlayerCaptorManager; import com.tiedup.remake.state.PlayerCaptorManager;
@@ -128,8 +126,8 @@ public class PacketSlaveAction {
ItemStack collarStack = kidnapped.getEquipment( ItemStack collarStack = kidnapped.getEquipment(
BodyRegionV2.NECK BodyRegionV2.NECK
); );
if (collarStack.getItem() instanceof ItemCollar collar) { if (CollarHelper.isCollar(collarStack)) {
if (collar.isOwner(collarStack, sender)) { if (CollarHelper.isOwner(collarStack, sender)) {
targetCaptive = kidnapped; targetCaptive = kidnapped;
break; break;
} }
@@ -219,10 +217,7 @@ public class PacketSlaveAction {
} }
ItemStack collarStack = target.getEquipment(BodyRegionV2.NECK); ItemStack collarStack = target.getEquipment(BodyRegionV2.NECK);
if ( if (!CollarHelper.canShock(collarStack)) {
!(collarStack.getItem() instanceof ItemCollar collar) ||
!collar.canShock()
) {
SystemMessageManager.sendToPlayer( SystemMessageManager.sendToPlayer(
sender, sender,
SystemMessageManager.MessageCategory.ERROR, SystemMessageManager.MessageCategory.ERROR,
@@ -232,14 +227,8 @@ public class PacketSlaveAction {
} }
// Check if sender is owner of the collar or collar is public // Check if sender is owner of the collar or collar is public
// FIX: Always check permissions for ANY collar that can shock, not just ItemShockCollar boolean isOwner = CollarHelper.isOwner(collarStack, sender);
boolean isOwner = collar.isOwner(collarStack, sender); boolean isPublic = CollarHelper.isPublicShock(collarStack);
boolean isPublic = false;
// ItemShockCollar has additional "public mode" that allows anyone to shock
if (collarStack.getItem() instanceof ItemShockCollar shockCollar) {
isPublic = shockCollar.isPublic(collarStack);
}
if (!isOwner && !isPublic) { if (!isOwner && !isPublic) {
SystemMessageManager.sendToPlayer( SystemMessageManager.sendToPlayer(
@@ -285,10 +274,7 @@ public class PacketSlaveAction {
} }
ItemStack collarStack = target.getEquipment(BodyRegionV2.NECK); ItemStack collarStack = target.getEquipment(BodyRegionV2.NECK);
if ( if (!CollarHelper.hasGPS(collarStack)) {
!(collarStack.getItem() instanceof ItemCollar collar) ||
!collar.hasGPS()
) {
SystemMessageManager.sendToPlayer( SystemMessageManager.sendToPlayer(
sender, sender,
SystemMessageManager.MessageCategory.ERROR, SystemMessageManager.MessageCategory.ERROR,
@@ -298,18 +284,16 @@ public class PacketSlaveAction {
} }
// Check permissions // Check permissions
if (collarStack.getItem() instanceof ItemGpsCollar gpsCollar) { boolean isOwner = CollarHelper.isOwner(collarStack, sender);
boolean isOwner = collar.isOwner(collarStack, sender); boolean isPublic = CollarHelper.hasPublicTracking(collarStack);
boolean isPublic = gpsCollar.hasPublicTracking(collarStack);
if (!isOwner && !isPublic) { if (!isOwner && !isPublic) {
SystemMessageManager.sendToPlayer( SystemMessageManager.sendToPlayer(
sender, sender,
SystemMessageManager.MessageCategory.ERROR, SystemMessageManager.MessageCategory.ERROR,
"You don't have permission to track " + name + "!" "You don't have permission to track " + name + "!"
); );
return; return;
}
} }
// Check same dimension // Check same dimension
@@ -367,8 +351,8 @@ public class PacketSlaveAction {
} else { } else {
// For collar-owned entities, just remove collar ownership // For collar-owned entities, just remove collar ownership
ItemStack collarStack = target.getEquipment(BodyRegionV2.NECK); ItemStack collarStack = target.getEquipment(BodyRegionV2.NECK);
if (collarStack.getItem() instanceof ItemCollar collar) { if (CollarHelper.isCollar(collarStack)) {
collar.removeOwner(collarStack, sender.getUUID()); CollarHelper.removeOwner(collarStack, sender.getUUID());
SystemMessageManager.sendToPlayer( SystemMessageManager.sendToPlayer(
sender, sender,
"Released collar control of " + name + "!", "Released collar control of " + name + "!",

View File

@@ -1,6 +1,6 @@
package com.tiedup.remake.personality; package com.tiedup.remake.personality;
import com.tiedup.remake.items.base.ItemBind; import com.tiedup.remake.v2.bondage.BindModeHelper;
import net.minecraft.world.item.AxeItem; import net.minecraft.world.item.AxeItem;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.PickaxeItem; import net.minecraft.world.item.PickaxeItem;
@@ -48,7 +48,7 @@ public enum ToolMode {
if (item instanceof AxeItem) { if (item instanceof AxeItem) {
return WOODCUTTING; return WOODCUTTING;
} }
if (item instanceof ItemBind) { if (BindModeHelper.isBindItem(mainHand)) {
return CAPTURE; return CAPTURE;
} }

View File

@@ -1,8 +1,8 @@
package com.tiedup.remake.state; package com.tiedup.remake.state;
import com.tiedup.remake.items.base.ILockable; import com.tiedup.remake.items.base.ILockable;
import com.tiedup.remake.items.base.ItemBind;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.BindModeHelper;
import java.util.function.Supplier; import java.util.function.Supplier;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
@@ -97,7 +97,7 @@ public interface IBondageState extends ICapturable {
if (!isTiedUp()) return false; if (!isTiedUp()) return false;
ItemStack bind = getEquipment(BodyRegionV2.ARMS); ItemStack bind = getEquipment(BodyRegionV2.ARMS);
if (bind.isEmpty()) return false; if (bind.isEmpty()) return false;
return ItemBind.hasArmsBound(bind); return BindModeHelper.hasArmsBound(bind);
} }
/** /**
@@ -111,7 +111,7 @@ public interface IBondageState extends ICapturable {
if (!isTiedUp()) return false; if (!isTiedUp()) return false;
ItemStack bind = getEquipment(BodyRegionV2.ARMS); ItemStack bind = getEquipment(BodyRegionV2.ARMS);
if (bind.isEmpty()) return false; if (bind.isEmpty()) return false;
return ItemBind.hasLegsBound(bind); return BindModeHelper.hasLegsBound(bind);
} }
/** /**
@@ -121,10 +121,10 @@ public interface IBondageState extends ICapturable {
* @return "full", "arms", or "legs" * @return "full", "arms", or "legs"
*/ */
default String getBindModeId() { default String getBindModeId() {
if (!isTiedUp()) return ItemBind.BIND_MODE_FULL; if (!isTiedUp()) return BindModeHelper.MODE_FULL;
ItemStack bind = getEquipment(BodyRegionV2.ARMS); ItemStack bind = getEquipment(BodyRegionV2.ARMS);
if (bind.isEmpty()) return ItemBind.BIND_MODE_FULL; if (bind.isEmpty()) return BindModeHelper.MODE_FULL;
return ItemBind.getBindModeId(bind); return BindModeHelper.getBindModeId(bind);
} }
/** /**

View File

@@ -3,8 +3,8 @@ package com.tiedup.remake.state;
import com.tiedup.remake.core.ModSounds; import com.tiedup.remake.core.ModSounds;
import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.entities.LeashProxyEntity; import com.tiedup.remake.entities.LeashProxyEntity;
import com.tiedup.remake.items.base.ItemBind; import com.tiedup.remake.items.base.ILockable;
import com.tiedup.remake.items.base.ItemCollar; import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.network.sync.SyncManager; import com.tiedup.remake.network.sync.SyncManager;
import com.tiedup.remake.state.components.PlayerCaptivity; import com.tiedup.remake.state.components.PlayerCaptivity;
import com.tiedup.remake.state.components.PlayerClothesPermission; import com.tiedup.remake.state.components.PlayerClothesPermission;
@@ -737,8 +737,8 @@ public class PlayerBindState implements IRestrainable, IPlayerBindStateHost {
ItemStack collar = getCurrentCollar(); ItemStack collar = getCurrentCollar();
return ( return (
!collar.isEmpty() && !collar.isEmpty() &&
collar.getItem() instanceof ItemCollar collarItem && collar.getItem() instanceof ILockable lockable &&
collarItem.isLocked(collar) lockable.isLocked(collar)
); );
} }
@@ -1211,9 +1211,9 @@ public class PlayerBindState implements IRestrainable, IPlayerBindStateHost {
ItemStack collar = getEquipment(BodyRegionV2.NECK); ItemStack collar = getEquipment(BodyRegionV2.NECK);
if ( if (
!collar.isEmpty() && !collar.isEmpty() &&
collar.getItem() instanceof ItemCollar collarItem collar.getItem() instanceof ILockable lockable
) { ) {
collarItem.setLocked(collar, false); lockable.setLocked(collar, false);
} }
// Drop all items // Drop all items

View File

@@ -1,8 +1,8 @@
package com.tiedup.remake.state; package com.tiedup.remake.state;
import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.CollarHelper;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
@@ -144,9 +144,9 @@ public class PlayerCaptorManager implements ICaptor {
// Check if target has collar with this captor as owner // Check if target has collar with this captor as owner
if (target.hasCollar()) { if (target.hasCollar()) {
ItemStack collar = target.getEquipment(BodyRegionV2.NECK); ItemStack collar = target.getEquipment(BodyRegionV2.NECK);
if (collar.getItem() instanceof ItemCollar collarItem) { if (CollarHelper.isCollar(collar)) {
if ( if (
collarItem.getOwners(collar).contains(this.captor.getUUID()) CollarHelper.getOwners(collar).contains(this.captor.getUUID())
) { ) {
return true; return true;
} }

View File

@@ -1,7 +1,7 @@
package com.tiedup.remake.state.components; package com.tiedup.remake.state.components;
import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.state.hosts.IPlayerBindStateHost; import com.tiedup.remake.state.hosts.IPlayerBindStateHost;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper; import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.LivingEntity;
@@ -87,12 +87,9 @@ public class PlayerDataRetrieval {
Player player = host.getPlayer(); Player player = host.getPlayer();
ItemStack collar = getCurrentCollar(); ItemStack collar = getCurrentCollar();
if ( if (!collar.isEmpty() && CollarHelper.isCollar(collar)) {
!collar.isEmpty() &&
collar.getItem() instanceof ItemCollar collarItem
) {
// Try to get nickname from collar NBT // Try to get nickname from collar NBT
String nickname = collarItem.getNickname(collar); String nickname = CollarHelper.getNickname(collar);
if (nickname != null && !nickname.isEmpty()) { if (nickname != null && !nickname.isEmpty()) {
return nickname; return nickname;
} }
@@ -109,8 +106,8 @@ public class PlayerDataRetrieval {
ItemStack collar = getCurrentCollar(); ItemStack collar = getCurrentCollar();
if (collar.isEmpty()) return false; if (collar.isEmpty()) return false;
if (collar.getItem() instanceof ItemCollar collarItem) { if (CollarHelper.isCollar(collar)) {
String nickname = collarItem.getNickname(collar); String nickname = CollarHelper.getNickname(collar);
return nickname != null && !nickname.isEmpty(); return nickname != null && !nickname.isEmpty();
} }
return false; return false;

View File

@@ -1,8 +1,6 @@
package com.tiedup.remake.state.components; package com.tiedup.remake.state.components;
import com.tiedup.remake.items.base.ILockable; import com.tiedup.remake.items.base.ILockable;
import com.tiedup.remake.items.base.ItemBind;
import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.state.hosts.IPlayerBindStateHost; import com.tiedup.remake.state.hosts.IPlayerBindStateHost;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.IV2BondageEquipment; import com.tiedup.remake.v2.bondage.IV2BondageEquipment;
@@ -154,9 +152,9 @@ public class PlayerEquipment {
); );
if (collar.isEmpty()) return ItemStack.EMPTY; if (collar.isEmpty()) return ItemStack.EMPTY;
if (collar.getItem() instanceof ItemCollar collarItem) { if (collar.getItem() instanceof ILockable lockable) {
if (!force && collarItem.isLocked(collar)) return ItemStack.EMPTY; if (!force && lockable.isLocked(collar)) return ItemStack.EMPTY;
collarItem.setLocked(collar, false); lockable.setLocked(collar, false);
} }
return V2EquipmentHelper.unequipFromRegion(player, BodyRegionV2.NECK); return V2EquipmentHelper.unequipFromRegion(player, BodyRegionV2.NECK);
} }

View File

@@ -3,8 +3,8 @@ package com.tiedup.remake.state.components;
import com.tiedup.remake.cells.CampOwnership; import com.tiedup.remake.cells.CampOwnership;
import com.tiedup.remake.cells.CellRegistryV2; import com.tiedup.remake.cells.CellRegistryV2;
import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.base.ItemBind;
import com.tiedup.remake.state.hosts.IPlayerBindStateHost; import com.tiedup.remake.state.hosts.IPlayerBindStateHost;
import com.tiedup.remake.v2.bondage.BindModeHelper;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
import java.util.UUID; import java.util.UUID;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
@@ -148,7 +148,7 @@ public class PlayerLifecycle {
*/ */
private boolean hasLegsBound(ItemStack bind) { private boolean hasLegsBound(ItemStack bind) {
if (bind.isEmpty()) return false; if (bind.isEmpty()) return false;
if (!(bind.getItem() instanceof ItemBind)) return false; if (!BindModeHelper.isBindItem(bind)) return false;
return ItemBind.hasLegsBound(bind); return BindModeHelper.hasLegsBound(bind);
} }
} }

View File

@@ -1,7 +1,19 @@
package com.tiedup.remake.util; package com.tiedup.remake.util;
import com.tiedup.remake.blocks.entity.IBondageItemHolder; import com.tiedup.remake.blocks.entity.IBondageItemHolder;
import com.tiedup.remake.items.base.*; import com.tiedup.remake.items.base.ItemBind;
import com.tiedup.remake.items.base.ItemBlindfold;
import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.items.base.ItemEarplugs;
import com.tiedup.remake.items.base.ItemGag;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.BindModeHelper;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.v2.bondage.component.ComponentType;
import com.tiedup.remake.v2.bondage.component.GaggingComponent;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemDefinition;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemRegistry;
import java.util.List; import java.util.List;
import net.minecraft.ChatFormatting; import net.minecraft.ChatFormatting;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
@@ -41,36 +53,32 @@ public final class BondageItemLoaderUtility {
ItemStack stack, ItemStack stack,
Player player Player player
) { ) {
if (stack.getItem() instanceof ItemBind && holder.getBind().isEmpty()) { if ((stack.getItem() instanceof ItemBind || BindModeHelper.isBindItem(stack)) && holder.getBind().isEmpty()) {
holder.setBind(stack.copyWithCount(1)); holder.setBind(stack.copyWithCount(1));
if (!player.isCreative()) stack.shrink(1); if (!player.isCreative()) stack.shrink(1);
return true; return true;
} }
if (stack.getItem() instanceof ItemGag && holder.getGag().isEmpty()) { if ((stack.getItem() instanceof ItemGag
|| DataDrivenBondageItem.getComponent(stack, ComponentType.GAGGING, GaggingComponent.class) != null)
&& holder.getGag().isEmpty()) {
holder.setGag(stack.copyWithCount(1)); holder.setGag(stack.copyWithCount(1));
if (!player.isCreative()) stack.shrink(1); if (!player.isCreative()) stack.shrink(1);
return true; return true;
} }
if ( if ((stack.getItem() instanceof ItemBlindfold || isDataDrivenForRegion(stack, BodyRegionV2.EYES))
stack.getItem() instanceof ItemBlindfold && && holder.getBlindfold().isEmpty()) {
holder.getBlindfold().isEmpty()
) {
holder.setBlindfold(stack.copyWithCount(1)); holder.setBlindfold(stack.copyWithCount(1));
if (!player.isCreative()) stack.shrink(1); if (!player.isCreative()) stack.shrink(1);
return true; return true;
} }
if ( if ((stack.getItem() instanceof ItemEarplugs || isDataDrivenForRegion(stack, BodyRegionV2.EARS))
stack.getItem() instanceof ItemEarplugs && && holder.getEarplugs().isEmpty()) {
holder.getEarplugs().isEmpty()
) {
holder.setEarplugs(stack.copyWithCount(1)); holder.setEarplugs(stack.copyWithCount(1));
if (!player.isCreative()) stack.shrink(1); if (!player.isCreative()) stack.shrink(1);
return true; return true;
} }
if ( if ((stack.getItem() instanceof ItemCollar || CollarHelper.isCollar(stack))
stack.getItem() instanceof ItemCollar && && holder.getCollar().isEmpty()) {
holder.getCollar().isEmpty()
) {
holder.setCollar(stack.copyWithCount(1)); holder.setCollar(stack.copyWithCount(1));
if (!player.isCreative()) stack.shrink(1); if (!player.isCreative()) stack.shrink(1);
return true; return true;
@@ -87,13 +95,28 @@ public final class BondageItemLoaderUtility {
* @return true if the item can be loaded into a bondage item holder * @return true if the item can be loaded into a bondage item holder
*/ */
public static boolean isLoadableBondageItem(ItemStack stack) { public static boolean isLoadableBondageItem(ItemStack stack) {
return ( if (stack.isEmpty()) return false;
(stack.getItem() instanceof ItemBind) || // V1 item types
(stack.getItem() instanceof ItemGag) || if (stack.getItem() instanceof ItemBind
(stack.getItem() instanceof ItemBlindfold) || || stack.getItem() instanceof ItemGag
(stack.getItem() instanceof ItemEarplugs) || || stack.getItem() instanceof ItemBlindfold
(stack.getItem() instanceof ItemCollar) || stack.getItem() instanceof ItemEarplugs
); || stack.getItem() instanceof ItemCollar) {
return true;
}
// V2 data-driven items: bind, gag, blindfold, earplugs, collar
if (BindModeHelper.isBindItem(stack)) return true;
if (DataDrivenBondageItem.getComponent(stack, ComponentType.GAGGING, GaggingComponent.class) != null) return true;
if (isDataDrivenForRegion(stack, BodyRegionV2.EYES)) return true;
if (isDataDrivenForRegion(stack, BodyRegionV2.EARS)) return true;
if (CollarHelper.isCollar(stack)) return true;
return false;
}
/** Check if a stack is a data-driven item occupying the given body region. */
private static boolean isDataDrivenForRegion(ItemStack stack, BodyRegionV2 region) {
DataDrivenItemDefinition def = DataDrivenItemRegistry.get(stack);
return def != null && def.occupiedRegions().contains(region);
} }
/** /**

View File

@@ -2,9 +2,8 @@ package com.tiedup.remake.util;
import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.base.IHasResistance; import com.tiedup.remake.items.base.IHasResistance;
import com.tiedup.remake.items.base.ItemBind;
import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.state.IBondageState; import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
import java.util.UUID; import java.util.UUID;
import net.minecraft.ChatFormatting; import net.minecraft.ChatFormatting;
@@ -60,7 +59,7 @@ public final class RestraintApplicator {
} }
// Tighten existing bind - reset resistance to max // Tighten existing bind - reset resistance to max
if (currentBind.getItem() instanceof ItemBind bindItem) { if (currentBind.getItem() instanceof IHasResistance bindItem) {
int maxResistance = bindItem.getBaseResistance(target); int maxResistance = bindItem.getBaseResistance(target);
state.setCurrentBindResistance(maxResistance); state.setCurrentBindResistance(maxResistance);
return true; return true;
@@ -286,11 +285,8 @@ public final class RestraintApplicator {
ItemStack collarCopy = collar.copy(); ItemStack collarCopy = collar.copy();
// Add owner if provided // Add owner if provided
if ( if (ownerUUID != null && CollarHelper.isCollar(collarCopy)) {
ownerUUID != null && CollarHelper.addOwner(
collarCopy.getItem() instanceof ItemCollar collarItem
) {
collarItem.addOwner(
collarCopy, collarCopy,
ownerUUID, ownerUUID,
ownerName != null ? ownerName : "Unknown" ownerName != null ? ownerName : "Unknown"

View File

@@ -1,7 +1,8 @@
package com.tiedup.remake.v2.bondage.movement; package com.tiedup.remake.v2.bondage.movement;
import com.tiedup.remake.items.base.ItemBind;
import com.tiedup.remake.items.base.PoseType; import com.tiedup.remake.items.base.PoseType;
import com.tiedup.remake.v2.bondage.BindModeHelper;
import com.tiedup.remake.v2.bondage.PoseTypeHelper;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemDefinition; import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemDefinition;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemRegistry; import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemRegistry;
@@ -19,7 +20,7 @@ import org.jetbrains.annotations.Nullable;
* default speed/jump values. Modifiers from lower-severity items are ignored.</p> * default speed/jump values. Modifiers from lower-severity items are ignored.</p>
* *
* <h3>V1 Compatibility (H6 fix)</h3> * <h3>V1 Compatibility (H6 fix)</h3>
* <p>V1 items ({@link ItemBind}) stored in V2 capability * <p>V1 items (ItemBind) stored in V2 capability
* do not have data-driven definitions. This resolver provides a fallback that * do not have data-driven definitions. This resolver provides a fallback that
* maps V1 bind mode + pose type to a {@link MovementStyle} with speed values matching * maps V1 bind mode + pose type to a {@link MovementStyle} with speed values matching
* the original V1 behavior, preventing double stacking between the legacy * the original V1 behavior, preventing double stacking between the legacy
@@ -122,7 +123,7 @@ public final class MovementStyleResolver {
// ==================== V1 Fallback ==================== // ==================== V1 Fallback ====================
/** /**
* Attempt to derive a movement style from a V1 {@link ItemBind} item. * Attempt to derive a movement style from a V1 bind item.
* *
* <p>Only items with legs bound produce a movement style. The mapping preserves * <p>Only items with legs bound produce a movement style. The mapping preserves
* the original V1 speed values:</p> * the original V1 speed values:</p>
@@ -137,15 +138,15 @@ public final class MovementStyleResolver {
*/ */
@Nullable @Nullable
private static V1Fallback resolveV1Fallback(ItemStack stack) { private static V1Fallback resolveV1Fallback(ItemStack stack) {
if (!(stack.getItem() instanceof ItemBind bindItem)) { if (!BindModeHelper.isBindItem(stack)) {
return null; return null;
} }
if (!ItemBind.hasLegsBound(stack)) { if (!BindModeHelper.hasLegsBound(stack)) {
return null; return null;
} }
PoseType poseType = bindItem.getPoseType(); PoseType poseType = PoseTypeHelper.getPoseType(stack);
return switch (poseType) { return switch (poseType) {
case WRAP, LATEX_SACK -> new V1Fallback( case WRAP, LATEX_SACK -> new V1Fallback(

View File

@@ -1,8 +1,8 @@
package com.tiedup.remake.v2.furniture.network; package com.tiedup.remake.v2.furniture.network;
import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.network.PacketRateLimiter; import com.tiedup.remake.network.PacketRateLimiter;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.state.IBondageState; import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.util.KidnappedHelper; import com.tiedup.remake.util.KidnappedHelper;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
@@ -28,7 +28,7 @@ import net.minecraftforge.network.NetworkEvent;
* Client-to-server packet: master forces a captive onto a furniture seat. * Client-to-server packet: master forces a captive onto a furniture seat.
* *
* <p>The sender must own the captive's collar (verified via * <p>The sender must own the captive's collar (verified via
* {@link ItemCollar#isOwner(ItemStack, net.minecraft.world.entity.player.Player)}), * {@link CollarHelper#isOwner(ItemStack, net.minecraft.world.entity.player.Player)}),
* the captive must be alive and within 5 blocks of both sender and furniture, * the captive must be alive and within 5 blocks of both sender and furniture,
* and the furniture must have an available seat.</p> * and the furniture must have an available seat.</p>
* *
@@ -132,10 +132,7 @@ public class PacketFurnitureForcemount {
} }
ItemStack collarStack = captiveState.getEquipment(BodyRegionV2.NECK); ItemStack collarStack = captiveState.getEquipment(BodyRegionV2.NECK);
if ( if (!CollarHelper.isCollar(collarStack)) {
collarStack.isEmpty() ||
!(collarStack.getItem() instanceof ItemCollar collar)
) {
TiedUpMod.LOGGER.debug( TiedUpMod.LOGGER.debug(
"[PacketFurnitureForcemount] Invalid collar item on captive" "[PacketFurnitureForcemount] Invalid collar item on captive"
); );
@@ -143,7 +140,7 @@ public class PacketFurnitureForcemount {
} }
// Collar must be owned by sender (or sender has admin permission) // Collar must be owned by sender (or sender has admin permission)
if (!collar.isOwner(collarStack, sender) && !sender.hasPermissions(2)) { if (!CollarHelper.isOwner(collarStack, sender) && !sender.hasPermissions(2)) {
TiedUpMod.LOGGER.debug( TiedUpMod.LOGGER.debug(
"[PacketFurnitureForcemount] {} is not the collar owner of {}", "[PacketFurnitureForcemount] {} is not the collar owner of {}",
sender.getName().getString(), sender.getName().getString(),

View File

@@ -3,13 +3,15 @@ package com.tiedup.remake.worldgen;
import com.tiedup.remake.blocks.ModBlocks; import com.tiedup.remake.blocks.ModBlocks;
import com.tiedup.remake.blocks.entity.TrappedChestBlockEntity; import com.tiedup.remake.blocks.entity.TrappedChestBlockEntity;
import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.ModItems;
import com.tiedup.remake.items.base.BindVariant;
import com.tiedup.remake.items.base.BlindfoldVariant;
import com.tiedup.remake.items.base.GagVariant;
import com.tiedup.remake.v2.V2Blocks; import com.tiedup.remake.v2.V2Blocks;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemDefinition;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemRegistry;
import com.tiedup.remake.v2.blocks.PetCageBlock; import com.tiedup.remake.v2.blocks.PetCageBlock;
import com.tiedup.remake.v2.blocks.PetCagePartBlock; import com.tiedup.remake.v2.blocks.PetCagePartBlock;
import java.util.List;
import java.util.stream.Collectors;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction; import net.minecraft.core.Direction;
@@ -540,34 +542,35 @@ public class HangingCagePiece extends StructurePiece {
BlockEntity be = level.getBlockEntity(chestPos); BlockEntity be = level.getBlockEntity(chestPos);
if (be instanceof TrappedChestBlockEntity trappedChest) { if (be instanceof TrappedChestBlockEntity trappedChest) {
// Random bind // Random bind from data-driven ARMS items
BindVariant[] bindVariants = BindVariant.values(); List<DataDrivenItemDefinition> binds = DataDrivenItemRegistry.getAll().stream()
BindVariant chosenBind = bindVariants[random.nextInt( .filter(d -> d.occupiedRegions().contains(BodyRegionV2.ARMS))
bindVariants.length .collect(Collectors.toList());
)]; if (!binds.isEmpty()) {
ItemStack bindStack = new ItemStack(ModItems.getBind(chosenBind)); DataDrivenItemDefinition chosenBind = binds.get(random.nextInt(binds.size()));
trappedChest.setBind(bindStack); trappedChest.setBind(DataDrivenBondageItem.createStack(chosenBind.id()));
// Random gag (50% chance)
if (random.nextFloat() < 0.50f) {
GagVariant[] gagVariants = GagVariant.values();
GagVariant chosenGag = gagVariants[random.nextInt(
gagVariants.length
)];
ItemStack gagStack = new ItemStack(ModItems.getGag(chosenGag));
trappedChest.setGag(gagStack);
} }
// Random blindfold (30% chance) // Random gag from data-driven MOUTH items (50% chance)
if (random.nextFloat() < 0.50f) {
List<DataDrivenItemDefinition> gags = DataDrivenItemRegistry.getAll().stream()
.filter(d -> d.occupiedRegions().contains(BodyRegionV2.MOUTH))
.collect(Collectors.toList());
if (!gags.isEmpty()) {
DataDrivenItemDefinition chosenGag = gags.get(random.nextInt(gags.size()));
trappedChest.setGag(DataDrivenBondageItem.createStack(chosenGag.id()));
}
}
// Random blindfold from data-driven EYES items (30% chance)
if (random.nextFloat() < 0.30f) { if (random.nextFloat() < 0.30f) {
BlindfoldVariant[] bfVariants = BlindfoldVariant.values(); List<DataDrivenItemDefinition> blindfolds = DataDrivenItemRegistry.getAll().stream()
BlindfoldVariant chosenBf = bfVariants[random.nextInt( .filter(d -> d.occupiedRegions().contains(BodyRegionV2.EYES))
bfVariants.length .collect(Collectors.toList());
)]; if (!blindfolds.isEmpty()) {
ItemStack bfStack = new ItemStack( DataDrivenItemDefinition chosenBf = blindfolds.get(random.nextInt(blindfolds.size()));
ModItems.getBlindfold(chosenBf) trappedChest.setBlindfold(DataDrivenBondageItem.createStack(chosenBf.id()));
); }
trappedChest.setBlindfold(bfStack);
} }
} }
} }
@@ -629,22 +632,26 @@ public class HangingCagePiece extends StructurePiece {
entityTag.putUUID("UUID", java.util.UUID.randomUUID()); entityTag.putUUID("UUID", java.util.UUID.randomUUID());
// Random bind item — the damsel spawns already restrained // Random bind item — the damsel spawns already restrained
BindVariant[] variants = BindVariant.values(); List<DataDrivenItemDefinition> bindDefs = DataDrivenItemRegistry.getAll().stream()
BindVariant chosenBind = variants[random.nextInt(variants.length)]; .filter(d -> d.occupiedRegions().contains(BodyRegionV2.ARMS))
ItemStack bindStack = new ItemStack(ModItems.getBind(chosenBind)); .collect(Collectors.toList());
bindStack.getOrCreateTag().putString("bindMode", "full"); if (!bindDefs.isEmpty()) {
entityTag.put("Bind", bindStack.save(new CompoundTag())); DataDrivenItemDefinition chosenBind = bindDefs.get(random.nextInt(bindDefs.size()));
ItemStack bindStack = DataDrivenBondageItem.createStack(chosenBind.id());
bindStack.getOrCreateTag().putString("bindMode", "full");
entityTag.put("Bind", bindStack.save(new CompoundTag()));
// Add directly to chunk's pending entity list // Add directly to chunk's pending entity list
ChunkAccess chunk = level.getChunk(masterPos); ChunkAccess chunk = level.getChunk(masterPos);
if (chunk instanceof ProtoChunk protoChunk) { if (chunk instanceof ProtoChunk protoChunk) {
protoChunk.addEntity(entityTag); protoChunk.addEntity(entityTag);
TiedUpMod.LOGGER.info( TiedUpMod.LOGGER.info(
"[HangingCage] Scheduled {} damsel with {} at {}", "[HangingCage] Scheduled {} damsel with {} at {}",
shiny ? "shiny" : "regular", shiny ? "shiny" : "regular",
chosenBind.getRegistryName(), chosenBind.id(),
masterPos.toShortString() masterPos.toShortString()
); );
}
} }
} }
} }