feature/d01-component-system #5
@@ -0,0 +1,67 @@
|
||||
package com.tiedup.remake.v2.bondage.component;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* Component: positional adjustment for data-driven items.
|
||||
* Allows Y offset adjustment via GUI slider.
|
||||
*
|
||||
* JSON config:
|
||||
* {@code "adjustable": {"default": 0.0, "min": -4.0, "max": 4.0, "step": 0.25}}
|
||||
*/
|
||||
public class AdjustableComponent implements IItemComponent {
|
||||
|
||||
private final float defaultValue;
|
||||
private final float min;
|
||||
private final float max;
|
||||
private final float step;
|
||||
|
||||
private AdjustableComponent(float defaultValue, float min, float max, float step) {
|
||||
this.defaultValue = defaultValue;
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
this.step = step;
|
||||
}
|
||||
|
||||
public static IItemComponent fromJson(JsonObject config) {
|
||||
float defaultVal = 0.0f;
|
||||
float min = -4.0f;
|
||||
float max = 4.0f;
|
||||
float step = 0.25f;
|
||||
if (config != null) {
|
||||
if (config.has("default")) defaultVal = config.get("default").getAsFloat();
|
||||
if (config.has("min")) min = config.get("min").getAsFloat();
|
||||
if (config.has("max")) max = config.get("max").getAsFloat();
|
||||
if (config.has("step")) step = config.get("step").getAsFloat();
|
||||
}
|
||||
// Ensure min <= max
|
||||
if (min > max) {
|
||||
float tmp = min;
|
||||
min = max;
|
||||
max = tmp;
|
||||
}
|
||||
step = Math.max(0.01f, step);
|
||||
defaultVal = Math.max(min, Math.min(max, defaultVal));
|
||||
return new AdjustableComponent(defaultVal, min, max, step);
|
||||
}
|
||||
|
||||
/** Default Y offset value. */
|
||||
public float getDefaultValue() {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/** Minimum Y offset. */
|
||||
public float getMin() {
|
||||
return min;
|
||||
}
|
||||
|
||||
/** Maximum Y offset. */
|
||||
public float getMax() {
|
||||
return max;
|
||||
}
|
||||
|
||||
/** Step increment for the adjustment slider. */
|
||||
public float getStep() {
|
||||
return step;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.tiedup.remake.v2.bondage.component;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Component: blinding effect for data-driven items.
|
||||
* Replaces IHasBlindingEffect marker interface.
|
||||
*
|
||||
* JSON config:
|
||||
* {@code "blinding": {}} or {@code "blinding": {"overlay": "tiedup:textures/overlay/custom.png"}}
|
||||
*/
|
||||
public class BlindingComponent implements IItemComponent {
|
||||
|
||||
private final String overlay; // nullable — null means default overlay
|
||||
|
||||
private BlindingComponent(String overlay) {
|
||||
this.overlay = overlay;
|
||||
}
|
||||
|
||||
public static IItemComponent fromJson(JsonObject config) {
|
||||
String overlay = null;
|
||||
if (config != null && config.has("overlay")) {
|
||||
overlay = config.get("overlay").getAsString();
|
||||
}
|
||||
return new BlindingComponent(overlay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom overlay texture path, or null for the mod's default blindfold overlay.
|
||||
*/
|
||||
@Nullable
|
||||
public String getOverlay() {
|
||||
return overlay;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.tiedup.remake.v2.bondage.component;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* Component: choking effect for data-driven items.
|
||||
* When active, drains air, applies darkness/slowness, deals damage.
|
||||
*
|
||||
* JSON config:
|
||||
* {@code "choking": {"air_drain_per_tick": 8, "non_lethal_for_master": true}}
|
||||
*/
|
||||
public class ChokingComponent implements IItemComponent {
|
||||
|
||||
private final int airDrainPerTick;
|
||||
private final boolean nonLethalForMaster;
|
||||
|
||||
private ChokingComponent(int airDrainPerTick, boolean nonLethalForMaster) {
|
||||
this.airDrainPerTick = airDrainPerTick;
|
||||
this.nonLethalForMaster = nonLethalForMaster;
|
||||
}
|
||||
|
||||
public static IItemComponent fromJson(JsonObject config) {
|
||||
int drain = 8;
|
||||
boolean nonLethal = true;
|
||||
if (config != null) {
|
||||
if (config.has("air_drain_per_tick")) {
|
||||
drain = config.get("air_drain_per_tick").getAsInt();
|
||||
}
|
||||
if (config.has("non_lethal_for_master")) {
|
||||
nonLethal = config.get("non_lethal_for_master").getAsBoolean();
|
||||
}
|
||||
}
|
||||
drain = Math.max(1, drain);
|
||||
return new ChokingComponent(drain, nonLethal);
|
||||
}
|
||||
|
||||
/** Air drained per tick (net after vanilla +4 restoration). */
|
||||
public int getAirDrainPerTick() {
|
||||
return airDrainPerTick;
|
||||
}
|
||||
|
||||
/** Whether the choke is non-lethal when used by a master on their pet. */
|
||||
public boolean isNonLethalForMaster() {
|
||||
return nonLethalForMaster;
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,12 @@ import org.jetbrains.annotations.Nullable;
|
||||
public enum ComponentType {
|
||||
LOCKABLE("lockable", LockableComponent::fromJson),
|
||||
RESISTANCE("resistance", ResistanceComponent::fromJson),
|
||||
GAGGING("gagging", GaggingComponent::fromJson);
|
||||
GAGGING("gagging", GaggingComponent::fromJson),
|
||||
BLINDING("blinding", BlindingComponent::fromJson),
|
||||
SHOCK("shock", ShockComponent::fromJson),
|
||||
GPS("gps", GpsComponent::fromJson),
|
||||
CHOKING("choking", ChokingComponent::fromJson),
|
||||
ADJUSTABLE("adjustable", AdjustableComponent::fromJson);
|
||||
|
||||
private final String jsonKey;
|
||||
private final Function<JsonObject, IItemComponent> factory;
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.tiedup.remake.v2.bondage.component;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* Component: GPS tracking and safe zone for data-driven items.
|
||||
*
|
||||
* JSON config:
|
||||
* {@code "gps": {"safe_zone_radius": 50, "public_tracking": false}}
|
||||
*/
|
||||
public class GpsComponent implements IItemComponent {
|
||||
|
||||
private final int safeZoneRadius;
|
||||
private final boolean publicTracking;
|
||||
|
||||
private GpsComponent(int safeZoneRadius, boolean publicTracking) {
|
||||
this.safeZoneRadius = safeZoneRadius;
|
||||
this.publicTracking = publicTracking;
|
||||
}
|
||||
|
||||
public static IItemComponent fromJson(JsonObject config) {
|
||||
int radius = 50;
|
||||
boolean publicTracking = false;
|
||||
if (config != null) {
|
||||
if (config.has("safe_zone_radius")) {
|
||||
radius = config.get("safe_zone_radius").getAsInt();
|
||||
}
|
||||
if (config.has("public_tracking")) {
|
||||
publicTracking = config.get("public_tracking").getAsBoolean();
|
||||
}
|
||||
}
|
||||
radius = Math.max(0, radius);
|
||||
return new GpsComponent(radius, publicTracking);
|
||||
}
|
||||
|
||||
/** Safe zone radius in blocks. 0 = no safe zone (tracking only). */
|
||||
public int getSafeZoneRadius() {
|
||||
return safeZoneRadius;
|
||||
}
|
||||
|
||||
/** Whether any player can track (not just the owner). */
|
||||
public boolean isPublicTracking() {
|
||||
return publicTracking;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.tiedup.remake.v2.bondage.component;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* Component: shock collar behavior for data-driven items.
|
||||
*
|
||||
* JSON config:
|
||||
* {@code "shock": {"damage": 2.0, "auto_interval": 0}}
|
||||
* auto_interval: ticks between auto-shocks (0 = no auto-shock, manual only)
|
||||
*/
|
||||
public class ShockComponent implements IItemComponent {
|
||||
|
||||
private final float damage;
|
||||
private final int autoInterval; // 0 = manual only
|
||||
|
||||
private ShockComponent(float damage, int autoInterval) {
|
||||
this.damage = damage;
|
||||
this.autoInterval = autoInterval;
|
||||
}
|
||||
|
||||
public static IItemComponent fromJson(JsonObject config) {
|
||||
float damage = 2.0f;
|
||||
int autoInterval = 0;
|
||||
if (config != null) {
|
||||
if (config.has("damage")) {
|
||||
damage = config.get("damage").getAsFloat();
|
||||
}
|
||||
if (config.has("auto_interval")) {
|
||||
autoInterval = config.get("auto_interval").getAsInt();
|
||||
}
|
||||
}
|
||||
damage = Math.max(0.0f, damage);
|
||||
autoInterval = Math.max(0, autoInterval);
|
||||
return new ShockComponent(damage, autoInterval);
|
||||
}
|
||||
|
||||
/** Damage dealt per shock. */
|
||||
public float getDamage() {
|
||||
return damage;
|
||||
}
|
||||
|
||||
/** Ticks between auto-shocks. 0 = manual shock only (via controller). */
|
||||
public int getAutoInterval() {
|
||||
return autoInterval;
|
||||
}
|
||||
|
||||
/** Whether this item has auto-shock capability. */
|
||||
public boolean hasAutoShock() {
|
||||
return autoInterval > 0;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user