Compare commits
37 Commits
feature/it
...
feature/d0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
530b86a9a7 | ||
|
|
258223bf68 | ||
|
|
679d7033f9 | ||
| 8bfd97ba57 | |||
|
|
df56ebb6bc | ||
|
|
b97bdf367e | ||
|
|
eb7f06bfc8 | ||
|
|
5c4e4c2352 | ||
|
|
eee4825aba | ||
|
|
b359c6be35 | ||
|
|
19cc69985d | ||
|
|
737a4fd59b | ||
|
|
b79225d684 | ||
|
|
751bad418d | ||
|
|
b81d3eed95 | ||
| fa4c332a10 | |||
|
|
7bd840705a | ||
|
|
90bc890b95 | ||
|
|
185ac63a44 | ||
|
|
dcc8493e5e | ||
|
|
bfcc20d242 | ||
|
|
3a81bb6e12 | ||
|
|
bb589d44f8 | ||
|
|
456335e0dd | ||
|
|
bb209bcd8e | ||
|
|
1327e3bfc3 | ||
|
|
dbacef66d5 | ||
|
|
231522c68e | ||
|
|
84f4c3a53f | ||
|
|
caeb4469b1 | ||
|
|
3a1f401ccf | ||
|
|
a781dad597 | ||
|
|
750be66d80 | ||
|
|
1b70041c36 | ||
|
|
b8a0d839f5 | ||
|
|
edfc3c6506 | ||
| 3fe3e16e0a |
@@ -16,7 +16,7 @@
|
|||||||
7. [Animations](#animations) — item poses, fallback chain, variants, context animations
|
7. [Animations](#animations) — item poses, fallback chain, variants, context animations
|
||||||
8. [Animation Templates](#animation-templates)
|
8. [Animation Templates](#animation-templates)
|
||||||
9. [Exporting from Blender](#exporting-from-blender)
|
9. [Exporting from Blender](#exporting-from-blender)
|
||||||
10. [The JSON Definition](#the-json-definition)
|
10. [The JSON Definition](#the-json-definition) — field reference, components, pose priority, movement styles
|
||||||
11. [Packaging as a Resource Pack](#packaging-as-a-resource-pack)
|
11. [Packaging as a Resource Pack](#packaging-as-a-resource-pack)
|
||||||
12. [Common Mistakes](#common-mistakes)
|
12. [Common Mistakes](#common-mistakes)
|
||||||
13. [Examples](#examples)
|
13. [Examples](#examples)
|
||||||
@@ -764,6 +764,7 @@ The `movement_style` changes how the player physically moves — slower speed, d
|
|||||||
| `movement_modifier` | object | No | Override speed/jump for the movement style (requires `movement_style`) |
|
| `movement_modifier` | object | No | Override speed/jump for the movement style (requires `movement_style`) |
|
||||||
| `creator` | string | No | Author/creator name, shown in the item tooltip |
|
| `creator` | string | No | Author/creator name, shown in the item tooltip |
|
||||||
| `animation_bones` | object | Yes | Per-animation bone whitelist (see below) |
|
| `animation_bones` | object | Yes | Per-animation bone whitelist (see below) |
|
||||||
|
| `components` | object | No | Gameplay behavior components (see [Components](#components-gameplay-behaviors) below) |
|
||||||
|
|
||||||
### animation_bones (required)
|
### animation_bones (required)
|
||||||
|
|
||||||
@@ -785,6 +786,87 @@ At runtime, the effective bones for a given animation clip are computed as the *
|
|||||||
|
|
||||||
This field is **required**. Items without `animation_bones` will be rejected by the parser.
|
This field is **required**. Items without `animation_bones` will be rejected by the parser.
|
||||||
|
|
||||||
|
### Components (Gameplay Behaviors)
|
||||||
|
|
||||||
|
Components add gameplay behaviors to your item without requiring Java code. Each component is a self-contained module you declare in the `"components"` block of your JSON definition.
|
||||||
|
|
||||||
|
**Format:** A JSON object where each key is a component name and each value is the component's configuration (an object, or `true` for defaults).
|
||||||
|
|
||||||
|
```json
|
||||||
|
"components": {
|
||||||
|
"lockable": { "lock_resistance": 200 },
|
||||||
|
"resistance": { "base": 150 },
|
||||||
|
"gagging": { "comprehension": 0.2, "range": 10.0 }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Items without the `"components"` field work normally — components are entirely optional.
|
||||||
|
|
||||||
|
#### Available Components
|
||||||
|
|
||||||
|
| Component | Description | Config Fields |
|
||||||
|
|-----------|-------------|---------------|
|
||||||
|
| `lockable` | Item can be locked with a padlock. Locked items cannot be unequipped. | `lock_resistance` (int, default: 250) — resistance added by the lock for struggle mechanics |
|
||||||
|
| `resistance` | Struggle resistance. Higher = harder to escape. | `base` (int, default: 100) — base resistance value |
|
||||||
|
| `gagging` | Muffles the wearer's speech. | `comprehension` (0.0–1.0, default: 0.2) — how much speech is understandable. `range` (float, default: 10.0) — max hearing distance in blocks |
|
||||||
|
| `blinding` | Applies a blindfold overlay to the wearer's screen. | `overlay` (string, optional) — custom overlay texture path. Omit for default |
|
||||||
|
| `shock` | Item can shock the wearer (manually or automatically). | `damage` (float, default: 2.0) — damage per shock. `auto_interval` (int, default: 0) — ticks between auto-shocks (0 = manual only) |
|
||||||
|
| `gps` | GPS tracking and safe zone enforcement. | `safe_zone_radius` (int, default: 50) — safe zone in blocks (0 = tracking only). `public_tracking` (bool, default: false) — anyone can track, not just owner |
|
||||||
|
| `choking` | Drains air, applies darkness/slowness, deals damage when activated. | `air_drain_per_tick` (int, default: 8) — air drained per tick. `non_lethal_for_master` (bool, default: true) — won't kill if worn by a master's pet |
|
||||||
|
| `adjustable` | Allows Y-offset adjustment via GUI slider. | `default` (float, default: 0.0), `min` (float, default: -4.0), `max` (float, default: 4.0), `step` (float, default: 0.25) — all in pixels (1px = 1/16 block) |
|
||||||
|
|
||||||
|
#### Example: Shock Collar with GPS
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "GPS Shock Collar",
|
||||||
|
"model": "mycreator:models/gltf/gps_shock_collar.glb",
|
||||||
|
"regions": ["NECK"],
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": []
|
||||||
|
},
|
||||||
|
"pose_priority": 10,
|
||||||
|
"escape_difficulty": 5,
|
||||||
|
"components": {
|
||||||
|
"lockable": { "lock_resistance": 300 },
|
||||||
|
"resistance": { "base": 150 },
|
||||||
|
"shock": { "damage": 3.0, "auto_interval": 200 },
|
||||||
|
"gps": { "safe_zone_radius": 50 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This collar can be locked (300 resistance to break the lock), has 150 base struggle resistance, shocks every 200 ticks (10 seconds) automatically, and enforces a 50-block safe zone.
|
||||||
|
|
||||||
|
#### Example: Adjustable Blindfold
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Leather Blindfold",
|
||||||
|
"model": "mycreator:models/gltf/blindfold.glb",
|
||||||
|
"regions": ["EYES"],
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": ["head"]
|
||||||
|
},
|
||||||
|
"pose_priority": 10,
|
||||||
|
"escape_difficulty": 2,
|
||||||
|
"components": {
|
||||||
|
"blinding": {},
|
||||||
|
"resistance": { "base": 80 },
|
||||||
|
"adjustable": { "min": -2.0, "max": 2.0, "step": 0.5 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Component Tips
|
||||||
|
|
||||||
|
- **You can combine any components.** A gag with `gagging` + `lockable` + `resistance` + `adjustable` is perfectly valid.
|
||||||
|
- **Omit components you don't need.** A decorative collar with no shock/GPS just omits those components entirely.
|
||||||
|
- **Default values are sensible.** `"lockable": {}` gives you standard lock behavior with default resistance. You only need to specify fields you want to customize.
|
||||||
|
- **Components don't affect rendering.** They are purely gameplay — your GLB model and animations are independent of which components you use.
|
||||||
|
|
||||||
### Pose Priority
|
### Pose Priority
|
||||||
|
|
||||||
When multiple items affect the same bones, the highest `pose_priority` wins.
|
When multiple items affect the same bones, the highest `pose_priority` wins.
|
||||||
|
|||||||
@@ -40,7 +40,11 @@ public class AnvilEventHandler {
|
|||||||
|
|
||||||
// Check if item can have a padlock attached (tape, slime, vine, web cannot)
|
// Check if item can have a padlock attached (tape, slime, vine, web cannot)
|
||||||
if (!lockable.canAttachPadlock()) {
|
if (!lockable.canAttachPadlock()) {
|
||||||
return; // Item type cannot have padlock
|
return; // Item type cannot have padlock (V1)
|
||||||
|
}
|
||||||
|
// V2 data-driven items: check definition's can_attach_padlock field
|
||||||
|
if (!com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem.canAttachPadlockTo(left)) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Item must not already have a padlock attached
|
// Item must not already have a padlock attached
|
||||||
|
|||||||
@@ -14,8 +14,10 @@ import com.tiedup.remake.tasks.TyingTask;
|
|||||||
import com.tiedup.remake.tasks.V2TyingPlayerTask;
|
import com.tiedup.remake.tasks.V2TyingPlayerTask;
|
||||||
import com.tiedup.remake.util.KidnappedHelper;
|
import com.tiedup.remake.util.KidnappedHelper;
|
||||||
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.IV2BondageItem;
|
import com.tiedup.remake.v2.bondage.IV2BondageItem;
|
||||||
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
|
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
|
||||||
|
import com.tiedup.remake.v2.bondage.V2EquipResult;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
import net.minecraft.server.level.ServerPlayer;
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
@@ -222,69 +224,116 @@ public class PacketSelfBondage {
|
|||||||
IV2BondageItem v2Item,
|
IV2BondageItem v2Item,
|
||||||
IBondageState state
|
IBondageState state
|
||||||
) {
|
) {
|
||||||
|
java.util.Set<BodyRegionV2> regions = v2Item.getOccupiedRegions(stack);
|
||||||
|
|
||||||
|
// Cannot self-collar
|
||||||
|
if (regions.contains(BodyRegionV2.NECK)) {
|
||||||
|
TiedUpMod.LOGGER.debug("[SelfBondage] {} tried to self-collar — blocked", player.getName().getString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ARMS: tying task with progress bar
|
||||||
|
if (regions.contains(BodyRegionV2.ARMS)) {
|
||||||
|
handleV2SelfBind(player, stack, v2Item, state);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accessories (MOUTH, EYES, EARS, HANDS): instant equip
|
||||||
|
handleV2SelfAccessory(player, stack, v2Item, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void handleV2SelfBind(
|
||||||
|
ServerPlayer player,
|
||||||
|
ItemStack stack,
|
||||||
|
IV2BondageItem v2Item,
|
||||||
|
IBondageState state
|
||||||
|
) {
|
||||||
|
// Can't self-tie if already tied (check both V1 state and V2 region to prevent dual-bind)
|
||||||
|
if (state.isTiedUp() || V2EquipmentHelper.isRegionOccupied(player, BodyRegionV2.ARMS)) {
|
||||||
|
TiedUpMod.LOGGER.debug("[SelfBondage] {} tried V2 self-tie but already tied", player.getName().getString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if all target regions are already occupied or blocked
|
// Check if all target regions are already occupied or blocked
|
||||||
boolean allBlocked = true;
|
boolean allBlocked = true;
|
||||||
for (BodyRegionV2 region : v2Item.getOccupiedRegions(stack)) {
|
for (BodyRegionV2 region : v2Item.getOccupiedRegions(stack)) {
|
||||||
if (
|
if (!V2EquipmentHelper.isRegionOccupied(player, region)
|
||||||
!V2EquipmentHelper.isRegionOccupied(player, region) &&
|
&& !V2EquipmentHelper.isRegionBlocked(player, region)) {
|
||||||
!V2EquipmentHelper.isRegionBlocked(player, region)
|
|
||||||
) {
|
|
||||||
allBlocked = false;
|
allBlocked = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (allBlocked) {
|
if (allBlocked) {
|
||||||
TiedUpMod.LOGGER.debug(
|
TiedUpMod.LOGGER.debug("[SelfBondage] {} tried V2 self-equip but all regions occupied", player.getName().getString());
|
||||||
"[SelfBondage] {} tried V2 self-equip but all regions occupied",
|
|
||||||
player.getName().getString()
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerBindState playerState = PlayerBindState.getInstance(player);
|
PlayerBindState playerState = PlayerBindState.getInstance(player);
|
||||||
if (playerState == null) return;
|
if (playerState == null) return;
|
||||||
|
|
||||||
int tyingSeconds = SettingsAccessor.getTyingPlayerTime(
|
int tyingSeconds = SettingsAccessor.getTyingPlayerTime(player.level().getGameRules());
|
||||||
player.level().getGameRules()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Create V2 tying task (uses V2EquipmentHelper on completion, NOT putBindOn)
|
|
||||||
V2TyingPlayerTask newTask = new V2TyingPlayerTask(
|
V2TyingPlayerTask newTask = new V2TyingPlayerTask(
|
||||||
stack.copy(), // copy for display/matching
|
stack.copy(), stack, state, player, tyingSeconds, player.level(), player
|
||||||
stack, // live reference for consumption
|
|
||||||
state,
|
|
||||||
player, // target is self
|
|
||||||
tyingSeconds,
|
|
||||||
player.level(),
|
|
||||||
player // kidnapper is also self
|
|
||||||
);
|
);
|
||||||
|
|
||||||
TyingTask currentTask = playerState.getCurrentTyingTask();
|
TyingTask currentTask = playerState.getCurrentTyingTask();
|
||||||
|
if (currentTask == null
|
||||||
if (
|
|| !currentTask.isSameTarget(player)
|
||||||
currentTask == null ||
|
|| currentTask.isOutdated()
|
||||||
!currentTask.isSameTarget(player) ||
|
|| !ItemStack.matches(currentTask.getBind(), stack)) {
|
||||||
currentTask.isOutdated() ||
|
|
||||||
!ItemStack.matches(currentTask.getBind(), stack)
|
|
||||||
) {
|
|
||||||
// Start new task
|
|
||||||
playerState.setCurrentTyingTask(newTask);
|
playerState.setCurrentTyingTask(newTask);
|
||||||
newTask.start();
|
newTask.start();
|
||||||
|
|
||||||
TiedUpMod.LOGGER.debug(
|
|
||||||
"[SelfBondage] {} started V2 self-tying ({} seconds)",
|
|
||||||
player.getName().getString(),
|
|
||||||
tyingSeconds
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
// Continue existing task — just mark active
|
|
||||||
currentTask.update();
|
currentTask.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we started a new task, mark it active too
|
|
||||||
if (playerState.getCurrentTyingTask() == newTask) {
|
if (playerState.getCurrentTyingTask() == newTask) {
|
||||||
newTask.update();
|
newTask.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear completed task to prevent blocking future tying interactions
|
||||||
|
TyingTask activeTask = playerState.getCurrentTyingTask();
|
||||||
|
if (activeTask != null && activeTask.isStopped()) {
|
||||||
|
playerState.setCurrentTyingTask(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void handleV2SelfAccessory(
|
||||||
|
ServerPlayer player,
|
||||||
|
ItemStack stack,
|
||||||
|
IV2BondageItem v2Item,
|
||||||
|
IBondageState state
|
||||||
|
) {
|
||||||
|
// Can't equip accessories if arms are fully bound
|
||||||
|
ItemStack currentBind = V2EquipmentHelper.getInRegion(player, BodyRegionV2.ARMS);
|
||||||
|
if (!currentBind.isEmpty() && BindModeHelper.hasArmsBound(currentBind)) {
|
||||||
|
TiedUpMod.LOGGER.debug("[SelfBondage] {} can't self-accessory — arms bound", player.getName().getString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if region is occupied — try to swap
|
||||||
|
for (BodyRegionV2 region : v2Item.getOccupiedRegions(stack)) {
|
||||||
|
if (V2EquipmentHelper.isRegionOccupied(player, region)) {
|
||||||
|
ItemStack existing = V2EquipmentHelper.getInRegion(player, region);
|
||||||
|
// Can't swap if locked
|
||||||
|
if (existing.getItem() instanceof ILockable lockable && lockable.isLocked(existing)) {
|
||||||
|
TiedUpMod.LOGGER.debug("[SelfBondage] {} can't swap — current is locked", player.getName().getString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equip via V2EquipmentHelper (handles conflict resolution, displaced items)
|
||||||
|
V2EquipResult result = V2EquipmentHelper.equipItem(player, stack.copy());
|
||||||
|
if (result.isSuccess()) {
|
||||||
|
for (ItemStack displaced : result.displaced()) {
|
||||||
|
player.spawnAtLocation(displaced);
|
||||||
|
}
|
||||||
|
stack.shrink(1);
|
||||||
|
SyncManager.syncInventory(player);
|
||||||
|
TiedUpMod.LOGGER.info("[SelfBondage] {} self-equipped V2 accessory", player.getName().getString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -320,10 +320,12 @@ public class PlayerEquipment {
|
|||||||
player,
|
player,
|
||||||
BodyRegionV2.ARMS
|
BodyRegionV2.ARMS
|
||||||
);
|
);
|
||||||
if (
|
if (stack.isEmpty()) return 0;
|
||||||
stack.isEmpty() || !(stack.getItem() instanceof ItemBind bind)
|
// V1 and V2 both implement IHasResistance
|
||||||
) return 0;
|
if (stack.getItem() instanceof com.tiedup.remake.items.base.IHasResistance resistance) {
|
||||||
return bind.getCurrentResistance(stack, player);
|
return resistance.getCurrentResistance(stack, player);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -334,10 +336,11 @@ public class PlayerEquipment {
|
|||||||
player,
|
player,
|
||||||
BodyRegionV2.ARMS
|
BodyRegionV2.ARMS
|
||||||
);
|
);
|
||||||
if (
|
if (stack.isEmpty()) return;
|
||||||
stack.isEmpty() || !(stack.getItem() instanceof ItemBind bind)
|
// V1 and V2 both implement IHasResistance
|
||||||
) return;
|
if (stack.getItem() instanceof com.tiedup.remake.items.base.IHasResistance resistanceItem) {
|
||||||
bind.setCurrentResistance(stack, resistance);
|
resistanceItem.setCurrentResistance(stack, resistance);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -348,10 +351,12 @@ public class PlayerEquipment {
|
|||||||
player,
|
player,
|
||||||
BodyRegionV2.NECK
|
BodyRegionV2.NECK
|
||||||
);
|
);
|
||||||
if (
|
if (stack.isEmpty()) return 0;
|
||||||
stack.isEmpty() || !(stack.getItem() instanceof ItemCollar collar)
|
// V1 and V2 both implement IHasResistance
|
||||||
) return 0;
|
if (stack.getItem() instanceof com.tiedup.remake.items.base.IHasResistance resistance) {
|
||||||
return collar.getCurrentResistance(stack, player);
|
return resistance.getCurrentResistance(stack, player);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -362,10 +367,11 @@ public class PlayerEquipment {
|
|||||||
player,
|
player,
|
||||||
BodyRegionV2.NECK
|
BodyRegionV2.NECK
|
||||||
);
|
);
|
||||||
if (
|
if (stack.isEmpty()) return;
|
||||||
stack.isEmpty() || !(stack.getItem() instanceof ItemCollar collar)
|
// V1 and V2 both implement IHasResistance
|
||||||
) return;
|
if (stack.getItem() instanceof com.tiedup.remake.items.base.IHasResistance resistanceItem) {
|
||||||
collar.setCurrentResistance(stack, resistance);
|
resistanceItem.setCurrentResistance(stack, resistance);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== Helper Methods ==========
|
// ========== Helper Methods ==========
|
||||||
|
|||||||
@@ -4,12 +4,18 @@ import com.tiedup.remake.core.SettingsAccessor;
|
|||||||
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.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.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;
|
||||||
|
import com.tiedup.remake.v2.bondage.BindModeHelper;
|
||||||
|
import com.tiedup.remake.v2.bondage.CollarHelper;
|
||||||
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
|
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
|
||||||
|
import com.tiedup.remake.v2.bondage.component.ComponentType;
|
||||||
|
import com.tiedup.remake.v2.bondage.component.ResistanceComponent;
|
||||||
|
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
|
||||||
import net.minecraft.world.entity.item.ItemEntity;
|
import net.minecraft.world.entity.item.ItemEntity;
|
||||||
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;
|
||||||
@@ -75,16 +81,17 @@ public class StruggleBinds extends StruggleState {
|
|||||||
player,
|
player,
|
||||||
BodyRegionV2.ARMS
|
BodyRegionV2.ARMS
|
||||||
);
|
);
|
||||||
if (
|
if (bindStack.isEmpty() || !BindModeHelper.isBindItem(bindStack)) {
|
||||||
bindStack.isEmpty() ||
|
|
||||||
!(bindStack.getItem() instanceof ItemBind bind)
|
|
||||||
) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The locked check has been moved to struggle() where decrease is reduced
|
// The locked check has been moved to struggle() where decrease is reduced
|
||||||
|
|
||||||
return bind.canBeStruggledOut(bindStack);
|
// Check canBeStruggledOut — works for both V1 and V2 via IHasResistance
|
||||||
|
if (bindStack.getItem() instanceof IHasResistance resistance) {
|
||||||
|
return resistance.canBeStruggledOut(bindStack);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -103,14 +110,13 @@ public class StruggleBinds extends StruggleState {
|
|||||||
player,
|
player,
|
||||||
BodyRegionV2.ARMS
|
BodyRegionV2.ARMS
|
||||||
);
|
);
|
||||||
if (
|
if (bindStack.isEmpty()) return false;
|
||||||
bindStack.isEmpty() ||
|
|
||||||
!(bindStack.getItem() instanceof ItemBind bind)
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return bind.isLocked(bindStack);
|
// Works for both V1 (ItemBind) and V2 (DataDrivenBondageItem) via ILockable
|
||||||
|
if (bindStack.getItem() instanceof ILockable lockable) {
|
||||||
|
return lockable.isLocked(bindStack);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -148,14 +154,18 @@ public class StruggleBinds extends StruggleState {
|
|||||||
BodyRegionV2.NECK
|
BodyRegionV2.NECK
|
||||||
);
|
);
|
||||||
|
|
||||||
if (
|
if (!collar.isEmpty() && CollarHelper.canShock(collar)) {
|
||||||
!collar.isEmpty() &&
|
// V1 shock collar
|
||||||
collar.getItem() instanceof
|
if (collar.getItem() instanceof com.tiedup.remake.items.ItemShockCollar shockCollar) {
|
||||||
com.tiedup.remake.items.ItemShockCollar shockCollar
|
return shockCollar.notifyStruggle(player, collar);
|
||||||
) {
|
}
|
||||||
return shockCollar.notifyStruggle(player, collar);
|
// V2 shock collar — notify via IHasResistance if available
|
||||||
|
if (collar.getItem() instanceof IHasResistance resistance) {
|
||||||
|
resistance.notifyStruggle(player);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return true; // No collar, proceed normally
|
return true; // No shock collar, proceed normally
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -317,18 +327,23 @@ public class StruggleBinds extends StruggleState {
|
|||||||
target,
|
target,
|
||||||
BodyRegionV2.ARMS
|
BodyRegionV2.ARMS
|
||||||
);
|
);
|
||||||
if (
|
if (bindStack.isEmpty() || !BindModeHelper.isBindItem(bindStack)) {
|
||||||
bindStack.isEmpty() ||
|
|
||||||
!(bindStack.getItem() instanceof ItemBind bind)
|
|
||||||
) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get base resistance from config (BUG-003 fix: was using ModGameRules which
|
// Get base resistance: V2 reads from ResistanceComponent directly,
|
||||||
// only knew 4 types and returned hardcoded 100 for the other 10)
|
// V1 reads from SettingsAccessor via item name (BUG-003 fix)
|
||||||
int baseResistance = SettingsAccessor.getBindResistance(
|
int baseResistance;
|
||||||
bind.getItemName()
|
ResistanceComponent comp = DataDrivenBondageItem.getComponent(
|
||||||
|
bindStack, ComponentType.RESISTANCE, ResistanceComponent.class
|
||||||
);
|
);
|
||||||
|
if (comp != null) {
|
||||||
|
baseResistance = comp.getBaseResistance();
|
||||||
|
} else if (bindStack.getItem() instanceof ItemBind bind) {
|
||||||
|
baseResistance = SettingsAccessor.getBindResistance(bind.getItemName());
|
||||||
|
} else {
|
||||||
|
baseResistance = 100;
|
||||||
|
}
|
||||||
|
|
||||||
// Set current resistance to base (full restore)
|
// Set current resistance to base (full restore)
|
||||||
setResistanceState(state, baseResistance);
|
setResistanceState(state, baseResistance);
|
||||||
|
|||||||
@@ -2,10 +2,16 @@ package com.tiedup.remake.state.struggle;
|
|||||||
|
|
||||||
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.base.IHasResistance;
|
||||||
|
import com.tiedup.remake.items.base.ILockable;
|
||||||
import com.tiedup.remake.items.base.ItemCollar;
|
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.capability.V2EquipmentHelper;
|
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
|
||||||
|
import com.tiedup.remake.v2.bondage.component.ComponentType;
|
||||||
|
import com.tiedup.remake.v2.bondage.component.ResistanceComponent;
|
||||||
|
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
|
||||||
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;
|
||||||
|
|
||||||
@@ -44,19 +50,15 @@ public class StruggleCollar extends StruggleState {
|
|||||||
@Override
|
@Override
|
||||||
protected int getResistanceState(PlayerBindState state) {
|
protected int getResistanceState(PlayerBindState state) {
|
||||||
Player player = state.getPlayer();
|
Player player = state.getPlayer();
|
||||||
ItemStack collar = V2EquipmentHelper.getInRegion(
|
if (player == null) return 0;
|
||||||
player,
|
ItemStack collar = V2EquipmentHelper.getInRegion(player, BodyRegionV2.NECK);
|
||||||
BodyRegionV2.NECK
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
if (collar.isEmpty() || !CollarHelper.isCollar(collar)) return 0;
|
||||||
collar.isEmpty() ||
|
|
||||||
!(collar.getItem() instanceof ItemCollar collarItem)
|
if (collar.getItem() instanceof IHasResistance resistance) {
|
||||||
) {
|
return resistance.getCurrentResistance(collar, player);
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
return 0;
|
||||||
return collarItem.getCurrentResistance(collar, player);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -68,19 +70,14 @@ public class StruggleCollar extends StruggleState {
|
|||||||
@Override
|
@Override
|
||||||
protected void setResistanceState(PlayerBindState state, int resistance) {
|
protected void setResistanceState(PlayerBindState state, int resistance) {
|
||||||
Player player = state.getPlayer();
|
Player player = state.getPlayer();
|
||||||
ItemStack collar = V2EquipmentHelper.getInRegion(
|
if (player == null) return;
|
||||||
player,
|
ItemStack collar = V2EquipmentHelper.getInRegion(player, BodyRegionV2.NECK);
|
||||||
BodyRegionV2.NECK
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
if (collar.isEmpty() || !CollarHelper.isCollar(collar)) return;
|
||||||
collar.isEmpty() ||
|
|
||||||
!(collar.getItem() instanceof ItemCollar collarItem)
|
if (collar.getItem() instanceof IHasResistance resistanceItem) {
|
||||||
) {
|
resistanceItem.setCurrentResistance(collar, resistance);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
collarItem.setCurrentResistance(collar, resistance);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -104,31 +101,29 @@ public class StruggleCollar extends StruggleState {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemStack collar = V2EquipmentHelper.getInRegion(
|
ItemStack collar = V2EquipmentHelper.getInRegion(player, BodyRegionV2.NECK);
|
||||||
player,
|
|
||||||
BodyRegionV2.NECK
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
if (collar.isEmpty() || !CollarHelper.isCollar(collar)) {
|
||||||
collar.isEmpty() ||
|
|
||||||
!(collar.getItem() instanceof ItemCollar collarItem)
|
|
||||||
) {
|
|
||||||
TiedUpMod.LOGGER.debug("[StruggleCollar] No collar equipped");
|
TiedUpMod.LOGGER.debug("[StruggleCollar] No collar equipped");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if locked
|
// Check if locked (works for V1 and V2 via ILockable)
|
||||||
if (!collarItem.isLocked(collar)) {
|
if (collar.getItem() instanceof ILockable lockable) {
|
||||||
TiedUpMod.LOGGER.debug("[StruggleCollar] Collar is not locked");
|
if (!lockable.isLocked(collar)) {
|
||||||
|
TiedUpMod.LOGGER.debug("[StruggleCollar] Collar is not locked");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if struggle is enabled
|
// Check if struggle is enabled (works for V1 and V2 via IHasResistance)
|
||||||
if (!collarItem.canBeStruggledOut(collar)) {
|
if (collar.getItem() instanceof IHasResistance resistance) {
|
||||||
TiedUpMod.LOGGER.debug(
|
if (!resistance.canBeStruggledOut(collar)) {
|
||||||
"[StruggleCollar] Collar struggle is disabled"
|
TiedUpMod.LOGGER.debug("[StruggleCollar] Collar struggle is disabled");
|
||||||
);
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -141,17 +136,17 @@ public class StruggleCollar extends StruggleState {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean onAttempt(PlayerBindState state) {
|
protected boolean onAttempt(PlayerBindState state) {
|
||||||
Player player = state.getPlayer();
|
Player player = state.getPlayer();
|
||||||
ItemStack collar = V2EquipmentHelper.getInRegion(
|
ItemStack collar = V2EquipmentHelper.getInRegion(player, BodyRegionV2.NECK);
|
||||||
player,
|
|
||||||
BodyRegionV2.NECK
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
if (!collar.isEmpty() && CollarHelper.canShock(collar)) {
|
||||||
!collar.isEmpty() &&
|
// V1 shock collar
|
||||||
collar.getItem() instanceof
|
if (collar.getItem() instanceof com.tiedup.remake.items.ItemShockCollar shockCollar) {
|
||||||
com.tiedup.remake.items.ItemShockCollar shockCollar
|
return shockCollar.notifyStruggle(player, collar);
|
||||||
) {
|
}
|
||||||
return shockCollar.notifyStruggle(player, collar);
|
// V2 shock collar — notify via IHasResistance
|
||||||
|
if (collar.getItem() instanceof IHasResistance resistance) {
|
||||||
|
resistance.notifyStruggle(player);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -167,31 +162,19 @@ public class StruggleCollar extends StruggleState {
|
|||||||
@Override
|
@Override
|
||||||
protected void successAction(PlayerBindState state) {
|
protected void successAction(PlayerBindState state) {
|
||||||
Player player = state.getPlayer();
|
Player player = state.getPlayer();
|
||||||
ItemStack collar = V2EquipmentHelper.getInRegion(
|
ItemStack collar = V2EquipmentHelper.getInRegion(player, BodyRegionV2.NECK);
|
||||||
player,
|
|
||||||
BodyRegionV2.NECK
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
if (collar.isEmpty() || !CollarHelper.isCollar(collar)) {
|
||||||
collar.isEmpty() ||
|
TiedUpMod.LOGGER.warn("[StruggleCollar] successAction called but no collar equipped");
|
||||||
!(collar.getItem() instanceof ItemCollar collarItem)
|
|
||||||
) {
|
|
||||||
TiedUpMod.LOGGER.warn(
|
|
||||||
"[StruggleCollar] successAction called but no collar equipped"
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unlock the collar
|
// Unlock the collar (works for V1 and V2 via ILockable)
|
||||||
collarItem.setLocked(collar, false);
|
if (collar.getItem() instanceof ILockable lockable) {
|
||||||
|
lockable.setLocked(collar, false);
|
||||||
|
}
|
||||||
|
|
||||||
TiedUpMod.LOGGER.info(
|
TiedUpMod.LOGGER.info("[StruggleCollar] {} unlocked their collar!", player.getName().getString());
|
||||||
"[StruggleCollar] {} unlocked their collar!",
|
|
||||||
player.getName().getString()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Note: Collar is NOT removed, just unlocked
|
|
||||||
// Player can now manually remove it
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -230,30 +213,36 @@ public class StruggleCollar extends StruggleState {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemStack collar = V2EquipmentHelper.getInRegion(
|
ItemStack collar = V2EquipmentHelper.getInRegion(target, BodyRegionV2.NECK);
|
||||||
target,
|
|
||||||
BodyRegionV2.NECK
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
if (collar.isEmpty() || !CollarHelper.isCollar(collar)) {
|
||||||
collar.isEmpty() ||
|
|
||||||
!(collar.getItem() instanceof ItemCollar collarItem)
|
|
||||||
) {
|
|
||||||
TiedUpMod.LOGGER.debug("[StruggleCollar] No collar to tighten");
|
TiedUpMod.LOGGER.debug("[StruggleCollar] No collar to tighten");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if collar is locked
|
// Check if collar is locked (V1 and V2 via ILockable)
|
||||||
if (!collarItem.isLocked(collar)) {
|
if (collar.getItem() instanceof ILockable lockable) {
|
||||||
TiedUpMod.LOGGER.debug(
|
if (!lockable.isLocked(collar)) {
|
||||||
"[StruggleCollar] Collar must be locked to tighten"
|
TiedUpMod.LOGGER.debug("[StruggleCollar] Collar must be locked to tighten");
|
||||||
);
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get base resistance from GameRules
|
// Get resistance — V2: read ResistanceComponent directly (avoids MAX-scan bug),
|
||||||
int baseResistance = collarItem.getBaseResistance(target);
|
// V1: use IHasResistance.getBaseResistance()
|
||||||
int currentResistance = collarItem.getCurrentResistance(collar, target);
|
if (!(collar.getItem() instanceof IHasResistance resistanceItem)) return;
|
||||||
|
int baseResistance;
|
||||||
|
ResistanceComponent comp = DataDrivenBondageItem.getComponent(
|
||||||
|
collar, ComponentType.RESISTANCE, ResistanceComponent.class
|
||||||
|
);
|
||||||
|
if (comp != null) {
|
||||||
|
baseResistance = comp.getBaseResistance();
|
||||||
|
} else {
|
||||||
|
baseResistance = resistanceItem.getBaseResistance(target);
|
||||||
|
}
|
||||||
|
int currentResistance = resistanceItem.getCurrentResistance(collar, target);
|
||||||
|
|
||||||
// Only tighten if current resistance is lower than base
|
// Only tighten if current resistance is lower than base
|
||||||
if (currentResistance >= baseResistance) {
|
if (currentResistance >= baseResistance) {
|
||||||
@@ -264,7 +253,7 @@ public class StruggleCollar extends StruggleState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Restore to base resistance
|
// Restore to base resistance
|
||||||
collarItem.setCurrentResistance(collar, baseResistance);
|
resistanceItem.setCurrentResistance(collar, baseResistance);
|
||||||
|
|
||||||
TiedUpMod.LOGGER.info(
|
TiedUpMod.LOGGER.info(
|
||||||
"[StruggleCollar] {} tightened {}'s collar (resistance {} -> {})",
|
"[StruggleCollar] {} tightened {}'s collar (resistance {} -> {})",
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package com.tiedup.remake.v2.bondage;
|
||||||
|
|
||||||
|
import com.tiedup.remake.items.base.ItemBind;
|
||||||
|
import com.tiedup.remake.v2.BodyRegionV2;
|
||||||
|
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemDefinition;
|
||||||
|
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemRegistry;
|
||||||
|
import java.util.Map;
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static utilities for bind mode operations on any bondage item stack (V1 or V2).
|
||||||
|
*
|
||||||
|
* <p>Bind mode determines whether a bind restrains arms, legs, or both.
|
||||||
|
* The mode is stored in the stack's NBT tag {@code "bindMode"}.</p>
|
||||||
|
*/
|
||||||
|
public final class BindModeHelper {
|
||||||
|
|
||||||
|
private BindModeHelper() {}
|
||||||
|
|
||||||
|
private static final String NBT_BIND_MODE = "bindMode";
|
||||||
|
|
||||||
|
public static final String MODE_FULL = "full";
|
||||||
|
public static final String MODE_ARMS = "arms";
|
||||||
|
public static final String MODE_LEGS = "legs";
|
||||||
|
|
||||||
|
private static final String[] MODE_CYCLE = { MODE_FULL, MODE_ARMS, MODE_LEGS };
|
||||||
|
|
||||||
|
private static final Map<String, String> MODE_TRANSLATION_KEYS = Map.of(
|
||||||
|
MODE_FULL, "tiedup.bindmode.full",
|
||||||
|
MODE_ARMS, "tiedup.bindmode.arms",
|
||||||
|
MODE_LEGS, "tiedup.bindmode.legs"
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the given stack is a bind item (V2 ARMS-region item or V1 ItemBind).
|
||||||
|
*/
|
||||||
|
public static boolean isBindItem(ItemStack stack) {
|
||||||
|
if (stack.isEmpty()) return false;
|
||||||
|
// V2: check data-driven definition
|
||||||
|
DataDrivenItemDefinition def = DataDrivenItemRegistry.get(stack);
|
||||||
|
if (def != null) {
|
||||||
|
return def.occupiedRegions().contains(BodyRegionV2.ARMS);
|
||||||
|
}
|
||||||
|
// V1 fallback
|
||||||
|
return stack.getItem() instanceof ItemBind;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the bind mode ID from the stack's NBT.
|
||||||
|
* @return "full", "arms", or "legs" (defaults to "full")
|
||||||
|
*/
|
||||||
|
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_ARMS.equals(value) || MODE_LEGS.equals(value)) return value;
|
||||||
|
return MODE_FULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** True if arms are restrained (mode is "arms" or "full"). */
|
||||||
|
public static boolean hasArmsBound(ItemStack stack) {
|
||||||
|
String mode = getBindModeId(stack);
|
||||||
|
return MODE_ARMS.equals(mode) || MODE_FULL.equals(mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** True if legs are restrained (mode is "legs" or "full"). */
|
||||||
|
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.
|
||||||
|
* @return the new mode ID
|
||||||
|
*/
|
||||||
|
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. */
|
||||||
|
public static String getBindModeTranslationKey(ItemStack stack) {
|
||||||
|
return MODE_TRANSLATION_KEYS.getOrDefault(getBindModeId(stack), "tiedup.bindmode.full");
|
||||||
|
}
|
||||||
|
}
|
||||||
371
src/main/java/com/tiedup/remake/v2/bondage/CollarHelper.java
Normal file
371
src/main/java/com/tiedup/remake/v2/bondage/CollarHelper.java
Normal file
@@ -0,0 +1,371 @@
|
|||||||
|
package com.tiedup.remake.v2.bondage;
|
||||||
|
|
||||||
|
import com.tiedup.remake.items.ItemChokeCollar;
|
||||||
|
import com.tiedup.remake.items.ItemGpsCollar;
|
||||||
|
import com.tiedup.remake.items.ItemShockCollar;
|
||||||
|
import com.tiedup.remake.items.ItemShockCollarAuto;
|
||||||
|
import com.tiedup.remake.items.base.ItemCollar;
|
||||||
|
import com.tiedup.remake.v2.bondage.component.ChokingComponent;
|
||||||
|
import com.tiedup.remake.v2.bondage.component.ComponentType;
|
||||||
|
import com.tiedup.remake.v2.bondage.component.GpsComponent;
|
||||||
|
import com.tiedup.remake.v2.bondage.component.OwnershipComponent;
|
||||||
|
import com.tiedup.remake.v2.bondage.component.ShockComponent;
|
||||||
|
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.nbt.ListTag;
|
||||||
|
import net.minecraft.nbt.Tag;
|
||||||
|
import net.minecraft.world.entity.player.Player;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static utility for collar operations bridging V1 (ItemCollar subclasses)
|
||||||
|
* and V2 (data-driven items with OwnershipComponent).
|
||||||
|
*/
|
||||||
|
public final class CollarHelper {
|
||||||
|
|
||||||
|
private CollarHelper() {}
|
||||||
|
|
||||||
|
// ===== DETECTION =====
|
||||||
|
|
||||||
|
// True if the stack is any kind of collar (V2 ownership component or V1 ItemCollar)
|
||||||
|
public static boolean isCollar(ItemStack stack) {
|
||||||
|
if (stack.isEmpty()) return false;
|
||||||
|
if (DataDrivenBondageItem.getComponent(stack, ComponentType.OWNERSHIP, OwnershipComponent.class) != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return stack.getItem() instanceof ItemCollar;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== OWNERSHIP (NBT: "owners") =====
|
||||||
|
|
||||||
|
// Returns all owner UUIDs stored in the collar's "owners" ListTag
|
||||||
|
public static List<UUID> getOwners(ItemStack stack) {
|
||||||
|
return getListUUIDs(stack, "owners");
|
||||||
|
}
|
||||||
|
|
||||||
|
// True if the given UUID is in the owners list
|
||||||
|
public static boolean isOwner(ItemStack stack, UUID uuid) {
|
||||||
|
return hasUUIDInList(stack, "owners", uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
// True if the given player is an owner
|
||||||
|
public static boolean isOwner(ItemStack stack, Player player) {
|
||||||
|
return isOwner(stack, player.getUUID());
|
||||||
|
}
|
||||||
|
|
||||||
|
// True if the collar has at least one owner
|
||||||
|
public static boolean hasOwner(ItemStack stack) {
|
||||||
|
CompoundTag tag = stack.getTag();
|
||||||
|
if (tag == null || !tag.contains("owners", Tag.TAG_LIST)) return false;
|
||||||
|
return !tag.getList("owners", Tag.TAG_COMPOUND).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds an owner entry {uuid, name} to the "owners" ListTag
|
||||||
|
public static void addOwner(ItemStack stack, UUID uuid, String name) {
|
||||||
|
addToList(stack, "owners", uuid, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convenience: add a player as owner
|
||||||
|
public static void addOwner(ItemStack stack, Player player) {
|
||||||
|
addOwner(stack, player.getUUID(), player.getGameProfile().getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Removes an owner by UUID
|
||||||
|
public static void removeOwner(ItemStack stack, UUID uuid) {
|
||||||
|
removeFromList(stack, "owners", uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== BLACKLIST (NBT: "blacklist") =====
|
||||||
|
|
||||||
|
public static List<UUID> getBlacklist(ItemStack stack) {
|
||||||
|
return getListUUIDs(stack, "blacklist");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isBlacklisted(ItemStack stack, UUID uuid) {
|
||||||
|
return hasUUIDInList(stack, "blacklist", uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void addToBlacklist(ItemStack stack, UUID uuid, String name) {
|
||||||
|
addToList(stack, "blacklist", uuid, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void removeFromBlacklist(ItemStack stack, UUID uuid) {
|
||||||
|
removeFromList(stack, "blacklist", uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== WHITELIST (NBT: "whitelist") =====
|
||||||
|
|
||||||
|
public static List<UUID> getWhitelist(ItemStack stack) {
|
||||||
|
return getListUUIDs(stack, "whitelist");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isWhitelisted(ItemStack stack, UUID uuid) {
|
||||||
|
return hasUUIDInList(stack, "whitelist", uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void addToWhitelist(ItemStack stack, UUID uuid, String name) {
|
||||||
|
addToList(stack, "whitelist", uuid, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void removeFromWhitelist(ItemStack stack, UUID uuid) {
|
||||||
|
removeFromList(stack, "whitelist", uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== LIST INTERNALS =====
|
||||||
|
|
||||||
|
private static List<UUID> getListUUIDs(ItemStack stack, String listKey) {
|
||||||
|
List<UUID> result = new ArrayList<>();
|
||||||
|
CompoundTag tag = stack.getTag();
|
||||||
|
if (tag == null || !tag.contains(listKey, Tag.TAG_LIST)) return result;
|
||||||
|
ListTag list = tag.getList(listKey, Tag.TAG_COMPOUND);
|
||||||
|
for (int i = 0; i < list.size(); i++) {
|
||||||
|
CompoundTag entry = list.getCompound(i);
|
||||||
|
if (entry.contains("uuid")) {
|
||||||
|
try {
|
||||||
|
result.add(UUID.fromString(entry.getString("uuid")));
|
||||||
|
} catch (IllegalArgumentException ignored) {
|
||||||
|
// Malformed UUID in NBT, skip
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean hasUUIDInList(ItemStack stack, String listKey, UUID uuid) {
|
||||||
|
CompoundTag tag = stack.getTag();
|
||||||
|
if (tag == null || !tag.contains(listKey, Tag.TAG_LIST)) return false;
|
||||||
|
String uuidStr = uuid.toString();
|
||||||
|
ListTag list = tag.getList(listKey, Tag.TAG_COMPOUND);
|
||||||
|
for (int i = 0; i < list.size(); i++) {
|
||||||
|
if (uuidStr.equals(list.getCompound(i).getString("uuid"))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addToList(ItemStack stack, String listKey, UUID uuid, String name) {
|
||||||
|
CompoundTag tag = stack.getOrCreateTag();
|
||||||
|
ListTag list = tag.contains(listKey, Tag.TAG_LIST)
|
||||||
|
? tag.getList(listKey, Tag.TAG_COMPOUND)
|
||||||
|
: new ListTag();
|
||||||
|
// Prevent duplicates
|
||||||
|
String uuidStr = uuid.toString();
|
||||||
|
for (int i = 0; i < list.size(); i++) {
|
||||||
|
if (uuidStr.equals(list.getCompound(i).getString("uuid"))) return;
|
||||||
|
}
|
||||||
|
CompoundTag entry = new CompoundTag();
|
||||||
|
entry.putString("uuid", uuidStr);
|
||||||
|
entry.putString("name", name);
|
||||||
|
list.add(entry);
|
||||||
|
tag.put(listKey, list);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void removeFromList(ItemStack stack, String listKey, UUID uuid) {
|
||||||
|
CompoundTag tag = stack.getTag();
|
||||||
|
if (tag == null || !tag.contains(listKey, Tag.TAG_LIST)) return;
|
||||||
|
String uuidStr = uuid.toString();
|
||||||
|
ListTag list = tag.getList(listKey, Tag.TAG_COMPOUND);
|
||||||
|
list.removeIf(element ->
|
||||||
|
uuidStr.equals(((CompoundTag) element).getString("uuid"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== FEATURES =====
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static String getNickname(ItemStack stack) {
|
||||||
|
CompoundTag tag = stack.getTag();
|
||||||
|
if (tag == null || !tag.contains("nickname")) return null;
|
||||||
|
return tag.getString("nickname");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setNickname(ItemStack stack, String nickname) {
|
||||||
|
stack.getOrCreateTag().putString("nickname", nickname);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean hasNickname(ItemStack stack) {
|
||||||
|
return stack.hasTag() && stack.getTag().contains("nickname");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static UUID getCellId(ItemStack stack) {
|
||||||
|
CompoundTag tag = stack.getTag();
|
||||||
|
if (tag == null || !tag.contains("cellId")) return null;
|
||||||
|
try {
|
||||||
|
return UUID.fromString(tag.getString("cellId"));
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setCellId(ItemStack stack, UUID cellId) {
|
||||||
|
stack.getOrCreateTag().putString("cellId", cellId.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean hasCellAssigned(ItemStack stack) {
|
||||||
|
return getCellId(stack) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isKidnappingModeEnabled(ItemStack stack) {
|
||||||
|
CompoundTag tag = stack.getTag();
|
||||||
|
return tag != null && tag.getBoolean("kidnappingMode");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setKidnappingModeEnabled(ItemStack stack, boolean enabled) {
|
||||||
|
stack.getOrCreateTag().putBoolean("kidnappingMode", enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kidnapping mode is ready when enabled AND a cell is assigned
|
||||||
|
public static boolean isKidnappingModeReady(ItemStack stack) {
|
||||||
|
return isKidnappingModeEnabled(stack) && hasCellAssigned(stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean shouldTieToPole(ItemStack stack) {
|
||||||
|
CompoundTag tag = stack.getTag();
|
||||||
|
return tag != null && tag.getBoolean("tieToPole");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setShouldTieToPole(ItemStack stack, boolean value) {
|
||||||
|
stack.getOrCreateTag().putBoolean("tieToPole", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default true when tag is absent or key is missing
|
||||||
|
public static boolean shouldWarnMasters(ItemStack stack) {
|
||||||
|
CompoundTag tag = stack.getTag();
|
||||||
|
if (tag == null || !tag.contains("warnMasters")) return true;
|
||||||
|
return tag.getBoolean("warnMasters");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setShouldWarnMasters(ItemStack stack, boolean value) {
|
||||||
|
stack.getOrCreateTag().putBoolean("warnMasters", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isBondageServiceEnabled(ItemStack stack) {
|
||||||
|
CompoundTag tag = stack.getTag();
|
||||||
|
return tag != null && tag.getBoolean("bondageservice");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setBondageServiceEnabled(ItemStack stack, boolean enabled) {
|
||||||
|
stack.getOrCreateTag().putBoolean("bondageservice", enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static String getServiceSentence(ItemStack stack) {
|
||||||
|
CompoundTag tag = stack.getTag();
|
||||||
|
if (tag == null || !tag.contains("servicesentence")) return null;
|
||||||
|
return tag.getString("servicesentence");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setServiceSentence(ItemStack stack, String sentence) {
|
||||||
|
stack.getOrCreateTag().putString("servicesentence", sentence);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== SHOCK =====
|
||||||
|
|
||||||
|
// True if the collar can shock (V2 ShockComponent or V1 ItemShockCollar)
|
||||||
|
public static boolean canShock(ItemStack stack) {
|
||||||
|
if (stack.isEmpty()) return false;
|
||||||
|
if (DataDrivenBondageItem.getComponent(stack, ComponentType.SHOCK, ShockComponent.class) != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return stack.getItem() instanceof ItemShockCollar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isPublicShock(ItemStack stack) {
|
||||||
|
CompoundTag tag = stack.getTag();
|
||||||
|
return tag != null && tag.getBoolean("public_mode");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setPublicShock(ItemStack stack, boolean publicMode) {
|
||||||
|
stack.getOrCreateTag().putBoolean("public_mode", publicMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// V2: from ShockComponent auto interval, V1: from ItemShockCollarAuto field, else 0
|
||||||
|
public static int getShockInterval(ItemStack stack) {
|
||||||
|
ShockComponent comp = DataDrivenBondageItem.getComponent(
|
||||||
|
stack, ComponentType.SHOCK, ShockComponent.class
|
||||||
|
);
|
||||||
|
if (comp != null) return comp.getAutoInterval();
|
||||||
|
if (stack.getItem() instanceof ItemShockCollarAuto auto) {
|
||||||
|
return auto.getInterval();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== GPS =====
|
||||||
|
|
||||||
|
// True if the collar has GPS capabilities
|
||||||
|
public static boolean hasGPS(ItemStack stack) {
|
||||||
|
if (stack.isEmpty()) return false;
|
||||||
|
if (DataDrivenBondageItem.getComponent(stack, ComponentType.GPS, GpsComponent.class) != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return stack.getItem() instanceof ItemGpsCollar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean hasPublicTracking(ItemStack stack) {
|
||||||
|
CompoundTag tag = stack.getTag();
|
||||||
|
return tag != null && tag.getBoolean("publicTracking");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setPublicTracking(ItemStack stack, boolean publicTracking) {
|
||||||
|
stack.getOrCreateTag().putBoolean("publicTracking", publicTracking);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GPS active defaults to true when absent
|
||||||
|
public static boolean isActive(ItemStack stack) {
|
||||||
|
CompoundTag tag = stack.getTag();
|
||||||
|
if (tag == null || !tag.contains("gpsActive")) return true;
|
||||||
|
return tag.getBoolean("gpsActive");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setActive(ItemStack stack, boolean active) {
|
||||||
|
stack.getOrCreateTag().putBoolean("gpsActive", active);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== CHOKE =====
|
||||||
|
|
||||||
|
// True if the collar is a choke collar
|
||||||
|
public static boolean isChokeCollar(ItemStack stack) {
|
||||||
|
if (stack.isEmpty()) return false;
|
||||||
|
if (DataDrivenBondageItem.getComponent(stack, ComponentType.CHOKING, ChokingComponent.class) != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return stack.getItem() instanceof ItemChokeCollar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isChoking(ItemStack stack) {
|
||||||
|
CompoundTag tag = stack.getTag();
|
||||||
|
return tag != null && tag.getBoolean("choking");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setChoking(ItemStack stack, boolean choking) {
|
||||||
|
stack.getOrCreateTag().putBoolean("choking", choking);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isPetPlayMode(ItemStack stack) {
|
||||||
|
CompoundTag tag = stack.getTag();
|
||||||
|
return tag != null && tag.getBoolean("petPlayMode");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setPetPlayMode(ItemStack stack, boolean petPlay) {
|
||||||
|
stack.getOrCreateTag().putBoolean("petPlayMode", petPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== ALERT SUPPRESSION =====
|
||||||
|
|
||||||
|
// Executes the action with collar removal alerts suppressed
|
||||||
|
public static void runWithSuppressedAlert(Runnable action) {
|
||||||
|
ItemCollar.runWithSuppressedAlert(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
// True if removal alerts are currently suppressed (ThreadLocal state)
|
||||||
|
public static boolean isRemovalAlertSuppressed() {
|
||||||
|
return ItemCollar.isRemovalAlertSuppressed();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package com.tiedup.remake.v2.bondage;
|
||||||
|
|
||||||
|
import com.tiedup.remake.items.base.ItemBind;
|
||||||
|
import com.tiedup.remake.items.base.PoseType;
|
||||||
|
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemDefinition;
|
||||||
|
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemRegistry;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves the {@link PoseType} for any bondage item stack (V1 or V2).
|
||||||
|
*
|
||||||
|
* <p>V2 items read from the data-driven definition's {@code pose_type} field.
|
||||||
|
* V1 items fall back to {@code ItemBind.getPoseType()}.</p>
|
||||||
|
*/
|
||||||
|
public final class PoseTypeHelper {
|
||||||
|
|
||||||
|
private PoseTypeHelper() {}
|
||||||
|
|
||||||
|
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().toUpperCase());
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
return PoseType.STANDARD;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// V1 fallback
|
||||||
|
if (stack.getItem() instanceof ItemBind bind) {
|
||||||
|
return bind.getPoseType();
|
||||||
|
}
|
||||||
|
return PoseType.STANDARD;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
package com.tiedup.remake.v2.bondage;
|
||||||
|
|
||||||
|
import com.tiedup.remake.core.SettingsAccessor;
|
||||||
|
import com.tiedup.remake.core.TiedUpMod;
|
||||||
|
import com.tiedup.remake.state.IBondageState;
|
||||||
|
import com.tiedup.remake.state.PlayerBindState;
|
||||||
|
import com.tiedup.remake.tasks.TyingTask;
|
||||||
|
import com.tiedup.remake.tasks.V2TyingPlayerTask;
|
||||||
|
import com.tiedup.remake.util.KidnappedHelper;
|
||||||
|
import com.tiedup.remake.v2.BodyRegionV2;
|
||||||
|
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
|
||||||
|
import com.tiedup.remake.v2.bondage.V2EquipResult;
|
||||||
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
|
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.ItemStack;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the tying interaction flow for V2 data-driven ARMS items.
|
||||||
|
*
|
||||||
|
* <p>Extracted from {@code ItemBind.interactLivingEntity()} to support
|
||||||
|
* the same tying task flow for data-driven items.</p>
|
||||||
|
*/
|
||||||
|
public final class TyingInteractionHelper {
|
||||||
|
|
||||||
|
private TyingInteractionHelper() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle right-click tying of a target entity with a V2 ARMS item.
|
||||||
|
* Creates/continues a V2TyingPlayerTask.
|
||||||
|
*/
|
||||||
|
public static InteractionResult handleTying(
|
||||||
|
ServerPlayer player,
|
||||||
|
LivingEntity target,
|
||||||
|
ItemStack stack,
|
||||||
|
InteractionHand hand
|
||||||
|
) {
|
||||||
|
IBondageState targetState = KidnappedHelper.getKidnappedState(target);
|
||||||
|
if (targetState == null) return InteractionResult.PASS;
|
||||||
|
|
||||||
|
// Kidnapper can't be tied themselves
|
||||||
|
IBondageState kidnapperState = KidnappedHelper.getKidnappedState(player);
|
||||||
|
if (kidnapperState != null && kidnapperState.isTiedUp()) {
|
||||||
|
return InteractionResult.PASS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Already tied — try to swap (check both V1 state and V2 region to prevent dual-bind)
|
||||||
|
if (targetState.isTiedUp() || V2EquipmentHelper.isRegionOccupied(target, BodyRegionV2.ARMS)) {
|
||||||
|
if (stack.isEmpty()) return InteractionResult.PASS;
|
||||||
|
ItemStack oldBind = V2EquipmentHelper.unequipFromRegion(target, BodyRegionV2.ARMS);
|
||||||
|
if (!oldBind.isEmpty()) {
|
||||||
|
V2EquipResult result = V2EquipmentHelper.equipItem(target, stack.copy());
|
||||||
|
if (result.isSuccess()) {
|
||||||
|
stack.shrink(1);
|
||||||
|
target.spawnAtLocation(oldBind);
|
||||||
|
TiedUpMod.LOGGER.debug("[TyingInteraction] Swapped bind on {}", target.getName().getString());
|
||||||
|
return InteractionResult.SUCCESS;
|
||||||
|
} else {
|
||||||
|
// Equip failed — rollback: re-equip old bind
|
||||||
|
V2EquipResult rollback = V2EquipmentHelper.equipItem(target, oldBind);
|
||||||
|
if (!rollback.isSuccess()) {
|
||||||
|
// Rollback also failed — drop old bind as safety net
|
||||||
|
target.spawnAtLocation(oldBind);
|
||||||
|
TiedUpMod.LOGGER.warn("[TyingInteraction] Swap AND rollback failed, dropped old bind for {}", target.getName().getString());
|
||||||
|
} else {
|
||||||
|
TiedUpMod.LOGGER.debug("[TyingInteraction] Swap failed, rolled back old bind on {}", target.getName().getString());
|
||||||
|
}
|
||||||
|
return InteractionResult.PASS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return InteractionResult.PASS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Distance + line-of-sight (skip for self-tying)
|
||||||
|
boolean isSelfTying = player.equals(target);
|
||||||
|
if (!isSelfTying) {
|
||||||
|
if (player.distanceTo(target) > 4.0 || !player.hasLineOfSight(target)) {
|
||||||
|
return InteractionResult.PASS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create/continue tying task
|
||||||
|
PlayerBindState playerState = PlayerBindState.getInstance(player);
|
||||||
|
if (playerState == null) return InteractionResult.PASS;
|
||||||
|
|
||||||
|
int tyingSeconds = SettingsAccessor.getTyingPlayerTime(player.level().getGameRules());
|
||||||
|
|
||||||
|
V2TyingPlayerTask newTask = new V2TyingPlayerTask(
|
||||||
|
stack.copy(),
|
||||||
|
stack,
|
||||||
|
targetState,
|
||||||
|
target,
|
||||||
|
tyingSeconds,
|
||||||
|
player.level(),
|
||||||
|
player
|
||||||
|
);
|
||||||
|
|
||||||
|
TyingTask currentTask = playerState.getCurrentTyingTask();
|
||||||
|
if (currentTask == null
|
||||||
|
|| !(currentTask instanceof V2TyingPlayerTask)
|
||||||
|
|| !currentTask.isSameTarget(target)
|
||||||
|
|| currentTask.isOutdated()
|
||||||
|
|| !ItemStack.matches(currentTask.getBind(), stack)) {
|
||||||
|
// Start new task (also handles case where existing task is V1 TyingPlayerTask)
|
||||||
|
playerState.setCurrentTyingTask(newTask);
|
||||||
|
newTask.start();
|
||||||
|
} else {
|
||||||
|
newTask = (V2TyingPlayerTask) currentTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
newTask.update();
|
||||||
|
|
||||||
|
if (newTask.isStopped()) {
|
||||||
|
// Item already consumed by V2TyingPlayerTask.onComplete() — don't shrink again
|
||||||
|
playerState.setCurrentTyingTask(null);
|
||||||
|
TiedUpMod.LOGGER.info("[TyingInteraction] {} tied {}", player.getName().getString(), target.getName().getString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return InteractionResult.SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,58 @@
|
|||||||
|
package com.tiedup.remake.v2.bondage.component;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import java.util.List;
|
||||||
|
import net.minecraft.ChatFormatting;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.minecraft.world.item.TooltipFlag;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void appendTooltip(ItemStack stack, @Nullable Level level, List<Component> tooltip, TooltipFlag flag) {
|
||||||
|
tooltip.add(Component.translatable("item.tiedup.tooltip.choking").withStyle(ChatFormatting.DARK_PURPLE));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
package com.tiedup.remake.v2.bondage.component;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.EnumMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
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;
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public IItemComponent get(ComponentType type) {
|
||||||
|
return components.get(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean has(ComponentType type) {
|
||||||
|
return components.containsKey(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onEquipped(ItemStack stack, LivingEntity entity) {
|
||||||
|
for (IItemComponent c : components.values()) {
|
||||||
|
c.onEquipped(stack, entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onUnequipped(ItemStack stack, LivingEntity entity) {
|
||||||
|
for (IItemComponent c : components.values()) {
|
||||||
|
c.onUnequipped(stack, entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean blocksUnequip(ItemStack stack, LivingEntity entity) {
|
||||||
|
for (IItemComponent c : components.values()) {
|
||||||
|
if (c.blocksUnequip(stack, entity)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void appendTooltip(ItemStack stack, @Nullable Level level, List<Component> tooltip, TooltipFlag flag) {
|
||||||
|
for (IItemComponent c : components.values()) {
|
||||||
|
c.appendTooltip(stack, level, tooltip, flag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return components.isEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package com.tiedup.remake.v2.bondage.component;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
public enum ComponentType {
|
||||||
|
LOCKABLE("lockable", LockableComponent::fromJson),
|
||||||
|
RESISTANCE("resistance", ResistanceComponent::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),
|
||||||
|
OWNERSHIP("ownership", OwnershipComponent::fromJson);
|
||||||
|
|
||||||
|
private final String jsonKey;
|
||||||
|
private final Function<JsonObject, IItemComponent> factory;
|
||||||
|
|
||||||
|
/** Pre-built lookup map for O(1) fromKey() instead of linear scan. */
|
||||||
|
private static final Map<String, ComponentType> BY_KEY;
|
||||||
|
static {
|
||||||
|
Map<String, ComponentType> map = new HashMap<>();
|
||||||
|
for (ComponentType type : values()) {
|
||||||
|
map.put(type.jsonKey, type);
|
||||||
|
}
|
||||||
|
BY_KEY = Collections.unmodifiableMap(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static ComponentType fromKey(String key) {
|
||||||
|
return BY_KEY.get(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
package com.tiedup.remake.v2.bondage.component;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.tiedup.remake.core.TiedUpMod;
|
||||||
|
import com.tiedup.remake.util.GagMaterial;
|
||||||
|
import java.util.List;
|
||||||
|
import net.minecraft.ChatFormatting;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.minecraft.world.item.TooltipFlag;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component: gagging behavior for data-driven items.
|
||||||
|
*
|
||||||
|
* <p>Config-driven: {@code "gagging": {"material": "ball"}} delegates to
|
||||||
|
* {@link GagMaterial} for comprehension/range from ModConfig at runtime.</p>
|
||||||
|
*
|
||||||
|
* <p>Override: {@code "gagging": {"comprehension": 0.15, "range": 8.0}} uses
|
||||||
|
* explicit values that take priority over the material lookup.</p>
|
||||||
|
*/
|
||||||
|
public class GaggingComponent implements IItemComponent {
|
||||||
|
|
||||||
|
private final @Nullable String material;
|
||||||
|
private final @Nullable GagMaterial cachedMaterial;
|
||||||
|
private final double comprehensionOverride;
|
||||||
|
private final double rangeOverride;
|
||||||
|
|
||||||
|
private GaggingComponent(@Nullable String material, @Nullable GagMaterial cachedMaterial,
|
||||||
|
double comprehensionOverride, double rangeOverride) {
|
||||||
|
this.material = material;
|
||||||
|
this.cachedMaterial = cachedMaterial;
|
||||||
|
this.comprehensionOverride = comprehensionOverride;
|
||||||
|
this.rangeOverride = rangeOverride;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IItemComponent fromJson(JsonObject config) {
|
||||||
|
String material = null;
|
||||||
|
double comprehension = -1;
|
||||||
|
double range = -1;
|
||||||
|
if (config != null) {
|
||||||
|
if (config.has("material")) {
|
||||||
|
material = config.get("material").getAsString();
|
||||||
|
}
|
||||||
|
if (config.has("comprehension")) {
|
||||||
|
comprehension = Math.max(0.0, Math.min(1.0, config.get("comprehension").getAsDouble()));
|
||||||
|
}
|
||||||
|
if (config.has("range")) {
|
||||||
|
range = Math.max(0.0, config.get("range").getAsDouble());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Resolve and cache GagMaterial at load time to avoid valueOf() on every chat message
|
||||||
|
GagMaterial resolved = null;
|
||||||
|
if (material != null) {
|
||||||
|
try {
|
||||||
|
resolved = GagMaterial.valueOf(material.toUpperCase());
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
TiedUpMod.LOGGER.warn("[GaggingComponent] Unknown gag material '{}' — using defaults", material);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new GaggingComponent(material, resolved, comprehension, range);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** How much of the gagged speech is comprehensible (0.0 = nothing, 1.0 = full). */
|
||||||
|
public double getComprehension() {
|
||||||
|
if (comprehensionOverride >= 0) return comprehensionOverride;
|
||||||
|
GagMaterial gag = getMaterial();
|
||||||
|
if (gag != null) return gag.getComprehension();
|
||||||
|
return 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Maximum range in blocks where muffled speech can be heard. */
|
||||||
|
public double getRange() {
|
||||||
|
if (rangeOverride >= 0) return rangeOverride;
|
||||||
|
GagMaterial gag = getMaterial();
|
||||||
|
if (gag != null) return gag.getTalkRange();
|
||||||
|
return 10.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The gag material enum, or null if not configured or invalid. Cached at load time. */
|
||||||
|
public @Nullable GagMaterial getMaterial() {
|
||||||
|
return cachedMaterial;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The raw material string from JSON, or null. */
|
||||||
|
public @Nullable String getMaterialName() {
|
||||||
|
return material;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void appendTooltip(ItemStack stack, @Nullable Level level, List<Component> tooltip, TooltipFlag flag) {
|
||||||
|
if (material != null) {
|
||||||
|
tooltip.add(Component.translatable("item.tiedup.tooltip.gag_material", material)
|
||||||
|
.withStyle(ChatFormatting.RED));
|
||||||
|
} else {
|
||||||
|
tooltip.add(Component.translatable("item.tiedup.tooltip.gagging").withStyle(ChatFormatting.RED));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package com.tiedup.remake.v2.bondage.component;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import java.util.List;
|
||||||
|
import net.minecraft.ChatFormatting;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.minecraft.world.item.TooltipFlag;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void appendTooltip(ItemStack stack, @Nullable Level level, List<Component> tooltip, TooltipFlag flag) {
|
||||||
|
tooltip.add(Component.translatable("item.tiedup.tooltip.gps_tracking")
|
||||||
|
.withStyle(ChatFormatting.AQUA));
|
||||||
|
if (safeZoneRadius > 0) {
|
||||||
|
tooltip.add(Component.translatable("item.tiedup.tooltip.gps_zone_radius", safeZoneRadius)
|
||||||
|
.withStyle(ChatFormatting.DARK_AQUA));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.tiedup.remake.v2.bondage.component;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A reusable behavior module for data-driven bondage items.
|
||||||
|
* Components are declared in JSON and instantiated per item definition.
|
||||||
|
*/
|
||||||
|
public interface IItemComponent {
|
||||||
|
|
||||||
|
default void onEquipped(ItemStack stack, LivingEntity entity) {}
|
||||||
|
|
||||||
|
default void onUnequipped(ItemStack stack, LivingEntity entity) {}
|
||||||
|
|
||||||
|
default boolean blocksUnequip(ItemStack stack, LivingEntity entity) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
default void appendTooltip(ItemStack stack, @Nullable Level level, List<Component> tooltip, TooltipFlag flag) {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package com.tiedup.remake.v2.bondage.component;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import java.util.List;
|
||||||
|
import net.minecraft.ChatFormatting;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.minecraft.world.item.TooltipFlag;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component: lockable behavior for data-driven items.
|
||||||
|
*
|
||||||
|
* <p>Stores the per-item lock resistance parsed from JSON. The lock check
|
||||||
|
* itself is NOT done here because {@code AbstractV2BondageItem.canUnequip()}
|
||||||
|
* already delegates to {@code ILockable.isLocked()} -- duplicating it in
|
||||||
|
* {@code blocksUnequip()} would be redundant.</p>
|
||||||
|
*
|
||||||
|
* <p>Consumers retrieve the per-item lock resistance via
|
||||||
|
* {@link com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem#getItemLockResistance(net.minecraft.world.item.ItemStack)}.</p>
|
||||||
|
*
|
||||||
|
* JSON config:
|
||||||
|
* {@code "lockable": {}} or {@code "lockable": {"lock_resistance": 300}}
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
if (config != null && config.has("lock_resistance")) {
|
||||||
|
resistance = config.get("lock_resistance").getAsInt();
|
||||||
|
}
|
||||||
|
resistance = Math.max(0, resistance);
|
||||||
|
return new LockableComponent(resistance);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the per-item lock resistance value parsed from JSON.
|
||||||
|
*
|
||||||
|
* @return lock resistance (always >= 0 after RISK-003 clamping)
|
||||||
|
*/
|
||||||
|
public int getLockResistance() {
|
||||||
|
return lockResistance;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void appendTooltip(ItemStack stack, @Nullable Level level, List<Component> tooltip, TooltipFlag flag) {
|
||||||
|
tooltip.add(Component.translatable("item.tiedup.tooltip.lockable").withStyle(ChatFormatting.GOLD));
|
||||||
|
if (flag.isAdvanced()) {
|
||||||
|
tooltip.add(Component.translatable("item.tiedup.tooltip.lock_resistance", lockResistance)
|
||||||
|
.withStyle(ChatFormatting.DARK_GRAY));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
package com.tiedup.remake.v2.bondage.component;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.tiedup.remake.core.TiedUpMod;
|
||||||
|
import com.tiedup.remake.items.base.ItemCollar;
|
||||||
|
import com.tiedup.remake.state.CollarRegistry;
|
||||||
|
import com.tiedup.remake.v2.bondage.CollarHelper;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
import net.minecraft.ChatFormatting;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component: collar ownership behavior for data-driven items.
|
||||||
|
*
|
||||||
|
* <p>Marks an item as a collar with ownership capabilities.
|
||||||
|
* Lifecycle hooks handle CollarRegistry registration/unregistration.</p>
|
||||||
|
*
|
||||||
|
* <p>JSON config: {@code "ownership": {}}</p>
|
||||||
|
*/
|
||||||
|
public class OwnershipComponent implements IItemComponent {
|
||||||
|
|
||||||
|
private OwnershipComponent() {}
|
||||||
|
|
||||||
|
public static IItemComponent fromJson(JsonObject config) {
|
||||||
|
return new OwnershipComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEquipped(ItemStack stack, LivingEntity entity) {
|
||||||
|
if (entity.level().isClientSide()) return;
|
||||||
|
if (!(entity.level() instanceof ServerLevel serverLevel)) return;
|
||||||
|
|
||||||
|
List<UUID> owners = CollarHelper.getOwners(stack);
|
||||||
|
if (owners.isEmpty()) {
|
||||||
|
TiedUpMod.LOGGER.debug(
|
||||||
|
"[OwnershipComponent] Collar equipped on {} with no owners in NBT — skipping registry",
|
||||||
|
entity.getName().getString()
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
CollarRegistry registry = CollarRegistry.get(serverLevel);
|
||||||
|
registry.registerCollar(entity.getUUID(), new HashSet<>(owners));
|
||||||
|
TiedUpMod.LOGGER.debug(
|
||||||
|
"[OwnershipComponent] Registered collar for {} with {} owner(s)",
|
||||||
|
entity.getName().getString(), owners.size()
|
||||||
|
);
|
||||||
|
} catch (Exception e) {
|
||||||
|
TiedUpMod.LOGGER.warn(
|
||||||
|
"[OwnershipComponent] Failed to register collar for {}: {}",
|
||||||
|
entity.getName().getString(), e.getMessage()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUnequipped(ItemStack stack, LivingEntity entity) {
|
||||||
|
if (entity.level().isClientSide()) return;
|
||||||
|
if (!(entity.level() instanceof ServerLevel serverLevel)) return;
|
||||||
|
|
||||||
|
// Alert kidnappers + unregister from CollarRegistry
|
||||||
|
// onCollarRemoved handles both the alert AND the unregister call internally,
|
||||||
|
// so we do NOT call registry.unregisterWearer() separately to avoid double unregister.
|
||||||
|
if (!CollarHelper.isRemovalAlertSuppressed()) {
|
||||||
|
ItemCollar.onCollarRemoved(entity, true);
|
||||||
|
} else {
|
||||||
|
// Suppressed alert path: still need to unregister, just skip the alert
|
||||||
|
try {
|
||||||
|
CollarRegistry registry = CollarRegistry.get(serverLevel);
|
||||||
|
registry.unregisterWearer(entity.getUUID());
|
||||||
|
TiedUpMod.LOGGER.debug(
|
||||||
|
"[OwnershipComponent] Unregistered collar for {} (alert suppressed)",
|
||||||
|
entity.getName().getString()
|
||||||
|
);
|
||||||
|
} catch (Exception e) {
|
||||||
|
TiedUpMod.LOGGER.warn(
|
||||||
|
"[OwnershipComponent] Failed to unregister collar for {}: {}",
|
||||||
|
entity.getName().getString(), e.getMessage()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void appendTooltip(ItemStack stack, @Nullable Level level, List<Component> tooltip, TooltipFlag flag) {
|
||||||
|
String nickname = CollarHelper.getNickname(stack);
|
||||||
|
if (nickname != null && !nickname.isEmpty()) {
|
||||||
|
tooltip.add(Component.translatable("item.tiedup.tooltip.nickname", nickname)
|
||||||
|
.withStyle(ChatFormatting.LIGHT_PURPLE));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<UUID> owners = CollarHelper.getOwners(stack);
|
||||||
|
if (!owners.isEmpty()) {
|
||||||
|
tooltip.add(Component.translatable("item.tiedup.tooltip.owners", owners.size())
|
||||||
|
.withStyle(ChatFormatting.GOLD));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CollarHelper.canShock(stack)) {
|
||||||
|
tooltip.add(Component.translatable("item.tiedup.tooltip.shock_capable")
|
||||||
|
.withStyle(ChatFormatting.DARK_RED));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CollarHelper.hasGPS(stack)) {
|
||||||
|
tooltip.add(Component.translatable("item.tiedup.tooltip.gps_capable")
|
||||||
|
.withStyle(ChatFormatting.AQUA));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CollarHelper.isChokeCollar(stack)) {
|
||||||
|
tooltip.add(Component.translatable("item.tiedup.tooltip.choke_capable")
|
||||||
|
.withStyle(ChatFormatting.DARK_PURPLE));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package com.tiedup.remake.v2.bondage.component;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.tiedup.remake.core.SettingsAccessor;
|
||||||
|
import java.util.List;
|
||||||
|
import net.minecraft.ChatFormatting;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.minecraft.world.item.TooltipFlag;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component: struggle resistance for data-driven items.
|
||||||
|
*
|
||||||
|
* <p>Config-driven: {@code "resistance": {"id": "rope"}} delegates to
|
||||||
|
* {@link SettingsAccessor#getBindResistance(String)} at runtime.</p>
|
||||||
|
*
|
||||||
|
* <p>Legacy/override: {@code "resistance": {"base": 150}} uses a hardcoded value.</p>
|
||||||
|
*/
|
||||||
|
public class ResistanceComponent implements IItemComponent {
|
||||||
|
|
||||||
|
private final @Nullable String resistanceId;
|
||||||
|
private final int fallbackBase;
|
||||||
|
|
||||||
|
private ResistanceComponent(@Nullable String resistanceId, int fallbackBase) {
|
||||||
|
this.resistanceId = resistanceId;
|
||||||
|
this.fallbackBase = fallbackBase;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IItemComponent fromJson(JsonObject config) {
|
||||||
|
String id = null;
|
||||||
|
int base = 100;
|
||||||
|
if (config != null) {
|
||||||
|
if (config.has("id")) {
|
||||||
|
id = config.get("id").getAsString();
|
||||||
|
}
|
||||||
|
if (config.has("base")) {
|
||||||
|
base = config.get("base").getAsInt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
base = Math.max(0, base);
|
||||||
|
return new ResistanceComponent(id, base);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the base resistance for this item.
|
||||||
|
* If a {@code resistanceId} is configured, delegates to server config at runtime.
|
||||||
|
* Otherwise returns the hardcoded fallback value.
|
||||||
|
*/
|
||||||
|
public int getBaseResistance() {
|
||||||
|
if (resistanceId != null) {
|
||||||
|
return SettingsAccessor.getBindResistance(resistanceId);
|
||||||
|
}
|
||||||
|
return fallbackBase;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The config key used for runtime resistance lookup, or null if hardcoded. */
|
||||||
|
public @Nullable String getResistanceId() {
|
||||||
|
return resistanceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void appendTooltip(ItemStack stack, @Nullable Level level, List<Component> tooltip, TooltipFlag flag) {
|
||||||
|
if (flag.isAdvanced()) {
|
||||||
|
tooltip.add(Component.translatable("item.tiedup.tooltip.resistance", getBaseResistance())
|
||||||
|
.withStyle(ChatFormatting.DARK_GRAY));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package com.tiedup.remake.v2.bondage.component;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import java.util.List;
|
||||||
|
import net.minecraft.ChatFormatting;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.minecraft.world.item.TooltipFlag;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void appendTooltip(ItemStack stack, @Nullable Level level, List<Component> tooltip, TooltipFlag flag) {
|
||||||
|
if (hasAutoShock()) {
|
||||||
|
float seconds = autoInterval / 20.0f;
|
||||||
|
tooltip.add(Component.translatable("item.tiedup.tooltip.shock_auto", String.format("%.1f", seconds))
|
||||||
|
.withStyle(ChatFormatting.DARK_RED));
|
||||||
|
} else {
|
||||||
|
tooltip.add(Component.translatable("item.tiedup.tooltip.shock_manual")
|
||||||
|
.withStyle(ChatFormatting.DARK_RED));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,17 @@
|
|||||||
package com.tiedup.remake.v2.bondage.datadriven;
|
package com.tiedup.remake.v2.bondage.datadriven;
|
||||||
|
|
||||||
|
import com.tiedup.remake.core.TiedUpMod;
|
||||||
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.IV2BondageEquipment;
|
import com.tiedup.remake.v2.bondage.IV2BondageEquipment;
|
||||||
|
import com.tiedup.remake.v2.bondage.TyingInteractionHelper;
|
||||||
import com.tiedup.remake.v2.bondage.V2BondageItems;
|
import com.tiedup.remake.v2.bondage.V2BondageItems;
|
||||||
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
|
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
|
||||||
|
import com.tiedup.remake.v2.bondage.component.ComponentHolder;
|
||||||
|
import com.tiedup.remake.v2.bondage.component.ComponentType;
|
||||||
|
import com.tiedup.remake.v2.bondage.component.IItemComponent;
|
||||||
|
import com.tiedup.remake.v2.bondage.component.LockableComponent;
|
||||||
|
import com.tiedup.remake.v2.bondage.component.ResistanceComponent;
|
||||||
import com.tiedup.remake.v2.bondage.items.AbstractV2BondageItem;
|
import com.tiedup.remake.v2.bondage.items.AbstractV2BondageItem;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -12,7 +20,13 @@ import java.util.stream.Collectors;
|
|||||||
import net.minecraft.ChatFormatting;
|
import net.minecraft.ChatFormatting;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
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.LivingEntity;
|
||||||
|
import net.minecraft.world.entity.player.Player;
|
||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
import net.minecraft.world.item.TooltipFlag;
|
import net.minecraft.world.item.TooltipFlag;
|
||||||
import net.minecraft.world.level.Level;
|
import net.minecraft.world.level.Level;
|
||||||
@@ -111,6 +125,60 @@ public class DataDrivenBondageItem extends AbstractV2BondageItem {
|
|||||||
return def != null && def.supportsColor();
|
return def != null && def.supportsColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===== INTERACTION ROUTING =====
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InteractionResultHolder<ItemStack> use(Level level, Player player, InteractionHand hand) {
|
||||||
|
ItemStack stack = player.getItemInHand(hand);
|
||||||
|
Set<BodyRegionV2> regions = getOccupiedRegions(stack);
|
||||||
|
|
||||||
|
// ARMS items: shift+click cycles bind mode
|
||||||
|
if (regions.contains(BodyRegionV2.ARMS) && player.isShiftKeyDown() && !level.isClientSide) {
|
||||||
|
BindModeHelper.cycleBindModeId(stack);
|
||||||
|
player.playSound(SoundEvents.CHAIN_STEP, 0.5f, 1.2f);
|
||||||
|
player.displayClientMessage(
|
||||||
|
Component.translatable(
|
||||||
|
"tiedup.message.bindmode_changed",
|
||||||
|
Component.translatable(BindModeHelper.getBindModeTranslationKey(stack))
|
||||||
|
),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
return InteractionResultHolder.success(stack);
|
||||||
|
}
|
||||||
|
return super.use(level, player, hand);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InteractionResult interactLivingEntity(
|
||||||
|
ItemStack stack, Player player, LivingEntity target, InteractionHand hand
|
||||||
|
) {
|
||||||
|
// Client: arm swing
|
||||||
|
if (player.level().isClientSide) {
|
||||||
|
return InteractionResult.SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
DataDrivenItemDefinition def = DataDrivenItemRegistry.get(stack);
|
||||||
|
if (def == null) return InteractionResult.PASS;
|
||||||
|
|
||||||
|
Set<BodyRegionV2> regions = def.occupiedRegions();
|
||||||
|
|
||||||
|
// ARMS: tying flow (do NOT call super — avoids double equip)
|
||||||
|
if (regions.contains(BodyRegionV2.ARMS) && player instanceof ServerPlayer serverPlayer) {
|
||||||
|
return TyingInteractionHelper.handleTying(serverPlayer, target, stack, hand);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NECK: blocked until Branch C wires the full collar equip flow
|
||||||
|
// (add owner to NBT, register in CollarRegistry, play sound, sync).
|
||||||
|
// Without this, V2 collars equip without ownership — breaking GPS, shock, alerts.
|
||||||
|
if (regions.contains(BodyRegionV2.NECK)) {
|
||||||
|
TiedUpMod.LOGGER.debug("[DataDrivenBondageItem] NECK equip blocked — collar flow not wired yet");
|
||||||
|
return InteractionResult.PASS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// All other regions (MOUTH, EYES, EARS, HANDS): instant equip via parent
|
||||||
|
return super.interactLivingEntity(stack, player, target, hand);
|
||||||
|
}
|
||||||
|
|
||||||
// ===== IHasResistance IMPLEMENTATION =====
|
// ===== IHasResistance IMPLEMENTATION =====
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -147,6 +215,21 @@ public class DataDrivenBondageItem extends AbstractV2BondageItem {
|
|||||||
.entrySet()) {
|
.entrySet()) {
|
||||||
ItemStack stack = entry.getValue();
|
ItemStack stack = entry.getValue();
|
||||||
if (stack.getItem() == this) {
|
if (stack.getItem() == this) {
|
||||||
|
// Try component first (stack-aware, fixes I-03)
|
||||||
|
ResistanceComponent comp = DataDrivenBondageItem
|
||||||
|
.getComponent(
|
||||||
|
stack,
|
||||||
|
ComponentType.RESISTANCE,
|
||||||
|
ResistanceComponent.class
|
||||||
|
);
|
||||||
|
if (comp != null) {
|
||||||
|
maxDifficulty = Math.max(
|
||||||
|
maxDifficulty,
|
||||||
|
comp.getBaseResistance()
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Fallback: read from definition directly
|
||||||
DataDrivenItemDefinition def =
|
DataDrivenItemDefinition def =
|
||||||
DataDrivenItemRegistry.get(stack);
|
DataDrivenItemRegistry.get(stack);
|
||||||
if (def != null) {
|
if (def != null) {
|
||||||
@@ -226,6 +309,12 @@ public class DataDrivenBondageItem extends AbstractV2BondageItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Component tooltips
|
||||||
|
ComponentHolder holder = DataDrivenItemRegistry.getComponents(stack);
|
||||||
|
if (holder != null) {
|
||||||
|
holder.appendTooltip(stack, level, tooltip, flag);
|
||||||
|
}
|
||||||
|
|
||||||
// Lock status + escape difficulty (from AbstractV2BondageItem)
|
// Lock status + escape difficulty (from AbstractV2BondageItem)
|
||||||
super.appendHoverText(stack, level, tooltip, flag);
|
super.appendHoverText(stack, level, tooltip, flag);
|
||||||
|
|
||||||
@@ -257,6 +346,109 @@ public class DataDrivenBondageItem extends AbstractV2BondageItem {
|
|||||||
return Component.literal(def.displayName());
|
return Component.literal(def.displayName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===== COMPONENT LIFECYCLE DELEGATION =====
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEquipped(ItemStack stack, LivingEntity entity) {
|
||||||
|
ComponentHolder holder = DataDrivenItemRegistry.getComponents(stack);
|
||||||
|
if (holder != null) {
|
||||||
|
holder.onEquipped(stack, entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NPC speed reduction (players use MovementStyleManager, not this legacy path)
|
||||||
|
if (!(entity instanceof Player)) {
|
||||||
|
Set<BodyRegionV2> regions = getOccupiedRegions(stack);
|
||||||
|
if (regions.contains(BodyRegionV2.ARMS) && BindModeHelper.hasLegsBound(stack)) {
|
||||||
|
com.tiedup.remake.items.base.PoseType pose = com.tiedup.remake.v2.bondage.PoseTypeHelper.getPoseType(stack);
|
||||||
|
boolean fullImmobilization = pose == com.tiedup.remake.items.base.PoseType.WRAP
|
||||||
|
|| pose == com.tiedup.remake.items.base.PoseType.LATEX_SACK;
|
||||||
|
com.tiedup.remake.util.RestraintEffectUtils.applyBindSpeedReduction(entity, fullImmobilization);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUnequipped(ItemStack stack, LivingEntity entity) {
|
||||||
|
ComponentHolder holder = DataDrivenItemRegistry.getComponents(stack);
|
||||||
|
if (holder != null) {
|
||||||
|
holder.onUnequipped(stack, entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NPC speed cleanup
|
||||||
|
if (!(entity instanceof Player)) {
|
||||||
|
Set<BodyRegionV2> regions = getOccupiedRegions(stack);
|
||||||
|
if (regions.contains(BodyRegionV2.ARMS)) {
|
||||||
|
com.tiedup.remake.util.RestraintEffectUtils.removeBindSpeedReduction(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a specific component from a data-driven item stack.
|
||||||
|
*
|
||||||
|
* @param stack the ItemStack to inspect
|
||||||
|
* @param type the component type to look up
|
||||||
|
* @param clazz the expected component class
|
||||||
|
* @param <T> the component type
|
||||||
|
* @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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get per-item lock resistance from the LockableComponent, if present.
|
||||||
|
*
|
||||||
|
* <p>Returns the component's JSON-configured value if the stack has a
|
||||||
|
* LockableComponent, otherwise falls back to the global config value
|
||||||
|
* from {@link com.tiedup.remake.core.SettingsAccessor#getPadlockResistance}.</p>
|
||||||
|
*
|
||||||
|
* @param stack the ItemStack to inspect
|
||||||
|
* @return lock resistance value for this specific item
|
||||||
|
*/
|
||||||
|
public static int getItemLockResistance(ItemStack stack) {
|
||||||
|
LockableComponent comp = getComponent(
|
||||||
|
stack,
|
||||||
|
ComponentType.LOCKABLE,
|
||||||
|
LockableComponent.class
|
||||||
|
);
|
||||||
|
if (comp != null) {
|
||||||
|
return comp.getLockResistance();
|
||||||
|
}
|
||||||
|
return com.tiedup.remake.core.SettingsAccessor.getPadlockResistance(
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stack-aware padlock attachment check for data-driven items.
|
||||||
|
* Returns false for organic items (slime, vine, web, tape) that have
|
||||||
|
* {@code can_attach_padlock: false} in their JSON definition.
|
||||||
|
*/
|
||||||
|
public static boolean canAttachPadlockTo(ItemStack stack) {
|
||||||
|
DataDrivenItemDefinition def = DataDrivenItemRegistry.get(stack);
|
||||||
|
if (def != null) {
|
||||||
|
return def.canAttachPadlock();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// ===== FACTORY =====
|
// ===== FACTORY =====
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package com.tiedup.remake.v2.bondage.datadriven;
|
package com.tiedup.remake.v2.bondage.datadriven;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
import com.tiedup.remake.v2.BodyRegionV2;
|
import com.tiedup.remake.v2.BodyRegionV2;
|
||||||
|
import com.tiedup.remake.v2.bondage.component.ComponentType;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
@@ -44,6 +46,9 @@ public record DataDrivenItemDefinition(
|
|||||||
/** Body regions this item blocks. Defaults to occupiedRegions if not specified. */
|
/** Body regions this item blocks. Defaults to occupiedRegions if not specified. */
|
||||||
Set<BodyRegionV2> blockedRegions,
|
Set<BodyRegionV2> blockedRegions,
|
||||||
|
|
||||||
|
/** Optional pose type identifier (e.g., "STANDARD", "STRAITJACKET", "DOG"). */
|
||||||
|
@Nullable String poseType,
|
||||||
|
|
||||||
/** Pose priority for conflict resolution. Higher wins. */
|
/** Pose priority for conflict resolution. Higher wins. */
|
||||||
int posePriority,
|
int posePriority,
|
||||||
|
|
||||||
@@ -53,6 +58,9 @@ public record DataDrivenItemDefinition(
|
|||||||
/** Whether this item can be locked with a padlock. */
|
/** Whether this item can be locked with a padlock. */
|
||||||
boolean lockable,
|
boolean lockable,
|
||||||
|
|
||||||
|
/** Whether a padlock can be attached to this item. False for organic items (slime, vine, web, tape). */
|
||||||
|
boolean canAttachPadlock,
|
||||||
|
|
||||||
/** Whether this item supports color variants. */
|
/** Whether this item supports color variants. */
|
||||||
boolean supportsColor,
|
boolean supportsColor,
|
||||||
|
|
||||||
@@ -97,5 +105,19 @@ public record DataDrivenItemDefinition(
|
|||||||
*
|
*
|
||||||
* <p>This field is required in the JSON definition. Never null, never empty.</p>
|
* <p>This field is required in the JSON definition. Never null, never empty.</p>
|
||||||
*/
|
*/
|
||||||
Map<String, Set<String>> animationBones
|
Map<String, Set<String>> animationBones,
|
||||||
) {}
|
|
||||||
|
/** Raw component configs from JSON, keyed by ComponentType. */
|
||||||
|
Map<ComponentType, JsonObject> componentConfigs
|
||||||
|
) {
|
||||||
|
|
||||||
|
/** Compact constructor: default null componentConfigs to empty immutable map. */
|
||||||
|
public DataDrivenItemDefinition {
|
||||||
|
if (componentConfigs == null) componentConfigs = Map.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Check whether this definition declares a given component type. */
|
||||||
|
public boolean hasComponent(ComponentType type) {
|
||||||
|
return componentConfigs.containsKey(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,12 +5,14 @@ import com.google.gson.JsonElement;
|
|||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import com.google.gson.JsonParser;
|
import com.google.gson.JsonParser;
|
||||||
import com.tiedup.remake.v2.BodyRegionV2;
|
import com.tiedup.remake.v2.BodyRegionV2;
|
||||||
|
import com.tiedup.remake.v2.bondage.component.ComponentType;
|
||||||
import com.tiedup.remake.v2.bondage.movement.MovementModifier;
|
import com.tiedup.remake.v2.bondage.movement.MovementModifier;
|
||||||
import com.tiedup.remake.v2.bondage.movement.MovementStyle;
|
import com.tiedup.remake.v2.bondage.movement.MovementStyle;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.EnumMap;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
@@ -186,6 +188,9 @@ public final class DataDrivenItemParser {
|
|||||||
blockedRegions = occupiedRegions;
|
blockedRegions = occupiedRegions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Optional: pose_type (e.g., "STANDARD", "STRAITJACKET", "DOG")
|
||||||
|
String poseType = getStringOrNull(root, "pose_type");
|
||||||
|
|
||||||
// Optional: pose_priority (default 0)
|
// Optional: pose_priority (default 0)
|
||||||
int posePriority = getIntOrDefault(root, "pose_priority", 0);
|
int posePriority = getIntOrDefault(root, "pose_priority", 0);
|
||||||
|
|
||||||
@@ -195,6 +200,9 @@ public final class DataDrivenItemParser {
|
|||||||
// Optional: lockable (default true)
|
// Optional: lockable (default true)
|
||||||
boolean lockable = getBooleanOrDefault(root, "lockable", true);
|
boolean lockable = getBooleanOrDefault(root, "lockable", true);
|
||||||
|
|
||||||
|
// Optional: can_attach_padlock (default true). False for organic items.
|
||||||
|
boolean canAttachPadlock = getBooleanOrDefault(root, "can_attach_padlock", true);
|
||||||
|
|
||||||
// Optional: supports_color (default false)
|
// Optional: supports_color (default false)
|
||||||
boolean supportsColor = getBooleanOrDefault(
|
boolean supportsColor = getBooleanOrDefault(
|
||||||
root,
|
root,
|
||||||
@@ -266,6 +274,39 @@ public final class DataDrivenItemParser {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Optional: components (per-component JSON configs)
|
||||||
|
Map<ComponentType, JsonObject> componentConfigs =
|
||||||
|
new EnumMap<>(ComponentType.class);
|
||||||
|
if (root.has("components")) {
|
||||||
|
JsonObject componentsObj = root.getAsJsonObject("components");
|
||||||
|
for (Map.Entry<String, JsonElement> entry :
|
||||||
|
componentsObj.entrySet()) {
|
||||||
|
ComponentType compType = ComponentType.fromKey(
|
||||||
|
entry.getKey()
|
||||||
|
);
|
||||||
|
if (compType != null) {
|
||||||
|
JsonObject config;
|
||||||
|
if (entry.getValue().isJsonObject()) {
|
||||||
|
config = entry.getValue().getAsJsonObject().deepCopy();
|
||||||
|
} else {
|
||||||
|
LOGGER.warn(
|
||||||
|
"[DataDrivenItemParser] Component '{}' in item '{}' has non-object config, using defaults",
|
||||||
|
entry.getKey(),
|
||||||
|
fileId
|
||||||
|
);
|
||||||
|
config = new JsonObject();
|
||||||
|
}
|
||||||
|
componentConfigs.put(compType, config);
|
||||||
|
} else {
|
||||||
|
LOGGER.warn(
|
||||||
|
"[DataDrivenItemParser] Unknown component type '{}' in item '{}'",
|
||||||
|
entry.getKey(),
|
||||||
|
fileId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Build the item ID from the file path
|
// Build the item ID from the file path
|
||||||
// fileId is like "tiedup:tiedup_items/leather_armbinder.json"
|
// fileId is like "tiedup:tiedup_items/leather_armbinder.json"
|
||||||
// We want "tiedup:leather_armbinder"
|
// We want "tiedup:leather_armbinder"
|
||||||
@@ -293,16 +334,19 @@ public final class DataDrivenItemParser {
|
|||||||
animationSource,
|
animationSource,
|
||||||
occupiedRegions,
|
occupiedRegions,
|
||||||
blockedRegions,
|
blockedRegions,
|
||||||
|
poseType,
|
||||||
posePriority,
|
posePriority,
|
||||||
escapeDifficulty,
|
escapeDifficulty,
|
||||||
lockable,
|
lockable,
|
||||||
|
canAttachPadlock,
|
||||||
supportsColor,
|
supportsColor,
|
||||||
tintChannels,
|
tintChannels,
|
||||||
icon,
|
icon,
|
||||||
movementStyle,
|
movementStyle,
|
||||||
movementModifier,
|
movementModifier,
|
||||||
creator,
|
creator,
|
||||||
animationBones
|
animationBones,
|
||||||
|
componentConfigs
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
package com.tiedup.remake.v2.bondage.datadriven;
|
package com.tiedup.remake.v2.bondage.datadriven;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.tiedup.remake.v2.bondage.component.ComponentHolder;
|
||||||
|
import com.tiedup.remake.v2.bondage.component.ComponentType;
|
||||||
|
import com.tiedup.remake.v2.bondage.component.IItemComponent;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.EnumMap;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import net.minecraft.nbt.CompoundTag;
|
import net.minecraft.nbt.CompoundTag;
|
||||||
@@ -13,9 +18,9 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
* Thread-safe registry for data-driven bondage item definitions.
|
* Thread-safe registry for data-driven bondage item definitions.
|
||||||
*
|
*
|
||||||
* <p>Populated by the reload listener that scans {@code tiedup_items/} JSON files.
|
* <p>Populated by the reload listener that scans {@code tiedup_items/} JSON files.
|
||||||
* Uses volatile atomic swap (same pattern as {@link
|
* Uses a single volatile snapshot reference to ensure readers always see a
|
||||||
* com.tiedup.remake.client.animation.context.ContextGlbRegistry}) to ensure
|
* consistent pair of definitions + component holders. This prevents torn reads
|
||||||
* the render thread always sees a consistent snapshot.</p>
|
* where one map is updated but the other is stale.</p>
|
||||||
*
|
*
|
||||||
* <p>Lookup methods accept either a {@link ResourceLocation} ID directly
|
* <p>Lookup methods accept either a {@link ResourceLocation} ID directly
|
||||||
* or an {@link ItemStack} (reads the {@code tiedup_item_id} NBT tag).</p>
|
* or an {@link ItemStack} (reads the {@code tiedup_item_id} NBT tag).</p>
|
||||||
@@ -26,13 +31,25 @@ public final class DataDrivenItemRegistry {
|
|||||||
public static final String NBT_ITEM_ID = "tiedup_item_id";
|
public static final String NBT_ITEM_ID = "tiedup_item_id";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Volatile reference to an unmodifiable map. Reload builds a new map
|
* Immutable snapshot combining definitions and their component holders.
|
||||||
* and swaps atomically; consumer threads always see a consistent snapshot.
|
* Swapped atomically via a single volatile write to prevent torn reads.
|
||||||
*/
|
*/
|
||||||
private static volatile Map<
|
private record RegistrySnapshot(
|
||||||
ResourceLocation,
|
Map<ResourceLocation, DataDrivenItemDefinition> definitions,
|
||||||
DataDrivenItemDefinition
|
Map<ResourceLocation, ComponentHolder> holders
|
||||||
> DEFINITIONS = Map.of();
|
) {
|
||||||
|
static final RegistrySnapshot EMPTY = new RegistrySnapshot(Map.of(), Map.of());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Single volatile reference to the current registry state.
|
||||||
|
* All read methods capture this reference ONCE at the start to ensure
|
||||||
|
* consistency within a single call.
|
||||||
|
*/
|
||||||
|
private static volatile RegistrySnapshot SNAPSHOT = RegistrySnapshot.EMPTY;
|
||||||
|
|
||||||
|
/** Guards read-then-write sequences in {@link #reload} and {@link #mergeAll}. */
|
||||||
|
private static final Object RELOAD_LOCK = new Object();
|
||||||
|
|
||||||
private DataDrivenItemRegistry() {}
|
private DataDrivenItemRegistry() {}
|
||||||
|
|
||||||
@@ -45,7 +62,11 @@ public final class DataDrivenItemRegistry {
|
|||||||
public static void reload(
|
public static void reload(
|
||||||
Map<ResourceLocation, DataDrivenItemDefinition> newDefs
|
Map<ResourceLocation, DataDrivenItemDefinition> newDefs
|
||||||
) {
|
) {
|
||||||
DEFINITIONS = Collections.unmodifiableMap(new HashMap<>(newDefs));
|
synchronized (RELOAD_LOCK) {
|
||||||
|
Map<ResourceLocation, DataDrivenItemDefinition> defs =
|
||||||
|
Collections.unmodifiableMap(new HashMap<>(newDefs));
|
||||||
|
SNAPSHOT = new RegistrySnapshot(defs, buildComponentHolders(defs));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -54,18 +75,23 @@ public final class DataDrivenItemRegistry {
|
|||||||
* <p>On an integrated server, both the client (assets/) and server (data/) reload
|
* <p>On an integrated server, both the client (assets/) and server (data/) reload
|
||||||
* listeners populate this registry. Using {@link #reload} would cause the second
|
* listeners populate this registry. Using {@link #reload} would cause the second
|
||||||
* listener to overwrite the first's definitions. This method builds a new map
|
* listener to overwrite the first's definitions. This method builds a new map
|
||||||
* from the existing snapshot + the new entries, then swaps atomically.</p>
|
* from the existing snapshot + the new entries, then swaps atomically as a
|
||||||
|
* single snapshot to prevent torn reads.</p>
|
||||||
*
|
*
|
||||||
* @param newDefs the definitions to merge (will overwrite existing entries with same key)
|
* @param newDefs the definitions to merge (will overwrite existing entries with same key)
|
||||||
*/
|
*/
|
||||||
public static void mergeAll(
|
public static void mergeAll(
|
||||||
Map<ResourceLocation, DataDrivenItemDefinition> newDefs
|
Map<ResourceLocation, DataDrivenItemDefinition> newDefs
|
||||||
) {
|
) {
|
||||||
Map<ResourceLocation, DataDrivenItemDefinition> merged = new HashMap<>(
|
synchronized (RELOAD_LOCK) {
|
||||||
DEFINITIONS
|
Map<ResourceLocation, DataDrivenItemDefinition> merged = new HashMap<>(
|
||||||
);
|
SNAPSHOT.definitions
|
||||||
merged.putAll(newDefs);
|
);
|
||||||
DEFINITIONS = Collections.unmodifiableMap(merged);
|
merged.putAll(newDefs);
|
||||||
|
Map<ResourceLocation, DataDrivenItemDefinition> defs =
|
||||||
|
Collections.unmodifiableMap(merged);
|
||||||
|
SNAPSHOT = new RegistrySnapshot(defs, buildComponentHolders(defs));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -76,7 +102,8 @@ public final class DataDrivenItemRegistry {
|
|||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
public static DataDrivenItemDefinition get(ResourceLocation id) {
|
public static DataDrivenItemDefinition get(ResourceLocation id) {
|
||||||
return DEFINITIONS.get(id);
|
RegistrySnapshot snap = SNAPSHOT;
|
||||||
|
return snap.definitions.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -94,7 +121,8 @@ public final class DataDrivenItemRegistry {
|
|||||||
tag.getString(NBT_ITEM_ID)
|
tag.getString(NBT_ITEM_ID)
|
||||||
);
|
);
|
||||||
if (id == null) return null;
|
if (id == null) return null;
|
||||||
return DEFINITIONS.get(id);
|
RegistrySnapshot snap = SNAPSHOT;
|
||||||
|
return snap.definitions.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -103,13 +131,85 @@ public final class DataDrivenItemRegistry {
|
|||||||
* @return unmodifiable collection of all definitions
|
* @return unmodifiable collection of all definitions
|
||||||
*/
|
*/
|
||||||
public static Collection<DataDrivenItemDefinition> getAll() {
|
public static Collection<DataDrivenItemDefinition> getAll() {
|
||||||
return DEFINITIONS.values();
|
RegistrySnapshot snap = SNAPSHOT;
|
||||||
|
return snap.definitions.values();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear all definitions. Called on world unload or for testing.
|
* Clear all definitions. Called on world unload or for testing.
|
||||||
*/
|
*/
|
||||||
public static void clear() {
|
public static void clear() {
|
||||||
DEFINITIONS = Map.of();
|
SNAPSHOT = RegistrySnapshot.EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== COMPONENT HOLDERS =====
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the ComponentHolder for a data-driven item stack.
|
||||||
|
*
|
||||||
|
* <p>Captures the snapshot reference once to ensure consistent reads
|
||||||
|
* between the definition lookup and the holder lookup.</p>
|
||||||
|
*
|
||||||
|
* @param stack the ItemStack to inspect
|
||||||
|
* @return the holder, or null if the stack is not data-driven or has no definition
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public static ComponentHolder getComponents(ItemStack stack) {
|
||||||
|
if (stack.isEmpty()) return null;
|
||||||
|
CompoundTag tag = stack.getTag();
|
||||||
|
if (tag == null || !tag.contains(NBT_ITEM_ID)) return null;
|
||||||
|
ResourceLocation id = ResourceLocation.tryParse(
|
||||||
|
tag.getString(NBT_ITEM_ID)
|
||||||
|
);
|
||||||
|
if (id == null) return null;
|
||||||
|
// Capture snapshot once to ensure definition and holder come from
|
||||||
|
// the same atomic snapshot, preventing torn reads.
|
||||||
|
RegistrySnapshot snap = SNAPSHOT;
|
||||||
|
DataDrivenItemDefinition def = snap.definitions.get(id);
|
||||||
|
if (def == null) return null;
|
||||||
|
return snap.holders.get(def.id());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the ComponentHolder for a data-driven item by its definition ID.
|
||||||
|
*
|
||||||
|
* @param id the definition ID
|
||||||
|
* @return the holder, or null if not found
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public static ComponentHolder getComponents(ResourceLocation id) {
|
||||||
|
RegistrySnapshot snap = SNAPSHOT;
|
||||||
|
return snap.holders.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build component holders from a definitions map.
|
||||||
|
* Each definition's raw componentConfigs are instantiated via
|
||||||
|
* {@link ComponentType#create(JsonObject)}.
|
||||||
|
*/
|
||||||
|
private static Map<ResourceLocation, ComponentHolder> buildComponentHolders(
|
||||||
|
Map<ResourceLocation, DataDrivenItemDefinition> definitions
|
||||||
|
) {
|
||||||
|
Map<ResourceLocation, ComponentHolder> holders = new HashMap<>();
|
||||||
|
for (Map.Entry<ResourceLocation, DataDrivenItemDefinition> entry :
|
||||||
|
definitions.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(),
|
||||||
|
components.isEmpty()
|
||||||
|
? ComponentHolder.EMPTY
|
||||||
|
: new ComponentHolder(components)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Collections.unmodifiableMap(holders);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
31
src/main/resources/assets/tiedup/tiedup_items/armbinder.json
Normal file
31
src/main/resources/assets/tiedup/tiedup_items/armbinder.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Armbinder",
|
||||||
|
"translation_key": "item.tiedup.armbinder",
|
||||||
|
"model": "tiedup:models/gltf/v2/binds/armbinder.glb",
|
||||||
|
"regions": [
|
||||||
|
"ARMS"
|
||||||
|
],
|
||||||
|
"pose_type": "STANDARD",
|
||||||
|
"pose_priority": 30,
|
||||||
|
"escape_difficulty": 100,
|
||||||
|
"lockable": true,
|
||||||
|
"can_attach_padlock": true,
|
||||||
|
"supports_color": false,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
],
|
||||||
|
"struggle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "armbinder"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Baguette Gag",
|
||||||
|
"translation_key": "item.tiedup.baguette_gag",
|
||||||
|
"model": "tiedup:models/gltf/v2/gags/baguette_gag.glb",
|
||||||
|
"regions": [
|
||||||
|
"MOUTH"
|
||||||
|
],
|
||||||
|
"pose_priority": 10,
|
||||||
|
"escape_difficulty": 50,
|
||||||
|
"lockable": true,
|
||||||
|
"can_attach_padlock": true,
|
||||||
|
"supports_color": false,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"head"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "gag"
|
||||||
|
},
|
||||||
|
"gagging": {
|
||||||
|
"material": "baguette"
|
||||||
|
},
|
||||||
|
"adjustable": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/main/resources/assets/tiedup/tiedup_items/ball_gag.json
Normal file
29
src/main/resources/assets/tiedup/tiedup_items/ball_gag.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Ball Gag",
|
||||||
|
"translation_key": "item.tiedup.ball_gag",
|
||||||
|
"model": "tiedup:models/gltf/v2/gags/ball_gag.glb",
|
||||||
|
"regions": [
|
||||||
|
"MOUTH"
|
||||||
|
],
|
||||||
|
"pose_priority": 10,
|
||||||
|
"escape_difficulty": 50,
|
||||||
|
"lockable": true,
|
||||||
|
"can_attach_padlock": true,
|
||||||
|
"supports_color": true,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"head"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "gag"
|
||||||
|
},
|
||||||
|
"gagging": {
|
||||||
|
"material": "ball"
|
||||||
|
},
|
||||||
|
"adjustable": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Ball Gag 3D",
|
||||||
|
"translation_key": "item.tiedup.ball_gag_3d",
|
||||||
|
"model": "tiedup:models/gltf/v2/combos/ball_gag_3d.glb",
|
||||||
|
"regions": [
|
||||||
|
"MOUTH"
|
||||||
|
],
|
||||||
|
"pose_priority": 10,
|
||||||
|
"escape_difficulty": 50,
|
||||||
|
"lockable": true,
|
||||||
|
"supports_color": true,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"head"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "gag"
|
||||||
|
},
|
||||||
|
"gagging": {
|
||||||
|
"material": "ball"
|
||||||
|
},
|
||||||
|
"adjustable": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Ball Gag Strap",
|
||||||
|
"translation_key": "item.tiedup.ball_gag_strap",
|
||||||
|
"model": "tiedup:models/gltf/v2/gags/ball_gag_strap.glb",
|
||||||
|
"regions": [
|
||||||
|
"MOUTH"
|
||||||
|
],
|
||||||
|
"pose_priority": 10,
|
||||||
|
"escape_difficulty": 50,
|
||||||
|
"lockable": true,
|
||||||
|
"can_attach_padlock": true,
|
||||||
|
"supports_color": true,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"head"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "gag"
|
||||||
|
},
|
||||||
|
"gagging": {
|
||||||
|
"material": "ball"
|
||||||
|
},
|
||||||
|
"adjustable": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Beam Cuffs",
|
||||||
|
"translation_key": "item.tiedup.beam_cuffs",
|
||||||
|
"model": "tiedup:models/gltf/v2/binds/beam_cuffs.glb",
|
||||||
|
"regions": [
|
||||||
|
"ARMS"
|
||||||
|
],
|
||||||
|
"pose_type": "STANDARD",
|
||||||
|
"pose_priority": 30,
|
||||||
|
"escape_difficulty": 100,
|
||||||
|
"lockable": true,
|
||||||
|
"can_attach_padlock": true,
|
||||||
|
"supports_color": false,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
],
|
||||||
|
"struggle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "chain"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Beam Panel Gag",
|
||||||
|
"translation_key": "item.tiedup.beam_panel_gag",
|
||||||
|
"model": "tiedup:models/gltf/v2/gags/beam_panel_gag.glb",
|
||||||
|
"regions": [
|
||||||
|
"MOUTH"
|
||||||
|
],
|
||||||
|
"pose_priority": 10,
|
||||||
|
"escape_difficulty": 50,
|
||||||
|
"lockable": true,
|
||||||
|
"can_attach_padlock": true,
|
||||||
|
"supports_color": false,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"head"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "gag"
|
||||||
|
},
|
||||||
|
"gagging": {
|
||||||
|
"material": "panel"
|
||||||
|
},
|
||||||
|
"adjustable": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/main/resources/assets/tiedup/tiedup_items/bite_gag.json
Normal file
29
src/main/resources/assets/tiedup/tiedup_items/bite_gag.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Bite Gag",
|
||||||
|
"translation_key": "item.tiedup.bite_gag",
|
||||||
|
"model": "tiedup:models/gltf/v2/gags/bite_gag.glb",
|
||||||
|
"regions": [
|
||||||
|
"MOUTH"
|
||||||
|
],
|
||||||
|
"pose_priority": 10,
|
||||||
|
"escape_difficulty": 50,
|
||||||
|
"lockable": true,
|
||||||
|
"can_attach_padlock": true,
|
||||||
|
"supports_color": false,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"head"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "gag"
|
||||||
|
},
|
||||||
|
"gagging": {
|
||||||
|
"material": "bite"
|
||||||
|
},
|
||||||
|
"adjustable": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Blindfold Mask",
|
||||||
|
"translation_key": "item.tiedup.blindfold_mask",
|
||||||
|
"model": "tiedup:models/gltf/v2/blindfolds/blindfold_mask.glb",
|
||||||
|
"regions": [
|
||||||
|
"EYES"
|
||||||
|
],
|
||||||
|
"pose_priority": 5,
|
||||||
|
"escape_difficulty": 30,
|
||||||
|
"lockable": true,
|
||||||
|
"supports_color": true,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"head"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "blindfold"
|
||||||
|
},
|
||||||
|
"blinding": {},
|
||||||
|
"adjustable": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/main/resources/assets/tiedup/tiedup_items/chain.json
Normal file
31
src/main/resources/assets/tiedup/tiedup_items/chain.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Chains",
|
||||||
|
"translation_key": "item.tiedup.chain",
|
||||||
|
"model": "tiedup:models/gltf/v2/binds/chain.glb",
|
||||||
|
"regions": [
|
||||||
|
"ARMS"
|
||||||
|
],
|
||||||
|
"pose_type": "STANDARD",
|
||||||
|
"pose_priority": 30,
|
||||||
|
"escape_difficulty": 100,
|
||||||
|
"lockable": true,
|
||||||
|
"can_attach_padlock": true,
|
||||||
|
"supports_color": false,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
],
|
||||||
|
"struggle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "chain"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Chain Panel Gag",
|
||||||
|
"translation_key": "item.tiedup.chain_panel_gag",
|
||||||
|
"model": "tiedup:models/gltf/v2/gags/chain_panel_gag.glb",
|
||||||
|
"regions": [
|
||||||
|
"MOUTH"
|
||||||
|
],
|
||||||
|
"pose_priority": 10,
|
||||||
|
"escape_difficulty": 50,
|
||||||
|
"lockable": true,
|
||||||
|
"can_attach_padlock": true,
|
||||||
|
"supports_color": false,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"head"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "gag"
|
||||||
|
},
|
||||||
|
"gagging": {
|
||||||
|
"material": "panel"
|
||||||
|
},
|
||||||
|
"adjustable": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"regions": [
|
||||||
|
"NECK"
|
||||||
|
],
|
||||||
|
"pose_priority": 5,
|
||||||
|
"escape_difficulty": 80,
|
||||||
|
"lockable": true,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"body"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"display_name": "Choke Collar",
|
||||||
|
"translation_key": "item.tiedup.choke_collar",
|
||||||
|
"model": "tiedup:models/gltf/v2/collars/choke_collar.glb",
|
||||||
|
"components": {
|
||||||
|
"ownership": {},
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "collar"
|
||||||
|
},
|
||||||
|
"choking": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Blindfold",
|
||||||
|
"translation_key": "item.tiedup.classic_blindfold",
|
||||||
|
"model": "tiedup:models/gltf/v2/blindfolds/classic_blindfold.glb",
|
||||||
|
"regions": [
|
||||||
|
"EYES"
|
||||||
|
],
|
||||||
|
"pose_priority": 5,
|
||||||
|
"escape_difficulty": 30,
|
||||||
|
"lockable": true,
|
||||||
|
"supports_color": true,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"head"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "blindfold"
|
||||||
|
},
|
||||||
|
"blinding": {},
|
||||||
|
"adjustable": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"regions": [
|
||||||
|
"NECK"
|
||||||
|
],
|
||||||
|
"pose_priority": 5,
|
||||||
|
"escape_difficulty": 80,
|
||||||
|
"lockable": true,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"body"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"display_name": "Classic Collar",
|
||||||
|
"translation_key": "item.tiedup.classic_collar",
|
||||||
|
"model": "tiedup:models/gltf/v2/collars/classic_collar.glb",
|
||||||
|
"components": {
|
||||||
|
"ownership": {},
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "collar"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Earplugs",
|
||||||
|
"translation_key": "item.tiedup.classic_earplugs",
|
||||||
|
"model": "tiedup:models/gltf/v2/earplugs/classic_earplugs.glb",
|
||||||
|
"regions": [
|
||||||
|
"EARS"
|
||||||
|
],
|
||||||
|
"pose_priority": 5,
|
||||||
|
"escape_difficulty": 20,
|
||||||
|
"lockable": true,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"head"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "blindfold"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Cleave Gag",
|
||||||
|
"translation_key": "item.tiedup.cleave_gag",
|
||||||
|
"model": "tiedup:models/gltf/v2/gags/cleave_gag.glb",
|
||||||
|
"regions": [
|
||||||
|
"MOUTH"
|
||||||
|
],
|
||||||
|
"pose_priority": 10,
|
||||||
|
"escape_difficulty": 50,
|
||||||
|
"lockable": true,
|
||||||
|
"can_attach_padlock": true,
|
||||||
|
"supports_color": true,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"head"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "gag"
|
||||||
|
},
|
||||||
|
"gagging": {
|
||||||
|
"material": "cloth"
|
||||||
|
},
|
||||||
|
"adjustable": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/main/resources/assets/tiedup/tiedup_items/cloth_gag.json
Normal file
29
src/main/resources/assets/tiedup/tiedup_items/cloth_gag.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Cloth Gag",
|
||||||
|
"translation_key": "item.tiedup.cloth_gag",
|
||||||
|
"model": "tiedup:models/gltf/v2/gags/cloth_gag.glb",
|
||||||
|
"regions": [
|
||||||
|
"MOUTH"
|
||||||
|
],
|
||||||
|
"pose_priority": 10,
|
||||||
|
"escape_difficulty": 50,
|
||||||
|
"lockable": true,
|
||||||
|
"can_attach_padlock": true,
|
||||||
|
"supports_color": true,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"head"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "gag"
|
||||||
|
},
|
||||||
|
"gagging": {
|
||||||
|
"material": "cloth"
|
||||||
|
},
|
||||||
|
"adjustable": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
32
src/main/resources/assets/tiedup/tiedup_items/dogbinder.json
Normal file
32
src/main/resources/assets/tiedup/tiedup_items/dogbinder.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Dogbinder",
|
||||||
|
"translation_key": "item.tiedup.dogbinder",
|
||||||
|
"model": "tiedup:models/gltf/v2/binds/dogbinder.glb",
|
||||||
|
"regions": [
|
||||||
|
"ARMS"
|
||||||
|
],
|
||||||
|
"pose_type": "DOG",
|
||||||
|
"pose_priority": 30,
|
||||||
|
"escape_difficulty": 100,
|
||||||
|
"lockable": true,
|
||||||
|
"can_attach_padlock": true,
|
||||||
|
"supports_color": false,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
],
|
||||||
|
"struggle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "armbinder"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"movement_style": "CRAWL"
|
||||||
|
}
|
||||||
30
src/main/resources/assets/tiedup/tiedup_items/duct_tape.json
Normal file
30
src/main/resources/assets/tiedup/tiedup_items/duct_tape.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Duct Tape",
|
||||||
|
"translation_key": "item.tiedup.duct_tape",
|
||||||
|
"model": "tiedup:models/gltf/v2/binds/duct_tape.glb",
|
||||||
|
"regions": [
|
||||||
|
"ARMS"
|
||||||
|
],
|
||||||
|
"pose_type": "STANDARD",
|
||||||
|
"pose_priority": 30,
|
||||||
|
"escape_difficulty": 100,
|
||||||
|
"lockable": false,
|
||||||
|
"can_attach_padlock": false,
|
||||||
|
"supports_color": true,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
],
|
||||||
|
"struggle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"resistance": {
|
||||||
|
"id": "tape"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"regions": [
|
||||||
|
"NECK"
|
||||||
|
],
|
||||||
|
"pose_priority": 5,
|
||||||
|
"escape_difficulty": 80,
|
||||||
|
"lockable": true,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"body"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"display_name": "GPS Collar",
|
||||||
|
"translation_key": "item.tiedup.gps_collar",
|
||||||
|
"model": "tiedup:models/gltf/v2/collars/gps_collar.glb",
|
||||||
|
"components": {
|
||||||
|
"ownership": {},
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "collar"
|
||||||
|
},
|
||||||
|
"gps": {
|
||||||
|
"safe_zone_radius": 50
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
32
src/main/resources/assets/tiedup/tiedup_items/hood.json
Normal file
32
src/main/resources/assets/tiedup/tiedup_items/hood.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Hood",
|
||||||
|
"translation_key": "item.tiedup.hood",
|
||||||
|
"model": "tiedup:models/gltf/v2/combos/hood.glb",
|
||||||
|
"regions": [
|
||||||
|
"EYES"
|
||||||
|
],
|
||||||
|
"blocked_regions": [
|
||||||
|
"EYES",
|
||||||
|
"EARS",
|
||||||
|
"MOUTH"
|
||||||
|
],
|
||||||
|
"pose_priority": 10,
|
||||||
|
"escape_difficulty": 40,
|
||||||
|
"lockable": true,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"head"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "blindfold"
|
||||||
|
},
|
||||||
|
"blinding": {},
|
||||||
|
"gagging": {
|
||||||
|
"material": "stuffed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/main/resources/assets/tiedup/tiedup_items/latex_gag.json
Normal file
29
src/main/resources/assets/tiedup/tiedup_items/latex_gag.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Latex Gag",
|
||||||
|
"translation_key": "item.tiedup.latex_gag",
|
||||||
|
"model": "tiedup:models/gltf/v2/gags/latex_gag.glb",
|
||||||
|
"regions": [
|
||||||
|
"MOUTH"
|
||||||
|
],
|
||||||
|
"pose_priority": 10,
|
||||||
|
"escape_difficulty": 50,
|
||||||
|
"lockable": true,
|
||||||
|
"can_attach_padlock": true,
|
||||||
|
"supports_color": false,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"head"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "gag"
|
||||||
|
},
|
||||||
|
"gagging": {
|
||||||
|
"material": "latex"
|
||||||
|
},
|
||||||
|
"adjustable": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Latex Sack",
|
||||||
|
"translation_key": "item.tiedup.latex_sack",
|
||||||
|
"model": "tiedup:models/gltf/v2/binds/latex_sack.glb",
|
||||||
|
"regions": [
|
||||||
|
"ARMS"
|
||||||
|
],
|
||||||
|
"pose_type": "LATEX_SACK",
|
||||||
|
"pose_priority": 30,
|
||||||
|
"escape_difficulty": 100,
|
||||||
|
"lockable": true,
|
||||||
|
"can_attach_padlock": true,
|
||||||
|
"supports_color": false,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
],
|
||||||
|
"struggle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "latex_sack"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Leather Mittens",
|
||||||
|
"translation_key": "item.tiedup.leather_mittens",
|
||||||
|
"model": "tiedup:models/gltf/v2/mittens/leather_mittens.glb",
|
||||||
|
"regions": [
|
||||||
|
"HANDS"
|
||||||
|
],
|
||||||
|
"pose_priority": 5,
|
||||||
|
"escape_difficulty": 20,
|
||||||
|
"lockable": true,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Leather Straps",
|
||||||
|
"translation_key": "item.tiedup.leather_straps",
|
||||||
|
"model": "tiedup:models/gltf/v2/binds/leather_straps.glb",
|
||||||
|
"regions": [
|
||||||
|
"ARMS"
|
||||||
|
],
|
||||||
|
"pose_type": "STANDARD",
|
||||||
|
"pose_priority": 30,
|
||||||
|
"escape_difficulty": 100,
|
||||||
|
"lockable": true,
|
||||||
|
"can_attach_padlock": true,
|
||||||
|
"supports_color": false,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
],
|
||||||
|
"struggle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "armbinder"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Medical Gag",
|
||||||
|
"translation_key": "item.tiedup.medical_gag",
|
||||||
|
"model": "tiedup:models/gltf/v2/combos/medical_gag.glb",
|
||||||
|
"regions": [
|
||||||
|
"MOUTH"
|
||||||
|
],
|
||||||
|
"pose_priority": 10,
|
||||||
|
"escape_difficulty": 50,
|
||||||
|
"lockable": true,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"head"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "gag"
|
||||||
|
},
|
||||||
|
"gagging": {
|
||||||
|
"material": "panel"
|
||||||
|
},
|
||||||
|
"blinding": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Medical Straps",
|
||||||
|
"translation_key": "item.tiedup.medical_straps",
|
||||||
|
"model": "tiedup:models/gltf/v2/binds/medical_straps.glb",
|
||||||
|
"regions": [
|
||||||
|
"ARMS"
|
||||||
|
],
|
||||||
|
"pose_type": "STANDARD",
|
||||||
|
"pose_priority": 30,
|
||||||
|
"escape_difficulty": 100,
|
||||||
|
"lockable": true,
|
||||||
|
"can_attach_padlock": true,
|
||||||
|
"supports_color": false,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
],
|
||||||
|
"struggle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "armbinder"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/main/resources/assets/tiedup/tiedup_items/panel_gag.json
Normal file
29
src/main/resources/assets/tiedup/tiedup_items/panel_gag.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Panel Gag",
|
||||||
|
"translation_key": "item.tiedup.panel_gag",
|
||||||
|
"model": "tiedup:models/gltf/v2/gags/panel_gag.glb",
|
||||||
|
"regions": [
|
||||||
|
"MOUTH"
|
||||||
|
],
|
||||||
|
"pose_priority": 10,
|
||||||
|
"escape_difficulty": 50,
|
||||||
|
"lockable": true,
|
||||||
|
"can_attach_padlock": true,
|
||||||
|
"supports_color": false,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"head"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "gag"
|
||||||
|
},
|
||||||
|
"gagging": {
|
||||||
|
"material": "panel"
|
||||||
|
},
|
||||||
|
"adjustable": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/main/resources/assets/tiedup/tiedup_items/ribbon.json
Normal file
31
src/main/resources/assets/tiedup/tiedup_items/ribbon.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Ribbon",
|
||||||
|
"translation_key": "item.tiedup.ribbon",
|
||||||
|
"model": "tiedup:models/gltf/v2/binds/ribbon.glb",
|
||||||
|
"regions": [
|
||||||
|
"ARMS"
|
||||||
|
],
|
||||||
|
"pose_type": "STANDARD",
|
||||||
|
"pose_priority": 30,
|
||||||
|
"escape_difficulty": 100,
|
||||||
|
"lockable": true,
|
||||||
|
"can_attach_padlock": true,
|
||||||
|
"supports_color": false,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
],
|
||||||
|
"struggle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "ribbon"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Ribbon Gag",
|
||||||
|
"translation_key": "item.tiedup.ribbon_gag",
|
||||||
|
"model": "tiedup:models/gltf/v2/gags/ribbon_gag.glb",
|
||||||
|
"regions": [
|
||||||
|
"MOUTH"
|
||||||
|
],
|
||||||
|
"pose_priority": 10,
|
||||||
|
"escape_difficulty": 50,
|
||||||
|
"lockable": true,
|
||||||
|
"can_attach_padlock": true,
|
||||||
|
"supports_color": false,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"head"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "gag"
|
||||||
|
},
|
||||||
|
"gagging": {
|
||||||
|
"material": "cloth"
|
||||||
|
},
|
||||||
|
"adjustable": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/main/resources/assets/tiedup/tiedup_items/ropes.json
Normal file
31
src/main/resources/assets/tiedup/tiedup_items/ropes.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Ropes",
|
||||||
|
"translation_key": "item.tiedup.ropes",
|
||||||
|
"model": "tiedup:models/gltf/v2/binds/ropes.glb",
|
||||||
|
"regions": [
|
||||||
|
"ARMS"
|
||||||
|
],
|
||||||
|
"pose_type": "STANDARD",
|
||||||
|
"pose_priority": 30,
|
||||||
|
"escape_difficulty": 100,
|
||||||
|
"lockable": true,
|
||||||
|
"can_attach_padlock": true,
|
||||||
|
"supports_color": true,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
],
|
||||||
|
"struggle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "rope"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/main/resources/assets/tiedup/tiedup_items/ropes_gag.json
Normal file
29
src/main/resources/assets/tiedup/tiedup_items/ropes_gag.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Rope Gag",
|
||||||
|
"translation_key": "item.tiedup.ropes_gag",
|
||||||
|
"model": "tiedup:models/gltf/v2/gags/ropes_gag.glb",
|
||||||
|
"regions": [
|
||||||
|
"MOUTH"
|
||||||
|
],
|
||||||
|
"pose_priority": 10,
|
||||||
|
"escape_difficulty": 50,
|
||||||
|
"lockable": true,
|
||||||
|
"can_attach_padlock": true,
|
||||||
|
"supports_color": true,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"head"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "gag"
|
||||||
|
},
|
||||||
|
"gagging": {
|
||||||
|
"material": "cloth"
|
||||||
|
},
|
||||||
|
"adjustable": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/main/resources/assets/tiedup/tiedup_items/shibari.json
Normal file
31
src/main/resources/assets/tiedup/tiedup_items/shibari.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Shibari",
|
||||||
|
"translation_key": "item.tiedup.shibari",
|
||||||
|
"model": "tiedup:models/gltf/v2/binds/shibari.glb",
|
||||||
|
"regions": [
|
||||||
|
"ARMS"
|
||||||
|
],
|
||||||
|
"pose_type": "STANDARD",
|
||||||
|
"pose_priority": 30,
|
||||||
|
"escape_difficulty": 100,
|
||||||
|
"lockable": true,
|
||||||
|
"can_attach_padlock": true,
|
||||||
|
"supports_color": true,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
],
|
||||||
|
"struggle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "rope"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"regions": [
|
||||||
|
"NECK"
|
||||||
|
],
|
||||||
|
"pose_priority": 5,
|
||||||
|
"escape_difficulty": 80,
|
||||||
|
"lockable": true,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"body"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"display_name": "Shock Collar",
|
||||||
|
"translation_key": "item.tiedup.shock_collar",
|
||||||
|
"model": "tiedup:models/gltf/v2/collars/shock_collar.glb",
|
||||||
|
"components": {
|
||||||
|
"ownership": {},
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "collar"
|
||||||
|
},
|
||||||
|
"shock": {
|
||||||
|
"damage": 2.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"regions": [
|
||||||
|
"NECK"
|
||||||
|
],
|
||||||
|
"pose_priority": 5,
|
||||||
|
"escape_difficulty": 80,
|
||||||
|
"lockable": true,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"body"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"display_name": "Auto Shock Collar",
|
||||||
|
"translation_key": "item.tiedup.shock_collar_auto",
|
||||||
|
"model": "tiedup:models/gltf/v2/collars/shock_collar_auto.glb",
|
||||||
|
"components": {
|
||||||
|
"ownership": {},
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "collar"
|
||||||
|
},
|
||||||
|
"shock": {
|
||||||
|
"damage": 2.0,
|
||||||
|
"auto_interval": 200
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
30
src/main/resources/assets/tiedup/tiedup_items/slime.json
Normal file
30
src/main/resources/assets/tiedup/tiedup_items/slime.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Slime Bind",
|
||||||
|
"translation_key": "item.tiedup.slime",
|
||||||
|
"model": "tiedup:models/gltf/v2/binds/slime.glb",
|
||||||
|
"regions": [
|
||||||
|
"ARMS"
|
||||||
|
],
|
||||||
|
"pose_type": "STANDARD",
|
||||||
|
"pose_priority": 30,
|
||||||
|
"escape_difficulty": 100,
|
||||||
|
"lockable": false,
|
||||||
|
"can_attach_padlock": false,
|
||||||
|
"supports_color": false,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
],
|
||||||
|
"struggle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"resistance": {
|
||||||
|
"id": "slime"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/main/resources/assets/tiedup/tiedup_items/slime_gag.json
Normal file
28
src/main/resources/assets/tiedup/tiedup_items/slime_gag.json
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Slime Gag",
|
||||||
|
"translation_key": "item.tiedup.slime_gag",
|
||||||
|
"model": "tiedup:models/gltf/v2/gags/slime_gag.glb",
|
||||||
|
"regions": [
|
||||||
|
"MOUTH"
|
||||||
|
],
|
||||||
|
"pose_priority": 10,
|
||||||
|
"escape_difficulty": 50,
|
||||||
|
"lockable": false,
|
||||||
|
"can_attach_padlock": false,
|
||||||
|
"supports_color": false,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"head"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"resistance": {
|
||||||
|
"id": "gag"
|
||||||
|
},
|
||||||
|
"gagging": {
|
||||||
|
"material": "stuffed"
|
||||||
|
},
|
||||||
|
"adjustable": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Sponge Gag",
|
||||||
|
"translation_key": "item.tiedup.sponge_gag",
|
||||||
|
"model": "tiedup:models/gltf/v2/gags/sponge_gag.glb",
|
||||||
|
"regions": [
|
||||||
|
"MOUTH"
|
||||||
|
],
|
||||||
|
"pose_priority": 10,
|
||||||
|
"escape_difficulty": 50,
|
||||||
|
"lockable": true,
|
||||||
|
"can_attach_padlock": true,
|
||||||
|
"supports_color": false,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"head"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "gag"
|
||||||
|
},
|
||||||
|
"gagging": {
|
||||||
|
"material": "sponge"
|
||||||
|
},
|
||||||
|
"adjustable": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Straitjacket",
|
||||||
|
"translation_key": "item.tiedup.straitjacket",
|
||||||
|
"model": "tiedup:models/gltf/v2/binds/straitjacket.glb",
|
||||||
|
"regions": [
|
||||||
|
"ARMS"
|
||||||
|
],
|
||||||
|
"pose_type": "STRAITJACKET",
|
||||||
|
"pose_priority": 30,
|
||||||
|
"escape_difficulty": 100,
|
||||||
|
"lockable": true,
|
||||||
|
"can_attach_padlock": true,
|
||||||
|
"supports_color": false,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
],
|
||||||
|
"struggle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "straitjacket"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/main/resources/assets/tiedup/tiedup_items/tape_gag.json
Normal file
28
src/main/resources/assets/tiedup/tiedup_items/tape_gag.json
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Tape Gag",
|
||||||
|
"translation_key": "item.tiedup.tape_gag",
|
||||||
|
"model": "tiedup:models/gltf/v2/gags/tape_gag.glb",
|
||||||
|
"regions": [
|
||||||
|
"MOUTH"
|
||||||
|
],
|
||||||
|
"pose_priority": 10,
|
||||||
|
"escape_difficulty": 50,
|
||||||
|
"lockable": false,
|
||||||
|
"can_attach_padlock": false,
|
||||||
|
"supports_color": true,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"head"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"resistance": {
|
||||||
|
"id": "gag"
|
||||||
|
},
|
||||||
|
"gagging": {
|
||||||
|
"material": "tape"
|
||||||
|
},
|
||||||
|
"adjustable": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"type": "tiedup:bondage_item",
|
|
||||||
"display_name": "Data-Driven Handcuffs",
|
|
||||||
"creator": "TiedUp! Team",
|
|
||||||
"model": "tiedup:models/gltf/v2/handcuffs/cuffs_prototype.glb",
|
|
||||||
"regions": ["ARMS"],
|
|
||||||
"pose_priority": 30,
|
|
||||||
"escape_difficulty": 100,
|
|
||||||
"lockable": true,
|
|
||||||
"icon": "tiedup:item/beam_cuffs",
|
|
||||||
"animation_bones": {
|
|
||||||
"idle": ["rightArm", "leftArm"],
|
|
||||||
"struggle": ["rightArm", "leftArm"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"type": "tiedup:bondage_item",
|
|
||||||
"display_name": "Prototype Leg Cuffs",
|
|
||||||
"model": "tiedup:models/gltf/leg_cuffs_proto.glb",
|
|
||||||
"regions": ["LEGS"],
|
|
||||||
"pose_priority": 30,
|
|
||||||
"escape_difficulty": 5,
|
|
||||||
"lockable": true,
|
|
||||||
"movement_style": "shuffle",
|
|
||||||
"supports_color": true,
|
|
||||||
"tint_channels": {
|
|
||||||
"tintable_1": "#808080"
|
|
||||||
},
|
|
||||||
"animation_bones": {
|
|
||||||
"idle": ["rightLeg", "leftLeg"],
|
|
||||||
"struggle": ["rightLeg", "leftLeg"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
29
src/main/resources/assets/tiedup/tiedup_items/tube_gag.json
Normal file
29
src/main/resources/assets/tiedup/tiedup_items/tube_gag.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Tube Gag",
|
||||||
|
"translation_key": "item.tiedup.tube_gag",
|
||||||
|
"model": "tiedup:models/gltf/v2/gags/tube_gag.glb",
|
||||||
|
"regions": [
|
||||||
|
"MOUTH"
|
||||||
|
],
|
||||||
|
"pose_priority": 10,
|
||||||
|
"escape_difficulty": 50,
|
||||||
|
"lockable": true,
|
||||||
|
"can_attach_padlock": true,
|
||||||
|
"supports_color": false,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"head"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "gag"
|
||||||
|
},
|
||||||
|
"gagging": {
|
||||||
|
"material": "ring"
|
||||||
|
},
|
||||||
|
"adjustable": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/main/resources/assets/tiedup/tiedup_items/vine_gag.json
Normal file
28
src/main/resources/assets/tiedup/tiedup_items/vine_gag.json
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Vine Gag",
|
||||||
|
"translation_key": "item.tiedup.vine_gag",
|
||||||
|
"model": "tiedup:models/gltf/v2/gags/vine_gag.glb",
|
||||||
|
"regions": [
|
||||||
|
"MOUTH"
|
||||||
|
],
|
||||||
|
"pose_priority": 10,
|
||||||
|
"escape_difficulty": 50,
|
||||||
|
"lockable": false,
|
||||||
|
"can_attach_padlock": false,
|
||||||
|
"supports_color": false,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"head"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"resistance": {
|
||||||
|
"id": "gag"
|
||||||
|
},
|
||||||
|
"gagging": {
|
||||||
|
"material": "stuffed"
|
||||||
|
},
|
||||||
|
"adjustable": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
30
src/main/resources/assets/tiedup/tiedup_items/vine_seed.json
Normal file
30
src/main/resources/assets/tiedup/tiedup_items/vine_seed.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Vine Bind",
|
||||||
|
"translation_key": "item.tiedup.vine_seed",
|
||||||
|
"model": "tiedup:models/gltf/v2/binds/vine_seed.glb",
|
||||||
|
"regions": [
|
||||||
|
"ARMS"
|
||||||
|
],
|
||||||
|
"pose_type": "STANDARD",
|
||||||
|
"pose_priority": 30,
|
||||||
|
"escape_difficulty": 100,
|
||||||
|
"lockable": false,
|
||||||
|
"can_attach_padlock": false,
|
||||||
|
"supports_color": false,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
],
|
||||||
|
"struggle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"resistance": {
|
||||||
|
"id": "vine"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
30
src/main/resources/assets/tiedup/tiedup_items/web_bind.json
Normal file
30
src/main/resources/assets/tiedup/tiedup_items/web_bind.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Web Bind",
|
||||||
|
"translation_key": "item.tiedup.web_bind",
|
||||||
|
"model": "tiedup:models/gltf/v2/binds/web_bind.glb",
|
||||||
|
"regions": [
|
||||||
|
"ARMS"
|
||||||
|
],
|
||||||
|
"pose_type": "STANDARD",
|
||||||
|
"pose_priority": 30,
|
||||||
|
"escape_difficulty": 100,
|
||||||
|
"lockable": false,
|
||||||
|
"can_attach_padlock": false,
|
||||||
|
"supports_color": false,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
],
|
||||||
|
"struggle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"resistance": {
|
||||||
|
"id": "web"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/main/resources/assets/tiedup/tiedup_items/web_gag.json
Normal file
28
src/main/resources/assets/tiedup/tiedup_items/web_gag.json
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Web Gag",
|
||||||
|
"translation_key": "item.tiedup.web_gag",
|
||||||
|
"model": "tiedup:models/gltf/v2/gags/web_gag.glb",
|
||||||
|
"regions": [
|
||||||
|
"MOUTH"
|
||||||
|
],
|
||||||
|
"pose_priority": 10,
|
||||||
|
"escape_difficulty": 50,
|
||||||
|
"lockable": false,
|
||||||
|
"can_attach_padlock": false,
|
||||||
|
"supports_color": false,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"head"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"resistance": {
|
||||||
|
"id": "gag"
|
||||||
|
},
|
||||||
|
"gagging": {
|
||||||
|
"material": "stuffed"
|
||||||
|
},
|
||||||
|
"adjustable": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/main/resources/assets/tiedup/tiedup_items/wrap.json
Normal file
31
src/main/resources/assets/tiedup/tiedup_items/wrap.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Wrap",
|
||||||
|
"translation_key": "item.tiedup.wrap",
|
||||||
|
"model": "tiedup:models/gltf/v2/binds/wrap.glb",
|
||||||
|
"regions": [
|
||||||
|
"ARMS"
|
||||||
|
],
|
||||||
|
"pose_type": "WRAP",
|
||||||
|
"pose_priority": 30,
|
||||||
|
"escape_difficulty": 100,
|
||||||
|
"lockable": true,
|
||||||
|
"can_attach_padlock": true,
|
||||||
|
"supports_color": false,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
],
|
||||||
|
"struggle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "wrap"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/main/resources/assets/tiedup/tiedup_items/wrap_gag.json
Normal file
29
src/main/resources/assets/tiedup/tiedup_items/wrap_gag.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Wrap Gag",
|
||||||
|
"translation_key": "item.tiedup.wrap_gag",
|
||||||
|
"model": "tiedup:models/gltf/v2/gags/wrap_gag.glb",
|
||||||
|
"regions": [
|
||||||
|
"MOUTH"
|
||||||
|
],
|
||||||
|
"pose_priority": 10,
|
||||||
|
"escape_difficulty": 50,
|
||||||
|
"lockable": true,
|
||||||
|
"can_attach_padlock": true,
|
||||||
|
"supports_color": false,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"head"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "gag"
|
||||||
|
},
|
||||||
|
"gagging": {
|
||||||
|
"material": "stuffed"
|
||||||
|
},
|
||||||
|
"adjustable": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/main/resources/data/tiedup/tiedup_items/armbinder.json
Normal file
31
src/main/resources/data/tiedup/tiedup_items/armbinder.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Armbinder",
|
||||||
|
"translation_key": "item.tiedup.armbinder",
|
||||||
|
"model": "tiedup:models/gltf/v2/binds/armbinder.glb",
|
||||||
|
"regions": [
|
||||||
|
"ARMS"
|
||||||
|
],
|
||||||
|
"pose_type": "STANDARD",
|
||||||
|
"pose_priority": 30,
|
||||||
|
"escape_difficulty": 100,
|
||||||
|
"lockable": true,
|
||||||
|
"can_attach_padlock": true,
|
||||||
|
"supports_color": false,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
],
|
||||||
|
"struggle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "armbinder"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Baguette Gag",
|
||||||
|
"translation_key": "item.tiedup.baguette_gag",
|
||||||
|
"model": "tiedup:models/gltf/v2/gags/baguette_gag.glb",
|
||||||
|
"regions": [
|
||||||
|
"MOUTH"
|
||||||
|
],
|
||||||
|
"pose_priority": 10,
|
||||||
|
"escape_difficulty": 50,
|
||||||
|
"lockable": true,
|
||||||
|
"can_attach_padlock": true,
|
||||||
|
"supports_color": false,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"head"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "gag"
|
||||||
|
},
|
||||||
|
"gagging": {
|
||||||
|
"material": "baguette"
|
||||||
|
},
|
||||||
|
"adjustable": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/main/resources/data/tiedup/tiedup_items/ball_gag.json
Normal file
29
src/main/resources/data/tiedup/tiedup_items/ball_gag.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Ball Gag",
|
||||||
|
"translation_key": "item.tiedup.ball_gag",
|
||||||
|
"model": "tiedup:models/gltf/v2/gags/ball_gag.glb",
|
||||||
|
"regions": [
|
||||||
|
"MOUTH"
|
||||||
|
],
|
||||||
|
"pose_priority": 10,
|
||||||
|
"escape_difficulty": 50,
|
||||||
|
"lockable": true,
|
||||||
|
"can_attach_padlock": true,
|
||||||
|
"supports_color": true,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"head"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "gag"
|
||||||
|
},
|
||||||
|
"gagging": {
|
||||||
|
"material": "ball"
|
||||||
|
},
|
||||||
|
"adjustable": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/main/resources/data/tiedup/tiedup_items/ball_gag_3d.json
Normal file
28
src/main/resources/data/tiedup/tiedup_items/ball_gag_3d.json
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Ball Gag 3D",
|
||||||
|
"translation_key": "item.tiedup.ball_gag_3d",
|
||||||
|
"model": "tiedup:models/gltf/v2/combos/ball_gag_3d.glb",
|
||||||
|
"regions": [
|
||||||
|
"MOUTH"
|
||||||
|
],
|
||||||
|
"pose_priority": 10,
|
||||||
|
"escape_difficulty": 50,
|
||||||
|
"lockable": true,
|
||||||
|
"supports_color": true,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"head"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "gag"
|
||||||
|
},
|
||||||
|
"gagging": {
|
||||||
|
"material": "ball"
|
||||||
|
},
|
||||||
|
"adjustable": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Ball Gag Strap",
|
||||||
|
"translation_key": "item.tiedup.ball_gag_strap",
|
||||||
|
"model": "tiedup:models/gltf/v2/gags/ball_gag_strap.glb",
|
||||||
|
"regions": [
|
||||||
|
"MOUTH"
|
||||||
|
],
|
||||||
|
"pose_priority": 10,
|
||||||
|
"escape_difficulty": 50,
|
||||||
|
"lockable": true,
|
||||||
|
"can_attach_padlock": true,
|
||||||
|
"supports_color": true,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"head"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "gag"
|
||||||
|
},
|
||||||
|
"gagging": {
|
||||||
|
"material": "ball"
|
||||||
|
},
|
||||||
|
"adjustable": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/main/resources/data/tiedup/tiedup_items/beam_cuffs.json
Normal file
31
src/main/resources/data/tiedup/tiedup_items/beam_cuffs.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Beam Cuffs",
|
||||||
|
"translation_key": "item.tiedup.beam_cuffs",
|
||||||
|
"model": "tiedup:models/gltf/v2/binds/beam_cuffs.glb",
|
||||||
|
"regions": [
|
||||||
|
"ARMS"
|
||||||
|
],
|
||||||
|
"pose_type": "STANDARD",
|
||||||
|
"pose_priority": 30,
|
||||||
|
"escape_difficulty": 100,
|
||||||
|
"lockable": true,
|
||||||
|
"can_attach_padlock": true,
|
||||||
|
"supports_color": false,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
],
|
||||||
|
"struggle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "chain"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Beam Panel Gag",
|
||||||
|
"translation_key": "item.tiedup.beam_panel_gag",
|
||||||
|
"model": "tiedup:models/gltf/v2/gags/beam_panel_gag.glb",
|
||||||
|
"regions": [
|
||||||
|
"MOUTH"
|
||||||
|
],
|
||||||
|
"pose_priority": 10,
|
||||||
|
"escape_difficulty": 50,
|
||||||
|
"lockable": true,
|
||||||
|
"can_attach_padlock": true,
|
||||||
|
"supports_color": false,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"head"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "gag"
|
||||||
|
},
|
||||||
|
"gagging": {
|
||||||
|
"material": "panel"
|
||||||
|
},
|
||||||
|
"adjustable": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/main/resources/data/tiedup/tiedup_items/bite_gag.json
Normal file
29
src/main/resources/data/tiedup/tiedup_items/bite_gag.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Bite Gag",
|
||||||
|
"translation_key": "item.tiedup.bite_gag",
|
||||||
|
"model": "tiedup:models/gltf/v2/gags/bite_gag.glb",
|
||||||
|
"regions": [
|
||||||
|
"MOUTH"
|
||||||
|
],
|
||||||
|
"pose_priority": 10,
|
||||||
|
"escape_difficulty": 50,
|
||||||
|
"lockable": true,
|
||||||
|
"can_attach_padlock": true,
|
||||||
|
"supports_color": false,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"head"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "gag"
|
||||||
|
},
|
||||||
|
"gagging": {
|
||||||
|
"material": "bite"
|
||||||
|
},
|
||||||
|
"adjustable": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Blindfold Mask",
|
||||||
|
"translation_key": "item.tiedup.blindfold_mask",
|
||||||
|
"model": "tiedup:models/gltf/v2/blindfolds/blindfold_mask.glb",
|
||||||
|
"regions": [
|
||||||
|
"EYES"
|
||||||
|
],
|
||||||
|
"pose_priority": 5,
|
||||||
|
"escape_difficulty": 30,
|
||||||
|
"lockable": true,
|
||||||
|
"supports_color": true,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"head"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "blindfold"
|
||||||
|
},
|
||||||
|
"blinding": {},
|
||||||
|
"adjustable": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/main/resources/data/tiedup/tiedup_items/chain.json
Normal file
31
src/main/resources/data/tiedup/tiedup_items/chain.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Chains",
|
||||||
|
"translation_key": "item.tiedup.chain",
|
||||||
|
"model": "tiedup:models/gltf/v2/binds/chain.glb",
|
||||||
|
"regions": [
|
||||||
|
"ARMS"
|
||||||
|
],
|
||||||
|
"pose_type": "STANDARD",
|
||||||
|
"pose_priority": 30,
|
||||||
|
"escape_difficulty": 100,
|
||||||
|
"lockable": true,
|
||||||
|
"can_attach_padlock": true,
|
||||||
|
"supports_color": false,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
],
|
||||||
|
"struggle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "chain"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Chain Panel Gag",
|
||||||
|
"translation_key": "item.tiedup.chain_panel_gag",
|
||||||
|
"model": "tiedup:models/gltf/v2/gags/chain_panel_gag.glb",
|
||||||
|
"regions": [
|
||||||
|
"MOUTH"
|
||||||
|
],
|
||||||
|
"pose_priority": 10,
|
||||||
|
"escape_difficulty": 50,
|
||||||
|
"lockable": true,
|
||||||
|
"can_attach_padlock": true,
|
||||||
|
"supports_color": false,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"head"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "gag"
|
||||||
|
},
|
||||||
|
"gagging": {
|
||||||
|
"material": "panel"
|
||||||
|
},
|
||||||
|
"adjustable": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"regions": [
|
||||||
|
"NECK"
|
||||||
|
],
|
||||||
|
"pose_priority": 5,
|
||||||
|
"escape_difficulty": 80,
|
||||||
|
"lockable": true,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"body"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"display_name": "Choke Collar",
|
||||||
|
"translation_key": "item.tiedup.choke_collar",
|
||||||
|
"model": "tiedup:models/gltf/v2/collars/choke_collar.glb",
|
||||||
|
"components": {
|
||||||
|
"ownership": {},
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "collar"
|
||||||
|
},
|
||||||
|
"choking": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Blindfold",
|
||||||
|
"translation_key": "item.tiedup.classic_blindfold",
|
||||||
|
"model": "tiedup:models/gltf/v2/blindfolds/classic_blindfold.glb",
|
||||||
|
"regions": [
|
||||||
|
"EYES"
|
||||||
|
],
|
||||||
|
"pose_priority": 5,
|
||||||
|
"escape_difficulty": 30,
|
||||||
|
"lockable": true,
|
||||||
|
"supports_color": true,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"head"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "blindfold"
|
||||||
|
},
|
||||||
|
"blinding": {},
|
||||||
|
"adjustable": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"regions": [
|
||||||
|
"NECK"
|
||||||
|
],
|
||||||
|
"pose_priority": 5,
|
||||||
|
"escape_difficulty": 80,
|
||||||
|
"lockable": true,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"body"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"display_name": "Classic Collar",
|
||||||
|
"translation_key": "item.tiedup.classic_collar",
|
||||||
|
"model": "tiedup:models/gltf/v2/collars/classic_collar.glb",
|
||||||
|
"components": {
|
||||||
|
"ownership": {},
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "collar"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Earplugs",
|
||||||
|
"translation_key": "item.tiedup.classic_earplugs",
|
||||||
|
"model": "tiedup:models/gltf/v2/earplugs/classic_earplugs.glb",
|
||||||
|
"regions": [
|
||||||
|
"EARS"
|
||||||
|
],
|
||||||
|
"pose_priority": 5,
|
||||||
|
"escape_difficulty": 20,
|
||||||
|
"lockable": true,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"head"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "blindfold"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/main/resources/data/tiedup/tiedup_items/cleave_gag.json
Normal file
29
src/main/resources/data/tiedup/tiedup_items/cleave_gag.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Cleave Gag",
|
||||||
|
"translation_key": "item.tiedup.cleave_gag",
|
||||||
|
"model": "tiedup:models/gltf/v2/gags/cleave_gag.glb",
|
||||||
|
"regions": [
|
||||||
|
"MOUTH"
|
||||||
|
],
|
||||||
|
"pose_priority": 10,
|
||||||
|
"escape_difficulty": 50,
|
||||||
|
"lockable": true,
|
||||||
|
"can_attach_padlock": true,
|
||||||
|
"supports_color": true,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"head"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "gag"
|
||||||
|
},
|
||||||
|
"gagging": {
|
||||||
|
"material": "cloth"
|
||||||
|
},
|
||||||
|
"adjustable": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/main/resources/data/tiedup/tiedup_items/cloth_gag.json
Normal file
29
src/main/resources/data/tiedup/tiedup_items/cloth_gag.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Cloth Gag",
|
||||||
|
"translation_key": "item.tiedup.cloth_gag",
|
||||||
|
"model": "tiedup:models/gltf/v2/gags/cloth_gag.glb",
|
||||||
|
"regions": [
|
||||||
|
"MOUTH"
|
||||||
|
],
|
||||||
|
"pose_priority": 10,
|
||||||
|
"escape_difficulty": 50,
|
||||||
|
"lockable": true,
|
||||||
|
"can_attach_padlock": true,
|
||||||
|
"supports_color": true,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"head"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "gag"
|
||||||
|
},
|
||||||
|
"gagging": {
|
||||||
|
"material": "cloth"
|
||||||
|
},
|
||||||
|
"adjustable": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
32
src/main/resources/data/tiedup/tiedup_items/dogbinder.json
Normal file
32
src/main/resources/data/tiedup/tiedup_items/dogbinder.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Dogbinder",
|
||||||
|
"translation_key": "item.tiedup.dogbinder",
|
||||||
|
"model": "tiedup:models/gltf/v2/binds/dogbinder.glb",
|
||||||
|
"regions": [
|
||||||
|
"ARMS"
|
||||||
|
],
|
||||||
|
"pose_type": "DOG",
|
||||||
|
"pose_priority": 30,
|
||||||
|
"escape_difficulty": 100,
|
||||||
|
"lockable": true,
|
||||||
|
"can_attach_padlock": true,
|
||||||
|
"supports_color": false,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
],
|
||||||
|
"struggle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "armbinder"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"movement_style": "CRAWL"
|
||||||
|
}
|
||||||
30
src/main/resources/data/tiedup/tiedup_items/duct_tape.json
Normal file
30
src/main/resources/data/tiedup/tiedup_items/duct_tape.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Duct Tape",
|
||||||
|
"translation_key": "item.tiedup.duct_tape",
|
||||||
|
"model": "tiedup:models/gltf/v2/binds/duct_tape.glb",
|
||||||
|
"regions": [
|
||||||
|
"ARMS"
|
||||||
|
],
|
||||||
|
"pose_type": "STANDARD",
|
||||||
|
"pose_priority": 30,
|
||||||
|
"escape_difficulty": 100,
|
||||||
|
"lockable": false,
|
||||||
|
"can_attach_padlock": false,
|
||||||
|
"supports_color": true,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
],
|
||||||
|
"struggle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"resistance": {
|
||||||
|
"id": "tape"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/main/resources/data/tiedup/tiedup_items/gps_collar.json
Normal file
27
src/main/resources/data/tiedup/tiedup_items/gps_collar.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"regions": [
|
||||||
|
"NECK"
|
||||||
|
],
|
||||||
|
"pose_priority": 5,
|
||||||
|
"escape_difficulty": 80,
|
||||||
|
"lockable": true,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"body"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"display_name": "GPS Collar",
|
||||||
|
"translation_key": "item.tiedup.gps_collar",
|
||||||
|
"model": "tiedup:models/gltf/v2/collars/gps_collar.glb",
|
||||||
|
"components": {
|
||||||
|
"ownership": {},
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "collar"
|
||||||
|
},
|
||||||
|
"gps": {
|
||||||
|
"safe_zone_radius": 50
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
32
src/main/resources/data/tiedup/tiedup_items/hood.json
Normal file
32
src/main/resources/data/tiedup/tiedup_items/hood.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Hood",
|
||||||
|
"translation_key": "item.tiedup.hood",
|
||||||
|
"model": "tiedup:models/gltf/v2/combos/hood.glb",
|
||||||
|
"regions": [
|
||||||
|
"EYES"
|
||||||
|
],
|
||||||
|
"blocked_regions": [
|
||||||
|
"EYES",
|
||||||
|
"EARS",
|
||||||
|
"MOUTH"
|
||||||
|
],
|
||||||
|
"pose_priority": 10,
|
||||||
|
"escape_difficulty": 40,
|
||||||
|
"lockable": true,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"head"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "blindfold"
|
||||||
|
},
|
||||||
|
"blinding": {},
|
||||||
|
"gagging": {
|
||||||
|
"material": "stuffed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/main/resources/data/tiedup/tiedup_items/latex_gag.json
Normal file
29
src/main/resources/data/tiedup/tiedup_items/latex_gag.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Latex Gag",
|
||||||
|
"translation_key": "item.tiedup.latex_gag",
|
||||||
|
"model": "tiedup:models/gltf/v2/gags/latex_gag.glb",
|
||||||
|
"regions": [
|
||||||
|
"MOUTH"
|
||||||
|
],
|
||||||
|
"pose_priority": 10,
|
||||||
|
"escape_difficulty": 50,
|
||||||
|
"lockable": true,
|
||||||
|
"can_attach_padlock": true,
|
||||||
|
"supports_color": false,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"head"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "gag"
|
||||||
|
},
|
||||||
|
"gagging": {
|
||||||
|
"material": "latex"
|
||||||
|
},
|
||||||
|
"adjustable": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/main/resources/data/tiedup/tiedup_items/latex_sack.json
Normal file
31
src/main/resources/data/tiedup/tiedup_items/latex_sack.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Latex Sack",
|
||||||
|
"translation_key": "item.tiedup.latex_sack",
|
||||||
|
"model": "tiedup:models/gltf/v2/binds/latex_sack.glb",
|
||||||
|
"regions": [
|
||||||
|
"ARMS"
|
||||||
|
],
|
||||||
|
"pose_type": "LATEX_SACK",
|
||||||
|
"pose_priority": 30,
|
||||||
|
"escape_difficulty": 100,
|
||||||
|
"lockable": true,
|
||||||
|
"can_attach_padlock": true,
|
||||||
|
"supports_color": false,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
],
|
||||||
|
"struggle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "latex_sack"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Leather Mittens",
|
||||||
|
"translation_key": "item.tiedup.leather_mittens",
|
||||||
|
"model": "tiedup:models/gltf/v2/mittens/leather_mittens.glb",
|
||||||
|
"regions": [
|
||||||
|
"HANDS"
|
||||||
|
],
|
||||||
|
"pose_priority": 5,
|
||||||
|
"escape_difficulty": 20,
|
||||||
|
"lockable": true,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"type": "tiedup:bondage_item",
|
||||||
|
"display_name": "Leather Straps",
|
||||||
|
"translation_key": "item.tiedup.leather_straps",
|
||||||
|
"model": "tiedup:models/gltf/v2/binds/leather_straps.glb",
|
||||||
|
"regions": [
|
||||||
|
"ARMS"
|
||||||
|
],
|
||||||
|
"pose_type": "STANDARD",
|
||||||
|
"pose_priority": 30,
|
||||||
|
"escape_difficulty": 100,
|
||||||
|
"lockable": true,
|
||||||
|
"can_attach_padlock": true,
|
||||||
|
"supports_color": false,
|
||||||
|
"animation_bones": {
|
||||||
|
"idle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
],
|
||||||
|
"struggle": [
|
||||||
|
"rightArm",
|
||||||
|
"leftArm"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"lockable": {},
|
||||||
|
"resistance": {
|
||||||
|
"id": "armbinder"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user