fix(D-01/D): checkup cleanup — 5 issues resolved
1. LockableComponent: remove duplicate "Lockable" tooltip line (ILockable.appendLockTooltip already handles lock status display) 2. ILockable/IHasResistance Javadoc: update @link refs from deleted V1 classes to V2 AbstractV2BondageItem/DataDrivenBondageItem 3. SettingsAccessor Javadoc: remove stale BindVariant @link references 4. DataDrivenBondageItem: update NECK block comment (remove branch ref) 5. Delete empty bondage3d/gags/ directory
This commit is contained in:
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
|
||||
Reference in New Issue
Block a user