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:
NotEvil
2026-04-15 02:52:27 +02:00
parent 75cf1358f9
commit ab1b3f66bb
12 changed files with 2184 additions and 17 deletions

View File

@@ -0,0 +1,869 @@
# D-01 Phase 1: Data-Driven Item Component System
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Create a reusable component system so data-driven bondage items can declare gameplay behaviors (lockable, shock, GPS, gagging, etc.) in their JSON definition instead of requiring per-item Java classes.
**Architecture:** Each component is a self-contained behavior module implementing `IItemComponent`. Components are declared in item JSON (`"components": {"shock": {...}}`), parsed by an extended `DataDrivenItemParser`, stored on `DataDrivenItemDefinition`, and ticked/queried via `DataDrivenBondageItem` delegation. The existing `ILockable` and `IHasResistance` interfaces are preserved as shared contracts — components implement them.
**Tech Stack:** Java 17, Forge 1.20.1, existing V2 data-driven infrastructure (`DataDrivenItemRegistry`, `DataDrivenItemParser`, `DataDrivenItemDefinition`, `DataDrivenBondageItem`)
**Scope:** This plan builds ONLY the component infrastructure + 3 core components (lockable, resistance, gagging). The remaining 5 components (shock, GPS, blinding, choking, adjustable) follow the same pattern and will be added in subsequent tasks or a follow-up plan.
---
## File Structure
### New files
| File | Responsibility |
|------|---------------|
| `v2/bondage/component/IItemComponent.java` | Component interface: lifecycle hooks, tick, query |
| `v2/bondage/component/ComponentType.java` | Enum of all component types with factory methods |
| `v2/bondage/component/ComponentHolder.java` | Container: holds instantiated components for an item stack |
| `v2/bondage/component/LockableComponent.java` | Lock/unlock, padlock, key matching, jam, lock resistance |
| `v2/bondage/component/ResistanceComponent.java` | Struggle resistance with configurable base value |
| `v2/bondage/component/GaggingComponent.java` | Muffled speech, comprehension %, range limit |
### Modified files
| File | Changes |
|------|---------|
| `v2/bondage/datadriven/DataDrivenItemDefinition.java` | Add `Map<ComponentType, JsonObject> componentConfigs` field |
| `v2/bondage/datadriven/DataDrivenItemParser.java` | Parse `"components"` JSON block |
| `v2/bondage/datadriven/DataDrivenBondageItem.java` | Delegate lifecycle hooks to components, expose `getComponent()` |
| `v2/bondage/datadriven/DataDrivenItemRegistry.java` | Instantiate `ComponentHolder` per definition |
---
## Tasks
### Task 1: IItemComponent interface
**Files:**
- Create: `src/main/java/com/tiedup/remake/v2/bondage/component/IItemComponent.java`
- [ ] **Step 1: Create the component interface**
```java
package com.tiedup.remake.v2.bondage.component;
import com.google.gson.JsonObject;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.ItemStack;
/**
* A reusable behavior module for data-driven bondage items.
* Components are declared in JSON and instantiated per item definition.
*
* <p>Lifecycle: parse config once (from JSON), then tick/query per equipped entity.</p>
*/
public interface IItemComponent {
/**
* Called when the item is equipped on an entity.
* @param stack The equipped item stack
* @param entity The entity wearing the item
*/
default void onEquipped(ItemStack stack, LivingEntity entity) {}
/**
* Called when the item is unequipped from an entity.
* @param stack The unequipped item stack
* @param entity The entity that was wearing the item
*/
default void onUnequipped(ItemStack stack, LivingEntity entity) {}
/**
* Called every tick while the item is equipped.
* @param stack The equipped item stack
* @param entity The entity wearing the item
*/
default void onWornTick(ItemStack stack, LivingEntity entity) {}
/**
* Whether this component prevents the item from being unequipped.
* @param stack The equipped item stack
* @param entity The entity wearing the item
* @return true if unequip should be blocked
*/
default boolean blocksUnequip(ItemStack stack, LivingEntity entity) {
return false;
}
}
```
- [ ] **Step 2: Verify file compiles**
Run: `cd /home/user/Documents/Projet/Open-TiedUp! && make build 2>&1 | tail -5`
Expected: BUILD SUCCESSFUL
- [ ] **Step 3: Commit**
```bash
git add src/main/java/com/tiedup/remake/v2/bondage/component/IItemComponent.java
git commit -m "feat(D-01): add IItemComponent interface for data-driven item behaviors"
```
---
### Task 2: ComponentType enum
**Files:**
- Create: `src/main/java/com/tiedup/remake/v2/bondage/component/ComponentType.java`
- [ ] **Step 1: Create the component type registry**
```java
package com.tiedup.remake.v2.bondage.component;
import com.google.gson.JsonObject;
import javax.annotation.Nullable;
import java.util.function.Function;
/**
* All known component types. Each type knows how to instantiate itself from JSON config.
*/
public enum ComponentType {
LOCKABLE("lockable", LockableComponent::fromJson),
RESISTANCE("resistance", ResistanceComponent::fromJson),
GAGGING("gagging", GaggingComponent::fromJson);
// Future: SHOCK, GPS, BLINDING, CHOKING, ADJUSTABLE
private final String jsonKey;
private final Function<JsonObject, IItemComponent> factory;
ComponentType(String jsonKey, Function<JsonObject, IItemComponent> factory) {
this.jsonKey = jsonKey;
this.factory = factory;
}
public String getJsonKey() {
return jsonKey;
}
public IItemComponent create(JsonObject config) {
return factory.apply(config);
}
/**
* Look up a ComponentType by its JSON key. Returns null if unknown.
*/
@Nullable
public static ComponentType fromKey(String key) {
for (ComponentType type : values()) {
if (type.jsonKey.equals(key)) {
return type;
}
}
return null;
}
}
```
Note: This file will not compile yet because `LockableComponent`, `ResistanceComponent`, and `GaggingComponent` don't exist. We'll create stub classes first, then implement them.
- [ ] **Step 2: Create stub classes so the enum compiles**
Create three empty stubs (they will be fully implemented in Tasks 4-6):
`src/main/java/com/tiedup/remake/v2/bondage/component/LockableComponent.java`:
```java
package com.tiedup.remake.v2.bondage.component;
import com.google.gson.JsonObject;
public class LockableComponent implements IItemComponent {
private LockableComponent() {}
public static IItemComponent fromJson(JsonObject config) {
return new LockableComponent();
}
}
```
`src/main/java/com/tiedup/remake/v2/bondage/component/ResistanceComponent.java`:
```java
package com.tiedup.remake.v2.bondage.component;
import com.google.gson.JsonObject;
public class ResistanceComponent implements IItemComponent {
private ResistanceComponent() {}
public static IItemComponent fromJson(JsonObject config) {
return new ResistanceComponent();
}
}
```
`src/main/java/com/tiedup/remake/v2/bondage/component/GaggingComponent.java`:
```java
package com.tiedup.remake.v2.bondage.component;
import com.google.gson.JsonObject;
public class GaggingComponent implements IItemComponent {
private GaggingComponent() {}
public static IItemComponent fromJson(JsonObject config) {
return new GaggingComponent();
}
}
```
- [ ] **Step 3: Verify all files compile**
Run: `cd /home/user/Documents/Projet/Open-TiedUp! && make build 2>&1 | tail -5`
Expected: BUILD SUCCESSFUL
- [ ] **Step 4: Commit**
```bash
git add src/main/java/com/tiedup/remake/v2/bondage/component/
git commit -m "feat(D-01): add ComponentType enum with stub component classes"
```
---
### Task 3: ComponentHolder container
**Files:**
- Create: `src/main/java/com/tiedup/remake/v2/bondage/component/ComponentHolder.java`
- [ ] **Step 1: Create the component container**
```java
package com.tiedup.remake.v2.bondage.component;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.ItemStack;
import javax.annotation.Nullable;
import java.util.Collections;
import java.util.EnumMap;
import java.util.Map;
/**
* Holds instantiated components for an item definition.
* Immutable after construction. One per DataDrivenItemDefinition.
*/
public final class ComponentHolder {
public static final ComponentHolder EMPTY = new ComponentHolder(Map.of());
private final Map<ComponentType, IItemComponent> components;
public ComponentHolder(Map<ComponentType, IItemComponent> components) {
this.components = components.isEmpty()
? Map.of()
: Collections.unmodifiableMap(new EnumMap<>(components));
}
/**
* Get a component by type, or null if not present.
*/
@Nullable
public IItemComponent get(ComponentType type) {
return components.get(type);
}
/**
* Get a component by type, cast to the expected class.
* Returns null if not present or wrong type.
*/
@Nullable
@SuppressWarnings("unchecked")
public <T extends IItemComponent> T get(ComponentType type, Class<T> clazz) {
IItemComponent component = components.get(type);
if (clazz.isInstance(component)) {
return (T) component;
}
return null;
}
/**
* Check if a component type is present.
*/
public boolean has(ComponentType type) {
return components.containsKey(type);
}
/**
* Fire onEquipped for all components.
*/
public void onEquipped(ItemStack stack, LivingEntity entity) {
for (IItemComponent component : components.values()) {
component.onEquipped(stack, entity);
}
}
/**
* Fire onUnequipped for all components.
*/
public void onUnequipped(ItemStack stack, LivingEntity entity) {
for (IItemComponent component : components.values()) {
component.onUnequipped(stack, entity);
}
}
/**
* Fire onWornTick for all components.
*/
public void onWornTick(ItemStack stack, LivingEntity entity) {
for (IItemComponent component : components.values()) {
component.onWornTick(stack, entity);
}
}
/**
* Check if any component blocks unequip.
*/
public boolean blocksUnequip(ItemStack stack, LivingEntity entity) {
for (IItemComponent component : components.values()) {
if (component.blocksUnequip(stack, entity)) {
return true;
}
}
return false;
}
/**
* Whether this holder has any components.
*/
public boolean isEmpty() {
return components.isEmpty();
}
}
```
- [ ] **Step 2: Verify compilation**
Run: `cd /home/user/Documents/Projet/Open-TiedUp! && make build 2>&1 | tail -5`
Expected: BUILD SUCCESSFUL
- [ ] **Step 3: Commit**
```bash
git add src/main/java/com/tiedup/remake/v2/bondage/component/ComponentHolder.java
git commit -m "feat(D-01): add ComponentHolder container for item components"
```
---
### Task 4: Integrate components into DataDrivenItemDefinition + Parser
**Files:**
- Modify: `src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenItemDefinition.java`
- Modify: `src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenItemParser.java`
- [ ] **Step 1: Add componentConfigs field to DataDrivenItemDefinition**
Read the current record definition, then add a new field. The record should get a new parameter:
```java
/** Raw component configs from JSON, keyed by ComponentType. */
Map<ComponentType, JsonObject> componentConfigs
```
Add after the last existing field in the record. Also add a convenience method:
```java
/**
* Whether this definition declares a specific component.
*/
public boolean hasComponent(ComponentType type) {
return componentConfigs != null && componentConfigs.containsKey(type);
}
```
- [ ] **Step 2: Parse "components" block in DataDrivenItemParser**
Read `DataDrivenItemParser.java` and add parsing for the `"components"` JSON field. After parsing all existing fields, add:
```java
// Parse components
Map<ComponentType, JsonObject> componentConfigs = new EnumMap<>(ComponentType.class);
if (json.has("components")) {
JsonObject componentsObj = json.getAsJsonObject("components");
for (Map.Entry<String, com.google.gson.JsonElement> entry : componentsObj.entrySet()) {
ComponentType type = ComponentType.fromKey(entry.getKey());
if (type != null) {
JsonObject config = entry.getValue().isJsonObject()
? entry.getValue().getAsJsonObject()
: new JsonObject();
componentConfigs.put(type, config);
} else {
LOGGER.warn("[DataDrivenItemParser] Unknown component type '{}' in item '{}'",
entry.getKey(), id);
}
}
}
```
Pass `componentConfigs` to the `DataDrivenItemDefinition` record constructor.
- [ ] **Step 3: Update all existing call sites that construct DataDrivenItemDefinition**
Search for all `new DataDrivenItemDefinition(` calls and add `Map.of()` for the new parameter (for the network sync deserialization path, etc.).
- [ ] **Step 4: Verify compilation**
Run: `cd /home/user/Documents/Projet/Open-TiedUp! && make build 2>&1 | tail -5`
Expected: BUILD SUCCESSFUL
- [ ] **Step 5: Commit**
```bash
git add src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenItemDefinition.java
git add src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenItemParser.java
git commit -m "feat(D-01): parse component configs from item JSON definitions"
```
---
### Task 5: Instantiate ComponentHolder in DataDrivenItemRegistry
**Files:**
- Modify: `src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenItemRegistry.java`
- [ ] **Step 1: Add ComponentHolder cache**
Read `DataDrivenItemRegistry.java`. Add a parallel cache that maps `ResourceLocation` to `ComponentHolder`. When definitions are loaded/reloaded, instantiate components from their `componentConfigs`.
Add field:
```java
private static volatile Map<ResourceLocation, ComponentHolder> COMPONENT_HOLDERS = Map.of();
```
In the reload/register method, after storing definitions, build component holders:
```java
Map<ResourceLocation, ComponentHolder> holders = new HashMap<>();
for (Map.Entry<ResourceLocation, DataDrivenItemDefinition> entry : newDefinitions.entrySet()) {
DataDrivenItemDefinition def = entry.getValue();
Map<ComponentType, IItemComponent> components = new EnumMap<>(ComponentType.class);
for (Map.Entry<ComponentType, JsonObject> compEntry : def.componentConfigs().entrySet()) {
components.put(compEntry.getKey(), compEntry.getKey().create(compEntry.getValue()));
}
holders.put(entry.getKey(), new ComponentHolder(components));
}
COMPONENT_HOLDERS = Collections.unmodifiableMap(holders);
```
Add accessor:
```java
@Nullable
public static ComponentHolder getComponents(ItemStack stack) {
DataDrivenItemDefinition def = get(stack);
if (def == null) return null;
return COMPONENT_HOLDERS.get(def.id());
}
@Nullable
public static ComponentHolder getComponents(ResourceLocation id) {
return COMPONENT_HOLDERS.get(id);
}
```
- [ ] **Step 2: Verify compilation**
Run: `cd /home/user/Documents/Projet/Open-TiedUp! && make build 2>&1 | tail -5`
Expected: BUILD SUCCESSFUL
- [ ] **Step 3: Commit**
```bash
git add src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenItemRegistry.java
git commit -m "feat(D-01): instantiate ComponentHolder per item definition on reload"
```
---
### Task 6: Delegate DataDrivenBondageItem lifecycle to components
**Files:**
- Modify: `src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenBondageItem.java`
- [ ] **Step 1: Add component delegation in lifecycle hooks**
Read `DataDrivenBondageItem.java`. In `onEquipped()` and `onUnequipped()`, delegate to components:
```java
@Override
public void onEquipped(ItemStack stack, LivingEntity entity) {
ComponentHolder holder = DataDrivenItemRegistry.getComponents(stack);
if (holder != null) {
holder.onEquipped(stack, entity);
}
}
@Override
public void onUnequipped(ItemStack stack, LivingEntity entity) {
ComponentHolder holder = DataDrivenItemRegistry.getComponents(stack);
if (holder != null) {
holder.onUnequipped(stack, entity);
}
}
```
Override `canUnequip` to check component blocks:
```java
@Override
public boolean canUnequip(ItemStack stack, LivingEntity entity) {
ComponentHolder holder = DataDrivenItemRegistry.getComponents(stack);
if (holder != null && holder.blocksUnequip(stack, entity)) {
return false;
}
return super.canUnequip(stack, entity);
}
```
Add a public static helper for external code to query components:
```java
/**
* Get a specific component from a data-driven item stack.
* @return The component, or null if the item is not data-driven or lacks this component.
*/
@Nullable
public static <T extends IItemComponent> T getComponent(ItemStack stack, ComponentType type, Class<T> clazz) {
ComponentHolder holder = DataDrivenItemRegistry.getComponents(stack);
if (holder == null) return null;
return holder.get(type, clazz);
}
```
- [ ] **Step 2: Verify compilation**
Run: `cd /home/user/Documents/Projet/Open-TiedUp! && make build 2>&1 | tail -5`
Expected: BUILD SUCCESSFUL
- [ ] **Step 3: Commit**
```bash
git add src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenBondageItem.java
git commit -m "feat(D-01): delegate DataDrivenBondageItem lifecycle to components"
```
---
### Task 7: Implement LockableComponent
**Files:**
- Modify: `src/main/java/com/tiedup/remake/v2/bondage/component/LockableComponent.java`
- [ ] **Step 1: Implement full lockable logic**
Replace the stub with the full implementation. Extract lock behavior from `ILockable` (which remains as a shared interface). The component reads its config from JSON and delegates to `ILockable` default methods on the item stack:
```java
package com.tiedup.remake.v2.bondage.component;
import com.google.gson.JsonObject;
import com.tiedup.remake.items.base.ILockable;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.ItemStack;
/**
* Component: lockable behavior for data-driven items.
* Delegates to ILockable interface methods on the item.
*
* JSON config:
* <pre>{"lockable": true}</pre>
* or
* <pre>{"lockable": {"lock_resistance": 300}}</pre>
*/
public class LockableComponent implements IItemComponent {
private final int lockResistance;
private LockableComponent(int lockResistance) {
this.lockResistance = lockResistance;
}
public static IItemComponent fromJson(JsonObject config) {
int resistance = 250; // default from SettingsAccessor
if (config.has("lock_resistance")) {
resistance = config.get("lock_resistance").getAsInt();
}
return new LockableComponent(resistance);
}
public int getLockResistance() {
return lockResistance;
}
@Override
public boolean blocksUnequip(ItemStack stack, LivingEntity entity) {
// If item implements ILockable, check if locked
if (stack.getItem() instanceof ILockable lockable) {
return lockable.isLocked(stack);
}
return false;
}
}
```
- [ ] **Step 2: Verify compilation**
Run: `cd /home/user/Documents/Projet/Open-TiedUp! && make build 2>&1 | tail -5`
Expected: BUILD SUCCESSFUL
- [ ] **Step 3: Commit**
```bash
git add src/main/java/com/tiedup/remake/v2/bondage/component/LockableComponent.java
git commit -m "feat(D-01): implement LockableComponent with configurable lock resistance"
```
---
### Task 8: Implement ResistanceComponent
**Files:**
- Modify: `src/main/java/com/tiedup/remake/v2/bondage/component/ResistanceComponent.java`
- [ ] **Step 1: Implement resistance logic**
```java
package com.tiedup.remake.v2.bondage.component;
import com.google.gson.JsonObject;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.ItemStack;
/**
* Component: struggle resistance for data-driven items.
* Replaces IHasResistance for data-driven items.
*
* JSON config:
* <pre>{"resistance": {"base": 150}}</pre>
*/
public class ResistanceComponent implements IItemComponent {
private final int baseResistance;
private ResistanceComponent(int baseResistance) {
this.baseResistance = baseResistance;
}
public static IItemComponent fromJson(JsonObject config) {
int base = 100; // default
if (config.has("base")) {
base = config.get("base").getAsInt();
}
return new ResistanceComponent(base);
}
/**
* Get the base resistance for this item.
* Used by DataDrivenBondageItem.getBaseResistance() to replace the MAX-scan workaround.
*/
public int getBaseResistance() {
return baseResistance;
}
}
```
- [ ] **Step 2: Update DataDrivenBondageItem.getBaseResistance() to use ResistanceComponent**
In `DataDrivenBondageItem.java`, update `getBaseResistance()`:
```java
@Override
public int getBaseResistance(LivingEntity entity) {
// Try stack-aware component lookup first (fixes I-03: no more MAX scan)
// Note: This method is called WITHOUT a stack parameter by IHasResistance.
// We still need the MAX scan as fallback until IHasResistance gets a stack-aware method.
if (entity != null) {
IV2BondageEquipment equip = V2EquipmentHelper.getEquipment(entity);
if (equip != null) {
int maxDifficulty = -1;
for (Map.Entry<BodyRegionV2, ItemStack> entry : equip.getAllEquipped().entrySet()) {
ItemStack stack = entry.getValue();
if (stack.getItem() == this) {
// Try component first
ResistanceComponent comp = DataDrivenBondageItem.getComponent(
stack, ComponentType.RESISTANCE, ResistanceComponent.class);
if (comp != null) {
maxDifficulty = Math.max(maxDifficulty, comp.getBaseResistance());
continue;
}
// Fallback to escape_difficulty from definition
DataDrivenItemDefinition def = DataDrivenItemRegistry.get(stack);
if (def != null) {
maxDifficulty = Math.max(maxDifficulty, def.escapeDifficulty());
}
}
}
if (maxDifficulty >= 0) return maxDifficulty;
}
}
return 100;
}
```
- [ ] **Step 3: Verify compilation**
Run: `cd /home/user/Documents/Projet/Open-TiedUp! && make build 2>&1 | tail -5`
Expected: BUILD SUCCESSFUL
- [ ] **Step 4: Commit**
```bash
git add src/main/java/com/tiedup/remake/v2/bondage/component/ResistanceComponent.java
git add src/main/java/com/tiedup/remake/v2/bondage/datadriven/DataDrivenBondageItem.java
git commit -m "feat(D-01): implement ResistanceComponent, fixes I-03 MAX scan for stack-aware items"
```
---
### Task 9: Implement GaggingComponent
**Files:**
- Modify: `src/main/java/com/tiedup/remake/v2/bondage/component/GaggingComponent.java`
- [ ] **Step 1: Implement gagging logic**
```java
package com.tiedup.remake.v2.bondage.component;
import com.google.gson.JsonObject;
/**
* Component: gagging behavior for data-driven items.
* Replaces IHasGaggingEffect for data-driven items.
*
* JSON config:
* <pre>{"gagging": {"comprehension": 0.2, "range": 10.0}}</pre>
*/
public class GaggingComponent implements IItemComponent {
private final double comprehension;
private final double range;
private GaggingComponent(double comprehension, double range) {
this.comprehension = comprehension;
this.range = range;
}
public static IItemComponent fromJson(JsonObject config) {
double comprehension = 0.2; // default: 20% understandable
double range = 10.0; // default: 10 blocks
if (config.has("comprehension")) {
comprehension = config.get("comprehension").getAsDouble();
}
if (config.has("range")) {
range = config.get("range").getAsDouble();
}
return new GaggingComponent(comprehension, range);
}
/**
* How much of the gagged speech is comprehensible (0.0 = nothing, 1.0 = full).
*/
public double getComprehension() {
return comprehension;
}
/**
* Maximum range in blocks where muffled speech can be heard.
*/
public double getRange() {
return range;
}
}
```
- [ ] **Step 2: Verify compilation**
Run: `cd /home/user/Documents/Projet/Open-TiedUp! && make build 2>&1 | tail -5`
Expected: BUILD SUCCESSFUL
- [ ] **Step 3: Commit**
```bash
git add src/main/java/com/tiedup/remake/v2/bondage/component/GaggingComponent.java
git commit -m "feat(D-01): implement GaggingComponent with comprehension and range"
```
---
### Task 10: Create a test item JSON using components
**Files:**
- Create: `src/main/resources/data/tiedup/tiedup_items/test_gag.json`
- [ ] **Step 1: Create a JSON definition that uses the new component system**
```json
{
"type": "tiedup:bondage_item",
"display_name": "Test Ball Gag",
"model": "tiedup:models/gltf/v2/handcuffs/cuffs_prototype.glb",
"regions": ["MOUTH"],
"animation_bones": {
"idle": []
},
"pose_priority": 10,
"escape_difficulty": 3,
"lockable": true,
"components": {
"lockable": {
"lock_resistance": 200
},
"resistance": {
"base": 80
},
"gagging": {
"comprehension": 0.15,
"range": 8.0
}
}
}
```
- [ ] **Step 2: Verify the mod loads without errors**
Run: `cd /home/user/Documents/Projet/Open-TiedUp! && make build 2>&1 | tail -5`
Expected: BUILD SUCCESSFUL
Check that the JSON parses by searching for component-related log output in the run logs (manual verification — start the game client with `make run`, check for errors in log).
- [ ] **Step 3: Commit**
```bash
git add src/main/resources/data/tiedup/tiedup_items/test_gag.json
git commit -m "feat(D-01): add test_gag.json demonstrating component system"
```
---
### Task 11: Verify and clean up
- [ ] **Step 1: Full build verification**
Run: `cd /home/user/Documents/Projet/Open-TiedUp! && make rebuild 2>&1 | tail -10`
Expected: BUILD SUCCESSFUL with zero errors
- [ ] **Step 2: Verify no regressions in existing items**
Existing data-driven items (in `data/tiedup/tiedup_items/`) should continue working without the `"components"` field. The parser should handle missing components gracefully (empty map).
- [ ] **Step 3: Reindex MCP**
Run the MCP reindex to update the symbol table with new classes.
- [ ] **Step 4: Final commit**
```bash
git add -A
git commit -m "feat(D-01): Phase 1 complete - data-driven item component system
Adds IItemComponent interface, ComponentType enum, ComponentHolder container,
and 3 core components (LockableComponent, ResistanceComponent, GaggingComponent).
Components are declared in item JSON 'components' field, parsed by DataDrivenItemParser,
instantiated by DataDrivenItemRegistry, and delegated by DataDrivenBondageItem.
Existing items without components continue to work unchanged."
```