feat(D-01/D): V1 cleanup — delete 28 files, ~5400 lines removed
D1: ThreadLocal alert suppression moved from ItemCollar to CollarHelper.
onCollarRemoved() logic (kidnapper alert) moved to CollarHelper.
D2+D3: Deleted 17 V1 item classes + 4 V1-only interfaces:
ItemBind, ItemGag, ItemBlindfold, ItemCollar, ItemEarplugs, ItemMittens,
ItemColor, ItemClassicCollar, ItemShockCollar, ItemShockCollarAuto,
ItemGpsCollar, ItemChokeCollar, ItemHood, ItemMedicalGag,
IBondageItem, IHasGaggingEffect, IHasBlindingEffect, IAdjustable
D4: KidnapperTheme/KidnapperItemSelector/DispenserBehaviors migrated
from variant enums to string-based DataDrivenItemRegistry IDs.
D5: Deleted 11 variant enums + Generic* factories + ItemBallGag3D:
BindVariant, GagVariant, BlindfoldVariant, EarplugsVariant, MittensVariant,
GenericBind, GenericGag, GenericBlindfold, GenericEarplugs, GenericMittens
D6: ModItems cleaned — all V1 bondage registrations removed.
D7: ModCreativeTabs rewritten — iterates DataDrivenItemRegistry.
D8+D9: All V2 helpers cleaned (V1 fallbacks removed), orphan imports removed.
Zero V1 bondage code references remain (only Javadoc comments).
All bondage items are now data-driven via 47 JSON definitions.
This commit is contained in:
413
docs/AUDIT.md
Normal file
413
docs/AUDIT.md
Normal 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. |
|
||||||
284
docs/plans/D01-branch-A-bridge-utilities.md
Normal file
284
docs/plans/D01-branch-A-bridge-utilities.md
Normal 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
|
||||||
128
docs/plans/D01-branch-B-json-definitions.md
Normal file
128
docs/plans/D01-branch-B-json-definitions.md
Normal 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
|
||||||
107
docs/plans/D01-branch-C-consumer-migration.md
Normal file
107
docs/plans/D01-branch-C-consumer-migration.md
Normal 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
|
||||||
188
docs/plans/D01-branch-D-v1-cleanup.md
Normal file
188
docs/plans/D01-branch-D-v1-cleanup.md
Normal 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
|
||||||
183
docs/plans/D01-branch-E-resistance-rework.md
Normal file
183
docs/plans/D01-branch-E-resistance-rework.md
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
# 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()`.
|
||||||
|
|
||||||
|
### B4. PlayerShockCollar ignore complètement les V2 collars
|
||||||
|
|
||||||
|
`checkAutoShockCollar()` dispatche exclusivement sur `instanceof ItemShockCollarAuto` et `instanceof ItemGpsCollar`. Les V2 data-driven collars avec ShockComponent ou GpsComponent ne déclenchent jamais les auto-shocks ni l'enforcement de zones GPS.
|
||||||
|
|
||||||
|
**Fichier :** `state/components/PlayerShockCollar.java` lignes 139-189
|
||||||
|
**Fix :** Utiliser `CollarHelper.canShock()`, `CollarHelper.getShockInterval()`, `CollarHelper.hasGPS()` pour la détection, avec fallback V1 pour les méthodes V1-specific (`getSafeSpots()`).
|
||||||
|
|
||||||
|
### B5. EntityKidnapperMerchant.remove() memory leak
|
||||||
|
|
||||||
|
`remove()` appelle `tradingPlayers.clear()` mais ne nettoie PAS la `playerToMerchant` ConcurrentHashMap statique. Entrées stales accumulées sur les serveurs long-running.
|
||||||
|
|
||||||
|
**Fichier :** `entities/EntityKidnapperMerchant.java` ligne 966-981
|
||||||
|
**Fix :** Itérer `tradingPlayers` et appeler `playerToMerchant.remove(uuid)` avant le clear.
|
||||||
|
|
||||||
|
### B6. Timer division potentiellement inversée (auto-shock)
|
||||||
|
|
||||||
|
`PlayerShockCollar.java` lignes 153-155 : `collarShock.getInterval() / GameConstants.TICKS_PER_SECOND`. Si Timer attend des ticks, la division réduit l'intervalle de 20x (shock toutes les 0.25s au lieu de 5s).
|
||||||
|
|
||||||
|
**Fichier :** `state/components/PlayerShockCollar.java` lignes 153-155 et 179-182
|
||||||
|
**Fix :** Vérifier le contrat du constructeur `Timer`. Si il attend des ticks, supprimer la division.
|
||||||
|
|
||||||
|
### B7. StruggleState.isItemLocked() dead code
|
||||||
|
|
||||||
|
`StruggleState.struggle()` ne call JAMAIS `isItemLocked()`. Le penalty x10 pour les items padlockés n'est jamais appliqué.
|
||||||
|
|
||||||
|
**Fichier :** `state/struggle/StruggleState.java` ligne 53-133
|
||||||
|
**Fix :** Inclus dans le rework E2 (nouveau modèle resistance/lock).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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
|
||||||
869
docs/superpowers/plans/2026-04-13-d01-phase1-component-system.md
Normal file
869
docs/superpowers/plans/2026-04-13-d01-phase1-component-system.md
Normal 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."
|
||||||
|
```
|
||||||
@@ -1,10 +1,5 @@
|
|||||||
package com.tiedup.remake.blocks.entity;
|
package com.tiedup.remake.blocks.entity;
|
||||||
|
|
||||||
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.BodyRegionV2;
|
||||||
import com.tiedup.remake.v2.bondage.BindModeHelper;
|
import com.tiedup.remake.v2.bondage.BindModeHelper;
|
||||||
@@ -199,45 +194,43 @@ public abstract class BondageItemBlockEntity
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void readBondageData(CompoundTag tag) {
|
public void readBondageData(CompoundTag tag) {
|
||||||
// Read bind with type validation (V1 ItemBind or V2 ARMS-region item)
|
// Read bind with type validation (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 (!bindStack.isEmpty() && (bindStack.getItem() instanceof ItemBind || BindModeHelper.isBindItem(bindStack))) {
|
if (!bindStack.isEmpty() && BindModeHelper.isBindItem(bindStack)) {
|
||||||
this.bind = bindStack;
|
this.bind = bindStack;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read gag with type validation (V1 ItemGag or V2 GAGGING component)
|
// Read gag with type validation (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()
|
||||||
|| DataDrivenBondageItem.getComponent(gagStack, ComponentType.GAGGING, GaggingComponent.class) != null)) {
|
&& DataDrivenBondageItem.getComponent(gagStack, ComponentType.GAGGING, GaggingComponent.class) != null) {
|
||||||
this.gag = gagStack;
|
this.gag = gagStack;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read blindfold with type validation (V1 ItemBlindfold or V2 EYES-region item)
|
// Read blindfold with type validation (V2 EYES-region item)
|
||||||
if (tag.contains("blindfold")) {
|
if (tag.contains("blindfold")) {
|
||||||
ItemStack blindfoldStack = ItemStack.of(tag.getCompound("blindfold"));
|
ItemStack blindfoldStack = ItemStack.of(tag.getCompound("blindfold"));
|
||||||
if (!blindfoldStack.isEmpty() && (blindfoldStack.getItem() instanceof ItemBlindfold
|
if (!blindfoldStack.isEmpty() && isDataDrivenForRegion(blindfoldStack, BodyRegionV2.EYES)) {
|
||||||
|| isDataDrivenForRegion(blindfoldStack, BodyRegionV2.EYES))) {
|
|
||||||
this.blindfold = blindfoldStack;
|
this.blindfold = blindfoldStack;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read earplugs with type validation (V1 ItemEarplugs or V2 EARS-region item)
|
// Read earplugs with type validation (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 (!earplugsStack.isEmpty() && (earplugsStack.getItem() instanceof ItemEarplugs
|
if (!earplugsStack.isEmpty() && isDataDrivenForRegion(earplugsStack, BodyRegionV2.EARS)) {
|
||||||
|| isDataDrivenForRegion(earplugsStack, BodyRegionV2.EARS))) {
|
|
||||||
this.earplugs = earplugsStack;
|
this.earplugs = earplugsStack;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read collar with type validation (V1 ItemCollar or V2 collar)
|
// Read collar with type validation (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 (!collarStack.isEmpty() && (collarStack.getItem() instanceof ItemCollar || CollarHelper.isCollar(collarStack))) {
|
if (!collarStack.isEmpty() && CollarHelper.isCollar(collarStack)) {
|
||||||
this.collar = collarStack;
|
this.collar = collarStack;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
package com.tiedup.remake.blocks.entity;
|
package com.tiedup.remake.blocks.entity;
|
||||||
|
|
||||||
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.BodyRegionV2;
|
||||||
import com.tiedup.remake.v2.bondage.BindModeHelper;
|
import com.tiedup.remake.v2.bondage.BindModeHelper;
|
||||||
@@ -57,7 +52,7 @@ public class TrappedChestBlockEntity
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setBind(ItemStack stack) {
|
public void setBind(ItemStack stack) {
|
||||||
if (stack.isEmpty() || stack.getItem() instanceof ItemBind || BindModeHelper.isBindItem(stack)) {
|
if (stack.isEmpty() || BindModeHelper.isBindItem(stack)) {
|
||||||
this.bind = stack;
|
this.bind = stack;
|
||||||
setChangedAndSync();
|
setChangedAndSync();
|
||||||
}
|
}
|
||||||
@@ -70,7 +65,7 @@ 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()
|
||||||
|| DataDrivenBondageItem.getComponent(stack, ComponentType.GAGGING, GaggingComponent.class) != null) {
|
|| DataDrivenBondageItem.getComponent(stack, ComponentType.GAGGING, GaggingComponent.class) != null) {
|
||||||
this.gag = stack;
|
this.gag = stack;
|
||||||
setChangedAndSync();
|
setChangedAndSync();
|
||||||
@@ -84,8 +79,7 @@ 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() || isDataDrivenForRegion(stack, BodyRegionV2.EYES)) {
|
||||||
|| isDataDrivenForRegion(stack, BodyRegionV2.EYES)) {
|
|
||||||
this.blindfold = stack;
|
this.blindfold = stack;
|
||||||
setChangedAndSync();
|
setChangedAndSync();
|
||||||
}
|
}
|
||||||
@@ -98,8 +92,7 @@ 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() || isDataDrivenForRegion(stack, BodyRegionV2.EARS)) {
|
||||||
|| isDataDrivenForRegion(stack, BodyRegionV2.EARS)) {
|
|
||||||
this.earplugs = stack;
|
this.earplugs = stack;
|
||||||
setChangedAndSync();
|
setChangedAndSync();
|
||||||
}
|
}
|
||||||
@@ -112,7 +105,7 @@ public class TrappedChestBlockEntity
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setCollar(ItemStack stack) {
|
public void setCollar(ItemStack stack) {
|
||||||
if (stack.isEmpty() || stack.getItem() instanceof ItemCollar || CollarHelper.isCollar(stack)) {
|
if (stack.isEmpty() || CollarHelper.isCollar(stack)) {
|
||||||
this.collar = stack;
|
this.collar = stack;
|
||||||
setChangedAndSync();
|
setChangedAndSync();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.tiedup.remake.client.events;
|
package com.tiedup.remake.client.events;
|
||||||
|
|
||||||
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.BindModeHelper;
|
||||||
@@ -166,8 +165,8 @@ public class SelfBondageInputHandler {
|
|||||||
private static boolean isSelfBondageItem(ItemStack stack) {
|
private static boolean isSelfBondageItem(ItemStack stack) {
|
||||||
if (stack.isEmpty()) return false;
|
if (stack.isEmpty()) return false;
|
||||||
|
|
||||||
// Collar cannot be self-equipped (V1 collar guard + V2 ownership component)
|
// Collar cannot be self-equipped (V2 ownership component)
|
||||||
if (stack.getItem() instanceof ItemCollar || CollarHelper.isCollar(stack)) {
|
if (CollarHelper.isCollar(stack)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ 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.v2.bondage.CollarHelper;
|
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;
|
||||||
@@ -370,9 +369,8 @@ public class SlaveEntryWidget
|
|||||||
// GPS zone status (right of health)
|
// GPS zone status (right of health)
|
||||||
if (hasGPSCollar()) {
|
if (hasGPSCollar()) {
|
||||||
ItemStack collarStack = slave.getEquipment(BodyRegionV2.NECK);
|
ItemStack collarStack = slave.getEquipment(BodyRegionV2.NECK);
|
||||||
if (collarStack.getItem() instanceof ItemGpsCollar gps) {
|
if (CollarHelper.hasGPS(collarStack)) {
|
||||||
boolean inSafeZone = isInAnySafeZone(
|
boolean inSafeZone = isInAnySafeZone(
|
||||||
gps,
|
|
||||||
collarStack,
|
collarStack,
|
||||||
entity
|
entity
|
||||||
);
|
);
|
||||||
@@ -570,17 +568,25 @@ public class SlaveEntryWidget
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean isInAnySafeZone(
|
private boolean isInAnySafeZone(
|
||||||
ItemGpsCollar gps,
|
|
||||||
ItemStack collarStack,
|
ItemStack collarStack,
|
||||||
LivingEntity entity
|
LivingEntity entity
|
||||||
) {
|
) {
|
||||||
if (!CollarHelper.isActive(collarStack)) return true;
|
if (!CollarHelper.isActive(collarStack)) return true;
|
||||||
|
|
||||||
var safeSpots = gps.getSafeSpots(collarStack);
|
// Read safe spots from NBT
|
||||||
|
net.minecraft.nbt.CompoundTag tag = collarStack.getTag();
|
||||||
|
if (tag == null || !tag.contains("safeSpots", net.minecraft.nbt.Tag.TAG_LIST)) return true;
|
||||||
|
net.minecraft.nbt.ListTag safeSpots = tag.getList("safeSpots", net.minecraft.nbt.Tag.TAG_COMPOUND);
|
||||||
if (safeSpots.isEmpty()) return true;
|
if (safeSpots.isEmpty()) return true;
|
||||||
|
|
||||||
for (var spot : safeSpots) {
|
for (int i = 0; i < safeSpots.size(); i++) {
|
||||||
if (spot.isInside(entity)) {
|
net.minecraft.nbt.CompoundTag spot = safeSpots.getCompound(i);
|
||||||
|
double x = spot.getDouble("x");
|
||||||
|
double y = spot.getDouble("y");
|
||||||
|
double z = spot.getDouble("z");
|
||||||
|
int radius = spot.contains("radius") ? spot.getInt("radius") : 50;
|
||||||
|
double dist = entity.distanceToSqr(x, y, z);
|
||||||
|
if (dist <= (double) radius * radius) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,16 +93,19 @@ public class KidnapSetCommand {
|
|||||||
given += giveDataDrivenItems(player, "classic_blindfold", 4);
|
given += giveDataDrivenItems(player, "classic_blindfold", 4);
|
||||||
given += giveDataDrivenItems(player, "blindfold_mask", 2);
|
given += giveDataDrivenItems(player, "blindfold_mask", 2);
|
||||||
|
|
||||||
// Collars
|
// Collars (data-driven)
|
||||||
given += giveItem(
|
ItemStack classicCollars = com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem.createStack(
|
||||||
player,
|
new net.minecraft.resources.ResourceLocation("tiedup", "classic_collar"));
|
||||||
new ItemStack(ModItems.CLASSIC_COLLAR.get(), 4)
|
classicCollars.setCount(4);
|
||||||
);
|
given += giveItem(player, classicCollars);
|
||||||
given += giveItem(
|
ItemStack shockCollars = com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem.createStack(
|
||||||
player,
|
new net.minecraft.resources.ResourceLocation("tiedup", "shock_collar"));
|
||||||
new ItemStack(ModItems.SHOCK_COLLAR.get(), 2)
|
shockCollars.setCount(2);
|
||||||
);
|
given += giveItem(player, shockCollars);
|
||||||
given += giveItem(player, new ItemStack(ModItems.GPS_COLLAR.get(), 2));
|
ItemStack gpsCollars = com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem.createStack(
|
||||||
|
new net.minecraft.resources.ResourceLocation("tiedup", "gps_collar"));
|
||||||
|
gpsCollars.setCount(2);
|
||||||
|
given += giveItem(player, gpsCollars);
|
||||||
|
|
||||||
// Tools
|
// Tools
|
||||||
given += giveItem(
|
given += giveItem(
|
||||||
|
|||||||
@@ -603,7 +603,7 @@ public class NPCCommand {
|
|||||||
|
|
||||||
npc.equip(
|
npc.equip(
|
||||||
BodyRegionV2.NECK,
|
BodyRegionV2.NECK,
|
||||||
new ItemStack(ModItems.CLASSIC_COLLAR.get())
|
DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_collar"))
|
||||||
);
|
);
|
||||||
context
|
context
|
||||||
.getSource()
|
.getSource()
|
||||||
@@ -658,7 +658,7 @@ public class NPCCommand {
|
|||||||
DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "cloth_gag")),
|
DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "cloth_gag")),
|
||||||
DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_blindfold")),
|
DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_blindfold")),
|
||||||
DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_earplugs")),
|
DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_earplugs")),
|
||||||
new ItemStack(ModItems.CLASSIC_COLLAR.get()),
|
DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_collar")),
|
||||||
ItemStack.EMPTY // No clothes
|
ItemStack.EMPTY // No clothes
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -493,7 +493,7 @@ public class BondageSubCommand {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemStack collar = new ItemStack(ModItems.CLASSIC_COLLAR.get());
|
ItemStack collar = com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem.createStack(new net.minecraft.resources.ResourceLocation("tiedup", "classic_collar"));
|
||||||
|
|
||||||
if (context.getSource().getEntity() instanceof ServerPlayer executor) {
|
if (context.getSource().getEntity() instanceof ServerPlayer executor) {
|
||||||
CollarHelper.addOwner(collar, executor);
|
CollarHelper.addOwner(collar, executor);
|
||||||
@@ -1078,7 +1078,7 @@ public class BondageSubCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!state.hasCollar()) {
|
if (!state.hasCollar()) {
|
||||||
ItemStack collar = new ItemStack(ModItems.CLASSIC_COLLAR.get());
|
ItemStack collar = com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem.createStack(new net.minecraft.resources.ResourceLocation("tiedup", "classic_collar"));
|
||||||
if (
|
if (
|
||||||
context.getSource().getEntity() instanceof ServerPlayer executor
|
context.getSource().getEntity() instanceof ServerPlayer executor
|
||||||
) {
|
) {
|
||||||
@@ -1163,7 +1163,7 @@ public class BondageSubCommand {
|
|||||||
state.putBlindfoldOn(blindfold);
|
state.putBlindfoldOn(blindfold);
|
||||||
}
|
}
|
||||||
if (!state.hasCollar()) {
|
if (!state.hasCollar()) {
|
||||||
ItemStack collar = new ItemStack(ModItems.CLASSIC_COLLAR.get());
|
ItemStack collar = com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem.createStack(new net.minecraft.resources.ResourceLocation("tiedup", "classic_collar"));
|
||||||
if (
|
if (
|
||||||
context.getSource().getEntity() instanceof ServerPlayer executor
|
context.getSource().getEntity() instanceof ServerPlayer executor
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import com.tiedup.remake.v2.bondage.component.GaggingComponent;
|
|||||||
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
|
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.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;
|
||||||
@@ -280,16 +279,14 @@ public class MCAKidnappedAdapter implements IRestrainable {
|
|||||||
public boolean hasGaggingEffect() {
|
public boolean hasGaggingEffect() {
|
||||||
ItemStack gag = cap.getGag();
|
ItemStack gag = cap.getGag();
|
||||||
if (gag.isEmpty()) return false;
|
if (gag.isEmpty()) return false;
|
||||||
if (DataDrivenBondageItem.getComponent(gag, ComponentType.GAGGING, GaggingComponent.class) != null) return true;
|
return DataDrivenBondageItem.getComponent(gag, ComponentType.GAGGING, GaggingComponent.class) != null;
|
||||||
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();
|
||||||
if (blindfold.isEmpty()) return false;
|
if (blindfold.isEmpty()) return false;
|
||||||
if (DataDrivenBondageItem.getComponent(blindfold, ComponentType.BLINDING, BlindingComponent.class) != null) return true;
|
return DataDrivenBondageItem.getComponent(blindfold, ComponentType.BLINDING, BlindingComponent.class) != null;
|
||||||
return blindfold.getItem() instanceof com.tiedup.remake.items.base.IHasBlindingEffect;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package com.tiedup.remake.dialogue;
|
|||||||
import static com.tiedup.remake.util.GameConstants.*;
|
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.state.IBondageState;
|
import com.tiedup.remake.state.IBondageState;
|
||||||
import com.tiedup.remake.v2.bondage.component.ComponentType;
|
import com.tiedup.remake.v2.bondage.component.ComponentType;
|
||||||
import com.tiedup.remake.v2.bondage.component.GaggingComponent;
|
import com.tiedup.remake.v2.bondage.component.GaggingComponent;
|
||||||
@@ -66,8 +65,6 @@ public class GagTalkManager {
|
|||||||
gagStack, ComponentType.GAGGING, GaggingComponent.class);
|
gagStack, ComponentType.GAGGING, GaggingComponent.class);
|
||||||
if (gaggingComp != null && gaggingComp.getMaterial() != null) {
|
if (gaggingComp != null && gaggingComp.getMaterial() != null) {
|
||||||
material = gaggingComp.getMaterial();
|
material = gaggingComp.getMaterial();
|
||||||
} else if (gagStack.getItem() instanceof ItemGag gag) {
|
|
||||||
material = gag.getGagMaterial();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. EFFET DE SUFFOCATION (Si message trop long)
|
// 1. EFFET DE SUFFOCATION (Si message trop long)
|
||||||
@@ -528,8 +525,6 @@ public class GagTalkManager {
|
|||||||
gagStack, ComponentType.GAGGING, GaggingComponent.class);
|
gagStack, ComponentType.GAGGING, GaggingComponent.class);
|
||||||
if (comp != null && comp.getMaterial() != null) {
|
if (comp != null && comp.getMaterial() != null) {
|
||||||
material = comp.getMaterial();
|
material = comp.getMaterial();
|
||||||
} else if (gagStack.getItem() instanceof ItemGag gag) {
|
|
||||||
material = gag.getGagMaterial();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,16 +2,19 @@ package com.tiedup.remake.dispenser;
|
|||||||
|
|
||||||
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.*;
|
|
||||||
import net.minecraft.world.level.block.DispenserBlock;
|
import net.minecraft.world.level.block.DispenserBlock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registration class for all TiedUp dispenser behaviors.
|
* Registration class for all TiedUp dispenser behaviors.
|
||||||
*
|
*
|
||||||
* Allows dispensers to:
|
* Allows dispensers to:
|
||||||
* - Equip bondage items (binds, gags, blindfolds, collars, earplugs, clothes) on entities
|
* - Equip bondage items (via data-driven V2 system) on entities
|
||||||
* - Shoot rope arrows
|
* - Shoot rope arrows
|
||||||
*
|
*
|
||||||
|
* Note: V1 per-variant dispenser registrations have been removed.
|
||||||
|
* Data-driven bondage items use a single universal dispenser behavior
|
||||||
|
* registered via DataDrivenBondageItem system.
|
||||||
|
*
|
||||||
* Based on original behaviors package from 1.12.2
|
* Based on original behaviors package from 1.12.2
|
||||||
*/
|
*/
|
||||||
public class DispenserBehaviors {
|
public class DispenserBehaviors {
|
||||||
@@ -25,72 +28,17 @@ public class DispenserBehaviors {
|
|||||||
"[DispenserBehaviors] Registering dispenser behaviors..."
|
"[DispenserBehaviors] Registering dispenser behaviors..."
|
||||||
);
|
);
|
||||||
|
|
||||||
registerBindBehaviors();
|
|
||||||
registerGagBehaviors();
|
|
||||||
registerBlindfoldBehaviors();
|
|
||||||
registerCollarBehaviors();
|
|
||||||
registerEarplugsBehaviors();
|
|
||||||
registerClothesBehaviors();
|
registerClothesBehaviors();
|
||||||
registerRopeArrowBehavior();
|
registerRopeArrowBehavior();
|
||||||
|
|
||||||
|
// V2 data-driven bondage items register their own dispenser behaviors
|
||||||
|
// via DataDrivenBondageItem.registerDispenserBehaviors()
|
||||||
|
|
||||||
TiedUpMod.LOGGER.info(
|
TiedUpMod.LOGGER.info(
|
||||||
"[DispenserBehaviors] Dispenser behaviors registered!"
|
"[DispenserBehaviors] Dispenser behaviors registered!"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void registerBindBehaviors() {
|
|
||||||
var behavior = GenericBondageDispenseBehavior.forBind();
|
|
||||||
for (BindVariant variant : BindVariant.values()) {
|
|
||||||
DispenserBlock.registerBehavior(
|
|
||||||
ModItems.getBind(variant),
|
|
||||||
behavior
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void registerGagBehaviors() {
|
|
||||||
var behavior = GenericBondageDispenseBehavior.forGag();
|
|
||||||
for (GagVariant variant : GagVariant.values()) {
|
|
||||||
DispenserBlock.registerBehavior(ModItems.getGag(variant), behavior);
|
|
||||||
}
|
|
||||||
DispenserBlock.registerBehavior(ModItems.MEDICAL_GAG.get(), behavior);
|
|
||||||
DispenserBlock.registerBehavior(ModItems.HOOD.get(), behavior);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void registerBlindfoldBehaviors() {
|
|
||||||
var behavior = GenericBondageDispenseBehavior.forBlindfold();
|
|
||||||
for (BlindfoldVariant variant : BlindfoldVariant.values()) {
|
|
||||||
DispenserBlock.registerBehavior(
|
|
||||||
ModItems.getBlindfold(variant),
|
|
||||||
behavior
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void registerCollarBehaviors() {
|
|
||||||
var behavior = GenericBondageDispenseBehavior.forCollar();
|
|
||||||
DispenserBlock.registerBehavior(
|
|
||||||
ModItems.CLASSIC_COLLAR.get(),
|
|
||||||
behavior
|
|
||||||
);
|
|
||||||
DispenserBlock.registerBehavior(ModItems.SHOCK_COLLAR.get(), behavior);
|
|
||||||
DispenserBlock.registerBehavior(
|
|
||||||
ModItems.SHOCK_COLLAR_AUTO.get(),
|
|
||||||
behavior
|
|
||||||
);
|
|
||||||
DispenserBlock.registerBehavior(ModItems.GPS_COLLAR.get(), behavior);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void registerEarplugsBehaviors() {
|
|
||||||
var behavior = GenericBondageDispenseBehavior.forEarplugs();
|
|
||||||
for (EarplugsVariant variant : EarplugsVariant.values()) {
|
|
||||||
DispenserBlock.registerBehavior(
|
|
||||||
ModItems.getEarplugs(variant),
|
|
||||||
behavior
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void registerClothesBehaviors() {
|
private static void registerClothesBehaviors() {
|
||||||
DispenserBlock.registerBehavior(
|
DispenserBlock.registerBehavior(
|
||||||
ModItems.CLOTHES.get(),
|
ModItems.CLOTHES.get(),
|
||||||
|
|||||||
@@ -1,16 +1,21 @@
|
|||||||
package com.tiedup.remake.dispenser;
|
package com.tiedup.remake.dispenser;
|
||||||
|
|
||||||
import com.tiedup.remake.items.base.*;
|
|
||||||
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.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.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import net.minecraft.world.item.Item;
|
|
||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generic dispenser behavior for equipping bondage items.
|
* Generic dispenser behavior for equipping bondage items.
|
||||||
* Replaces individual BindDispenseBehavior, GagDispenseBehavior, etc.
|
* Uses V2 data-driven item detection instead of V1 class checks.
|
||||||
*
|
*
|
||||||
* Use factory methods to create instances for each bondage type.
|
* Use factory methods to create instances for each bondage type.
|
||||||
*/
|
*/
|
||||||
@@ -18,23 +23,23 @@ public class GenericBondageDispenseBehavior
|
|||||||
extends EquipBondageDispenseBehavior
|
extends EquipBondageDispenseBehavior
|
||||||
{
|
{
|
||||||
|
|
||||||
private final Class<? extends Item> itemClass;
|
private final Predicate<ItemStack> itemCheck;
|
||||||
private final Predicate<IBondageState> canEquipCheck;
|
private final Predicate<IBondageState> canEquipCheck;
|
||||||
private final BiConsumer<IBondageState, ItemStack> equipAction;
|
private final BiConsumer<IBondageState, ItemStack> equipAction;
|
||||||
|
|
||||||
private GenericBondageDispenseBehavior(
|
private GenericBondageDispenseBehavior(
|
||||||
Class<? extends Item> itemClass,
|
Predicate<ItemStack> itemCheck,
|
||||||
Predicate<IBondageState> canEquipCheck,
|
Predicate<IBondageState> canEquipCheck,
|
||||||
BiConsumer<IBondageState, ItemStack> equipAction
|
BiConsumer<IBondageState, ItemStack> equipAction
|
||||||
) {
|
) {
|
||||||
this.itemClass = itemClass;
|
this.itemCheck = itemCheck;
|
||||||
this.canEquipCheck = canEquipCheck;
|
this.canEquipCheck = canEquipCheck;
|
||||||
this.equipAction = equipAction;
|
this.equipAction = equipAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean isValidItem(ItemStack stack) {
|
protected boolean isValidItem(ItemStack stack) {
|
||||||
return !stack.isEmpty() && itemClass.isInstance(stack.getItem());
|
return !stack.isEmpty() && itemCheck.test(stack);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -53,7 +58,7 @@ public class GenericBondageDispenseBehavior
|
|||||||
|
|
||||||
public static GenericBondageDispenseBehavior forBind() {
|
public static GenericBondageDispenseBehavior forBind() {
|
||||||
return new GenericBondageDispenseBehavior(
|
return new GenericBondageDispenseBehavior(
|
||||||
ItemBind.class,
|
BindModeHelper::isBindItem,
|
||||||
state -> !state.isTiedUp(),
|
state -> !state.isTiedUp(),
|
||||||
(s, i) -> s.equip(BodyRegionV2.ARMS, i)
|
(s, i) -> s.equip(BodyRegionV2.ARMS, i)
|
||||||
);
|
);
|
||||||
@@ -61,7 +66,7 @@ public class GenericBondageDispenseBehavior
|
|||||||
|
|
||||||
public static GenericBondageDispenseBehavior forGag() {
|
public static GenericBondageDispenseBehavior forGag() {
|
||||||
return new GenericBondageDispenseBehavior(
|
return new GenericBondageDispenseBehavior(
|
||||||
ItemGag.class,
|
stack -> DataDrivenBondageItem.getComponent(stack, ComponentType.GAGGING, GaggingComponent.class) != null,
|
||||||
state -> !state.isGagged(),
|
state -> !state.isGagged(),
|
||||||
(s, i) -> s.equip(BodyRegionV2.MOUTH, i)
|
(s, i) -> s.equip(BodyRegionV2.MOUTH, i)
|
||||||
);
|
);
|
||||||
@@ -69,7 +74,10 @@ public class GenericBondageDispenseBehavior
|
|||||||
|
|
||||||
public static GenericBondageDispenseBehavior forBlindfold() {
|
public static GenericBondageDispenseBehavior forBlindfold() {
|
||||||
return new GenericBondageDispenseBehavior(
|
return new GenericBondageDispenseBehavior(
|
||||||
ItemBlindfold.class,
|
stack -> {
|
||||||
|
DataDrivenItemDefinition def = DataDrivenItemRegistry.get(stack);
|
||||||
|
return def != null && def.occupiedRegions().contains(BodyRegionV2.EYES);
|
||||||
|
},
|
||||||
state -> !state.isBlindfolded(),
|
state -> !state.isBlindfolded(),
|
||||||
(s, i) -> s.equip(BodyRegionV2.EYES, i)
|
(s, i) -> s.equip(BodyRegionV2.EYES, i)
|
||||||
);
|
);
|
||||||
@@ -77,7 +85,7 @@ public class GenericBondageDispenseBehavior
|
|||||||
|
|
||||||
public static GenericBondageDispenseBehavior forCollar() {
|
public static GenericBondageDispenseBehavior forCollar() {
|
||||||
return new GenericBondageDispenseBehavior(
|
return new GenericBondageDispenseBehavior(
|
||||||
ItemCollar.class,
|
CollarHelper::isCollar,
|
||||||
state -> !state.hasCollar(),
|
state -> !state.hasCollar(),
|
||||||
(s, i) -> s.equip(BodyRegionV2.NECK, i)
|
(s, i) -> s.equip(BodyRegionV2.NECK, i)
|
||||||
);
|
);
|
||||||
@@ -85,7 +93,10 @@ public class GenericBondageDispenseBehavior
|
|||||||
|
|
||||||
public static GenericBondageDispenseBehavior forEarplugs() {
|
public static GenericBondageDispenseBehavior forEarplugs() {
|
||||||
return new GenericBondageDispenseBehavior(
|
return new GenericBondageDispenseBehavior(
|
||||||
ItemEarplugs.class,
|
stack -> {
|
||||||
|
DataDrivenItemDefinition def = DataDrivenItemRegistry.get(stack);
|
||||||
|
return def != null && def.occupiedRegions().contains(BodyRegionV2.EARS);
|
||||||
|
},
|
||||||
state -> !state.hasEarplugs(),
|
state -> !state.hasEarplugs(),
|
||||||
(s, i) -> s.equip(BodyRegionV2.EARS, i)
|
(s, i) -> s.equip(BodyRegionV2.EARS, i)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -796,16 +796,14 @@ 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;
|
||||||
if (DataDrivenBondageItem.getComponent(gag, ComponentType.GAGGING, GaggingComponent.class) != null) return true;
|
return DataDrivenBondageItem.getComponent(gag, ComponentType.GAGGING, GaggingComponent.class) != null;
|
||||||
return gag.getItem() instanceof 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;
|
||||||
if (DataDrivenBondageItem.getComponent(blindfold, ComponentType.BLINDING, BlindingComponent.class) != null) return true;
|
return DataDrivenBondageItem.getComponent(blindfold, ComponentType.BLINDING, BlindingComponent.class) != null;
|
||||||
return blindfold.getItem() instanceof com.tiedup.remake.items.base.IHasBlindingEffect;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1087,9 +1085,7 @@ public abstract class AbstractTiedUpNpc
|
|||||||
public boolean hasShockCollar() {
|
public boolean hasShockCollar() {
|
||||||
ItemStack collar = this.getEquipment(BodyRegionV2.NECK);
|
ItemStack collar = this.getEquipment(BodyRegionV2.NECK);
|
||||||
if (collar.isEmpty()) return false;
|
if (collar.isEmpty()) return false;
|
||||||
return (
|
return com.tiedup.remake.v2.bondage.CollarHelper.canShock(collar);
|
||||||
collar.getItem() instanceof com.tiedup.remake.items.ItemShockCollar
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// BONDAGE SERVICE (delegated to BondageManager)
|
// BONDAGE SERVICE (delegated to BondageManager)
|
||||||
|
|||||||
@@ -616,82 +616,10 @@ 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 — iterate V1 variants, create V2 stacks
|
// All data-driven bondage items (binds, gags, blindfolds, earplugs, mittens, collars, etc.)
|
||||||
for (BindVariant variant : BindVariant.values()) {
|
for (com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemDefinition def :
|
||||||
if (variant.supportsColor()) {
|
com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemRegistry.getAll()) {
|
||||||
for (ItemColor color : ItemColor.values()) {
|
items.add(DataDrivenBondageItem.createStack(def.id()));
|
||||||
if (
|
|
||||||
color != ItemColor.CAUTION && color != ItemColor.CLEAR
|
|
||||||
) {
|
|
||||||
ItemStack stack = DataDrivenBondageItem.createStack(
|
|
||||||
new net.minecraft.resources.ResourceLocation("tiedup", variant.getRegistryName())
|
|
||||||
);
|
|
||||||
KidnapperItemSelector.applyColor(stack, color);
|
|
||||||
items.add(stack);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
items.add(DataDrivenBondageItem.createStack(
|
|
||||||
new net.minecraft.resources.ResourceLocation("tiedup", variant.getRegistryName())
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// All gags
|
|
||||||
for (GagVariant variant : GagVariant.values()) {
|
|
||||||
if (variant.supportsColor()) {
|
|
||||||
for (ItemColor color : ItemColor.values()) {
|
|
||||||
if (
|
|
||||||
variant == GagVariant.TAPE_GAG ||
|
|
||||||
(color != ItemColor.CAUTION && color != ItemColor.CLEAR)
|
|
||||||
) {
|
|
||||||
ItemStack stack = DataDrivenBondageItem.createStack(
|
|
||||||
new net.minecraft.resources.ResourceLocation("tiedup", variant.getRegistryName())
|
|
||||||
);
|
|
||||||
KidnapperItemSelector.applyColor(stack, color);
|
|
||||||
items.add(stack);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
items.add(DataDrivenBondageItem.createStack(
|
|
||||||
new net.minecraft.resources.ResourceLocation("tiedup", variant.getRegistryName())
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// All blindfolds
|
|
||||||
for (BlindfoldVariant variant : BlindfoldVariant.values()) {
|
|
||||||
if (variant.supportsColor()) {
|
|
||||||
for (ItemColor color : ItemColor.values()) {
|
|
||||||
if (
|
|
||||||
color != ItemColor.CAUTION && color != ItemColor.CLEAR
|
|
||||||
) {
|
|
||||||
ItemStack stack = DataDrivenBondageItem.createStack(
|
|
||||||
new net.minecraft.resources.ResourceLocation("tiedup", variant.getRegistryName())
|
|
||||||
);
|
|
||||||
KidnapperItemSelector.applyColor(stack, color);
|
|
||||||
items.add(stack);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
items.add(DataDrivenBondageItem.createStack(
|
|
||||||
new net.minecraft.resources.ResourceLocation("tiedup", variant.getRegistryName())
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Earplugs - no color support
|
|
||||||
for (EarplugsVariant variant : EarplugsVariant.values()) {
|
|
||||||
items.add(DataDrivenBondageItem.createStack(
|
|
||||||
new net.minecraft.resources.ResourceLocation("tiedup", variant.getRegistryName())
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mittens - no color support
|
|
||||||
for (MittensVariant variant : MittensVariant.values()) {
|
|
||||||
items.add(DataDrivenBondageItem.createStack(
|
|
||||||
new net.minecraft.resources.ResourceLocation("tiedup", variant.getRegistryName())
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Knives - no color support
|
// Knives - no color support
|
||||||
@@ -699,16 +627,11 @@ public class EntityKidnapperMerchant extends EntityKidnapperElite {
|
|||||||
items.add(new ItemStack(ModItems.getKnife(variant)));
|
items.add(new ItemStack(ModItems.getKnife(variant)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Complex items
|
// Tools
|
||||||
items.add(new ItemStack(ModItems.CLASSIC_COLLAR.get()));
|
|
||||||
items.add(new ItemStack(ModItems.SHOCK_COLLAR.get()));
|
|
||||||
items.add(new ItemStack(ModItems.GPS_COLLAR.get()));
|
|
||||||
items.add(new ItemStack(ModItems.WHIP.get()));
|
items.add(new ItemStack(ModItems.WHIP.get()));
|
||||||
// BLACKLIST: TASER (too powerful)
|
// BLACKLIST: TASER (too powerful)
|
||||||
// BLACKLIST: LOCKPICK (now in guaranteed utilities)
|
// BLACKLIST: LOCKPICK (now in guaranteed utilities)
|
||||||
// BLACKLIST: MASTER_KEY (too OP - unlocks everything)
|
// BLACKLIST: MASTER_KEY (too OP - unlocks everything)
|
||||||
items.add(new ItemStack(ModItems.MEDICAL_GAG.get()));
|
|
||||||
items.add(new ItemStack(ModItems.HOOD.get()));
|
|
||||||
items.add(new ItemStack(ModItems.CLOTHES.get()));
|
items.add(new ItemStack(ModItems.CLOTHES.get()));
|
||||||
|
|
||||||
return items;
|
return items;
|
||||||
@@ -754,13 +677,13 @@ public class EntityKidnapperMerchant extends EntityKidnapperElite {
|
|||||||
Item i = item.getItem();
|
Item i = item.getItem();
|
||||||
|
|
||||||
// Tier 4: GPS collar
|
// Tier 4: GPS collar
|
||||||
if (i == ModItems.GPS_COLLAR.get()) {
|
if (com.tiedup.remake.v2.bondage.CollarHelper.hasGPS(item)) {
|
||||||
return 4;
|
return 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tier 3: Shock collar, taser, master key
|
// Tier 3: Shock collar, taser, master key
|
||||||
if (
|
if (
|
||||||
i == ModItems.SHOCK_COLLAR.get() ||
|
com.tiedup.remake.v2.bondage.CollarHelper.canShock(item) ||
|
||||||
i == ModItems.TASER.get() ||
|
i == ModItems.TASER.get() ||
|
||||||
i == ModItems.MASTER_KEY.get()
|
i == ModItems.MASTER_KEY.get()
|
||||||
) {
|
) {
|
||||||
@@ -769,11 +692,9 @@ public class EntityKidnapperMerchant extends EntityKidnapperElite {
|
|||||||
|
|
||||||
// Tier 2: Collars, whip, tools, complex items, clothes
|
// Tier 2: Collars, whip, tools, complex items, clothes
|
||||||
if (
|
if (
|
||||||
i == ModItems.CLASSIC_COLLAR.get() ||
|
com.tiedup.remake.v2.bondage.CollarHelper.isCollar(item) ||
|
||||||
i == ModItems.WHIP.get() ||
|
i == ModItems.WHIP.get() ||
|
||||||
i == ModItems.LOCKPICK.get() ||
|
i == ModItems.LOCKPICK.get() ||
|
||||||
i == ModItems.MEDICAL_GAG.get() ||
|
|
||||||
i == ModItems.HOOD.get() ||
|
|
||||||
i instanceof GenericClothes
|
i instanceof GenericClothes
|
||||||
) {
|
) {
|
||||||
return 2;
|
return 2;
|
||||||
|
|||||||
@@ -115,7 +115,8 @@ public class KidnapperCaptureEquipment {
|
|||||||
@Nullable
|
@Nullable
|
||||||
public ItemStack getCollarItem() {
|
public ItemStack getCollarItem() {
|
||||||
// Kidnappers always have a shock collar to mark their captives
|
// Kidnappers always have a shock collar to mark their captives
|
||||||
return new ItemStack(ModItems.SHOCK_COLLAR.get());
|
return com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem.createStack(
|
||||||
|
new net.minecraft.resources.ResourceLocation("tiedup", "shock_collar"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// HELD ITEM MANAGEMENT
|
// HELD ITEM MANAGEMENT
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
* Helper class for selecting themed items for kidnappers.
|
* Helper class for selecting themed items for kidnappers.
|
||||||
* Handles probability-based item selection and color application.
|
* Handles probability-based item selection and color application.
|
||||||
*
|
*
|
||||||
|
* All bondage items are now created via DataDrivenBondageItem.createStack().
|
||||||
|
*
|
||||||
* Item selection order (most to least common):
|
* Item selection order (most to least common):
|
||||||
* 1. Bind (arms) - 100% (always)
|
* 1. Bind (arms) - 100% (always)
|
||||||
* 2. Gag - 50%
|
* 2. Gag - 50%
|
||||||
@@ -79,33 +81,10 @@ public class KidnapperItemSelector {
|
|||||||
this.blindfold = blindfold;
|
this.blindfold = blindfold;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public boolean hasGag() { return !gag.isEmpty(); }
|
||||||
* Check if this selection has a gag.
|
public boolean hasMittens() { return !mittens.isEmpty(); }
|
||||||
*/
|
public boolean hasEarplugs() { return !earplugs.isEmpty(); }
|
||||||
public boolean hasGag() {
|
public boolean hasBlindfold() { return !blindfold.isEmpty(); }
|
||||||
return !gag.isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if this selection has mittens.
|
|
||||||
*/
|
|
||||||
public boolean hasMittens() {
|
|
||||||
return !mittens.isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if this selection has earplugs.
|
|
||||||
*/
|
|
||||||
public boolean hasEarplugs() {
|
|
||||||
return !earplugs.isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if this selection has a blindfold.
|
|
||||||
*/
|
|
||||||
public boolean hasBlindfold() {
|
|
||||||
return !blindfold.isEmpty();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -129,16 +108,6 @@ public class KidnapperItemSelector {
|
|||||||
return selectItems(false, true);
|
return selectItems(false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate adjusted probability based on kidnapper type.
|
|
||||||
*
|
|
||||||
* @param baseProb Base probability for the item
|
|
||||||
* @param eliteBonus Bonus probability for elite kidnappers
|
|
||||||
* @param archerPenalty Penalty probability for archer kidnappers
|
|
||||||
* @param isElite Whether the kidnapper is elite
|
|
||||||
* @param isArcher Whether the kidnapper is an archer
|
|
||||||
* @return Adjusted probability
|
|
||||||
*/
|
|
||||||
private static double getAdjustedProbability(
|
private static double getAdjustedProbability(
|
||||||
double baseProb,
|
double baseProb,
|
||||||
double eliteBonus,
|
double eliteBonus,
|
||||||
@@ -152,9 +121,6 @@ public class KidnapperItemSelector {
|
|||||||
return prob;
|
return prob;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal item selection logic.
|
|
||||||
*/
|
|
||||||
private static SelectionResult selectItems(
|
private static SelectionResult selectItems(
|
||||||
boolean isElite,
|
boolean isElite,
|
||||||
boolean isArcher
|
boolean isArcher
|
||||||
@@ -163,132 +129,64 @@ public class KidnapperItemSelector {
|
|||||||
KidnapperTheme theme = KidnapperTheme.getRandomWeighted();
|
KidnapperTheme theme = KidnapperTheme.getRandomWeighted();
|
||||||
|
|
||||||
// 2. Select color (if theme supports it)
|
// 2. Select color (if theme supports it)
|
||||||
// Filter out colors that don't have textures for this theme's bind
|
|
||||||
ItemColor color = theme.supportsColor()
|
ItemColor color = theme.supportsColor()
|
||||||
? getValidColorForBind(theme.getBind())
|
? ItemColor.getRandomStandard()
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
// 3. Create bind (always)
|
// 3. Create bind (always)
|
||||||
ItemStack bind = createBind(theme.getBind(), color);
|
ItemStack bind = createItemById(theme.getBindId(), color);
|
||||||
|
|
||||||
// 4. Roll for gag (randomly selected from theme's compatible gags)
|
// 4. Roll for gag
|
||||||
ItemStack gag = ItemStack.EMPTY;
|
ItemStack gag = ItemStack.EMPTY;
|
||||||
double gagProb = getAdjustedProbability(
|
double gagProb = getAdjustedProbability(
|
||||||
PROB_GAG,
|
PROB_GAG, ELITE_GAG_BONUS, ARCHER_GAG_PENALTY, isElite, isArcher
|
||||||
ELITE_GAG_BONUS,
|
|
||||||
ARCHER_GAG_PENALTY,
|
|
||||||
isElite,
|
|
||||||
isArcher
|
|
||||||
);
|
);
|
||||||
if (RANDOM.nextDouble() < gagProb) {
|
if (RANDOM.nextDouble() < gagProb) {
|
||||||
gag = createGag(theme.getRandomGag(), color);
|
gag = createItemById(theme.getRandomGagId(), color);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Roll for mittens (same for all themes)
|
// 5. Roll for mittens
|
||||||
ItemStack mittens = ItemStack.EMPTY;
|
ItemStack mittens = ItemStack.EMPTY;
|
||||||
double mittensProb = getAdjustedProbability(
|
double mittensProb = getAdjustedProbability(
|
||||||
PROB_MITTENS,
|
PROB_MITTENS, ELITE_MITTENS_BONUS, ARCHER_MITTENS_PENALTY, isElite, isArcher
|
||||||
ELITE_MITTENS_BONUS,
|
|
||||||
ARCHER_MITTENS_PENALTY,
|
|
||||||
isElite,
|
|
||||||
isArcher
|
|
||||||
);
|
);
|
||||||
if (RANDOM.nextDouble() < mittensProb) {
|
if (RANDOM.nextDouble() < mittensProb) {
|
||||||
mittens = createMittens();
|
mittens = createMittens();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. Roll for earplugs (same for all themes)
|
// 6. Roll for earplugs
|
||||||
ItemStack earplugs = ItemStack.EMPTY;
|
ItemStack earplugs = ItemStack.EMPTY;
|
||||||
double earplugsProb = getAdjustedProbability(
|
double earplugsProb = getAdjustedProbability(
|
||||||
PROB_EARPLUGS,
|
PROB_EARPLUGS, ELITE_EARPLUGS_BONUS, ARCHER_EARPLUGS_PENALTY, isElite, isArcher
|
||||||
ELITE_EARPLUGS_BONUS,
|
|
||||||
ARCHER_EARPLUGS_PENALTY,
|
|
||||||
isElite,
|
|
||||||
isArcher
|
|
||||||
);
|
);
|
||||||
if (RANDOM.nextDouble() < earplugsProb) {
|
if (RANDOM.nextDouble() < earplugsProb) {
|
||||||
earplugs = createEarplugs();
|
earplugs = createEarplugs();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 7. Roll for blindfold (last, most restrictive - randomly selected)
|
// 7. Roll for blindfold
|
||||||
ItemStack blindfold = ItemStack.EMPTY;
|
ItemStack blindfold = ItemStack.EMPTY;
|
||||||
double blindfoldProb = getAdjustedProbability(
|
double blindfoldProb = getAdjustedProbability(
|
||||||
PROB_BLINDFOLD,
|
PROB_BLINDFOLD, ELITE_BLINDFOLD_BONUS, ARCHER_BLINDFOLD_PENALTY, isElite, isArcher
|
||||||
ELITE_BLINDFOLD_BONUS,
|
|
||||||
ARCHER_BLINDFOLD_PENALTY,
|
|
||||||
isElite,
|
|
||||||
isArcher
|
|
||||||
);
|
);
|
||||||
if (theme.hasBlindfolds() && RANDOM.nextDouble() < blindfoldProb) {
|
if (theme.hasBlindfolds() && RANDOM.nextDouble() < blindfoldProb) {
|
||||||
blindfold = createBlindfold(theme.getRandomBlindfold(), color);
|
blindfold = createItemById(theme.getRandomBlindfoldId(), color);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new SelectionResult(
|
return new SelectionResult(
|
||||||
theme,
|
theme, color, bind, gag, mittens, earplugs, blindfold
|
||||||
color,
|
|
||||||
bind,
|
|
||||||
gag,
|
|
||||||
mittens,
|
|
||||||
earplugs,
|
|
||||||
blindfold
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ITEM CREATION METHODS
|
// ITEM CREATION METHODS
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a bind ItemStack with optional color.
|
* Create a data-driven bondage item by registry name, with optional color.
|
||||||
*/
|
*/
|
||||||
public static ItemStack createBind(
|
public static ItemStack createItemById(String id, @Nullable ItemColor color) {
|
||||||
BindVariant variant,
|
|
||||||
@Nullable ItemColor color
|
|
||||||
) {
|
|
||||||
ItemStack stack = DataDrivenBondageItem.createStack(
|
ItemStack stack = DataDrivenBondageItem.createStack(
|
||||||
new ResourceLocation("tiedup", variant.getRegistryName())
|
new ResourceLocation("tiedup", id)
|
||||||
);
|
);
|
||||||
if (color != null && variant.supportsColor()) {
|
if (color != null) {
|
||||||
applyColor(stack, color);
|
|
||||||
}
|
|
||||||
return stack;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a gag ItemStack with optional color.
|
|
||||||
* Validates that the color has a texture for this gag variant.
|
|
||||||
*/
|
|
||||||
public static ItemStack createGag(
|
|
||||||
GagVariant variant,
|
|
||||||
@Nullable ItemColor color
|
|
||||||
) {
|
|
||||||
ItemStack stack = DataDrivenBondageItem.createStack(
|
|
||||||
new ResourceLocation("tiedup", variant.getRegistryName())
|
|
||||||
);
|
|
||||||
if (
|
|
||||||
color != null &&
|
|
||||||
variant.supportsColor() &&
|
|
||||||
isColorValidForGag(color, variant)
|
|
||||||
) {
|
|
||||||
applyColor(stack, color);
|
|
||||||
}
|
|
||||||
return stack;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a blindfold ItemStack with optional color.
|
|
||||||
* Validates that the color has a texture for this blindfold variant.
|
|
||||||
*/
|
|
||||||
public static ItemStack createBlindfold(
|
|
||||||
BlindfoldVariant variant,
|
|
||||||
@Nullable ItemColor color
|
|
||||||
) {
|
|
||||||
ItemStack stack = DataDrivenBondageItem.createStack(
|
|
||||||
new ResourceLocation("tiedup", variant.getRegistryName())
|
|
||||||
);
|
|
||||||
if (
|
|
||||||
color != null &&
|
|
||||||
variant.supportsColor() &&
|
|
||||||
isColorValidForBlindfold(color, variant)
|
|
||||||
) {
|
|
||||||
applyColor(stack, color);
|
applyColor(stack, color);
|
||||||
}
|
}
|
||||||
return stack;
|
return stack;
|
||||||
@@ -296,7 +194,6 @@ public class KidnapperItemSelector {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Create mittens ItemStack.
|
* Create mittens ItemStack.
|
||||||
* Mittens don't have color variants.
|
|
||||||
*/
|
*/
|
||||||
public static ItemStack createMittens() {
|
public static ItemStack createMittens() {
|
||||||
return DataDrivenBondageItem.createStack(
|
return DataDrivenBondageItem.createStack(
|
||||||
@@ -306,7 +203,6 @@ public class KidnapperItemSelector {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Create earplugs ItemStack.
|
* Create earplugs ItemStack.
|
||||||
* Earplugs don't have color variants.
|
|
||||||
*/
|
*/
|
||||||
public static ItemStack createEarplugs() {
|
public static ItemStack createEarplugs() {
|
||||||
return DataDrivenBondageItem.createStack(
|
return DataDrivenBondageItem.createStack(
|
||||||
@@ -321,7 +217,6 @@ public class KidnapperItemSelector {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply color NBT to an ItemStack.
|
* Apply color NBT to an ItemStack.
|
||||||
* Sets both the ItemColor name and CustomModelData for model selection.
|
|
||||||
*/
|
*/
|
||||||
public static void applyColor(ItemStack stack, ItemColor color) {
|
public static void applyColor(ItemStack stack, ItemColor color) {
|
||||||
if (stack.isEmpty() || color == null) return;
|
if (stack.isEmpty() || color == null) return;
|
||||||
@@ -351,141 +246,9 @@ public class KidnapperItemSelector {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the texture suffix for an item's color.
|
* Get the texture suffix for an item's color.
|
||||||
* Example: "ropes" + "_red" = "ropes_red"
|
|
||||||
* @return The color suffix (e.g., "_red"), or empty string if no color
|
|
||||||
*/
|
*/
|
||||||
public static String getColorSuffix(ItemStack stack) {
|
public static String getColorSuffix(ItemStack stack) {
|
||||||
ItemColor color = getColor(stack);
|
ItemColor color = getColor(stack);
|
||||||
return color != null ? "_" + color.getName() : "";
|
return color != null ? "_" + color.getName() : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
// COLOR VALIDATION
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a random color that has a texture for the given bind variant.
|
|
||||||
* Excludes colors that don't have textures for specific variants.
|
|
||||||
*/
|
|
||||||
public static ItemColor getValidColorForBind(BindVariant variant) {
|
|
||||||
ItemColor color;
|
|
||||||
int attempts = 0;
|
|
||||||
do {
|
|
||||||
color = ItemColor.getRandomStandard();
|
|
||||||
attempts++;
|
|
||||||
// Prevent infinite loop
|
|
||||||
if (attempts > 50) break;
|
|
||||||
} while (!isColorValidForBind(color, variant));
|
|
||||||
return color;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a color has a texture for the given bind variant.
|
|
||||||
* Returns false for colors without textures.
|
|
||||||
*/
|
|
||||||
public static boolean isColorValidForBind(
|
|
||||||
ItemColor color,
|
|
||||||
BindVariant variant
|
|
||||||
) {
|
|
||||||
if (color == null || variant == null) return true;
|
|
||||||
|
|
||||||
// BROWN doesn't have textures for ROPES and SHIBARI
|
|
||||||
if (
|
|
||||||
color == ItemColor.BROWN &&
|
|
||||||
(variant == BindVariant.ROPES || variant == BindVariant.SHIBARI)
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// GRAY doesn't have texture for DUCT_TAPE
|
|
||||||
if (color == ItemColor.GRAY && variant == BindVariant.DUCT_TAPE) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a color has a texture for the given gag variant.
|
|
||||||
*/
|
|
||||||
public static boolean isColorValidForGag(
|
|
||||||
ItemColor color,
|
|
||||||
GagVariant variant
|
|
||||||
) {
|
|
||||||
if (color == null || variant == null) return true;
|
|
||||||
|
|
||||||
// GRAY doesn't have texture for TAPE_GAG
|
|
||||||
if (color == ItemColor.GRAY && variant == GagVariant.TAPE_GAG) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// WHITE doesn't have texture for CLOTH_GAG and CLEAVE_GAG
|
|
||||||
if (
|
|
||||||
color == ItemColor.WHITE &&
|
|
||||||
(variant == GagVariant.CLOTH_GAG ||
|
|
||||||
variant == GagVariant.CLEAVE_GAG)
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// RED doesn't have texture for BALL_GAG and BALL_GAG_STRAP
|
|
||||||
if (
|
|
||||||
color == ItemColor.RED &&
|
|
||||||
(variant == GagVariant.BALL_GAG ||
|
|
||||||
variant == GagVariant.BALL_GAG_STRAP)
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a color has a texture for the given blindfold variant.
|
|
||||||
*/
|
|
||||||
public static boolean isColorValidForBlindfold(
|
|
||||||
ItemColor color,
|
|
||||||
BlindfoldVariant variant
|
|
||||||
) {
|
|
||||||
if (color == null || variant == null) return true;
|
|
||||||
|
|
||||||
// BLACK doesn't have texture for CLASSIC or MASK blindfolds
|
|
||||||
if (
|
|
||||||
color == ItemColor.BLACK &&
|
|
||||||
(variant == BlindfoldVariant.CLASSIC ||
|
|
||||||
variant == BlindfoldVariant.MASK)
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a random color that has a texture for the given gag variant.
|
|
||||||
*/
|
|
||||||
public static ItemColor getValidColorForGag(GagVariant variant) {
|
|
||||||
ItemColor color;
|
|
||||||
int attempts = 0;
|
|
||||||
do {
|
|
||||||
color = ItemColor.getRandomStandard();
|
|
||||||
attempts++;
|
|
||||||
if (attempts > 50) break;
|
|
||||||
} while (!isColorValidForGag(color, variant));
|
|
||||||
return color;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a random color that has a texture for the given blindfold variant.
|
|
||||||
*/
|
|
||||||
public static ItemColor getValidColorForBlindfold(
|
|
||||||
BlindfoldVariant variant
|
|
||||||
) {
|
|
||||||
ItemColor color;
|
|
||||||
int attempts = 0;
|
|
||||||
do {
|
|
||||||
color = ItemColor.getRandomStandard();
|
|
||||||
attempts++;
|
|
||||||
if (attempts > 50) break;
|
|
||||||
} while (!isColorValidForBlindfold(color, variant));
|
|
||||||
return color;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -161,7 +161,8 @@ 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 = com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem.createStack(
|
||||||
|
new net.minecraft.resources.ResourceLocation("tiedup", "shock_collar_auto"));
|
||||||
// Add kidnapper as owner so the collar is linked
|
// Add kidnapper as owner so the collar is linked
|
||||||
CollarHelper.addOwner(
|
CollarHelper.addOwner(
|
||||||
shockCollar,
|
shockCollar,
|
||||||
|
|||||||
@@ -1,42 +1,33 @@
|
|||||||
package com.tiedup.remake.entities;
|
package com.tiedup.remake.entities;
|
||||||
|
|
||||||
import com.tiedup.remake.items.base.BindVariant;
|
|
||||||
import com.tiedup.remake.items.base.BlindfoldVariant;
|
|
||||||
import com.tiedup.remake.items.base.GagVariant;
|
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines themed item sets for kidnappers.
|
* Defines themed item sets for kidnappers.
|
||||||
* Each theme groups compatible binds, gags, and blindfolds.
|
* Each theme groups compatible binds, gags, and blindfolds by registry name.
|
||||||
*
|
*
|
||||||
* Themes are selected randomly with weighted probabilities.
|
* Themes are selected randomly with weighted probabilities.
|
||||||
* Higher weight = more common theme.
|
* Higher weight = more common theme.
|
||||||
*
|
*
|
||||||
* Note: Natural themes (SLIME, VINE, WEB) are reserved for monsters.
|
* Note: Natural themes (SLIME, VINE, WEB) are reserved for monsters.
|
||||||
|
*
|
||||||
|
* Registry names correspond to data-driven bondage item IDs in DataDrivenItemRegistry.
|
||||||
*/
|
*/
|
||||||
public enum KidnapperTheme {
|
public enum KidnapperTheme {
|
||||||
// === ROPE THEMES (most common) ===
|
// === ROPE THEMES (most common) ===
|
||||||
|
|
||||||
ROPE(
|
ROPE(
|
||||||
BindVariant.ROPES,
|
"ropes",
|
||||||
new GagVariant[] {
|
new String[] { "ropes_gag", "cloth_gag", "cleave_gag" },
|
||||||
GagVariant.ROPES_GAG,
|
new String[] { "classic_blindfold" },
|
||||||
GagVariant.CLOTH_GAG,
|
|
||||||
GagVariant.CLEAVE_GAG,
|
|
||||||
},
|
|
||||||
new BlindfoldVariant[] { BlindfoldVariant.CLASSIC },
|
|
||||||
true, // supportsColor
|
true, // supportsColor
|
||||||
30 // weight (spawn probability)
|
30 // weight (spawn probability)
|
||||||
),
|
),
|
||||||
|
|
||||||
SHIBARI(
|
SHIBARI(
|
||||||
BindVariant.SHIBARI,
|
"shibari",
|
||||||
new GagVariant[] {
|
new String[] { "ropes_gag", "cloth_gag", "ribbon_gag" },
|
||||||
GagVariant.ROPES_GAG,
|
new String[] { "classic_blindfold" },
|
||||||
GagVariant.CLOTH_GAG,
|
|
||||||
GagVariant.RIBBON_GAG,
|
|
||||||
},
|
|
||||||
new BlindfoldVariant[] { BlindfoldVariant.CLASSIC },
|
|
||||||
true,
|
true,
|
||||||
15
|
15
|
||||||
),
|
),
|
||||||
@@ -44,9 +35,9 @@ public enum KidnapperTheme {
|
|||||||
// === TAPE THEME ===
|
// === TAPE THEME ===
|
||||||
|
|
||||||
TAPE(
|
TAPE(
|
||||||
BindVariant.DUCT_TAPE,
|
"duct_tape",
|
||||||
new GagVariant[] { GagVariant.TAPE_GAG, GagVariant.WRAP_GAG },
|
new String[] { "tape_gag", "wrap_gag" },
|
||||||
new BlindfoldVariant[] { BlindfoldVariant.MASK },
|
new String[] { "mask_blindfold" },
|
||||||
true,
|
true,
|
||||||
20
|
20
|
||||||
),
|
),
|
||||||
@@ -54,13 +45,9 @@ public enum KidnapperTheme {
|
|||||||
// === LEATHER/BDSM THEME ===
|
// === LEATHER/BDSM THEME ===
|
||||||
|
|
||||||
LEATHER(
|
LEATHER(
|
||||||
BindVariant.LEATHER_STRAPS,
|
"leather_straps",
|
||||||
new GagVariant[] {
|
new String[] { "ball_gag", "ball_gag_strap", "panel_gag" },
|
||||||
GagVariant.BALL_GAG,
|
new String[] { "mask_blindfold" },
|
||||||
GagVariant.BALL_GAG_STRAP,
|
|
||||||
GagVariant.PANEL_GAG,
|
|
||||||
},
|
|
||||||
new BlindfoldVariant[] { BlindfoldVariant.MASK },
|
|
||||||
false,
|
false,
|
||||||
15
|
15
|
||||||
),
|
),
|
||||||
@@ -68,12 +55,9 @@ public enum KidnapperTheme {
|
|||||||
// === CHAIN THEME ===
|
// === CHAIN THEME ===
|
||||||
|
|
||||||
CHAIN(
|
CHAIN(
|
||||||
BindVariant.CHAIN,
|
"chain",
|
||||||
new GagVariant[] {
|
new String[] { "chain_panel_gag", "ball_gag_strap" },
|
||||||
GagVariant.CHAIN_PANEL_GAG,
|
new String[] { "mask_blindfold" },
|
||||||
GagVariant.BALL_GAG_STRAP,
|
|
||||||
},
|
|
||||||
new BlindfoldVariant[] { BlindfoldVariant.MASK },
|
|
||||||
false,
|
false,
|
||||||
10
|
10
|
||||||
),
|
),
|
||||||
@@ -81,13 +65,9 @@ public enum KidnapperTheme {
|
|||||||
// === MEDICAL THEME ===
|
// === MEDICAL THEME ===
|
||||||
|
|
||||||
MEDICAL(
|
MEDICAL(
|
||||||
BindVariant.MEDICAL_STRAPS,
|
"medical_straps",
|
||||||
new GagVariant[] {
|
new String[] { "tube_gag", "sponge_gag", "ball_gag" },
|
||||||
GagVariant.TUBE_GAG,
|
new String[] { "mask_blindfold" },
|
||||||
GagVariant.SPONGE_GAG,
|
|
||||||
GagVariant.BALL_GAG,
|
|
||||||
},
|
|
||||||
new BlindfoldVariant[] { BlindfoldVariant.MASK },
|
|
||||||
false,
|
false,
|
||||||
8
|
8
|
||||||
),
|
),
|
||||||
@@ -95,9 +75,9 @@ public enum KidnapperTheme {
|
|||||||
// === SCI-FI/BEAM THEME ===
|
// === SCI-FI/BEAM THEME ===
|
||||||
|
|
||||||
BEAM(
|
BEAM(
|
||||||
BindVariant.BEAM_CUFFS,
|
"beam_cuffs",
|
||||||
new GagVariant[] { GagVariant.BEAM_PANEL_GAG, GagVariant.LATEX_GAG },
|
new String[] { "beam_panel_gag", "latex_gag" },
|
||||||
new BlindfoldVariant[] { BlindfoldVariant.MASK },
|
new String[] { "mask_blindfold" },
|
||||||
false,
|
false,
|
||||||
5
|
5
|
||||||
),
|
),
|
||||||
@@ -105,9 +85,9 @@ public enum KidnapperTheme {
|
|||||||
// === LATEX THEME (rare) ===
|
// === LATEX THEME (rare) ===
|
||||||
|
|
||||||
LATEX(
|
LATEX(
|
||||||
BindVariant.LATEX_SACK,
|
"latex_sack",
|
||||||
new GagVariant[] { GagVariant.LATEX_GAG, GagVariant.TUBE_GAG },
|
new String[] { "latex_gag", "tube_gag" },
|
||||||
new BlindfoldVariant[] { BlindfoldVariant.MASK },
|
new String[] { "mask_blindfold" },
|
||||||
false,
|
false,
|
||||||
3
|
3
|
||||||
),
|
),
|
||||||
@@ -115,13 +95,9 @@ public enum KidnapperTheme {
|
|||||||
// === ASYLUM THEME (rare) ===
|
// === ASYLUM THEME (rare) ===
|
||||||
|
|
||||||
ASYLUM(
|
ASYLUM(
|
||||||
BindVariant.STRAITJACKET,
|
"straitjacket",
|
||||||
new GagVariant[] {
|
new String[] { "bite_gag", "sponge_gag", "ball_gag" },
|
||||||
GagVariant.BITE_GAG,
|
new String[] { "mask_blindfold" },
|
||||||
GagVariant.SPONGE_GAG,
|
|
||||||
GagVariant.BALL_GAG,
|
|
||||||
},
|
|
||||||
new BlindfoldVariant[] { BlindfoldVariant.MASK },
|
|
||||||
false,
|
false,
|
||||||
5
|
5
|
||||||
),
|
),
|
||||||
@@ -129,9 +105,9 @@ public enum KidnapperTheme {
|
|||||||
// === RIBBON THEME (cute/playful) ===
|
// === RIBBON THEME (cute/playful) ===
|
||||||
|
|
||||||
RIBBON(
|
RIBBON(
|
||||||
BindVariant.RIBBON,
|
"ribbon",
|
||||||
new GagVariant[] { GagVariant.RIBBON_GAG, GagVariant.CLOTH_GAG },
|
new String[] { "ribbon_gag", "cloth_gag" },
|
||||||
new BlindfoldVariant[] { BlindfoldVariant.CLASSIC },
|
new String[] { "classic_blindfold" },
|
||||||
false,
|
false,
|
||||||
8
|
8
|
||||||
),
|
),
|
||||||
@@ -139,54 +115,54 @@ public enum KidnapperTheme {
|
|||||||
// === WRAP THEME ===
|
// === WRAP THEME ===
|
||||||
|
|
||||||
WRAP(
|
WRAP(
|
||||||
BindVariant.WRAP,
|
"wrap",
|
||||||
new GagVariant[] { GagVariant.WRAP_GAG, GagVariant.TAPE_GAG },
|
new String[] { "wrap_gag", "tape_gag" },
|
||||||
new BlindfoldVariant[] { BlindfoldVariant.MASK },
|
new String[] { "mask_blindfold" },
|
||||||
false,
|
false,
|
||||||
5
|
5
|
||||||
);
|
);
|
||||||
|
|
||||||
private static final Random RANDOM = new Random();
|
private static final Random RANDOM = new Random();
|
||||||
|
|
||||||
private final BindVariant bind;
|
private final String bindId;
|
||||||
private final GagVariant[] gags;
|
private final String[] gagIds;
|
||||||
private final BlindfoldVariant[] blindfolds;
|
private final String[] blindfoldIds;
|
||||||
private final boolean supportsColor;
|
private final boolean supportsColor;
|
||||||
private final int weight;
|
private final int weight;
|
||||||
|
|
||||||
KidnapperTheme(
|
KidnapperTheme(
|
||||||
BindVariant bind,
|
String bindId,
|
||||||
GagVariant[] gags,
|
String[] gagIds,
|
||||||
BlindfoldVariant[] blindfolds,
|
String[] blindfoldIds,
|
||||||
boolean supportsColor,
|
boolean supportsColor,
|
||||||
int weight
|
int weight
|
||||||
) {
|
) {
|
||||||
this.bind = bind;
|
this.bindId = bindId;
|
||||||
this.gags = gags;
|
this.gagIds = gagIds;
|
||||||
this.blindfolds = blindfolds;
|
this.blindfoldIds = blindfoldIds;
|
||||||
this.supportsColor = supportsColor;
|
this.supportsColor = supportsColor;
|
||||||
this.weight = weight;
|
this.weight = weight;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the primary bind for this theme.
|
* Get the primary bind registry name for this theme.
|
||||||
*/
|
*/
|
||||||
public BindVariant getBind() {
|
public String getBindId() {
|
||||||
return bind;
|
return bindId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get compatible gags for this theme (ordered by preference).
|
* Get compatible gag IDs for this theme (ordered by preference).
|
||||||
*/
|
*/
|
||||||
public GagVariant[] getGags() {
|
public String[] getGagIds() {
|
||||||
return gags;
|
return gagIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get compatible blindfolds for this theme.
|
* Get compatible blindfold IDs for this theme.
|
||||||
*/
|
*/
|
||||||
public BlindfoldVariant[] getBlindfolds() {
|
public String[] getBlindfoldIds() {
|
||||||
return blindfolds;
|
return blindfoldIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -206,41 +182,40 @@ public enum KidnapperTheme {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get primary gag (first in list).
|
* Get primary gag ID (first in list).
|
||||||
* Used when only one gag is selected.
|
|
||||||
*/
|
*/
|
||||||
public GagVariant getPrimaryGag() {
|
public String getPrimaryGagId() {
|
||||||
return gags.length > 0 ? gags[0] : GagVariant.BALL_GAG;
|
return gagIds.length > 0 ? gagIds[0] : "ball_gag";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a random gag from this theme's compatible list.
|
* Get a random gag ID from this theme's compatible list.
|
||||||
*/
|
*/
|
||||||
public GagVariant getRandomGag() {
|
public String getRandomGagId() {
|
||||||
if (gags.length == 0) return GagVariant.BALL_GAG;
|
if (gagIds.length == 0) return "ball_gag";
|
||||||
return gags[RANDOM.nextInt(gags.length)];
|
return gagIds[RANDOM.nextInt(gagIds.length)];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get primary blindfold (first in list).
|
* Get primary blindfold ID (first in list).
|
||||||
*/
|
*/
|
||||||
public BlindfoldVariant getPrimaryBlindfold() {
|
public String getPrimaryBlindfoldId() {
|
||||||
return blindfolds.length > 0 ? blindfolds[0] : BlindfoldVariant.CLASSIC;
|
return blindfoldIds.length > 0 ? blindfoldIds[0] : "classic_blindfold";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a random blindfold from this theme's compatible list.
|
* Get a random blindfold ID from this theme's compatible list.
|
||||||
*/
|
*/
|
||||||
public BlindfoldVariant getRandomBlindfold() {
|
public String getRandomBlindfoldId() {
|
||||||
if (blindfolds.length == 0) return BlindfoldVariant.CLASSIC;
|
if (blindfoldIds.length == 0) return "classic_blindfold";
|
||||||
return blindfolds[RANDOM.nextInt(blindfolds.length)];
|
return blindfoldIds[RANDOM.nextInt(blindfoldIds.length)];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if this theme has any blindfolds.
|
* Check if this theme has any blindfolds.
|
||||||
*/
|
*/
|
||||||
public boolean hasBlindfolds() {
|
public boolean hasBlindfolds() {
|
||||||
return blindfolds.length > 0;
|
return blindfoldIds.length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -229,20 +229,21 @@ public class MaidDeliverCaptiveGoal extends Goal {
|
|||||||
kidnappedState.getEquipment(BodyRegionV2.NECK);
|
kidnappedState.getEquipment(BodyRegionV2.NECK);
|
||||||
if (
|
if (
|
||||||
!collar.isEmpty() &&
|
!collar.isEmpty() &&
|
||||||
collar.getItem() instanceof
|
com.tiedup.remake.v2.bondage.CollarHelper.isCollar(collar)
|
||||||
com.tiedup.remake.items.base.ItemCollar collarItem
|
|
||||||
) {
|
) {
|
||||||
for (java.util.UUID ownerId : new java.util.ArrayList<>(
|
for (java.util.UUID ownerId : new java.util.ArrayList<>(
|
||||||
collarItem.getOwners(collar)
|
com.tiedup.remake.v2.bondage.CollarHelper.getOwners(collar)
|
||||||
)) {
|
)) {
|
||||||
collarItem.removeOwner(collar, ownerId);
|
com.tiedup.remake.v2.bondage.CollarHelper.removeOwner(collar, ownerId);
|
||||||
}
|
}
|
||||||
collarItem.addOwner(
|
com.tiedup.remake.v2.bondage.CollarHelper.addOwner(
|
||||||
collar,
|
collar,
|
||||||
buyerEntity.getUUID(),
|
buyerEntity.getUUID(),
|
||||||
buyerEntity.getName().getString()
|
buyerEntity.getName().getString()
|
||||||
);
|
);
|
||||||
collarItem.setLocked(collar, false);
|
if (collar.getItem() instanceof com.tiedup.remake.items.base.ILockable lockable) {
|
||||||
|
lockable.setLocked(collar, false);
|
||||||
|
}
|
||||||
kidnappedState.equip(BodyRegionV2.NECK, collar);
|
kidnappedState.equip(BodyRegionV2.NECK, collar);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -305,11 +305,8 @@ public class NpcStruggleGoal extends Goal {
|
|||||||
ItemStack collar = npc.getEquipment(BodyRegionV2.NECK);
|
ItemStack collar = npc.getEquipment(BodyRegionV2.NECK);
|
||||||
if (collar.isEmpty()) return false;
|
if (collar.isEmpty()) return false;
|
||||||
|
|
||||||
if (
|
if (com.tiedup.remake.v2.bondage.CollarHelper.isCollar(collar)) {
|
||||||
collar.getItem() instanceof
|
List<UUID> ownerUUIDs = com.tiedup.remake.v2.bondage.CollarHelper.getOwners(collar);
|
||||||
com.tiedup.remake.items.base.ItemCollar collarItem
|
|
||||||
) {
|
|
||||||
List<UUID> ownerUUIDs = collarItem.getOwners(collar);
|
|
||||||
if (!ownerUUIDs.isEmpty()) {
|
if (!ownerUUIDs.isEmpty()) {
|
||||||
// Check if any owner is nearby
|
// Check if any owner is nearby
|
||||||
List<Player> players = npc
|
List<Player> players = npc
|
||||||
@@ -338,11 +335,8 @@ public class NpcStruggleGoal extends Goal {
|
|||||||
ItemStack collar = npc.getEquipment(BodyRegionV2.NECK);
|
ItemStack collar = npc.getEquipment(BodyRegionV2.NECK);
|
||||||
if (collar.isEmpty()) return null;
|
if (collar.isEmpty()) return null;
|
||||||
|
|
||||||
if (
|
if (com.tiedup.remake.v2.bondage.CollarHelper.isCollar(collar)) {
|
||||||
collar.getItem() instanceof
|
List<UUID> ownerUUIDs = com.tiedup.remake.v2.bondage.CollarHelper.getOwners(collar);
|
||||||
com.tiedup.remake.items.base.ItemCollar collarItem
|
|
||||||
) {
|
|
||||||
List<UUID> ownerUUIDs = collarItem.getOwners(collar);
|
|
||||||
if (!ownerUUIDs.isEmpty()) {
|
if (!ownerUUIDs.isEmpty()) {
|
||||||
return ownerUUIDs.get(0);
|
return ownerUUIDs.get(0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package com.tiedup.remake.entities.armorstand;
|
package com.tiedup.remake.entities.armorstand;
|
||||||
|
|
||||||
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.v2.BodyRegionV2;
|
import com.tiedup.remake.v2.BodyRegionV2;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import net.minecraft.core.Rotations;
|
import net.minecraft.core.Rotations;
|
||||||
@@ -185,11 +185,8 @@ public class ArmorStandBondageHelper {
|
|||||||
// Save original pose if not already saved
|
// Save original pose if not already saved
|
||||||
saveOriginalPose(stand);
|
saveOriginalPose(stand);
|
||||||
|
|
||||||
// Get pose type from bind
|
// Get pose type from bind (V2 data-driven)
|
||||||
PoseType poseType = PoseType.STANDARD;
|
PoseType poseType = PoseTypeHelper.getPoseType(bindStack);
|
||||||
if (bindStack.getItem() instanceof ItemBind bind) {
|
|
||||||
poseType = bind.getPoseType();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply pose
|
// Apply pose
|
||||||
applyBondagePose(stand, poseType);
|
applyBondagePose(stand, poseType);
|
||||||
|
|||||||
@@ -116,15 +116,13 @@ 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;
|
||||||
if (DataDrivenBondageItem.getComponent(gag, ComponentType.GAGGING, GaggingComponent.class) != null) return true;
|
return DataDrivenBondageItem.getComponent(gag, ComponentType.GAGGING, GaggingComponent.class) != null;
|
||||||
return gag.getItem() instanceof 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;
|
||||||
if (DataDrivenBondageItem.getComponent(blindfold, ComponentType.BLINDING, BlindingComponent.class) != null) return true;
|
return DataDrivenBondageItem.getComponent(blindfold, ComponentType.BLINDING, BlindingComponent.class) != null;
|
||||||
return blindfold.getItem() instanceof com.tiedup.remake.items.base.IHasBlindingEffect;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasKnives() {
|
public boolean hasKnives() {
|
||||||
|
|||||||
@@ -361,19 +361,19 @@ public class KidnapperAppearance {
|
|||||||
this.itemSelection = new KidnapperItemSelector.SelectionResult(
|
this.itemSelection = new KidnapperItemSelector.SelectionResult(
|
||||||
this.currentTheme,
|
this.currentTheme,
|
||||||
this.themeColor,
|
this.themeColor,
|
||||||
KidnapperItemSelector.createBind(
|
KidnapperItemSelector.createItemById(
|
||||||
this.currentTheme.getBind(),
|
this.currentTheme.getBindId(),
|
||||||
this.themeColor
|
this.themeColor
|
||||||
),
|
),
|
||||||
KidnapperItemSelector.createGag(
|
KidnapperItemSelector.createItemById(
|
||||||
this.currentTheme.getPrimaryGag(),
|
this.currentTheme.getPrimaryGagId(),
|
||||||
this.themeColor
|
this.themeColor
|
||||||
),
|
),
|
||||||
KidnapperItemSelector.createMittens(),
|
KidnapperItemSelector.createMittens(),
|
||||||
KidnapperItemSelector.createEarplugs(),
|
KidnapperItemSelector.createEarplugs(),
|
||||||
this.currentTheme.hasBlindfolds()
|
this.currentTheme.hasBlindfolds()
|
||||||
? KidnapperItemSelector.createBlindfold(
|
? KidnapperItemSelector.createItemById(
|
||||||
this.currentTheme.getPrimaryBlindfold(),
|
this.currentTheme.getPrimaryBlindfoldId(),
|
||||||
this.themeColor
|
this.themeColor
|
||||||
)
|
)
|
||||||
: ItemStack.EMPTY
|
: ItemStack.EMPTY
|
||||||
|
|||||||
@@ -119,8 +119,8 @@ public class MasterPetManager {
|
|||||||
if (state == null) return;
|
if (state == null) return;
|
||||||
|
|
||||||
// Create a choke collar for pet play
|
// Create a choke collar for pet play
|
||||||
ItemStack chokeCollar = new ItemStack(
|
ItemStack chokeCollar = com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem.createStack(
|
||||||
com.tiedup.remake.items.ModItems.CHOKE_COLLAR.get()
|
new net.minecraft.resources.ResourceLocation("tiedup", "choke_collar")
|
||||||
);
|
);
|
||||||
|
|
||||||
// Configure for pet play BEFORE equipping
|
// Configure for pet play BEFORE equipping
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import com.tiedup.remake.core.SettingsAccessor;
|
|||||||
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.dialogue.GagTalkManager;
|
import com.tiedup.remake.dialogue.GagTalkManager;
|
||||||
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.ComponentType;
|
||||||
@@ -57,11 +56,10 @@ public class ChatEventHandler {
|
|||||||
BodyRegionV2.MOUTH
|
BodyRegionV2.MOUTH
|
||||||
);
|
);
|
||||||
|
|
||||||
// V2: check gagging component, V1 fallback: instanceof ItemGag
|
// V2: check gagging component
|
||||||
GaggingComponent gaggingComp = DataDrivenBondageItem.getComponent(
|
GaggingComponent gaggingComp = DataDrivenBondageItem.getComponent(
|
||||||
gagStack, ComponentType.GAGGING, GaggingComponent.class);
|
gagStack, ComponentType.GAGGING, GaggingComponent.class);
|
||||||
boolean isGagItem = gaggingComp != null
|
boolean isGagItem = gaggingComp != null;
|
||||||
|| gagStack.getItem() instanceof ItemGag;
|
|
||||||
|
|
||||||
if (!gagStack.isEmpty() && isGagItem) {
|
if (!gagStack.isEmpty() && isGagItem) {
|
||||||
String originalMessage = event.getRawText();
|
String originalMessage = event.getRawText();
|
||||||
@@ -70,9 +68,7 @@ public class ChatEventHandler {
|
|||||||
if (gaggingComp != null) {
|
if (gaggingComp != null) {
|
||||||
material = gaggingComp.getMaterial();
|
material = gaggingComp.getMaterial();
|
||||||
}
|
}
|
||||||
if (material == null && gagStack.getItem() instanceof ItemGag gagItem) {
|
// material stays null if no component; GagTalkManager handles null → CLOTH fallback
|
||||||
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(
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
package com.tiedup.remake.items;
|
|
||||||
|
|
||||||
import com.tiedup.remake.items.base.BindVariant;
|
|
||||||
import com.tiedup.remake.items.base.ItemBind;
|
|
||||||
import com.tiedup.remake.items.base.PoseType;
|
|
||||||
import net.minecraft.world.item.Item;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generic bind item created from BindVariant enum.
|
|
||||||
* Replaces individual bind classes (ItemRopes, ItemChain, ItemStraitjacket, etc.)
|
|
||||||
*
|
|
||||||
* Factory pattern: All bind variants are created using this single class.
|
|
||||||
*/
|
|
||||||
public class GenericBind extends ItemBind {
|
|
||||||
|
|
||||||
private final BindVariant variant;
|
|
||||||
|
|
||||||
public GenericBind(BindVariant variant) {
|
|
||||||
super(new Item.Properties().stacksTo(16));
|
|
||||||
this.variant = variant;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getItemName() {
|
|
||||||
return variant.getItemName();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PoseType getPoseType() {
|
|
||||||
return variant.getPoseType();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the variant this bind was created from.
|
|
||||||
*/
|
|
||||||
public BindVariant getVariant() {
|
|
||||||
return variant;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the default resistance value for this bind variant.
|
|
||||||
* Note: Actual resistance is managed by GameRules, this is just the configured default.
|
|
||||||
*/
|
|
||||||
public int getDefaultResistance() {
|
|
||||||
return variant.getResistance();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if this bind can have a padlock attached via anvil.
|
|
||||||
* Adhesive (tape) and organic (slime, vine, web) binds cannot have padlocks.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean canAttachPadlock() {
|
|
||||||
return switch (variant) {
|
|
||||||
case DUCT_TAPE, SLIME, VINE_SEED, WEB_BIND -> false;
|
|
||||||
default -> true;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the texture subfolder for this bind variant.
|
|
||||||
* Issue #12 fix: Eliminates string checks in renderers.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String getTextureSubfolder() {
|
|
||||||
return variant.getTextureSubfolder();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
package com.tiedup.remake.items;
|
|
||||||
|
|
||||||
import com.tiedup.remake.items.base.BlindfoldVariant;
|
|
||||||
import com.tiedup.remake.items.base.ItemBlindfold;
|
|
||||||
import net.minecraft.world.item.Item;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generic blindfold item created from BlindfoldVariant enum.
|
|
||||||
* Replaces individual blindfold classes (ItemClassicBlindfold, ItemBlindfoldMask).
|
|
||||||
*
|
|
||||||
* Factory pattern: All blindfold variants are created using this single class.
|
|
||||||
*/
|
|
||||||
public class GenericBlindfold extends ItemBlindfold {
|
|
||||||
|
|
||||||
private final BlindfoldVariant variant;
|
|
||||||
|
|
||||||
public GenericBlindfold(BlindfoldVariant variant) {
|
|
||||||
super(new Item.Properties().stacksTo(16));
|
|
||||||
this.variant = variant;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the variant this blindfold was created from.
|
|
||||||
*/
|
|
||||||
public BlindfoldVariant getVariant() {
|
|
||||||
return variant;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the texture subfolder for this blindfold variant.
|
|
||||||
* Issue #12 fix: Eliminates string checks in renderers.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String getTextureSubfolder() {
|
|
||||||
return variant.getTextureSubfolder();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
package com.tiedup.remake.items;
|
|
||||||
|
|
||||||
import com.tiedup.remake.items.base.EarplugsVariant;
|
|
||||||
import com.tiedup.remake.items.base.ItemEarplugs;
|
|
||||||
import net.minecraft.world.item.Item;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generic earplugs item created from EarplugsVariant enum.
|
|
||||||
* Replaces individual earplugs classes (ItemClassicEarplugs).
|
|
||||||
*
|
|
||||||
* Factory pattern: All earplugs variants are created using this single class.
|
|
||||||
*/
|
|
||||||
public class GenericEarplugs extends ItemEarplugs {
|
|
||||||
|
|
||||||
private final EarplugsVariant variant;
|
|
||||||
|
|
||||||
public GenericEarplugs(EarplugsVariant variant) {
|
|
||||||
super(new Item.Properties().stacksTo(16));
|
|
||||||
this.variant = variant;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the variant this earplugs was created from.
|
|
||||||
*/
|
|
||||||
public EarplugsVariant getVariant() {
|
|
||||||
return variant;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the texture subfolder for this earplugs variant.
|
|
||||||
* Issue #12 fix: Eliminates string checks in renderers.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String getTextureSubfolder() {
|
|
||||||
return variant.getTextureSubfolder();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
package com.tiedup.remake.items;
|
|
||||||
|
|
||||||
import com.tiedup.remake.items.base.GagVariant;
|
|
||||||
import com.tiedup.remake.items.base.ItemGag;
|
|
||||||
import net.minecraft.resources.ResourceLocation;
|
|
||||||
import net.minecraft.world.item.Item;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generic gag item created from GagVariant enum.
|
|
||||||
* Replaces individual gag classes (ItemBallGag, ItemTapeGag, etc.)
|
|
||||||
*
|
|
||||||
* Factory pattern: All gag variants are created using this single class.
|
|
||||||
*
|
|
||||||
* Note: ItemMedicalGag is NOT handled by this class because it implements
|
|
||||||
* IHasBlindingEffect (combo item with special behavior).
|
|
||||||
*/
|
|
||||||
public class GenericGag extends ItemGag {
|
|
||||||
|
|
||||||
private final GagVariant variant;
|
|
||||||
|
|
||||||
public GenericGag(GagVariant variant) {
|
|
||||||
super(new Item.Properties().stacksTo(16), variant.getMaterial());
|
|
||||||
this.variant = variant;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the variant this gag was created from.
|
|
||||||
*/
|
|
||||||
public GagVariant getVariant() {
|
|
||||||
return variant;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if this gag can have a padlock attached via anvil.
|
|
||||||
* Adhesive (tape) and organic (slime, vine, web) gags cannot have padlocks.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean canAttachPadlock() {
|
|
||||||
return switch (variant) {
|
|
||||||
case TAPE_GAG, SLIME_GAG, VINE_GAG, WEB_GAG -> false;
|
|
||||||
default -> true;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the texture subfolder for this gag variant.
|
|
||||||
* Issue #12 fix: Eliminates string checks in renderers.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String getTextureSubfolder() {
|
|
||||||
return variant.getTextureSubfolder();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if this gag uses a 3D OBJ model.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean uses3DModel() {
|
|
||||||
return variant.uses3DModel();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the 3D model location for this gag.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
@Nullable
|
|
||||||
public ResourceLocation get3DModelLocation() {
|
|
||||||
String path = variant.getModelPath();
|
|
||||||
return path != null ? ResourceLocation.tryParse(path) : null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,8 +3,8 @@ package com.tiedup.remake.items;
|
|||||||
import com.tiedup.remake.core.TiedUpMod;
|
import com.tiedup.remake.core.TiedUpMod;
|
||||||
import com.tiedup.remake.items.base.IKnife;
|
import com.tiedup.remake.items.base.IKnife;
|
||||||
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.KnifeVariant;
|
import com.tiedup.remake.items.base.KnifeVariant;
|
||||||
|
import com.tiedup.remake.v2.bondage.BindModeHelper;
|
||||||
import com.tiedup.remake.network.sync.SyncManager;
|
import com.tiedup.remake.network.sync.SyncManager;
|
||||||
import com.tiedup.remake.state.IBondageState;
|
import com.tiedup.remake.state.IBondageState;
|
||||||
import com.tiedup.remake.state.PlayerBindState;
|
import com.tiedup.remake.state.PlayerBindState;
|
||||||
@@ -306,10 +306,7 @@ public class GenericKnife extends Item implements IKnife {
|
|||||||
player,
|
player,
|
||||||
BodyRegionV2.ARMS
|
BodyRegionV2.ARMS
|
||||||
);
|
);
|
||||||
if (
|
if (bindStack.isEmpty() || !BindModeHelper.isBindItem(bindStack)) {
|
||||||
bindStack.isEmpty() ||
|
|
||||||
!(bindStack.getItem() instanceof ItemBind bind)
|
|
||||||
) {
|
|
||||||
player.stopUsingItem();
|
player.stopUsingItem();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
package com.tiedup.remake.items;
|
|
||||||
|
|
||||||
import com.tiedup.remake.items.base.ItemMittens;
|
|
||||||
import com.tiedup.remake.items.base.MittensVariant;
|
|
||||||
import net.minecraft.world.item.Item;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generic mittens item created from MittensVariant enum.
|
|
||||||
*
|
|
||||||
* Factory pattern: All mittens variants are created using this single class.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class GenericMittens extends ItemMittens {
|
|
||||||
|
|
||||||
private final MittensVariant variant;
|
|
||||||
|
|
||||||
public GenericMittens(MittensVariant variant) {
|
|
||||||
super(new Item.Properties().stacksTo(16));
|
|
||||||
this.variant = variant;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the variant this mittens was created from.
|
|
||||||
*/
|
|
||||||
public MittensVariant getVariant() {
|
|
||||||
return variant;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the texture subfolder for this mittens variant.
|
|
||||||
* Issue #12 fix: Eliminates string checks in renderers.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String getTextureSubfolder() {
|
|
||||||
return variant.getTextureSubfolder();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,154 +0,0 @@
|
|||||||
package com.tiedup.remake.items;
|
|
||||||
|
|
||||||
import com.tiedup.remake.items.base.ItemCollar;
|
|
||||||
import com.tiedup.remake.items.bondage3d.IHas3DModelConfig;
|
|
||||||
import com.tiedup.remake.items.bondage3d.Model3DConfig;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import net.minecraft.ChatFormatting;
|
|
||||||
import net.minecraft.nbt.CompoundTag;
|
|
||||||
import net.minecraft.network.chat.Component;
|
|
||||||
import net.minecraft.resources.ResourceLocation;
|
|
||||||
import net.minecraft.world.item.Item;
|
|
||||||
import net.minecraft.world.item.ItemStack;
|
|
||||||
import net.minecraft.world.item.TooltipFlag;
|
|
||||||
import net.minecraft.world.level.Level;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Choke Collar - Pet play collar used by Masters.
|
|
||||||
*
|
|
||||||
* <p>Special feature: Can be put in "choke mode" which applies a drowning effect.</p>
|
|
||||||
* <p>Used by Masters for punishment. The effect simulates choking by reducing air supply,
|
|
||||||
* which triggers drowning damage if left active for too long.</p>
|
|
||||||
*
|
|
||||||
* <p><b>Mechanics:</b></p>
|
|
||||||
* <ul>
|
|
||||||
* <li>When choking is active, the wearer's air supply decreases rapidly</li>
|
|
||||||
* <li>This creates the drowning effect (damage and bubble particles)</li>
|
|
||||||
* <li>Masters should deactivate the choke before the pet dies</li>
|
|
||||||
* <li>The choke is controlled by the Master's punishment system</li>
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* @see com.tiedup.remake.entities.ai.master.MasterPunishGoal
|
|
||||||
* @see com.tiedup.remake.events.restriction.PetPlayRestrictionHandler
|
|
||||||
*/
|
|
||||||
public class ItemChokeCollar extends ItemCollar implements IHas3DModelConfig {
|
|
||||||
|
|
||||||
private static final String NBT_CHOKING = "choking";
|
|
||||||
|
|
||||||
private static final Model3DConfig CONFIG = new Model3DConfig(
|
|
||||||
"tiedup:models/obj/choke_collar_leather/model.obj",
|
|
||||||
"tiedup:models/obj/choke_collar_leather/texture.png",
|
|
||||||
0.0f,
|
|
||||||
1.47f,
|
|
||||||
0.0f, // Collar band centered at neck level
|
|
||||||
1.0f,
|
|
||||||
0.0f,
|
|
||||||
0.0f,
|
|
||||||
180.0f, // Flip Y to match rendering convention
|
|
||||||
Set.of()
|
|
||||||
);
|
|
||||||
|
|
||||||
public ItemChokeCollar() {
|
|
||||||
super(new Item.Properties());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getItemName() {
|
|
||||||
return "choke_collar";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if choke mode is active.
|
|
||||||
*
|
|
||||||
* @param stack The collar ItemStack
|
|
||||||
* @return true if choking is active
|
|
||||||
*/
|
|
||||||
public boolean isChoking(ItemStack stack) {
|
|
||||||
CompoundTag tag = stack.getTag();
|
|
||||||
return tag != null && tag.getBoolean(NBT_CHOKING);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set choke mode on/off.
|
|
||||||
* When active, applies drowning effect to wearer (handled by PetPlayRestrictionHandler).
|
|
||||||
*
|
|
||||||
* @param stack The collar ItemStack
|
|
||||||
* @param choking true to activate choking, false to deactivate
|
|
||||||
*/
|
|
||||||
public void setChoking(ItemStack stack, boolean choking) {
|
|
||||||
stack.getOrCreateTag().putBoolean(NBT_CHOKING, choking);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if collar is in pet play mode (from Master).
|
|
||||||
*
|
|
||||||
* @param stack The collar ItemStack
|
|
||||||
* @return true if this is a pet play collar
|
|
||||||
*/
|
|
||||||
public boolean isPetPlayMode(ItemStack stack) {
|
|
||||||
CompoundTag tag = stack.getTag();
|
|
||||||
return tag != null && tag.getBoolean("petPlayMode");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void appendHoverText(
|
|
||||||
ItemStack stack,
|
|
||||||
@Nullable Level level,
|
|
||||||
List<Component> tooltip,
|
|
||||||
TooltipFlag flag
|
|
||||||
) {
|
|
||||||
super.appendHoverText(stack, level, tooltip, flag);
|
|
||||||
|
|
||||||
// Show choke status
|
|
||||||
if (isChoking(stack)) {
|
|
||||||
tooltip.add(
|
|
||||||
Component.literal("CHOKING ACTIVE!")
|
|
||||||
.withStyle(ChatFormatting.DARK_RED)
|
|
||||||
.withStyle(ChatFormatting.BOLD)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show pet play mode status
|
|
||||||
if (isPetPlayMode(stack)) {
|
|
||||||
tooltip.add(
|
|
||||||
Component.literal("Pet Play Mode").withStyle(
|
|
||||||
ChatFormatting.LIGHT_PURPLE
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Description
|
|
||||||
tooltip.add(
|
|
||||||
Component.literal("A special collar used for pet play punishment")
|
|
||||||
.withStyle(ChatFormatting.DARK_GRAY)
|
|
||||||
.withStyle(ChatFormatting.ITALIC)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Choke collar cannot shock like shock collar.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean canShock() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3D Model Support
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean uses3DModel() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ResourceLocation get3DModelLocation() {
|
|
||||||
return ResourceLocation.tryParse(CONFIG.objPath());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Model3DConfig getModelConfig() {
|
|
||||||
return CONFIG;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
package com.tiedup.remake.items;
|
|
||||||
|
|
||||||
import com.tiedup.remake.items.base.ItemCollar;
|
|
||||||
import net.minecraft.world.item.Item;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Classic Collar - Basic collar item
|
|
||||||
* Standard collar for marking ownership.
|
|
||||||
*
|
|
||||||
* Based on original ItemCollar from 1.12.2
|
|
||||||
* Note: Collars have maxStackSize of 1 (unique items)
|
|
||||||
*/
|
|
||||||
public class ItemClassicCollar extends ItemCollar {
|
|
||||||
|
|
||||||
public ItemClassicCollar() {
|
|
||||||
super(
|
|
||||||
new Item.Properties()
|
|
||||||
// stacksTo(1) is set by ItemCollar base class
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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.ModNetwork;
|
import com.tiedup.remake.network.ModNetwork;
|
||||||
|
import com.tiedup.remake.v2.bondage.CollarHelper;
|
||||||
import com.tiedup.remake.network.personality.PacketOpenCommandWandScreen;
|
import com.tiedup.remake.network.personality.PacketOpenCommandWandScreen;
|
||||||
import com.tiedup.remake.personality.HomeType;
|
import com.tiedup.remake.personality.HomeType;
|
||||||
import com.tiedup.remake.personality.JobExperience;
|
import com.tiedup.remake.personality.JobExperience;
|
||||||
@@ -349,11 +349,11 @@ public class ItemCommandWand extends Item {
|
|||||||
|
|
||||||
// Get collar and verify ownership
|
// Get collar and verify ownership
|
||||||
ItemStack collar = damsel.getEquipment(BodyRegionV2.NECK);
|
ItemStack collar = damsel.getEquipment(BodyRegionV2.NECK);
|
||||||
if (!(collar.getItem() instanceof ItemCollar collarItem)) {
|
if (!CollarHelper.isCollar(collar)) {
|
||||||
return InteractionResult.PASS;
|
return InteractionResult.PASS;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!collarItem.getOwners(collar).contains(player.getUUID())) {
|
if (!CollarHelper.isOwner(collar, player)) {
|
||||||
SystemMessageManager.sendToPlayer(
|
SystemMessageManager.sendToPlayer(
|
||||||
player,
|
player,
|
||||||
SystemMessageManager.MessageCategory.ERROR,
|
SystemMessageManager.MessageCategory.ERROR,
|
||||||
|
|||||||
@@ -1,369 +0,0 @@
|
|||||||
package com.tiedup.remake.items;
|
|
||||||
|
|
||||||
import com.tiedup.remake.state.IRestrainable;
|
|
||||||
import com.tiedup.remake.util.KidnappedHelper;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import net.minecraft.ChatFormatting;
|
|
||||||
import net.minecraft.nbt.CompoundTag;
|
|
||||||
import net.minecraft.nbt.ListTag;
|
|
||||||
import net.minecraft.network.chat.Component;
|
|
||||||
import net.minecraft.world.entity.Entity;
|
|
||||||
import net.minecraft.world.entity.LivingEntity;
|
|
||||||
import net.minecraft.world.item.ItemStack;
|
|
||||||
import net.minecraft.world.item.TooltipFlag;
|
|
||||||
import net.minecraft.world.level.Level;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
|
|
||||||
* GPS Collar - Advanced shock collar with tracking and safe zone features.
|
|
||||||
|
|
||||||
*
|
|
||||||
|
|
||||||
* <p>Mechanics:</p>
|
|
||||||
|
|
||||||
* <ul>
|
|
||||||
|
|
||||||
* <li><b>Safe Zones:</b> Can store multiple coordinates (SafeSpots) where the wearer is allowed to be.</li>
|
|
||||||
|
|
||||||
* <li><b>Auto-Shock:</b> If the wearer is outside ALL active safe zones, they are shocked at intervals.</li>
|
|
||||||
|
|
||||||
* <li><b>Master Warning:</b> Masters receive an alert message when a safe zone violation is detected.</li>
|
|
||||||
|
|
||||||
* <li><b>Public Tracking:</b> If enabled, allows anyone with a Locator to see distance and direction.</li>
|
|
||||||
|
|
||||||
* </ul>
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class ItemGpsCollar extends ItemShockCollar {
|
|
||||||
|
|
||||||
private static final String NBT_PUBLIC_TRACKING = "publicTracking";
|
|
||||||
|
|
||||||
private static final String NBT_GPS_ACTIVE = "gpsActive";
|
|
||||||
|
|
||||||
private static final String NBT_SAFE_SPOTS = "gpsSafeSpots";
|
|
||||||
|
|
||||||
private static final String NBT_SHOCK_INTERVAL = "gpsShockInterval";
|
|
||||||
|
|
||||||
private static final String NBT_WARN_MASTERS = "warn_masters";
|
|
||||||
|
|
||||||
private final int defaultInterval;
|
|
||||||
|
|
||||||
public ItemGpsCollar() {
|
|
||||||
this(200); // 10 seconds default
|
|
||||||
}
|
|
||||||
|
|
||||||
public ItemGpsCollar(int defaultInterval) {
|
|
||||||
super();
|
|
||||||
this.defaultInterval = defaultInterval;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasGPS() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
|
|
||||||
* Renders detailed GPS status, safe zone list, and alert settings in the item tooltip.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void appendHoverText(
|
|
||||||
ItemStack stack,
|
|
||||||
@Nullable Level level,
|
|
||||||
List<Component> tooltip,
|
|
||||||
TooltipFlag flag
|
|
||||||
) {
|
|
||||||
super.appendHoverText(stack, level, tooltip, flag);
|
|
||||||
|
|
||||||
tooltip.add(
|
|
||||||
Component.literal("GPS Enabled").withStyle(
|
|
||||||
ChatFormatting.DARK_GREEN
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (hasPublicTracking(stack)) {
|
|
||||||
tooltip.add(
|
|
||||||
Component.literal("Public Tracking Enabled").withStyle(
|
|
||||||
ChatFormatting.GREEN
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shouldWarnMasters(stack)) {
|
|
||||||
tooltip.add(
|
|
||||||
Component.literal("Alert Masters on Violation").withStyle(
|
|
||||||
ChatFormatting.GOLD
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<SafeSpot> safeSpots = getSafeSpots(stack);
|
|
||||||
|
|
||||||
if (!safeSpots.isEmpty()) {
|
|
||||||
tooltip.add(
|
|
||||||
Component.literal("GPS Shocks: ")
|
|
||||||
.withStyle(ChatFormatting.GREEN)
|
|
||||||
.append(
|
|
||||||
Component.literal(
|
|
||||||
isActive(stack) ? "ENABLED" : "DISABLED"
|
|
||||||
).withStyle(
|
|
||||||
isActive(stack)
|
|
||||||
? ChatFormatting.RED
|
|
||||||
: ChatFormatting.GRAY
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
tooltip.add(
|
|
||||||
Component.literal(
|
|
||||||
"Safe Spots (" + safeSpots.size() + "):"
|
|
||||||
).withStyle(ChatFormatting.GREEN)
|
|
||||||
);
|
|
||||||
|
|
||||||
for (int i = 0; i < safeSpots.size(); i++) {
|
|
||||||
SafeSpot spot = safeSpots.get(i);
|
|
||||||
|
|
||||||
tooltip.add(
|
|
||||||
Component.literal(
|
|
||||||
(spot.active ? "[+] " : "[-] ") +
|
|
||||||
(i + 1) +
|
|
||||||
": " +
|
|
||||||
spot.x +
|
|
||||||
"," +
|
|
||||||
spot.y +
|
|
||||||
"," +
|
|
||||||
spot.z +
|
|
||||||
" (Range: " +
|
|
||||||
spot.distance +
|
|
||||||
"m)"
|
|
||||||
).withStyle(ChatFormatting.GRAY)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean shouldWarnMasters(ItemStack stack) {
|
|
||||||
CompoundTag tag = stack.getTag();
|
|
||||||
|
|
||||||
// Default to true if tag doesn't exist
|
|
||||||
|
|
||||||
return (
|
|
||||||
tag == null ||
|
|
||||||
!tag.contains(NBT_WARN_MASTERS) ||
|
|
||||||
tag.getBoolean(NBT_WARN_MASTERS)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setWarnMasters(ItemStack stack, boolean warn) {
|
|
||||||
stack.getOrCreateTag().putBoolean(NBT_WARN_MASTERS, warn);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasPublicTracking(ItemStack stack) {
|
|
||||||
CompoundTag tag = stack.getTag();
|
|
||||||
|
|
||||||
return tag != null && tag.getBoolean(NBT_PUBLIC_TRACKING);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPublicTracking(ItemStack stack, boolean publicTracking) {
|
|
||||||
stack.getOrCreateTag().putBoolean(NBT_PUBLIC_TRACKING, publicTracking);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isActive(ItemStack stack) {
|
|
||||||
CompoundTag tag = stack.getTag();
|
|
||||||
|
|
||||||
// Default to active if tag doesn't exist
|
|
||||||
|
|
||||||
return (
|
|
||||||
tag == null ||
|
|
||||||
!tag.contains(NBT_GPS_ACTIVE) ||
|
|
||||||
tag.getBoolean(NBT_GPS_ACTIVE)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setActive(ItemStack stack, boolean active) {
|
|
||||||
stack.getOrCreateTag().putBoolean(NBT_GPS_ACTIVE, active);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
|
|
||||||
* Parses the NBT List into a Java List of SafeSpot objects.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
public List<SafeSpot> getSafeSpots(ItemStack stack) {
|
|
||||||
List<SafeSpot> list = new ArrayList<>();
|
|
||||||
|
|
||||||
CompoundTag tag = stack.getTag();
|
|
||||||
|
|
||||||
if (tag != null && tag.contains(NBT_SAFE_SPOTS)) {
|
|
||||||
ListTag spotList = tag.getList(NBT_SAFE_SPOTS, 10);
|
|
||||||
|
|
||||||
for (int i = 0; i < spotList.size(); i++) {
|
|
||||||
list.add(new SafeSpot(spotList.getCompound(i)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
|
|
||||||
* Adds a new safe zone to the collar's NBT data.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
public void addSafeSpot(
|
|
||||||
ItemStack stack,
|
|
||||||
int x,
|
|
||||||
int y,
|
|
||||||
int z,
|
|
||||||
String dimension,
|
|
||||||
int distance
|
|
||||||
) {
|
|
||||||
CompoundTag tag = stack.getOrCreateTag();
|
|
||||||
|
|
||||||
ListTag spotList = tag.getList(NBT_SAFE_SPOTS, 10);
|
|
||||||
|
|
||||||
SafeSpot spot = new SafeSpot(x, y, z, dimension, distance, true);
|
|
||||||
|
|
||||||
spotList.add(spot.toNBT());
|
|
||||||
|
|
||||||
tag.put(NBT_SAFE_SPOTS, spotList);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
|
|
||||||
* Gets frequency of GPS violation shocks.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
public int getShockInterval(ItemStack stack) {
|
|
||||||
CompoundTag tag = stack.getTag();
|
|
||||||
|
|
||||||
if (tag != null && tag.contains(NBT_SHOCK_INTERVAL)) {
|
|
||||||
return tag.getInt(NBT_SHOCK_INTERVAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
return defaultInterval;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onUnequipped(ItemStack stack, LivingEntity entity) {
|
|
||||||
// Use IRestrainable interface instead of Player-only
|
|
||||||
IRestrainable state = KidnappedHelper.getKidnappedState(entity);
|
|
||||||
if (state != null) {
|
|
||||||
state.resetAutoShockTimer();
|
|
||||||
}
|
|
||||||
|
|
||||||
super.onUnequipped(stack, entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
|
|
||||||
* Represents a defined safe zone in the 3D world.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
public static class SafeSpot {
|
|
||||||
|
|
||||||
public int x, y, z;
|
|
||||||
|
|
||||||
public String dimension;
|
|
||||||
|
|
||||||
public int distance;
|
|
||||||
|
|
||||||
public boolean active;
|
|
||||||
|
|
||||||
public SafeSpot(
|
|
||||||
int x,
|
|
||||||
int y,
|
|
||||||
int z,
|
|
||||||
String dimension,
|
|
||||||
int distance,
|
|
||||||
boolean active
|
|
||||||
) {
|
|
||||||
this.x = x;
|
|
||||||
|
|
||||||
this.y = y;
|
|
||||||
|
|
||||||
this.z = z;
|
|
||||||
|
|
||||||
this.dimension = dimension;
|
|
||||||
|
|
||||||
this.distance = distance;
|
|
||||||
|
|
||||||
this.active = active;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SafeSpot(CompoundTag nbt) {
|
|
||||||
this.x = nbt.getInt("x");
|
|
||||||
|
|
||||||
this.y = nbt.getInt("y");
|
|
||||||
|
|
||||||
this.z = nbt.getInt("z");
|
|
||||||
|
|
||||||
this.dimension = nbt.getString("dim");
|
|
||||||
|
|
||||||
this.distance = nbt.getInt("dist");
|
|
||||||
|
|
||||||
this.active = !nbt.contains("active") || nbt.getBoolean("active");
|
|
||||||
}
|
|
||||||
|
|
||||||
public CompoundTag toNBT() {
|
|
||||||
CompoundTag nbt = new CompoundTag();
|
|
||||||
|
|
||||||
nbt.putInt("x", x);
|
|
||||||
|
|
||||||
nbt.putInt("y", y);
|
|
||||||
|
|
||||||
nbt.putInt("z", z);
|
|
||||||
|
|
||||||
nbt.putString("dim", dimension);
|
|
||||||
|
|
||||||
nbt.putInt("dist", distance);
|
|
||||||
|
|
||||||
nbt.putBoolean("active", active);
|
|
||||||
|
|
||||||
return nbt;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
|
|
||||||
* Checks if an entity is within the cuboid boundaries of this safe zone.
|
|
||||||
|
|
||||||
* Faithful to original 1.12.2 distance logic.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
public boolean isInside(Entity entity) {
|
|
||||||
if (!active) return true;
|
|
||||||
|
|
||||||
// LOW FIX: Cross-dimension GPS fix
|
|
||||||
// If entity is in a different dimension, consider them as "inside" the zone
|
|
||||||
// to prevent false positive shocks when traveling between dimensions
|
|
||||||
if (
|
|
||||||
!entity
|
|
||||||
.level()
|
|
||||||
.dimension()
|
|
||||||
.location()
|
|
||||||
.toString()
|
|
||||||
.equals(dimension)
|
|
||||||
) return true; // Changed from false to true
|
|
||||||
|
|
||||||
// Cuboid distance check
|
|
||||||
|
|
||||||
return (
|
|
||||||
Math.abs(entity.getX() - x) < distance &&
|
|
||||||
Math.abs(entity.getY() - y) < distance &&
|
|
||||||
Math.abs(entity.getZ() - z) < distance
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
package com.tiedup.remake.items;
|
package com.tiedup.remake.items;
|
||||||
|
|
||||||
import com.tiedup.remake.core.SystemMessageManager;
|
import com.tiedup.remake.core.SystemMessageManager;
|
||||||
import com.tiedup.remake.items.base.ItemCollar;
|
|
||||||
import com.tiedup.remake.items.base.ItemOwnerTarget;
|
import com.tiedup.remake.items.base.ItemOwnerTarget;
|
||||||
|
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;
|
||||||
@@ -91,13 +91,10 @@ public class ItemGpsLocator extends ItemOwnerTarget {
|
|||||||
ItemStack collarStack = targetState.getEquipment(
|
ItemStack collarStack = targetState.getEquipment(
|
||||||
BodyRegionV2.NECK
|
BodyRegionV2.NECK
|
||||||
);
|
);
|
||||||
if (
|
if (CollarHelper.hasGPS(collarStack)) {
|
||||||
collarStack.getItem() instanceof
|
|
||||||
ItemGpsCollar collarItem
|
|
||||||
) {
|
|
||||||
if (
|
if (
|
||||||
collarItem.isOwner(collarStack, player) ||
|
CollarHelper.isOwner(collarStack, player) ||
|
||||||
collarItem.hasPublicTracking(collarStack)
|
CollarHelper.hasPublicTracking(collarStack)
|
||||||
) {
|
) {
|
||||||
// Check if same dimension
|
// Check if same dimension
|
||||||
boolean sameDimension = player
|
boolean sameDimension = player
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
package com.tiedup.remake.items;
|
|
||||||
|
|
||||||
import com.tiedup.remake.items.base.IHasGaggingEffect;
|
|
||||||
import com.tiedup.remake.items.base.ItemBlindfold;
|
|
||||||
import com.tiedup.remake.util.GagMaterial;
|
|
||||||
import net.minecraft.world.item.Item;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hood - Covers the head completely
|
|
||||||
* Combines blindfold effect with gagging effect.
|
|
||||||
*
|
|
||||||
* Extends ItemBlindfold for slot behavior, implements IHasGaggingEffect for speech muffling.
|
|
||||||
*/
|
|
||||||
public class ItemHood extends ItemBlindfold implements IHasGaggingEffect {
|
|
||||||
|
|
||||||
private final GagMaterial gagMaterial;
|
|
||||||
|
|
||||||
public ItemHood() {
|
|
||||||
super(new Item.Properties().stacksTo(16));
|
|
||||||
this.gagMaterial = GagMaterial.STUFFED; // Hoods muffle speech like stuffed gags
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the gag material type for speech conversion.
|
|
||||||
* @return The gag material (STUFFED for hoods)
|
|
||||||
*/
|
|
||||||
public GagMaterial getGagMaterial() {
|
|
||||||
return gagMaterial;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getTextureSubfolder() {
|
|
||||||
return "hoods";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -259,13 +259,10 @@ public class ItemKey extends ItemOwnerTarget {
|
|||||||
ItemStack collarStack = targetState.getEquipment(BodyRegionV2.NECK);
|
ItemStack collarStack = targetState.getEquipment(BodyRegionV2.NECK);
|
||||||
if (collarStack.isEmpty()) return;
|
if (collarStack.isEmpty()) return;
|
||||||
|
|
||||||
if (
|
if (com.tiedup.remake.v2.bondage.CollarHelper.isCollar(collarStack)) {
|
||||||
collarStack.getItem() instanceof
|
|
||||||
com.tiedup.remake.items.base.ItemCollar collar
|
|
||||||
) {
|
|
||||||
// Add player as owner to the collar (if not already)
|
// Add player as owner to the collar (if not already)
|
||||||
if (!collar.getOwners(collarStack).contains(player.getUUID())) {
|
if (!com.tiedup.remake.v2.bondage.CollarHelper.isOwner(collarStack, player)) {
|
||||||
collar.addOwner(collarStack, player);
|
com.tiedup.remake.v2.bondage.CollarHelper.addOwner(collarStack, player);
|
||||||
|
|
||||||
// Update the collar in the target's inventory
|
// Update the collar in the target's inventory
|
||||||
targetState.equip(BodyRegionV2.NECK, collarStack);
|
targetState.equip(BodyRegionV2.NECK, collarStack);
|
||||||
|
|||||||
@@ -332,15 +332,12 @@ public class ItemLockpick extends Item {
|
|||||||
);
|
);
|
||||||
if (collar.isEmpty()) return;
|
if (collar.isEmpty()) return;
|
||||||
|
|
||||||
if (
|
if (com.tiedup.remake.v2.bondage.CollarHelper.canShock(collar)) {
|
||||||
collar.getItem() instanceof
|
|
||||||
com.tiedup.remake.items.ItemShockCollar shockCollar
|
|
||||||
) {
|
|
||||||
// Shock the player
|
// Shock the player
|
||||||
state.shockKidnapped(" (Failed lockpick attempt)", 2.0f);
|
state.shockKidnapped(" (Failed lockpick attempt)", 2.0f);
|
||||||
|
|
||||||
// Notify owners
|
// Notify owners
|
||||||
notifyOwnersLockpickAttempt(player, collar, shockCollar);
|
notifyOwnersLockpickAttempt(player, collar);
|
||||||
|
|
||||||
TiedUpMod.LOGGER.info(
|
TiedUpMod.LOGGER.info(
|
||||||
"[LOCKPICK] {} was shocked for failed lockpick attempt",
|
"[LOCKPICK] {} was shocked for failed lockpick attempt",
|
||||||
@@ -354,8 +351,7 @@ public class ItemLockpick extends Item {
|
|||||||
*/
|
*/
|
||||||
private static void notifyOwnersLockpickAttempt(
|
private static void notifyOwnersLockpickAttempt(
|
||||||
Player player,
|
Player player,
|
||||||
ItemStack collar,
|
ItemStack collar
|
||||||
com.tiedup.remake.items.ItemShockCollar shockCollar
|
|
||||||
) {
|
) {
|
||||||
if (player.getServer() == null) return;
|
if (player.getServer() == null) return;
|
||||||
|
|
||||||
@@ -367,7 +363,7 @@ public class ItemLockpick extends Item {
|
|||||||
).withStyle(ChatFormatting.GOLD)
|
).withStyle(ChatFormatting.GOLD)
|
||||||
);
|
);
|
||||||
|
|
||||||
List<UUID> owners = shockCollar.getOwners(collar);
|
List<UUID> owners = com.tiedup.remake.v2.bondage.CollarHelper.getOwners(collar);
|
||||||
for (UUID ownerId : owners) {
|
for (UUID ownerId : owners) {
|
||||||
ServerPlayer owner = player
|
ServerPlayer owner = player
|
||||||
.getServer()
|
.getServer()
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
package com.tiedup.remake.items;
|
|
||||||
|
|
||||||
import com.tiedup.remake.items.base.IHasBlindingEffect;
|
|
||||||
import com.tiedup.remake.items.base.ItemGag;
|
|
||||||
import com.tiedup.remake.util.GagMaterial;
|
|
||||||
import net.minecraft.world.item.Item;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Medical Gag - Full face medical restraint
|
|
||||||
* Combines gag effect with blinding effect.
|
|
||||||
*
|
|
||||||
* Extends ItemGag for slot behavior, implements IHasBlindingEffect for vision obstruction.
|
|
||||||
*/
|
|
||||||
public class ItemMedicalGag extends ItemGag implements IHasBlindingEffect {
|
|
||||||
|
|
||||||
public ItemMedicalGag() {
|
|
||||||
super(new Item.Properties().stacksTo(16), GagMaterial.PANEL);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getTextureSubfolder() {
|
|
||||||
return "straps";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
package com.tiedup.remake.items;
|
|
||||||
|
|
||||||
import com.tiedup.remake.core.SystemMessageManager;
|
|
||||||
import com.tiedup.remake.items.base.ItemCollar;
|
|
||||||
import com.tiedup.remake.state.IBondageState;
|
|
||||||
import com.tiedup.remake.util.KidnappedHelper;
|
|
||||||
import java.util.List;
|
|
||||||
import net.minecraft.ChatFormatting;
|
|
||||||
import net.minecraft.nbt.CompoundTag;
|
|
||||||
import net.minecraft.network.chat.Component;
|
|
||||||
import net.minecraft.world.InteractionHand;
|
|
||||||
import net.minecraft.world.InteractionResultHolder;
|
|
||||||
import net.minecraft.world.entity.LivingEntity;
|
|
||||||
import net.minecraft.world.entity.player.Player;
|
|
||||||
import net.minecraft.world.item.Item;
|
|
||||||
import net.minecraft.world.item.ItemStack;
|
|
||||||
import net.minecraft.world.item.TooltipFlag;
|
|
||||||
import net.minecraft.world.level.Level;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shock Collar - Advanced collar that can be remotely triggered.
|
|
||||||
*
|
|
||||||
* <p>Mechanics:</p>
|
|
||||||
* <ul>
|
|
||||||
* <li><b>Remote Shocking:</b> Can be triggered by anyone holding a linked Shocker Controller.</li>
|
|
||||||
* <li><b>Struggle Penalty:</b> If locked, has a chance to shock the wearer during struggle attempts, interrupting them.</li>
|
|
||||||
* <li><b>Public Mode:</b> Can be set to public mode, allowing anyone to shock the wearer even if they aren't the owner.</li>
|
|
||||||
* </ul>
|
|
||||||
*/
|
|
||||||
public class ItemShockCollar extends ItemCollar {
|
|
||||||
|
|
||||||
private static final String NBT_PUBLIC_MODE = "public_mode";
|
|
||||||
|
|
||||||
public ItemShockCollar() {
|
|
||||||
super(new Item.Properties());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canShock() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows current mode (PUBLIC/PRIVATE) and usage instructions in tooltip.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void appendHoverText(
|
|
||||||
ItemStack stack,
|
|
||||||
@Nullable Level level,
|
|
||||||
List<Component> tooltip,
|
|
||||||
TooltipFlag flag
|
|
||||||
) {
|
|
||||||
super.appendHoverText(stack, level, tooltip, flag);
|
|
||||||
|
|
||||||
tooltip.add(
|
|
||||||
Component.literal("Shock Feature: ")
|
|
||||||
.withStyle(ChatFormatting.YELLOW)
|
|
||||||
.append(
|
|
||||||
Component.literal(
|
|
||||||
isPublic(stack) ? "PUBLIC" : "PRIVATE"
|
|
||||||
).withStyle(
|
|
||||||
isPublic(stack)
|
|
||||||
? ChatFormatting.GREEN
|
|
||||||
: ChatFormatting.RED
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
tooltip.add(
|
|
||||||
Component.literal("Shift + Right-click to toggle public mode")
|
|
||||||
.withStyle(ChatFormatting.DARK_GRAY)
|
|
||||||
.withStyle(ChatFormatting.ITALIC)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggles Public mode when shift-right-clicking in air.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public InteractionResultHolder<ItemStack> use(
|
|
||||||
Level level,
|
|
||||||
Player player,
|
|
||||||
InteractionHand hand
|
|
||||||
) {
|
|
||||||
ItemStack stack = player.getItemInHand(hand);
|
|
||||||
|
|
||||||
if (player.isShiftKeyDown()) {
|
|
||||||
if (!level.isClientSide) {
|
|
||||||
boolean newState = !isPublic(stack);
|
|
||||||
setPublic(stack, newState);
|
|
||||||
SystemMessageManager.sendToPlayer(
|
|
||||||
player,
|
|
||||||
SystemMessageManager.MessageCategory.SHOCKER_MODE_SET,
|
|
||||||
(newState ? "PUBLIC" : "PRIVATE")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return InteractionResultHolder.sidedSuccess(
|
|
||||||
stack,
|
|
||||||
level.isClientSide()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.use(level, player, hand);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the risk of shocking the wearer during a struggle attempt.
|
|
||||||
*
|
|
||||||
* NOTE: For the new continuous struggle mini-game, shock logic is handled
|
|
||||||
* directly in MiniGameSessionManager.tickContinuousSessions(). This method
|
|
||||||
* is now a no-op that always returns true, kept for API compatibility.
|
|
||||||
*
|
|
||||||
* @param entity The wearer of the collar
|
|
||||||
* @param stack The collar instance
|
|
||||||
* @return Always true (shock logic moved to MiniGameSessionManager)
|
|
||||||
*/
|
|
||||||
public boolean notifyStruggle(LivingEntity entity, ItemStack stack) {
|
|
||||||
// Shock collar checks during continuous struggle are now handled by
|
|
||||||
// MiniGameSessionManager.shouldTriggerShock() with 10% chance every 5 seconds.
|
|
||||||
// This method is kept for backwards compatibility but no longer performs the check.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isPublic(ItemStack stack) {
|
|
||||||
CompoundTag tag = stack.getTag();
|
|
||||||
return tag != null && tag.getBoolean(NBT_PUBLIC_MODE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPublic(ItemStack stack, boolean publicMode) {
|
|
||||||
stack.getOrCreateTag().putBoolean(NBT_PUBLIC_MODE, publicMode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
package com.tiedup.remake.items;
|
|
||||||
|
|
||||||
import com.tiedup.remake.state.IRestrainable;
|
|
||||||
import com.tiedup.remake.util.KidnappedHelper;
|
|
||||||
import net.minecraft.world.entity.LivingEntity;
|
|
||||||
import net.minecraft.world.item.ItemStack;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Automatic Shock Collar - Shocks the wearer at regular intervals.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* <p>Mechanics:</p>
|
|
||||||
* <ul>
|
|
||||||
* <li><b>Self-Triggering:</b> Has an internal timer stored in NBT that shocks the entity when it reaches 0.</li>
|
|
||||||
* <li><b>Unstruggable:</b> By default, cannot be escaped via struggle mechanics (requires key).</li>
|
|
||||||
* </ul>
|
|
||||||
*/
|
|
||||||
public class ItemShockCollarAuto extends ItemShockCollar {
|
|
||||||
|
|
||||||
private final int interval;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param interval Frequency of shocks in TICKS (20 ticks = 1 second).
|
|
||||||
*/
|
|
||||||
public ItemShockCollarAuto() {
|
|
||||||
this(600); // 30 seconds default
|
|
||||||
}
|
|
||||||
|
|
||||||
public ItemShockCollarAuto(int interval) {
|
|
||||||
super();
|
|
||||||
this.interval = interval;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getInterval() {
|
|
||||||
return interval;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensures the internal shock timer is cleaned up when the item is removed.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onUnequipped(ItemStack stack, LivingEntity entity) {
|
|
||||||
IRestrainable state = KidnappedHelper.getKidnappedState(entity);
|
|
||||||
if (state != null) {
|
|
||||||
state.resetAutoShockTimer();
|
|
||||||
}
|
|
||||||
super.onUnequipped(stack, entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prevents escaping through struggle mechanics for this specific collar type.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean canBeStruggledOut(ItemStack stack) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,8 +3,8 @@ package com.tiedup.remake.items;
|
|||||||
import com.tiedup.remake.core.ModSounds;
|
import com.tiedup.remake.core.ModSounds;
|
||||||
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.base.ItemCollar;
|
|
||||||
import com.tiedup.remake.items.base.ItemOwnerTarget;
|
import com.tiedup.remake.items.base.ItemOwnerTarget;
|
||||||
|
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;
|
||||||
@@ -77,12 +77,11 @@ public class ItemShockerController extends ItemOwnerTarget {
|
|||||||
BodyRegionV2.NECK
|
BodyRegionV2.NECK
|
||||||
);
|
);
|
||||||
if (
|
if (
|
||||||
collar.getItem() instanceof
|
CollarHelper.isCollar(collar) &&
|
||||||
ItemCollar collarItem &&
|
CollarHelper.hasNickname(collar)
|
||||||
collarItem.hasNickname(collar)
|
|
||||||
) {
|
) {
|
||||||
displayName =
|
displayName =
|
||||||
collarItem.getNickname(collar) +
|
CollarHelper.getNickname(collar) +
|
||||||
" (" +
|
" (" +
|
||||||
displayName +
|
displayName +
|
||||||
")";
|
")";
|
||||||
@@ -330,12 +329,10 @@ public class ItemShockerController extends ItemOwnerTarget {
|
|||||||
IRestrainable state = KidnappedHelper.getKidnappedState(entity);
|
IRestrainable state = KidnappedHelper.getKidnappedState(entity);
|
||||||
if (state != null && state.hasCollar()) {
|
if (state != null && state.hasCollar()) {
|
||||||
ItemStack collarStack = state.getEquipment(BodyRegionV2.NECK);
|
ItemStack collarStack = state.getEquipment(BodyRegionV2.NECK);
|
||||||
if (
|
if (CollarHelper.canShock(collarStack)) {
|
||||||
collarStack.getItem() instanceof ItemShockCollar collarItem
|
|
||||||
) {
|
|
||||||
if (
|
if (
|
||||||
collarItem.getOwners(collarStack).contains(ownerId) ||
|
CollarHelper.isOwner(collarStack, ownerId) ||
|
||||||
collarItem.isPublic(collarStack)
|
CollarHelper.isPublicShock(collarStack)
|
||||||
) {
|
) {
|
||||||
targets.add(entity);
|
targets.add(entity);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ package com.tiedup.remake.items;
|
|||||||
|
|
||||||
import com.tiedup.remake.blocks.ModBlocks;
|
import com.tiedup.remake.blocks.ModBlocks;
|
||||||
import com.tiedup.remake.core.TiedUpMod;
|
import com.tiedup.remake.core.TiedUpMod;
|
||||||
import com.tiedup.remake.entities.KidnapperItemSelector;
|
|
||||||
import com.tiedup.remake.items.base.*;
|
import com.tiedup.remake.items.base.*;
|
||||||
import com.tiedup.remake.v2.V2Items;
|
import com.tiedup.remake.v2.V2Items;
|
||||||
|
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.core.registries.Registries;
|
import net.minecraft.core.registries.Registries;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
import net.minecraft.world.item.CreativeModeTab;
|
import net.minecraft.world.item.CreativeModeTab;
|
||||||
@@ -16,7 +18,7 @@ import net.minecraftforge.registries.RegistryObject;
|
|||||||
* Creative Mode Tabs Registration
|
* Creative Mode Tabs Registration
|
||||||
* Defines the creative inventory tabs where TiedUp items will appear.
|
* Defines the creative inventory tabs where TiedUp items will appear.
|
||||||
*
|
*
|
||||||
* Updated to use factory pattern with enum-based item registration.
|
* All bondage items are now sourced from DataDrivenItemRegistry.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("null") // Minecraft API guarantees non-null returns
|
@SuppressWarnings("null") // Minecraft API guarantees non-null returns
|
||||||
public class ModCreativeTabs {
|
public class ModCreativeTabs {
|
||||||
@@ -28,126 +30,27 @@ public class ModCreativeTabs {
|
|||||||
CREATIVE_MODE_TABS.register("tiedup_tab", () ->
|
CREATIVE_MODE_TABS.register("tiedup_tab", () ->
|
||||||
CreativeModeTab.builder()
|
CreativeModeTab.builder()
|
||||||
.title(Component.translatable("itemGroup.tiedup"))
|
.title(Component.translatable("itemGroup.tiedup"))
|
||||||
.icon(() -> new ItemStack(ModItems.getBind(BindVariant.ROPES)))
|
.icon(() -> {
|
||||||
|
// Use first data-driven item as icon, or fallback to whip
|
||||||
|
var allDefs = DataDrivenItemRegistry.getAll();
|
||||||
|
if (!allDefs.isEmpty()) {
|
||||||
|
return DataDrivenBondageItem.createStack(allDefs.iterator().next().id());
|
||||||
|
}
|
||||||
|
return new ItemStack(ModItems.WHIP.get());
|
||||||
|
})
|
||||||
.displayItems((parameters, output) -> {
|
.displayItems((parameters, output) -> {
|
||||||
// ========== BINDS (from enum) ==========
|
// ========== DATA-DRIVEN BONDAGE ITEMS ==========
|
||||||
for (BindVariant variant : BindVariant.values()) {
|
// All binds, gags, blindfolds, earplugs, mittens, collars,
|
||||||
// Add base item
|
// hood, medical gag, etc. are now data-driven
|
||||||
output.accept(ModItems.getBind(variant));
|
for (DataDrivenItemDefinition def : DataDrivenItemRegistry.getAll()) {
|
||||||
|
output.accept(
|
||||||
// Add colored variants if supported
|
DataDrivenBondageItem.createStack(def.id())
|
||||||
if (variant.supportsColor()) {
|
);
|
||||||
for (ItemColor color : ItemColor.values()) {
|
|
||||||
// Skip special colors (caution, clear) except for duct tape
|
|
||||||
if (
|
|
||||||
color.isSpecial() &&
|
|
||||||
variant != BindVariant.DUCT_TAPE
|
|
||||||
) continue;
|
|
||||||
// Use validation method to check if color has texture
|
|
||||||
if (
|
|
||||||
KidnapperItemSelector.isColorValidForBind(
|
|
||||||
color,
|
|
||||||
variant
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
output.accept(
|
|
||||||
KidnapperItemSelector.createBind(
|
|
||||||
variant,
|
|
||||||
color
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== GAGS (from enum) ==========
|
|
||||||
for (GagVariant variant : GagVariant.values()) {
|
|
||||||
// Add base item
|
|
||||||
output.accept(ModItems.getGag(variant));
|
|
||||||
|
|
||||||
// Add colored variants if supported
|
|
||||||
if (variant.supportsColor()) {
|
|
||||||
for (ItemColor color : ItemColor.values()) {
|
|
||||||
// Skip special colors (caution, clear) except for tape gag
|
|
||||||
if (
|
|
||||||
color.isSpecial() &&
|
|
||||||
variant != GagVariant.TAPE_GAG
|
|
||||||
) continue;
|
|
||||||
// Use validation method to check if color has texture
|
|
||||||
if (
|
|
||||||
KidnapperItemSelector.isColorValidForGag(
|
|
||||||
color,
|
|
||||||
variant
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
output.accept(
|
|
||||||
KidnapperItemSelector.createGag(
|
|
||||||
variant,
|
|
||||||
color
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== BLINDFOLDS (from enum) ==========
|
|
||||||
for (BlindfoldVariant variant : BlindfoldVariant.values()) {
|
|
||||||
// Add base item
|
|
||||||
output.accept(ModItems.getBlindfold(variant));
|
|
||||||
|
|
||||||
// Add colored variants if supported
|
|
||||||
if (variant.supportsColor()) {
|
|
||||||
for (ItemColor color : ItemColor.values()) {
|
|
||||||
// Skip special colors for blindfolds
|
|
||||||
if (color.isSpecial()) continue;
|
|
||||||
// Use validation method to check if color has texture
|
|
||||||
if (
|
|
||||||
KidnapperItemSelector.isColorValidForBlindfold(
|
|
||||||
color,
|
|
||||||
variant
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
output.accept(
|
|
||||||
KidnapperItemSelector.createBlindfold(
|
|
||||||
variant,
|
|
||||||
color
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hood (combo item, not in enum)
|
|
||||||
output.accept(ModItems.HOOD.get());
|
|
||||||
|
|
||||||
// ========== 3D ITEMS ==========
|
|
||||||
output.accept(ModItems.BALL_GAG_3D.get());
|
|
||||||
|
|
||||||
// ========== COMBO ITEMS ==========
|
|
||||||
output.accept(ModItems.MEDICAL_GAG.get());
|
|
||||||
|
|
||||||
// ========== CLOTHES ==========
|
// ========== CLOTHES ==========
|
||||||
output.accept(ModItems.CLOTHES.get());
|
output.accept(ModItems.CLOTHES.get());
|
||||||
|
|
||||||
// ========== COLLARS ==========
|
|
||||||
output.accept(ModItems.CLASSIC_COLLAR.get());
|
|
||||||
output.accept(ModItems.CHOKE_COLLAR.get());
|
|
||||||
output.accept(ModItems.SHOCK_COLLAR.get());
|
|
||||||
output.accept(ModItems.GPS_COLLAR.get());
|
|
||||||
|
|
||||||
// ========== EARPLUGS (from enum) ==========
|
|
||||||
for (EarplugsVariant variant : EarplugsVariant.values()) {
|
|
||||||
output.accept(ModItems.getEarplugs(variant));
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== MITTENS (from enum) ==========
|
|
||||||
for (MittensVariant variant : MittensVariant.values()) {
|
|
||||||
output.accept(ModItems.getMittens(variant));
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== KNIVES (from enum) ==========
|
// ========== KNIVES (from enum) ==========
|
||||||
for (KnifeVariant variant : KnifeVariant.values()) {
|
for (KnifeVariant variant : KnifeVariant.values()) {
|
||||||
output.accept(ModItems.getKnife(variant));
|
output.accept(ModItems.getKnife(variant));
|
||||||
@@ -196,15 +99,6 @@ public class ModCreativeTabs {
|
|||||||
com.tiedup.remake.v2.bondage.V2BondageItems.V2_HANDCUFFS.get()
|
com.tiedup.remake.v2.bondage.V2BondageItems.V2_HANDCUFFS.get()
|
||||||
);
|
);
|
||||||
|
|
||||||
// ========== DATA-DRIVEN BONDAGE ITEMS ==========
|
|
||||||
for (com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemDefinition def : com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemRegistry.getAll()) {
|
|
||||||
output.accept(
|
|
||||||
com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem.createStack(
|
|
||||||
def.id()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== FURNITURE PLACER ITEMS ==========
|
// ========== FURNITURE PLACER ITEMS ==========
|
||||||
for (com.tiedup.remake.v2.furniture.FurnitureDefinition def : com.tiedup.remake.v2.furniture.FurnitureRegistry.getAll()) {
|
for (com.tiedup.remake.v2.furniture.FurnitureDefinition def : com.tiedup.remake.v2.furniture.FurnitureRegistry.getAll()) {
|
||||||
output.accept(
|
output.accept(
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package com.tiedup.remake.items;
|
|||||||
import com.tiedup.remake.core.TiedUpMod;
|
import com.tiedup.remake.core.TiedUpMod;
|
||||||
import com.tiedup.remake.entities.ModEntities;
|
import com.tiedup.remake.entities.ModEntities;
|
||||||
import com.tiedup.remake.items.base.*;
|
import com.tiedup.remake.items.base.*;
|
||||||
import com.tiedup.remake.items.bondage3d.gags.ItemBallGag3D;
|
|
||||||
import com.tiedup.remake.items.clothes.GenericClothes;
|
import com.tiedup.remake.items.clothes.GenericClothes;
|
||||||
import java.util.EnumMap;
|
import java.util.EnumMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -17,14 +16,9 @@ import net.minecraftforge.registries.RegistryObject;
|
|||||||
* Mod Items Registration
|
* Mod Items Registration
|
||||||
* Handles registration of all TiedUp items using DeferredRegister.
|
* Handles registration of all TiedUp items using DeferredRegister.
|
||||||
*
|
*
|
||||||
* Refactored with Factory Pattern:
|
* V1 bondage items (binds, gags, blindfolds, earplugs, mittens, collars, hood, medical gag)
|
||||||
* - Binds, Gags, Blindfolds, Earplugs, Knives use EnumMaps and factory methods
|
* have been removed. All bondage items are now data-driven via DataDrivenItemRegistry.
|
||||||
* - Complex items (collars, whip, chloroform, etc.) remain individual registrations
|
* Only non-bondage items and knives remain here.
|
||||||
*
|
|
||||||
* Usage:
|
|
||||||
* - ModItems.getBind(BindVariant.ROPES) - Get a specific bind item
|
|
||||||
* - ModItems.getGag(GagVariant.BALL_GAG) - Get a specific gag item
|
|
||||||
* - ModItems.WHIP.get() - Get complex items directly
|
|
||||||
*/
|
*/
|
||||||
public class ModItems {
|
public class ModItems {
|
||||||
|
|
||||||
@@ -34,42 +28,7 @@ public class ModItems {
|
|||||||
TiedUpMod.MOD_ID
|
TiedUpMod.MOD_ID
|
||||||
);
|
);
|
||||||
|
|
||||||
// ========== FACTORY-BASED ITEMS ==========
|
// ========== KNIVES (still V1 — not bondage items) ==========
|
||||||
|
|
||||||
/**
|
|
||||||
* All bind items (15 variants via BindVariant enum)
|
|
||||||
*/
|
|
||||||
public static final Map<BindVariant, RegistryObject<Item>> BINDS =
|
|
||||||
registerAllBinds();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All gag items (via GagVariant enum)
|
|
||||||
* Note: ItemMedicalGag is registered separately as it has special behavior
|
|
||||||
* Note: BALL_GAG_3D is a separate 3D item (not in enum)
|
|
||||||
*/
|
|
||||||
public static final Map<GagVariant, RegistryObject<Item>> GAGS =
|
|
||||||
registerAllGags();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ball Gag 3D - Uses 3D OBJ model rendering via dedicated class.
|
|
||||||
* This is a separate item from BALL_GAG (which uses 2D textures).
|
|
||||||
*/
|
|
||||||
public static final RegistryObject<Item> BALL_GAG_3D = ITEMS.register(
|
|
||||||
"ball_gag_3d",
|
|
||||||
ItemBallGag3D::new
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All blindfold items (2 variants via BlindfoldVariant enum)
|
|
||||||
*/
|
|
||||||
public static final Map<BlindfoldVariant, RegistryObject<Item>> BLINDFOLDS =
|
|
||||||
registerAllBlindfolds();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All earplugs items (1 variant via EarplugsVariant enum)
|
|
||||||
*/
|
|
||||||
public static final Map<EarplugsVariant, RegistryObject<Item>> EARPLUGS =
|
|
||||||
registerAllEarplugs();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All knife items (3 variants via KnifeVariant enum)
|
* All knife items (3 variants via KnifeVariant enum)
|
||||||
@@ -77,12 +36,6 @@ public class ModItems {
|
|||||||
public static final Map<KnifeVariant, RegistryObject<Item>> KNIVES =
|
public static final Map<KnifeVariant, RegistryObject<Item>> KNIVES =
|
||||||
registerAllKnives();
|
registerAllKnives();
|
||||||
|
|
||||||
/**
|
|
||||||
* All mittens items (1 variant via MittensVariant enum)
|
|
||||||
*/
|
|
||||||
public static final Map<MittensVariant, RegistryObject<Item>> MITTENS =
|
|
||||||
registerAllMittens();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clothes item - uses dynamic textures from URLs.
|
* Clothes item - uses dynamic textures from URLs.
|
||||||
* Users can create presets via anvil naming.
|
* Users can create presets via anvil naming.
|
||||||
@@ -92,48 +45,8 @@ public class ModItems {
|
|||||||
GenericClothes::new
|
GenericClothes::new
|
||||||
);
|
);
|
||||||
|
|
||||||
// ========== COMPLEX ITEMS (individual registrations) ==========
|
// ========== TOOLS ==========
|
||||||
|
|
||||||
// Medical gag - combo item with IHasBlindingEffect
|
|
||||||
public static final RegistryObject<Item> MEDICAL_GAG = ITEMS.register(
|
|
||||||
"medical_gag",
|
|
||||||
ItemMedicalGag::new
|
|
||||||
);
|
|
||||||
|
|
||||||
// Hood - combo item
|
|
||||||
public static final RegistryObject<Item> HOOD = ITEMS.register(
|
|
||||||
"hood",
|
|
||||||
ItemHood::new
|
|
||||||
);
|
|
||||||
|
|
||||||
// Collars - complex logic
|
|
||||||
public static final RegistryObject<Item> CLASSIC_COLLAR = ITEMS.register(
|
|
||||||
"classic_collar",
|
|
||||||
ItemClassicCollar::new
|
|
||||||
);
|
|
||||||
|
|
||||||
public static final RegistryObject<Item> SHOCK_COLLAR = ITEMS.register(
|
|
||||||
"shock_collar",
|
|
||||||
ItemShockCollar::new
|
|
||||||
);
|
|
||||||
|
|
||||||
public static final RegistryObject<Item> SHOCK_COLLAR_AUTO = ITEMS.register(
|
|
||||||
"shock_collar_auto",
|
|
||||||
ItemShockCollarAuto::new
|
|
||||||
);
|
|
||||||
|
|
||||||
public static final RegistryObject<Item> GPS_COLLAR = ITEMS.register(
|
|
||||||
"gps_collar",
|
|
||||||
ItemGpsCollar::new
|
|
||||||
);
|
|
||||||
|
|
||||||
// Choke Collar - Pet play collar used by Masters
|
|
||||||
public static final RegistryObject<Item> CHOKE_COLLAR = ITEMS.register(
|
|
||||||
"choke_collar",
|
|
||||||
ItemChokeCollar::new
|
|
||||||
);
|
|
||||||
|
|
||||||
// Tools with complex behavior
|
|
||||||
public static final RegistryObject<Item> WHIP = ITEMS.register(
|
public static final RegistryObject<Item> WHIP = ITEMS.register(
|
||||||
"whip",
|
"whip",
|
||||||
ItemWhip::new
|
ItemWhip::new
|
||||||
@@ -213,13 +126,11 @@ public class ModItems {
|
|||||||
|
|
||||||
// ========== CELL SYSTEM ITEMS ==========
|
// ========== CELL SYSTEM ITEMS ==========
|
||||||
|
|
||||||
// Admin Wand - Structure marker placement and Cell Core management
|
|
||||||
public static final RegistryObject<Item> ADMIN_WAND = ITEMS.register(
|
public static final RegistryObject<Item> ADMIN_WAND = ITEMS.register(
|
||||||
"admin_wand",
|
"admin_wand",
|
||||||
ItemAdminWand::new
|
ItemAdminWand::new
|
||||||
);
|
);
|
||||||
|
|
||||||
// Cell Key - Universal key for iron bar doors
|
|
||||||
public static final RegistryObject<Item> CELL_KEY = ITEMS.register(
|
public static final RegistryObject<Item> CELL_KEY = ITEMS.register(
|
||||||
"cell_key",
|
"cell_key",
|
||||||
ItemCellKey::new
|
ItemCellKey::new
|
||||||
@@ -227,7 +138,6 @@ public class ModItems {
|
|||||||
|
|
||||||
// ========== SLAVE TRADER SYSTEM ==========
|
// ========== SLAVE TRADER SYSTEM ==========
|
||||||
|
|
||||||
// Token - Access pass for kidnapper camps
|
|
||||||
public static final RegistryObject<Item> TOKEN = ITEMS.register(
|
public static final RegistryObject<Item> TOKEN = ITEMS.register(
|
||||||
"token",
|
"token",
|
||||||
ItemToken::new
|
ItemToken::new
|
||||||
@@ -252,72 +162,6 @@ public class ModItems {
|
|||||||
|
|
||||||
// ========== FACTORY METHODS ==========
|
// ========== FACTORY METHODS ==========
|
||||||
|
|
||||||
private static Map<BindVariant, RegistryObject<Item>> registerAllBinds() {
|
|
||||||
Map<BindVariant, RegistryObject<Item>> map = new EnumMap<>(
|
|
||||||
BindVariant.class
|
|
||||||
);
|
|
||||||
for (BindVariant variant : BindVariant.values()) {
|
|
||||||
map.put(
|
|
||||||
variant,
|
|
||||||
ITEMS.register(variant.getRegistryName(), () ->
|
|
||||||
new GenericBind(variant)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Map<GagVariant, RegistryObject<Item>> registerAllGags() {
|
|
||||||
Map<GagVariant, RegistryObject<Item>> map = new EnumMap<>(
|
|
||||||
GagVariant.class
|
|
||||||
);
|
|
||||||
for (GagVariant variant : GagVariant.values()) {
|
|
||||||
map.put(
|
|
||||||
variant,
|
|
||||||
ITEMS.register(variant.getRegistryName(), () ->
|
|
||||||
new GenericGag(variant)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Map<
|
|
||||||
BlindfoldVariant,
|
|
||||||
RegistryObject<Item>
|
|
||||||
> registerAllBlindfolds() {
|
|
||||||
Map<BlindfoldVariant, RegistryObject<Item>> map = new EnumMap<>(
|
|
||||||
BlindfoldVariant.class
|
|
||||||
);
|
|
||||||
for (BlindfoldVariant variant : BlindfoldVariant.values()) {
|
|
||||||
map.put(
|
|
||||||
variant,
|
|
||||||
ITEMS.register(variant.getRegistryName(), () ->
|
|
||||||
new GenericBlindfold(variant)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Map<
|
|
||||||
EarplugsVariant,
|
|
||||||
RegistryObject<Item>
|
|
||||||
> registerAllEarplugs() {
|
|
||||||
Map<EarplugsVariant, RegistryObject<Item>> map = new EnumMap<>(
|
|
||||||
EarplugsVariant.class
|
|
||||||
);
|
|
||||||
for (EarplugsVariant variant : EarplugsVariant.values()) {
|
|
||||||
map.put(
|
|
||||||
variant,
|
|
||||||
ITEMS.register(variant.getRegistryName(), () ->
|
|
||||||
new GenericEarplugs(variant)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Map<KnifeVariant, RegistryObject<Item>> registerAllKnives() {
|
private static Map<KnifeVariant, RegistryObject<Item>> registerAllKnives() {
|
||||||
Map<KnifeVariant, RegistryObject<Item>> map = new EnumMap<>(
|
Map<KnifeVariant, RegistryObject<Item>> map = new EnumMap<>(
|
||||||
KnifeVariant.class
|
KnifeVariant.class
|
||||||
@@ -333,62 +177,8 @@ public class ModItems {
|
|||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Map<
|
|
||||||
MittensVariant,
|
|
||||||
RegistryObject<Item>
|
|
||||||
> registerAllMittens() {
|
|
||||||
Map<MittensVariant, RegistryObject<Item>> map = new EnumMap<>(
|
|
||||||
MittensVariant.class
|
|
||||||
);
|
|
||||||
for (MittensVariant variant : MittensVariant.values()) {
|
|
||||||
map.put(
|
|
||||||
variant,
|
|
||||||
ITEMS.register(variant.getRegistryName(), () ->
|
|
||||||
new GenericMittens(variant)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== HELPER ACCESSORS ==========
|
// ========== HELPER ACCESSORS ==========
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a bind item by variant.
|
|
||||||
* @param variant The bind variant
|
|
||||||
* @return The bind item
|
|
||||||
*/
|
|
||||||
public static Item getBind(BindVariant variant) {
|
|
||||||
return BINDS.get(variant).get();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a gag item by variant.
|
|
||||||
* @param variant The gag variant
|
|
||||||
* @return The gag item
|
|
||||||
*/
|
|
||||||
public static Item getGag(GagVariant variant) {
|
|
||||||
return GAGS.get(variant).get();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a blindfold item by variant.
|
|
||||||
* @param variant The blindfold variant
|
|
||||||
* @return The blindfold item
|
|
||||||
*/
|
|
||||||
public static Item getBlindfold(BlindfoldVariant variant) {
|
|
||||||
return BLINDFOLDS.get(variant).get();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get an earplugs item by variant.
|
|
||||||
* @param variant The earplugs variant
|
|
||||||
* @return The earplugs item
|
|
||||||
*/
|
|
||||||
public static Item getEarplugs(EarplugsVariant variant) {
|
|
||||||
return EARPLUGS.get(variant).get();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a knife item by variant.
|
* Get a knife item by variant.
|
||||||
* @param variant The knife variant
|
* @param variant The knife variant
|
||||||
@@ -397,13 +187,4 @@ public class ModItems {
|
|||||||
public static Item getKnife(KnifeVariant variant) {
|
public static Item getKnife(KnifeVariant variant) {
|
||||||
return KNIVES.get(variant).get();
|
return KNIVES.get(variant).get();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a mittens item by variant.
|
|
||||||
* @param variant The mittens variant
|
|
||||||
* @return The mittens item
|
|
||||||
*/
|
|
||||||
public static Item getMittens(MittensVariant variant) {
|
|
||||||
return MITTENS.get(variant).get();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
package com.tiedup.remake.items.base;
|
package com.tiedup.remake.items.base;
|
||||||
|
|
||||||
|
import com.tiedup.remake.v2.bondage.component.AdjustableComponent;
|
||||||
|
import com.tiedup.remake.v2.bondage.component.ComponentType;
|
||||||
|
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
|
||||||
import net.minecraft.nbt.CompoundTag;
|
import net.minecraft.nbt.CompoundTag;
|
||||||
import net.minecraft.util.Mth;
|
import net.minecraft.util.Mth;
|
||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
@@ -56,9 +59,12 @@ public class AdjustmentHelper {
|
|||||||
return tag.getFloat(NBT_ADJUSTMENT_Y);
|
return tag.getFloat(NBT_ADJUSTMENT_Y);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback to item's default adjustment
|
// Fallback to item's default adjustment from V2 AdjustableComponent
|
||||||
if (stack.getItem() instanceof IAdjustable adj) {
|
AdjustableComponent comp = DataDrivenBondageItem.getComponent(
|
||||||
return adj.getDefaultAdjustment();
|
stack, ComponentType.ADJUSTABLE, AdjustableComponent.class
|
||||||
|
);
|
||||||
|
if (comp != null) {
|
||||||
|
return comp.getDefaultValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
return DEFAULT_VALUE;
|
return DEFAULT_VALUE;
|
||||||
@@ -127,16 +133,15 @@ public class AdjustmentHelper {
|
|||||||
* Check if an ItemStack's item supports adjustment.
|
* Check if an ItemStack's item supports adjustment.
|
||||||
*
|
*
|
||||||
* @param stack The ItemStack to check
|
* @param stack The ItemStack to check
|
||||||
* @return true if the item implements IAdjustable and canBeAdjusted() returns true
|
* @return true if the item has an AdjustableComponent
|
||||||
*/
|
*/
|
||||||
public static boolean isAdjustable(ItemStack stack) {
|
public static boolean isAdjustable(ItemStack stack) {
|
||||||
if (stack.isEmpty()) {
|
if (stack.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (stack.getItem() instanceof IAdjustable adj) {
|
return DataDrivenBondageItem.getComponent(
|
||||||
return adj.canBeAdjusted();
|
stack, ComponentType.ADJUSTABLE, AdjustableComponent.class
|
||||||
}
|
) != null;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,90 +0,0 @@
|
|||||||
package com.tiedup.remake.items.base;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enum defining all bind variants with their properties.
|
|
||||||
* Used by GenericBind to create bind items via factory pattern.
|
|
||||||
*
|
|
||||||
* <p><b>Issue #12 fix:</b> Added textureSubfolder to eliminate 40+ string checks in renderers.
|
|
||||||
*/
|
|
||||||
public enum BindVariant {
|
|
||||||
// Standard binds (PoseType.STANDARD)
|
|
||||||
ROPES("ropes", PoseType.STANDARD, true, "ropes"),
|
|
||||||
ARMBINDER("armbinder", PoseType.STANDARD, false, "armbinder"),
|
|
||||||
DOGBINDER("dogbinder", PoseType.DOG, false, "armbinder"),
|
|
||||||
CHAIN("chain", PoseType.STANDARD, false, "chain"),
|
|
||||||
RIBBON("ribbon", PoseType.STANDARD, false, "ribbon"),
|
|
||||||
SLIME("slime", PoseType.STANDARD, false, "slime"),
|
|
||||||
VINE_SEED("vine_seed", PoseType.STANDARD, false, "vine"),
|
|
||||||
WEB_BIND("web_bind", PoseType.STANDARD, false, "web"),
|
|
||||||
SHIBARI("shibari", PoseType.STANDARD, true, "shibari"),
|
|
||||||
LEATHER_STRAPS("leather_straps", PoseType.STANDARD, false, "straps"),
|
|
||||||
MEDICAL_STRAPS("medical_straps", PoseType.STANDARD, false, "straps"),
|
|
||||||
BEAM_CUFFS("beam_cuffs", PoseType.STANDARD, false, "beam"),
|
|
||||||
DUCT_TAPE("duct_tape", PoseType.STANDARD, true, "tape"),
|
|
||||||
|
|
||||||
// Pose items (special PoseType)
|
|
||||||
STRAITJACKET("straitjacket", PoseType.STRAITJACKET, false, "straitjacket"),
|
|
||||||
WRAP("wrap", PoseType.WRAP, false, "wrap"),
|
|
||||||
LATEX_SACK("latex_sack", PoseType.LATEX_SACK, false, "latex");
|
|
||||||
|
|
||||||
private final String registryName;
|
|
||||||
private final PoseType poseType;
|
|
||||||
private final boolean supportsColor;
|
|
||||||
private final String textureSubfolder;
|
|
||||||
|
|
||||||
BindVariant(
|
|
||||||
String registryName,
|
|
||||||
PoseType poseType,
|
|
||||||
boolean supportsColor,
|
|
||||||
String textureSubfolder
|
|
||||||
) {
|
|
||||||
this.registryName = registryName;
|
|
||||||
this.poseType = poseType;
|
|
||||||
this.supportsColor = supportsColor;
|
|
||||||
this.textureSubfolder = textureSubfolder;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRegistryName() {
|
|
||||||
return registryName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the configured resistance for this bind variant.
|
|
||||||
* Delegates to {@link com.tiedup.remake.core.SettingsAccessor#getBindResistance(String)}.
|
|
||||||
*/
|
|
||||||
public int getResistance() {
|
|
||||||
return com.tiedup.remake.core.SettingsAccessor.getBindResistance(
|
|
||||||
registryName
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PoseType getPoseType() {
|
|
||||||
return poseType;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if this bind variant supports color variations.
|
|
||||||
* Items with colors: ropes, shibari, duct_tape
|
|
||||||
*/
|
|
||||||
public boolean supportsColor() {
|
|
||||||
return supportsColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the texture subfolder for this bind variant.
|
|
||||||
* Used by renderers to locate texture files.
|
|
||||||
*
|
|
||||||
* @return Subfolder path under textures/entity/bondage/ (e.g., "ropes", "straps")
|
|
||||||
*/
|
|
||||||
public String getTextureSubfolder() {
|
|
||||||
return textureSubfolder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the item name used for textures and translations.
|
|
||||||
* For most variants this is the same as registryName.
|
|
||||||
*/
|
|
||||||
public String getItemName() {
|
|
||||||
return registryName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
package com.tiedup.remake.items.base;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enum defining all blindfold variants.
|
|
||||||
* Used by GenericBlindfold to create blindfold items via factory pattern.
|
|
||||||
*
|
|
||||||
* <p><b>Issue #12 fix:</b> Added textureSubfolder to eliminate string checks in renderers.
|
|
||||||
*/
|
|
||||||
public enum BlindfoldVariant {
|
|
||||||
CLASSIC("classic_blindfold", true, "blindfolds"),
|
|
||||||
MASK("blindfold_mask", true, "blindfolds/mask");
|
|
||||||
|
|
||||||
private final String registryName;
|
|
||||||
private final boolean supportsColor;
|
|
||||||
private final String textureSubfolder;
|
|
||||||
|
|
||||||
BlindfoldVariant(
|
|
||||||
String registryName,
|
|
||||||
boolean supportsColor,
|
|
||||||
String textureSubfolder
|
|
||||||
) {
|
|
||||||
this.registryName = registryName;
|
|
||||||
this.supportsColor = supportsColor;
|
|
||||||
this.textureSubfolder = textureSubfolder;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRegistryName() {
|
|
||||||
return registryName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if this blindfold variant supports color variations.
|
|
||||||
* Both variants support colors in the original mod.
|
|
||||||
*/
|
|
||||||
public boolean supportsColor() {
|
|
||||||
return supportsColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the texture subfolder for this blindfold variant.
|
|
||||||
* Used by renderers to locate texture files.
|
|
||||||
*
|
|
||||||
* @return Subfolder path under textures/entity/bondage/ (e.g., "blindfolds", "blindfolds/mask")
|
|
||||||
*/
|
|
||||||
public String getTextureSubfolder() {
|
|
||||||
return textureSubfolder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
package com.tiedup.remake.items.base;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enum defining all earplugs variants.
|
|
||||||
* Used by GenericEarplugs to create earplugs items via factory pattern.
|
|
||||||
*
|
|
||||||
* <p><b>Issue #12 fix:</b> Added textureSubfolder to eliminate string checks in renderers.
|
|
||||||
*/
|
|
||||||
public enum EarplugsVariant {
|
|
||||||
CLASSIC("classic_earplugs", "earplugs");
|
|
||||||
|
|
||||||
private final String registryName;
|
|
||||||
private final String textureSubfolder;
|
|
||||||
|
|
||||||
EarplugsVariant(String registryName, String textureSubfolder) {
|
|
||||||
this.registryName = registryName;
|
|
||||||
this.textureSubfolder = textureSubfolder;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRegistryName() {
|
|
||||||
return registryName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the texture subfolder for this earplugs variant.
|
|
||||||
* Used by renderers to locate texture files.
|
|
||||||
*
|
|
||||||
* @return Subfolder path under textures/entity/bondage/ (e.g., "earplugs")
|
|
||||||
*/
|
|
||||||
public String getTextureSubfolder() {
|
|
||||||
return textureSubfolder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,163 +0,0 @@
|
|||||||
package com.tiedup.remake.items.base;
|
|
||||||
|
|
||||||
import com.tiedup.remake.util.GagMaterial;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enum defining all gag variants with their properties.
|
|
||||||
* Used by GenericGag to create gag items via factory pattern.
|
|
||||||
*
|
|
||||||
* <p>Note: ItemMedicalGag is NOT included here because it implements
|
|
||||||
* IHasBlindingEffect (combo item with special behavior).
|
|
||||||
*
|
|
||||||
* <p><b>Issue #12 fix:</b> Added textureSubfolder to eliminate 40+ string checks in renderers.
|
|
||||||
*/
|
|
||||||
public enum GagVariant {
|
|
||||||
// Cloth-based gags
|
|
||||||
CLOTH_GAG("cloth_gag", GagMaterial.CLOTH, true, "cloth", false, null),
|
|
||||||
ROPES_GAG("ropes_gag", GagMaterial.CLOTH, true, "shibari", false, null),
|
|
||||||
CLEAVE_GAG("cleave_gag", GagMaterial.CLOTH, true, "cleave", false, null),
|
|
||||||
RIBBON_GAG("ribbon_gag", GagMaterial.CLOTH, false, "ribbon", false, null),
|
|
||||||
|
|
||||||
// Ball gags - standard 2D texture rendering
|
|
||||||
BALL_GAG(
|
|
||||||
"ball_gag",
|
|
||||||
GagMaterial.BALL,
|
|
||||||
true,
|
|
||||||
"ballgags/normal",
|
|
||||||
false,
|
|
||||||
null
|
|
||||||
),
|
|
||||||
BALL_GAG_STRAP(
|
|
||||||
"ball_gag_strap",
|
|
||||||
GagMaterial.BALL,
|
|
||||||
true,
|
|
||||||
"ballgags/harness",
|
|
||||||
false,
|
|
||||||
null
|
|
||||||
),
|
|
||||||
|
|
||||||
// Tape gags
|
|
||||||
TAPE_GAG("tape_gag", GagMaterial.TAPE, true, "tape", false, null),
|
|
||||||
|
|
||||||
// Stuffed/filling gags (no colors)
|
|
||||||
WRAP_GAG("wrap_gag", GagMaterial.STUFFED, false, "wrap", false, null),
|
|
||||||
SLIME_GAG("slime_gag", GagMaterial.STUFFED, false, "slime", false, null),
|
|
||||||
VINE_GAG("vine_gag", GagMaterial.STUFFED, false, "vine", false, null),
|
|
||||||
WEB_GAG("web_gag", GagMaterial.STUFFED, false, "web", false, null),
|
|
||||||
|
|
||||||
// Panel gags (no colors)
|
|
||||||
PANEL_GAG(
|
|
||||||
"panel_gag",
|
|
||||||
GagMaterial.PANEL,
|
|
||||||
false,
|
|
||||||
"straitjacket",
|
|
||||||
false,
|
|
||||||
null
|
|
||||||
),
|
|
||||||
BEAM_PANEL_GAG(
|
|
||||||
"beam_panel_gag",
|
|
||||||
GagMaterial.PANEL,
|
|
||||||
false,
|
|
||||||
"beam",
|
|
||||||
false,
|
|
||||||
null
|
|
||||||
),
|
|
||||||
CHAIN_PANEL_GAG(
|
|
||||||
"chain_panel_gag",
|
|
||||||
GagMaterial.PANEL,
|
|
||||||
false,
|
|
||||||
"chain",
|
|
||||||
false,
|
|
||||||
null
|
|
||||||
),
|
|
||||||
|
|
||||||
// Latex gags (no colors)
|
|
||||||
LATEX_GAG("latex_gag", GagMaterial.LATEX, false, "latex", false, null),
|
|
||||||
|
|
||||||
// Ring/tube gags (no colors)
|
|
||||||
TUBE_GAG("tube_gag", GagMaterial.RING, false, "tube", false, null),
|
|
||||||
|
|
||||||
// Bite gags (no colors)
|
|
||||||
BITE_GAG("bite_gag", GagMaterial.BITE, false, "armbinder", false, null),
|
|
||||||
|
|
||||||
// Sponge gags (no colors)
|
|
||||||
SPONGE_GAG("sponge_gag", GagMaterial.SPONGE, false, "sponge", false, null),
|
|
||||||
|
|
||||||
// Baguette gags (no colors)
|
|
||||||
BAGUETTE_GAG(
|
|
||||||
"baguette_gag",
|
|
||||||
GagMaterial.BAGUETTE,
|
|
||||||
false,
|
|
||||||
"baguette",
|
|
||||||
false,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
private final String registryName;
|
|
||||||
private final GagMaterial material;
|
|
||||||
private final boolean supportsColor;
|
|
||||||
private final String textureSubfolder;
|
|
||||||
private final boolean uses3DModel;
|
|
||||||
private final String modelPath;
|
|
||||||
|
|
||||||
GagVariant(
|
|
||||||
String registryName,
|
|
||||||
GagMaterial material,
|
|
||||||
boolean supportsColor,
|
|
||||||
String textureSubfolder,
|
|
||||||
boolean uses3DModel,
|
|
||||||
String modelPath
|
|
||||||
) {
|
|
||||||
this.registryName = registryName;
|
|
||||||
this.material = material;
|
|
||||||
this.supportsColor = supportsColor;
|
|
||||||
this.textureSubfolder = textureSubfolder;
|
|
||||||
this.uses3DModel = uses3DModel;
|
|
||||||
this.modelPath = modelPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRegistryName() {
|
|
||||||
return registryName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GagMaterial getMaterial() {
|
|
||||||
return material;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if this gag variant supports color variations.
|
|
||||||
* Items with colors: cloth_gag, ropes_gag, cleave_gag, ribbon_gag,
|
|
||||||
* ball_gag, ball_gag_strap, tape_gag
|
|
||||||
*/
|
|
||||||
public boolean supportsColor() {
|
|
||||||
return supportsColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the texture subfolder for this gag variant.
|
|
||||||
* Used by renderers to locate texture files.
|
|
||||||
*
|
|
||||||
* @return Subfolder path under textures/entity/bondage/ (e.g., "cloth", "ballgags/normal")
|
|
||||||
*/
|
|
||||||
public String getTextureSubfolder() {
|
|
||||||
return textureSubfolder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if this gag variant uses a 3D OBJ model.
|
|
||||||
*
|
|
||||||
* @return true if this variant uses a 3D model
|
|
||||||
*/
|
|
||||||
public boolean uses3DModel() {
|
|
||||||
return uses3DModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the model path for 3D rendering.
|
|
||||||
*
|
|
||||||
* @return ResourceLocation string path (e.g., "tiedup:models/obj/ball_gag.obj"), or null if no 3D model
|
|
||||||
*/
|
|
||||||
public String getModelPath() {
|
|
||||||
return modelPath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
package com.tiedup.remake.items.base;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface for items that can have their render position adjusted.
|
|
||||||
* Typically gags and blindfolds that render on the player's head.
|
|
||||||
*
|
|
||||||
* Players can adjust the Y position of these items to better fit their skin.
|
|
||||||
* Adjustment values are stored in the ItemStack's NBT via AdjustmentHelper.
|
|
||||||
*/
|
|
||||||
public interface IAdjustable {
|
|
||||||
/**
|
|
||||||
* Whether this item supports position adjustment.
|
|
||||||
* @return true if adjustable
|
|
||||||
*/
|
|
||||||
boolean canBeAdjusted();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default Y offset for this item type (in pixels, 1 pixel = 1/16 block).
|
|
||||||
* Override for items that need a non-zero default position.
|
|
||||||
* @return default adjustment value
|
|
||||||
*/
|
|
||||||
default float getDefaultAdjustment() {
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Minimum allowed adjustment value (pixels).
|
|
||||||
* @return minimum value (typically -4.0)
|
|
||||||
*/
|
|
||||||
default float getMinAdjustment() {
|
|
||||||
return -4.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maximum allowed adjustment value (pixels).
|
|
||||||
* @return maximum value (typically +4.0)
|
|
||||||
*/
|
|
||||||
default float getMaxAdjustment() {
|
|
||||||
return 4.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Step size for GUI slider (smaller = more precise).
|
|
||||||
* @return step size (typically 0.25)
|
|
||||||
*/
|
|
||||||
default float getAdjustmentStep() {
|
|
||||||
return 0.25f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
package com.tiedup.remake.items.base;
|
|
||||||
|
|
||||||
import com.tiedup.remake.v2.BodyRegionV2;
|
|
||||||
import net.minecraft.resources.ResourceLocation;
|
|
||||||
import net.minecraft.world.entity.LivingEntity;
|
|
||||||
import net.minecraft.world.item.ItemStack;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface for all bondage equipment items.
|
|
||||||
* Defines the core behavior for items that can be equipped in custom bondage slots.
|
|
||||||
*
|
|
||||||
* Based on original IExtraBondageItem from 1.12.2
|
|
||||||
*/
|
|
||||||
public interface IBondageItem {
|
|
||||||
/**
|
|
||||||
* Get the body region this item occupies when equipped.
|
|
||||||
* @return The body region
|
|
||||||
*/
|
|
||||||
BodyRegionV2 getBodyRegion();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called every tick while this item is equipped on an entity.
|
|
||||||
* @param stack The equipped item stack
|
|
||||||
* @param entity The entity wearing the item
|
|
||||||
*/
|
|
||||||
default void onWornTick(ItemStack stack, LivingEntity entity) {
|
|
||||||
// Default: do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when this 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) {
|
|
||||||
// Default: do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when this 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) {
|
|
||||||
// Default: do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if this item can be equipped on the given entity.
|
|
||||||
* @param stack The item stack to equip
|
|
||||||
* @param entity The target entity
|
|
||||||
* @return true if the item can be equipped, false otherwise
|
|
||||||
*/
|
|
||||||
default boolean canEquip(ItemStack stack, LivingEntity entity) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if this item can be unequipped from the given entity.
|
|
||||||
* @param stack The equipped item stack
|
|
||||||
* @param entity The entity wearing the item
|
|
||||||
* @return true if the item can be unequipped, false otherwise
|
|
||||||
*/
|
|
||||||
default boolean canUnequip(ItemStack stack, LivingEntity entity) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the texture subfolder for this bondage item.
|
|
||||||
* Used by renderers to locate texture files.
|
|
||||||
*
|
|
||||||
* <p><b>Issue #12 fix:</b> Eliminates 40+ string checks in renderers by letting
|
|
||||||
* each item type declare its own texture subfolder.
|
|
||||||
*
|
|
||||||
* @return Subfolder path under textures/entity/bondage/ (e.g., "ropes", "ballgags/normal")
|
|
||||||
*/
|
|
||||||
default String getTextureSubfolder() {
|
|
||||||
return "misc"; // Fallback for items without explicit subfolder
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if this bondage item uses a 3D OBJ model instead of a flat texture.
|
|
||||||
* Items with 3D models will be rendered using ObjModelRenderer.
|
|
||||||
*
|
|
||||||
* @return true if this item uses a 3D model, false for standard texture rendering
|
|
||||||
*/
|
|
||||||
default boolean uses3DModel() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the ResourceLocation of the 3D model for this item.
|
|
||||||
* Only called if uses3DModel() returns true.
|
|
||||||
*
|
|
||||||
* @return ResourceLocation pointing to the .obj file, or null if no 3D model
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
default ResourceLocation get3DModelLocation() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
package com.tiedup.remake.items.base;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Marker interface for items that have a blinding visual effect.
|
|
||||||
*
|
|
||||||
* <p>Items implementing this interface will:
|
|
||||||
* <ul>
|
|
||||||
* <li>Apply a screen overlay when worn (client-side)</li>
|
|
||||||
* <li>Reduce the player's visibility</li>
|
|
||||||
* <li>Potentially disable certain UI elements</li>
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* <h2>Usage</h2>
|
|
||||||
* <pre>{@code
|
|
||||||
* if (blindfold.getItem() instanceof IHasBlindingEffect) {
|
|
||||||
* // Apply blinding overlay
|
|
||||||
* renderBlindingOverlay();
|
|
||||||
* }
|
|
||||||
* }</pre>
|
|
||||||
*
|
|
||||||
* <h2>Implementations</h2>
|
|
||||||
* <ul>
|
|
||||||
* <li>{@link ItemBlindfold} - All blindfold items</li>
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* <p>Based on original IHasBlindingEffect.java from 1.12.2
|
|
||||||
*
|
|
||||||
* @see ItemBlindfold
|
|
||||||
*/
|
|
||||||
public interface IHasBlindingEffect {
|
|
||||||
// Marker interface - no methods required
|
|
||||||
// Presence of this interface indicates the item has a blinding effect
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
package com.tiedup.remake.items.base;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Marker interface for items that have a gagging (speech muffling) effect.
|
|
||||||
*
|
|
||||||
* <p>Items implementing this interface will:
|
|
||||||
* <ul>
|
|
||||||
* <li>Convert chat messages to "mmpphh" sounds</li>
|
|
||||||
* <li>Play gagged speech sounds</li>
|
|
||||||
* <li>Potentially block certain chat commands</li>
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* <h2>Usage</h2>
|
|
||||||
* <pre>{@code
|
|
||||||
* if (gag.getItem() instanceof IHasGaggingEffect) {
|
|
||||||
* // Convert chat message to gagged speech
|
|
||||||
* message = GagTalkConverter.convert(message);
|
|
||||||
* }
|
|
||||||
* }</pre>
|
|
||||||
*
|
|
||||||
* <h2>Implementations</h2>
|
|
||||||
* <ul>
|
|
||||||
* <li>{@link ItemGag} - Ball gags, tape gags, cloth gags, etc.</li>
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* <p>Based on original ItemGaggingEffect.java from 1.12.2
|
|
||||||
*
|
|
||||||
* @see ItemGag
|
|
||||||
*/
|
|
||||||
public interface IHasGaggingEffect {
|
|
||||||
// Marker interface - no methods required
|
|
||||||
// Presence of this interface indicates the item has a gagging effect
|
|
||||||
}
|
|
||||||
@@ -1,637 +0,0 @@
|
|||||||
package com.tiedup.remake.items.base;
|
|
||||||
|
|
||||||
import com.tiedup.remake.core.SettingsAccessor;
|
|
||||||
import com.tiedup.remake.core.TiedUpMod;
|
|
||||||
import com.tiedup.remake.network.ModNetwork;
|
|
||||||
import com.tiedup.remake.network.action.PacketTying;
|
|
||||||
import com.tiedup.remake.state.IBondageState;
|
|
||||||
import com.tiedup.remake.state.PlayerBindState;
|
|
||||||
import com.tiedup.remake.tasks.TyingPlayerTask;
|
|
||||||
import com.tiedup.remake.tasks.TyingTask;
|
|
||||||
import com.tiedup.remake.util.KidnappedHelper;
|
|
||||||
import com.tiedup.remake.util.RestraintEffectUtils;
|
|
||||||
import com.tiedup.remake.util.TiedUpSounds;
|
|
||||||
import com.tiedup.remake.v2.BodyRegionV2;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.UUID;
|
|
||||||
import net.minecraft.ChatFormatting;
|
|
||||||
import net.minecraft.nbt.CompoundTag;
|
|
||||||
import net.minecraft.network.chat.Component;
|
|
||||||
import net.minecraft.server.level.ServerPlayer;
|
|
||||||
import net.minecraft.sounds.SoundEvents;
|
|
||||||
import net.minecraft.world.InteractionHand;
|
|
||||||
import net.minecraft.world.InteractionResult;
|
|
||||||
import net.minecraft.world.InteractionResultHolder;
|
|
||||||
import net.minecraft.world.entity.LivingEntity;
|
|
||||||
import net.minecraft.world.entity.player.Player;
|
|
||||||
import net.minecraft.world.item.Item;
|
|
||||||
import net.minecraft.world.item.ItemStack;
|
|
||||||
import net.minecraft.world.item.TooltipFlag;
|
|
||||||
import net.minecraft.world.item.context.UseOnContext;
|
|
||||||
import net.minecraft.world.level.Level;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base class for binding/restraint items (ropes, chains, straitjacket, etc.)
|
|
||||||
* These items restrain a player's movement and actions when equipped.
|
|
||||||
*
|
|
||||||
* <p>Implements {@link IHasResistance} for the struggle/escape system.
|
|
||||||
* <p>Implements {@link ILockable} for the padlock system.
|
|
||||||
*
|
|
||||||
* Based on original ItemBind from 1.12.2
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public abstract class ItemBind
|
|
||||||
extends Item
|
|
||||||
implements IBondageItem, IHasResistance, ILockable
|
|
||||||
{
|
|
||||||
|
|
||||||
// ========== Leg Binding: Bind Mode NBT Key ==========
|
|
||||||
private static final String NBT_BIND_MODE = "bindMode";
|
|
||||||
|
|
||||||
public ItemBind(Properties properties) {
|
|
||||||
super(properties);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BodyRegionV2 getBodyRegion() {
|
|
||||||
return BodyRegionV2.ARMS;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== Leg Binding: Bind Mode Methods ==========
|
|
||||||
|
|
||||||
// String constants matching NBT values
|
|
||||||
public static final String BIND_MODE_FULL = "full";
|
|
||||||
private static final String MODE_FULL = BIND_MODE_FULL;
|
|
||||||
private static final String MODE_ARMS = "arms";
|
|
||||||
private static final String MODE_LEGS = "legs";
|
|
||||||
private static final String[] MODE_CYCLE = {
|
|
||||||
MODE_FULL,
|
|
||||||
MODE_ARMS,
|
|
||||||
MODE_LEGS,
|
|
||||||
};
|
|
||||||
private static final java.util.Map<String, String> MODE_TRANSLATION_KEYS =
|
|
||||||
java.util.Map.of(
|
|
||||||
MODE_FULL,
|
|
||||||
"tiedup.bindmode.full",
|
|
||||||
MODE_ARMS,
|
|
||||||
"tiedup.bindmode.arms",
|
|
||||||
MODE_LEGS,
|
|
||||||
"tiedup.bindmode.legs"
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the bind mode ID string from the stack's NBT.
|
|
||||||
* @param stack The bind ItemStack
|
|
||||||
* @return "full", "arms", or "legs" (defaults to "full" if absent)
|
|
||||||
*/
|
|
||||||
public static String getBindModeId(ItemStack stack) {
|
|
||||||
if (stack.isEmpty()) return MODE_FULL;
|
|
||||||
CompoundTag tag = stack.getTag();
|
|
||||||
if (tag == null || !tag.contains(NBT_BIND_MODE)) return MODE_FULL;
|
|
||||||
String value = tag.getString(NBT_BIND_MODE);
|
|
||||||
if (
|
|
||||||
MODE_FULL.equals(value) ||
|
|
||||||
MODE_ARMS.equals(value) ||
|
|
||||||
MODE_LEGS.equals(value)
|
|
||||||
) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
return MODE_FULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if arms are bound (mode is "arms" or "full").
|
|
||||||
* @param stack The bind ItemStack
|
|
||||||
* @return true if arms are restrained
|
|
||||||
*/
|
|
||||||
public static boolean hasArmsBound(ItemStack stack) {
|
|
||||||
String mode = getBindModeId(stack);
|
|
||||||
return MODE_ARMS.equals(mode) || MODE_FULL.equals(mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if legs are bound (mode is "legs" or "full").
|
|
||||||
* @param stack The bind ItemStack
|
|
||||||
* @return true if legs are restrained
|
|
||||||
*/
|
|
||||||
public static boolean hasLegsBound(ItemStack stack) {
|
|
||||||
String mode = getBindModeId(stack);
|
|
||||||
return MODE_LEGS.equals(mode) || MODE_FULL.equals(mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cycle bind mode: full -> arms -> legs -> full.
|
|
||||||
* @param stack The bind ItemStack
|
|
||||||
* @return the new mode ID string
|
|
||||||
*/
|
|
||||||
public static String cycleBindModeId(ItemStack stack) {
|
|
||||||
String current = getBindModeId(stack);
|
|
||||||
String next = MODE_FULL;
|
|
||||||
for (int i = 0; i < MODE_CYCLE.length; i++) {
|
|
||||||
if (MODE_CYCLE[i].equals(current)) {
|
|
||||||
next = MODE_CYCLE[(i + 1) % MODE_CYCLE.length];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stack.getOrCreateTag().putString(NBT_BIND_MODE, next);
|
|
||||||
return next;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the translation key for the current bind mode.
|
|
||||||
* @param stack The bind ItemStack
|
|
||||||
* @return the i18n key for the mode
|
|
||||||
*/
|
|
||||||
public static String getBindModeTranslationKey(ItemStack stack) {
|
|
||||||
return MODE_TRANSLATION_KEYS.getOrDefault(
|
|
||||||
getBindModeId(stack),
|
|
||||||
"tiedup.bindmode.full"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when player right-clicks in air with bind item.
|
|
||||||
* Sneak+click cycles the bind mode.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public InteractionResultHolder<ItemStack> use(
|
|
||||||
Level level,
|
|
||||||
Player player,
|
|
||||||
InteractionHand hand
|
|
||||||
) {
|
|
||||||
ItemStack stack = player.getItemInHand(hand);
|
|
||||||
|
|
||||||
// Sneak+click in air cycles bind mode
|
|
||||||
if (player.isShiftKeyDown()) {
|
|
||||||
if (!level.isClientSide) {
|
|
||||||
String newModeId = cycleBindModeId(stack);
|
|
||||||
|
|
||||||
// Play feedback sound
|
|
||||||
player.playSound(SoundEvents.CHAIN_STEP, 0.5f, 1.2f);
|
|
||||||
|
|
||||||
// Show action bar message
|
|
||||||
player.displayClientMessage(
|
|
||||||
Component.translatable(
|
|
||||||
"tiedup.message.bindmode_changed",
|
|
||||||
Component.translatable(getBindModeTranslationKey(stack))
|
|
||||||
),
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
TiedUpMod.LOGGER.debug(
|
|
||||||
"[ItemBind] {} cycled bind mode to {}",
|
|
||||||
player.getName().getString(),
|
|
||||||
newModeId
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return InteractionResultHolder.sidedSuccess(
|
|
||||||
stack,
|
|
||||||
level.isClientSide
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.use(level, player, hand);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void appendHoverText(
|
|
||||||
ItemStack stack,
|
|
||||||
@Nullable Level level,
|
|
||||||
List<Component> tooltip,
|
|
||||||
TooltipFlag flag
|
|
||||||
) {
|
|
||||||
super.appendHoverText(stack, level, tooltip, flag);
|
|
||||||
|
|
||||||
// Show bind mode
|
|
||||||
tooltip.add(
|
|
||||||
Component.translatable(
|
|
||||||
"item.tiedup.tooltip.bindmode",
|
|
||||||
Component.translatable(getBindModeTranslationKey(stack))
|
|
||||||
).withStyle(ChatFormatting.GRAY)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Show lock status
|
|
||||||
if (isLockable(stack)) {
|
|
||||||
if (isLocked(stack)) {
|
|
||||||
tooltip.add(
|
|
||||||
Component.translatable(
|
|
||||||
"item.tiedup.tooltip.locked"
|
|
||||||
).withStyle(ChatFormatting.RED)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
tooltip.add(
|
|
||||||
Component.translatable(
|
|
||||||
"item.tiedup.tooltip.lockable"
|
|
||||||
).withStyle(ChatFormatting.GOLD)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the bind is equipped on an entity.
|
|
||||||
* Applies movement speed reduction only if legs are bound.
|
|
||||||
*
|
|
||||||
* Leg Binding: Speed reduction conditional on mode
|
|
||||||
* Based on original ItemBind.onEquipped() (1.12.2)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onEquipped(ItemStack stack, LivingEntity entity) {
|
|
||||||
String modeId = getBindModeId(stack);
|
|
||||||
|
|
||||||
// Only apply speed reduction if legs are bound
|
|
||||||
if (hasLegsBound(stack)) {
|
|
||||||
// H6 fix: For players, speed is handled exclusively by MovementStyleManager
|
|
||||||
// (V2 tick-based system) via MovementStyleResolver V1 fallback.
|
|
||||||
// Applying V1 RestraintEffectUtils here would cause double stacking (different
|
|
||||||
// UUIDs, ADDITION vs MULTIPLY_BASE) leading to quasi-immobility.
|
|
||||||
if (entity instanceof Player) {
|
|
||||||
TiedUpMod.LOGGER.debug(
|
|
||||||
"[ItemBind] Applied bind (mode={}, pose={}) to player {} - speed delegated to MovementStyleManager",
|
|
||||||
modeId,
|
|
||||||
getPoseType().getAnimationId(),
|
|
||||||
entity.getName().getString()
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// NPCs: MovementStyleManager only handles ServerPlayer, so NPCs
|
|
||||||
// still need the legacy RestraintEffectUtils speed modifier.
|
|
||||||
PoseType poseType = getPoseType();
|
|
||||||
boolean fullImmobilization =
|
|
||||||
poseType == PoseType.WRAP ||
|
|
||||||
poseType == PoseType.LATEX_SACK;
|
|
||||||
|
|
||||||
RestraintEffectUtils.applyBindSpeedReduction(
|
|
||||||
entity,
|
|
||||||
fullImmobilization
|
|
||||||
);
|
|
||||||
TiedUpMod.LOGGER.debug(
|
|
||||||
"[ItemBind] Applied bind (mode={}, pose={}) to NPC {} - speed reduced (full={})",
|
|
||||||
modeId,
|
|
||||||
poseType.getAnimationId(),
|
|
||||||
entity.getName().getString(),
|
|
||||||
fullImmobilization
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
TiedUpMod.LOGGER.debug(
|
|
||||||
"[ItemBind] Applied bind (mode={}) to {} - no speed reduction",
|
|
||||||
modeId,
|
|
||||||
entity.getName().getString()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the bind is unequipped from an entity.
|
|
||||||
* Restores normal movement speed for all entities.
|
|
||||||
*
|
|
||||||
* Based on original ItemBind.onUnequipped() (1.12.2)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onUnequipped(ItemStack stack, LivingEntity entity) {
|
|
||||||
// H6 fix: For players, speed cleanup is handled by MovementStyleManager
|
|
||||||
// (V2 tick-based system). On the next tick, the resolver will see the item
|
|
||||||
// is gone, deactivate the style, and remove the modifier automatically.
|
|
||||||
// NPCs still need the legacy RestraintEffectUtils cleanup.
|
|
||||||
if (!(entity instanceof Player)) {
|
|
||||||
RestraintEffectUtils.removeBindSpeedReduction(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
IHasResistance.super.resetCurrentResistance(stack);
|
|
||||||
|
|
||||||
TiedUpMod.LOGGER.debug(
|
|
||||||
"[ItemBind] Removed bind from {} - speed {} resistance reset",
|
|
||||||
entity.getName().getString(),
|
|
||||||
entity instanceof Player
|
|
||||||
? "delegated to MovementStyleManager,"
|
|
||||||
: "restored,"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== Tying Interaction ==========
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when player right-clicks another entity with this bind item.
|
|
||||||
* Starts or continues a tying task to tie up the target entity.
|
|
||||||
*
|
|
||||||
* - Players: Uses tying task with progress bar
|
|
||||||
* - NPCs: Instant bind (no tying mini-game)
|
|
||||||
*
|
|
||||||
* Based on original ItemBind.itemInteractionForEntity() (1.12.2)
|
|
||||||
*
|
|
||||||
* @param stack The item stack
|
|
||||||
* @param player The player using the item (kidnapper)
|
|
||||||
* @param target The entity being interacted with
|
|
||||||
* @param hand The hand holding the item
|
|
||||||
* @return SUCCESS if tying started/continued, PASS otherwise
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public InteractionResult interactLivingEntity(
|
|
||||||
ItemStack stack,
|
|
||||||
Player player,
|
|
||||||
LivingEntity target,
|
|
||||||
InteractionHand hand
|
|
||||||
) {
|
|
||||||
// Only run on server side
|
|
||||||
if (player.level().isClientSide) {
|
|
||||||
return InteractionResult.SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
IBondageState targetState = KidnappedHelper.getKidnappedState(target);
|
|
||||||
if (targetState == null) {
|
|
||||||
return InteractionResult.PASS; // Target cannot be restrained
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get kidnapper state (player using the item)
|
|
||||||
IBondageState kidnapperState = KidnappedHelper.getKidnappedState(
|
|
||||||
player
|
|
||||||
);
|
|
||||||
if (kidnapperState == null) {
|
|
||||||
return InteractionResult.FAIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Already tied - try to swap binds (if not locked)
|
|
||||||
// Check stack.isEmpty() first to prevent accidental unbinding when
|
|
||||||
// the original stack was consumed (e.g., rapid clicks after tying completes)
|
|
||||||
if (targetState.isTiedUp()) {
|
|
||||||
if (stack.isEmpty()) {
|
|
||||||
// No bind in hand - can't swap, just pass
|
|
||||||
return InteractionResult.PASS;
|
|
||||||
}
|
|
||||||
ItemStack oldBind = targetState.replaceEquipment(
|
|
||||||
BodyRegionV2.ARMS,
|
|
||||||
stack.copy(),
|
|
||||||
false
|
|
||||||
);
|
|
||||||
if (!oldBind.isEmpty()) {
|
|
||||||
stack.shrink(1);
|
|
||||||
targetState.kidnappedDropItem(oldBind);
|
|
||||||
TiedUpMod.LOGGER.debug(
|
|
||||||
"[ItemBind] Swapped bind on {} - dropped old bind",
|
|
||||||
target.getName().getString()
|
|
||||||
);
|
|
||||||
return InteractionResult.SUCCESS;
|
|
||||||
}
|
|
||||||
// Locked or failed - can't swap
|
|
||||||
return InteractionResult.PASS;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (kidnapperState.isTiedUp()) {
|
|
||||||
TiedUpMod.LOGGER.debug(
|
|
||||||
"[ItemBind] {} tried to tie but is tied themselves",
|
|
||||||
player.getName().getString()
|
|
||||||
);
|
|
||||||
return InteractionResult.PASS;
|
|
||||||
}
|
|
||||||
|
|
||||||
// SECURITY: Distance and line-of-sight validation (skip for self-tying)
|
|
||||||
boolean isSelfTying = player.equals(target);
|
|
||||||
if (!isSelfTying) {
|
|
||||||
double maxTieDistance = 4.0; // Max distance to tie (blocks)
|
|
||||||
double distance = player.distanceTo(target);
|
|
||||||
if (distance > maxTieDistance) {
|
|
||||||
TiedUpMod.LOGGER.warn(
|
|
||||||
"[ItemBind] {} tried to tie {} from too far away ({} blocks)",
|
|
||||||
player.getName().getString(),
|
|
||||||
target.getName().getString(),
|
|
||||||
String.format("%.1f", distance)
|
|
||||||
);
|
|
||||||
return InteractionResult.PASS;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check line-of-sight (must be able to see target)
|
|
||||||
if (!player.hasLineOfSight(target)) {
|
|
||||||
TiedUpMod.LOGGER.warn(
|
|
||||||
"[ItemBind] {} tried to tie {} without line of sight",
|
|
||||||
player.getName().getString(),
|
|
||||||
target.getName().getString()
|
|
||||||
);
|
|
||||||
return InteractionResult.PASS;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return handleTying(stack, player, target, targetState);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle tying any target entity (Player or NPC).
|
|
||||||
*
|
|
||||||
* Uses progress-based system:
|
|
||||||
* - update() marks the tick as active
|
|
||||||
* - tick() in RestraintTaskTickHandler.onPlayerTick() handles progress increment/decrement
|
|
||||||
*/
|
|
||||||
private InteractionResult handleTying(
|
|
||||||
ItemStack stack,
|
|
||||||
Player player,
|
|
||||||
LivingEntity target,
|
|
||||||
IBondageState targetState
|
|
||||||
) {
|
|
||||||
// Get kidnapper's state to track the tying task
|
|
||||||
PlayerBindState kidnapperState = PlayerBindState.getInstance(player);
|
|
||||||
if (kidnapperState == null) {
|
|
||||||
return InteractionResult.FAIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get tying duration from GameRule (default: 5 seconds)
|
|
||||||
int tyingSeconds = getTyingDuration(player);
|
|
||||||
|
|
||||||
// Get current tying task (if any)
|
|
||||||
TyingTask currentTask = kidnapperState.getCurrentTyingTask();
|
|
||||||
|
|
||||||
// Check if we should start a new task or continue existing one
|
|
||||||
if (
|
|
||||||
currentTask == null ||
|
|
||||||
!currentTask.isSameTarget(target) ||
|
|
||||||
currentTask.isStopped() ||
|
|
||||||
!ItemStack.matches(currentTask.getBind(), stack)
|
|
||||||
) {
|
|
||||||
// Create new tying task (works for both Players and NPCs)
|
|
||||||
TyingPlayerTask newTask = new TyingPlayerTask(
|
|
||||||
stack.copy(),
|
|
||||||
targetState,
|
|
||||||
target,
|
|
||||||
tyingSeconds,
|
|
||||||
player.level(),
|
|
||||||
player // Pass kidnapper for SystemMessage
|
|
||||||
);
|
|
||||||
|
|
||||||
// FIX: Store the inventory slot for consumption when task completes
|
|
||||||
// This prevents duplication AND allows refund if task is cancelled
|
|
||||||
int sourceSlot = player.getInventory().selected;
|
|
||||||
newTask.setSourceSlot(sourceSlot);
|
|
||||||
newTask.setSourcePlayer(player);
|
|
||||||
|
|
||||||
// Start new task
|
|
||||||
kidnapperState.setCurrentTyingTask(newTask);
|
|
||||||
newTask.setUpTargetState(); // Initialize target's restraint state (only for players)
|
|
||||||
newTask.start();
|
|
||||||
currentTask = newTask;
|
|
||||||
|
|
||||||
TiedUpMod.LOGGER.debug(
|
|
||||||
"[ItemBind] {} started tying {} ({} seconds, slot={})",
|
|
||||||
player.getName().getString(),
|
|
||||||
target.getName().getString(),
|
|
||||||
tyingSeconds,
|
|
||||||
sourceSlot
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// Continue existing task - ensure kidnapper is set
|
|
||||||
if (currentTask instanceof TyingPlayerTask playerTask) {
|
|
||||||
playerTask.setKidnapper(player);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark this tick as active (progress will increase in onPlayerTick)
|
|
||||||
// The tick() method in RestraintTaskTickHandler.onPlayerTick handles progress increment/decrement
|
|
||||||
currentTask.update();
|
|
||||||
|
|
||||||
return InteractionResult.SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when player right-clicks with the bind item (not targeting an entity).
|
|
||||||
* Cancels any ongoing tying task.
|
|
||||||
*
|
|
||||||
* Based on original ItemBind.onItemRightClick() (1.12.2)
|
|
||||||
*
|
|
||||||
* @param context The use context
|
|
||||||
* @return FAIL to cancel the action
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public InteractionResult useOn(UseOnContext context) {
|
|
||||||
// Only run on server side
|
|
||||||
if (context.getLevel().isClientSide) {
|
|
||||||
return InteractionResult.SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
Player player = context.getPlayer();
|
|
||||||
if (player == null) {
|
|
||||||
return InteractionResult.FAIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cancel any ongoing tying task
|
|
||||||
PlayerBindState state = PlayerBindState.getInstance(player);
|
|
||||||
if (state == null) {
|
|
||||||
return InteractionResult.FAIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for active tying task (unified for both players and NPCs)
|
|
||||||
TyingTask task = state.getCurrentTyingTask();
|
|
||||||
if (task != null) {
|
|
||||||
task.stop();
|
|
||||||
state.setCurrentTyingTask(null);
|
|
||||||
|
|
||||||
LivingEntity target = task.getTargetEntity();
|
|
||||||
String targetName =
|
|
||||||
target != null ? target.getName().getString() : "???";
|
|
||||||
String kidnapperName = player.getName().getString();
|
|
||||||
|
|
||||||
// Send cancellation packet to kidnapper
|
|
||||||
if (player instanceof ServerPlayer serverPlayer) {
|
|
||||||
PacketTying packet = new PacketTying(
|
|
||||||
-1,
|
|
||||||
task.getMaxSeconds(),
|
|
||||||
true,
|
|
||||||
targetName
|
|
||||||
);
|
|
||||||
ModNetwork.sendToPlayer(packet, serverPlayer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send cancellation packet to target (if it's a player)
|
|
||||||
if (target instanceof ServerPlayer serverTarget) {
|
|
||||||
PacketTying packet = new PacketTying(
|
|
||||||
-1,
|
|
||||||
task.getMaxSeconds(),
|
|
||||||
false,
|
|
||||||
kidnapperName
|
|
||||||
);
|
|
||||||
ModNetwork.sendToPlayer(packet, serverTarget);
|
|
||||||
}
|
|
||||||
|
|
||||||
TiedUpMod.LOGGER.debug(
|
|
||||||
"[ItemBind] {} cancelled tying task",
|
|
||||||
player.getName().getString()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return InteractionResult.FAIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the tying duration in seconds from GameRule.
|
|
||||||
*
|
|
||||||
* @param player The player (for accessing world/GameRules)
|
|
||||||
* @return Duration in seconds (default: 5)
|
|
||||||
*/
|
|
||||||
private int getTyingDuration(Player player) {
|
|
||||||
return SettingsAccessor.getTyingPlayerTime(
|
|
||||||
player.level().getGameRules()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== Resistance System (via IHasResistance) ==========
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the item name for GameRule lookup.
|
|
||||||
* Each subclass must implement this to return its identifier (e.g., "rope", "chain", etc.)
|
|
||||||
*
|
|
||||||
* @return Item name for resistance GameRule lookup
|
|
||||||
*/
|
|
||||||
public abstract String getItemName();
|
|
||||||
|
|
||||||
// ========== Pose System ==========
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the pose type for this bind item.
|
|
||||||
* Determines which animation/pose is applied when this item is equipped.
|
|
||||||
*
|
|
||||||
* Override in subclasses for special poses (straitjacket, wrap, latex_sack).
|
|
||||||
*
|
|
||||||
* @return PoseType for this bind (default: STANDARD)
|
|
||||||
*/
|
|
||||||
public PoseType getPoseType() {
|
|
||||||
return PoseType.STANDARD;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implementation of IHasResistance.getResistanceId().
|
|
||||||
* Delegates to getItemName() for backward compatibility with subclasses.
|
|
||||||
*
|
|
||||||
* @return Item identifier for resistance lookup
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String getResistanceId() {
|
|
||||||
return getItemName();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the entity struggles against this bind.
|
|
||||||
* Plays struggle sound and shows message.
|
|
||||||
*
|
|
||||||
* Based on original ItemBind struggle notification (1.12.2)
|
|
||||||
*
|
|
||||||
* @param entity The entity struggling
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void notifyStruggle(LivingEntity entity) {
|
|
||||||
// Play struggle sound
|
|
||||||
TiedUpSounds.playStruggleSound(entity);
|
|
||||||
|
|
||||||
// Log the struggle attempt
|
|
||||||
TiedUpMod.LOGGER.debug(
|
|
||||||
"[ItemBind] {} is struggling against bind",
|
|
||||||
entity.getName().getString()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Notify nearby players if the entity is a player
|
|
||||||
if (entity instanceof ServerPlayer serverPlayer) {
|
|
||||||
serverPlayer.displayClientMessage(
|
|
||||||
Component.translatable("tiedup.message.struggling"),
|
|
||||||
true // Action bar
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ILockable implementation inherited from interface default methods
|
|
||||||
}
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
package com.tiedup.remake.items.base;
|
|
||||||
|
|
||||||
import com.tiedup.remake.core.SystemMessageManager;
|
|
||||||
import com.tiedup.remake.util.EquipmentInteractionHelper;
|
|
||||||
import com.tiedup.remake.v2.BodyRegionV2;
|
|
||||||
import java.util.List;
|
|
||||||
import net.minecraft.network.chat.Component;
|
|
||||||
import net.minecraft.world.InteractionHand;
|
|
||||||
import net.minecraft.world.InteractionResult;
|
|
||||||
import net.minecraft.world.entity.LivingEntity;
|
|
||||||
import net.minecraft.world.entity.player.Player;
|
|
||||||
import net.minecraft.world.item.Item;
|
|
||||||
import net.minecraft.world.item.ItemStack;
|
|
||||||
import net.minecraft.world.item.TooltipFlag;
|
|
||||||
import net.minecraft.world.level.Level;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base class for blindfold items (classic blindfold, mask, hood, etc.)
|
|
||||||
* These items obstruct a player's vision when equipped.
|
|
||||||
*
|
|
||||||
* Based on original ItemBlindfold from 1.12.2
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public abstract class ItemBlindfold
|
|
||||||
extends Item
|
|
||||||
implements IBondageItem, IHasBlindingEffect, IAdjustable, ILockable
|
|
||||||
{
|
|
||||||
|
|
||||||
public ItemBlindfold(Properties properties) {
|
|
||||||
super(properties);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BodyRegionV2 getBodyRegion() {
|
|
||||||
return BodyRegionV2.EYES;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void appendHoverText(
|
|
||||||
ItemStack stack,
|
|
||||||
@Nullable Level level,
|
|
||||||
List<Component> tooltip,
|
|
||||||
TooltipFlag flag
|
|
||||||
) {
|
|
||||||
super.appendHoverText(stack, level, tooltip, flag);
|
|
||||||
appendLockTooltip(stack, tooltip);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All blindfolds can be adjusted to better fit player skins.
|
|
||||||
* @return true - blindfolds support position adjustment
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean canBeAdjusted() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when player right-clicks another entity with this blindfold.
|
|
||||||
* Allows putting blindfold on tied-up entities (Players and NPCs).
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public InteractionResult interactLivingEntity(
|
|
||||||
ItemStack stack,
|
|
||||||
Player player,
|
|
||||||
LivingEntity target,
|
|
||||||
InteractionHand hand
|
|
||||||
) {
|
|
||||||
return EquipmentInteractionHelper.equipOnTarget(
|
|
||||||
stack,
|
|
||||||
player,
|
|
||||||
target,
|
|
||||||
state -> state.isBlindfolded(),
|
|
||||||
(state, item) -> state.equip(BodyRegionV2.EYES, item),
|
|
||||||
(state, item) ->
|
|
||||||
state.replaceEquipment(BodyRegionV2.EYES, item, false),
|
|
||||||
(p, t) ->
|
|
||||||
SystemMessageManager.sendToTarget(
|
|
||||||
p,
|
|
||||||
t,
|
|
||||||
SystemMessageManager.MessageCategory.BLINDFOLDED
|
|
||||||
),
|
|
||||||
"ItemBlindfold"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ILockable implementation inherited from interface default methods
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,90 +0,0 @@
|
|||||||
package com.tiedup.remake.items.base;
|
|
||||||
|
|
||||||
import com.tiedup.remake.core.SystemMessageManager;
|
|
||||||
import com.tiedup.remake.util.EquipmentInteractionHelper;
|
|
||||||
import com.tiedup.remake.util.TiedUpSounds;
|
|
||||||
import com.tiedup.remake.v2.BodyRegionV2;
|
|
||||||
import java.util.List;
|
|
||||||
import net.minecraft.network.chat.Component;
|
|
||||||
import net.minecraft.world.InteractionHand;
|
|
||||||
import net.minecraft.world.InteractionResult;
|
|
||||||
import net.minecraft.world.entity.LivingEntity;
|
|
||||||
import net.minecraft.world.entity.player.Player;
|
|
||||||
import net.minecraft.world.item.Item;
|
|
||||||
import net.minecraft.world.item.ItemStack;
|
|
||||||
import net.minecraft.world.item.TooltipFlag;
|
|
||||||
import net.minecraft.world.level.Level;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base class for earplug items.
|
|
||||||
* These items block or reduce sounds when equipped.
|
|
||||||
*
|
|
||||||
* Based on original ItemEarplugs from 1.12.2
|
|
||||||
*
|
|
||||||
* Phase future: Sound blocking effect
|
|
||||||
*/
|
|
||||||
public abstract class ItemEarplugs
|
|
||||||
extends Item
|
|
||||||
implements IBondageItem, ILockable
|
|
||||||
{
|
|
||||||
|
|
||||||
public ItemEarplugs(Properties properties) {
|
|
||||||
super(properties.stacksTo(16)); // Earplugs can stack to 16
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BodyRegionV2 getBodyRegion() {
|
|
||||||
return BodyRegionV2.EARS;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void appendHoverText(
|
|
||||||
ItemStack stack,
|
|
||||||
@Nullable Level level,
|
|
||||||
List<Component> tooltip,
|
|
||||||
TooltipFlag flag
|
|
||||||
) {
|
|
||||||
super.appendHoverText(stack, level, tooltip, flag);
|
|
||||||
appendLockTooltip(stack, tooltip);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when player right-clicks another entity with earplugs.
|
|
||||||
* Allows putting earplugs on tied-up entities (Players and NPCs).
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public InteractionResult interactLivingEntity(
|
|
||||||
ItemStack stack,
|
|
||||||
Player player,
|
|
||||||
LivingEntity target,
|
|
||||||
InteractionHand hand
|
|
||||||
) {
|
|
||||||
return EquipmentInteractionHelper.equipOnTarget(
|
|
||||||
stack,
|
|
||||||
player,
|
|
||||||
target,
|
|
||||||
state -> state.hasEarplugs(),
|
|
||||||
(state, item) -> state.equip(BodyRegionV2.EARS, item),
|
|
||||||
(state, item) ->
|
|
||||||
state.replaceEquipment(BodyRegionV2.EARS, item, false),
|
|
||||||
(p, t) ->
|
|
||||||
SystemMessageManager.sendToTarget(
|
|
||||||
p,
|
|
||||||
t,
|
|
||||||
SystemMessageManager.MessageCategory.EARPLUGS_ON
|
|
||||||
),
|
|
||||||
"ItemEarplugs",
|
|
||||||
null, // No pre-equip hook
|
|
||||||
(s, p, t, state) -> TiedUpSounds.playEarplugsEquipSound(t), // Post-equip: play sound
|
|
||||||
null // No replace check
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sound blocking implemented in:
|
|
||||||
// - client/events/EarplugSoundHandler.java (event interception)
|
|
||||||
// - client/MuffledSoundInstance.java (volume/pitch wrapper)
|
|
||||||
// - Configurable via ModConfig.CLIENT.earplugVolumeMultiplier
|
|
||||||
|
|
||||||
// ILockable implementation inherited from interface default methods
|
|
||||||
}
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
package com.tiedup.remake.items.base;
|
|
||||||
|
|
||||||
import com.tiedup.remake.core.SystemMessageManager;
|
|
||||||
import com.tiedup.remake.util.EquipmentInteractionHelper;
|
|
||||||
import com.tiedup.remake.util.GagMaterial;
|
|
||||||
import com.tiedup.remake.v2.BodyRegionV2;
|
|
||||||
import java.util.List;
|
|
||||||
import net.minecraft.ChatFormatting;
|
|
||||||
import net.minecraft.network.chat.Component;
|
|
||||||
import net.minecraft.world.InteractionHand;
|
|
||||||
import net.minecraft.world.InteractionResult;
|
|
||||||
import net.minecraft.world.entity.LivingEntity;
|
|
||||||
import net.minecraft.world.entity.player.Player;
|
|
||||||
import net.minecraft.world.item.Item;
|
|
||||||
import net.minecraft.world.item.ItemStack;
|
|
||||||
import net.minecraft.world.item.TooltipFlag;
|
|
||||||
import net.minecraft.world.level.Level;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base class for gag items (ball gag, cloth gag, tape, etc.)
|
|
||||||
* These items prevent or muffle a player's speech when equipped.
|
|
||||||
*
|
|
||||||
* Based on original ItemGag from 1.12.2
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public abstract class ItemGag
|
|
||||||
extends Item
|
|
||||||
implements IBondageItem, IHasGaggingEffect, IAdjustable, ILockable
|
|
||||||
{
|
|
||||||
|
|
||||||
private final GagMaterial material;
|
|
||||||
|
|
||||||
public ItemGag(Properties properties, GagMaterial material) {
|
|
||||||
super(properties);
|
|
||||||
this.material = material;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GagMaterial getGagMaterial() {
|
|
||||||
return this.material;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BodyRegionV2 getBodyRegion() {
|
|
||||||
return BodyRegionV2.MOUTH;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void appendHoverText(
|
|
||||||
ItemStack stack,
|
|
||||||
@Nullable Level level,
|
|
||||||
List<Component> tooltip,
|
|
||||||
TooltipFlag flag
|
|
||||||
) {
|
|
||||||
super.appendHoverText(stack, level, tooltip, flag);
|
|
||||||
appendLockTooltip(stack, tooltip);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All gags can be adjusted to better fit player skins.
|
|
||||||
* @return true - gags support position adjustment
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean canBeAdjusted() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when player right-clicks another entity with this gag.
|
|
||||||
* Allows putting gag on tied-up entities (Players and NPCs).
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public InteractionResult interactLivingEntity(
|
|
||||||
ItemStack stack,
|
|
||||||
Player player,
|
|
||||||
LivingEntity target,
|
|
||||||
InteractionHand hand
|
|
||||||
) {
|
|
||||||
return EquipmentInteractionHelper.equipOnTarget(
|
|
||||||
stack,
|
|
||||||
player,
|
|
||||||
target,
|
|
||||||
state -> state.isGagged(),
|
|
||||||
(state, item) -> state.equip(BodyRegionV2.MOUTH, item),
|
|
||||||
(state, item) ->
|
|
||||||
state.replaceEquipment(BodyRegionV2.MOUTH, item, false),
|
|
||||||
SystemMessageManager::sendGagged,
|
|
||||||
"ItemGag"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ILockable implementation inherited from interface default methods
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
package com.tiedup.remake.items.base;
|
|
||||||
|
|
||||||
import com.tiedup.remake.core.SystemMessageManager;
|
|
||||||
import com.tiedup.remake.util.EquipmentInteractionHelper;
|
|
||||||
import com.tiedup.remake.v2.BodyRegionV2;
|
|
||||||
import net.minecraft.world.InteractionHand;
|
|
||||||
import net.minecraft.world.InteractionResult;
|
|
||||||
import net.minecraft.world.entity.LivingEntity;
|
|
||||||
import net.minecraft.world.entity.player.Player;
|
|
||||||
import net.minecraft.world.item.Item;
|
|
||||||
import net.minecraft.world.item.ItemStack;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base class for mittens items.
|
|
||||||
* These items block hand interactions (mining, placing, using items) when equipped.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* Restrictions when wearing mittens:
|
|
||||||
* - Cannot mine/break blocks
|
|
||||||
* - Cannot place blocks
|
|
||||||
* - Cannot use items
|
|
||||||
* - Cannot attack (0 damage punch allowed)
|
|
||||||
*
|
|
||||||
* Allowed:
|
|
||||||
* - Push buttons/levers
|
|
||||||
* - Open doors
|
|
||||||
*/
|
|
||||||
public abstract class ItemMittens
|
|
||||||
extends Item
|
|
||||||
implements IBondageItem, ILockable
|
|
||||||
{
|
|
||||||
|
|
||||||
public ItemMittens(Properties properties) {
|
|
||||||
super(properties);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BodyRegionV2 getBodyRegion() {
|
|
||||||
return BodyRegionV2.HANDS;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when player right-clicks another entity with these mittens.
|
|
||||||
* Allows putting mittens on tied-up entities (Players and NPCs).
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public InteractionResult interactLivingEntity(
|
|
||||||
ItemStack stack,
|
|
||||||
Player player,
|
|
||||||
LivingEntity target,
|
|
||||||
InteractionHand hand
|
|
||||||
) {
|
|
||||||
return EquipmentInteractionHelper.equipOnTarget(
|
|
||||||
stack,
|
|
||||||
player,
|
|
||||||
target,
|
|
||||||
state -> state.hasMittens(),
|
|
||||||
(state, item) -> state.equip(BodyRegionV2.HANDS, item),
|
|
||||||
(state, item) ->
|
|
||||||
state.replaceEquipment(BodyRegionV2.HANDS, item, false),
|
|
||||||
(p, t) ->
|
|
||||||
SystemMessageManager.sendToTarget(
|
|
||||||
p,
|
|
||||||
t,
|
|
||||||
SystemMessageManager.MessageCategory.MITTENS_ON
|
|
||||||
),
|
|
||||||
"ItemMittens"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ILockable implementation inherited from interface default methods
|
|
||||||
}
|
|
||||||
@@ -195,11 +195,11 @@ public abstract class ItemOwnerTarget extends Item {
|
|||||||
BodyRegionV2.NECK
|
BodyRegionV2.NECK
|
||||||
);
|
);
|
||||||
if (
|
if (
|
||||||
collar.getItem() instanceof ItemCollar collarItem &&
|
com.tiedup.remake.v2.bondage.CollarHelper.isCollar(collar) &&
|
||||||
collarItem.hasNickname(collar)
|
com.tiedup.remake.v2.bondage.CollarHelper.hasNickname(collar)
|
||||||
) {
|
) {
|
||||||
displayName =
|
displayName =
|
||||||
collarItem.getNickname(collar) +
|
com.tiedup.remake.v2.bondage.CollarHelper.getNickname(collar) +
|
||||||
" (" +
|
" (" +
|
||||||
displayName +
|
displayName +
|
||||||
")";
|
")";
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
package com.tiedup.remake.items.base;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enum defining all mittens variants.
|
|
||||||
* Used by GenericMittens to create mittens items via factory pattern.
|
|
||||||
*
|
|
||||||
* <p>Mittens system - blocks hand interactions when equipped.
|
|
||||||
*
|
|
||||||
* <p><b>Issue #12 fix:</b> Added textureSubfolder to eliminate string checks in renderers.
|
|
||||||
*/
|
|
||||||
public enum MittensVariant {
|
|
||||||
LEATHER("mittens", "mittens");
|
|
||||||
|
|
||||||
private final String registryName;
|
|
||||||
private final String textureSubfolder;
|
|
||||||
|
|
||||||
MittensVariant(String registryName, String textureSubfolder) {
|
|
||||||
this.registryName = registryName;
|
|
||||||
this.textureSubfolder = textureSubfolder;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRegistryName() {
|
|
||||||
return registryName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the texture subfolder for this mittens variant.
|
|
||||||
* Used by renderers to locate texture files.
|
|
||||||
*
|
|
||||||
* @return Subfolder path under textures/entity/bondage/ (e.g., "mittens")
|
|
||||||
*/
|
|
||||||
public String getTextureSubfolder() {
|
|
||||||
return textureSubfolder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
package com.tiedup.remake.items.bondage3d.gags;
|
|
||||||
|
|
||||||
import com.tiedup.remake.items.base.ItemGag;
|
|
||||||
import com.tiedup.remake.items.bondage3d.IHas3DModelConfig;
|
|
||||||
import com.tiedup.remake.items.bondage3d.Model3DConfig;
|
|
||||||
import com.tiedup.remake.util.GagMaterial;
|
|
||||||
import java.util.Set;
|
|
||||||
import net.minecraft.resources.ResourceLocation;
|
|
||||||
import net.minecraft.world.item.Item;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ball Gag 3D - Extends ItemGag with 3D OBJ model rendering.
|
|
||||||
* All 3D configuration is defined here.
|
|
||||||
* Supports color variants via tinting the "Ball" material.
|
|
||||||
*/
|
|
||||||
public class ItemBallGag3D extends ItemGag implements IHas3DModelConfig {
|
|
||||||
|
|
||||||
// 3D config with "Ball" material tintable for color variants
|
|
||||||
private static final Model3DConfig CONFIG = new Model3DConfig(
|
|
||||||
"tiedup:models/obj/ball_gag/model.obj", // OBJ
|
|
||||||
"tiedup:models/obj/ball_gag/texture.png", // Explicit texture
|
|
||||||
0.0f, // posX
|
|
||||||
1.55f, // posY
|
|
||||||
0.0f, // posZ
|
|
||||||
1.0f, // scale
|
|
||||||
0.0f,
|
|
||||||
0.0f,
|
|
||||||
180.0f, // rotation
|
|
||||||
Set.of("Ball") // Tintable materials (for color variants)
|
|
||||||
);
|
|
||||||
|
|
||||||
public ItemBallGag3D() {
|
|
||||||
super(new Item.Properties().stacksTo(16), GagMaterial.BALL);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===== 3D Model Support =====
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean uses3DModel() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Nullable
|
|
||||||
public ResourceLocation get3DModelLocation() {
|
|
||||||
return ResourceLocation.tryParse(CONFIG.objPath());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the complete 3D configuration for the renderer.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Model3DConfig getModelConfig() {
|
|
||||||
return CONFIG;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Explicit texture (if non-null, overrides MTL map_Kd).
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
public ResourceLocation getExplicitTexture() {
|
|
||||||
String path = CONFIG.texturePath();
|
|
||||||
return path != null ? ResourceLocation.tryParse(path) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===== Gag Properties =====
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getTextureSubfolder() {
|
|
||||||
return "ballgags/normal"; // Fallback if 3D fails
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canAttachPadlock() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -166,11 +166,8 @@ public class PacketTighten {
|
|||||||
// Check if sender owns collar
|
// Check if sender owns collar
|
||||||
if (!hasPermission && closestState.hasCollar()) {
|
if (!hasPermission && closestState.hasCollar()) {
|
||||||
var collarStack = closestState.getEquipment(BodyRegionV2.NECK);
|
var collarStack = closestState.getEquipment(BodyRegionV2.NECK);
|
||||||
if (
|
if (com.tiedup.remake.v2.bondage.CollarHelper.isCollar(collarStack)) {
|
||||||
collarStack.getItem() instanceof
|
if (com.tiedup.remake.v2.bondage.CollarHelper.isOwner(collarStack, tightener)) {
|
||||||
com.tiedup.remake.items.base.ItemCollar collar
|
|
||||||
) {
|
|
||||||
if (collar.isOwner(collarStack, tightener)) {
|
|
||||||
hasPermission = true;
|
hasPermission = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package com.tiedup.remake.network.item;
|
|||||||
|
|
||||||
import com.tiedup.remake.core.TiedUpMod;
|
import com.tiedup.remake.core.TiedUpMod;
|
||||||
import com.tiedup.remake.items.base.AdjustmentHelper;
|
import com.tiedup.remake.items.base.AdjustmentHelper;
|
||||||
import com.tiedup.remake.items.base.IAdjustable;
|
|
||||||
import com.tiedup.remake.network.sync.SyncManager;
|
import com.tiedup.remake.network.sync.SyncManager;
|
||||||
import com.tiedup.remake.state.PlayerBindState;
|
import com.tiedup.remake.state.PlayerBindState;
|
||||||
import com.tiedup.remake.v2.BodyRegionV2;
|
import com.tiedup.remake.v2.BodyRegionV2;
|
||||||
@@ -152,7 +151,7 @@ public class PacketAdjustItem {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(stack.getItem() instanceof IAdjustable)) {
|
if (!AdjustmentHelper.isAdjustable(stack)) {
|
||||||
TiedUpMod.LOGGER.warn(
|
TiedUpMod.LOGGER.warn(
|
||||||
"[PACKET] PacketAdjustItem: Item {} is not adjustable",
|
"[PACKET] PacketAdjustItem: Item {} is not adjustable",
|
||||||
stack.getItem()
|
stack.getItem()
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package com.tiedup.remake.network.item;
|
|||||||
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.base.AdjustmentHelper;
|
import com.tiedup.remake.items.base.AdjustmentHelper;
|
||||||
import com.tiedup.remake.items.base.IAdjustable;
|
|
||||||
import com.tiedup.remake.network.sync.SyncManager;
|
import com.tiedup.remake.network.sync.SyncManager;
|
||||||
import com.tiedup.remake.state.IBondageState;
|
import com.tiedup.remake.state.IBondageState;
|
||||||
import com.tiedup.remake.state.PlayerBindState;
|
import com.tiedup.remake.state.PlayerBindState;
|
||||||
@@ -195,7 +194,7 @@ public class PacketAdjustRemote {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(stack.getItem() instanceof IAdjustable)) {
|
if (!AdjustmentHelper.isAdjustable(stack)) {
|
||||||
TiedUpMod.LOGGER.warn(
|
TiedUpMod.LOGGER.warn(
|
||||||
"[PACKET] PacketAdjustRemote: Item {} is not adjustable",
|
"[PACKET] PacketAdjustRemote: Item {} is not adjustable",
|
||||||
stack.getItem()
|
stack.getItem()
|
||||||
@@ -255,11 +254,8 @@ public class PacketAdjustRemote {
|
|||||||
ItemStack collarStack = kidnapped.getEquipment(
|
ItemStack collarStack = kidnapped.getEquipment(
|
||||||
BodyRegionV2.NECK
|
BodyRegionV2.NECK
|
||||||
);
|
);
|
||||||
if (
|
if (com.tiedup.remake.v2.bondage.CollarHelper.isCollar(collarStack)) {
|
||||||
collarStack.getItem() instanceof
|
if (com.tiedup.remake.v2.bondage.CollarHelper.isOwner(collarStack, sender)) {
|
||||||
com.tiedup.remake.items.base.ItemCollar collar
|
|
||||||
) {
|
|
||||||
if (collar.isOwner(collarStack, sender)) {
|
|
||||||
return kidnapped;
|
return kidnapped;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -454,9 +454,7 @@ public class PacketLockpickAttempt {
|
|||||||
);
|
);
|
||||||
if (collar.isEmpty()) return;
|
if (collar.isEmpty()) return;
|
||||||
|
|
||||||
if (
|
if (com.tiedup.remake.v2.bondage.CollarHelper.canShock(collar)) {
|
||||||
collar.getItem() instanceof com.tiedup.remake.items.ItemShockCollar
|
|
||||||
) {
|
|
||||||
state.shockKidnapped(" (Failed lockpick attempt)", 2.0f);
|
state.shockKidnapped(" (Failed lockpick attempt)", 2.0f);
|
||||||
TiedUpMod.LOGGER.info(
|
TiedUpMod.LOGGER.info(
|
||||||
"[PacketLockpickAttempt] Player {} shocked for failed lockpick",
|
"[PacketLockpickAttempt] Player {} shocked for failed lockpick",
|
||||||
|
|||||||
@@ -70,11 +70,8 @@ public class PacketRequestNpcInventory {
|
|||||||
// Verify player is owner of collar (or NPC has no collar)
|
// Verify player is owner of collar (or NPC has no collar)
|
||||||
if (damsel.hasCollar()) {
|
if (damsel.hasCollar()) {
|
||||||
ItemStack collar = damsel.getEquipment(BodyRegionV2.NECK);
|
ItemStack collar = damsel.getEquipment(BodyRegionV2.NECK);
|
||||||
if (
|
if (com.tiedup.remake.v2.bondage.CollarHelper.isCollar(collar)) {
|
||||||
collar.getItem() instanceof
|
if (!com.tiedup.remake.v2.bondage.CollarHelper.isOwner(collar, player)) {
|
||||||
com.tiedup.remake.items.base.ItemCollar collarItem
|
|
||||||
) {
|
|
||||||
if (!collarItem.isOwner(collar, player)) {
|
|
||||||
TiedUpMod.LOGGER.warn(
|
TiedUpMod.LOGGER.warn(
|
||||||
"[PacketRequestNpcInventory] Player {} is not owner of NPC collar",
|
"[PacketRequestNpcInventory] Player {} is not owner of NPC collar",
|
||||||
player.getName().getString()
|
player.getName().getString()
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package com.tiedup.remake.network.selfbondage;
|
|||||||
|
|
||||||
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.items.base.*;
|
import com.tiedup.remake.items.base.ILockable;
|
||||||
import com.tiedup.remake.network.ModNetwork;
|
import com.tiedup.remake.network.ModNetwork;
|
||||||
import com.tiedup.remake.network.PacketRateLimiter;
|
import com.tiedup.remake.network.PacketRateLimiter;
|
||||||
import com.tiedup.remake.network.action.PacketTying;
|
import com.tiedup.remake.network.action.PacketTying;
|
||||||
@@ -79,141 +79,13 @@ public class PacketSelfBondage {
|
|||||||
// V2 bondage items — use tying task with V2 equip
|
// V2 bondage items — use tying task with V2 equip
|
||||||
if (item instanceof IV2BondageItem v2Item) {
|
if (item instanceof IV2BondageItem v2Item) {
|
||||||
handleV2SelfBondage(player, stack, v2Item, state);
|
handleV2SelfBondage(player, stack, v2Item, state);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
// V1 routes removed — all bondage items are now V2/data-driven
|
||||||
// V1 routes below (legacy)
|
|
||||||
if (item instanceof ItemBind bind) {
|
|
||||||
handleSelfBind(player, stack, bind, state);
|
|
||||||
} else if (item instanceof ItemGag) {
|
|
||||||
handleSelfAccessory(
|
|
||||||
player,
|
|
||||||
stack,
|
|
||||||
state,
|
|
||||||
"gag",
|
|
||||||
s -> s.isGagged(),
|
|
||||||
s -> s.getEquipment(BodyRegionV2.MOUTH),
|
|
||||||
s -> s.unequip(BodyRegionV2.MOUTH),
|
|
||||||
(s, i) -> s.equip(BodyRegionV2.MOUTH, i)
|
|
||||||
);
|
|
||||||
} else if (item instanceof ItemBlindfold) {
|
|
||||||
handleSelfAccessory(
|
|
||||||
player,
|
|
||||||
stack,
|
|
||||||
state,
|
|
||||||
"blindfold",
|
|
||||||
s -> s.isBlindfolded(),
|
|
||||||
s -> s.getEquipment(BodyRegionV2.EYES),
|
|
||||||
s -> s.unequip(BodyRegionV2.EYES),
|
|
||||||
(s, i) -> s.equip(BodyRegionV2.EYES, i)
|
|
||||||
);
|
|
||||||
} else if (item instanceof ItemMittens) {
|
|
||||||
handleSelfAccessory(
|
|
||||||
player,
|
|
||||||
stack,
|
|
||||||
state,
|
|
||||||
"mittens",
|
|
||||||
s -> s.hasMittens(),
|
|
||||||
s -> s.getEquipment(BodyRegionV2.HANDS),
|
|
||||||
s -> s.unequip(BodyRegionV2.HANDS),
|
|
||||||
(s, i) -> s.equip(BodyRegionV2.HANDS, i)
|
|
||||||
);
|
|
||||||
} else if (item instanceof ItemEarplugs) {
|
|
||||||
handleSelfAccessory(
|
|
||||||
player,
|
|
||||||
stack,
|
|
||||||
state,
|
|
||||||
"earplugs",
|
|
||||||
s -> s.hasEarplugs(),
|
|
||||||
s -> s.getEquipment(BodyRegionV2.EARS),
|
|
||||||
s -> s.unequip(BodyRegionV2.EARS),
|
|
||||||
(s, i) -> s.equip(BodyRegionV2.EARS, i)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// ItemCollar: NOT handled - cannot self-collar
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ctx.get().setPacketHandled(true);
|
ctx.get().setPacketHandled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle self-binding with a bind item (rope, chain, etc.).
|
|
||||||
* Uses tying task system - requires holding left-click.
|
|
||||||
*/
|
|
||||||
private static void handleSelfBind(
|
|
||||||
ServerPlayer player,
|
|
||||||
ItemStack stack,
|
|
||||||
ItemBind bind,
|
|
||||||
IBondageState state
|
|
||||||
) {
|
|
||||||
// Can't self-tie if already tied
|
|
||||||
if (state.isTiedUp()) {
|
|
||||||
TiedUpMod.LOGGER.debug(
|
|
||||||
"[SelfBondage] {} tried to self-tie but is already tied",
|
|
||||||
player.getName().getString()
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get player's bind state for tying task management
|
|
||||||
PlayerBindState playerState = PlayerBindState.getInstance(player);
|
|
||||||
if (playerState == null) return;
|
|
||||||
|
|
||||||
// Get tying duration from GameRule
|
|
||||||
int tyingSeconds = SettingsAccessor.getTyingPlayerTime(
|
|
||||||
player.level().getGameRules()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Create self-tying task (target == kidnapper)
|
|
||||||
TyingPlayerTask newTask = new TyingPlayerTask(
|
|
||||||
stack.copy(),
|
|
||||||
state,
|
|
||||||
player, // Target is self
|
|
||||||
tyingSeconds,
|
|
||||||
player.level(),
|
|
||||||
player // Kidnapper is also self
|
|
||||||
);
|
|
||||||
|
|
||||||
// Get current tying task
|
|
||||||
TyingTask currentTask = playerState.getCurrentTyingTask();
|
|
||||||
|
|
||||||
// Check if we should start a new task or continue existing one
|
|
||||||
if (
|
|
||||||
currentTask == null ||
|
|
||||||
!currentTask.isSameTarget(player) ||
|
|
||||||
currentTask.isOutdated() ||
|
|
||||||
!ItemStack.matches(currentTask.getBind(), stack)
|
|
||||||
) {
|
|
||||||
// Start new self-tying task
|
|
||||||
playerState.setCurrentTyingTask(newTask);
|
|
||||||
newTask.start();
|
|
||||||
|
|
||||||
TiedUpMod.LOGGER.debug(
|
|
||||||
"[SelfBondage] {} started self-tying ({} seconds)",
|
|
||||||
player.getName().getString(),
|
|
||||||
tyingSeconds
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// Continue existing task
|
|
||||||
newTask = (TyingPlayerTask) currentTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update task progress
|
|
||||||
newTask.update();
|
|
||||||
|
|
||||||
// Check if task completed
|
|
||||||
if (newTask.isStopped()) {
|
|
||||||
// Self-tying complete! Consume the item
|
|
||||||
stack.shrink(1);
|
|
||||||
playerState.setCurrentTyingTask(null);
|
|
||||||
|
|
||||||
TiedUpMod.LOGGER.info(
|
|
||||||
"[SelfBondage] {} successfully self-tied",
|
|
||||||
player.getName().getString()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle self-bondage with a V2 bondage item.
|
* Handle self-bondage with a V2 bondage item.
|
||||||
* Uses V2TyingPlayerTask for progress, V2EquipmentHelper for equip.
|
* Uses V2TyingPlayerTask for progress, V2EquipmentHelper for equip.
|
||||||
@@ -355,7 +227,7 @@ public class PacketSelfBondage {
|
|||||||
// Can't equip if arms are fully bound (need hands to put on accessories)
|
// Can't equip if arms are fully bound (need hands to put on accessories)
|
||||||
ItemStack currentBind = state.getEquipment(BodyRegionV2.ARMS);
|
ItemStack currentBind = state.getEquipment(BodyRegionV2.ARMS);
|
||||||
if (!currentBind.isEmpty()) {
|
if (!currentBind.isEmpty()) {
|
||||||
if (ItemBind.hasArmsBound(currentBind)) {
|
if (BindModeHelper.hasArmsBound(currentBind)) {
|
||||||
TiedUpMod.LOGGER.debug(
|
TiedUpMod.LOGGER.debug(
|
||||||
"[SelfBondage] {} can't self-{} - arms are bound",
|
"[SelfBondage] {} can't self-{} - arms are bound",
|
||||||
player.getName().getString(),
|
player.getName().getString(),
|
||||||
|
|||||||
@@ -163,12 +163,9 @@ public class PacketSlaveItemManage {
|
|||||||
ItemStack collarStack = targetState.getEquipment(
|
ItemStack collarStack = targetState.getEquipment(
|
||||||
BodyRegionV2.NECK
|
BodyRegionV2.NECK
|
||||||
);
|
);
|
||||||
if (
|
if (com.tiedup.remake.v2.bondage.CollarHelper.isCollar(collarStack)) {
|
||||||
collarStack.getItem() instanceof
|
|
||||||
com.tiedup.remake.items.base.ItemCollar collar
|
|
||||||
) {
|
|
||||||
if (
|
if (
|
||||||
!collar.isOwner(collarStack, sender) &&
|
!com.tiedup.remake.v2.bondage.CollarHelper.isOwner(collarStack, sender) &&
|
||||||
!sender.hasPermissions(2)
|
!sender.hasPermissions(2)
|
||||||
) {
|
) {
|
||||||
TiedUpMod.LOGGER.debug(
|
TiedUpMod.LOGGER.debug(
|
||||||
@@ -437,15 +434,14 @@ public class PacketSlaveItemManage {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
region == BodyRegionV2.ARMS &&
|
region == BodyRegionV2.ARMS &&
|
||||||
itemStack.getItem() instanceof
|
itemStack.getItem() instanceof com.tiedup.remake.items.base.IHasResistance resistanceItem
|
||||||
com.tiedup.remake.items.base.ItemBind bind
|
|
||||||
) {
|
) {
|
||||||
int currentResistance = bind.getCurrentResistance(
|
int currentResistance = resistanceItem.getCurrentResistance(
|
||||||
itemStack,
|
itemStack,
|
||||||
target
|
target
|
||||||
);
|
);
|
||||||
int lockResistance = lockable.getLockResistance(); // Configurable via ModConfig
|
int lockResistance = lockable.getLockResistance(); // Configurable via ModConfig
|
||||||
bind.setCurrentResistance(
|
resistanceItem.setCurrentResistance(
|
||||||
itemStack,
|
itemStack,
|
||||||
currentResistance + lockResistance
|
currentResistance + lockResistance
|
||||||
);
|
);
|
||||||
@@ -599,8 +595,7 @@ public class PacketSlaveItemManage {
|
|||||||
) {
|
) {
|
||||||
if (
|
if (
|
||||||
collarStack.isEmpty() ||
|
collarStack.isEmpty() ||
|
||||||
!(collarStack.getItem() instanceof
|
!com.tiedup.remake.v2.bondage.CollarHelper.isCollar(collarStack)
|
||||||
com.tiedup.remake.items.base.ItemCollar collar)
|
|
||||||
) {
|
) {
|
||||||
TiedUpMod.LOGGER.debug(
|
TiedUpMod.LOGGER.debug(
|
||||||
"[PacketSlaveItemManage] No collar for bondage service toggle"
|
"[PacketSlaveItemManage] No collar for bondage service toggle"
|
||||||
@@ -609,7 +604,7 @@ public class PacketSlaveItemManage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if cell is configured (required for bondage service)
|
// Check if cell is configured (required for bondage service)
|
||||||
if (!collar.hasCellAssigned(collarStack)) {
|
if (!com.tiedup.remake.v2.bondage.CollarHelper.hasCellAssigned(collarStack)) {
|
||||||
TiedUpMod.LOGGER.debug(
|
TiedUpMod.LOGGER.debug(
|
||||||
"[PacketSlaveItemManage] Cannot enable bondage service: no cell configured"
|
"[PacketSlaveItemManage] Cannot enable bondage service: no cell configured"
|
||||||
);
|
);
|
||||||
@@ -623,8 +618,8 @@ public class PacketSlaveItemManage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Toggle bondage service
|
// Toggle bondage service
|
||||||
boolean currentState = collar.isBondageServiceEnabled(collarStack);
|
boolean currentState = com.tiedup.remake.v2.bondage.CollarHelper.isBondageServiceEnabled(collarStack);
|
||||||
collar.setBondageServiceEnabled(collarStack, !currentState);
|
com.tiedup.remake.v2.bondage.CollarHelper.setBondageServiceEnabled(collarStack, !currentState);
|
||||||
|
|
||||||
String newState = !currentState ? "enabled" : "disabled";
|
String newState = !currentState ? "enabled" : "disabled";
|
||||||
TiedUpMod.LOGGER.info(
|
TiedUpMod.LOGGER.info(
|
||||||
|
|||||||
@@ -243,22 +243,23 @@ public class PacketBuyCaptive {
|
|||||||
kidnappedState.getEquipment(BodyRegionV2.NECK);
|
kidnappedState.getEquipment(BodyRegionV2.NECK);
|
||||||
if (
|
if (
|
||||||
!collar.isEmpty() &&
|
!collar.isEmpty() &&
|
||||||
collar.getItem() instanceof
|
com.tiedup.remake.v2.bondage.CollarHelper.isCollar(collar)
|
||||||
com.tiedup.remake.items.base.ItemCollar collarItem
|
|
||||||
) {
|
) {
|
||||||
// Remove all existing owners from collar NBT
|
// Remove all existing owners from collar NBT
|
||||||
for (UUID ownerId : new java.util.ArrayList<>(
|
for (UUID ownerId : new java.util.ArrayList<>(
|
||||||
collarItem.getOwners(collar)
|
com.tiedup.remake.v2.bondage.CollarHelper.getOwners(collar)
|
||||||
)) {
|
)) {
|
||||||
collarItem.removeOwner(collar, ownerId);
|
com.tiedup.remake.v2.bondage.CollarHelper.removeOwner(collar, ownerId);
|
||||||
}
|
}
|
||||||
// Add buyer as new owner
|
// Add buyer as new owner
|
||||||
collarItem.addOwner(
|
com.tiedup.remake.v2.bondage.CollarHelper.addOwner(
|
||||||
collar,
|
collar,
|
||||||
buyer.getUUID(),
|
buyer.getUUID(),
|
||||||
buyer.getName().getString()
|
buyer.getName().getString()
|
||||||
);
|
);
|
||||||
collarItem.setLocked(collar, false);
|
if (collar.getItem() instanceof com.tiedup.remake.items.base.ILockable lockable) {
|
||||||
|
lockable.setLocked(collar, false);
|
||||||
|
}
|
||||||
|
|
||||||
// Re-apply modified collar to persist NBT changes
|
// Re-apply modified collar to persist NBT changes
|
||||||
kidnappedState.equip(BodyRegionV2.NECK, collar);
|
kidnappedState.equip(BodyRegionV2.NECK, collar);
|
||||||
|
|||||||
@@ -757,10 +757,9 @@ public class PrisonerService {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
!collar.isEmpty() &&
|
!collar.isEmpty() &&
|
||||||
collar.getItem() instanceof
|
com.tiedup.remake.v2.bondage.CollarHelper.isCollar(collar)
|
||||||
com.tiedup.remake.items.base.ItemCollar collarItem
|
|
||||||
) {
|
) {
|
||||||
List<UUID> nbtOwners = collarItem.getOwners(collar);
|
List<UUID> nbtOwners = com.tiedup.remake.v2.bondage.CollarHelper.getOwners(collar);
|
||||||
if (!nbtOwners.isEmpty()) {
|
if (!nbtOwners.isEmpty()) {
|
||||||
for (UUID ownerUUID : nbtOwners) {
|
for (UUID ownerUUID : nbtOwners) {
|
||||||
collars.registerCollar(playerId, ownerUUID);
|
collars.registerCollar(playerId, ownerUUID);
|
||||||
|
|||||||
@@ -4,12 +4,14 @@ import com.tiedup.remake.core.ModSounds;
|
|||||||
import com.tiedup.remake.core.SystemMessageManager;
|
import com.tiedup.remake.core.SystemMessageManager;
|
||||||
import com.tiedup.remake.core.SystemMessageManager.MessageCategory;
|
import com.tiedup.remake.core.SystemMessageManager.MessageCategory;
|
||||||
import com.tiedup.remake.core.TiedUpMod;
|
import com.tiedup.remake.core.TiedUpMod;
|
||||||
import com.tiedup.remake.items.ItemGpsCollar;
|
|
||||||
import com.tiedup.remake.items.ItemShockCollarAuto;
|
|
||||||
import com.tiedup.remake.state.hosts.IPlayerBindStateHost;
|
import com.tiedup.remake.state.hosts.IPlayerBindStateHost;
|
||||||
import com.tiedup.remake.util.GameConstants;
|
import com.tiedup.remake.util.GameConstants;
|
||||||
import com.tiedup.remake.util.time.Timer;
|
import com.tiedup.remake.util.time.Timer;
|
||||||
import com.tiedup.remake.v2.BodyRegionV2;
|
import com.tiedup.remake.v2.BodyRegionV2;
|
||||||
|
import com.tiedup.remake.v2.bondage.CollarHelper;
|
||||||
|
import com.tiedup.remake.v2.bondage.component.ComponentType;
|
||||||
|
import com.tiedup.remake.v2.bondage.component.GpsComponent;
|
||||||
|
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import net.minecraft.ChatFormatting;
|
import net.minecraft.ChatFormatting;
|
||||||
@@ -128,17 +130,16 @@ public class PlayerShockCollar {
|
|||||||
// Flags set inside lock, actions performed outside
|
// Flags set inside lock, actions performed outside
|
||||||
boolean shouldShockAuto = false;
|
boolean shouldShockAuto = false;
|
||||||
boolean shouldShockGPS = false;
|
boolean shouldShockGPS = false;
|
||||||
ItemGpsCollar gpsCollar = null;
|
ItemStack gpsStackCopy = null;
|
||||||
ItemStack gpsStack = null;
|
|
||||||
|
|
||||||
synchronized (lockTimerAutoShock) {
|
synchronized (lockTimerAutoShock) {
|
||||||
ItemStack collarStack = getCurrentCollar();
|
ItemStack collarStack = getCurrentCollar();
|
||||||
if (collarStack.isEmpty()) return;
|
if (collarStack.isEmpty()) return;
|
||||||
|
|
||||||
// Auto-Shock Collar handling
|
// Auto-Shock Collar handling: collar can shock AND has auto interval > 0
|
||||||
if (
|
if (CollarHelper.canShock(collarStack) && CollarHelper.getShockInterval(collarStack) > 0) {
|
||||||
collarStack.getItem() instanceof ItemShockCollarAuto collarShock
|
int interval = CollarHelper.getShockInterval(collarStack);
|
||||||
) {
|
|
||||||
if (
|
if (
|
||||||
timerAutoShockCollar != null &&
|
timerAutoShockCollar != null &&
|
||||||
timerAutoShockCollar.isExpired()
|
timerAutoShockCollar.isExpired()
|
||||||
@@ -151,40 +152,38 @@ public class PlayerShockCollar {
|
|||||||
timerAutoShockCollar.isExpired()
|
timerAutoShockCollar.isExpired()
|
||||||
) {
|
) {
|
||||||
timerAutoShockCollar = new Timer(
|
timerAutoShockCollar = new Timer(
|
||||||
collarShock.getInterval() /
|
interval / GameConstants.TICKS_PER_SECOND,
|
||||||
GameConstants.TICKS_PER_SECOND,
|
|
||||||
player.level()
|
player.level()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// GPS Collar handling
|
// GPS Collar handling
|
||||||
else if (collarStack.getItem() instanceof ItemGpsCollar gps) {
|
else if (CollarHelper.hasGPS(collarStack)) {
|
||||||
if (
|
if (
|
||||||
gps.isActive(collarStack) &&
|
CollarHelper.isActive(collarStack) &&
|
||||||
(timerAutoShockCollar == null ||
|
(timerAutoShockCollar == null ||
|
||||||
timerAutoShockCollar.isExpired())
|
timerAutoShockCollar.isExpired())
|
||||||
) {
|
) {
|
||||||
List<ItemGpsCollar.SafeSpot> safeSpots = gps.getSafeSpots(
|
GpsComponent gpsComp = DataDrivenBondageItem.getComponent(
|
||||||
collarStack
|
collarStack, ComponentType.GPS, GpsComponent.class
|
||||||
);
|
);
|
||||||
if (safeSpots != null && !safeSpots.isEmpty()) {
|
int safeZoneRadius = gpsComp != null ? gpsComp.getSafeZoneRadius() : 50;
|
||||||
boolean isSafe = false;
|
// Shock interval from ShockComponent (GPS collars also have shock capability)
|
||||||
for (ItemGpsCollar.SafeSpot spot : safeSpots) {
|
int shockInterval = CollarHelper.getShockInterval(collarStack);
|
||||||
if (spot.isInside(player)) {
|
if (shockInterval <= 0) shockInterval = 100; // Fallback: 5 seconds
|
||||||
isSafe = true;
|
|
||||||
break;
|
// Check safe spots from NBT (CollarHelper reads "safeSpots" NBT)
|
||||||
}
|
// For now, use safeZoneRadius from GpsComponent
|
||||||
}
|
// GPS safe zone check: if the collar has safe spots in NBT, check them
|
||||||
if (!isSafe) {
|
boolean isSafe = isInSafeZone(collarStack, player, safeZoneRadius);
|
||||||
timerAutoShockCollar = new Timer(
|
|
||||||
gps.getShockInterval(collarStack) /
|
if (!isSafe) {
|
||||||
GameConstants.TICKS_PER_SECOND,
|
timerAutoShockCollar = new Timer(
|
||||||
player.level()
|
shockInterval / GameConstants.TICKS_PER_SECOND,
|
||||||
);
|
player.level()
|
||||||
shouldShockGPS = true;
|
);
|
||||||
gpsCollar = gps;
|
shouldShockGPS = true;
|
||||||
gpsStack = collarStack.copy();
|
gpsStackCopy = collarStack.copy();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -195,30 +194,58 @@ public class PlayerShockCollar {
|
|||||||
this.shockKidnapped();
|
this.shockKidnapped();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldShockGPS && gpsCollar != null) {
|
if (shouldShockGPS && gpsStackCopy != null) {
|
||||||
this.shockKidnapped(
|
this.shockKidnapped(
|
||||||
" Return back to your allowed area!",
|
" Return back to your allowed area!",
|
||||||
GameConstants.DEFAULT_SHOCK_DAMAGE
|
GameConstants.DEFAULT_SHOCK_DAMAGE
|
||||||
);
|
);
|
||||||
warnOwnersGPSViolation(gpsCollar, gpsStack);
|
warnOwnersGPSViolation(gpsStackCopy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the player is inside any safe zone defined on the collar.
|
||||||
|
* Reads safe spots from NBT "safeSpots" ListTag.
|
||||||
|
*/
|
||||||
|
private boolean isInSafeZone(ItemStack collarStack, Player player, int defaultRadius) {
|
||||||
|
net.minecraft.nbt.CompoundTag tag = collarStack.getTag();
|
||||||
|
if (tag == null || !tag.contains("safeSpots", net.minecraft.nbt.Tag.TAG_LIST)) {
|
||||||
|
return true; // No safe spots defined = always safe
|
||||||
|
}
|
||||||
|
net.minecraft.nbt.ListTag safeSpots = tag.getList("safeSpots", net.minecraft.nbt.Tag.TAG_COMPOUND);
|
||||||
|
if (safeSpots.isEmpty()) return true;
|
||||||
|
|
||||||
|
for (int i = 0; i < safeSpots.size(); i++) {
|
||||||
|
net.minecraft.nbt.CompoundTag spot = safeSpots.getCompound(i);
|
||||||
|
double x = spot.getDouble("x");
|
||||||
|
double y = spot.getDouble("y");
|
||||||
|
double z = spot.getDouble("z");
|
||||||
|
int radius = spot.contains("radius") ? spot.getInt("radius") : defaultRadius;
|
||||||
|
double dist = player.distanceToSqr(x, y, z);
|
||||||
|
if (dist <= (double) radius * radius) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a global alert to masters when a slave violates their GPS zone.
|
* Sends a global alert to masters when a slave violates their GPS zone.
|
||||||
* Private helper method.
|
* Private helper method.
|
||||||
*/
|
*/
|
||||||
private void warnOwnersGPSViolation(ItemGpsCollar gps, ItemStack stack) {
|
private void warnOwnersGPSViolation(ItemStack stack) {
|
||||||
Player player = host.getPlayer();
|
Player player = host.getPlayer();
|
||||||
if (player.getServer() == null) return;
|
if (player.getServer() == null) return;
|
||||||
|
|
||||||
|
if (!CollarHelper.shouldWarnMasters(stack)) return;
|
||||||
|
|
||||||
// Format: "ALERT: <player name> is outside the safe zone!"
|
// Format: "ALERT: <player name> is outside the safe zone!"
|
||||||
String alertMessage = String.format(
|
String alertMessage = String.format(
|
||||||
SystemMessageManager.getTemplate(MessageCategory.GPS_OWNER_ALERT),
|
SystemMessageManager.getTemplate(MessageCategory.GPS_OWNER_ALERT),
|
||||||
player.getName().getString()
|
player.getName().getString()
|
||||||
);
|
);
|
||||||
|
|
||||||
for (UUID ownerId : gps.getOwners(stack)) {
|
for (UUID ownerId : CollarHelper.getOwners(stack)) {
|
||||||
ServerPlayer owner = player
|
ServerPlayer owner = player
|
||||||
.getServer()
|
.getServer()
|
||||||
.getPlayerList()
|
.getPlayerList()
|
||||||
|
|||||||
@@ -133,10 +133,9 @@ public class PlayerStateQuery {
|
|||||||
BodyRegionV2.MOUTH
|
BodyRegionV2.MOUTH
|
||||||
);
|
);
|
||||||
if (gag.isEmpty()) return false;
|
if (gag.isEmpty()) return false;
|
||||||
return (
|
return com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem.getComponent(
|
||||||
gag.getItem() instanceof
|
gag, com.tiedup.remake.v2.bondage.component.ComponentType.GAGGING,
|
||||||
com.tiedup.remake.items.base.IHasGaggingEffect
|
com.tiedup.remake.v2.bondage.component.GaggingComponent.class) != null;
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -150,10 +149,9 @@ public class PlayerStateQuery {
|
|||||||
BodyRegionV2.EYES
|
BodyRegionV2.EYES
|
||||||
);
|
);
|
||||||
if (blindfold.isEmpty()) return false;
|
if (blindfold.isEmpty()) return false;
|
||||||
return (
|
return com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem.getComponent(
|
||||||
blindfold.getItem() instanceof
|
blindfold, com.tiedup.remake.v2.bondage.component.ComponentType.BLINDING,
|
||||||
com.tiedup.remake.items.base.IHasBlindingEffect
|
com.tiedup.remake.v2.bondage.component.BlindingComponent.class) != null;
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -199,10 +199,13 @@ public class StruggleAccessory extends StruggleState {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
!collar.isEmpty() &&
|
!collar.isEmpty() &&
|
||||||
collar.getItem() instanceof
|
com.tiedup.remake.v2.bondage.CollarHelper.canShock(collar)
|
||||||
com.tiedup.remake.items.ItemShockCollar shockCollar
|
|
||||||
) {
|
) {
|
||||||
return shockCollar.notifyStruggle(player, collar);
|
// V2 shock collar — notify via IHasResistance
|
||||||
|
if (collar.getItem() instanceof com.tiedup.remake.items.base.IHasResistance resistance) {
|
||||||
|
resistance.notifyStruggle(player);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return true; // No collar, proceed normally
|
return true; // No collar, proceed normally
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import com.tiedup.remake.core.SystemMessageManager.MessageCategory;
|
|||||||
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.ILockable;
|
import com.tiedup.remake.items.base.ILockable;
|
||||||
import com.tiedup.remake.items.base.ItemBind;
|
|
||||||
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.v2.BodyRegionV2;
|
import com.tiedup.remake.v2.BodyRegionV2;
|
||||||
@@ -155,10 +154,6 @@ public class StruggleBinds extends StruggleState {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!collar.isEmpty() && CollarHelper.canShock(collar)) {
|
if (!collar.isEmpty() && CollarHelper.canShock(collar)) {
|
||||||
// V1 shock collar
|
|
||||||
if (collar.getItem() instanceof com.tiedup.remake.items.ItemShockCollar shockCollar) {
|
|
||||||
return shockCollar.notifyStruggle(player, collar);
|
|
||||||
}
|
|
||||||
// V2 shock collar — notify via IHasResistance if available
|
// V2 shock collar — notify via IHasResistance if available
|
||||||
if (collar.getItem() instanceof IHasResistance resistance) {
|
if (collar.getItem() instanceof IHasResistance resistance) {
|
||||||
resistance.notifyStruggle(player);
|
resistance.notifyStruggle(player);
|
||||||
@@ -339,8 +334,6 @@ public class StruggleBinds extends StruggleState {
|
|||||||
);
|
);
|
||||||
if (comp != null) {
|
if (comp != null) {
|
||||||
baseResistance = comp.getBaseResistance();
|
baseResistance = comp.getBaseResistance();
|
||||||
} else if (bindStack.getItem() instanceof ItemBind bind) {
|
|
||||||
baseResistance = SettingsAccessor.getBindResistance(bind.getItemName());
|
|
||||||
} else {
|
} else {
|
||||||
baseResistance = 100;
|
baseResistance = 100;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import com.tiedup.remake.core.SystemMessageManager.MessageCategory;
|
|||||||
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.ILockable;
|
import com.tiedup.remake.items.base.ILockable;
|
||||||
import com.tiedup.remake.items.base.ItemCollar;
|
|
||||||
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.CollarHelper;
|
import com.tiedup.remake.v2.bondage.CollarHelper;
|
||||||
@@ -139,10 +138,6 @@ public class StruggleCollar extends StruggleState {
|
|||||||
ItemStack collar = V2EquipmentHelper.getInRegion(player, BodyRegionV2.NECK);
|
ItemStack collar = V2EquipmentHelper.getInRegion(player, BodyRegionV2.NECK);
|
||||||
|
|
||||||
if (!collar.isEmpty() && CollarHelper.canShock(collar)) {
|
if (!collar.isEmpty() && CollarHelper.canShock(collar)) {
|
||||||
// V1 shock collar
|
|
||||||
if (collar.getItem() instanceof com.tiedup.remake.items.ItemShockCollar shockCollar) {
|
|
||||||
return shockCollar.notifyStruggle(player, collar);
|
|
||||||
}
|
|
||||||
// V2 shock collar — notify via IHasResistance
|
// V2 shock collar — notify via IHasResistance
|
||||||
if (collar.getItem() instanceof IHasResistance resistance) {
|
if (collar.getItem() instanceof IHasResistance resistance) {
|
||||||
resistance.notifyStruggle(player);
|
resistance.notifyStruggle(player);
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
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.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.BodyRegionV2;
|
||||||
import com.tiedup.remake.v2.bondage.BindModeHelper;
|
import com.tiedup.remake.v2.bondage.BindModeHelper;
|
||||||
import com.tiedup.remake.v2.bondage.CollarHelper;
|
import com.tiedup.remake.v2.bondage.CollarHelper;
|
||||||
@@ -53,31 +48,30 @@ public final class BondageItemLoaderUtility {
|
|||||||
ItemStack stack,
|
ItemStack stack,
|
||||||
Player player
|
Player player
|
||||||
) {
|
) {
|
||||||
if ((stack.getItem() instanceof ItemBind || BindModeHelper.isBindItem(stack)) && holder.getBind().isEmpty()) {
|
if (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
|
if (DataDrivenBondageItem.getComponent(stack, ComponentType.GAGGING, GaggingComponent.class) != null
|
||||||
|| DataDrivenBondageItem.getComponent(stack, ComponentType.GAGGING, GaggingComponent.class) != null)
|
|
||||||
&& holder.getGag().isEmpty()) {
|
&& 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 ((stack.getItem() instanceof ItemBlindfold || isDataDrivenForRegion(stack, BodyRegionV2.EYES))
|
if (isDataDrivenForRegion(stack, BodyRegionV2.EYES)
|
||||||
&& 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 ((stack.getItem() instanceof ItemEarplugs || isDataDrivenForRegion(stack, BodyRegionV2.EARS))
|
if (isDataDrivenForRegion(stack, BodyRegionV2.EARS)
|
||||||
&& 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 ((stack.getItem() instanceof ItemCollar || CollarHelper.isCollar(stack))
|
if (CollarHelper.isCollar(stack)
|
||||||
&& 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);
|
||||||
@@ -96,14 +90,6 @@ public final class BondageItemLoaderUtility {
|
|||||||
*/
|
*/
|
||||||
public static boolean isLoadableBondageItem(ItemStack stack) {
|
public static boolean isLoadableBondageItem(ItemStack stack) {
|
||||||
if (stack.isEmpty()) return false;
|
if (stack.isEmpty()) return false;
|
||||||
// V1 item types
|
|
||||||
if (stack.getItem() instanceof ItemBind
|
|
||||||
|| stack.getItem() instanceof ItemGag
|
|
||||||
|| stack.getItem() instanceof ItemBlindfold
|
|
||||||
|| stack.getItem() instanceof ItemEarplugs
|
|
||||||
|| stack.getItem() instanceof ItemCollar) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// V2 data-driven items: bind, gag, blindfold, earplugs, collar
|
// V2 data-driven items: bind, gag, blindfold, earplugs, collar
|
||||||
if (BindModeHelper.isBindItem(stack)) return true;
|
if (BindModeHelper.isBindItem(stack)) return true;
|
||||||
if (DataDrivenBondageItem.getComponent(stack, ComponentType.GAGGING, GaggingComponent.class) != null) return true;
|
if (DataDrivenBondageItem.getComponent(stack, ComponentType.GAGGING, GaggingComponent.class) != null) return true;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.tiedup.remake.v2.bondage;
|
package com.tiedup.remake.v2.bondage;
|
||||||
|
|
||||||
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.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;
|
||||||
@@ -42,8 +41,8 @@ public final class BindModeHelper {
|
|||||||
if (def != null) {
|
if (def != null) {
|
||||||
return def.occupiedRegions().contains(BodyRegionV2.ARMS);
|
return def.occupiedRegions().contains(BodyRegionV2.ARMS);
|
||||||
}
|
}
|
||||||
// V1 fallback
|
// No V2 definition found
|
||||||
return stack.getItem() instanceof ItemBind;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
package com.tiedup.remake.v2.bondage;
|
package com.tiedup.remake.v2.bondage;
|
||||||
|
|
||||||
import com.tiedup.remake.items.ItemChokeCollar;
|
import com.tiedup.remake.core.TiedUpMod;
|
||||||
import com.tiedup.remake.items.ItemGpsCollar;
|
import com.tiedup.remake.entities.EntityKidnapper;
|
||||||
import com.tiedup.remake.items.ItemShockCollar;
|
import com.tiedup.remake.entities.ai.kidnapper.KidnapperState;
|
||||||
import com.tiedup.remake.items.ItemShockCollarAuto;
|
import com.tiedup.remake.state.CollarRegistry;
|
||||||
import com.tiedup.remake.items.base.ItemCollar;
|
|
||||||
import com.tiedup.remake.v2.bondage.component.ChokingComponent;
|
import com.tiedup.remake.v2.bondage.component.ChokingComponent;
|
||||||
import com.tiedup.remake.v2.bondage.component.ComponentType;
|
import com.tiedup.remake.v2.bondage.component.ComponentType;
|
||||||
import com.tiedup.remake.v2.bondage.component.GpsComponent;
|
import com.tiedup.remake.v2.bondage.component.GpsComponent;
|
||||||
@@ -17,8 +16,11 @@ import java.util.UUID;
|
|||||||
import net.minecraft.nbt.CompoundTag;
|
import net.minecraft.nbt.CompoundTag;
|
||||||
import net.minecraft.nbt.ListTag;
|
import net.minecraft.nbt.ListTag;
|
||||||
import net.minecraft.nbt.Tag;
|
import net.minecraft.nbt.Tag;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
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 net.minecraft.world.phys.AABB;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -29,15 +31,16 @@ public final class CollarHelper {
|
|||||||
|
|
||||||
private CollarHelper() {}
|
private CollarHelper() {}
|
||||||
|
|
||||||
|
// Thread-local flag to suppress collar removal alerts during programmatic unequip
|
||||||
|
private static final ThreadLocal<Boolean> SUPPRESS_REMOVAL_ALERT =
|
||||||
|
ThreadLocal.withInitial(() -> false);
|
||||||
|
|
||||||
// ===== DETECTION =====
|
// ===== DETECTION =====
|
||||||
|
|
||||||
// True if the stack is any kind of collar (V2 ownership component or V1 ItemCollar)
|
// True if the stack is a collar (V2 data-driven item with OwnershipComponent)
|
||||||
public static boolean isCollar(ItemStack stack) {
|
public static boolean isCollar(ItemStack stack) {
|
||||||
if (stack.isEmpty()) return false;
|
if (stack.isEmpty()) return false;
|
||||||
if (DataDrivenBondageItem.getComponent(stack, ComponentType.OWNERSHIP, OwnershipComponent.class) != null) {
|
return DataDrivenBondageItem.getComponent(stack, ComponentType.OWNERSHIP, OwnershipComponent.class) != null;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return stack.getItem() instanceof ItemCollar;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== OWNERSHIP (NBT: "owners") =====
|
// ===== OWNERSHIP (NBT: "owners") =====
|
||||||
@@ -267,13 +270,10 @@ public final class CollarHelper {
|
|||||||
|
|
||||||
// ===== SHOCK =====
|
// ===== SHOCK =====
|
||||||
|
|
||||||
// True if the collar can shock (V2 ShockComponent or V1 ItemShockCollar)
|
// True if the collar can shock (V2 ShockComponent)
|
||||||
public static boolean canShock(ItemStack stack) {
|
public static boolean canShock(ItemStack stack) {
|
||||||
if (stack.isEmpty()) return false;
|
if (stack.isEmpty()) return false;
|
||||||
if (DataDrivenBondageItem.getComponent(stack, ComponentType.SHOCK, ShockComponent.class) != null) {
|
return DataDrivenBondageItem.getComponent(stack, ComponentType.SHOCK, ShockComponent.class) != null;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return stack.getItem() instanceof ItemShockCollar;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isPublicShock(ItemStack stack) {
|
public static boolean isPublicShock(ItemStack stack) {
|
||||||
@@ -285,27 +285,21 @@ public final class CollarHelper {
|
|||||||
stack.getOrCreateTag().putBoolean("public_mode", publicMode);
|
stack.getOrCreateTag().putBoolean("public_mode", publicMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
// V2: from ShockComponent auto interval, V1: from ItemShockCollarAuto field, else 0
|
// V2: from ShockComponent auto interval, else 0
|
||||||
public static int getShockInterval(ItemStack stack) {
|
public static int getShockInterval(ItemStack stack) {
|
||||||
ShockComponent comp = DataDrivenBondageItem.getComponent(
|
ShockComponent comp = DataDrivenBondageItem.getComponent(
|
||||||
stack, ComponentType.SHOCK, ShockComponent.class
|
stack, ComponentType.SHOCK, ShockComponent.class
|
||||||
);
|
);
|
||||||
if (comp != null) return comp.getAutoInterval();
|
if (comp != null) return comp.getAutoInterval();
|
||||||
if (stack.getItem() instanceof ItemShockCollarAuto auto) {
|
|
||||||
return auto.getInterval();
|
|
||||||
}
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== GPS =====
|
// ===== GPS =====
|
||||||
|
|
||||||
// True if the collar has GPS capabilities
|
// True if the collar has GPS capabilities (V2 GpsComponent)
|
||||||
public static boolean hasGPS(ItemStack stack) {
|
public static boolean hasGPS(ItemStack stack) {
|
||||||
if (stack.isEmpty()) return false;
|
if (stack.isEmpty()) return false;
|
||||||
if (DataDrivenBondageItem.getComponent(stack, ComponentType.GPS, GpsComponent.class) != null) {
|
return DataDrivenBondageItem.getComponent(stack, ComponentType.GPS, GpsComponent.class) != null;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return stack.getItem() instanceof ItemGpsCollar;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean hasPublicTracking(ItemStack stack) {
|
public static boolean hasPublicTracking(ItemStack stack) {
|
||||||
@@ -330,13 +324,10 @@ public final class CollarHelper {
|
|||||||
|
|
||||||
// ===== CHOKE =====
|
// ===== CHOKE =====
|
||||||
|
|
||||||
// True if the collar is a choke collar
|
// True if the collar is a choke collar (V2 ChokingComponent)
|
||||||
public static boolean isChokeCollar(ItemStack stack) {
|
public static boolean isChokeCollar(ItemStack stack) {
|
||||||
if (stack.isEmpty()) return false;
|
if (stack.isEmpty()) return false;
|
||||||
if (DataDrivenBondageItem.getComponent(stack, ComponentType.CHOKING, ChokingComponent.class) != null) {
|
return DataDrivenBondageItem.getComponent(stack, ComponentType.CHOKING, ChokingComponent.class) != null;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return stack.getItem() instanceof ItemChokeCollar;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isChoking(ItemStack stack) {
|
public static boolean isChoking(ItemStack stack) {
|
||||||
@@ -361,11 +352,69 @@ public final class CollarHelper {
|
|||||||
|
|
||||||
// Executes the action with collar removal alerts suppressed
|
// Executes the action with collar removal alerts suppressed
|
||||||
public static void runWithSuppressedAlert(Runnable action) {
|
public static void runWithSuppressedAlert(Runnable action) {
|
||||||
ItemCollar.runWithSuppressedAlert(action);
|
SUPPRESS_REMOVAL_ALERT.set(true);
|
||||||
|
try {
|
||||||
|
action.run();
|
||||||
|
} finally {
|
||||||
|
SUPPRESS_REMOVAL_ALERT.set(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// True if removal alerts are currently suppressed (ThreadLocal state)
|
// True if removal alerts are currently suppressed (ThreadLocal state)
|
||||||
public static boolean isRemovalAlertSuppressed() {
|
public static boolean isRemovalAlertSuppressed() {
|
||||||
return ItemCollar.isRemovalAlertSuppressed();
|
return SUPPRESS_REMOVAL_ALERT.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== COLLAR REMOVAL =====
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alert nearby kidnappers when a collar is forcibly removed.
|
||||||
|
* Handles CollarRegistry unregistration and kidnapper alerting.
|
||||||
|
*
|
||||||
|
* @param wearer The entity whose collar was removed
|
||||||
|
* @param forced Whether the removal was forced (struggle, lockpick, etc.)
|
||||||
|
*/
|
||||||
|
public static void onCollarRemoved(LivingEntity wearer, boolean forced) {
|
||||||
|
if (wearer == null || wearer.level().isClientSide()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!forced) {
|
||||||
|
return; // Only alert on forced removals
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(wearer.level() instanceof ServerLevel serverLevel)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TiedUpMod.LOGGER.info(
|
||||||
|
"[CollarHelper] {} collar was forcibly removed - alerting kidnappers",
|
||||||
|
wearer.getName().getString()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Unregister from CollarRegistry
|
||||||
|
CollarRegistry registry = CollarRegistry.get(serverLevel);
|
||||||
|
if (registry != null) {
|
||||||
|
registry.unregisterWearer(wearer.getUUID());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find and alert nearby kidnappers
|
||||||
|
AABB searchBox = wearer.getBoundingBox().inflate(50, 20, 50);
|
||||||
|
List<EntityKidnapper> kidnappers = serverLevel.getEntitiesOfClass(
|
||||||
|
EntityKidnapper.class, searchBox
|
||||||
|
);
|
||||||
|
|
||||||
|
for (EntityKidnapper kidnapper : kidnappers) {
|
||||||
|
if (!kidnapper.hasCaptives() && !kidnapper.isTiedUp()) {
|
||||||
|
kidnapper.setAlertTarget(wearer);
|
||||||
|
kidnapper.setCurrentState(KidnapperState.ALERT);
|
||||||
|
kidnapper.broadcastAlert(wearer);
|
||||||
|
|
||||||
|
TiedUpMod.LOGGER.debug(
|
||||||
|
"[CollarHelper] Alerted kidnapper {} about collar removal",
|
||||||
|
kidnapper.getNpcName()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.tiedup.remake.v2.bondage;
|
package com.tiedup.remake.v2.bondage;
|
||||||
|
|
||||||
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.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;
|
||||||
@@ -26,10 +25,6 @@ public final class PoseTypeHelper {
|
|||||||
return PoseType.STANDARD;
|
return PoseType.STANDARD;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// V1 fallback
|
|
||||||
if (stack.getItem() instanceof ItemBind bind) {
|
|
||||||
return bind.getPoseType();
|
|
||||||
}
|
|
||||||
return PoseType.STANDARD;
|
return PoseType.STANDARD;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package com.tiedup.remake.v2.bondage.component;
|
|||||||
|
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import com.tiedup.remake.core.TiedUpMod;
|
import com.tiedup.remake.core.TiedUpMod;
|
||||||
import com.tiedup.remake.items.base.ItemCollar;
|
|
||||||
import com.tiedup.remake.state.CollarRegistry;
|
import com.tiedup.remake.state.CollarRegistry;
|
||||||
import com.tiedup.remake.v2.bondage.CollarHelper;
|
import com.tiedup.remake.v2.bondage.CollarHelper;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@@ -71,7 +70,7 @@ public class OwnershipComponent implements IItemComponent {
|
|||||||
// onCollarRemoved handles both the alert AND the unregister call internally,
|
// onCollarRemoved handles both the alert AND the unregister call internally,
|
||||||
// so we do NOT call registry.unregisterWearer() separately to avoid double unregister.
|
// so we do NOT call registry.unregisterWearer() separately to avoid double unregister.
|
||||||
if (!CollarHelper.isRemovalAlertSuppressed()) {
|
if (!CollarHelper.isRemovalAlertSuppressed()) {
|
||||||
ItemCollar.onCollarRemoved(entity, true);
|
CollarHelper.onCollarRemoved(entity, true);
|
||||||
} else {
|
} else {
|
||||||
// Suppressed alert path: still need to unregister, just skip the alert
|
// Suppressed alert path: still need to unregister, just skip the alert
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package com.tiedup.remake.v2.furniture;
|
|||||||
|
|
||||||
import com.tiedup.remake.core.TiedUpMod;
|
import com.tiedup.remake.core.TiedUpMod;
|
||||||
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.state.PlayerBindState;
|
import com.tiedup.remake.state.PlayerBindState;
|
||||||
import com.tiedup.remake.state.PlayerCaptorManager;
|
import com.tiedup.remake.state.PlayerCaptorManager;
|
||||||
@@ -713,11 +713,10 @@ public class EntityFurniture
|
|||||||
);
|
);
|
||||||
if (
|
if (
|
||||||
collarStack.isEmpty() ||
|
collarStack.isEmpty() ||
|
||||||
!(collarStack.getItem() instanceof
|
!CollarHelper.isCollar(collarStack)
|
||||||
ItemCollar collar)
|
|
||||||
) continue;
|
) continue;
|
||||||
if (
|
if (
|
||||||
!collar.isOwner(collarStack, serverPlayer) &&
|
!CollarHelper.isOwner(collarStack, serverPlayer) &&
|
||||||
!serverPlayer.hasPermissions(2)
|
!serverPlayer.hasPermissions(2)
|
||||||
) continue;
|
) continue;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user