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.
870 lines
27 KiB
Markdown
870 lines
27 KiB
Markdown
# 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."
|
|
```
|